2012-04-22 12:45:45 +02:00
/ * *
2016-07-11 11:58:15 +02:00
* PrivateBin
2012-04-23 16:30:02 +02:00
*
2012-04-30 22:58:08 +02:00
* a zero - knowledge paste bin
*
2016-07-11 11:58:15 +02:00
* @ link https : //github.com/PrivateBin/PrivateBin
2012-04-30 22:58:08 +02:00
* @ copyright 2012 Sébastien SAUVAGE ( sebsauvage . net )
2016-07-19 13:56:52 +02:00
* @ license https : //www.opensource.org/licenses/zlib-license.php The zlib/libpng License
2016-12-26 12:13:50 +01:00
* @ version 1.1
2012-04-22 12:45:45 +02:00
* /
2012-04-21 21:59:45 +02:00
2015-09-06 13:07:46 +02:00
'use strict' ;
2016-07-11 15:47:42 +02:00
/** global: Base64 */
/** global: FileReader */
/** global: RawDeflate */
/** global: history */
/** global: navigator */
/** global: prettyPrint */
/** global: prettyPrintOne */
/** global: showdown */
/** global: sjcl */
2015-09-06 13:07:46 +02:00
2012-04-21 21:59:45 +02:00
// Immediately start random number generator collector.
sjcl . random . startCollectors ( ) ;
2015-09-06 13:07:46 +02:00
$ ( function ( ) {
2015-09-05 17:12:11 +02:00
/ * *
* static helper methods
* /
var helper = {
/ * *
2015-09-06 15:54:43 +02:00
* Converts a duration ( in seconds ) into human friendly approximation .
2015-09-05 17:12:11 +02:00
*
* @ param int seconds
2015-09-06 15:54:43 +02:00
* @ return array
2015-09-05 17:12:11 +02:00
* /
secondsToHuman : function ( seconds )
{
2016-07-11 15:47:42 +02:00
var v ;
2015-09-05 17:12:11 +02:00
if ( seconds < 60 )
{
2016-07-11 15:47:42 +02:00
v = Math . floor ( seconds ) ;
2015-09-06 15:54:43 +02:00
return [ v , 'second' ] ;
2015-09-05 17:12:11 +02:00
}
if ( seconds < 60 * 60 )
{
2016-07-11 15:47:42 +02:00
v = Math . floor ( seconds / 60 ) ;
2015-09-06 15:54:43 +02:00
return [ v , 'minute' ] ;
2015-09-05 17:12:11 +02:00
}
if ( seconds < 60 * 60 * 24 )
{
2016-07-11 15:47:42 +02:00
v = Math . floor ( seconds / ( 60 * 60 ) ) ;
2015-09-06 15:54:43 +02:00
return [ v , 'hour' ] ;
2015-09-05 17:12:11 +02:00
}
// If less than 2 months, display in days:
if ( seconds < 60 * 60 * 24 * 60 )
{
2016-07-11 15:47:42 +02:00
v = Math . floor ( seconds / ( 60 * 60 * 24 ) ) ;
2015-09-06 15:54:43 +02:00
return [ v , 'day' ] ;
2015-09-05 17:12:11 +02:00
}
2016-07-11 15:47:42 +02:00
v = Math . floor ( seconds / ( 60 * 60 * 24 * 30 ) ) ;
2015-09-06 15:54:43 +02:00
return [ v , 'month' ] ;
2015-09-05 17:12:11 +02:00
} ,
/ * *
* Converts an associative array to an encoded string
* for appending to the anchor .
*
* @ param object associative _array Object to be serialized
* @ return string
* /
hashToParameterString : function ( associativeArray )
{
var parameterString = '' ;
2016-07-19 14:13:52 +02:00
for ( var key in associativeArray )
2015-09-05 17:12:11 +02:00
{
if ( parameterString === '' )
{
parameterString = encodeURIComponent ( key ) ;
parameterString += '=' + encodeURIComponent ( associativeArray [ key ] ) ;
}
else
{
parameterString += '&' + encodeURIComponent ( key ) ;
parameterString += '=' + encodeURIComponent ( associativeArray [ key ] ) ;
}
}
// padding for URL shorteners
parameterString += '&p=p' ;
return parameterString ;
} ,
/ * *
* Converts a string to an associative array .
*
* @ param string parameter _string String containing parameters
* @ return object
* /
parameterStringToHash : function ( parameterString )
{
var parameterHash = { } ;
var parameterArray = parameterString . split ( '&' ) ;
for ( var i = 0 ; i < parameterArray . length ; i ++ )
{
var pair = parameterArray [ i ] . split ( '=' ) ;
var key = decodeURIComponent ( pair [ 0 ] ) ;
var value = decodeURIComponent ( pair [ 1 ] ) ;
parameterHash [ key ] = value ;
}
2012-04-22 12:45:45 +02:00
2015-09-05 17:12:11 +02:00
return parameterHash ;
} ,
2013-02-23 15:11:45 +01:00
2015-09-05 17:12:11 +02:00
/ * *
* Get an associative array of the parameters found in the anchor
*
* @ return object
* /
getParameterHash : function ( )
{
var hashIndex = window . location . href . indexOf ( '#' ) ;
if ( hashIndex >= 0 )
{
return this . parameterStringToHash ( window . location . href . substring ( hashIndex + 1 ) ) ;
}
else
{
return { } ;
}
} ,
/ * *
* Text range selection .
2016-07-19 13:56:52 +02:00
* From : https : //stackoverflow.com/questions/985272/jquery-selecting-text-in-an-element-akin-to-highlighting-with-your-mouse
2015-09-05 17:12:11 +02:00
*
* @ param string element : Indentifier of the element to select ( id = "" ) .
* /
selectText : function ( element )
{
var doc = document ,
text = doc . getElementById ( element ) ,
range ,
selection ;
// MS
if ( doc . body . createTextRange )
{
range = doc . body . createTextRange ( ) ;
range . moveToElementText ( text ) ;
range . select ( ) ;
}
// all others
else if ( window . getSelection )
{
selection = window . getSelection ( ) ;
range = doc . createRange ( ) ;
range . selectNodeContents ( text ) ;
selection . removeAllRanges ( ) ;
selection . addRange ( range ) ;
}
} ,
/ * *
* Set text of a DOM element ( required for IE )
* This is equivalent to element . text ( text )
*
* @ param object element : a DOM element .
* @ param string text : the text to enter .
* /
setElementText : function ( element , text )
{
2015-09-06 13:07:46 +02:00
// For IE<10: Doesn't support white-space:pre-wrap; so we have to do this...
2015-09-05 17:12:11 +02:00
if ( $ ( '#oldienotice' ) . is ( ':visible' ) ) {
var html = this . htmlEntities ( text ) . replace ( /\n/ig , '\r\n<br>' ) ;
element . html ( '<pre>' + html + '</pre>' ) ;
}
// for other (sane) browsers:
else
{
element . text ( text ) ;
}
} ,
2015-09-12 17:33:16 +02:00
/ * *
* replace last child of element with message
*
* @ param object element : a jQuery wrapped DOM element .
* @ param string message : the message to append .
* /
setMessage : function ( element , message )
{
var content = element . contents ( ) ;
if ( content . length > 0 )
{
content [ content . length - 1 ] . nodeValue = ' ' + message ;
}
else
{
this . setElementText ( element , message ) ;
}
} ,
2015-09-05 17:12:11 +02:00
/ * *
* Convert URLs to clickable links .
* URLs to handle :
* < code >
* magnet : ? xt . 1 = urn : sha1 : YNCKHTQCWBTRNJIV4WNAE52SJUQCZO5C & xt . 2 = urn : sha1 : TXGCZQTH26NL6OUQAJJPFALHG2LTGBC7
* http : //localhost:8800/zero/?6f09182b8ea51997#WtLEUO5Epj9UHAV9JFs+6pUQZp13TuspAUjnF+iM+dM=
* http : //user:password@localhost:8800/zero/?6f09182b8ea51997#WtLEUO5Epj9UHAV9JFs+6pUQZp13TuspAUjnF+iM+dM=
* < / c o d e >
*
* @ param object element : a jQuery DOM element .
* /
urls2links : function ( element )
{
2015-09-06 13:07:46 +02:00
var markup = '<a href="$1" rel="nofollow">$1</a>' ;
2015-09-05 17:12:11 +02:00
element . html (
element . html ( ) . replace (
/((http|https|ftp):\/\/[\w?=&.\/-;#@~%+-]+(?![\w\s?&.\/;#~%"=-]*>))/ig ,
2015-09-06 13:07:46 +02:00
markup
2015-09-05 17:12:11 +02:00
)
) ;
element . html (
element . html ( ) . replace (
/((magnet):[\w?=&.\/-;#@~%+-]+)/ig ,
2015-09-06 13:07:46 +02:00
markup
2015-09-05 17:12:11 +02:00
)
) ;
2015-09-06 13:07:46 +02:00
} ,
/ * *
* minimal sprintf emulation for % s and % d formats
2016-07-19 13:56:52 +02:00
* From : https : //stackoverflow.com/questions/610406/javascript-equivalent-to-printf-string-format#4795914
2015-09-06 13:07:46 +02:00
*
* @ param string format
* @ param mixed args one or multiple parameters injected into format string
* @ return string
* /
sprintf : function ( )
{
var args = arguments ;
2016-07-11 16:09:38 +02:00
if ( typeof arguments [ 0 ] === 'object' )
2016-07-11 15:47:42 +02:00
{
args = arguments [ 0 ] ;
}
2015-09-06 13:07:46 +02:00
var string = args [ 0 ] ,
i = 1 ;
return string . replace ( /%((%)|s|d)/g , function ( m ) {
// m is the matched format, e.g. %s, %d
2016-07-11 15:47:42 +02:00
var val ;
2015-09-06 13:07:46 +02:00
if ( m [ 2 ] ) {
val = m [ 2 ] ;
} else {
val = args [ i ] ;
// A switch statement so that the formatter can be extended.
2015-09-08 20:48:18 +02:00
switch ( m )
{
2015-09-06 13:07:46 +02:00
case '%d' :
val = parseFloat ( val ) ;
if ( isNaN ( val ) ) {
val = 0 ;
}
break ;
2016-07-11 15:47:42 +02:00
default :
// Default is %s
2015-09-06 13:07:46 +02:00
}
++ i ;
}
return val ;
} ) ;
2015-09-19 11:21:13 +02:00
} ,
2015-09-21 22:43:00 +02:00
2015-09-19 11:21:13 +02:00
/ * *
* get value of cookie , if it was set , empty string otherwise
* From : http : //www.w3schools.com/js/js_cookies.asp
*
* @ param string cname
* @ return string
* /
getCookie : function ( cname ) {
var name = cname + '=' ;
var ca = document . cookie . split ( ';' ) ;
2016-08-09 14:46:32 +02:00
for ( var i = 0 ; i < ca . length ; ++ i ) {
2015-09-19 11:21:13 +02:00
var c = ca [ i ] ;
2016-07-11 15:47:42 +02:00
while ( c . charAt ( 0 ) === ' ' ) c = c . substring ( 1 ) ;
if ( c . indexOf ( name ) === 0 )
{
return c . substring ( name . length , c . length ) ;
}
2015-09-19 11:21:13 +02:00
}
return '' ;
2016-07-19 16:12:11 +02:00
} ,
/ * *
* Convert all applicable characters to HTML entities .
* From : https : //github.com/janl/mustache.js/blob/master/mustache.js#L60
2016-12-25 13:04:06 +01:00
* Also : 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
2016-07-19 16:12:11 +02:00
*
2017-01-07 20:35:47 +01:00
* @ param string str
2016-07-19 16:12:11 +02:00
* @ return string escaped HTML
* /
htmlEntities : function ( str ) {
return String ( str ) . replace (
/[&<>"'`=\/]/g , function ( s ) {
return helper . entityMap [ s ] ;
} ) ;
} ,
/ * *
* character to HTML entity lookup table
* /
entityMap : {
'&' : '&' ,
'<' : '<' ,
'>' : '>' ,
'"' : '"' ,
"'" : ''' ,
'/' : '/' ,
'`' : '`' ,
'=' : '='
2015-09-21 22:43:00 +02:00
}
2015-09-05 17:12:11 +02:00
} ;
2015-09-06 13:07:46 +02:00
/ * *
* internationalization methods
* /
var i18n = {
2015-09-06 15:54:43 +02:00
/ * *
* supported languages , minus the built in 'en'
* /
2017-01-07 15:48:42 +01:00
supportedLanguages : [ 'de' , 'es' , 'fr' , 'it' , 'pl' , 'oc' , 'ru' , 'sl' , 'zh' ] ,
2015-09-06 15:54:43 +02:00
2015-09-06 13:07:46 +02:00
/ * *
* translate a string , alias for translate ( )
*
* @ param string $messageId
* @ param mixed args one or multiple parameters injected into placeholders
* @ return string
* /
_ : function ( )
{
return this . translate ( arguments ) ;
} ,
/ * *
* translate a string
*
* @ param string $messageId
* @ param mixed args one or multiple parameters injected into placeholders
* @ return string
* /
translate : function ( )
{
2016-07-11 16:09:38 +02:00
var args = arguments , messageId ;
if ( typeof arguments [ 0 ] === 'object' )
2016-07-11 15:47:42 +02:00
{
args = arguments [ 0 ] ;
}
2016-07-11 16:09:38 +02:00
var usesPlurals = $ . isArray ( args [ 0 ] ) ;
if ( usesPlurals )
2015-09-06 15:54:43 +02:00
{
// use the first plural form as messageId, otherwise the singular
messageId = ( args [ 0 ] . length > 1 ? args [ 0 ] [ 1 ] : args [ 0 ] [ 0 ] ) ;
}
else
{
messageId = args [ 0 ] ;
}
2016-07-11 15:47:42 +02:00
if ( messageId . length === 0 )
{
return messageId ;
}
2015-09-06 13:07:46 +02:00
if ( ! this . translations . hasOwnProperty ( messageId ) )
{
2016-07-11 15:47:42 +02:00
if ( this . language !== 'en' )
{
console . debug (
'Missing ' + this . language + ' translation for: ' + messageId
) ;
}
2015-09-06 15:54:43 +02:00
this . translations [ messageId ] = args [ 0 ] ;
}
if ( usesPlurals && $ . isArray ( this . translations [ messageId ] ) )
{
2016-07-11 15:47:42 +02:00
var n = parseInt ( args [ 1 ] || 1 , 10 ) ,
2015-09-08 20:48:18 +02:00
key = this . getPluralForm ( n ) ,
2015-09-06 15:54:43 +02:00
maxKey = this . translations [ messageId ] . length - 1 ;
2016-07-11 15:47:42 +02:00
if ( key > maxKey )
{
key = maxKey ;
}
2015-09-06 15:54:43 +02:00
args [ 0 ] = this . translations [ messageId ] [ key ] ;
args [ 1 ] = n ;
}
else
{
args [ 0 ] = this . translations [ messageId ] ;
2015-09-06 13:07:46 +02:00
}
return helper . sprintf ( args ) ;
} ,
2015-09-08 20:48:18 +02:00
/ * *
* per language functions to use to determine the plural form
* From : http : //localization-guide.readthedocs.org/en/latest/l10n/pluralforms.html
*
* @ param int number
* @ return int array key
* /
getPluralForm : function ( n ) {
switch ( this . language )
{
case 'fr' :
2016-04-26 20:21:30 +02:00
case 'zh' :
2015-09-08 20:48:18 +02:00
return ( n > 1 ? 1 : 0 ) ;
case 'pl' :
2017-01-01 14:35:39 +01:00
return ( n === 1 ? 0 : ( n % 10 >= 2 && n % 10 <= 4 && ( n % 100 < 10 || n % 100 >= 20 ) ? 1 : 2 ) ) ;
2016-12-16 10:21:15 +01:00
case 'ru' :
return ( n % 10 === 1 && n % 100 !== 11 ? 0 : ( n % 10 >= 2 && n % 10 <= 4 && ( n % 100 < 10 || n % 100 >= 20 ) ? 1 : 2 ) ) ;
2017-01-01 14:35:39 +01:00
case 'sl' :
return ( n % 100 === 1 ? 1 : ( n % 100 === 2 ? 2 : ( n % 100 === 3 || n % 100 === 4 ? 3 : 0 ) ) ) ;
// de, en, es, it
2015-09-08 20:48:18 +02:00
default :
2016-07-05 17:23:25 +02:00
return ( n !== 1 ? 1 : 0 ) ;
2015-09-08 20:48:18 +02:00
}
} ,
2015-09-06 15:54:43 +02:00
/ * *
* load translations into cache , then execute callback function
*
* @ param function callback
* /
2015-09-06 13:07:46 +02:00
loadTranslations : function ( callback )
{
2015-09-19 11:21:13 +02:00
var selectedLang = helper . getCookie ( 'lang' ) ;
var language = selectedLang . length > 0 ? selectedLang : ( navigator . language || navigator . userLanguage ) . substring ( 0 , 2 ) ;
2015-09-06 13:07:46 +02:00
// note that 'en' is built in, so no translation is necessary
2016-07-11 15:47:42 +02:00
if ( this . supportedLanguages . indexOf ( language ) === - 1 )
2015-09-08 20:48:18 +02:00
{
2015-09-06 13:07:46 +02:00
callback ( ) ;
2015-09-08 20:48:18 +02:00
}
else
{
$ . getJSON ( 'i18n/' + language + '.json' , function ( data ) {
i18n . language = language ;
i18n . translations = data ;
callback ( ) ;
} ) ;
}
2015-09-06 13:07:46 +02:00
} ,
2015-09-06 15:54:43 +02:00
/ * *
* built in language
* /
language : 'en' ,
/ * *
* translation cache
* /
2015-09-06 13:07:46 +02:00
translations : { }
2016-01-31 09:56:06 +01:00
} ;
2015-09-06 13:07:46 +02:00
2015-09-05 17:12:11 +02:00
/ * *
* filter methods
* /
var filter = {
/ * *
* Compress a message ( deflate compression ) . Returns base64 encoded data .
*
* @ param string message
* @ return base64 string data
* /
compress : function ( message )
{
return Base64 . toBase64 ( RawDeflate . deflate ( Base64 . utob ( message ) ) ) ;
} ,
/ * *
* Decompress a message compressed with compress ( ) .
*
* @ param base64 string data
* @ return string message
* /
decompress : function ( data )
{
return Base64 . btou ( RawDeflate . inflate ( Base64 . fromBase64 ( data ) ) ) ;
} ,
/ * *
* Compress , then encrypt message with key .
*
* @ param string key
* @ param string password
* @ param string message
* @ return encrypted string data
* /
cipher : function ( key , password , message )
{
2016-08-09 13:16:15 +02:00
// Galois Counter Mode, keysize 256 bit, authentication tag 128 bit
var options = { mode : 'gcm' , ks : 256 , ts : 128 } ;
2016-07-11 15:47:42 +02:00
if ( ( password || '' ) . trim ( ) . length === 0 )
2015-09-05 17:12:11 +02:00
{
2016-08-09 13:16:15 +02:00
return sjcl . encrypt ( key , this . compress ( message ) , options ) ;
2015-09-05 17:12:11 +02:00
}
2016-08-09 13:16:15 +02:00
return sjcl . encrypt ( key + sjcl . codec . hex . fromBits ( sjcl . hash . sha256 . hash ( password ) ) , this . compress ( message ) , options ) ;
2015-09-05 17:12:11 +02:00
} ,
/ * *
* Decrypt message with key , then decompress .
*
* @ param string key
* @ param string password
* @ param encrypted string data
* @ return string readable message
* /
decipher : function ( key , password , data )
{
2016-07-11 15:47:42 +02:00
if ( data !== undefined )
2015-09-05 17:12:11 +02:00
{
try
{
return this . decompress ( sjcl . decrypt ( key , data ) ) ;
}
catch ( err )
{
try
{
return this . decompress ( sjcl . decrypt ( key + sjcl . codec . hex . fromBits ( sjcl . hash . sha256 . hash ( password ) ) , data ) ) ;
}
2016-07-11 15:47:42 +02:00
catch ( e )
2015-09-05 17:12:11 +02:00
{ }
2015-08-31 21:14:12 +02:00
}
}
2015-09-05 17:12:11 +02:00
return '' ;
2015-08-22 17:23:41 +02:00
}
2015-09-05 17:12:11 +02:00
} ;
2016-07-11 11:58:15 +02:00
var privatebin = {
2015-09-27 20:34:39 +02:00
/ * *
* headers to send in AJAX requests
* /
headers : { 'X-Requested-With' : 'JSONHttpRequest' } ,
2016-08-18 15:09:58 +02:00
/ * *
* URL shortners create address
* /
shortenerUrl : '' ,
/ * *
* URL of newly created paste
* /
createdPasteUrl : '' ,
2015-09-05 17:12:11 +02:00
/ * *
* Get the current script location ( without search or hash part of the URL ) .
* eg . http : //server.com/zero/?aaaa#bbbb --> http://server.com/zero/
*
* @ return string current script location
* /
scriptLocation : function ( )
{
var scriptLocation = window . location . href . substring ( 0 , window . location . href . length
2015-09-06 13:07:46 +02:00
- window . location . search . length - window . location . hash . length ) ,
hashIndex = scriptLocation . indexOf ( '#' ) ;
2015-09-05 17:12:11 +02:00
if ( hashIndex !== - 1 )
{
scriptLocation = scriptLocation . substring ( 0 , hashIndex ) ;
}
return scriptLocation ;
} ,
/ * *
* Get the pastes unique identifier from the URL
2016-07-11 14:15:20 +02:00
* eg . http : //server.com/zero/?c05354954c49a487#c05354954c49a487 returns c05354954c49a487
2015-09-05 17:12:11 +02:00
*
* @ return string unique identifier
* /
pasteID : function ( )
{
return window . location . search . substring ( 1 ) ;
} ,
/ * *
* Return the deciphering key stored in anchor part of the URL
*
* @ return string key
* /
pageKey : function ( )
{
2015-09-06 13:07:46 +02:00
// Some web 2.0 services and redirectors add data AFTER the anchor
// (such as &utm_source=...). We will strip any additional data.
2012-04-21 21:59:45 +02:00
2015-09-06 13:07:46 +02:00
var key = window . location . hash . substring ( 1 ) , // Get key
i = key . indexOf ( '=' ) ;
2012-04-21 21:59:45 +02:00
2015-09-05 17:12:11 +02:00
// First, strip everything after the equal sign (=) which signals end of base64 string.
2016-07-11 15:47:42 +02:00
if ( i > - 1 )
{
key = key . substring ( 0 , i + 1 ) ;
}
2012-04-21 21:59:45 +02:00
2015-09-05 17:12:11 +02:00
// If the equal sign was not present, some parameters may remain:
i = key . indexOf ( '&' ) ;
2016-07-11 15:47:42 +02:00
if ( i > - 1 )
{
key = key . substring ( 0 , i ) ;
}
2012-04-21 21:59:45 +02:00
2015-09-05 17:12:11 +02:00
// Then add trailing equal sign if it's missing
2016-07-11 15:47:42 +02:00
if ( key . charAt ( key . length - 1 ) !== '=' )
{
key += '=' ;
}
2012-04-23 16:30:02 +02:00
2015-09-05 17:12:11 +02:00
return key ;
} ,
2012-04-21 21:59:45 +02:00
2015-09-05 17:12:11 +02:00
/ * *
2016-11-13 18:12:10 +01:00
* ask the user for the password and set it
2015-09-05 17:12:11 +02:00
* /
requestPassword : function ( )
{
2016-11-13 18:22:37 +01:00
if ( this . passwordModal . length === 0 ) {
2016-11-13 18:12:10 +01:00
var password = prompt ( i18n . _ ( 'Please enter the password for this paste:' ) , '' ) ;
if ( password === null )
{
throw 'password prompt canceled' ;
}
if ( password . length === 0 )
{
this . requestPassword ( ) ;
} else {
this . passwordInput . val ( password ) ;
this . displayMessages ( ) ;
}
} else {
this . passwordModal . modal ( ) ;
}
2015-09-05 17:12:11 +02:00
} ,
2015-09-12 17:33:16 +02:00
/ * *
* use given format on paste , defaults to plain text
*
* @ param string format
2016-05-22 16:18:57 +02:00
* @ param string text
2015-09-12 17:33:16 +02:00
* /
2016-05-22 16:18:57 +02:00
formatPaste : function ( format , text )
2015-09-12 17:33:16 +02:00
{
2016-05-22 16:18:57 +02:00
helper . setElementText ( this . clearText , text ) ;
helper . setElementText ( this . prettyPrint , text ) ;
2015-09-12 17:33:16 +02:00
switch ( format || 'plaintext' )
{
case 'markdown' :
2016-07-11 15:47:42 +02:00
if ( typeof showdown === 'object' )
2015-09-12 17:33:16 +02:00
{
2016-05-22 16:18:57 +02:00
showdown . setOption ( 'strikethrough' , true ) ;
showdown . setOption ( 'tables' , true ) ;
showdown . setOption ( 'tablesHeaderId' , true ) ;
var converter = new showdown . Converter ( ) ;
2015-09-12 17:33:16 +02:00
this . clearText . html (
2016-05-22 16:18:57 +02:00
converter . makeHtml ( text )
2015-09-12 17:33:16 +02:00
) ;
2016-12-13 23:30:28 +01:00
// add table classes from bootstrap css
this . clearText . find ( 'table' ) . addClass ( 'table-condensed table-bordered' ) ;
2016-12-12 17:37:51 +01:00
2015-09-16 20:49:28 +02:00
this . clearText . removeClass ( 'hidden' ) ;
2015-09-12 17:33:16 +02:00
}
2016-12-13 23:30:28 +01:00
this . prettyMessage . addClass ( 'hidden' ) ;
2015-09-12 17:33:16 +02:00
break ;
case 'syntaxhighlighting' :
2016-07-11 15:47:42 +02:00
if ( typeof prettyPrintOne === 'function' )
2016-07-11 11:09:41 +02:00
{
2016-07-11 15:47:42 +02:00
if ( typeof prettyPrint === 'function' )
{
prettyPrint ( ) ;
}
2016-07-11 11:09:41 +02:00
this . prettyPrint . html (
2016-07-19 16:12:11 +02:00
prettyPrintOne (
helper . htmlEntities ( text ) , null , true
)
2016-07-11 11:09:41 +02:00
) ;
2016-07-11 14:15:20 +02:00
}
2016-07-11 15:47:42 +02:00
// fall through, as the rest is the same
2015-09-12 17:33:16 +02:00
default :
// Convert URLs to clickable links.
helper . urls2links ( this . clearText ) ;
helper . urls2links ( this . prettyPrint ) ;
2015-09-16 20:49:28 +02:00
this . clearText . addClass ( 'hidden' ) ;
2016-07-11 15:47:42 +02:00
if ( format === 'plaintext' )
2015-10-18 22:16:15 +02:00
{
this . prettyPrint . css ( 'white-space' , 'pre-wrap' ) ;
this . prettyPrint . css ( 'word-break' , 'normal' ) ;
this . prettyPrint . removeClass ( 'prettyprint' ) ;
}
2015-09-16 20:49:28 +02:00
this . prettyMessage . removeClass ( 'hidden' ) ;
2015-09-12 17:33:16 +02:00
}
} ,
2015-09-05 17:12:11 +02:00
/ * *
* Show decrypted text in the display area , including discussion ( if open )
*
2016-11-13 18:12:10 +01:00
* @ param object paste ( optional ) object including comments to display ( items = array with keys ( 'data' , 'meta' )
2015-09-05 17:12:11 +02:00
* /
2016-11-13 18:12:10 +01:00
displayMessages : function ( paste )
2015-09-05 17:12:11 +02:00
{
2016-11-13 18:12:10 +01:00
paste = paste || $ . parseJSON ( this . cipherData . text ( ) ) ;
var key = this . pageKey ( ) ;
2015-09-05 17:12:11 +02:00
var password = this . passwordInput . val ( ) ;
if ( ! this . prettyPrint . hasClass ( 'prettyprinted' ) ) {
2016-11-13 18:12:10 +01:00
// Try to decrypt the paste.
2015-09-05 17:12:11 +02:00
try
{
2015-10-18 11:08:28 +02:00
if ( paste . attachment )
2015-09-16 22:51:48 +02:00
{
2015-10-18 11:08:28 +02:00
var attachment = filter . decipher ( key , password , paste . attachment ) ;
2016-07-11 15:47:42 +02:00
if ( attachment . length === 0 )
2015-09-18 12:33:10 +02:00
{
2016-07-11 15:47:42 +02:00
if ( password . length === 0 )
{
2016-11-13 18:12:10 +01:00
this . requestPassword ( ) ;
return ;
2016-07-11 15:47:42 +02:00
}
2015-10-18 11:08:28 +02:00
attachment = filter . decipher ( key , password , paste . attachment ) ;
2015-09-18 12:33:10 +02:00
}
2016-07-11 15:47:42 +02:00
if ( attachment . length === 0 )
{
throw 'failed to decipher attachment' ;
}
2015-09-18 12:33:10 +02:00
2015-10-18 11:08:28 +02:00
if ( paste . attachmentname )
2015-09-16 22:51:48 +02:00
{
2015-10-18 11:08:28 +02:00
var attachmentname = filter . decipher ( key , password , paste . attachmentname ) ;
2016-07-11 15:47:42 +02:00
if ( attachmentname . length > 0 )
{
this . attachmentLink . attr ( 'download' , attachmentname ) ;
}
2015-09-16 22:51:48 +02:00
}
2015-09-18 12:33:10 +02:00
this . attachmentLink . attr ( 'href' , attachment ) ;
this . attachment . removeClass ( 'hidden' ) ;
2015-09-21 22:43:00 +02:00
2015-09-18 21:41:50 +02:00
// if the attachment is an image, display it
var imagePrefix = 'data:image/' ;
2016-07-11 15:47:42 +02:00
if ( attachment . substring ( 0 , imagePrefix . length ) === imagePrefix )
2015-09-18 21:41:50 +02:00
{
this . image . html (
$ ( document . createElement ( 'img' ) )
. attr ( 'src' , attachment )
. attr ( 'class' , 'img-thumbnail' )
) ;
this . image . removeClass ( 'hidden' ) ;
}
2015-09-16 22:51:48 +02:00
}
2015-10-18 11:08:28 +02:00
var cleartext = filter . decipher ( key , password , paste . data ) ;
2016-07-11 15:47:42 +02:00
if ( cleartext . length === 0 && password . length === 0 && ! paste . attachment )
2015-09-18 12:33:10 +02:00
{
2016-11-13 18:12:10 +01:00
this . requestPassword ( ) ;
return ;
2015-09-18 12:33:10 +02:00
}
2016-07-11 15:47:42 +02:00
if ( cleartext . length === 0 && ! paste . attachment )
{
throw 'failed to decipher message' ;
}
2015-09-05 17:12:11 +02:00
2015-09-18 12:33:10 +02:00
this . passwordInput . val ( password ) ;
2015-09-18 21:41:50 +02:00
if ( cleartext . length > 0 )
{
2016-08-15 15:04:12 +02:00
$ ( '#pasteFormatter' ) . val ( paste . meta . formatter ) ;
2016-05-22 16:18:57 +02:00
this . formatPaste ( paste . meta . formatter , cleartext ) ;
2015-09-18 21:41:50 +02:00
}
2015-09-05 17:12:11 +02:00
}
catch ( err )
{
this . clearText . addClass ( 'hidden' ) ;
this . prettyMessage . addClass ( 'hidden' ) ;
this . cloneButton . addClass ( 'hidden' ) ;
2015-09-06 13:07:46 +02:00
this . showError ( i18n . _ ( 'Could not decrypt data (Wrong key?)' ) ) ;
2015-09-05 17:12:11 +02:00
return ;
}
}
2012-04-21 21:59:45 +02:00
2015-09-12 10:38:04 +02:00
// Display paste expiration / for your eyes only.
2015-10-18 11:08:28 +02:00
if ( paste . meta . expire _date )
2015-09-05 17:12:11 +02:00
{
2015-10-18 11:08:28 +02:00
var expiration = helper . secondsToHuman ( paste . meta . remaining _time ) ,
2015-09-06 15:54:43 +02:00
expirationLabel = [
'This document will expire in %d ' + expiration [ 1 ] + '.' ,
'This document will expire in %d ' + expiration [ 1 ] + 's.'
] ;
2015-09-12 17:33:16 +02:00
helper . setMessage ( this . remainingTime , i18n . _ ( expirationLabel , expiration [ 0 ] ) ) ;
2015-09-05 17:12:11 +02:00
this . remainingTime . removeClass ( 'foryoureyesonly' )
2015-09-12 10:38:04 +02:00
. removeClass ( 'hidden' ) ;
2015-09-05 17:12:11 +02:00
}
2015-10-18 11:08:28 +02:00
if ( paste . meta . burnafterreading )
2015-09-05 17:12:11 +02:00
{
2015-10-11 21:22:00 +02:00
// unfortunately many web servers don't support DELETE (and PUT) out of the box
2015-09-27 20:34:39 +02:00
$ . ajax ( {
2015-10-11 21:22:00 +02:00
type : 'POST' ,
url : this . scriptLocation ( ) + '?' + this . pasteID ( ) ,
data : { deletetoken : 'burnafterreading' } ,
2015-09-27 20:34:39 +02:00
dataType : 'json' ,
headers : this . headers
} )
2015-09-05 17:12:11 +02:00
. fail ( function ( ) {
2016-07-11 11:58:15 +02:00
privatebin . showError ( i18n . _ ( 'Could not delete the paste, it was not stored in burn after reading mode.' ) ) ;
2015-09-05 17:12:11 +02:00
} ) ;
2015-09-12 17:33:16 +02:00
helper . setMessage ( this . remainingTime , i18n . _ (
2015-09-12 10:38:04 +02:00
'FOR YOUR EYES ONLY. Don\'t close this window, this message can\'t be displayed again.'
2015-09-12 17:33:16 +02:00
) ) ;
2015-09-05 17:12:11 +02:00
this . remainingTime . addClass ( 'foryoureyesonly' )
2015-09-12 10:38:04 +02:00
. removeClass ( 'hidden' ) ;
2015-09-05 17:12:11 +02:00
// Discourage cloning (as it can't really be prevented).
this . cloneButton . addClass ( 'hidden' ) ;
}
2012-04-23 16:30:02 +02:00
2015-09-05 17:12:11 +02:00
// If the discussion is opened on this paste, display it.
2015-10-18 11:08:28 +02:00
if ( paste . meta . opendiscussion )
2015-09-05 17:12:11 +02:00
{
this . comments . html ( '' ) ;
// iterate over comments
2015-10-18 11:08:28 +02:00
for ( var i = 0 ; i < paste . comments . length ; ++ i )
2015-09-05 17:12:11 +02:00
{
var place = this . comments ;
2015-10-18 11:08:28 +02:00
var comment = paste . comments [ i ] ;
2016-07-11 15:47:42 +02:00
var commenttext = filter . decipher ( key , password , comment . data ) ;
2015-09-05 17:12:11 +02:00
// If parent comment exists, display below (CSS will automatically shift it right.)
2015-10-18 11:38:48 +02:00
var cname = '#comment_' + comment . parentid ;
2015-09-05 17:12:11 +02:00
// If the element exists in page
if ( $ ( cname ) . length )
{
place = $ ( cname ) ;
}
2015-10-18 11:38:48 +02:00
var divComment = $ ( '<article><div class="comment" id="comment_' + comment . id + '">'
2015-09-05 17:12:11 +02:00
+ '<div class="commentmeta"><span class="nickname"></span><span class="commentdate"></span></div><div class="commentdata"></div>'
2015-09-06 13:07:46 +02:00
+ '<button class="btn btn-default btn-sm">' + i18n . _ ( 'Reply' ) + '</button>'
2015-09-05 17:12:11 +02:00
+ '</div></article>' ) ;
2015-10-18 11:38:48 +02:00
divComment . find ( 'button' ) . click ( { commentid : comment . id } , $ . proxy ( this . openReply , this ) ) ;
2016-07-11 15:47:42 +02:00
helper . setElementText ( divComment . find ( 'div.commentdata' ) , commenttext ) ;
2015-09-05 17:12:11 +02:00
// Convert URLs to clickable links in comment.
helper . urls2links ( divComment . find ( 'div.commentdata' ) ) ;
// Try to get optional nickname:
var nick = filter . decipher ( key , password , comment . meta . nickname ) ;
if ( nick . length > 0 )
{
divComment . find ( 'span.nickname' ) . text ( nick ) ;
}
else
{
2015-09-06 13:07:46 +02:00
divComment . find ( 'span.nickname' ) . html ( '<i>' + i18n . _ ( 'Anonymous' ) + '</i>' ) ;
2015-09-05 17:12:11 +02:00
}
divComment . find ( 'span.commentdate' )
2015-09-06 13:07:46 +02:00
. text ( ' (' + ( new Date ( comment . meta . postdate * 1000 ) . toLocaleString ( ) ) + ')' )
2015-10-18 11:38:48 +02:00
. attr ( 'title' , 'CommentID: ' + comment . id ) ;
2015-09-05 17:12:11 +02:00
// If an avatar is available, display it.
if ( comment . meta . vizhash )
{
divComment . find ( 'span.nickname' )
2015-09-06 13:07:46 +02:00
. before (
'<img src="' + comment . meta . vizhash + '" class="vizhash" title="' +
i18n . _ ( 'Anonymous avatar (Vizhash of the IP address)' ) + '" /> '
) ;
2015-09-05 17:12:11 +02:00
}
place . append ( divComment ) ;
}
2015-09-06 13:07:46 +02:00
var divComment = $ (
'<div class="comment"><button class="btn btn-default btn-sm">' +
i18n . _ ( 'Add comment' ) + '</button></div>'
) ;
2015-09-05 17:12:11 +02:00
divComment . find ( 'button' ) . click ( { commentid : this . pasteID ( ) } , $ . proxy ( this . openReply , this ) ) ;
this . comments . append ( divComment ) ;
this . discussion . removeClass ( 'hidden' ) ;
}
} ,
/ * *
* Open the comment entry when clicking the "Reply" button of a comment .
*
* @ param Event event
* /
openReply : function ( event )
{
event . preventDefault ( ) ;
var source = $ ( event . target ) ,
2015-09-06 19:21:17 +02:00
commentid = event . data . commentid ,
2015-09-06 13:07:46 +02:00
hint = i18n . _ ( 'Optional nickname...' ) ;
2015-09-05 17:12:11 +02:00
// Remove any other reply area.
$ ( 'div.reply' ) . remove ( ) ;
2015-09-06 13:07:46 +02:00
var reply = $ (
'<div class="reply">' +
'<input type="text" id="nickname" class="form-control" title="' + hint + '" placeholder="' + hint + '" />' +
'<textarea id="replymessage" class="replymessage form-control" cols="80" rows="7"></textarea>' +
2016-08-15 15:38:21 +02:00
'<br /><div id="replystatus"></div><button id="replybutton" class="btn btn-default btn-sm">' +
i18n . _ ( 'Post comment' ) + '</button></div>'
2015-09-06 13:07:46 +02:00
) ;
2015-09-05 17:12:11 +02:00
reply . find ( 'button' ) . click ( { parentid : commentid } , $ . proxy ( this . sendComment , this ) ) ;
source . after ( reply ) ;
2016-08-15 15:38:21 +02:00
this . replyStatus = $ ( '#replystatus' ) ;
2015-09-05 17:12:11 +02:00
$ ( '#replymessage' ) . focus ( ) ;
} ,
/ * *
* Send a reply in a discussion .
*
* @ param Event event
* /
sendComment : function ( event )
{
event . preventDefault ( ) ;
this . errorMessage . addClass ( 'hidden' ) ;
// Do not send if no data.
var replyMessage = $ ( '#replymessage' ) ;
2016-07-11 15:47:42 +02:00
if ( replyMessage . val ( ) . length === 0 )
{
return ;
}
2015-09-05 17:12:11 +02:00
2015-09-06 13:07:46 +02:00
this . showStatus ( i18n . _ ( 'Sending comment...' ) , true ) ;
2015-09-05 17:12:11 +02:00
var parentid = event . data . parentid ;
var cipherdata = filter . cipher ( this . pageKey ( ) , this . passwordInput . val ( ) , replyMessage . val ( ) ) ;
var ciphernickname = '' ;
var nick = $ ( '#nickname' ) . val ( ) ;
2016-07-11 15:47:42 +02:00
if ( nick !== '' )
2015-09-05 17:12:11 +02:00
{
ciphernickname = filter . cipher ( this . pageKey ( ) , this . passwordInput . val ( ) , nick ) ;
}
var data _to _send = {
data : cipherdata ,
parentid : parentid ,
pasteid : this . pasteID ( ) ,
nickname : ciphernickname
} ;
2015-09-27 20:34:39 +02:00
$ . ajax ( {
type : 'POST' ,
url : this . scriptLocation ( ) ,
data : data _to _send ,
dataType : 'json' ,
headers : this . headers ,
success : function ( data )
2015-09-05 17:12:11 +02:00
{
2016-07-11 15:47:42 +02:00
if ( data . status === 0 )
2015-09-05 17:12:11 +02:00
{
2016-07-11 11:58:15 +02:00
privatebin . showStatus ( i18n . _ ( 'Comment posted.' ) , false ) ;
2015-09-27 20:34:39 +02:00
$ . ajax ( {
type : 'GET' ,
2016-07-11 11:58:15 +02:00
url : privatebin . scriptLocation ( ) + '?' + privatebin . pasteID ( ) ,
2015-09-27 20:34:39 +02:00
dataType : 'json' ,
2016-07-11 11:58:15 +02:00
headers : privatebin . headers ,
2015-09-27 20:34:39 +02:00
success : function ( data )
{
2016-07-11 15:47:42 +02:00
if ( data . status === 0 )
2015-09-27 20:34:39 +02:00
{
2016-11-13 18:12:10 +01:00
privatebin . displayMessages ( data ) ;
2015-09-27 20:34:39 +02:00
}
2016-07-11 15:47:42 +02:00
else if ( data . status === 1 )
2015-09-27 20:34:39 +02:00
{
2016-07-11 11:58:15 +02:00
privatebin . showError ( i18n . _ ( 'Could not refresh display: %s' , data . message ) ) ;
2015-09-27 20:34:39 +02:00
}
else
{
2016-07-11 11:58:15 +02:00
privatebin . showError ( i18n . _ ( 'Could not refresh display: %s' , i18n . _ ( 'unknown status' ) ) ) ;
2015-09-27 20:34:39 +02:00
}
}
} )
. fail ( function ( ) {
2016-07-11 11:58:15 +02:00
privatebin . showError ( i18n . _ ( 'Could not refresh display: %s' , i18n . _ ( 'server error or not responding' ) ) ) ;
2015-09-27 20:34:39 +02:00
} ) ;
}
2016-07-11 15:47:42 +02:00
else if ( data . status === 1 )
2015-09-27 20:34:39 +02:00
{
2016-07-11 11:58:15 +02:00
privatebin . showError ( i18n . _ ( 'Could not post comment: %s' , data . message ) ) ;
2015-09-27 20:34:39 +02:00
}
else
{
2016-07-11 11:58:15 +02:00
privatebin . showError ( i18n . _ ( 'Could not post comment: %s' , i18n . _ ( 'unknown status' ) ) ) ;
2015-09-27 20:34:39 +02:00
}
2015-09-01 22:33:07 +02:00
}
2015-09-27 20:34:39 +02:00
} )
2015-09-01 22:33:07 +02:00
. fail ( function ( ) {
2016-07-11 11:58:15 +02:00
privatebin . showError ( i18n . _ ( 'Could not post comment: %s' , i18n . _ ( 'server error or not responding' ) ) ) ;
2015-09-01 22:33:07 +02:00
} ) ;
2015-09-05 17:12:11 +02:00
} ,
/ * *
* Send a new paste to server
*
* @ param Event event
* /
sendData : function ( event )
2015-09-01 22:33:07 +02:00
{
2015-09-05 17:12:11 +02:00
event . preventDefault ( ) ;
2015-09-19 14:19:42 +02:00
var file = document . getElementById ( 'file' ) ,
files = ( file && file . files ) ? file . files : null ; // FileList object
2015-09-05 17:12:11 +02:00
// Do not send if no data.
2016-07-11 15:47:42 +02:00
if ( this . message . val ( ) . length === 0 && ! ( files && files [ 0 ] ) )
{
return ;
}
2015-09-05 17:12:11 +02:00
// If sjcl has not collected enough entropy yet, display a message.
if ( ! sjcl . random . isReady ( ) )
{
2015-09-06 13:07:46 +02:00
this . showStatus ( i18n . _ ( 'Sending paste (Please move your mouse for more entropy)...' ) , true ) ;
2015-09-05 17:12:11 +02:00
sjcl . random . addEventListener ( 'seeded' , function ( ) {
this . sendData ( event ) ;
} ) ;
return ;
}
2013-02-24 16:29:13 +01:00
2015-09-12 10:23:12 +02:00
$ ( '.navbar-toggle' ) . click ( ) ;
this . password . addClass ( 'hidden' ) ;
2015-09-06 13:07:46 +02:00
this . showStatus ( i18n . _ ( 'Sending paste...' ) , true ) ;
2015-09-05 17:12:11 +02:00
var randomkey = sjcl . codec . base64 . fromBits ( sjcl . random . randomWords ( 8 , 0 ) , 0 ) ;
2015-09-16 22:51:48 +02:00
var password = this . passwordInput . val ( ) ;
if ( files && files [ 0 ] )
{
if ( typeof FileReader === undefined )
{
this . showError ( i18n . _ ( 'Your browser does not support uploading encrypted files. Please use a newer browser.' ) ) ;
return ;
}
var reader = new FileReader ( ) ;
// Closure to capture the file information.
reader . onload = ( function ( theFile )
{
return function ( e ) {
2016-07-11 11:58:15 +02:00
privatebin . sendDataContinue (
2015-09-16 22:51:48 +02:00
randomkey ,
2015-09-18 12:33:10 +02:00
filter . cipher ( randomkey , password , e . target . result ) ,
filter . cipher ( randomkey , password , theFile . name )
2015-09-16 22:51:48 +02:00
) ;
2016-01-31 09:56:06 +01:00
} ;
2015-09-16 22:51:48 +02:00
} ) ( files [ 0 ] ) ;
reader . readAsDataURL ( files [ 0 ] ) ;
}
else if ( this . attachmentLink . attr ( 'href' ) )
{
this . sendDataContinue (
randomkey ,
2015-09-18 12:33:10 +02:00
filter . cipher ( randomkey , password , this . attachmentLink . attr ( 'href' ) ) ,
this . attachmentLink . attr ( 'download' )
2015-09-16 22:51:48 +02:00
) ;
}
else
{
2015-09-18 12:33:10 +02:00
this . sendDataContinue ( randomkey , '' , '' ) ;
2015-09-16 22:51:48 +02:00
}
} ,
/ * *
* Send a new paste to server , step 2
*
2016-01-31 09:56:06 +01:00
* @ param string randomkey
* @ param encrypted string cipherdata _attachment
* @ param encrypted string cipherdata _attachment _name
2015-09-16 22:51:48 +02:00
* /
2015-09-18 12:33:10 +02:00
sendDataContinue : function ( randomkey , cipherdata _attachment , cipherdata _attachment _name )
2015-09-16 22:51:48 +02:00
{
2015-09-05 17:12:11 +02:00
var cipherdata = filter . cipher ( randomkey , this . passwordInput . val ( ) , this . message . val ( ) ) ;
var data _to _send = {
data : cipherdata ,
expire : $ ( '#pasteExpiration' ) . val ( ) ,
2015-09-12 17:33:16 +02:00
formatter : $ ( '#pasteFormatter' ) . val ( ) ,
2015-09-05 17:12:11 +02:00
burnafterreading : this . burnAfterReading . is ( ':checked' ) ? 1 : 0 ,
opendiscussion : this . openDiscussion . is ( ':checked' ) ? 1 : 0
} ;
2015-09-16 22:51:48 +02:00
if ( cipherdata _attachment . length > 0 )
{
data _to _send . attachment = cipherdata _attachment ;
2015-09-18 12:33:10 +02:00
if ( cipherdata _attachment _name . length > 0 )
{
data _to _send . attachmentname = cipherdata _attachment _name ;
}
2015-09-16 22:51:48 +02:00
}
2015-09-27 20:34:39 +02:00
$ . ajax ( {
type : 'POST' ,
url : this . scriptLocation ( ) ,
data : data _to _send ,
dataType : 'json' ,
headers : this . headers ,
success : function ( data )
2015-09-05 17:12:11 +02:00
{
2016-07-11 15:47:42 +02:00
if ( data . status === 0 ) {
2016-07-11 11:58:15 +02:00
privatebin . stateExistingPaste ( ) ;
var url = privatebin . scriptLocation ( ) + '?' + data . id + '#' + randomkey ;
var deleteUrl = privatebin . scriptLocation ( ) + '?pasteid=' + data . id + '&deletetoken=' + data . deletetoken ;
privatebin . showStatus ( '' , false ) ;
privatebin . errorMessage . addClass ( 'hidden' ) ;
2015-09-27 20:34:39 +02:00
2016-01-31 09:56:06 +01:00
$ ( '#pastelink' ) . html (
i18n . _ (
'Your paste is <a id="pasteurl" href="%s">%s</a> <span id="copyhint">(Hit [Ctrl]+[c] to copy)</span>' ,
url , url
2016-07-11 11:58:15 +02:00
) + privatebin . shortenUrl ( url )
2016-01-31 09:56:06 +01:00
) ;
2016-08-18 15:09:58 +02:00
var shortenButton = $ ( '#shortenbutton' ) ;
if ( shortenButton ) {
shortenButton . click ( $ . proxy ( privatebin . sendToShortener , privatebin ) ) ;
}
2015-09-27 20:34:39 +02:00
$ ( '#deletelink' ) . html ( '<a href="' + deleteUrl + '">' + i18n . _ ( 'Delete data' ) + '</a>' ) ;
2016-07-11 11:58:15 +02:00
privatebin . pasteResult . removeClass ( 'hidden' ) ;
2015-09-27 20:34:39 +02:00
// We pre-select the link so that the user only has to [Ctrl]+[c] the link.
helper . selectText ( 'pasteurl' ) ;
2016-07-11 11:58:15 +02:00
privatebin . showStatus ( '' , false ) ;
privatebin . formatPaste ( data _to _send . formatter , privatebin . message . val ( ) ) ;
2015-09-27 20:34:39 +02:00
}
2016-07-11 15:47:42 +02:00
else if ( data . status === 1 )
2015-09-27 20:34:39 +02:00
{
2016-07-11 11:58:15 +02:00
privatebin . showError ( i18n . _ ( 'Could not create paste: %s' , data . message ) ) ;
2015-09-27 20:34:39 +02:00
}
else
{
2016-07-11 11:58:15 +02:00
privatebin . showError ( i18n . _ ( 'Could not create paste: %s' , i18n . _ ( 'unknown status' ) ) ) ;
2015-09-27 20:34:39 +02:00
}
2015-09-05 17:12:11 +02:00
}
2015-09-27 20:34:39 +02:00
} )
. fail ( function ( )
{
2016-07-11 11:58:15 +02:00
privatebin . showError ( i18n . _ ( 'Could not create paste: %s' , i18n . _ ( 'server error or not responding' ) ) ) ;
2015-09-05 17:12:11 +02:00
} ) ;
} ,
2012-04-21 21:59:45 +02:00
2016-01-31 09:56:06 +01:00
/ * *
* Check if a URL shortener was defined and create HTML containing a link to it .
*
* @ param string url
* @ return string html
* /
shortenUrl : function ( url )
{
var shortenerHtml = $ ( '#shortenbutton' ) ;
if ( shortenerHtml ) {
2016-08-18 15:09:58 +02:00
this . shortenerUrl = shortenerHtml . data ( 'shortener' ) ;
this . createdPasteUrl = url ;
2016-01-31 09:56:06 +01:00
return ' ' + $ ( '<div />' ) . append ( shortenerHtml . clone ( ) ) . html ( ) ;
}
return '' ;
} ,
2015-09-05 17:12:11 +02:00
/ * *
* Put the screen in "New paste" mode .
* /
stateNewPaste : function ( )
{
this . message . text ( '' ) ;
2015-09-18 10:49:39 +02:00
this . attachment . addClass ( 'hidden' ) ;
2015-09-05 17:12:11 +02:00
this . cloneButton . addClass ( 'hidden' ) ;
this . rawTextButton . addClass ( 'hidden' ) ;
this . remainingTime . addClass ( 'hidden' ) ;
this . pasteResult . addClass ( 'hidden' ) ;
this . clearText . addClass ( 'hidden' ) ;
this . discussion . addClass ( 'hidden' ) ;
this . prettyMessage . addClass ( 'hidden' ) ;
this . sendButton . removeClass ( 'hidden' ) ;
this . expiration . removeClass ( 'hidden' ) ;
2015-09-12 17:33:16 +02:00
this . formatter . removeClass ( 'hidden' ) ;
2015-09-05 17:12:11 +02:00
this . burnAfterReadingOption . removeClass ( 'hidden' ) ;
this . openDisc . removeClass ( 'hidden' ) ;
this . newButton . removeClass ( 'hidden' ) ;
this . password . removeClass ( 'hidden' ) ;
2015-09-16 22:51:48 +02:00
this . attach . removeClass ( 'hidden' ) ;
2015-09-05 17:12:11 +02:00
this . message . removeClass ( 'hidden' ) ;
2016-07-11 11:09:41 +02:00
this . preview . removeClass ( 'hidden' ) ;
2015-09-05 17:12:11 +02:00
this . message . focus ( ) ;
} ,
/ * *
* Put the screen in "Existing paste" mode .
2016-07-11 11:09:41 +02:00
*
2016-11-13 18:12:10 +01:00
* @ param boolean preview ( optional ) tell if the preview tabs should be displayed , defaults to false .
2015-09-05 17:12:11 +02:00
* /
2016-07-11 11:09:41 +02:00
stateExistingPaste : function ( preview )
2015-09-05 17:12:11 +02:00
{
2016-07-11 11:09:41 +02:00
preview = preview || false ;
2012-04-21 21:59:45 +02:00
2016-07-11 11:09:41 +02:00
if ( ! preview )
2015-09-05 17:12:11 +02:00
{
2016-07-11 11:09:41 +02:00
// No "clone" for IE<10.
if ( $ ( '#oldienotice' ) . is ( ":visible" ) )
{
this . cloneButton . addClass ( 'hidden' ) ;
}
else
{
this . cloneButton . removeClass ( 'hidden' ) ;
}
this . rawTextButton . removeClass ( 'hidden' ) ;
this . sendButton . addClass ( 'hidden' ) ;
this . attach . addClass ( 'hidden' ) ;
this . expiration . addClass ( 'hidden' ) ;
this . formatter . addClass ( 'hidden' ) ;
this . burnAfterReadingOption . addClass ( 'hidden' ) ;
this . openDisc . addClass ( 'hidden' ) ;
this . newButton . removeClass ( 'hidden' ) ;
this . preview . addClass ( 'hidden' ) ;
2015-09-05 17:12:11 +02:00
}
this . pasteResult . addClass ( 'hidden' ) ;
this . message . addClass ( 'hidden' ) ;
this . clearText . addClass ( 'hidden' ) ;
2015-09-18 21:41:50 +02:00
this . prettyMessage . addClass ( 'hidden' ) ;
2015-09-05 17:12:11 +02:00
} ,
/ * *
* If "burn after reading" is checked , disable discussion .
* /
changeBurnAfterReading : function ( )
{
if ( this . burnAfterReading . is ( ':checked' ) )
{
this . openDisc . addClass ( 'buttondisabled' ) ;
this . openDiscussion . attr ( { checked : false , disabled : true } ) ;
}
else
{
this . openDisc . removeClass ( 'buttondisabled' ) ;
this . openDiscussion . removeAttr ( 'disabled' ) ;
}
} ,
2016-08-11 11:31:34 +02:00
/ * *
* If discussion is checked , disable "burn after reading" .
* /
changeOpenDisc : function ( )
{
if ( this . openDiscussion . is ( ':checked' ) )
{
this . burnAfterReadingOption . addClass ( 'buttondisabled' ) ;
this . burnAfterReading . attr ( { checked : false , disabled : true } ) ;
}
else
{
this . burnAfterReadingOption . removeClass ( 'buttondisabled' ) ;
this . burnAfterReading . removeAttr ( 'disabled' ) ;
}
} ,
2016-08-18 15:09:58 +02:00
/ * *
* Forward to URL shortener .
*
* @ param Event event
* /
sendToShortener : function ( event )
{
event . preventDefault ( ) ;
window . location . href = this . shortenerUrl + encodeURIComponent ( this . createdPasteUrl ) ;
} ,
2015-09-05 17:12:11 +02:00
/ * *
2015-10-15 22:06:01 +02:00
* Reload the page .
2015-09-05 17:12:11 +02:00
*
* @ param Event event
* /
reloadPage : function ( event )
{
event . preventDefault ( ) ;
window . location . href = this . scriptLocation ( ) ;
} ,
/ * *
2015-10-15 22:06:01 +02:00
* Return raw text .
2015-09-05 17:12:11 +02:00
*
* @ param Event event
* /
rawText : function ( event )
{
event . preventDefault ( ) ;
2016-08-15 15:04:12 +02:00
var paste = $ ( '#pasteFormatter' ) . val ( ) === 'markdown' ?
this . prettyPrint . text ( ) : this . clearText . text ( ) ;
2016-08-16 09:51:36 +02:00
history . pushState (
null , document . title , this . scriptLocation ( ) + '?' +
this . pasteID ( ) + '#' + this . pageKey ( )
) ;
// we use text/html instead of text/plain to avoid a bug when
// reloading the raw text view (it reverts to type text/html)
2015-09-05 17:12:11 +02:00
var newDoc = document . open ( 'text/html' , 'replace' ) ;
2016-12-25 13:04:06 +01:00
newDoc . write ( '<pre>' + helper . htmlEntities ( paste ) + '</pre>' ) ;
2015-09-05 17:12:11 +02:00
newDoc . close ( ) ;
} ,
/ * *
* Clone the current paste .
*
* @ param Event event
* /
clonePaste : function ( event )
{
event . preventDefault ( ) ;
this . stateNewPaste ( ) ;
2012-04-21 21:59:45 +02:00
2015-09-05 17:12:11 +02:00
// Erase the id and the key in url
2016-08-16 09:51:36 +02:00
history . replaceState ( null , document . title , this . scriptLocation ( ) ) ;
2012-04-21 21:59:45 +02:00
2015-09-05 17:12:11 +02:00
this . showStatus ( '' , false ) ;
2015-09-16 22:51:48 +02:00
if ( this . attachmentLink . attr ( 'href' ) )
{
this . clonedFile . removeClass ( 'hidden' ) ;
this . fileWrap . addClass ( 'hidden' ) ;
}
2016-08-15 15:04:12 +02:00
this . message . text (
$ ( '#pasteFormatter' ) . val ( ) === 'markdown' ?
this . prettyPrint . text ( ) : this . clearText . text ( )
) ;
2015-09-12 10:23:12 +02:00
$ ( '.navbar-toggle' ) . click ( ) ;
2015-09-05 17:12:11 +02:00
} ,
2012-04-21 21:59:45 +02:00
2016-08-09 14:46:32 +02:00
/ * *
* Set the expiration on bootstrap templates .
*
* @ param Event event
* /
setExpiration : function ( event )
{
event . preventDefault ( ) ;
var target = $ ( event . target ) ;
$ ( '#pasteExpiration' ) . val ( target . data ( 'expiration' ) ) ;
$ ( '#pasteExpirationDisplay' ) . text ( target . text ( ) ) ;
} ,
/ * *
* Set the format on bootstrap templates .
*
* @ param Event event
* /
setFormat : function ( event )
{
event . preventDefault ( ) ;
var target = $ ( event . target ) ;
$ ( '#pasteFormatter' ) . val ( target . data ( 'format' ) ) ;
$ ( '#pasteFormatterDisplay' ) . text ( target . text ( ) ) ;
2016-08-11 11:40:37 +02:00
if ( this . messagePreview . parent ( ) . hasClass ( 'active' ) ) {
this . viewPreview ( event ) ;
}
2016-08-09 14:46:32 +02:00
} ,
/ * *
* Set the language on bootstrap templates .
*
* Sets the language cookie and reloads the page .
*
* @ param Event event
* /
setLanguage : function ( event )
{
document . cookie = 'lang=' + $ ( event . target ) . data ( 'lang' ) ;
this . reloadPage ( event ) ;
} ,
2015-10-15 22:06:01 +02:00
/ * *
* Support input of tab character .
*
* @ param Event event
* /
supportTabs : function ( event )
{
var keyCode = event . keyCode || event . which ;
// tab was pressed
if ( keyCode === 9 )
{
// prevent the textarea to lose focus
event . preventDefault ( ) ;
// get caret position & selection
var val = this . value ,
start = this . selectionStart ,
end = this . selectionEnd ;
// set textarea value to: text before caret + tab + text after caret
this . value = val . substring ( 0 , start ) + '\t' + val . substring ( end ) ;
// put caret at right position again
this . selectionStart = this . selectionEnd = start + 1 ;
}
} ,
2016-07-11 11:09:41 +02:00
/ * *
* View the editor tab .
*
* @ param Event event
* /
viewEditor : function ( event )
{
event . preventDefault ( ) ;
this . messagePreview . parent ( ) . removeClass ( 'active' ) ;
this . messageEdit . parent ( ) . addClass ( 'active' ) ;
this . message . focus ( ) ;
this . stateNewPaste ( ) ;
} ,
/ * *
* View the preview tab .
*
* @ param Event event
* /
viewPreview : function ( event )
{
event . preventDefault ( ) ;
this . messageEdit . parent ( ) . removeClass ( 'active' ) ;
this . messagePreview . parent ( ) . addClass ( 'active' ) ;
this . message . focus ( ) ;
this . stateExistingPaste ( true ) ;
this . formatPaste ( $ ( '#pasteFormatter' ) . val ( ) , this . message . val ( ) ) ;
} ,
2015-09-05 17:12:11 +02:00
/ * *
* Create a new paste .
* /
newPaste : function ( )
{
this . stateNewPaste ( ) ;
this . showStatus ( '' , false ) ;
this . message . text ( '' ) ;
2016-08-15 14:25:52 +02:00
this . changeBurnAfterReading ( ) ;
this . changeOpenDisc ( ) ;
2015-09-05 17:12:11 +02:00
} ,
2015-09-16 22:51:48 +02:00
/ * *
* Removes an attachment .
* /
removeAttachment : function ( )
{
this . clonedFile . addClass ( 'hidden' ) ;
// removes the saved decrypted file data
2015-09-18 12:33:10 +02:00
this . attachmentLink . attr ( 'href' , '' ) ;
2015-09-16 22:51:48 +02:00
// the only way to deselect the file is to recreate the input
this . fileWrap . html ( this . fileWrap . html ( ) ) ;
this . fileWrap . removeClass ( 'hidden' ) ;
} ,
2016-11-13 18:12:10 +01:00
/ * *
* Focus on the modal password dialog .
* /
focusPasswordModal : function ( )
{
this . passwordDecrypt . focus ( ) ;
} ,
/ * *
* Decrypt using the password from the modal dialog .
* /
decryptPasswordModal : function ( )
{
this . passwordInput . val ( this . passwordDecrypt . val ( ) ) ;
this . displayMessages ( ) ;
} ,
/ * *
* Submit a password in the modal dialog .
*
* @ param Event event
* /
submitPasswordModal : function ( event )
{
event . preventDefault ( ) ;
this . passwordModal . modal ( 'hide' ) ;
} ,
2015-09-05 17:12:11 +02:00
/ * *
* Display an error message
* ( We use the same function for paste and reply to comments )
*
* @ param string message : text to display
* /
showError : function ( message )
{
if ( this . status . length )
{
this . status . addClass ( 'errorMessage' ) . text ( message ) ;
}
else
{
2015-09-05 17:21:05 +02:00
this . errorMessage . removeClass ( 'hidden' ) ;
2015-09-12 17:33:16 +02:00
helper . setMessage ( this . errorMessage , message ) ;
2015-09-05 17:12:11 +02:00
}
2016-08-15 15:38:21 +02:00
if ( typeof this . replyStatus !== 'undefined' ) {
this . replyStatus . addClass ( 'errorMessage' ) ;
this . replyStatus . addClass ( this . errorMessage . attr ( 'class' ) ) ;
if ( this . status . length )
{
this . replyStatus . html ( this . status . html ( ) ) ;
}
else
{
this . replyStatus . html ( this . errorMessage . html ( ) ) ;
}
}
2015-09-05 17:12:11 +02:00
} ,
/ * *
* Display a status message
* ( We use the same function for paste and reply to comments )
*
* @ param string message : text to display
* @ param boolean spin ( optional ) : tell if the "spinning" animation should be displayed .
* /
showStatus : function ( message , spin )
{
2016-08-15 15:38:21 +02:00
if ( typeof this . replyStatus !== 'undefined' ) {
this . replyStatus . removeClass ( 'errorMessage' ) . text ( message ) ;
}
2015-09-05 17:12:11 +02:00
if ( ! message )
{
this . status . html ( ' ' ) ;
return ;
}
2016-07-11 15:47:42 +02:00
if ( message === '' )
2015-09-05 17:12:11 +02:00
{
this . status . html ( ' ' ) ;
return ;
}
this . status . removeClass ( 'errorMessage' ) . text ( message ) ;
if ( spin )
{
var img = '<img src="img/busy.gif" style="width:16px;height:9px;margin:0 4px 0 0;" />' ;
this . status . prepend ( img ) ;
2016-08-15 15:38:21 +02:00
if ( typeof this . replyStatus !== 'undefined' ) {
this . replyStatus . prepend ( img ) ;
}
2015-09-05 17:12:11 +02:00
}
} ,
2012-04-23 16:30:02 +02:00
2015-09-05 17:12:11 +02:00
/ * *
* bind events to DOM elements
* /
bindEvents : function ( )
{
this . burnAfterReading . change ( $ . proxy ( this . changeBurnAfterReading , this ) ) ;
2016-08-11 11:31:34 +02:00
this . openDisc . change ( $ . proxy ( this . changeOpenDisc , this ) ) ;
2015-09-05 17:12:11 +02:00
this . sendButton . click ( $ . proxy ( this . sendData , this ) ) ;
this . cloneButton . click ( $ . proxy ( this . clonePaste , this ) ) ;
this . rawTextButton . click ( $ . proxy ( this . rawText , this ) ) ;
2015-09-16 22:51:48 +02:00
this . fileRemoveButton . click ( $ . proxy ( this . removeAttachment , this ) ) ;
2015-09-05 17:12:11 +02:00
$ ( '.reloadlink' ) . click ( $ . proxy ( this . reloadPage , this ) ) ;
2015-10-15 22:06:01 +02:00
this . message . keydown ( this . supportTabs ) ;
2016-07-11 11:09:41 +02:00
this . messageEdit . click ( $ . proxy ( this . viewEditor , this ) ) ;
this . messagePreview . click ( $ . proxy ( this . viewPreview , this ) ) ;
2016-08-09 14:46:32 +02:00
// bootstrap template drop downs
$ ( 'ul.dropdown-menu li a' , $ ( '#expiration' ) . parent ( ) ) . click ( $ . proxy ( this . setExpiration , this ) ) ;
$ ( 'ul.dropdown-menu li a' , $ ( '#formatter' ) . parent ( ) ) . click ( $ . proxy ( this . setFormat , this ) ) ;
$ ( '#language ul.dropdown-menu li a' ) . click ( $ . proxy ( this . setLanguage , this ) ) ;
// page template drop down
$ ( '#language select option' ) . click ( $ . proxy ( this . setLanguage , this ) ) ;
2016-11-13 18:12:10 +01:00
// handle modal password request on decryption
this . passwordModal . on ( 'shown.bs.modal' , $ . proxy ( this . focusPasswordModal , this ) ) ;
this . passwordModal . on ( 'hidden.bs.modal' , $ . proxy ( this . decryptPasswordModal , this ) ) ;
this . passwordForm . submit ( $ . proxy ( this . submitPasswordModal , this ) ) ;
2015-09-05 17:12:11 +02:00
} ,
/ * *
* main application
* /
init : function ( )
{
// hide "no javascript" message
$ ( '#noscript' ) . hide ( ) ;
// preload jQuery wrapped DOM elements and bind events
2015-09-16 22:51:48 +02:00
this . attach = $ ( '#attach' ) ;
this . attachment = $ ( '#attachment' ) ;
this . attachmentLink = $ ( '#attachment a' ) ;
2015-09-05 17:12:11 +02:00
this . burnAfterReading = $ ( '#burnafterreading' ) ;
this . burnAfterReadingOption = $ ( '#burnafterreadingoption' ) ;
this . cipherData = $ ( '#cipherdata' ) ;
this . clearText = $ ( '#cleartext' ) ;
this . cloneButton = $ ( '#clonebutton' ) ;
2015-09-16 22:51:48 +02:00
this . clonedFile = $ ( '#clonedfile' ) ;
2015-09-05 17:12:11 +02:00
this . comments = $ ( '#comments' ) ;
this . discussion = $ ( '#discussion' ) ;
this . errorMessage = $ ( '#errormessage' ) ;
this . expiration = $ ( '#expiration' ) ;
2015-09-16 22:51:48 +02:00
this . fileRemoveButton = $ ( '#fileremovebutton' ) ;
this . fileWrap = $ ( '#filewrap' ) ;
2015-09-12 17:33:16 +02:00
this . formatter = $ ( '#formatter' ) ;
2015-09-18 21:41:50 +02:00
this . image = $ ( '#image' ) ;
2015-09-05 17:12:11 +02:00
this . message = $ ( '#message' ) ;
2016-07-11 11:09:41 +02:00
this . messageEdit = $ ( '#messageedit' ) ;
this . messagePreview = $ ( '#messagepreview' ) ;
2015-09-05 17:12:11 +02:00
this . newButton = $ ( '#newbutton' ) ;
this . openDisc = $ ( '#opendisc' ) ;
this . openDiscussion = $ ( '#opendiscussion' ) ;
this . password = $ ( '#password' ) ;
this . passwordInput = $ ( '#passwordinput' ) ;
2016-11-13 18:12:10 +01:00
this . passwordModal = $ ( '#passwordmodal' ) ;
this . passwordForm = $ ( '#passwordform' ) ;
this . passwordDecrypt = $ ( '#passworddecrypt' ) ;
2015-09-05 17:12:11 +02:00
this . pasteResult = $ ( '#pasteresult' ) ;
this . prettyMessage = $ ( '#prettymessage' ) ;
this . prettyPrint = $ ( '#prettyprint' ) ;
2016-07-11 11:09:41 +02:00
this . preview = $ ( '#preview' ) ;
2015-09-05 17:12:11 +02:00
this . rawTextButton = $ ( '#rawtextbutton' ) ;
this . remainingTime = $ ( '#remainingtime' ) ;
this . sendButton = $ ( '#sendbutton' ) ;
this . status = $ ( '#status' ) ;
this . bindEvents ( ) ;
// Display status returned by php code if any (eg. Paste was properly deleted.)
if ( this . status . text ( ) . length > 0 )
{
this . showStatus ( this . status . text ( ) , false ) ;
return ;
}
2012-04-23 16:30:02 +02:00
2015-09-05 17:12:11 +02:00
// Keep line height even if content empty.
this . status . html ( ' ' ) ;
2012-04-23 16:30:02 +02:00
2015-09-05 17:12:11 +02:00
// Display an existing paste
if ( this . cipherData . text ( ) . length > 1 )
{
// Missing decryption key in URL?
2016-07-11 15:47:42 +02:00
if ( window . location . hash . length === 0 )
2015-09-05 17:12:11 +02:00
{
2015-09-06 13:07:46 +02:00
this . showError ( i18n . _ ( 'Cannot decrypt paste: Decryption key missing in URL (Did you use a redirector or an URL shortener which strips part of the URL?)' ) ) ;
2015-09-05 17:12:11 +02:00
return ;
}
2012-04-23 16:30:02 +02:00
2015-09-05 17:12:11 +02:00
// Show proper elements on screen.
this . stateExistingPaste ( ) ;
2016-11-13 18:12:10 +01:00
this . displayMessages ( ) ;
2015-09-05 17:12:11 +02:00
}
// Display error message from php code.
else if ( this . errorMessage . text ( ) . length > 1 )
{
this . showError ( this . errorMessage . text ( ) ) ;
}
// Create a new paste.
else
{
this . newPaste ( ) ;
}
2012-04-22 12:45:45 +02:00
}
2013-11-01 01:15:14 +01:00
}
2015-09-05 17:12:11 +02:00
/ * *
* main application start , called when DOM is fully loaded
2016-07-11 11:58:15 +02:00
* runs privatebin when translations were loaded
2015-09-05 17:12:11 +02:00
* /
2016-07-11 11:58:15 +02:00
i18n . loadTranslations ( $ . proxy ( privatebin . init , privatebin ) ) ;
2012-04-21 21:59:45 +02:00
} ) ;