/**
* PrivateBin
*
* a zero-knowledge paste bin
*
* @see {@link https://github.com/PrivateBin/PrivateBin}
* @copyright 2012 Sébastien SAUVAGE ({@link http://sebsauvage.net})
* @license {@link https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License}
* @version 1.1
* @name PrivateBin
* @namespace
*/
/** global: Base64 */
/** global: FileReader */
/** global: RawDeflate */
/** global: history */
/** global: navigator */
/** global: prettyPrint */
/** global: prettyPrintOne */
/** global: showdown */
/** global: sjcl */
// Immediately start random number generator collector.
sjcl.random.startCollectors();
// main application start, called when DOM is fully loaded
jQuery(document).ready(function() {
// run main controller
$.PrivateBin.Controller.init();
});
jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
'use strict';
/**
* static Helper methods
*
* @name Helper
* @class
*/
var Helper = (function () {
var me = {};
/**
* character to HTML entity lookup table
*
* @see {@link https://github.com/janl/mustache.js/blob/master/mustache.js#L60}
* @name Helper.entityMap
* @private
* @enum {Object}
* @readonly
*/
var entityMap = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
'/': '/',
'`': '`',
'=': '='
};
/**
* cache for script location
*
* @name Helper.baseUri
* @private
* @enum {string|null}
*/
var baseUri = null;
/**
* converts a duration (in seconds) into human friendly approximation
*
* @name Helper.secondsToHuman
* @function
* @param {number} seconds
* @return {Array}
*/
me.secondsToHuman = function(seconds)
{
var v;
if (seconds < 60)
{
v = Math.floor(seconds);
return [v, 'second'];
}
if (seconds < 60 * 60)
{
v = Math.floor(seconds / 60);
return [v, 'minute'];
}
if (seconds < 60 * 60 * 24)
{
v = Math.floor(seconds / (60 * 60));
return [v, 'hour'];
}
// If less than 2 months, display in days:
if (seconds < 60 * 60 * 24 * 60)
{
v = Math.floor(seconds / (60 * 60 * 24));
return [v, 'day'];
}
v = Math.floor(seconds / (60 * 60 * 24 * 30));
return [v, 'month'];
}
/**
* checks if a string is valid text (and not onyl whitespace)
*
* @name Helper.isValidText
* @function
* @param {string} string
* @return {bool}
*/
me.isValidText = function(string)
{
return (string.length > 0 && $.trim(string) !== '')
}
/**
* text range selection
*
* @see {@link https://stackoverflow.com/questions/985272/jquery-selecting-text-in-an-element-akin-to-highlighting-with-your-mouse}
* @name Helper.selectText
* @function
* @param {HTMLElement} element
*/
me.selectText = function(element)
{
var range, selection;
// MS
if (document.body.createTextRange) {
range = document.body.createTextRange();
range.moveToElementText(element);
range.select();
} else if (window.getSelection){
selection = window.getSelection();
range = document.createRange();
range.selectNodeContents(element);
selection.removeAllRanges();
selection.addRange(range);
}
}
/**
* set text of a jQuery element (required for IE),
*
* @name Helper.setElementText
* @function
* @param {jQuery} $element - a jQuery element
* @param {string} text - the text to enter
*/
me.setElementText = function($element, text)
{
// For IE<10: Doesn't support white-space:pre-wrap; so we have to do this...
if ($('#oldienotice').is(':visible')) {
var html = me.htmlEntities(text).replace(/\n/ig, '\r\n
');
$element.html('
' + html + ''); } // for other (sane) browsers: else { $element.text(text); } } /** * convert URLs to clickable links. * URLs to handle: *
* magnet:?xt.1=urn:sha1:YNCKHTQCWBTRNJIV4WNAE52SJUQCZO5C&xt.2=urn:sha1:TXGCZQTH26NL6OUQAJJPFALHG2LTGBC7 * http://example.com:8800/zero/?6f09182b8ea51997#WtLEUO5Epj9UHAV9JFs+6pUQZp13TuspAUjnF+iM+dM= * http://user:example.com@localhost:8800/zero/?6f09182b8ea51997#WtLEUO5Epj9UHAV9JFs+6pUQZp13TuspAUjnF+iM+dM= ** * @name Helper.urls2links * @function * @param {Object} $element - a jQuery DOM element */ me.urls2links = function($element) { var markup = '$1'; $element.html( $element.html().replace( /((http|https|ftp):\/\/[\w?=&.\/-;#@~%+*-]+(?![\w\s?&.\/;#~%"=-]*>))/ig, markup ) ); $element.html( $element.html().replace( /((magnet):[\w?=&.\/-;#@~%+*-]+)/ig, markup ) ); } /** * minimal sprintf emulation for %s and %d formats * * Note that this function needs the parameters in the same order as the * format strings appear in the string, contrary to the original. * * @see {@link https://stackoverflow.com/questions/610406/javascript-equivalent-to-printf-string-format#4795914} * @name Helper.sprintf * @function * @param {string} format * @param {...*} args - one or multiple parameters injected into format string * @return {string} */ me.sprintf = function() { var args = Array.prototype.slice.call(arguments); var format = args[0], i = 1; return format.replace(/%(s|d)/g, function (m) { // m is the matched format, e.g. %s, %d var val = args[i]; // A switch statement so that the formatter can be extended. switch (m) { case '%d': val = parseFloat(val); if (isNaN(val)) { val = 0; } break; default: // Default is %s } ++i; return val; }); } /** * get value of cookie, if it was set, empty string otherwise * * @see {@link http://www.w3schools.com/js/js_cookies.asp} * @name Helper.getCookie * @function * @param {string} cname - may not be empty * @return {string} */ me.getCookie = function(cname) { var name = cname + '=', ca = document.cookie.split(';'); for (var i = 0; i < ca.length; ++i) { var c = ca[i]; while (c.charAt(0) === ' ') { c = c.substring(1); } if (c.indexOf(name) === 0) { return c.substring(name.length, c.length); } } return ''; } /** * get the current location (without search or hash part of the URL), * eg. http://example.com/path/?aaaa#bbbb --> http://example.com/path/ * * @name Helper.baseUri * @function * @return {string} */ me.baseUri = function() { // check for cached version if (baseUri !== null) { return baseUri; } baseUri = window.location.origin + window.location.pathname; return baseUri; } /** * convert all applicable characters to HTML entities * * @see {@link https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet#RULE_.231_-_HTML_Escape_Before_Inserting_Untrusted_Data_into_HTML_Element_Content} * @name Helper.htmlEntities * @function * @param {string} str * @return {string} escaped HTML */ me.htmlEntities = function(str) { return String(str).replace( /[&<>"'`=\/]/g, function(s) { return entityMap[s]; }); } /** * resets state, used for unit testing * * @name Helper.reset * @function */ me.reset = function() { baseUri = null; } me.attachmentHelpers = { attachmentData: undefined, file: undefined, $fileInput: undefined, init: function () { me.attachmentHelpers.$fileInput = $('#file'); me.attachmentHelpers.addDragDropHandler(); }, /* * Read file data as dataURL using the FileReader API * https://developer.mozilla.org/en-US/docs/Web/API/FileReader#readAsDataURL() */ readFileData: function (file) { if (typeof FileReader === 'undefined') { // revert loading status… AttachmentViewer.hideAttachment(); AttachmentViewer.hideAttachmentPreview(); Alert.showError(I18n._('Your browser does not support uploading encrypted files. Please use a newer browser.')); return; } var fileReader = new FileReader(); if (file === undefined) { file = me.attachmentHelpers.$fileInput[0].files[0]; $('#dragAndDropFileName').text(''); } else { $('#dragAndDropFileName').text(file.name); } me.attachmentHelpers.file = file; fileReader.onload = function (event) { var dataURL = event.target.result; me.attachmentHelpers.attachmentData = dataURL; if (Editor.isPreview()) { me.attachmentHelpers.handleAttachmentPreview(AttachmentViewer.$attachmentPreview, dataURL); } }; fileReader.readAsDataURL(file); }, /** * Handle the preview of files that can either be an image, video, audio or pdf element. * @argument {DOM Element} targetElement where the preview should be appended. * @argument {File Data} data of the file to be displayed. */ handleAttachmentPreview: function (targetElement, data) { if (data) { var mimeType = this.getMimeTypeFromDataURL(data); if (mimeType.match(/image\//i)) { this.showImagePreview(targetElement, data); } else if (mimeType.match(/video\//i)) { this.showVideoPreview(targetElement, data, mimeType); } else if (mimeType.match(/audio\//i)) { this.showAudioPreview(targetElement, data, mimeType); } else if (mimeType.match(/\/pdf/i)) { this.showPDFPreview(targetElement, data); } //else { //console.log("file but no image/video/audio/pdf"); //} } }, /** * Get Mime Type from a DataURL * * @param {type} dataURL * @returns Mime Type from a dataURL as obtained for a file using the FileReader API https://developer.mozilla.org/en-US/docs/Web/API/FileReader#readAsDataURL() */ getMimeTypeFromDataURL: function (dataURL) { return dataURL.slice(dataURL.indexOf('data:') + 5, dataURL.indexOf(';base64,')); }, showImagePreview: function (targetElement, image) { targetElement.html( $(document.createElement('img')) .attr('src', image) .attr('class', 'img-thumbnail') ); targetElement.removeClass('hidden'); }, showVideoPreview: function (targetElement, video, mimeType) { var videoPlayer = $(document.createElement('video')) .attr('controls', 'true') .attr('autoplay', 'true') .attr('loop', 'true') .attr('class', 'img-thumbnail'); videoPlayer.append($(document.createElement('source')) .attr('type', mimeType) .attr('src', video)); targetElement.html(videoPlayer); targetElement.removeClass('hidden'); }, showAudioPreview: function (targetElement, audio, mimeType) { var audioPlayer = $(document.createElement('audio')) .attr('controls', 'true') .attr('autoplay', 'true'); audioPlayer.append($(document.createElement('source')) .attr('type', mimeType) .attr('src', audio)); targetElement.html(audioPlayer); targetElement.removeClass('hidden'); }, showPDFPreview: function (targetElement, pdf) { //PDFs are only displayed if the filesize is smaller than about 1MB (after base64 encoding). //Bigger filesizes currently cause crashes in various browsers. //See also: https://code.google.com/p/chromium/issues/detail?id=69227 //Firefox crashes with files that are about 1.5MB //The performance with 1MB files is bareable if (pdf.length < 1398488) { //Fallback for browsers, that don't support the vh unit var clientHeight = $(window).height(); targetElement.html( $(document.createElement('embed')) .attr('src', pdf) .attr('type', 'application/pdf') .attr('class', 'pdfPreview') .css('height', clientHeight) ); targetElement.removeClass('hidden'); } else { Alert.showError(I18n._('File too large, to display a preview. Please download the attachment.')); } }, /** * Attaches the file attachment drag & drop handler to the page. * @returns {undefined} */ addDragDropHandler: function () { var fileInput = me.attachmentHelpers.$fileInput; if (typeof fileInput === 'undefined' || fileInput.length === 0) { return; } var ignoreDragDrop = function(event) { event.stopPropagation(); event.preventDefault(); }; var drop = function(event) { event.stopPropagation(); event.preventDefault(); if (fileInput) { var file = event.dataTransfer.files[0]; //Clear the file input: fileInput.wrap('