diff --git a/swh/web/ui/static/js/filedrop.js b/swh/web/ui/static/js/filedrop.js deleted file mode 100644 --- a/swh/web/ui/static/js/filedrop.js +++ /dev/null @@ -1,194 +0,0 @@ -/** - * Search page management - */ - - -$.fn.extend({ - /** - * Call on any HTMLElement to make that element the recipient of files - * drag & dropped into it. - * Files then have their sha1 checksum calculated - * and searched in SWH. - * Args: - * resultDiv: the table where the result should be displayed - * errorDiv: the element where the error message should be displayed - */ - filedrop: function(fileLister, searchForm) { - - return this.each(function() { - - var dragwin = $(this); - var fileshovering = false; - - dragwin.on('dragover', function(event) { - event.stopPropagation(); - event.preventDefault(); - }); - - dragwin.on('dragenter', function(event) { - event.stopPropagation(); - event.preventDefault(); - if (!fileshovering) { - dragwin.css("border-style", "solid"); - dragwin.css("box-shadow", "inset 0 3px 4px"); - fileshovering = true; - } - }); - - dragwin.on('dragover', function(event) { - event.stopPropagation(); - event.preventDefault(); - if (!fileshovering) { - dragwin.css("border-style", "solid"); - dragwin.css("box-shadow", "inset 0 3px 4px"); - fileshovering = true; - } - }); - - dragwin.on('dragleave', function(event) { - event.stopPropagation(); - event.preventDefault(); - if (fileshovering) { - dragwin.css("border-style", "dashed"); - dragwin.css("box-shadow", "none"); - fileshovering = false; - } - }); - - dragwin.on('drop', function(event) { - event.stopPropagation(); - event.preventDefault(); - if (fileshovering) { - dragwin.css("border-style", "dashed"); - dragwin.css("box-shadow", "none"); - fileshovering = false; - } - var myfiles = event.originalEvent.dataTransfer.files; - if (myfiles.length >= 1) { - handleFiles(myfiles, fileLister, searchForm); - } - }); - }); - }, - /** - * Call on a jQuery-selected input to make it sensitive to - * the reception of new files, and have it process received - * files. - * Args: - * fileLister: the element keeping track of the files - * searchForm: the form whose submission will POST the file - * information - */ - filedialog: function(fileLister, searchForm) { - return this.each(function() { - var elem = $(this); - elem.on('change', function(){ - handleFiles(this.files, fileLister, searchForm); - }); - }); - }, - /** - * Call on a jQuery-selected element to delegate its click - * event to the given input instead. - * Args: - * input: the element to be clicked when the caller is clicked. - */ - inputclick: function(input) { - return this.each(function() { - $(this).click(function(event) { - event.preventDefault(); - input.click(); - }); - }); - }, - /** - * Call on a form to intercept its submission event and - * check the validity of the text input if present before submitting - * the form. - * Args: - * textInput: the input to validate - * messageElement: the element where the warning will be written - */ - checkSubmission: function(textInput, messageElement) { - var CHECKSUM_RE = /^([0-9a-f]{40}|[0-9a-f]{64})$/i; - $(this).submit(function(event) { - event.preventDefault(); - var q = textInput.val(); - if (q && !q.match(CHECKSUM_RE)) { - messageElement.empty(); - messageElement.html('Please enter a valid SHA-1'); - } else { - searchForm.submit(); - } - }); - } -}); - - -var nameList = []; /** Avoid adding the same file twice **/ - -/** - * Start reading the supplied files to hash them and add them to the form, - * and add their names to the file lister pre-search. - * Args: - * myfiles: the file array - * fileLister: the element that will receive the file names - * searchForm: the form to which we add hidden inputs with the - * correct values - */ -function handleFiles(myfiles, fileLister, searchForm) { - for (var i = 0; i < myfiles.length; i++) { - var file = myfiles.item(i); - if (nameList.indexOf(file.name) == -1) { - nameList.push(file.name); - var fr = new FileReader(); - fileLister.append(make_row(file.name)); - bind_reader(fr, file.name, searchForm); - fr.readAsArrayBuffer(file); - } - } -}; - -/** - * Bind a given FileReader to hash the file contents when the file - * has been read - * Args: - * filereader: the FileReader object - * filename: the name of the file being read by the FileReader - * searchForm: the form the corresponding hidden input will be - * appended to - */ -function bind_reader(filereader, filename, searchForm) { - filereader.onloadend = function(evt) { - if (evt.target.readyState == FileReader.DONE){ - return fileReadDone(evt.target.result, filename, searchForm); - } - }; -} - -function make_row(name) { - return "
"+name+"
"; -} - -/** - * Hash the buffer contents with CryptoJS's SHA1 implementation, and - * append the result to the given form for submission. - * Args: - * buffer: the buffer to be hashed - * fname: the file name corresponding to the buffer - * searchForm: the form the inputs should be appended to - */ -function fileReadDone(buffer, fname, searchForm) { - var wordArray = CryptoJS.lib.WordArray.create(buffer); - var sha1 = CryptoJS.SHA1(wordArray); - /** - var git_hd = "blob " + wordArray.length + "\0"; - var git_Array = CryptoJS.enc.utf8.parse(git_hd).concat(wordArray); - var sha256 = CryptoJS.SHA256(wordArray); - var sha1_git = CryptoJS.SHA1(wordArray); - **/ - searchForm.append($("", {type: "hidden", - name: fname, - value: sha1} - )); -} diff --git a/swh/web/ui/static/js/search.js b/swh/web/ui/static/js/search.js new file mode 100644 --- /dev/null +++ b/swh/web/ui/static/js/search.js @@ -0,0 +1,242 @@ +/** + * Search page management + * Args: + * textForm: the form containing the text input, if any + * fileForm: the form containing the file input, if any + * messageElem: the element that should display search messages + */ +var SearchFormController = function(textForm, fileForm, messageElem) + +{ + this.textForm = textForm; + this.fileForm = fileForm; + this.messageElem = messageElem; + + // List of hashes to check against files being processed + this.hashed_already = { + 'sha1': {}, + 'sha256': {}, + 'sha1_git': {} + }; + this.algos = ['sha1', 'sha256', 'sha1_git']; + this.CHECKSUM_RE = /^([0-9a-f]{40}|[0-9a-f]{64})$/i; + var self = this; + + /** + * Show search messages on the page + * Args: + * msg: the message to show + */ + this.searchMessage = function(msg) { + self.messageElem.empty(); + self.messageElem.text(msg); + }; + + /** + * Setup the text field + * Args: + * textFormInput: the text form's input + */ + this.setupTextForm = function(textFormInput) { + self.textForm.submit(function(event) { + var q = textFormInput.val(); + if (!q) { + event.preventDefault(); + self.searchMessage("Please enter a SHA-1 or SHA-256 checksum."); + } + else if (q && !q.match(self.CHECKSUM_RE)) { + event.preventDefault(); + self.searchMessage("Invalid SHA-1 or SHA-256 checksum"); + } + }); + }; + + /** + * Setup the file drag&drop UI and hashing support. + * Args: + * fileDropElem: the element receptive to drag & drop + * hashedListerElem: the element that receives the hased file descriptions + * fileFormInput: the input that actually receives files + * clearButton: the button used to clear currently hashed files + */ + this.setupFileForm = function(fileDropElem, hashedListerElem, fileFormInput, clearButton) { + if (!FileReader || !CryptoJS) { + self.searchMessage("Client-side file hashing is not available for your browser."); + return; + } + + // Enable clicking on the text element for file picker + fileDropElem.click(function(event) { + event.preventDefault(); + fileFormInput.click(); + }); + + // Enable drag&drop + var makeDroppable = function(fileReceptionElt) { + var fileshovering = false; + + fileReceptionElt.on('dragover', function(event) { + event.stopPropagation(); + event.preventDefault(); + }); + + fileReceptionElt.on('dragenter', function(event) { + event.stopPropagation(); + event.preventDefault(); + if (!fileshovering) { + fileReceptionElt.css("border-style", "solid"); + fileReceptionElt.css("box-shadow", "inset 0 3px 4px"); + fileshovering = true; + } + }); + + fileReceptionElt.on('dragover', function(event) { + event.stopPropagation(); + event.preventDefault(); + if (!fileshovering) { + fileReceptionElt.css("border-style", "solid"); + fileReceptionElt.css("box-shadow", "inset 0 3px 4px"); + fileshovering = true; + } + }); + + fileReceptionElt.on('dragleave', function(event) { + event.stopPropagation(); + event.preventDefault(); + if (fileshovering) { + fileReceptionElt.css("border-style", "dashed"); + fileReceptionElt.css("box-shadow", "none"); + fileshovering = false; + } + }); + + fileReceptionElt.on('drop', function(event) { + event.stopPropagation(); + event.preventDefault(); + if (fileshovering) { + fileReceptionElt.css("border-style", "dashed"); + fileReceptionElt.css("box-shadow", "none"); + fileshovering = false; + } + var myfiles = event.originalEvent.dataTransfer.files; + readAndHash(myfiles); + }); + }; + makeDroppable(fileDropElem); + + // Connect input change and rehash + var makeInputChange = function(fileInput) { + return fileInput.each(function() { + $(this).on('change', function(){ + readAndHash(this.files); + }); + }); + }; + makeInputChange(fileFormInput); + + // Connect clear button + var makeClearButton = function(button) { + return button.each(function() { + $(this).click(function(event) { + event.preventDefault(); + hashedListerElem.empty(); + self.fileForm.children('.search-hidden').remove(); + self.hashed_already = { + 'sha1': {}, + 'sha256': {}, + 'sha1_git': {} + }; + }); + }); + }; + makeClearButton(clearButton); + + var readAndHash = function(filelist) { + for (var file_idx = 0; file_idx < filelist.length; file_idx++) { + var file = filelist.item(file_idx); + var fr = new FileReader(); + bindReader(fr, file.name); + fr.readAsArrayBuffer(file); + } + }; + + var bindReader = function(freader, fname) { + freader.onloadend = function(event) { + if (event.target.readyState == FileReader.DONE) + return dedupAndAdd(event.target.result, fname); + else + return null; + }; + }; + + /** + * Hash the buffer with SHA-1, SHA-1_GIT, SHA-256 + * Args: + * buffer: the buffer to hash + * fname: the file name corresponding to the buffer + * Returns: + * a dict of algo_hash: hash + */ + var hashBuffer = function (buffer, fname) { + function str2ab(header) { + var buf = new ArrayBuffer(header.length); + var view = new Uint8Array(buf); // byte view, all we need is ASCII + for (var idx = 0, len=header.length; idx < len; idx++) + view[idx] = header.charCodeAt(idx); + return buf; + } + + var content_array = CryptoJS.lib.WordArray.create(buffer); + var git_hd_str = 'blob ' + buffer.byteLength + '\0'; + var git_hd_buffer = str2ab(git_hd_str); + var git_hd_array = CryptoJS.lib.WordArray.create(git_hd_buffer); + + var sha1 = CryptoJS.SHA1(content_array); + var sha256 = CryptoJS.SHA256(content_array); + var sha1_git = CryptoJS.SHA1(git_hd_array.concat(content_array)); + return { + 'sha1': sha1 + '', + 'sha256': sha256 + '', + 'sha1_git': sha1_git + '' + }; + }; + + /** + * Hash the buffer and add it to the form if it is unique + * If not, display which file has the same content + * Args: + * buffer: the buffer to hash + * fname: the file name corresponding to the buffer + */ + var dedupAndAdd = function(buffer, fname) { + var hashes = hashBuffer(buffer); + var has_duplicate = false; + for (var algo_s in hashes) { + if (self.hashed_already[algo_s][hashes[algo_s]] != undefined) { + // Duplicate content -- fileLister addition only, as duplicate + hashedListerElem.append($('
') + .addClass('span3') + .text(fname + ': duplicate of ' + self.hashed_already[algo_s][hashes[algo_s]])); + has_duplicate = true; + break; + } + } + // First file read with this content -- fileLister and form addition + if (!has_duplicate) { + // Add to hashed list + for (var algo_c in self.hashed_already) + self.hashed_already[algo_c][hashes[algo_c]] = fname; + hashedListerElem.append($('
') + .addClass('span3') + .text(fname)); + var hashstring = JSON.stringify(hashes).replace('\"', '\''); + self.fileForm.append($("", {type: 'hidden', + class: 'search-hidden', + name: fname, + value: hashes['sha1']}// hashstring} + )); + + } + }; + }; +}; diff --git a/swh/web/ui/static/lib/jquery.js b/swh/web/ui/static/lib/jquery.js deleted file mode 120000 --- a/swh/web/ui/static/lib/jquery.js +++ /dev/null @@ -1 +0,0 @@ -/usr/share/javascript/jquery-flot/jquery.js \ No newline at end of file diff --git a/swh/web/ui/templates/search.html b/swh/web/ui/templates/search.html --- a/swh/web/ui/templates/search.html +++ b/swh/web/ui/templates/search.html @@ -2,63 +2,90 @@ {% block title %}Search SWH{% endblock %} {% block content %} - - + +
-
+

Search with SHA-1 or SHA-256:

+ +
+
+ + + + +
+
+ + +

Search with files

+
-
- -
-
- -
-
-
- -
- Drag and drop or click here to hash files and search for them. - Your files will NOT be uploaded, hashing is done locally. - Filesizes over 20Mb may be slow to process, use with care. -
-
-
+ + + +
+ Drag and drop or click here to hash files and search for them. + Your files will NOT be uploaded, hashing is done locally. + Filesizes over 20Mb may be slow to process, use with care. +
-
- +
- -
-
+ +
+ + + + {% if search_res is not none %} + {% if search_stats is not none %} -
{% endif %} - -
-
{% endblock %}