/**
* 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('