diff --git a/.gitignore b/.gitignore
index f6ec423..0ef9858 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,4 +3,6 @@ settings.php
nbproject/private
*.sync-conflict*
*.bak
-GeoLite2-City.mmdb
\ No newline at end of file
+GeoLite2-City.mmdb
+public/files/*
+!public/files/.htaccess
\ No newline at end of file
diff --git a/action.php b/action.php
index 36788f4..16e6337 100644
--- a/action.php
+++ b/action.php
@@ -30,6 +30,12 @@ function returnToSender($msg, $arg = "") {
die();
}
+// https://andrewcurioso.com/blog/archive/2010/detecting-file-size-overflow-in-php.html
+if ($_SERVER['REQUEST_METHOD'] == 'POST' && empty($_POST) &&
+ empty($_FILES) && $_SERVER['CONTENT_LENGTH'] > 0) {
+ returnToSender("upload_too_big");
+}
+
switch ($VARS['action']) {
case "newpage":
if (is_empty($VARS['siteid']) || !$database->has("sites", ["siteid" => $VARS['siteid']])) {
@@ -187,6 +193,75 @@ switch ($VARS['action']) {
$database->delete('messages', ["mid" => $VARS['id']]);
returnToSender("message_deleted");
break;
+ case "fileupload":
+ $destpath = FILE_UPLOAD_PATH . $VARS['path'];
+ if (strpos(realpath($destpath), FILE_UPLOAD_PATH) !== 0) {
+ returnToSender("file_security_error");
+ }
+ if (!file_exists($destpath) || !is_dir($destpath)) {
+ returnToSender("missing_folder");
+ }
+ if (!is_writable($destpath)) {
+ returnToSender("unwritable_folder");
+ }
+
+ $files = [];
+ foreach ($_FILES['files'] as $key => $all) {
+ foreach ($all as $i => $val) {
+ $files[$i][$key] = $val;
+ }
+ }
+
+ $errors = [];
+ foreach ($files as $f) {
+ if ($f['error'] !== UPLOAD_ERR_OK) {
+ $err = "could not be uploaded.";
+ switch ($f['error']) {
+ case UPLOAD_ERR_INI_SIZE:
+ case UPLOAD_ERR_FORM_SIZE:
+ $err = "is too big.";
+ break;
+ case UPLOAD_ERR_CANT_WRITE:
+ $err = "could not be saved to disk.";
+ break;
+ case UPLOAD_ERR_NO_FILE:
+ $err = "was not actually sent.";
+ break;
+ case UPLOAD_ERR_PARTIAL:
+ $err = "was only partially sent.";
+ break;
+ default:
+ $err = "could not be uploaded.";
+ }
+ $errors[] = htmlspecialchars($f['name']) . " $err";
+ continue;
+ }
+
+ $filename = basename($f['name']);
+ $filename = preg_replace("/[^a-z0-9\._\-]/", "_", strtolower($filename));
+ $n = 1;
+ if (file_exists($destpath . "/" . $filename)) {
+ while (file_exists($destpath . '/' . $n . '_' . $filename)) {
+ $n++;
+ }
+ $filename = $n . '_' . $filename;
+ }
+
+ $finalpath = $destpath . "/" . $filename;
+
+ if (move_uploaded_file($f['tmp_name'], $finalpath)) {
+
+ } else {
+ $errors[] = htmlspecialchars($f['name']) . " could not be uploaded.";
+ }
+ }
+
+ if (count($errors) > 0) {
+ returnToSender("upload_warning", implode("
", $errors) . "&path=" . $VARS['path']);
+ }
+
+ returnToSender("upload_success", "&path=" . $VARS['path']);
+ break;
case "signout":
session_destroy();
header('Location: index.php');
diff --git a/lang/en_us.php b/lang/en_us.php
index 85adebf..491bd84 100644
--- a/lang/en_us.php
+++ b/lang/en_us.php
@@ -100,4 +100,13 @@ define("STRINGS", [
"message" => "Message",
"date" => "Date",
"message deleted" => "Message deleted.",
+ "files" => "Files",
+ "browse" => "Browse",
+ "upload" => "Upload",
+ "operation cancelled for security reasons" => "Operation cancelled for security reasons.",
+ "upload successful" => "Upload successful.",
+ "upload warning" => "Upload finished with some problems:
{arg}",
+ "destination folder does not exist" => "Destination folder does not exist.",
+ "destination folder does not allow uploads" => "Destination folder does not allow uploads.",
+ "uploaded data too large" => "Uploaded data too large.",
]);
\ No newline at end of file
diff --git a/lang/messages.php b/lang/messages.php
index 01a574f..7e8a789 100644
--- a/lang/messages.php
+++ b/lang/messages.php
@@ -33,4 +33,28 @@ define("MESSAGES", [
"string" => "message deleted",
"type" => "success"
],
+ "file_security_error" => [
+ "string" => "operation cancelled for security reasons",
+ "type" => "danger"
+ ],
+ "upload_success" => [
+ "string" => "upload successful",
+ "type" => "success"
+ ],
+ "upload_warning" => [
+ "string" => "upload warning",
+ "type" => "warning"
+ ],
+ "missing_folder" => [
+ "string" => "destination folder does not exist",
+ "type" => "danger"
+ ],
+ "unwritable_folder" => [
+ "string" => "destination folder does not allow uploads",
+ "type" => "danger"
+ ],
+ "upload_too_big" => [
+ "string" => "uploaded data too large",
+ "type" => "danger"
+ ],
]);
diff --git a/lib/mimetypes.php b/lib/mimetypes.php
new file mode 100644
index 0000000..04bf2c5
--- /dev/null
+++ b/lib/mimetypes.php
@@ -0,0 +1,1108 @@
+ "fas fa-align-left",
+ "application/octet-stream" => "fas fa-file",
+ // Text/Code
+ "text/html" => "fab fa-html5",
+ "text/css" => "fab fa-css3",
+ "text/csv" => "fas fa-table",
+ "application/ecmascript" => "fas fa-code",
+ "text/calendar" => "fas fa-calendar",
+ "application/javascript" => "fab fa-js",
+ "application/json" => "fas fa-list",
+ "application/x-sh" => "fas fa-terminal",
+ "application/typescript" => "fas fa-code",
+ "application/xhtml+xml" => "fas fa-code",
+ "application/xml" => "fas fa-code",
+ "text/less" => "fab fa-less",
+ "text/sass" => "fab fa-sass",
+ "text/markdown" => "fas fa-align-left",
+ "text/checksum" => "fas fa-file-medical-alt",
+ "text/config" => "fas fa-list",
+ "text/other" => "fas fa-file-alt",
+ // Archives and disk images
+ "application/x-iso9660-image" => "fas fa-hdd",
+ "application/x-gzip" => "fas fa-file-archive",
+ "application/zip" => "fas fa-file-archive",
+ "application/x-bzip" => "fas fa-file-archive",
+ "application/x-bzip2" => "fas fa-file-archive",
+ "application/x-7z-compressed" => "fas fa-file-archive",
+ "application/x-msi" => "fab fa-windows",
+ "application/x-msdownload" => "fab fa-windows",
+ "application/java-archive" => "fas fa-coffee",
+ "application/x-rar-compressed" => "fas fa-file-archive",
+ "application/x-tar" => "fas fa-file-archive",
+ "application/x-apple-diskimage" => "fab fa-apple",
+ "application/imgfile" => "fas fa-hdd",
+ // Fonts
+ "application/vnd.ms-fontobject" => "fas fa-font",
+ "application/x-font-ttf" => "fas fa-font",
+ "font/otf" => "fas fa-font",
+ "font/ttf" => "fas fa-font",
+ "font/woff" => "fas fa-font",
+ "font/woff2" => "fas fa-font",
+ "font/other" => "fas fa-font",
+ // Images
+ "image/svg+xml" => "fas fa-file-image",
+ "image/png" => "fas fa-image",
+ "image/jpeg" => "fas fa-image",
+ "image/gif" => "fas fa-image",
+ "image/bmp" => "fas fa-image",
+ "image/x-windows-bmp" => "fas fa-image",
+ "image/x-icon" => "fas fa-image",
+ "image/tiff" => "fas fa-image",
+ "image/webp" => "fas fa-image",
+ "image/other" => "fas fa-image",
+ // Audio
+ "audio/acc" => "fas fa-file-audio",
+ "audio/ogg" => "fas fa-file-audio",
+ "audio/x-wav" => "fas fa-file-audio",
+ "audio/webm" => "fas fa-file-audio",
+ "audio/midi" => "fas fa-music",
+ "audio/3gpp" => "fas fa-file-audio",
+ "audio/3gpp2" => "fas fa-file-audio",
+ "audio/other" => "fas fa-file-audio",
+ // Video
+ "application/x-shockwave-flash" => "fas fa-video-slash",
+ "video/mpeg" => "fas fa-file-video",
+ "video/ogg" => "fas fa-file-video",
+ "video/webm" => "fas fa-file-video",
+ "video/3gpp" => "fas fa-file-video",
+ "video/3gpp2" => "fas fa-file-video",
+ "video/other" => "fas fa-file-video",
+ // Office files
+ "application/x-abiword" => "fas fa-file-word",
+ "application/msword" => "fas fa-file-word",
+ "application/vnd.ms-powerpoint" => "fas fa-file-powerpoint",
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document" => "fas fa-file-word",
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation" => "fas fa-file-powerpoint",
+ "application/vnd.oasis.opendocument.presentation" => "fas fa-file-powerpoint",
+ "application/vnd.oasis.opendocument.spreadsheet" => "fas fa-file-excel",
+ "application/vnd.oasis.opendocument.text" => "fas fa-file-word",
+ "application/pdf" => "fas fa-file-pdf",
+ "application/rtf" => "fas fa-file",
+ // Other
+ "application/vnd.amazon.ebook" => "fas fa-book",
+ "application/epub+zip" => "fas fa-book",
+ "application/ogg" => "fas fa-play",
+ "application/x-sql" => "fas fa-database",
+ "application/odb" => "fas fa-database",
+];
+// File extension to mimetype conversion
+$EXT2MIME = [
+ "less" => "text/less",
+ "scss" => "text/sass",
+ "md" => "text/markdown",
+ "md5" => "text/checksum",
+ "md5sum" => "text/checksum",
+ "md5sums" => "text/checksum",
+ "sha1" => "text/checksum",
+ "sha1sum" => "text/checksum",
+ "sha1sums" => "text/checksum",
+ "sha256" => "text/checksum",
+ "sha256sum" => "text/checksum",
+ "sha256sums" => "text/checksum",
+ "sha512" => "text/checksum",
+ "sha512sum" => "text/checksum",
+ "sha512sums" => "text/checksum",
+ "gz" => "application/x-gzip",
+ "img" => "application/imgfile",
+ "ini" => "text/config",
+ "yml" => "text/config",
+ // From Apache: http://svn.apache.org/viewvc?view=revision&revision=1810122
+ "ez" => "application/andrew-inset",
+ "aw" => "application/applixware",
+ "atom" => "application/atom+xml",
+ "atomcat" => "application/atomcat+xml",
+ "atomsvc" => "application/atomsvc+xml",
+ "ccxml" => "application/ccxml+xml",
+ "cdmia" => "application/cdmi-capability",
+ "cdmic" => "application/cdmi-container",
+ "cdmid" => "application/cdmi-domain",
+ "cdmio" => "application/cdmi-object",
+ "cdmiq" => "application/cdmi-queue",
+ "cu" => "application/cu-seeme",
+ "davmount" => "application/davmount+xml",
+ "dbk" => "application/docbook+xml",
+ "dssc" => "application/dssc+der",
+ "xdssc" => "application/dssc+xml",
+ "ecma" => "application/ecmascript",
+ "emma" => "application/emma+xml",
+ "epub" => "application/epub+zip",
+ "exi" => "application/exi",
+ "pfr" => "application/font-tdpfr",
+ "gml" => "application/gml+xml",
+ "gpx" => "application/gpx+xml",
+ "gxf" => "application/gxf",
+ "stk" => "application/hyperstudio",
+ "ink" => "application/inkml+xml",
+ "inkml" => "application/inkml+xml",
+ "ipfix" => "application/ipfix",
+ "jar" => "application/java-archive",
+ "ser" => "application/java-serialized-object",
+ "class" => "application/java-vm",
+ "js" => "application/javascript",
+ "json" => "application/json",
+ "jsonml" => "application/jsonml+json",
+ "lostxml" => "application/lost+xml",
+ "hqx" => "application/mac-binhex40",
+ "cpt" => "application/mac-compactpro",
+ "mads" => "application/mads+xml",
+ "mrc" => "application/marc",
+ "mrcx" => "application/marcxml+xml",
+ "ma" => "application/mathematica",
+ "nb" => "application/mathematica",
+ "mb" => "application/mathematica",
+ "mathml" => "application/mathml+xml",
+ "mbox" => "application/mbox",
+ "mscml" => "application/mediaservercontrol+xml",
+ "metalink" => "application/metalink+xml",
+ "meta4" => "application/metalink4+xml",
+ "mets" => "application/mets+xml",
+ "mods" => "application/mods+xml",
+ "m21" => "application/mp21",
+ "mp21" => "application/mp21",
+ "mp4s" => "application/mp4",
+ "doc" => "application/msword",
+ "dot" => "application/msword",
+ "mxf" => "application/mxf",
+ "bin" => "application/octet-stream",
+ "dms" => "application/octet-stream",
+ "lrf" => "application/octet-stream",
+ "mar" => "application/octet-stream",
+ "so" => "application/octet-stream",
+ "dist" => "application/octet-stream",
+ "distz" => "application/octet-stream",
+ "pkg" => "application/octet-stream",
+ "bpk" => "application/octet-stream",
+ "dump" => "application/octet-stream",
+ "elc" => "application/octet-stream",
+ "deploy" => "application/octet-stream",
+ "oda" => "application/oda",
+ "opf" => "application/oebps-package+xml",
+ "ogx" => "application/ogg",
+ "omdoc" => "application/omdoc+xml",
+ "onetoc" => "application/onenote",
+ "onetoc2" => "application/onenote",
+ "onetmp" => "application/onenote",
+ "onepkg" => "application/onenote",
+ "oxps" => "application/oxps",
+ "xer" => "application/patch-ops-error+xml",
+ "pdf" => "application/pdf",
+ "pgp" => "application/pgp-encrypted",
+ "asc" => "application/pgp-signature",
+ "sig" => "application/pgp-signature",
+ "prf" => "application/pics-rules",
+ "p10" => "application/pkcs10",
+ "p7m" => "application/pkcs7-mime",
+ "p7c" => "application/pkcs7-mime",
+ "p7s" => "application/pkcs7-signature",
+ "p8" => "application/pkcs8",
+ "ac" => "application/pkix-attr-cert",
+ "cer" => "application/pkix-cert",
+ "crl" => "application/pkix-crl",
+ "pkipath" => "application/pkix-pkipath",
+ "pki" => "application/pkixcmp",
+ "pls" => "application/pls+xml",
+ "ai" => "application/postscript",
+ "eps" => "application/postscript",
+ "ps" => "application/postscript",
+ "cww" => "application/prs.cww",
+ "pskcxml" => "application/pskc+xml",
+ "rdf" => "application/rdf+xml",
+ "rif" => "application/reginfo+xml",
+ "rnc" => "application/relax-ng-compact-syntax",
+ "rl" => "application/resource-lists+xml",
+ "rld" => "application/resource-lists-diff+xml",
+ "rs" => "application/rls-services+xml",
+ "gbr" => "application/rpki-ghostbusters",
+ "mft" => "application/rpki-manifest",
+ "roa" => "application/rpki-roa",
+ "rsd" => "application/rsd+xml",
+ "rss" => "application/rss+xml",
+ "rtf" => "application/rtf",
+ "sbml" => "application/sbml+xml",
+ "scq" => "application/scvp-cv-request",
+ "scs" => "application/scvp-cv-response",
+ "spq" => "application/scvp-vp-request",
+ "spp" => "application/scvp-vp-response",
+ "sdp" => "application/sdp",
+ "setpay" => "application/set-payment-initiation",
+ "setreg" => "application/set-registration-initiation",
+ "shf" => "application/shf+xml",
+ "smi" => "application/smil+xml",
+ "smil" => "application/smil+xml",
+ "rq" => "application/sparql-query",
+ "srx" => "application/sparql-results+xml",
+ "gram" => "application/srgs",
+ "grxml" => "application/srgs+xml",
+ "sru" => "application/sru+xml",
+ "ssdl" => "application/ssdl+xml",
+ "ssml" => "application/ssml+xml",
+ "tei" => "application/tei+xml",
+ "teicorpus" => "application/tei+xml",
+ "tfi" => "application/thraud+xml",
+ "tsd" => "application/timestamped-data",
+ "plb" => "application/vnd.3gpp.pic-bw-large",
+ "psb" => "application/vnd.3gpp.pic-bw-small",
+ "pvb" => "application/vnd.3gpp.pic-bw-var",
+ "tcap" => "application/vnd.3gpp2.tcap",
+ "pwn" => "application/vnd.3m.post-it-notes",
+ "aso" => "application/vnd.accpac.simply.aso",
+ "imp" => "application/vnd.accpac.simply.imp",
+ "acu" => "application/vnd.acucobol",
+ "atc" => "application/vnd.acucorp",
+ "acutc" => "application/vnd.acucorp",
+ "air" => "application/vnd.adobe.air-application-installer-package+zip",
+ "fcdt" => "application/vnd.adobe.formscentral.fcdt",
+ "fxp" => "application/vnd.adobe.fxp",
+ "fxpl" => "application/vnd.adobe.fxp",
+ "xdp" => "application/vnd.adobe.xdp+xml",
+ "xfdf" => "application/vnd.adobe.xfdf",
+ "ahead" => "application/vnd.ahead.space",
+ "azf" => "application/vnd.airzip.filesecure.azf",
+ "azs" => "application/vnd.airzip.filesecure.azs",
+ "azw" => "application/vnd.amazon.ebook",
+ "acc" => "application/vnd.americandynamics.acc",
+ "ami" => "application/vnd.amiga.ami",
+ "apk" => "application/vnd.android.package-archive",
+ "cii" => "application/vnd.anser-web-certificate-issue-initiation",
+ "fti" => "application/vnd.anser-web-funds-transfer-initiation",
+ "atx" => "application/vnd.antix.game-component",
+ "mpkg" => "application/vnd.apple.installer+xml",
+ "m3u8" => "application/vnd.apple.mpegurl",
+ "swi" => "application/vnd.aristanetworks.swi",
+ "iota" => "application/vnd.astraea-software.iota",
+ "aep" => "application/vnd.audiograph",
+ "mpm" => "application/vnd.blueice.multipass",
+ "bmi" => "application/vnd.bmi",
+ "rep" => "application/vnd.businessobjects",
+ "cdxml" => "application/vnd.chemdraw+xml",
+ "mmd" => "application/vnd.chipnuts.karaoke-mmd",
+ "cdy" => "application/vnd.cinderella",
+ "cla" => "application/vnd.claymore",
+ "rp9" => "application/vnd.cloanto.rp9",
+ "c4g" => "application/vnd.clonk.c4group",
+ "c4d" => "application/vnd.clonk.c4group",
+ "c4f" => "application/vnd.clonk.c4group",
+ "c4p" => "application/vnd.clonk.c4group",
+ "c4u" => "application/vnd.clonk.c4group",
+ "c11amc" => "application/vnd.cluetrust.cartomobile-config",
+ "c11amz" => "application/vnd.cluetrust.cartomobile-config-pkg",
+ "csp" => "application/vnd.commonspace",
+ "cdbcmsg" => "application/vnd.contact.cmsg",
+ "cmc" => "application/vnd.cosmocaller",
+ "clkx" => "application/vnd.crick.clicker",
+ "clkk" => "application/vnd.crick.clicker.keyboard",
+ "clkp" => "application/vnd.crick.clicker.palette",
+ "clkt" => "application/vnd.crick.clicker.template",
+ "clkw" => "application/vnd.crick.clicker.wordbank",
+ "wbs" => "application/vnd.criticaltools.wbs+xml",
+ "pml" => "application/vnd.ctc-posml",
+ "ppd" => "application/vnd.cups-ppd",
+ "car" => "application/vnd.curl.car",
+ "pcurl" => "application/vnd.curl.pcurl",
+ "dart" => "application/vnd.dart",
+ "rdz" => "application/vnd.data-vision.rdz",
+ "uvf" => "application/vnd.dece.data",
+ "uvvf" => "application/vnd.dece.data",
+ "uvd" => "application/vnd.dece.data",
+ "uvvd" => "application/vnd.dece.data",
+ "uvt" => "application/vnd.dece.ttml+xml",
+ "uvvt" => "application/vnd.dece.ttml+xml",
+ "uvx" => "application/vnd.dece.unspecified",
+ "uvvx" => "application/vnd.dece.unspecified",
+ "uvz" => "application/vnd.dece.zip",
+ "uvvz" => "application/vnd.dece.zip",
+ "fe_launch" => "application/vnd.denovo.fcselayout-link",
+ "dna" => "application/vnd.dna",
+ "mlp" => "application/vnd.dolby.mlp",
+ "dpg" => "application/vnd.dpgraph",
+ "dfac" => "application/vnd.dreamfactory",
+ "kpxx" => "application/vnd.ds-keypoint",
+ "ait" => "application/vnd.dvb.ait",
+ "svc" => "application/vnd.dvb.service",
+ "geo" => "application/vnd.dynageo",
+ "mag" => "application/vnd.ecowin.chart",
+ "nml" => "application/vnd.enliven",
+ "esf" => "application/vnd.epson.esf",
+ "msf" => "application/vnd.epson.msf",
+ "qam" => "application/vnd.epson.quickanime",
+ "slt" => "application/vnd.epson.salt",
+ "ssf" => "application/vnd.epson.ssf",
+ "es3" => "application/vnd.eszigno3+xml",
+ "et3" => "application/vnd.eszigno3+xml",
+ "ez2" => "application/vnd.ezpix-album",
+ "ez3" => "application/vnd.ezpix-package",
+ "fdf" => "application/vnd.fdf",
+ "mseed" => "application/vnd.fdsn.mseed",
+ "seed" => "application/vnd.fdsn.seed",
+ "dataless" => "application/vnd.fdsn.seed",
+ "gph" => "application/vnd.flographit",
+ "ftc" => "application/vnd.fluxtime.clip",
+ "fm" => "application/vnd.framemaker",
+ "frame" => "application/vnd.framemaker",
+ "maker" => "application/vnd.framemaker",
+ "book" => "application/vnd.framemaker",
+ "fnc" => "application/vnd.frogans.fnc",
+ "ltf" => "application/vnd.frogans.ltf",
+ "fsc" => "application/vnd.fsc.weblaunch",
+ "oas" => "application/vnd.fujitsu.oasys",
+ "oa2" => "application/vnd.fujitsu.oasys2",
+ "oa3" => "application/vnd.fujitsu.oasys3",
+ "fg5" => "application/vnd.fujitsu.oasysgp",
+ "bh2" => "application/vnd.fujitsu.oasysprs",
+ "ddd" => "application/vnd.fujixerox.ddd",
+ "xdw" => "application/vnd.fujixerox.docuworks",
+ "xbd" => "application/vnd.fujixerox.docuworks.binder",
+ "fzs" => "application/vnd.fuzzysheet",
+ "txd" => "application/vnd.genomatix.tuxedo",
+ "ggb" => "application/vnd.geogebra.file",
+ "ggt" => "application/vnd.geogebra.tool",
+ "gex" => "application/vnd.geometry-explorer",
+ "gre" => "application/vnd.geometry-explorer",
+ "gxt" => "application/vnd.geonext",
+ "g2w" => "application/vnd.geoplan",
+ "g3w" => "application/vnd.geospace",
+ "gmx" => "application/vnd.gmx",
+ "kml" => "application/vnd.google-earth.kml+xml",
+ "kmz" => "application/vnd.google-earth.kmz",
+ "gqf" => "application/vnd.grafeq",
+ "gqs" => "application/vnd.grafeq",
+ "gac" => "application/vnd.groove-account",
+ "ghf" => "application/vnd.groove-help",
+ "gim" => "application/vnd.groove-identity-message",
+ "grv" => "application/vnd.groove-injector",
+ "gtm" => "application/vnd.groove-tool-message",
+ "tpl" => "application/vnd.groove-tool-template",
+ "vcg" => "application/vnd.groove-vcard",
+ "hal" => "application/vnd.hal+xml",
+ "zmm" => "application/vnd.handheld-entertainment+xml",
+ "hbci" => "application/vnd.hbci",
+ "les" => "application/vnd.hhe.lesson-player",
+ "hpgl" => "application/vnd.hp-hpgl",
+ "hpid" => "application/vnd.hp-hpid",
+ "hps" => "application/vnd.hp-hps",
+ "jlt" => "application/vnd.hp-jlyt",
+ "pcl" => "application/vnd.hp-pcl",
+ "pclxl" => "application/vnd.hp-pclxl",
+ "sfd-hdstx" => "application/vnd.hydrostatix.sof-data",
+ "mpy" => "application/vnd.ibm.minipay",
+ "afp" => "application/vnd.ibm.modcap",
+ "listafp" => "application/vnd.ibm.modcap",
+ "list3820" => "application/vnd.ibm.modcap",
+ "irm" => "application/vnd.ibm.rights-management",
+ "sc" => "application/vnd.ibm.secure-container",
+ "icc" => "application/vnd.iccprofile",
+ "icm" => "application/vnd.iccprofile",
+ "igl" => "application/vnd.igloader",
+ "ivp" => "application/vnd.immervision-ivp",
+ "ivu" => "application/vnd.immervision-ivu",
+ "igm" => "application/vnd.insors.igm",
+ "xpw" => "application/vnd.intercon.formnet",
+ "xpx" => "application/vnd.intercon.formnet",
+ "i2g" => "application/vnd.intergeo",
+ "qbo" => "application/vnd.intu.qbo",
+ "qfx" => "application/vnd.intu.qfx",
+ "rcprofile" => "application/vnd.ipunplugged.rcprofile",
+ "irp" => "application/vnd.irepository.package+xml",
+ "xpr" => "application/vnd.is-xpr",
+ "fcs" => "application/vnd.isac.fcs",
+ "jam" => "application/vnd.jam",
+ "rms" => "application/vnd.jcp.javame.midlet-rms",
+ "jisp" => "application/vnd.jisp",
+ "joda" => "application/vnd.joost.joda-archive",
+ "ktz" => "application/vnd.kahootz",
+ "ktr" => "application/vnd.kahootz",
+ "karbon" => "application/vnd.kde.karbon",
+ "chrt" => "application/vnd.kde.kchart",
+ "kfo" => "application/vnd.kde.kformula",
+ "flw" => "application/vnd.kde.kivio",
+ "kon" => "application/vnd.kde.kontour",
+ "kpr" => "application/vnd.kde.kpresenter",
+ "kpt" => "application/vnd.kde.kpresenter",
+ "ksp" => "application/vnd.kde.kspread",
+ "kwd" => "application/vnd.kde.kword",
+ "kwt" => "application/vnd.kde.kword",
+ "htke" => "application/vnd.kenameaapp",
+ "kia" => "application/vnd.kidspiration",
+ "kne" => "application/vnd.kinar",
+ "knp" => "application/vnd.kinar",
+ "skp" => "application/vnd.koan",
+ "skd" => "application/vnd.koan",
+ "skt" => "application/vnd.koan",
+ "skm" => "application/vnd.koan",
+ "sse" => "application/vnd.kodak-descriptor",
+ "lasxml" => "application/vnd.las.las+xml",
+ "lbd" => "application/vnd.llamagraphics.life-balance.desktop",
+ "lbe" => "application/vnd.llamagraphics.life-balance.exchange+xml",
+ "123" => "application/vnd.lotus-1-2-3",
+ "apr" => "application/vnd.lotus-approach",
+ "pre" => "application/vnd.lotus-freelance",
+ "nsf" => "application/vnd.lotus-notes",
+ "org" => "application/vnd.lotus-organizer",
+ "scm" => "application/vnd.lotus-screencam",
+ "lwp" => "application/vnd.lotus-wordpro",
+ "portpkg" => "application/vnd.macports.portpkg",
+ "mcd" => "application/vnd.mcd",
+ "mc1" => "application/vnd.medcalcdata",
+ "cdkey" => "application/vnd.mediastation.cdkey",
+ "mwf" => "application/vnd.mfer",
+ "mfm" => "application/vnd.mfmp",
+ "flo" => "application/vnd.micrografx.flo",
+ "igx" => "application/vnd.micrografx.igx",
+ "mif" => "application/vnd.mif",
+ "daf" => "application/vnd.mobius.daf",
+ "dis" => "application/vnd.mobius.dis",
+ "mbk" => "application/vnd.mobius.mbk",
+ "mqy" => "application/vnd.mobius.mqy",
+ "msl" => "application/vnd.mobius.msl",
+ "plc" => "application/vnd.mobius.plc",
+ "txf" => "application/vnd.mobius.txf",
+ "mpn" => "application/vnd.mophun.application",
+ "mpc" => "application/vnd.mophun.certificate",
+ "xul" => "application/vnd.mozilla.xul+xml",
+ "cil" => "application/vnd.ms-artgalry",
+ "cab" => "application/vnd.ms-cab-compressed",
+ "xls" => "application/vnd.ms-excel",
+ "xlm" => "application/vnd.ms-excel",
+ "xla" => "application/vnd.ms-excel",
+ "xlc" => "application/vnd.ms-excel",
+ "xlt" => "application/vnd.ms-excel",
+ "xlw" => "application/vnd.ms-excel",
+ "xlam" => "application/vnd.ms-excel.addin.macroenabled.12",
+ "xlsb" => "application/vnd.ms-excel.sheet.binary.macroenabled.12",
+ "xlsm" => "application/vnd.ms-excel.sheet.macroenabled.12",
+ "xltm" => "application/vnd.ms-excel.template.macroenabled.12",
+ "eot" => "application/vnd.ms-fontobject",
+ "chm" => "application/vnd.ms-htmlhelp",
+ "ims" => "application/vnd.ms-ims",
+ "lrm" => "application/vnd.ms-lrm",
+ "thmx" => "application/vnd.ms-officetheme",
+ "cat" => "application/vnd.ms-pki.seccat",
+ "stl" => "application/vnd.ms-pki.stl",
+ "ppt" => "application/vnd.ms-powerpoint",
+ "pps" => "application/vnd.ms-powerpoint",
+ "pot" => "application/vnd.ms-powerpoint",
+ "ppam" => "application/vnd.ms-powerpoint.addin.macroenabled.12",
+ "pptm" => "application/vnd.ms-powerpoint.presentation.macroenabled.12",
+ "sldm" => "application/vnd.ms-powerpoint.slide.macroenabled.12",
+ "ppsm" => "application/vnd.ms-powerpoint.slideshow.macroenabled.12",
+ "potm" => "application/vnd.ms-powerpoint.template.macroenabled.12",
+ "mpp" => "application/vnd.ms-project",
+ "mpt" => "application/vnd.ms-project",
+ "docm" => "application/vnd.ms-word.document.macroenabled.12",
+ "dotm" => "application/vnd.ms-word.template.macroenabled.12",
+ "wps" => "application/vnd.ms-works",
+ "wks" => "application/vnd.ms-works",
+ "wcm" => "application/vnd.ms-works",
+ "wdb" => "application/vnd.ms-works",
+ "wpl" => "application/vnd.ms-wpl",
+ "xps" => "application/vnd.ms-xpsdocument",
+ "mseq" => "application/vnd.mseq",
+ "mus" => "application/vnd.musician",
+ "msty" => "application/vnd.muvee.style",
+ "taglet" => "application/vnd.mynfc",
+ "nlu" => "application/vnd.neurolanguage.nlu",
+ "ntf" => "application/vnd.nitf",
+ "nitf" => "application/vnd.nitf",
+ "nnd" => "application/vnd.noblenet-directory",
+ "nns" => "application/vnd.noblenet-sealer",
+ "nnw" => "application/vnd.noblenet-web",
+ "ngdat" => "application/vnd.nokia.n-gage.data",
+ "n-gage" => "application/vnd.nokia.n-gage.symbian.install",
+ "rpst" => "application/vnd.nokia.radio-preset",
+ "rpss" => "application/vnd.nokia.radio-presets",
+ "edm" => "application/vnd.novadigm.edm",
+ "edx" => "application/vnd.novadigm.edx",
+ "ext" => "application/vnd.novadigm.ext",
+ "odc" => "application/vnd.oasis.opendocument.chart",
+ "otc" => "application/vnd.oasis.opendocument.chart-template",
+ "odb" => "application/vnd.oasis.opendocument.database",
+ "odf" => "application/vnd.oasis.opendocument.formula",
+ "odft" => "application/vnd.oasis.opendocument.formula-template",
+ "odg" => "application/vnd.oasis.opendocument.graphics",
+ "otg" => "application/vnd.oasis.opendocument.graphics-template",
+ "odi" => "application/vnd.oasis.opendocument.image",
+ "oti" => "application/vnd.oasis.opendocument.image-template",
+ "odp" => "application/vnd.oasis.opendocument.presentation",
+ "otp" => "application/vnd.oasis.opendocument.presentation-template",
+ "ods" => "application/vnd.oasis.opendocument.spreadsheet",
+ "ots" => "application/vnd.oasis.opendocument.spreadsheet-template",
+ "odt" => "application/vnd.oasis.opendocument.text",
+ "odm" => "application/vnd.oasis.opendocument.text-master",
+ "ott" => "application/vnd.oasis.opendocument.text-template",
+ "oth" => "application/vnd.oasis.opendocument.text-web",
+ "xo" => "application/vnd.olpc-sugar",
+ "dd2" => "application/vnd.oma.dd2+xml",
+ "oxt" => "application/vnd.openofficeorg.extension",
+ "pptx" => "application/vnd.openxmlformats-officedocument.presentationml.presentation",
+ "sldx" => "application/vnd.openxmlformats-officedocument.presentationml.slide",
+ "ppsx" => "application/vnd.openxmlformats-officedocument.presentationml.slideshow",
+ "potx" => "application/vnd.openxmlformats-officedocument.presentationml.template",
+ "xlsx" => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
+ "xltx" => "application/vnd.openxmlformats-officedocument.spreadsheetml.template",
+ "docx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
+ "dotx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.template",
+ "mgp" => "application/vnd.osgeo.mapguide.package",
+ "dp" => "application/vnd.osgi.dp",
+ "esa" => "application/vnd.osgi.subsystem",
+ "pdb" => "application/vnd.palm",
+ "pqa" => "application/vnd.palm",
+ "oprc" => "application/vnd.palm",
+ "paw" => "application/vnd.pawaafile",
+ "str" => "application/vnd.pg.format",
+ "ei6" => "application/vnd.pg.osasli",
+ "efif" => "application/vnd.picsel",
+ "wg" => "application/vnd.pmi.widget",
+ "plf" => "application/vnd.pocketlearn",
+ "pbd" => "application/vnd.powerbuilder6",
+ "box" => "application/vnd.previewsystems.box",
+ "mgz" => "application/vnd.proteus.magazine",
+ "qps" => "application/vnd.publishare-delta-tree",
+ "ptid" => "application/vnd.pvi.ptid1",
+ "qxd" => "application/vnd.quark.quarkxpress",
+ "qxt" => "application/vnd.quark.quarkxpress",
+ "qwd" => "application/vnd.quark.quarkxpress",
+ "qwt" => "application/vnd.quark.quarkxpress",
+ "qxl" => "application/vnd.quark.quarkxpress",
+ "qxb" => "application/vnd.quark.quarkxpress",
+ "bed" => "application/vnd.realvnc.bed",
+ "mxl" => "application/vnd.recordare.musicxml",
+ "musicxml" => "application/vnd.recordare.musicxml+xml",
+ "cryptonote" => "application/vnd.rig.cryptonote",
+ "cod" => "application/vnd.rim.cod",
+ "rm" => "application/vnd.rn-realmedia",
+ "rmvb" => "application/vnd.rn-realmedia-vbr",
+ "link66" => "application/vnd.route66.link66+xml",
+ "st" => "application/vnd.sailingtracker.track",
+ "see" => "application/vnd.seemail",
+ "sema" => "application/vnd.sema",
+ "semd" => "application/vnd.semd",
+ "semf" => "application/vnd.semf",
+ "ifm" => "application/vnd.shana.informed.formdata",
+ "itp" => "application/vnd.shana.informed.formtemplate",
+ "iif" => "application/vnd.shana.informed.interchange",
+ "ipk" => "application/vnd.shana.informed.package",
+ "twd" => "application/vnd.simtech-mindmapper",
+ "twds" => "application/vnd.simtech-mindmapper",
+ "mmf" => "application/vnd.smaf",
+ "teacher" => "application/vnd.smart.teacher",
+ "sdkm" => "application/vnd.solent.sdkm+xml",
+ "sdkd" => "application/vnd.solent.sdkm+xml",
+ "dxp" => "application/vnd.spotfire.dxp",
+ "sfs" => "application/vnd.spotfire.sfs",
+ "sdc" => "application/vnd.stardivision.calc",
+ "sda" => "application/vnd.stardivision.draw",
+ "sdd" => "application/vnd.stardivision.impress",
+ "smf" => "application/vnd.stardivision.math",
+ "sdw" => "application/vnd.stardivision.writer",
+ "vor" => "application/vnd.stardivision.writer",
+ "sgl" => "application/vnd.stardivision.writer-global",
+ "smzip" => "application/vnd.stepmania.package",
+ "sm" => "application/vnd.stepmania.stepchart",
+ "sxc" => "application/vnd.sun.xml.calc",
+ "stc" => "application/vnd.sun.xml.calc.template",
+ "sxd" => "application/vnd.sun.xml.draw",
+ "std" => "application/vnd.sun.xml.draw.template",
+ "sxi" => "application/vnd.sun.xml.impress",
+ "sti" => "application/vnd.sun.xml.impress.template",
+ "sxm" => "application/vnd.sun.xml.math",
+ "sxw" => "application/vnd.sun.xml.writer",
+ "sxg" => "application/vnd.sun.xml.writer.global",
+ "stw" => "application/vnd.sun.xml.writer.template",
+ "sus" => "application/vnd.sus-calendar",
+ "susp" => "application/vnd.sus-calendar",
+ "svd" => "application/vnd.svd",
+ "sis" => "application/vnd.symbian.install",
+ "sisx" => "application/vnd.symbian.install",
+ "xsm" => "application/vnd.syncml+xml",
+ "bdm" => "application/vnd.syncml.dm+wbxml",
+ "xdm" => "application/vnd.syncml.dm+xml",
+ "tao" => "application/vnd.tao.intent-module-archive",
+ "pcap" => "application/vnd.tcpdump.pcap",
+ "cap" => "application/vnd.tcpdump.pcap",
+ "dmp" => "application/vnd.tcpdump.pcap",
+ "tmo" => "application/vnd.tmobile-livetv",
+ "tpt" => "application/vnd.trid.tpt",
+ "mxs" => "application/vnd.triscape.mxs",
+ "tra" => "application/vnd.trueapp",
+ "ufd" => "application/vnd.ufdl",
+ "ufdl" => "application/vnd.ufdl",
+ "utz" => "application/vnd.uiq.theme",
+ "umj" => "application/vnd.umajin",
+ "unityweb" => "application/vnd.unity",
+ "uoml" => "application/vnd.uoml+xml",
+ "vcx" => "application/vnd.vcx",
+ "vsd" => "application/vnd.visio",
+ "vst" => "application/vnd.visio",
+ "vss" => "application/vnd.visio",
+ "vsw" => "application/vnd.visio",
+ "vis" => "application/vnd.visionary",
+ "vsf" => "application/vnd.vsf",
+ "wbxml" => "application/vnd.wap.wbxml",
+ "wmlc" => "application/vnd.wap.wmlc",
+ "wmlsc" => "application/vnd.wap.wmlscriptc",
+ "wtb" => "application/vnd.webturbo",
+ "nbp" => "application/vnd.wolfram.player",
+ "wpd" => "application/vnd.wordperfect",
+ "wqd" => "application/vnd.wqd",
+ "stf" => "application/vnd.wt.stf",
+ "xar" => "application/vnd.xara",
+ "xfdl" => "application/vnd.xfdl",
+ "hvd" => "application/vnd.yamaha.hv-dic",
+ "hvs" => "application/vnd.yamaha.hv-script",
+ "hvp" => "application/vnd.yamaha.hv-voice",
+ "osf" => "application/vnd.yamaha.openscoreformat",
+ "osfpvg" => "application/vnd.yamaha.openscoreformat.osfpvg+xml",
+ "saf" => "application/vnd.yamaha.smaf-audio",
+ "spf" => "application/vnd.yamaha.smaf-phrase",
+ "cmp" => "application/vnd.yellowriver-custom-menu",
+ "zir" => "application/vnd.zul",
+ "zirz" => "application/vnd.zul",
+ "zaz" => "application/vnd.zzazz.deck+xml",
+ "vxml" => "application/voicexml+xml",
+ "wgt" => "application/widget",
+ "hlp" => "application/winhlp",
+ "wsdl" => "application/wsdl+xml",
+ "wspolicy" => "application/wspolicy+xml",
+ "7z" => "application/x-7z-compressed",
+ "abw" => "application/x-abiword",
+ "ace" => "application/x-ace-compressed",
+ "dmg" => "application/x-apple-diskimage",
+ "aab" => "application/x-authorware-bin",
+ "x32" => "application/x-authorware-bin",
+ "u32" => "application/x-authorware-bin",
+ "vox" => "application/x-authorware-bin",
+ "aam" => "application/x-authorware-map",
+ "aas" => "application/x-authorware-seg",
+ "bcpio" => "application/x-bcpio",
+ "torrent" => "application/x-bittorrent",
+ "blb" => "application/x-blorb",
+ "blorb" => "application/x-blorb",
+ "bz" => "application/x-bzip",
+ "bz2" => "application/x-bzip2",
+ "boz" => "application/x-bzip2",
+ "cbr" => "application/x-cbr",
+ "cba" => "application/x-cbr",
+ "cbt" => "application/x-cbr",
+ "cbz" => "application/x-cbr",
+ "cb7" => "application/x-cbr",
+ "vcd" => "application/x-cdlink",
+ "cfs" => "application/x-cfs-compressed",
+ "chat" => "application/x-chat",
+ "pgn" => "application/x-chess-pgn",
+ "nsc" => "application/x-conference",
+ "cpio" => "application/x-cpio",
+ "csh" => "application/x-csh",
+ "deb" => "application/x-debian-package",
+ "udeb" => "application/x-debian-package",
+ "dgc" => "application/x-dgc-compressed",
+ "dir" => "application/x-director",
+ "dcr" => "application/x-director",
+ "dxr" => "application/x-director",
+ "cst" => "application/x-director",
+ "cct" => "application/x-director",
+ "cxt" => "application/x-director",
+ "w3d" => "application/x-director",
+ "fgd" => "application/x-director",
+ "swa" => "application/x-director",
+ "wad" => "application/x-doom",
+ "ncx" => "application/x-dtbncx+xml",
+ "dtb" => "application/x-dtbook+xml",
+ "res" => "application/x-dtbresource+xml",
+ "dvi" => "application/x-dvi",
+ "evy" => "application/x-envoy",
+ "eva" => "application/x-eva",
+ "bdf" => "application/x-font-bdf",
+ "gsf" => "application/x-font-ghostscript",
+ "psf" => "application/x-font-linux-psf",
+ "pcf" => "application/x-font-pcf",
+ "snf" => "application/x-font-snf",
+ "pfa" => "application/x-font-type1",
+ "pfb" => "application/x-font-type1",
+ "pfm" => "application/x-font-type1",
+ "afm" => "application/x-font-type1",
+ "arc" => "application/x-freearc",
+ "spl" => "application/x-futuresplash",
+ "gca" => "application/x-gca-compressed",
+ "ulx" => "application/x-glulx",
+ "gnumeric" => "application/x-gnumeric",
+ "gramps" => "application/x-gramps-xml",
+ "gtar" => "application/x-gtar",
+ "hdf" => "application/x-hdf",
+ "install" => "application/x-install-instructions",
+ "iso" => "application/x-iso9660-image",
+ "jnlp" => "application/x-java-jnlp-file",
+ "latex" => "application/x-latex",
+ "lzh" => "application/x-lzh-compressed",
+ "lha" => "application/x-lzh-compressed",
+ "mie" => "application/x-mie",
+ "prc" => "application/x-mobipocket-ebook",
+ "mobi" => "application/x-mobipocket-ebook",
+ "application" => "application/x-ms-application",
+ "lnk" => "application/x-ms-shortcut",
+ "wmd" => "application/x-ms-wmd",
+ "wmz" => "application/x-ms-wmz",
+ "xbap" => "application/x-ms-xbap",
+ "mdb" => "application/x-msaccess",
+ "obd" => "application/x-msbinder",
+ "crd" => "application/x-mscardfile",
+ "clp" => "application/x-msclip",
+ "exe" => "application/x-msdownload",
+ "dll" => "application/x-msdownload",
+ "com" => "application/x-msdownload",
+ "bat" => "application/x-msdownload",
+ "msi" => "application/x-msdownload",
+ "mvb" => "application/x-msmediaview",
+ "m13" => "application/x-msmediaview",
+ "m14" => "application/x-msmediaview",
+ "wmf" => "application/x-msmetafile",
+ "wmz" => "application/x-msmetafile",
+ "emf" => "application/x-msmetafile",
+ "emz" => "application/x-msmetafile",
+ "mny" => "application/x-msmoney",
+ "pub" => "application/x-mspublisher",
+ "scd" => "application/x-msschedule",
+ "trm" => "application/x-msterminal",
+ "wri" => "application/x-mswrite",
+ "nc" => "application/x-netcdf",
+ "cdf" => "application/x-netcdf",
+ "nzb" => "application/x-nzb",
+ "p12" => "application/x-pkcs12",
+ "pfx" => "application/x-pkcs12",
+ "p7b" => "application/x-pkcs7-certificates",
+ "spc" => "application/x-pkcs7-certificates",
+ "p7r" => "application/x-pkcs7-certreqresp",
+ "rar" => "application/x-rar-compressed",
+ "ris" => "application/x-research-info-systems",
+ "sh" => "application/x-sh",
+ "shar" => "application/x-shar",
+ "swf" => "application/x-shockwave-flash",
+ "xap" => "application/x-silverlight-app",
+ "sql" => "application/x-sql",
+ "sit" => "application/x-stuffit",
+ "sitx" => "application/x-stuffitx",
+ "srt" => "application/x-subrip",
+ "sv4cpio" => "application/x-sv4cpio",
+ "sv4crc" => "application/x-sv4crc",
+ "t3" => "application/x-t3vm-image",
+ "gam" => "application/x-tads",
+ "tar" => "application/x-tar",
+ "tcl" => "application/x-tcl",
+ "tex" => "application/x-tex",
+ "tfm" => "application/x-tex-tfm",
+ "texinfo" => "application/x-texinfo",
+ "texi" => "application/x-texinfo",
+ "obj" => "application/x-tgif",
+ "ustar" => "application/x-ustar",
+ "src" => "application/x-wais-source",
+ "der" => "application/x-x509-ca-cert",
+ "crt" => "application/x-x509-ca-cert",
+ "fig" => "application/x-xfig",
+ "xlf" => "application/x-xliff+xml",
+ "xpi" => "application/x-xpinstall",
+ "xz" => "application/x-xz",
+ "z1" => "application/x-zmachine",
+ "z2" => "application/x-zmachine",
+ "z3" => "application/x-zmachine",
+ "z4" => "application/x-zmachine",
+ "z5" => "application/x-zmachine",
+ "z6" => "application/x-zmachine",
+ "z7" => "application/x-zmachine",
+ "z8" => "application/x-zmachine",
+ "xaml" => "application/xaml+xml",
+ "xdf" => "application/xcap-diff+xml",
+ "xenc" => "application/xenc+xml",
+ "xhtml" => "application/xhtml+xml",
+ "xht" => "application/xhtml+xml",
+ "xml" => "application/xml",
+ "xsl" => "application/xml",
+ "dtd" => "application/xml-dtd",
+ "xop" => "application/xop+xml",
+ "xpl" => "application/xproc+xml",
+ "xslt" => "application/xslt+xml",
+ "xspf" => "application/xspf+xml",
+ "mxml" => "application/xv+xml",
+ "xhvml" => "application/xv+xml",
+ "xvml" => "application/xv+xml",
+ "xvm" => "application/xv+xml",
+ "yang" => "application/yang",
+ "yin" => "application/yin+xml",
+ "zip" => "application/zip",
+ "adp" => "audio/adpcm",
+ "au" => "audio/basic",
+ "snd" => "audio/basic",
+ "mid" => "audio/midi",
+ "midi" => "audio/midi",
+ "kar" => "audio/midi",
+ "rmi" => "audio/midi",
+ "m4a" => "audio/mp4",
+ "mp4a" => "audio/mp4",
+ "mpga" => "audio/mpeg",
+ "mp2" => "audio/mpeg",
+ "mp2a" => "audio/mpeg",
+ "mp3" => "audio/mpeg",
+ "m2a" => "audio/mpeg",
+ "m3a" => "audio/mpeg",
+ "oga" => "audio/ogg",
+ "ogg" => "audio/ogg",
+ "spx" => "audio/ogg",
+ "s3m" => "audio/s3m",
+ "sil" => "audio/silk",
+ "uva" => "audio/vnd.dece.audio",
+ "uvva" => "audio/vnd.dece.audio",
+ "eol" => "audio/vnd.digital-winds",
+ "dra" => "audio/vnd.dra",
+ "dts" => "audio/vnd.dts",
+ "dtshd" => "audio/vnd.dts.hd",
+ "lvp" => "audio/vnd.lucent.voice",
+ "pya" => "audio/vnd.ms-playready.media.pya",
+ "ecelp4800" => "audio/vnd.nuera.ecelp4800",
+ "ecelp7470" => "audio/vnd.nuera.ecelp7470",
+ "ecelp9600" => "audio/vnd.nuera.ecelp9600",
+ "rip" => "audio/vnd.rip",
+ "weba" => "audio/webm",
+ "aac" => "audio/x-aac",
+ "aif" => "audio/x-aiff",
+ "aiff" => "audio/x-aiff",
+ "aifc" => "audio/x-aiff",
+ "caf" => "audio/x-caf",
+ "flac" => "audio/x-flac",
+ "mka" => "audio/x-matroska",
+ "m3u" => "audio/x-mpegurl",
+ "wax" => "audio/x-ms-wax",
+ "wma" => "audio/x-ms-wma",
+ "ram" => "audio/x-pn-realaudio",
+ "ra" => "audio/x-pn-realaudio",
+ "rmp" => "audio/x-pn-realaudio-plugin",
+ "wav" => "audio/x-wav",
+ "xm" => "audio/xm",
+ "cdx" => "chemical/x-cdx",
+ "cif" => "chemical/x-cif",
+ "cmdf" => "chemical/x-cmdf",
+ "cml" => "chemical/x-cml",
+ "csml" => "chemical/x-csml",
+ "xyz" => "chemical/x-xyz",
+ "ttc" => "font/collection",
+ "otf" => "font/otf",
+ "ttf" => "font/ttf",
+ "woff" => "font/woff",
+ "woff2" => "font/woff2",
+ "bmp" => "image/bmp",
+ "cgm" => "image/cgm",
+ "g3" => "image/g3fax",
+ "gif" => "image/gif",
+ "ief" => "image/ief",
+ "jpeg" => "image/jpeg",
+ "jpg" => "image/jpeg",
+ "jpe" => "image/jpeg",
+ "ktx" => "image/ktx",
+ "png" => "image/png",
+ "btif" => "image/prs.btif",
+ "sgi" => "image/sgi",
+ "svg" => "image/svg+xml",
+ "svgz" => "image/svg+xml",
+ "tiff" => "image/tiff",
+ "tif" => "image/tiff",
+ "psd" => "image/vnd.adobe.photoshop",
+ "uvi" => "image/vnd.dece.graphic",
+ "uvvi" => "image/vnd.dece.graphic",
+ "uvg" => "image/vnd.dece.graphic",
+ "uvvg" => "image/vnd.dece.graphic",
+ "djvu" => "image/vnd.djvu",
+ "djv" => "image/vnd.djvu",
+ "sub" => "image/vnd.dvb.subtitle",
+ "dwg" => "image/vnd.dwg",
+ "dxf" => "image/vnd.dxf",
+ "fbs" => "image/vnd.fastbidsheet",
+ "fpx" => "image/vnd.fpx",
+ "fst" => "image/vnd.fst",
+ "mmr" => "image/vnd.fujixerox.edmics-mmr",
+ "rlc" => "image/vnd.fujixerox.edmics-rlc",
+ "mdi" => "image/vnd.ms-modi",
+ "wdp" => "image/vnd.ms-photo",
+ "npx" => "image/vnd.net-fpx",
+ "wbmp" => "image/vnd.wap.wbmp",
+ "xif" => "image/vnd.xiff",
+ "webp" => "image/webp",
+ "3ds" => "image/x-3ds",
+ "ras" => "image/x-cmu-raster",
+ "cmx" => "image/x-cmx",
+ "fh" => "image/x-freehand",
+ "fhc" => "image/x-freehand",
+ "fh4" => "image/x-freehand",
+ "fh5" => "image/x-freehand",
+ "fh7" => "image/x-freehand",
+ "ico" => "image/x-icon",
+ "sid" => "image/x-mrsid-image",
+ "pcx" => "image/x-pcx",
+ "pic" => "image/x-pict",
+ "pct" => "image/x-pict",
+ "pnm" => "image/x-portable-anymap",
+ "pbm" => "image/x-portable-bitmap",
+ "pgm" => "image/x-portable-graymap",
+ "ppm" => "image/x-portable-pixmap",
+ "rgb" => "image/x-rgb",
+ "tga" => "image/x-tga",
+ "xbm" => "image/x-xbitmap",
+ "xpm" => "image/x-xpixmap",
+ "xwd" => "image/x-xwindowdump",
+ "eml" => "message/rfc822",
+ "mime" => "message/rfc822",
+ "igs" => "model/iges",
+ "iges" => "model/iges",
+ "msh" => "model/mesh",
+ "mesh" => "model/mesh",
+ "silo" => "model/mesh",
+ "dae" => "model/vnd.collada+xml",
+ "dwf" => "model/vnd.dwf",
+ "gdl" => "model/vnd.gdl",
+ "gtw" => "model/vnd.gtw",
+ "mts" => "model/vnd.mts",
+ "vtu" => "model/vnd.vtu",
+ "wrl" => "model/vrml",
+ "vrml" => "model/vrml",
+ "x3db" => "model/x3d+binary",
+ "x3dbz" => "model/x3d+binary",
+ "x3dv" => "model/x3d+vrml",
+ "x3dvz" => "model/x3d+vrml",
+ "x3d" => "model/x3d+xml",
+ "x3dz" => "model/x3d+xml",
+ "appcache" => "text/cache-manifest",
+ "ics" => "text/calendar",
+ "ifb" => "text/calendar",
+ "css" => "text/css",
+ "csv" => "text/csv",
+ "html" => "text/html",
+ "htm" => "text/html",
+ "n3" => "text/n3",
+ "txt" => "text/plain",
+ "text" => "text/plain",
+ "conf" => "text/plain",
+ "def" => "text/plain",
+ "list" => "text/plain",
+ "log" => "text/plain",
+ "in" => "text/plain",
+ "dsc" => "text/prs.lines.tag",
+ "rtx" => "text/richtext",
+ "sgml" => "text/sgml",
+ "sgm" => "text/sgml",
+ "tsv" => "text/tab-separated-values",
+ "t" => "text/troff",
+ "tr" => "text/troff",
+ "roff" => "text/troff",
+ "man" => "text/troff",
+ "me" => "text/troff",
+ "ms" => "text/troff",
+ "ttl" => "text/turtle",
+ "uri" => "text/uri-list",
+ "uris" => "text/uri-list",
+ "urls" => "text/uri-list",
+ "vcard" => "text/vcard",
+ "curl" => "text/vnd.curl",
+ "dcurl" => "text/vnd.curl.dcurl",
+ "mcurl" => "text/vnd.curl.mcurl",
+ "scurl" => "text/vnd.curl.scurl",
+ "sub" => "text/vnd.dvb.subtitle",
+ "fly" => "text/vnd.fly",
+ "flx" => "text/vnd.fmi.flexstor",
+ "gv" => "text/vnd.graphviz",
+ "3dml" => "text/vnd.in3d.3dml",
+ "spot" => "text/vnd.in3d.spot",
+ "jad" => "text/vnd.sun.j2me.app-descriptor",
+ "wml" => "text/vnd.wap.wml",
+ "wmls" => "text/vnd.wap.wmlscript",
+ "s" => "text/x-asm",
+ "asm" => "text/x-asm",
+ "c" => "text/x-c",
+ "cc" => "text/x-c",
+ "cxx" => "text/x-c",
+ "cpp" => "text/x-c",
+ "h" => "text/x-c",
+ "hh" => "text/x-c",
+ "dic" => "text/x-c",
+ "f" => "text/x-fortran",
+ "for" => "text/x-fortran",
+ "f77" => "text/x-fortran",
+ "f90" => "text/x-fortran",
+ "java" => "text/x-java-source",
+ "nfo" => "text/x-nfo",
+ "opml" => "text/x-opml",
+ "p" => "text/x-pascal",
+ "pas" => "text/x-pascal",
+ "etx" => "text/x-setext",
+ "sfv" => "text/x-sfv",
+ "uu" => "text/x-uuencode",
+ "vcs" => "text/x-vcalendar",
+ "vcf" => "text/x-vcard",
+ "3gp" => "video/3gpp",
+ "3g2" => "video/3gpp2",
+ "h261" => "video/h261",
+ "h263" => "video/h263",
+ "h264" => "video/h264",
+ "jpgv" => "video/jpeg",
+ "jpm" => "video/jpm",
+ "jpgm" => "video/jpm",
+ "mj2" => "video/mj2",
+ "mjp2" => "video/mj2",
+ "mp4" => "video/mp4",
+ "mp4v" => "video/mp4",
+ "mpg4" => "video/mp4",
+ "mpeg" => "video/mpeg",
+ "mpg" => "video/mpeg",
+ "mpe" => "video/mpeg",
+ "m1v" => "video/mpeg",
+ "m2v" => "video/mpeg",
+ "ogv" => "video/ogg",
+ "qt" => "video/quicktime",
+ "mov" => "video/quicktime",
+ "uvh" => "video/vnd.dece.hd",
+ "uvvh" => "video/vnd.dece.hd",
+ "uvm" => "video/vnd.dece.mobile",
+ "uvvm" => "video/vnd.dece.mobile",
+ "uvp" => "video/vnd.dece.pd",
+ "uvvp" => "video/vnd.dece.pd",
+ "uvs" => "video/vnd.dece.sd",
+ "uvvs" => "video/vnd.dece.sd",
+ "uvv" => "video/vnd.dece.video",
+ "uvvv" => "video/vnd.dece.video",
+ "dvb" => "video/vnd.dvb.file",
+ "fvt" => "video/vnd.fvt",
+ "mxu" => "video/vnd.mpegurl",
+ "m4u" => "video/vnd.mpegurl",
+ "pyv" => "video/vnd.ms-playready.media.pyv",
+ "uvu" => "video/vnd.uvvu.mp4",
+ "uvvu" => "video/vnd.uvvu.mp4",
+ "viv" => "video/vnd.vivo",
+ "webm" => "video/webm",
+ "f4v" => "video/x-f4v",
+ "fli" => "video/x-fli",
+ "flv" => "video/x-flv",
+ "m4v" => "video/x-m4v",
+ "mkv" => "video/x-matroska",
+ "mk3d" => "video/x-matroska",
+ "mks" => "video/x-matroska",
+ "mng" => "video/x-mng",
+ "asf" => "video/x-ms-asf",
+ "asx" => "video/x-ms-asf",
+ "vob" => "video/x-ms-vob",
+ "wm" => "video/x-ms-wm",
+ "wmv" => "video/x-ms-wmv",
+ "wmx" => "video/x-ms-wmx",
+ "wvx" => "video/x-ms-wvx",
+ "avi" => "video/x-msvideo",
+ "movie" => "video/x-sgi-movie",
+ "smv" => "video/x-smv",
+ "ice" => "x-conference/x-cooltalk",
+];
diff --git a/pages.php b/pages.php
index 6d1b47c..8a88eb5 100644
--- a/pages.php
+++ b/pages.php
@@ -69,6 +69,17 @@ define("PAGES", [
"static/js/messages.js"
]
],
+ "files" => [
+ "title" => "files",
+ "navbar" => true,
+ "icon" => "fas fa-folder",
+ "styles" => [
+ "static/css/files.css"
+ ],
+ "scripts" => [
+ "static/js/files.js"
+ ]
+ ],
"404" => [
"title" => "404 error"
]
diff --git a/pages/files.php b/pages/files.php
new file mode 100644
index 0000000..4097c09
--- /dev/null
+++ b/pages/files.php
@@ -0,0 +1,116 @@
+
+
+
+
+
+
+ \n";
+ $link = "$folder/$f";
+ $target = "_BLANK";
+ $isdir = false;
+ $icon = "fas fa-file";
+ if (is_dir($fullpath . "/" . $f)) {
+ $isdir = true;
+ $link = "app.php?page=files&path=$folder/$f";
+ $icon = "fas fa-folder";
+ $target = "";
+ } else {
+ $link = "public/file.php?file=$folder/$f";
+ $extension = pathinfo($fullpath . "/" . $f)['extension'];
+ // If we don't have an extension, try using the whole filename
+ if ($extension == "") {
+ $extension = $f;
+ }
+ $mimetype = "application/octet-stream";
+ // Lookup mimetype from extension
+ if (array_key_exists($extension, $EXT2MIME)) {
+ $mimetype = $EXT2MIME[$extension];
+ }
+ // Lookup icon from mimetype
+ if (array_key_exists($mimetype, $MIMEICONS)) {
+ $icon = $MIMEICONS[$mimetype];
+ } else { // Allow broad generic
/other icons
+ $mimefirst = explode("/", $mimetype, 2)[0];
+ if (array_key_exists($mimefirst . "/other", $MIMEICONS)) {
+ $icon = $MIMEICONS[$mimetype];
+ }
+ }
+ }
+ echo "\t";
+ echo " ";
+ echo $f . "\n\n";
+ }
+ }
+ ?>
+
+
+
\ No newline at end of file
diff --git a/public/file.php b/public/file.php
index fd257eb..1ccb8c8 100644
--- a/public/file.php
+++ b/public/file.php
@@ -6,4 +6,45 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
-// TODO: Allow access to an uploaded files directory via this script.
\ No newline at end of file
+require_once __DIR__ . "/../lib/requiredpublic.php";
+
+$base = FILE_UPLOAD_PATH;
+
+$filepath = "";
+
+if (isset($_GET['file'])) {
+ $file = $_GET['file'];
+ $filepath = $base . $file;
+ if (!file_exists($filepath) || is_dir($filepath)) {
+ http_response_code(404);
+ die("404 File Not Found");
+ }
+ if (strpos(realpath($filepath), FILE_UPLOAD_PATH) !== 0) {
+ http_response_code(404);
+ die("404 File Not Found");
+ }
+} else {
+ http_response_code(404);
+ die("404 File Not Found");
+}
+
+include_once __DIR__ . "/../lib/mimetypes.php";
+
+$extension = pathinfo($filepath)['extension'];
+// If we don't have an extension, try using the whole filename
+if ($extension == "") {
+ $extension = $f;
+}
+$mimetype = "application/octet-stream";
+// Lookup mimetype from extension
+if (array_key_exists($extension, $EXT2MIME)) {
+ $mimetype = $EXT2MIME[$extension];
+}
+
+header("Content-Type: $mimetype");
+header('Content-Length: ' . filesize($filepath));
+header("X-Content-Type-Options: nosniff");
+
+ob_end_flush();
+
+readfile($filepath);
\ No newline at end of file
diff --git a/public/files/.htaccess b/public/files/.htaccess
new file mode 100755
index 0000000..14249c5
--- /dev/null
+++ b/public/files/.htaccess
@@ -0,0 +1 @@
+Deny from all
\ No newline at end of file
diff --git a/static/img/logo.png b/public/files/test/logo.png
old mode 100644
new mode 100755
similarity index 100%
rename from static/img/logo.png
rename to public/files/test/logo.png
diff --git a/settings.template.php b/settings.template.php
index 8f9e40a..653828b 100644
--- a/settings.template.php
+++ b/settings.template.php
@@ -35,6 +35,7 @@ define("TIMEZONE", "America/Denver");
define('URL', '/sitewriter');
// Folder for public files
+// This should not be inside the web root for security reasons.
define('FILE_UPLOAD_PATH', __DIR__ . '/public/files');
// Use pretty URLs (requires correct web server configuration)
diff --git a/static/css/files.css b/static/css/files.css
new file mode 100644
index 0000000..99485dc
--- /dev/null
+++ b/static/css/files.css
@@ -0,0 +1,24 @@
+/*
+This Source Code Form is subject to the terms of the Mozilla Public
+License, v. 2.0. If a copy of the MPL was not distributed with this
+file, You can obtain one at http://mozilla.org/MPL/2.0/.
+*/
+.btn-file {
+ position: relative;
+ overflow: hidden;
+}
+.btn-file input[type=file] {
+ position: absolute;
+ top: 0;
+ right: 0;
+ min-width: 100%;
+ min-height: 100%;
+ font-size: 100px;
+ text-align: right;
+ filter: alpha(opacity=0);
+ opacity: 0;
+ outline: none;
+ background: white;
+ cursor: inherit;
+ display: block;
+}
diff --git a/static/js/app.js b/static/js/app.js
index 2fe1a03..65b0a86 100644
--- a/static/js/app.js
+++ b/static/js/app.js
@@ -20,7 +20,7 @@ function getniceurl() {
return url;
}
try {
- window.history.replaceState("", "", getniceurl());
+ //window.history.replaceState("", "", getniceurl());
} catch (ex) {
}
\ No newline at end of file
diff --git a/static/js/files.js b/static/js/files.js
new file mode 100644
index 0000000..a92b98b
--- /dev/null
+++ b/static/js/files.js
@@ -0,0 +1,15 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+$(document).on('change', ':file', function () {
+ var input = $(this),
+ numFiles = input.get(0).files ? input.get(0).files.length : 1,
+ label = input.val().replace(/\\/g, '/').replace(/.*\//, '');
+ input.trigger('fileselect', [numFiles, label]);
+});
+
+$(':file').on('fileselect', function (event, numFiles, label) {
+ var message = numFiles > 1 ? numFiles + ' files selected' : label;
+ $("#uploadstatus").val(message);
+});
\ No newline at end of file
diff --git a/static/js/jquery.fileupload.js b/static/js/jquery.fileupload.js
new file mode 100644
index 0000000..7929daa
--- /dev/null
+++ b/static/js/jquery.fileupload.js
@@ -0,0 +1,1486 @@
+/*
+ * jQuery File Upload Plugin
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2010, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * https://opensource.org/licenses/MIT
+ */
+
+/* jshint nomen:false */
+/* global define, require, window, document, location, Blob, FormData */
+
+;(function (factory) {
+ 'use strict';
+ if (typeof define === 'function' && define.amd) {
+ // Register as an anonymous AMD module:
+ define([
+ 'jquery',
+ 'jquery-ui/ui/widget'
+ ], factory);
+ } else if (typeof exports === 'object') {
+ // Node/CommonJS:
+ factory(
+ require('jquery'),
+ require('./vendor/jquery.ui.widget')
+ );
+ } else {
+ // Browser globals:
+ factory(window.jQuery);
+ }
+}(function ($) {
+ 'use strict';
+
+ // Detect file input support, based on
+ // http://viljamis.com/blog/2012/file-upload-support-on-mobile/
+ $.support.fileInput = !(new RegExp(
+ // Handle devices which give false positives for the feature detection:
+ '(Android (1\\.[0156]|2\\.[01]))' +
+ '|(Windows Phone (OS 7|8\\.0))|(XBLWP)|(ZuneWP)|(WPDesktop)' +
+ '|(w(eb)?OSBrowser)|(webOS)' +
+ '|(Kindle/(1\\.0|2\\.[05]|3\\.0))'
+ ).test(window.navigator.userAgent) ||
+ // Feature detection for all other devices:
+ $('').prop('disabled'));
+
+ // The FileReader API is not actually used, but works as feature detection,
+ // as some Safari versions (5?) support XHR file uploads via the FormData API,
+ // but not non-multipart XHR file uploads.
+ // window.XMLHttpRequestUpload is not available on IE10, so we check for
+ // window.ProgressEvent instead to detect XHR2 file upload capability:
+ $.support.xhrFileUpload = !!(window.ProgressEvent && window.FileReader);
+ $.support.xhrFormDataFileUpload = !!window.FormData;
+
+ // Detect support for Blob slicing (required for chunked uploads):
+ $.support.blobSlice = window.Blob && (Blob.prototype.slice ||
+ Blob.prototype.webkitSlice || Blob.prototype.mozSlice);
+
+ // Helper function to create drag handlers for dragover/dragenter/dragleave:
+ function getDragHandler(type) {
+ var isDragOver = type === 'dragover';
+ return function (e) {
+ e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer;
+ var dataTransfer = e.dataTransfer;
+ if (dataTransfer && $.inArray('Files', dataTransfer.types) !== -1 &&
+ this._trigger(
+ type,
+ $.Event(type, {delegatedEvent: e})
+ ) !== false) {
+ e.preventDefault();
+ if (isDragOver) {
+ dataTransfer.dropEffect = 'copy';
+ }
+ }
+ };
+ }
+
+ // The fileupload widget listens for change events on file input fields defined
+ // via fileInput setting and paste or drop events of the given dropZone.
+ // In addition to the default jQuery Widget methods, the fileupload widget
+ // exposes the "add" and "send" methods, to add or directly send files using
+ // the fileupload API.
+ // By default, files added via file input selection, paste, drag & drop or
+ // "add" method are uploaded immediately, but it is possible to override
+ // the "add" callback option to queue file uploads.
+ $.widget('blueimp.fileupload', {
+
+ options: {
+ // The drop target element(s), by the default the complete document.
+ // Set to null to disable drag & drop support:
+ dropZone: $(document),
+ // The paste target element(s), by the default undefined.
+ // Set to a DOM node or jQuery object to enable file pasting:
+ pasteZone: undefined,
+ // The file input field(s), that are listened to for change events.
+ // If undefined, it is set to the file input fields inside
+ // of the widget element on plugin initialization.
+ // Set to null to disable the change listener.
+ fileInput: undefined,
+ // By default, the file input field is replaced with a clone after
+ // each input field change event. This is required for iframe transport
+ // queues and allows change events to be fired for the same file
+ // selection, but can be disabled by setting the following option to false:
+ replaceFileInput: true,
+ // The parameter name for the file form data (the request argument name).
+ // If undefined or empty, the name property of the file input field is
+ // used, or "files[]" if the file input name property is also empty,
+ // can be a string or an array of strings:
+ paramName: undefined,
+ // By default, each file of a selection is uploaded using an individual
+ // request for XHR type uploads. Set to false to upload file
+ // selections in one request each:
+ singleFileUploads: true,
+ // To limit the number of files uploaded with one XHR request,
+ // set the following option to an integer greater than 0:
+ limitMultiFileUploads: undefined,
+ // The following option limits the number of files uploaded with one
+ // XHR request to keep the request size under or equal to the defined
+ // limit in bytes:
+ limitMultiFileUploadSize: undefined,
+ // Multipart file uploads add a number of bytes to each uploaded file,
+ // therefore the following option adds an overhead for each file used
+ // in the limitMultiFileUploadSize configuration:
+ limitMultiFileUploadSizeOverhead: 512,
+ // Set the following option to true to issue all file upload requests
+ // in a sequential order:
+ sequentialUploads: false,
+ // To limit the number of concurrent uploads,
+ // set the following option to an integer greater than 0:
+ limitConcurrentUploads: undefined,
+ // Set the following option to true to force iframe transport uploads:
+ forceIframeTransport: false,
+ // Set the following option to the location of a redirect url on the
+ // origin server, for cross-domain iframe transport uploads:
+ redirect: undefined,
+ // The parameter name for the redirect url, sent as part of the form
+ // data and set to 'redirect' if this option is empty:
+ redirectParamName: undefined,
+ // Set the following option to the location of a postMessage window,
+ // to enable postMessage transport uploads:
+ postMessage: undefined,
+ // By default, XHR file uploads are sent as multipart/form-data.
+ // The iframe transport is always using multipart/form-data.
+ // Set to false to enable non-multipart XHR uploads:
+ multipart: true,
+ // To upload large files in smaller chunks, set the following option
+ // to a preferred maximum chunk size. If set to 0, null or undefined,
+ // or the browser does not support the required Blob API, files will
+ // be uploaded as a whole.
+ maxChunkSize: undefined,
+ // When a non-multipart upload or a chunked multipart upload has been
+ // aborted, this option can be used to resume the upload by setting
+ // it to the size of the already uploaded bytes. This option is most
+ // useful when modifying the options object inside of the "add" or
+ // "send" callbacks, as the options are cloned for each file upload.
+ uploadedBytes: undefined,
+ // By default, failed (abort or error) file uploads are removed from the
+ // global progress calculation. Set the following option to false to
+ // prevent recalculating the global progress data:
+ recalculateProgress: true,
+ // Interval in milliseconds to calculate and trigger progress events:
+ progressInterval: 100,
+ // Interval in milliseconds to calculate progress bitrate:
+ bitrateInterval: 500,
+ // By default, uploads are started automatically when adding files:
+ autoUpload: true,
+
+ // Error and info messages:
+ messages: {
+ uploadedBytes: 'Uploaded bytes exceed file size'
+ },
+
+ // Translation function, gets the message key to be translated
+ // and an object with context specific data as arguments:
+ i18n: function (message, context) {
+ message = this.messages[message] || message.toString();
+ if (context) {
+ $.each(context, function (key, value) {
+ message = message.replace('{' + key + '}', value);
+ });
+ }
+ return message;
+ },
+
+ // Additional form data to be sent along with the file uploads can be set
+ // using this option, which accepts an array of objects with name and
+ // value properties, a function returning such an array, a FormData
+ // object (for XHR file uploads), or a simple object.
+ // The form of the first fileInput is given as parameter to the function:
+ formData: function (form) {
+ return form.serializeArray();
+ },
+
+ // The add callback is invoked as soon as files are added to the fileupload
+ // widget (via file input selection, drag & drop, paste or add API call).
+ // If the singleFileUploads option is enabled, this callback will be
+ // called once for each file in the selection for XHR file uploads, else
+ // once for each file selection.
+ //
+ // The upload starts when the submit method is invoked on the data parameter.
+ // The data object contains a files property holding the added files
+ // and allows you to override plugin options as well as define ajax settings.
+ //
+ // Listeners for this callback can also be bound the following way:
+ // .bind('fileuploadadd', func);
+ //
+ // data.submit() returns a Promise object and allows to attach additional
+ // handlers using jQuery's Deferred callbacks:
+ // data.submit().done(func).fail(func).always(func);
+ add: function (e, data) {
+ if (e.isDefaultPrevented()) {
+ return false;
+ }
+ if (data.autoUpload || (data.autoUpload !== false &&
+ $(this).fileupload('option', 'autoUpload'))) {
+ data.process().done(function () {
+ data.submit();
+ });
+ }
+ },
+
+ // Other callbacks:
+
+ // Callback for the submit event of each file upload:
+ // submit: function (e, data) {}, // .bind('fileuploadsubmit', func);
+
+ // Callback for the start of each file upload request:
+ // send: function (e, data) {}, // .bind('fileuploadsend', func);
+
+ // Callback for successful uploads:
+ // done: function (e, data) {}, // .bind('fileuploaddone', func);
+
+ // Callback for failed (abort or error) uploads:
+ // fail: function (e, data) {}, // .bind('fileuploadfail', func);
+
+ // Callback for completed (success, abort or error) requests:
+ // always: function (e, data) {}, // .bind('fileuploadalways', func);
+
+ // Callback for upload progress events:
+ // progress: function (e, data) {}, // .bind('fileuploadprogress', func);
+
+ // Callback for global upload progress events:
+ // progressall: function (e, data) {}, // .bind('fileuploadprogressall', func);
+
+ // Callback for uploads start, equivalent to the global ajaxStart event:
+ // start: function (e) {}, // .bind('fileuploadstart', func);
+
+ // Callback for uploads stop, equivalent to the global ajaxStop event:
+ // stop: function (e) {}, // .bind('fileuploadstop', func);
+
+ // Callback for change events of the fileInput(s):
+ // change: function (e, data) {}, // .bind('fileuploadchange', func);
+
+ // Callback for paste events to the pasteZone(s):
+ // paste: function (e, data) {}, // .bind('fileuploadpaste', func);
+
+ // Callback for drop events of the dropZone(s):
+ // drop: function (e, data) {}, // .bind('fileuploaddrop', func);
+
+ // Callback for dragover events of the dropZone(s):
+ // dragover: function (e) {}, // .bind('fileuploaddragover', func);
+
+ // Callback for the start of each chunk upload request:
+ // chunksend: function (e, data) {}, // .bind('fileuploadchunksend', func);
+
+ // Callback for successful chunk uploads:
+ // chunkdone: function (e, data) {}, // .bind('fileuploadchunkdone', func);
+
+ // Callback for failed (abort or error) chunk uploads:
+ // chunkfail: function (e, data) {}, // .bind('fileuploadchunkfail', func);
+
+ // Callback for completed (success, abort or error) chunk upload requests:
+ // chunkalways: function (e, data) {}, // .bind('fileuploadchunkalways', func);
+
+ // The plugin options are used as settings object for the ajax calls.
+ // The following are jQuery ajax settings required for the file uploads:
+ processData: false,
+ contentType: false,
+ cache: false,
+ timeout: 0
+ },
+
+ // A list of options that require reinitializing event listeners and/or
+ // special initialization code:
+ _specialOptions: [
+ 'fileInput',
+ 'dropZone',
+ 'pasteZone',
+ 'multipart',
+ 'forceIframeTransport'
+ ],
+
+ _blobSlice: $.support.blobSlice && function () {
+ var slice = this.slice || this.webkitSlice || this.mozSlice;
+ return slice.apply(this, arguments);
+ },
+
+ _BitrateTimer: function () {
+ this.timestamp = ((Date.now) ? Date.now() : (new Date()).getTime());
+ this.loaded = 0;
+ this.bitrate = 0;
+ this.getBitrate = function (now, loaded, interval) {
+ var timeDiff = now - this.timestamp;
+ if (!this.bitrate || !interval || timeDiff > interval) {
+ this.bitrate = (loaded - this.loaded) * (1000 / timeDiff) * 8;
+ this.loaded = loaded;
+ this.timestamp = now;
+ }
+ return this.bitrate;
+ };
+ },
+
+ _isXHRUpload: function (options) {
+ return !options.forceIframeTransport &&
+ ((!options.multipart && $.support.xhrFileUpload) ||
+ $.support.xhrFormDataFileUpload);
+ },
+
+ _getFormData: function (options) {
+ var formData;
+ if ($.type(options.formData) === 'function') {
+ return options.formData(options.form);
+ }
+ if ($.isArray(options.formData)) {
+ return options.formData;
+ }
+ if ($.type(options.formData) === 'object') {
+ formData = [];
+ $.each(options.formData, function (name, value) {
+ formData.push({name: name, value: value});
+ });
+ return formData;
+ }
+ return [];
+ },
+
+ _getTotal: function (files) {
+ var total = 0;
+ $.each(files, function (index, file) {
+ total += file.size || 1;
+ });
+ return total;
+ },
+
+ _initProgressObject: function (obj) {
+ var progress = {
+ loaded: 0,
+ total: 0,
+ bitrate: 0
+ };
+ if (obj._progress) {
+ $.extend(obj._progress, progress);
+ } else {
+ obj._progress = progress;
+ }
+ },
+
+ _initResponseObject: function (obj) {
+ var prop;
+ if (obj._response) {
+ for (prop in obj._response) {
+ if (obj._response.hasOwnProperty(prop)) {
+ delete obj._response[prop];
+ }
+ }
+ } else {
+ obj._response = {};
+ }
+ },
+
+ _onProgress: function (e, data) {
+ if (e.lengthComputable) {
+ var now = ((Date.now) ? Date.now() : (new Date()).getTime()),
+ loaded;
+ if (data._time && data.progressInterval &&
+ (now - data._time < data.progressInterval) &&
+ e.loaded !== e.total) {
+ return;
+ }
+ data._time = now;
+ loaded = Math.floor(
+ e.loaded / e.total * (data.chunkSize || data._progress.total)
+ ) + (data.uploadedBytes || 0);
+ // Add the difference from the previously loaded state
+ // to the global loaded counter:
+ this._progress.loaded += (loaded - data._progress.loaded);
+ this._progress.bitrate = this._bitrateTimer.getBitrate(
+ now,
+ this._progress.loaded,
+ data.bitrateInterval
+ );
+ data._progress.loaded = data.loaded = loaded;
+ data._progress.bitrate = data.bitrate = data._bitrateTimer.getBitrate(
+ now,
+ loaded,
+ data.bitrateInterval
+ );
+ // Trigger a custom progress event with a total data property set
+ // to the file size(s) of the current upload and a loaded data
+ // property calculated accordingly:
+ this._trigger(
+ 'progress',
+ $.Event('progress', {delegatedEvent: e}),
+ data
+ );
+ // Trigger a global progress event for all current file uploads,
+ // including ajax calls queued for sequential file uploads:
+ this._trigger(
+ 'progressall',
+ $.Event('progressall', {delegatedEvent: e}),
+ this._progress
+ );
+ }
+ },
+
+ _initProgressListener: function (options) {
+ var that = this,
+ xhr = options.xhr ? options.xhr() : $.ajaxSettings.xhr();
+ // Accesss to the native XHR object is required to add event listeners
+ // for the upload progress event:
+ if (xhr.upload) {
+ $(xhr.upload).bind('progress', function (e) {
+ var oe = e.originalEvent;
+ // Make sure the progress event properties get copied over:
+ e.lengthComputable = oe.lengthComputable;
+ e.loaded = oe.loaded;
+ e.total = oe.total;
+ that._onProgress(e, options);
+ });
+ options.xhr = function () {
+ return xhr;
+ };
+ }
+ },
+
+ _isInstanceOf: function (type, obj) {
+ // Cross-frame instanceof check
+ return Object.prototype.toString.call(obj) === '[object ' + type + ']';
+ },
+
+ _initXHRData: function (options) {
+ var that = this,
+ formData,
+ file = options.files[0],
+ // Ignore non-multipart setting if not supported:
+ multipart = options.multipart || !$.support.xhrFileUpload,
+ paramName = $.type(options.paramName) === 'array' ?
+ options.paramName[0] : options.paramName;
+ options.headers = $.extend({}, options.headers);
+ if (options.contentRange) {
+ options.headers['Content-Range'] = options.contentRange;
+ }
+ if (!multipart || options.blob || !this._isInstanceOf('File', file)) {
+ options.headers['Content-Disposition'] = 'attachment; filename="' +
+ encodeURI(file.uploadName || file.name) + '"';
+ }
+ if (!multipart) {
+ options.contentType = file.type || 'application/octet-stream';
+ options.data = options.blob || file;
+ } else if ($.support.xhrFormDataFileUpload) {
+ if (options.postMessage) {
+ // window.postMessage does not allow sending FormData
+ // objects, so we just add the File/Blob objects to
+ // the formData array and let the postMessage window
+ // create the FormData object out of this array:
+ formData = this._getFormData(options);
+ if (options.blob) {
+ formData.push({
+ name: paramName,
+ value: options.blob
+ });
+ } else {
+ $.each(options.files, function (index, file) {
+ formData.push({
+ name: ($.type(options.paramName) === 'array' &&
+ options.paramName[index]) || paramName,
+ value: file
+ });
+ });
+ }
+ } else {
+ if (that._isInstanceOf('FormData', options.formData)) {
+ formData = options.formData;
+ } else {
+ formData = new FormData();
+ $.each(this._getFormData(options), function (index, field) {
+ formData.append(field.name, field.value);
+ });
+ }
+ if (options.blob) {
+ formData.append(
+ paramName,
+ options.blob,
+ file.uploadName || file.name
+ );
+ } else {
+ $.each(options.files, function (index, file) {
+ // This check allows the tests to run with
+ // dummy objects:
+ if (that._isInstanceOf('File', file) ||
+ that._isInstanceOf('Blob', file)) {
+ formData.append(
+ ($.type(options.paramName) === 'array' &&
+ options.paramName[index]) || paramName,
+ file,
+ file.uploadName || file.name
+ );
+ }
+ });
+ }
+ }
+ options.data = formData;
+ }
+ // Blob reference is not needed anymore, free memory:
+ options.blob = null;
+ },
+
+ _initIframeSettings: function (options) {
+ var targetHost = $('').prop('href', options.url).prop('host');
+ // Setting the dataType to iframe enables the iframe transport:
+ options.dataType = 'iframe ' + (options.dataType || '');
+ // The iframe transport accepts a serialized array as form data:
+ options.formData = this._getFormData(options);
+ // Add redirect url to form data on cross-domain uploads:
+ if (options.redirect && targetHost && targetHost !== location.host) {
+ options.formData.push({
+ name: options.redirectParamName || 'redirect',
+ value: options.redirect
+ });
+ }
+ },
+
+ _initDataSettings: function (options) {
+ if (this._isXHRUpload(options)) {
+ if (!this._chunkedUpload(options, true)) {
+ if (!options.data) {
+ this._initXHRData(options);
+ }
+ this._initProgressListener(options);
+ }
+ if (options.postMessage) {
+ // Setting the dataType to postmessage enables the
+ // postMessage transport:
+ options.dataType = 'postmessage ' + (options.dataType || '');
+ }
+ } else {
+ this._initIframeSettings(options);
+ }
+ },
+
+ _getParamName: function (options) {
+ var fileInput = $(options.fileInput),
+ paramName = options.paramName;
+ if (!paramName) {
+ paramName = [];
+ fileInput.each(function () {
+ var input = $(this),
+ name = input.prop('name') || 'files[]',
+ i = (input.prop('files') || [1]).length;
+ while (i) {
+ paramName.push(name);
+ i -= 1;
+ }
+ });
+ if (!paramName.length) {
+ paramName = [fileInput.prop('name') || 'files[]'];
+ }
+ } else if (!$.isArray(paramName)) {
+ paramName = [paramName];
+ }
+ return paramName;
+ },
+
+ _initFormSettings: function (options) {
+ // Retrieve missing options from the input field and the
+ // associated form, if available:
+ if (!options.form || !options.form.length) {
+ options.form = $(options.fileInput.prop('form'));
+ // If the given file input doesn't have an associated form,
+ // use the default widget file input's form:
+ if (!options.form.length) {
+ options.form = $(this.options.fileInput.prop('form'));
+ }
+ }
+ options.paramName = this._getParamName(options);
+ if (!options.url) {
+ options.url = options.form.prop('action') || location.href;
+ }
+ // The HTTP request method must be "POST" or "PUT":
+ options.type = (options.type ||
+ ($.type(options.form.prop('method')) === 'string' &&
+ options.form.prop('method')) || ''
+ ).toUpperCase();
+ if (options.type !== 'POST' && options.type !== 'PUT' &&
+ options.type !== 'PATCH') {
+ options.type = 'POST';
+ }
+ if (!options.formAcceptCharset) {
+ options.formAcceptCharset = options.form.attr('accept-charset');
+ }
+ },
+
+ _getAJAXSettings: function (data) {
+ var options = $.extend({}, this.options, data);
+ this._initFormSettings(options);
+ this._initDataSettings(options);
+ return options;
+ },
+
+ // jQuery 1.6 doesn't provide .state(),
+ // while jQuery 1.8+ removed .isRejected() and .isResolved():
+ _getDeferredState: function (deferred) {
+ if (deferred.state) {
+ return deferred.state();
+ }
+ if (deferred.isResolved()) {
+ return 'resolved';
+ }
+ if (deferred.isRejected()) {
+ return 'rejected';
+ }
+ return 'pending';
+ },
+
+ // Maps jqXHR callbacks to the equivalent
+ // methods of the given Promise object:
+ _enhancePromise: function (promise) {
+ promise.success = promise.done;
+ promise.error = promise.fail;
+ promise.complete = promise.always;
+ return promise;
+ },
+
+ // Creates and returns a Promise object enhanced with
+ // the jqXHR methods abort, success, error and complete:
+ _getXHRPromise: function (resolveOrReject, context, args) {
+ var dfd = $.Deferred(),
+ promise = dfd.promise();
+ context = context || this.options.context || promise;
+ if (resolveOrReject === true) {
+ dfd.resolveWith(context, args);
+ } else if (resolveOrReject === false) {
+ dfd.rejectWith(context, args);
+ }
+ promise.abort = dfd.promise;
+ return this._enhancePromise(promise);
+ },
+
+ // Adds convenience methods to the data callback argument:
+ _addConvenienceMethods: function (e, data) {
+ var that = this,
+ getPromise = function (args) {
+ return $.Deferred().resolveWith(that, args).promise();
+ };
+ data.process = function (resolveFunc, rejectFunc) {
+ if (resolveFunc || rejectFunc) {
+ data._processQueue = this._processQueue =
+ (this._processQueue || getPromise([this])).then(
+ function () {
+ if (data.errorThrown) {
+ return $.Deferred()
+ .rejectWith(that, [data]).promise();
+ }
+ return getPromise(arguments);
+ }
+ ).then(resolveFunc, rejectFunc);
+ }
+ return this._processQueue || getPromise([this]);
+ };
+ data.submit = function () {
+ if (this.state() !== 'pending') {
+ data.jqXHR = this.jqXHR =
+ (that._trigger(
+ 'submit',
+ $.Event('submit', {delegatedEvent: e}),
+ this
+ ) !== false) && that._onSend(e, this);
+ }
+ return this.jqXHR || that._getXHRPromise();
+ };
+ data.abort = function () {
+ if (this.jqXHR) {
+ return this.jqXHR.abort();
+ }
+ this.errorThrown = 'abort';
+ that._trigger('fail', null, this);
+ return that._getXHRPromise(false);
+ };
+ data.state = function () {
+ if (this.jqXHR) {
+ return that._getDeferredState(this.jqXHR);
+ }
+ if (this._processQueue) {
+ return that._getDeferredState(this._processQueue);
+ }
+ };
+ data.processing = function () {
+ return !this.jqXHR && this._processQueue && that
+ ._getDeferredState(this._processQueue) === 'pending';
+ };
+ data.progress = function () {
+ return this._progress;
+ };
+ data.response = function () {
+ return this._response;
+ };
+ },
+
+ // Parses the Range header from the server response
+ // and returns the uploaded bytes:
+ _getUploadedBytes: function (jqXHR) {
+ var range = jqXHR.getResponseHeader('Range'),
+ parts = range && range.split('-'),
+ upperBytesPos = parts && parts.length > 1 &&
+ parseInt(parts[1], 10);
+ return upperBytesPos && upperBytesPos + 1;
+ },
+
+ // Uploads a file in multiple, sequential requests
+ // by splitting the file up in multiple blob chunks.
+ // If the second parameter is true, only tests if the file
+ // should be uploaded in chunks, but does not invoke any
+ // upload requests:
+ _chunkedUpload: function (options, testOnly) {
+ options.uploadedBytes = options.uploadedBytes || 0;
+ var that = this,
+ file = options.files[0],
+ fs = file.size,
+ ub = options.uploadedBytes,
+ mcs = options.maxChunkSize || fs,
+ slice = this._blobSlice,
+ dfd = $.Deferred(),
+ promise = dfd.promise(),
+ jqXHR,
+ upload;
+ if (!(this._isXHRUpload(options) && slice && (ub || ($.type(mcs) === 'function' ? mcs(options) : mcs) < fs)) ||
+ options.data) {
+ return false;
+ }
+ if (testOnly) {
+ return true;
+ }
+ if (ub >= fs) {
+ file.error = options.i18n('uploadedBytes');
+ return this._getXHRPromise(
+ false,
+ options.context,
+ [null, 'error', file.error]
+ );
+ }
+ // The chunk upload method:
+ upload = function () {
+ // Clone the options object for each chunk upload:
+ var o = $.extend({}, options),
+ currentLoaded = o._progress.loaded;
+ o.blob = slice.call(
+ file,
+ ub,
+ ub + ($.type(mcs) === 'function' ? mcs(o) : mcs),
+ file.type
+ );
+ // Store the current chunk size, as the blob itself
+ // will be dereferenced after data processing:
+ o.chunkSize = o.blob.size;
+ // Expose the chunk bytes position range:
+ o.contentRange = 'bytes ' + ub + '-' +
+ (ub + o.chunkSize - 1) + '/' + fs;
+ // Process the upload data (the blob and potential form data):
+ that._initXHRData(o);
+ // Add progress listeners for this chunk upload:
+ that._initProgressListener(o);
+ jqXHR = ((that._trigger('chunksend', null, o) !== false && $.ajax(o)) ||
+ that._getXHRPromise(false, o.context))
+ .done(function (result, textStatus, jqXHR) {
+ ub = that._getUploadedBytes(jqXHR) ||
+ (ub + o.chunkSize);
+ // Create a progress event if no final progress event
+ // with loaded equaling total has been triggered
+ // for this chunk:
+ if (currentLoaded + o.chunkSize - o._progress.loaded) {
+ that._onProgress($.Event('progress', {
+ lengthComputable: true,
+ loaded: ub - o.uploadedBytes,
+ total: ub - o.uploadedBytes
+ }), o);
+ }
+ options.uploadedBytes = o.uploadedBytes = ub;
+ o.result = result;
+ o.textStatus = textStatus;
+ o.jqXHR = jqXHR;
+ that._trigger('chunkdone', null, o);
+ that._trigger('chunkalways', null, o);
+ if (ub < fs) {
+ // File upload not yet complete,
+ // continue with the next chunk:
+ upload();
+ } else {
+ dfd.resolveWith(
+ o.context,
+ [result, textStatus, jqXHR]
+ );
+ }
+ })
+ .fail(function (jqXHR, textStatus, errorThrown) {
+ o.jqXHR = jqXHR;
+ o.textStatus = textStatus;
+ o.errorThrown = errorThrown;
+ that._trigger('chunkfail', null, o);
+ that._trigger('chunkalways', null, o);
+ dfd.rejectWith(
+ o.context,
+ [jqXHR, textStatus, errorThrown]
+ );
+ });
+ };
+ this._enhancePromise(promise);
+ promise.abort = function () {
+ return jqXHR.abort();
+ };
+ upload();
+ return promise;
+ },
+
+ _beforeSend: function (e, data) {
+ if (this._active === 0) {
+ // the start callback is triggered when an upload starts
+ // and no other uploads are currently running,
+ // equivalent to the global ajaxStart event:
+ this._trigger('start');
+ // Set timer for global bitrate progress calculation:
+ this._bitrateTimer = new this._BitrateTimer();
+ // Reset the global progress values:
+ this._progress.loaded = this._progress.total = 0;
+ this._progress.bitrate = 0;
+ }
+ // Make sure the container objects for the .response() and
+ // .progress() methods on the data object are available
+ // and reset to their initial state:
+ this._initResponseObject(data);
+ this._initProgressObject(data);
+ data._progress.loaded = data.loaded = data.uploadedBytes || 0;
+ data._progress.total = data.total = this._getTotal(data.files) || 1;
+ data._progress.bitrate = data.bitrate = 0;
+ this._active += 1;
+ // Initialize the global progress values:
+ this._progress.loaded += data.loaded;
+ this._progress.total += data.total;
+ },
+
+ _onDone: function (result, textStatus, jqXHR, options) {
+ var total = options._progress.total,
+ response = options._response;
+ if (options._progress.loaded < total) {
+ // Create a progress event if no final progress event
+ // with loaded equaling total has been triggered:
+ this._onProgress($.Event('progress', {
+ lengthComputable: true,
+ loaded: total,
+ total: total
+ }), options);
+ }
+ response.result = options.result = result;
+ response.textStatus = options.textStatus = textStatus;
+ response.jqXHR = options.jqXHR = jqXHR;
+ this._trigger('done', null, options);
+ },
+
+ _onFail: function (jqXHR, textStatus, errorThrown, options) {
+ var response = options._response;
+ if (options.recalculateProgress) {
+ // Remove the failed (error or abort) file upload from
+ // the global progress calculation:
+ this._progress.loaded -= options._progress.loaded;
+ this._progress.total -= options._progress.total;
+ }
+ response.jqXHR = options.jqXHR = jqXHR;
+ response.textStatus = options.textStatus = textStatus;
+ response.errorThrown = options.errorThrown = errorThrown;
+ this._trigger('fail', null, options);
+ },
+
+ _onAlways: function (jqXHRorResult, textStatus, jqXHRorError, options) {
+ // jqXHRorResult, textStatus and jqXHRorError are added to the
+ // options object via done and fail callbacks
+ this._trigger('always', null, options);
+ },
+
+ _onSend: function (e, data) {
+ if (!data.submit) {
+ this._addConvenienceMethods(e, data);
+ }
+ var that = this,
+ jqXHR,
+ aborted,
+ slot,
+ pipe,
+ options = that._getAJAXSettings(data),
+ send = function () {
+ that._sending += 1;
+ // Set timer for bitrate progress calculation:
+ options._bitrateTimer = new that._BitrateTimer();
+ jqXHR = jqXHR || (
+ ((aborted || that._trigger(
+ 'send',
+ $.Event('send', {delegatedEvent: e}),
+ options
+ ) === false) &&
+ that._getXHRPromise(false, options.context, aborted)) ||
+ that._chunkedUpload(options) || $.ajax(options)
+ ).done(function (result, textStatus, jqXHR) {
+ that._onDone(result, textStatus, jqXHR, options);
+ }).fail(function (jqXHR, textStatus, errorThrown) {
+ that._onFail(jqXHR, textStatus, errorThrown, options);
+ }).always(function (jqXHRorResult, textStatus, jqXHRorError) {
+ that._onAlways(
+ jqXHRorResult,
+ textStatus,
+ jqXHRorError,
+ options
+ );
+ that._sending -= 1;
+ that._active -= 1;
+ if (options.limitConcurrentUploads &&
+ options.limitConcurrentUploads > that._sending) {
+ // Start the next queued upload,
+ // that has not been aborted:
+ var nextSlot = that._slots.shift();
+ while (nextSlot) {
+ if (that._getDeferredState(nextSlot) === 'pending') {
+ nextSlot.resolve();
+ break;
+ }
+ nextSlot = that._slots.shift();
+ }
+ }
+ if (that._active === 0) {
+ // The stop callback is triggered when all uploads have
+ // been completed, equivalent to the global ajaxStop event:
+ that._trigger('stop');
+ }
+ });
+ return jqXHR;
+ };
+ this._beforeSend(e, options);
+ if (this.options.sequentialUploads ||
+ (this.options.limitConcurrentUploads &&
+ this.options.limitConcurrentUploads <= this._sending)) {
+ if (this.options.limitConcurrentUploads > 1) {
+ slot = $.Deferred();
+ this._slots.push(slot);
+ pipe = slot.then(send);
+ } else {
+ this._sequence = this._sequence.then(send, send);
+ pipe = this._sequence;
+ }
+ // Return the piped Promise object, enhanced with an abort method,
+ // which is delegated to the jqXHR object of the current upload,
+ // and jqXHR callbacks mapped to the equivalent Promise methods:
+ pipe.abort = function () {
+ aborted = [undefined, 'abort', 'abort'];
+ if (!jqXHR) {
+ if (slot) {
+ slot.rejectWith(options.context, aborted);
+ }
+ return send();
+ }
+ return jqXHR.abort();
+ };
+ return this._enhancePromise(pipe);
+ }
+ return send();
+ },
+
+ _onAdd: function (e, data) {
+ var that = this,
+ result = true,
+ options = $.extend({}, this.options, data),
+ files = data.files,
+ filesLength = files.length,
+ limit = options.limitMultiFileUploads,
+ limitSize = options.limitMultiFileUploadSize,
+ overhead = options.limitMultiFileUploadSizeOverhead,
+ batchSize = 0,
+ paramName = this._getParamName(options),
+ paramNameSet,
+ paramNameSlice,
+ fileSet,
+ i,
+ j = 0;
+ if (!filesLength) {
+ return false;
+ }
+ if (limitSize && files[0].size === undefined) {
+ limitSize = undefined;
+ }
+ if (!(options.singleFileUploads || limit || limitSize) ||
+ !this._isXHRUpload(options)) {
+ fileSet = [files];
+ paramNameSet = [paramName];
+ } else if (!(options.singleFileUploads || limitSize) && limit) {
+ fileSet = [];
+ paramNameSet = [];
+ for (i = 0; i < filesLength; i += limit) {
+ fileSet.push(files.slice(i, i + limit));
+ paramNameSlice = paramName.slice(i, i + limit);
+ if (!paramNameSlice.length) {
+ paramNameSlice = paramName;
+ }
+ paramNameSet.push(paramNameSlice);
+ }
+ } else if (!options.singleFileUploads && limitSize) {
+ fileSet = [];
+ paramNameSet = [];
+ for (i = 0; i < filesLength; i = i + 1) {
+ batchSize += files[i].size + overhead;
+ if (i + 1 === filesLength ||
+ ((batchSize + files[i + 1].size + overhead) > limitSize) ||
+ (limit && i + 1 - j >= limit)) {
+ fileSet.push(files.slice(j, i + 1));
+ paramNameSlice = paramName.slice(j, i + 1);
+ if (!paramNameSlice.length) {
+ paramNameSlice = paramName;
+ }
+ paramNameSet.push(paramNameSlice);
+ j = i + 1;
+ batchSize = 0;
+ }
+ }
+ } else {
+ paramNameSet = paramName;
+ }
+ data.originalFiles = files;
+ $.each(fileSet || files, function (index, element) {
+ var newData = $.extend({}, data);
+ newData.files = fileSet ? element : [element];
+ newData.paramName = paramNameSet[index];
+ that._initResponseObject(newData);
+ that._initProgressObject(newData);
+ that._addConvenienceMethods(e, newData);
+ result = that._trigger(
+ 'add',
+ $.Event('add', {delegatedEvent: e}),
+ newData
+ );
+ return result;
+ });
+ return result;
+ },
+
+ _replaceFileInput: function (data) {
+ var input = data.fileInput,
+ inputClone = input.clone(true),
+ restoreFocus = input.is(document.activeElement);
+ // Add a reference for the new cloned file input to the data argument:
+ data.fileInputClone = inputClone;
+ $('').append(inputClone)[0].reset();
+ // Detaching allows to insert the fileInput on another form
+ // without loosing the file input value:
+ input.after(inputClone).detach();
+ // If the fileInput had focus before it was detached,
+ // restore focus to the inputClone.
+ if (restoreFocus) {
+ inputClone.focus();
+ }
+ // Avoid memory leaks with the detached file input:
+ $.cleanData(input.unbind('remove'));
+ // Replace the original file input element in the fileInput
+ // elements set with the clone, which has been copied including
+ // event handlers:
+ this.options.fileInput = this.options.fileInput.map(function (i, el) {
+ if (el === input[0]) {
+ return inputClone[0];
+ }
+ return el;
+ });
+ // If the widget has been initialized on the file input itself,
+ // override this.element with the file input clone:
+ if (input[0] === this.element[0]) {
+ this.element = inputClone;
+ }
+ },
+
+ _handleFileTreeEntry: function (entry, path) {
+ var that = this,
+ dfd = $.Deferred(),
+ entries = [],
+ dirReader,
+ errorHandler = function (e) {
+ if (e && !e.entry) {
+ e.entry = entry;
+ }
+ // Since $.when returns immediately if one
+ // Deferred is rejected, we use resolve instead.
+ // This allows valid files and invalid items
+ // to be returned together in one set:
+ dfd.resolve([e]);
+ },
+ successHandler = function (entries) {
+ that._handleFileTreeEntries(
+ entries,
+ path + entry.name + '/'
+ ).done(function (files) {
+ dfd.resolve(files);
+ }).fail(errorHandler);
+ },
+ readEntries = function () {
+ dirReader.readEntries(function (results) {
+ if (!results.length) {
+ successHandler(entries);
+ } else {
+ entries = entries.concat(results);
+ readEntries();
+ }
+ }, errorHandler);
+ };
+ path = path || '';
+ if (entry.isFile) {
+ if (entry._file) {
+ // Workaround for Chrome bug #149735
+ entry._file.relativePath = path;
+ dfd.resolve(entry._file);
+ } else {
+ entry.file(function (file) {
+ file.relativePath = path;
+ dfd.resolve(file);
+ }, errorHandler);
+ }
+ } else if (entry.isDirectory) {
+ dirReader = entry.createReader();
+ readEntries();
+ } else {
+ // Return an empy list for file system items
+ // other than files or directories:
+ dfd.resolve([]);
+ }
+ return dfd.promise();
+ },
+
+ _handleFileTreeEntries: function (entries, path) {
+ var that = this;
+ return $.when.apply(
+ $,
+ $.map(entries, function (entry) {
+ return that._handleFileTreeEntry(entry, path);
+ })
+ ).then(function () {
+ return Array.prototype.concat.apply(
+ [],
+ arguments
+ );
+ });
+ },
+
+ _getDroppedFiles: function (dataTransfer) {
+ dataTransfer = dataTransfer || {};
+ var items = dataTransfer.items;
+ if (items && items.length && (items[0].webkitGetAsEntry ||
+ items[0].getAsEntry)) {
+ return this._handleFileTreeEntries(
+ $.map(items, function (item) {
+ var entry;
+ if (item.webkitGetAsEntry) {
+ entry = item.webkitGetAsEntry();
+ if (entry) {
+ // Workaround for Chrome bug #149735:
+ entry._file = item.getAsFile();
+ }
+ return entry;
+ }
+ return item.getAsEntry();
+ })
+ );
+ }
+ return $.Deferred().resolve(
+ $.makeArray(dataTransfer.files)
+ ).promise();
+ },
+
+ _getSingleFileInputFiles: function (fileInput) {
+ fileInput = $(fileInput);
+ var entries = fileInput.prop('webkitEntries') ||
+ fileInput.prop('entries'),
+ files,
+ value;
+ if (entries && entries.length) {
+ return this._handleFileTreeEntries(entries);
+ }
+ files = $.makeArray(fileInput.prop('files'));
+ if (!files.length) {
+ value = fileInput.prop('value');
+ if (!value) {
+ return $.Deferred().resolve([]).promise();
+ }
+ // If the files property is not available, the browser does not
+ // support the File API and we add a pseudo File object with
+ // the input value as name with path information removed:
+ files = [{name: value.replace(/^.*\\/, '')}];
+ } else if (files[0].name === undefined && files[0].fileName) {
+ // File normalization for Safari 4 and Firefox 3:
+ $.each(files, function (index, file) {
+ file.name = file.fileName;
+ file.size = file.fileSize;
+ });
+ }
+ return $.Deferred().resolve(files).promise();
+ },
+
+ _getFileInputFiles: function (fileInput) {
+ if (!(fileInput instanceof $) || fileInput.length === 1) {
+ return this._getSingleFileInputFiles(fileInput);
+ }
+ return $.when.apply(
+ $,
+ $.map(fileInput, this._getSingleFileInputFiles)
+ ).then(function () {
+ return Array.prototype.concat.apply(
+ [],
+ arguments
+ );
+ });
+ },
+
+ _onChange: function (e) {
+ var that = this,
+ data = {
+ fileInput: $(e.target),
+ form: $(e.target.form)
+ };
+ this._getFileInputFiles(data.fileInput).always(function (files) {
+ data.files = files;
+ if (that.options.replaceFileInput) {
+ that._replaceFileInput(data);
+ }
+ if (that._trigger(
+ 'change',
+ $.Event('change', {delegatedEvent: e}),
+ data
+ ) !== false) {
+ that._onAdd(e, data);
+ }
+ });
+ },
+
+ _onPaste: function (e) {
+ var items = e.originalEvent && e.originalEvent.clipboardData &&
+ e.originalEvent.clipboardData.items,
+ data = {files: []};
+ if (items && items.length) {
+ $.each(items, function (index, item) {
+ var file = item.getAsFile && item.getAsFile();
+ if (file) {
+ data.files.push(file);
+ }
+ });
+ if (this._trigger(
+ 'paste',
+ $.Event('paste', {delegatedEvent: e}),
+ data
+ ) !== false) {
+ this._onAdd(e, data);
+ }
+ }
+ },
+
+ _onDrop: function (e) {
+ e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer;
+ var that = this,
+ dataTransfer = e.dataTransfer,
+ data = {};
+ if (dataTransfer && dataTransfer.files && dataTransfer.files.length) {
+ e.preventDefault();
+ this._getDroppedFiles(dataTransfer).always(function (files) {
+ data.files = files;
+ if (that._trigger(
+ 'drop',
+ $.Event('drop', {delegatedEvent: e}),
+ data
+ ) !== false) {
+ that._onAdd(e, data);
+ }
+ });
+ }
+ },
+
+ _onDragOver: getDragHandler('dragover'),
+
+ _onDragEnter: getDragHandler('dragenter'),
+
+ _onDragLeave: getDragHandler('dragleave'),
+
+ _initEventHandlers: function () {
+ if (this._isXHRUpload(this.options)) {
+ this._on(this.options.dropZone, {
+ dragover: this._onDragOver,
+ drop: this._onDrop,
+ // event.preventDefault() on dragenter is required for IE10+:
+ dragenter: this._onDragEnter,
+ // dragleave is not required, but added for completeness:
+ dragleave: this._onDragLeave
+ });
+ this._on(this.options.pasteZone, {
+ paste: this._onPaste
+ });
+ }
+ if ($.support.fileInput) {
+ this._on(this.options.fileInput, {
+ change: this._onChange
+ });
+ }
+ },
+
+ _destroyEventHandlers: function () {
+ this._off(this.options.dropZone, 'dragenter dragleave dragover drop');
+ this._off(this.options.pasteZone, 'paste');
+ this._off(this.options.fileInput, 'change');
+ },
+
+ _destroy: function () {
+ this._destroyEventHandlers();
+ },
+
+ _setOption: function (key, value) {
+ var reinit = $.inArray(key, this._specialOptions) !== -1;
+ if (reinit) {
+ this._destroyEventHandlers();
+ }
+ this._super(key, value);
+ if (reinit) {
+ this._initSpecialOptions();
+ this._initEventHandlers();
+ }
+ },
+
+ _initSpecialOptions: function () {
+ var options = this.options;
+ if (options.fileInput === undefined) {
+ options.fileInput = this.element.is('input[type="file"]') ?
+ this.element : this.element.find('input[type="file"]');
+ } else if (!(options.fileInput instanceof $)) {
+ options.fileInput = $(options.fileInput);
+ }
+ if (!(options.dropZone instanceof $)) {
+ options.dropZone = $(options.dropZone);
+ }
+ if (!(options.pasteZone instanceof $)) {
+ options.pasteZone = $(options.pasteZone);
+ }
+ },
+
+ _getRegExp: function (str) {
+ var parts = str.split('/'),
+ modifiers = parts.pop();
+ parts.shift();
+ return new RegExp(parts.join('/'), modifiers);
+ },
+
+ _isRegExpOption: function (key, value) {
+ return key !== 'url' && $.type(value) === 'string' &&
+ /^\/.*\/[igm]{0,3}$/.test(value);
+ },
+
+ _initDataAttributes: function () {
+ var that = this,
+ options = this.options,
+ data = this.element.data();
+ // Initialize options set via HTML5 data-attributes:
+ $.each(
+ this.element[0].attributes,
+ function (index, attr) {
+ var key = attr.name.toLowerCase(),
+ value;
+ if (/^data-/.test(key)) {
+ // Convert hyphen-ated key to camelCase:
+ key = key.slice(5).replace(/-[a-z]/g, function (str) {
+ return str.charAt(1).toUpperCase();
+ });
+ value = data[key];
+ if (that._isRegExpOption(key, value)) {
+ value = that._getRegExp(value);
+ }
+ options[key] = value;
+ }
+ }
+ );
+ },
+
+ _create: function () {
+ this._initDataAttributes();
+ this._initSpecialOptions();
+ this._slots = [];
+ this._sequence = this._getXHRPromise(true);
+ this._sending = this._active = 0;
+ this._initProgressObject(this);
+ this._initEventHandlers();
+ },
+
+ // This method is exposed to the widget API and allows to query
+ // the number of active uploads:
+ active: function () {
+ return this._active;
+ },
+
+ // This method is exposed to the widget API and allows to query
+ // the widget upload progress.
+ // It returns an object with loaded, total and bitrate properties
+ // for the running uploads:
+ progress: function () {
+ return this._progress;
+ },
+
+ // This method is exposed to the widget API and allows adding files
+ // using the fileupload API. The data parameter accepts an object which
+ // must have a files property and can contain additional options:
+ // .fileupload('add', {files: filesList});
+ add: function (data) {
+ var that = this;
+ if (!data || this.options.disabled) {
+ return;
+ }
+ if (data.fileInput && !data.files) {
+ this._getFileInputFiles(data.fileInput).always(function (files) {
+ data.files = files;
+ that._onAdd(null, data);
+ });
+ } else {
+ data.files = $.makeArray(data.files);
+ this._onAdd(null, data);
+ }
+ },
+
+ // This method is exposed to the widget API and allows sending files
+ // using the fileupload API. The data parameter accepts an object which
+ // must have a files or fileInput property and can contain additional options:
+ // .fileupload('send', {files: filesList});
+ // The method returns a Promise object for the file upload call.
+ send: function (data) {
+ if (data && !this.options.disabled) {
+ if (data.fileInput && !data.files) {
+ var that = this,
+ dfd = $.Deferred(),
+ promise = dfd.promise(),
+ jqXHR,
+ aborted;
+ promise.abort = function () {
+ aborted = true;
+ if (jqXHR) {
+ return jqXHR.abort();
+ }
+ dfd.reject(null, 'abort', 'abort');
+ return promise;
+ };
+ this._getFileInputFiles(data.fileInput).always(
+ function (files) {
+ if (aborted) {
+ return;
+ }
+ if (!files.length) {
+ dfd.reject();
+ return;
+ }
+ data.files = files;
+ jqXHR = that._onSend(null, data);
+ jqXHR.then(
+ function (result, textStatus, jqXHR) {
+ dfd.resolve(result, textStatus, jqXHR);
+ },
+ function (jqXHR, textStatus, errorThrown) {
+ dfd.reject(jqXHR, textStatus, errorThrown);
+ }
+ );
+ }
+ );
+ return this._enhancePromise(promise);
+ }
+ data.files = $.makeArray(data.files);
+ if (data.files.length) {
+ return this._onSend(null, data);
+ }
+ }
+ return this._getXHRPromise(false, data && data.context);
+ }
+
+ });
+
+}));
diff --git a/static/js/jquery.iframe-transport.js b/static/js/jquery.iframe-transport.js
new file mode 100644
index 0000000..8d25c46
--- /dev/null
+++ b/static/js/jquery.iframe-transport.js
@@ -0,0 +1,224 @@
+/*
+ * jQuery Iframe Transport Plugin
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2011, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * https://opensource.org/licenses/MIT
+ */
+
+/* global define, require, window, document, JSON */
+
+;(function (factory) {
+ 'use strict';
+ if (typeof define === 'function' && define.amd) {
+ // Register as an anonymous AMD module:
+ define(['jquery'], factory);
+ } else if (typeof exports === 'object') {
+ // Node/CommonJS:
+ factory(require('jquery'));
+ } else {
+ // Browser globals:
+ factory(window.jQuery);
+ }
+}(function ($) {
+ 'use strict';
+
+ // Helper variable to create unique names for the transport iframes:
+ var counter = 0,
+ jsonAPI = $,
+ jsonParse = 'parseJSON';
+
+ if ('JSON' in window && 'parse' in JSON) {
+ jsonAPI = JSON;
+ jsonParse = 'parse';
+ }
+
+ // The iframe transport accepts four additional options:
+ // options.fileInput: a jQuery collection of file input fields
+ // options.paramName: the parameter name for the file form data,
+ // overrides the name property of the file input field(s),
+ // can be a string or an array of strings.
+ // options.formData: an array of objects with name and value properties,
+ // equivalent to the return data of .serializeArray(), e.g.:
+ // [{name: 'a', value: 1}, {name: 'b', value: 2}]
+ // options.initialIframeSrc: the URL of the initial iframe src,
+ // by default set to "javascript:false;"
+ $.ajaxTransport('iframe', function (options) {
+ if (options.async) {
+ // javascript:false as initial iframe src
+ // prevents warning popups on HTTPS in IE6:
+ /*jshint scripturl: true */
+ var initialIframeSrc = options.initialIframeSrc || 'javascript:false;',
+ /*jshint scripturl: false */
+ form,
+ iframe,
+ addParamChar;
+ return {
+ send: function (_, completeCallback) {
+ form = $('');
+ form.attr('accept-charset', options.formAcceptCharset);
+ addParamChar = /\?/.test(options.url) ? '&' : '?';
+ // XDomainRequest only supports GET and POST:
+ if (options.type === 'DELETE') {
+ options.url = options.url + addParamChar + '_method=DELETE';
+ options.type = 'POST';
+ } else if (options.type === 'PUT') {
+ options.url = options.url + addParamChar + '_method=PUT';
+ options.type = 'POST';
+ } else if (options.type === 'PATCH') {
+ options.url = options.url + addParamChar + '_method=PATCH';
+ options.type = 'POST';
+ }
+ // IE versions below IE8 cannot set the name property of
+ // elements that have already been added to the DOM,
+ // so we set the name along with the iframe HTML markup:
+ counter += 1;
+ iframe = $(
+ ''
+ ).bind('load', function () {
+ var fileInputClones,
+ paramNames = $.isArray(options.paramName) ?
+ options.paramName : [options.paramName];
+ iframe
+ .unbind('load')
+ .bind('load', function () {
+ var response;
+ // Wrap in a try/catch block to catch exceptions thrown
+ // when trying to access cross-domain iframe contents:
+ try {
+ response = iframe.contents();
+ // Google Chrome and Firefox do not throw an
+ // exception when calling iframe.contents() on
+ // cross-domain requests, so we unify the response:
+ if (!response.length || !response[0].firstChild) {
+ throw new Error();
+ }
+ } catch (e) {
+ response = undefined;
+ }
+ // The complete callback returns the
+ // iframe content document as response object:
+ completeCallback(
+ 200,
+ 'success',
+ {'iframe': response}
+ );
+ // Fix for IE endless progress bar activity bug
+ // (happens on form submits to iframe targets):
+ $('')
+ .appendTo(form);
+ window.setTimeout(function () {
+ // Removing the form in a setTimeout call
+ // allows Chrome's developer tools to display
+ // the response result
+ form.remove();
+ }, 0);
+ });
+ form
+ .prop('target', iframe.prop('name'))
+ .prop('action', options.url)
+ .prop('method', options.type);
+ if (options.formData) {
+ $.each(options.formData, function (index, field) {
+ $('')
+ .prop('name', field.name)
+ .val(field.value)
+ .appendTo(form);
+ });
+ }
+ if (options.fileInput && options.fileInput.length &&
+ options.type === 'POST') {
+ fileInputClones = options.fileInput.clone();
+ // Insert a clone for each file input field:
+ options.fileInput.after(function (index) {
+ return fileInputClones[index];
+ });
+ if (options.paramName) {
+ options.fileInput.each(function (index) {
+ $(this).prop(
+ 'name',
+ paramNames[index] || options.paramName
+ );
+ });
+ }
+ // Appending the file input fields to the hidden form
+ // removes them from their original location:
+ form
+ .append(options.fileInput)
+ .prop('enctype', 'multipart/form-data')
+ // enctype must be set as encoding for IE:
+ .prop('encoding', 'multipart/form-data');
+ // Remove the HTML5 form attribute from the input(s):
+ options.fileInput.removeAttr('form');
+ }
+ form.submit();
+ // Insert the file input fields at their original location
+ // by replacing the clones with the originals:
+ if (fileInputClones && fileInputClones.length) {
+ options.fileInput.each(function (index, input) {
+ var clone = $(fileInputClones[index]);
+ // Restore the original name and form properties:
+ $(input)
+ .prop('name', clone.prop('name'))
+ .attr('form', clone.attr('form'));
+ clone.replaceWith(input);
+ });
+ }
+ });
+ form.append(iframe).appendTo(document.body);
+ },
+ abort: function () {
+ if (iframe) {
+ // javascript:false as iframe src aborts the request
+ // and prevents warning popups on HTTPS in IE6.
+ // concat is used to avoid the "Script URL" JSLint error:
+ iframe
+ .unbind('load')
+ .prop('src', initialIframeSrc);
+ }
+ if (form) {
+ form.remove();
+ }
+ }
+ };
+ }
+ });
+
+ // The iframe transport returns the iframe content document as response.
+ // The following adds converters from iframe to text, json, html, xml
+ // and script.
+ // Please note that the Content-Type for JSON responses has to be text/plain
+ // or text/html, if the browser doesn't include application/json in the
+ // Accept header, else IE will show a download dialog.
+ // The Content-Type for XML responses on the other hand has to be always
+ // application/xml or text/xml, so IE properly parses the XML response.
+ // See also
+ // https://github.com/blueimp/jQuery-File-Upload/wiki/Setup#content-type-negotiation
+ $.ajaxSetup({
+ converters: {
+ 'iframe text': function (iframe) {
+ return iframe && $(iframe[0].body).text();
+ },
+ 'iframe json': function (iframe) {
+ return iframe && jsonAPI[jsonParse]($(iframe[0].body).text());
+ },
+ 'iframe html': function (iframe) {
+ return iframe && $(iframe[0].body).html();
+ },
+ 'iframe xml': function (iframe) {
+ var xmlDoc = iframe && iframe[0];
+ return xmlDoc && $.isXMLDoc(xmlDoc) ? xmlDoc :
+ $.parseXML((xmlDoc.XMLDocument && xmlDoc.XMLDocument.xml) ||
+ $(xmlDoc.body).html());
+ },
+ 'iframe script': function (iframe) {
+ return iframe && $.globalEval($(iframe[0].body).text());
+ }
+ }
+ });
+
+}));
diff --git a/static/js/jquery.ui.widget.js b/static/js/jquery.ui.widget.js
new file mode 100644
index 0000000..a4131b2
--- /dev/null
+++ b/static/js/jquery.ui.widget.js
@@ -0,0 +1,748 @@
+/*! jQuery UI - v1.12.1 - 2018-02-10
+ * http://jqueryui.com
+ * Includes: widget.js
+ * Copyright jQuery Foundation and other contributors; Licensed MIT */
+
+(function( factory ) {
+ if ( typeof define === "function" && define.amd ) {
+
+ // AMD. Register as an anonymous module.
+ define([ "jquery" ], factory );
+ } else {
+
+ // Browser globals
+ factory( jQuery );
+ }
+}(function( $ ) {
+
+ $.ui = $.ui || {};
+
+ var version = $.ui.version = "1.12.1";
+
+
+ /*!
+ * jQuery UI Widget 1.12.1
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+
+ //>>label: Widget
+ //>>group: Core
+ //>>description: Provides a factory for creating stateful widgets with a common API.
+ //>>docs: http://api.jqueryui.com/jQuery.widget/
+ //>>demos: http://jqueryui.com/widget/
+
+
+
+ var widgetUuid = 0;
+ var widgetSlice = Array.prototype.slice;
+
+ $.cleanData = ( function( orig ) {
+ return function( elems ) {
+ var events, elem, i;
+ for ( i = 0; ( elem = elems[ i ] ) != null; i++ ) {
+ try {
+
+ // Only trigger remove when necessary to save time
+ events = $._data( elem, "events" );
+ if ( events && events.remove ) {
+ $( elem ).triggerHandler( "remove" );
+ }
+
+ // Http://bugs.jquery.com/ticket/8235
+ } catch ( e ) {}
+ }
+ orig( elems );
+ };
+ } )( $.cleanData );
+
+ $.widget = function( name, base, prototype ) {
+ var existingConstructor, constructor, basePrototype;
+
+ // ProxiedPrototype allows the provided prototype to remain unmodified
+ // so that it can be used as a mixin for multiple widgets (#8876)
+ var proxiedPrototype = {};
+
+ var namespace = name.split( "." )[ 0 ];
+ name = name.split( "." )[ 1 ];
+ var fullName = namespace + "-" + name;
+
+ if ( !prototype ) {
+ prototype = base;
+ base = $.Widget;
+ }
+
+ if ( $.isArray( prototype ) ) {
+ prototype = $.extend.apply( null, [ {} ].concat( prototype ) );
+ }
+
+ // Create selector for plugin
+ $.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) {
+ return !!$.data( elem, fullName );
+ };
+
+ $[ namespace ] = $[ namespace ] || {};
+ existingConstructor = $[ namespace ][ name ];
+ constructor = $[ namespace ][ name ] = function( options, element ) {
+
+ // Allow instantiation without "new" keyword
+ if ( !this._createWidget ) {
+ return new constructor( options, element );
+ }
+
+ // Allow instantiation without initializing for simple inheritance
+ // must use "new" keyword (the code above always passes args)
+ if ( arguments.length ) {
+ this._createWidget( options, element );
+ }
+ };
+
+ // Extend with the existing constructor to carry over any static properties
+ $.extend( constructor, existingConstructor, {
+ version: prototype.version,
+
+ // Copy the object used to create the prototype in case we need to
+ // redefine the widget later
+ _proto: $.extend( {}, prototype ),
+
+ // Track widgets that inherit from this widget in case this widget is
+ // redefined after a widget inherits from it
+ _childConstructors: []
+ } );
+
+ basePrototype = new base();
+
+ // We need to make the options hash a property directly on the new instance
+ // otherwise we'll modify the options hash on the prototype that we're
+ // inheriting from
+ basePrototype.options = $.widget.extend( {}, basePrototype.options );
+ $.each( prototype, function( prop, value ) {
+ if ( !$.isFunction( value ) ) {
+ proxiedPrototype[ prop ] = value;
+ return;
+ }
+ proxiedPrototype[ prop ] = ( function() {
+ function _super() {
+ return base.prototype[ prop ].apply( this, arguments );
+ }
+
+ function _superApply( args ) {
+ return base.prototype[ prop ].apply( this, args );
+ }
+
+ return function() {
+ var __super = this._super;
+ var __superApply = this._superApply;
+ var returnValue;
+
+ this._super = _super;
+ this._superApply = _superApply;
+
+ returnValue = value.apply( this, arguments );
+
+ this._super = __super;
+ this._superApply = __superApply;
+
+ return returnValue;
+ };
+ } )();
+ } );
+ constructor.prototype = $.widget.extend( basePrototype, {
+
+ // TODO: remove support for widgetEventPrefix
+ // always use the name + a colon as the prefix, e.g., draggable:start
+ // don't prefix for widgets that aren't DOM-based
+ widgetEventPrefix: existingConstructor ? ( basePrototype.widgetEventPrefix || name ) : name
+ }, proxiedPrototype, {
+ constructor: constructor,
+ namespace: namespace,
+ widgetName: name,
+ widgetFullName: fullName
+ } );
+
+ // If this widget is being redefined then we need to find all widgets that
+ // are inheriting from it and redefine all of them so that they inherit from
+ // the new version of this widget. We're essentially trying to replace one
+ // level in the prototype chain.
+ if ( existingConstructor ) {
+ $.each( existingConstructor._childConstructors, function( i, child ) {
+ var childPrototype = child.prototype;
+
+ // Redefine the child widget using the same prototype that was
+ // originally used, but inherit from the new version of the base
+ $.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor,
+ child._proto );
+ } );
+
+ // Remove the list of existing child constructors from the old constructor
+ // so the old child constructors can be garbage collected
+ delete existingConstructor._childConstructors;
+ } else {
+ base._childConstructors.push( constructor );
+ }
+
+ $.widget.bridge( name, constructor );
+
+ return constructor;
+ };
+
+ $.widget.extend = function( target ) {
+ var input = widgetSlice.call( arguments, 1 );
+ var inputIndex = 0;
+ var inputLength = input.length;
+ var key;
+ var value;
+
+ for ( ; inputIndex < inputLength; inputIndex++ ) {
+ for ( key in input[ inputIndex ] ) {
+ value = input[ inputIndex ][ key ];
+ if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) {
+
+ // Clone objects
+ if ( $.isPlainObject( value ) ) {
+ target[ key ] = $.isPlainObject( target[ key ] ) ?
+ $.widget.extend( {}, target[ key ], value ) :
+
+ // Don't extend strings, arrays, etc. with objects
+ $.widget.extend( {}, value );
+
+ // Copy everything else by reference
+ } else {
+ target[ key ] = value;
+ }
+ }
+ }
+ }
+ return target;
+ };
+
+ $.widget.bridge = function( name, object ) {
+ var fullName = object.prototype.widgetFullName || name;
+ $.fn[ name ] = function( options ) {
+ var isMethodCall = typeof options === "string";
+ var args = widgetSlice.call( arguments, 1 );
+ var returnValue = this;
+
+ if ( isMethodCall ) {
+
+ // If this is an empty collection, we need to have the instance method
+ // return undefined instead of the jQuery instance
+ if ( !this.length && options === "instance" ) {
+ returnValue = undefined;
+ } else {
+ this.each( function() {
+ var methodValue;
+ var instance = $.data( this, fullName );
+
+ if ( options === "instance" ) {
+ returnValue = instance;
+ return false;
+ }
+
+ if ( !instance ) {
+ return $.error( "cannot call methods on " + name +
+ " prior to initialization; " +
+ "attempted to call method '" + options + "'" );
+ }
+
+ if ( !$.isFunction( instance[ options ] ) || options.charAt( 0 ) === "_" ) {
+ return $.error( "no such method '" + options + "' for " + name +
+ " widget instance" );
+ }
+
+ methodValue = instance[ options ].apply( instance, args );
+
+ if ( methodValue !== instance && methodValue !== undefined ) {
+ returnValue = methodValue && methodValue.jquery ?
+ returnValue.pushStack( methodValue.get() ) :
+ methodValue;
+ return false;
+ }
+ } );
+ }
+ } else {
+
+ // Allow multiple hashes to be passed on init
+ if ( args.length ) {
+ options = $.widget.extend.apply( null, [ options ].concat( args ) );
+ }
+
+ this.each( function() {
+ var instance = $.data( this, fullName );
+ if ( instance ) {
+ instance.option( options || {} );
+ if ( instance._init ) {
+ instance._init();
+ }
+ } else {
+ $.data( this, fullName, new object( options, this ) );
+ }
+ } );
+ }
+
+ return returnValue;
+ };
+ };
+
+ $.Widget = function( /* options, element */ ) {};
+ $.Widget._childConstructors = [];
+
+ $.Widget.prototype = {
+ widgetName: "widget",
+ widgetEventPrefix: "",
+ defaultElement: "",
+
+ options: {
+ classes: {},
+ disabled: false,
+
+ // Callbacks
+ create: null
+ },
+
+ _createWidget: function( options, element ) {
+ element = $( element || this.defaultElement || this )[ 0 ];
+ this.element = $( element );
+ this.uuid = widgetUuid++;
+ this.eventNamespace = "." + this.widgetName + this.uuid;
+
+ this.bindings = $();
+ this.hoverable = $();
+ this.focusable = $();
+ this.classesElementLookup = {};
+
+ if ( element !== this ) {
+ $.data( element, this.widgetFullName, this );
+ this._on( true, this.element, {
+ remove: function( event ) {
+ if ( event.target === element ) {
+ this.destroy();
+ }
+ }
+ } );
+ this.document = $( element.style ?
+
+ // Element within the document
+ element.ownerDocument :
+
+ // Element is window or document
+ element.document || element );
+ this.window = $( this.document[ 0 ].defaultView || this.document[ 0 ].parentWindow );
+ }
+
+ this.options = $.widget.extend( {},
+ this.options,
+ this._getCreateOptions(),
+ options );
+
+ this._create();
+
+ if ( this.options.disabled ) {
+ this._setOptionDisabled( this.options.disabled );
+ }
+
+ this._trigger( "create", null, this._getCreateEventData() );
+ this._init();
+ },
+
+ _getCreateOptions: function() {
+ return {};
+ },
+
+ _getCreateEventData: $.noop,
+
+ _create: $.noop,
+
+ _init: $.noop,
+
+ destroy: function() {
+ var that = this;
+
+ this._destroy();
+ $.each( this.classesElementLookup, function( key, value ) {
+ that._removeClass( value, key );
+ } );
+
+ // We can probably remove the unbind calls in 2.0
+ // all event bindings should go through this._on()
+ this.element
+ .off( this.eventNamespace )
+ .removeData( this.widgetFullName );
+ this.widget()
+ .off( this.eventNamespace )
+ .removeAttr( "aria-disabled" );
+
+ // Clean up events and states
+ this.bindings.off( this.eventNamespace );
+ },
+
+ _destroy: $.noop,
+
+ widget: function() {
+ return this.element;
+ },
+
+ option: function( key, value ) {
+ var options = key;
+ var parts;
+ var curOption;
+ var i;
+
+ if ( arguments.length === 0 ) {
+
+ // Don't return a reference to the internal hash
+ return $.widget.extend( {}, this.options );
+ }
+
+ if ( typeof key === "string" ) {
+
+ // Handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } }
+ options = {};
+ parts = key.split( "." );
+ key = parts.shift();
+ if ( parts.length ) {
+ curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] );
+ for ( i = 0; i < parts.length - 1; i++ ) {
+ curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {};
+ curOption = curOption[ parts[ i ] ];
+ }
+ key = parts.pop();
+ if ( arguments.length === 1 ) {
+ return curOption[ key ] === undefined ? null : curOption[ key ];
+ }
+ curOption[ key ] = value;
+ } else {
+ if ( arguments.length === 1 ) {
+ return this.options[ key ] === undefined ? null : this.options[ key ];
+ }
+ options[ key ] = value;
+ }
+ }
+
+ this._setOptions( options );
+
+ return this;
+ },
+
+ _setOptions: function( options ) {
+ var key;
+
+ for ( key in options ) {
+ this._setOption( key, options[ key ] );
+ }
+
+ return this;
+ },
+
+ _setOption: function( key, value ) {
+ if ( key === "classes" ) {
+ this._setOptionClasses( value );
+ }
+
+ this.options[ key ] = value;
+
+ if ( key === "disabled" ) {
+ this._setOptionDisabled( value );
+ }
+
+ return this;
+ },
+
+ _setOptionClasses: function( value ) {
+ var classKey, elements, currentElements;
+
+ for ( classKey in value ) {
+ currentElements = this.classesElementLookup[ classKey ];
+ if ( value[ classKey ] === this.options.classes[ classKey ] ||
+ !currentElements ||
+ !currentElements.length ) {
+ continue;
+ }
+
+ // We are doing this to create a new jQuery object because the _removeClass() call
+ // on the next line is going to destroy the reference to the current elements being
+ // tracked. We need to save a copy of this collection so that we can add the new classes
+ // below.
+ elements = $( currentElements.get() );
+ this._removeClass( currentElements, classKey );
+
+ // We don't use _addClass() here, because that uses this.options.classes
+ // for generating the string of classes. We want to use the value passed in from
+ // _setOption(), this is the new value of the classes option which was passed to
+ // _setOption(). We pass this value directly to _classes().
+ elements.addClass( this._classes( {
+ element: elements,
+ keys: classKey,
+ classes: value,
+ add: true
+ } ) );
+ }
+ },
+
+ _setOptionDisabled: function( value ) {
+ this._toggleClass( this.widget(), this.widgetFullName + "-disabled", null, !!value );
+
+ // If the widget is becoming disabled, then nothing is interactive
+ if ( value ) {
+ this._removeClass( this.hoverable, null, "ui-state-hover" );
+ this._removeClass( this.focusable, null, "ui-state-focus" );
+ }
+ },
+
+ enable: function() {
+ return this._setOptions( { disabled: false } );
+ },
+
+ disable: function() {
+ return this._setOptions( { disabled: true } );
+ },
+
+ _classes: function( options ) {
+ var full = [];
+ var that = this;
+
+ options = $.extend( {
+ element: this.element,
+ classes: this.options.classes || {}
+ }, options );
+
+ function processClassString( classes, checkOption ) {
+ var current, i;
+ for ( i = 0; i < classes.length; i++ ) {
+ current = that.classesElementLookup[ classes[ i ] ] || $();
+ if ( options.add ) {
+ current = $( $.unique( current.get().concat( options.element.get() ) ) );
+ } else {
+ current = $( current.not( options.element ).get() );
+ }
+ that.classesElementLookup[ classes[ i ] ] = current;
+ full.push( classes[ i ] );
+ if ( checkOption && options.classes[ classes[ i ] ] ) {
+ full.push( options.classes[ classes[ i ] ] );
+ }
+ }
+ }
+
+ this._on( options.element, {
+ "remove": "_untrackClassesElement"
+ } );
+
+ if ( options.keys ) {
+ processClassString( options.keys.match( /\S+/g ) || [], true );
+ }
+ if ( options.extra ) {
+ processClassString( options.extra.match( /\S+/g ) || [] );
+ }
+
+ return full.join( " " );
+ },
+
+ _untrackClassesElement: function( event ) {
+ var that = this;
+ $.each( that.classesElementLookup, function( key, value ) {
+ if ( $.inArray( event.target, value ) !== -1 ) {
+ that.classesElementLookup[ key ] = $( value.not( event.target ).get() );
+ }
+ } );
+ },
+
+ _removeClass: function( element, keys, extra ) {
+ return this._toggleClass( element, keys, extra, false );
+ },
+
+ _addClass: function( element, keys, extra ) {
+ return this._toggleClass( element, keys, extra, true );
+ },
+
+ _toggleClass: function( element, keys, extra, add ) {
+ add = ( typeof add === "boolean" ) ? add : extra;
+ var shift = ( typeof element === "string" || element === null ),
+ options = {
+ extra: shift ? keys : extra,
+ keys: shift ? element : keys,
+ element: shift ? this.element : element,
+ add: add
+ };
+ options.element.toggleClass( this._classes( options ), add );
+ return this;
+ },
+
+ _on: function( suppressDisabledCheck, element, handlers ) {
+ var delegateElement;
+ var instance = this;
+
+ // No suppressDisabledCheck flag, shuffle arguments
+ if ( typeof suppressDisabledCheck !== "boolean" ) {
+ handlers = element;
+ element = suppressDisabledCheck;
+ suppressDisabledCheck = false;
+ }
+
+ // No element argument, shuffle and use this.element
+ if ( !handlers ) {
+ handlers = element;
+ element = this.element;
+ delegateElement = this.widget();
+ } else {
+ element = delegateElement = $( element );
+ this.bindings = this.bindings.add( element );
+ }
+
+ $.each( handlers, function( event, handler ) {
+ function handlerProxy() {
+
+ // Allow widgets to customize the disabled handling
+ // - disabled as an array instead of boolean
+ // - disabled class as method for disabling individual parts
+ if ( !suppressDisabledCheck &&
+ ( instance.options.disabled === true ||
+ $( this ).hasClass( "ui-state-disabled" ) ) ) {
+ return;
+ }
+ return ( typeof handler === "string" ? instance[ handler ] : handler )
+ .apply( instance, arguments );
+ }
+
+ // Copy the guid so direct unbinding works
+ if ( typeof handler !== "string" ) {
+ handlerProxy.guid = handler.guid =
+ handler.guid || handlerProxy.guid || $.guid++;
+ }
+
+ var match = event.match( /^([\w:-]*)\s*(.*)$/ );
+ var eventName = match[ 1 ] + instance.eventNamespace;
+ var selector = match[ 2 ];
+
+ if ( selector ) {
+ delegateElement.on( eventName, selector, handlerProxy );
+ } else {
+ element.on( eventName, handlerProxy );
+ }
+ } );
+ },
+
+ _off: function( element, eventName ) {
+ eventName = ( eventName || "" ).split( " " ).join( this.eventNamespace + " " ) +
+ this.eventNamespace;
+ element.off( eventName ).off( eventName );
+
+ // Clear the stack to avoid memory leaks (#10056)
+ this.bindings = $( this.bindings.not( element ).get() );
+ this.focusable = $( this.focusable.not( element ).get() );
+ this.hoverable = $( this.hoverable.not( element ).get() );
+ },
+
+ _delay: function( handler, delay ) {
+ function handlerProxy() {
+ return ( typeof handler === "string" ? instance[ handler ] : handler )
+ .apply( instance, arguments );
+ }
+ var instance = this;
+ return setTimeout( handlerProxy, delay || 0 );
+ },
+
+ _hoverable: function( element ) {
+ this.hoverable = this.hoverable.add( element );
+ this._on( element, {
+ mouseenter: function( event ) {
+ this._addClass( $( event.currentTarget ), null, "ui-state-hover" );
+ },
+ mouseleave: function( event ) {
+ this._removeClass( $( event.currentTarget ), null, "ui-state-hover" );
+ }
+ } );
+ },
+
+ _focusable: function( element ) {
+ this.focusable = this.focusable.add( element );
+ this._on( element, {
+ focusin: function( event ) {
+ this._addClass( $( event.currentTarget ), null, "ui-state-focus" );
+ },
+ focusout: function( event ) {
+ this._removeClass( $( event.currentTarget ), null, "ui-state-focus" );
+ }
+ } );
+ },
+
+ _trigger: function( type, event, data ) {
+ var prop, orig;
+ var callback = this.options[ type ];
+
+ data = data || {};
+ event = $.Event( event );
+ event.type = ( type === this.widgetEventPrefix ?
+ type :
+ this.widgetEventPrefix + type ).toLowerCase();
+
+ // The original event may come from any element
+ // so we need to reset the target on the new event
+ event.target = this.element[ 0 ];
+
+ // Copy original event properties over to the new event
+ orig = event.originalEvent;
+ if ( orig ) {
+ for ( prop in orig ) {
+ if ( !( prop in event ) ) {
+ event[ prop ] = orig[ prop ];
+ }
+ }
+ }
+
+ this.element.trigger( event, data );
+ return !( $.isFunction( callback ) &&
+ callback.apply( this.element[ 0 ], [ event ].concat( data ) ) === false ||
+ event.isDefaultPrevented() );
+ }
+ };
+
+ $.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) {
+ $.Widget.prototype[ "_" + method ] = function( element, options, callback ) {
+ if ( typeof options === "string" ) {
+ options = { effect: options };
+ }
+
+ var hasOptions;
+ var effectName = !options ?
+ method :
+ options === true || typeof options === "number" ?
+ defaultEffect :
+ options.effect || defaultEffect;
+
+ options = options || {};
+ if ( typeof options === "number" ) {
+ options = { duration: options };
+ }
+
+ hasOptions = !$.isEmptyObject( options );
+ options.complete = callback;
+
+ if ( options.delay ) {
+ element.delay( options.delay );
+ }
+
+ if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) {
+ element[ method ]( options );
+ } else if ( effectName !== method && element[ effectName ] ) {
+ element[ effectName ]( options.duration, options.easing, callback );
+ } else {
+ element.queue( function( next ) {
+ $( this )[ method ]();
+ if ( callback ) {
+ callback.call( element[ 0 ] );
+ }
+ next();
+ } );
+ }
+ };
+ } );
+
+ var widget = $.widget;
+
+
+
+
+}));