From 041c172a554a97087d58465c40436047b7786bbd Mon Sep 17 00:00:00 2001 From: Lars Jung Date: Fri, 24 Feb 2012 16:31:47 +0100 Subject: [PATCH] Adds support for zipped download of htaccess restricted files. --- README.md | 1 + src/_h5ai/css/inc/nav.less | 22 +++++ src/_h5ai/js/inc/ZippedDownload.js | 109 ++++++++++++++++------- src/_h5ai/js/inc/lib/base64.js | 135 +++++++++++++++++++++++++++++ src/_h5ai/js/libs.js | 1 + src/_h5ai/php/api.php | 5 +- src/_h5ai/php/inc/H5ai.php | 15 ---- src/_h5ai/php/inc/ZipIt.php | 22 ++++- 8 files changed, 258 insertions(+), 52 deletions(-) create mode 100644 src/_h5ai/js/inc/lib/base64.js diff --git a/README.md b/README.md index 7b344991..ebd4c7ea 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ h5ai is provided under the terms of the [MIT License](http://github.com/lrsjng/h * adds optional filtering for displayed files and folders * updates design * improves zipped download +* adds support for zipped download of htaccess restricted files * changes h5ai.htaccess * custom headers/footers are now optional and disabled by default * fixes problems with folder recognition in the JS version diff --git a/src/_h5ai/css/inc/nav.less b/src/_h5ai/css/inc/nav.less index 88eec564..89c4f580 100644 --- a/src/_h5ai/css/inc/nav.less +++ b/src/_h5ai/css/inc/nav.less @@ -111,3 +111,25 @@ body > nav { } } } + + +#download-auth { + display: none; + position: fixed; + z-index: 5; + left: 0; + top: 0; + font-size: 0.85em; + .vert-gradient(rgb(241,241,241), rgb(228,228,228)); + border: 1px solid rgb(210,210,210); + + input { + display: block; + margin: 4px 6px; + border: 1px solid rgb(210,210,210); + font-family: Ubuntu, sans-serif; + color: #555; + background-color: rgba(255,255,255,1); + width: 100px; + } +} diff --git a/src/_h5ai/js/inc/ZippedDownload.js b/src/_h5ai/js/inc/ZippedDownload.js index 2f0a954e..45d7c1cc 100644 --- a/src/_h5ai/js/inc/ZippedDownload.js +++ b/src/_h5ai/js/inc/ZippedDownload.js @@ -8,6 +8,8 @@ $document = $(document), $selectionRect = $("#selection-rect"), selectedHrefsStr = "", + $download, $img, $downloadAuth, $downloadUser, $downloadPassword, + updateDownloadBtn = function () { var $selected = $("#extended a.selected"), @@ -23,6 +25,7 @@ $downloadBtn.show(); } else { $downloadBtn.hide(); + $downloadAuth.hide(); } }, selectionUpdate = function (event) { @@ -90,45 +93,91 @@ noSelection(event); } }, + failed = function () { + + $download.addClass('failed'); + setTimeout(function () { + $download.removeClass('failed'); + }, 1000); + }, + handleResponse = function (response) { + + + $download.removeClass('current'); + $img.attr('src', H5AI.core.image("download")); + + if (response) { + if (response.status === 'ok') { + window.location = H5AI.core.api() + '?action=getzip&id=' + response.id; + } else { + if (response.code === 401) { + $downloadAuth + .css({ + left: $download.offset().left, + top: $download.offset().top + $download.outerHeight() + }) + .show(); + $downloadUser.focus(); + } + failed(); + } + } else { + failed(); + } + }, + requestZipping = function (hrefsStr) { + + $download.addClass('current'); + $img.attr('src', H5AI.core.image("loading.gif", true)); + $.ajax({ + url: H5AI.core.api(), + data: { + action: 'zip', + hrefs: selectedHrefsStr + }, + type: 'POST', + dataType: 'json', + beforeSend: function (xhr) { + + var user = $downloadUser.val(), + password = $downloadPassword.val(); + + if (user) { + xhr.setRequestHeader ('Authorization', 'Basic ' + Base64.encode(user + ':' + password)); + } + }, + success: function (response) { + + handleResponse(response); + }, + failed: function () { + + handleResponse(); + } + }); + }, init = function () { if (H5AI.core.settings.zippedDownload) { $("
  • downloaddownload
  • ") .find("img").attr("src", H5AI.core.image("download")).end() - .find("a").click(function () { + .find("a").click(function (event) { - $('#download').addClass('current'); - $('#download img').attr('src', H5AI.core.image("loading.gif", true)); - $.ajax({ - url: H5AI.core.api(), - data: { - action: 'zip', - hrefs: selectedHrefsStr - }, - type: 'POST', - dataType: 'json', - success: function (response) { - - $('#download').removeClass('current'); - $('#download img').attr('src', H5AI.core.image("download")); - if (response.status === 'ok') { - window.location = H5AI.core.api() + '?action=getzip&id=' + response.id; - } else { - $('#download').addClass('failed'); - setTimeout(function () { - $('#download').removeClass('failed'); - }, 1000); - } - }, - failed: function () { - $('#download').removeClass('current'); - $('#download img').attr('src', H5AI.core.image("download")); - } - }); + event.preventDefault(); + $downloadAuth.hide(); + requestZipping(selectedHrefsStr); }).end() .appendTo($("#navbar")); + $("
    ") + .appendTo($("body")); - $("body>nav,body>footer,#tree").on("mousedown", noSelection); + $download = $('#download'); + $downloadAuth = $('#download-auth'); + $downloadUser = $('#download-auth-user'); + $downloadPassword = $('#download-auth-password'); + $img = $download.find('img'); + + $("body>nav,body>footer,#tree,input").on("mousedown", noSelection); $("#content").on("mousedown", "a", noSelectionUnlessCtrl); $document.on("mousedown", selectionStart); } diff --git a/src/_h5ai/js/inc/lib/base64.js b/src/_h5ai/js/inc/lib/base64.js new file mode 100644 index 00000000..61e4c290 --- /dev/null +++ b/src/_h5ai/js/inc/lib/base64.js @@ -0,0 +1,135 @@ +var Base64 = { + +// private property +_keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", + +// public method for encoding +encode : function (input) { + var output = ""; + var chr1, chr2, chr3, enc1, enc2, enc3, enc4; + var i = 0; + + input = Base64._utf8_encode(input); + + while (i < input.length) { + + chr1 = input.charCodeAt(i++); + chr2 = input.charCodeAt(i++); + chr3 = input.charCodeAt(i++); + + enc1 = chr1 >> 2; + enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); + enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); + enc4 = chr3 & 63; + + if (isNaN(chr2)) { + enc3 = enc4 = 64; + } else if (isNaN(chr3)) { + enc4 = 64; + } + + output = output + + this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) + + this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4); + + } + + return output; +}, + +// public method for decoding +decode : function (input) { + var output = ""; + var chr1, chr2, chr3; + var enc1, enc2, enc3, enc4; + var i = 0; + + input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); + + while (i < input.length) { + + enc1 = this._keyStr.indexOf(input.charAt(i++)); + enc2 = this._keyStr.indexOf(input.charAt(i++)); + enc3 = this._keyStr.indexOf(input.charAt(i++)); + enc4 = this._keyStr.indexOf(input.charAt(i++)); + + chr1 = (enc1 << 2) | (enc2 >> 4); + chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); + chr3 = ((enc3 & 3) << 6) | enc4; + + output = output + String.fromCharCode(chr1); + + if (enc3 != 64) { + output = output + String.fromCharCode(chr2); + } + if (enc4 != 64) { + output = output + String.fromCharCode(chr3); + } + + } + + output = Base64._utf8_decode(output); + + return output; + +}, + +// private method for UTF-8 encoding +_utf8_encode : function (string) { + string = string.replace(/\r\n/g,"\n"); + var utftext = ""; + + for (var n = 0; n < string.length; n++) { + + var c = string.charCodeAt(n); + + if (c < 128) { + utftext += String.fromCharCode(c); + } + else if((c > 127) && (c < 2048)) { + utftext += String.fromCharCode((c >> 6) | 192); + utftext += String.fromCharCode((c & 63) | 128); + } + else { + utftext += String.fromCharCode((c >> 12) | 224); + utftext += String.fromCharCode(((c >> 6) & 63) | 128); + utftext += String.fromCharCode((c & 63) | 128); + } + + } + + return utftext; +}, + +// private method for UTF-8 decoding +_utf8_decode : function (utftext) { + var string = ""; + var i = 0; + var c = c1 = c2 = 0; + + while ( i < utftext.length ) { + + c = utftext.charCodeAt(i); + + if (c < 128) { + string += String.fromCharCode(c); + i++; + } + else if((c > 191) && (c < 224)) { + c2 = utftext.charCodeAt(i+1); + string += String.fromCharCode(((c & 31) << 6) | (c2 & 63)); + i += 2; + } + else { + c2 = utftext.charCodeAt(i+1); + c3 = utftext.charCodeAt(i+2); + string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); + i += 3; + } + + } + + return string; +} + +} diff --git a/src/_h5ai/js/libs.js b/src/_h5ai/js/libs.js index 1df256fb..236cc1b0 100644 --- a/src/_h5ai/js/libs.js +++ b/src/_h5ai/js/libs.js @@ -6,6 +6,7 @@ // @include "inc/lib/jquery.qrcode.js" // @include "inc/lib/amplify.min.js" +// @include "inc/lib/base64.js" // @include "inc/lib/date.js" // @include "inc/main.js" diff --git a/src/_h5ai/php/api.php b/src/_h5ai/php/api.php index c2a8de1c..0edbd45a 100644 --- a/src/_h5ai/php/api.php +++ b/src/_h5ai/php/api.php @@ -118,10 +118,10 @@ else if ($action === "zip") { $hrefs = explode(":", trim($hrefs)); $zipFile = $zipit->zip($hrefs); - if ($zipFile) { + if (is_string($zipFile)) { $response = array('status' => 'ok', 'id' => basename($zipFile), 'size' => filesize($zipFile)); } else { - $response = array('status' => 'failed', 'msg' => 'none'); + $response = array('status' => 'failed', 'code' => $zipFile); } echo json_encode($response); } @@ -136,7 +136,6 @@ else if ($action === "getzip") { fail(2, "zipped file not found: " . $id, !file_exists($zipFile)); header("Content-Disposition: attachment; filename=\"h5ai-selection.zip\""); - // header("Content-Type: application/force-download"); header("Content-Type: application/octet-stream"); header("Content-Length: " . filesize($zipFile)); header("Connection: close"); diff --git a/src/_h5ai/php/inc/H5ai.php b/src/_h5ai/php/inc/H5ai.php index 4a70aced..bf8251a0 100644 --- a/src/_h5ai/php/inc/H5ai.php +++ b/src/_h5ai/php/inc/H5ai.php @@ -258,7 +258,6 @@ class H5ai { //return $this->cachedHttpCode($absHref); return $this->fetchHttpCode($absHref); - // return $this->guessHttpCode($absHref); } @@ -312,20 +311,6 @@ class H5ai { fclose($socket); return $code; } - - - public function guessHttpCode($absHref) { - - $indexFiles = array("index.html", "index.cgi", "index.pl", "index.php", "index.xhtml", "index.htm"); - $absPath = $this->getAbsPath($absHref); - $files = $this->readDir($absPath); - foreach ($files as $file) { - if (in_array($file, $indexFiles)) { - return 200; - } - } - return "h5ai"; - } } ?> \ No newline at end of file diff --git a/src/_h5ai/php/inc/ZipIt.php b/src/_h5ai/php/inc/ZipIt.php index cb67a953..a926b3bc 100644 --- a/src/_h5ai/php/inc/ZipIt.php +++ b/src/_h5ai/php/inc/ZipIt.php @@ -24,11 +24,19 @@ class ZipIt { foreach ($hrefs as $href) { $d = safe_dirname($href, true); $n = basename($href); - if ($this->h5ai->getHttpCode($d) === "h5ai" && !$this->h5ai->ignoreThisFile($n)) { + $code = $this->h5ai->getHttpCode($d); + if ($code == 401) { + return $code; + } + + if ($code == "h5ai" && !$this->h5ai->ignoreThisFile($n)) { $localFile = $this->h5ai->getAbsPath($href); $file = preg_replace("!^" . $this->h5ai->getRootAbsPath() . "!", "", $localFile); if (is_dir($localFile)) { - $this->zipDir($zip, $localFile, $file); + $rcode = $this->zipDir($zip, $localFile, $file); + if ($rcode == 401) { + return $rcode; + } } else { $this->zipFile($zip, $localFile, $file); } @@ -50,19 +58,25 @@ class ZipIt { private function zipDir($zip, $localDir, $dir) { - if ($this->h5ai->getHttpCode($this->h5ai->getAbsHref($localDir)) === "h5ai") { + $code = $this->h5ai->getHttpCode($this->h5ai->getAbsHref($localDir)); + + if ($code == 'h5ai') { $zip->addEmptyDir($dir); $files = $this->h5ai->readDir($localDir); foreach ($files as $file) { $localFile = $localDir . "/" . $file; $file = $dir . "/" . $file; if (is_dir($localFile)) { - $this->zipDir($zip, $localFile, $file); + $rcode = $this->zipDir($zip, $localFile, $file); + if ($rcode == 401) { + return $rcode; + } } else { $this->zipFile($zip, $localFile, $file); } } } + return code; } }