2012-04-22 18:45:45 +08:00
/ * *
2016-07-11 17:58:15 +08:00
* PrivateBin
2012-04-23 22:30:02 +08:00
*
2012-05-01 04:58:08 +08:00
* a zero - knowledge paste bin
*
2017-01-14 22:29:12 +08:00
* @ 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}
2016-12-26 19:13:50 +08:00
* @ version 1.1
2017-01-14 22:29:12 +08:00
* @ name PrivateBin
* @ namespace
2012-04-22 18:45:45 +08:00
* /
2012-04-22 03:59:45 +08:00
2015-09-06 19:07:46 +08:00
'use strict' ;
2016-07-11 21:47:42 +08:00
/** global: Base64 */
/** global: FileReader */
/** global: RawDeflate */
/** global: history */
/** global: navigator */
/** global: prettyPrint */
/** global: prettyPrintOne */
/** global: showdown */
/** global: sjcl */
2015-09-06 19:07:46 +08:00
2012-04-22 03:59:45 +08:00
// Immediately start random number generator collector.
sjcl . random . startCollectors ( ) ;
2017-01-22 17:42:11 +08:00
jQuery . PrivateBin = function ( $ , sjcl , Base64 , RawDeflate ) {
2015-09-05 23:12:11 +08:00
/ * *
* static helper methods
2017-01-14 22:29:12 +08:00
*
* @ name helper
* @ class
2015-09-05 23:12:11 +08:00
* /
var helper = {
/ * *
2017-01-14 22:29:12 +08:00
* converts a duration ( in seconds ) into human friendly approximation
2015-09-05 23:12:11 +08:00
*
2017-01-14 22:29:12 +08:00
* @ name helper . secondsToHuman
* @ function
* @ param { number } seconds
* @ return { Array }
2015-09-05 23:12:11 +08:00
* /
secondsToHuman : function ( seconds )
{
2016-07-11 21:47:42 +08:00
var v ;
2015-09-05 23:12:11 +08:00
if ( seconds < 60 )
{
2016-07-11 21:47:42 +08:00
v = Math . floor ( seconds ) ;
2015-09-06 21:54:43 +08:00
return [ v , 'second' ] ;
2015-09-05 23:12:11 +08:00
}
if ( seconds < 60 * 60 )
{
2016-07-11 21:47:42 +08:00
v = Math . floor ( seconds / 60 ) ;
2015-09-06 21:54:43 +08:00
return [ v , 'minute' ] ;
2015-09-05 23:12:11 +08:00
}
if ( seconds < 60 * 60 * 24 )
{
2016-07-11 21:47:42 +08:00
v = Math . floor ( seconds / ( 60 * 60 ) ) ;
2015-09-06 21:54:43 +08:00
return [ v , 'hour' ] ;
2015-09-05 23:12:11 +08:00
}
// If less than 2 months, display in days:
if ( seconds < 60 * 60 * 24 * 60 )
{
2016-07-11 21:47:42 +08:00
v = Math . floor ( seconds / ( 60 * 60 * 24 ) ) ;
2015-09-06 21:54:43 +08:00
return [ v , 'day' ] ;
2015-09-05 23:12:11 +08:00
}
2016-07-11 21:47:42 +08:00
v = Math . floor ( seconds / ( 60 * 60 * 24 * 30 ) ) ;
2015-09-06 21:54:43 +08:00
return [ v , 'month' ] ;
2015-09-05 23:12:11 +08:00
} ,
/ * *
2017-01-14 22:29:12 +08:00
* text range selection
2015-09-05 23:12:11 +08:00
*
2017-01-14 22:29:12 +08:00
* @ see { @ link https : //stackoverflow.com/questions/985272/jquery-selecting-text-in-an-element-akin-to-highlighting-with-your-mouse}
* @ name helper . selectText
* @ function
* @ param { string } element - Indentifier of the element to select ( id = "" )
2015-09-05 23:12:11 +08:00
* /
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 ) ;
}
} ,
/ * *
2017-01-14 22:29:12 +08:00
* set text of a DOM element ( required for IE ) ,
* this is equivalent to element . text ( text )
2015-09-05 23:12:11 +08:00
*
2017-01-14 22:29:12 +08:00
* @ name helper . setElementText
* @ function
* @ param { Object } element - a DOM element
* @ param { string } text - the text to enter
2015-09-05 23:12:11 +08:00
* /
setElementText : function ( element , text )
{
2015-09-06 19:07:46 +08:00
// For IE<10: Doesn't support white-space:pre-wrap; so we have to do this...
2015-09-05 23:12:11 +08:00
if ( $ ( '#oldienotice' ) . is ( ':visible' ) ) {
2017-02-05 21:47:03 +08:00
var html = this . htmlEntities ( text ) . replace ( /\n/ig , '\r\n<br>' ) ;
element . html ( '<pre>' + html + '</pre>' ) ;
2015-09-05 23:12:11 +08:00
}
// for other (sane) browsers:
else
{
element . text ( text ) ;
}
} ,
2015-09-12 23:33:16 +08:00
/ * *
* replace last child of element with message
*
2017-01-14 22:29:12 +08:00
* @ name helper . setMessage
* @ function
* @ param { Object } element - a jQuery wrapped DOM element
* @ param { string } message - the message to append
2015-09-12 23:33:16 +08:00
* /
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 23:12:11 +08:00
/ * *
2017-01-14 22:29:12 +08:00
* convert URLs to clickable links .
2015-09-05 23:12:11 +08:00
* URLs to handle :
2017-01-14 22:29:12 +08:00
* < pre >
2015-09-05 23:12:11 +08:00
* magnet : ? xt . 1 = urn : sha1 : YNCKHTQCWBTRNJIV4WNAE52SJUQCZO5C & xt . 2 = urn : sha1 : TXGCZQTH26NL6OUQAJJPFALHG2LTGBC7
2017-01-14 22:29:12 +08:00
* http : //example.com:8800/zero/?6f09182b8ea51997#WtLEUO5Epj9UHAV9JFs+6pUQZp13TuspAUjnF+iM+dM=
* http : //user:example.com@localhost:8800/zero/?6f09182b8ea51997#WtLEUO5Epj9UHAV9JFs+6pUQZp13TuspAUjnF+iM+dM=
* < / p r e >
2015-09-05 23:12:11 +08:00
*
2017-01-14 22:29:12 +08:00
* @ name helper . urls2links
* @ function
* @ param { Object } element - a jQuery DOM element
2015-09-05 23:12:11 +08:00
* /
urls2links : function ( element )
{
2015-09-06 19:07:46 +08:00
var markup = '<a href="$1" rel="nofollow">$1</a>' ;
2015-09-05 23:12:11 +08:00
element . html (
element . html ( ) . replace (
/((http|https|ftp):\/\/[\w?=&.\/-;#@~%+-]+(?![\w\s?&.\/;#~%"=-]*>))/ig ,
2015-09-06 19:07:46 +08:00
markup
2015-09-05 23:12:11 +08:00
)
) ;
element . html (
element . html ( ) . replace (
/((magnet):[\w?=&.\/-;#@~%+-]+)/ig ,
2015-09-06 19:07:46 +08:00
markup
2015-09-05 23:12:11 +08:00
)
) ;
2015-09-06 19:07:46 +08:00
} ,
/ * *
* minimal sprintf emulation for % s and % d formats
*
2017-01-14 22:29:12 +08:00
* @ 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 }
2015-09-06 19:07:46 +08:00
* /
sprintf : function ( )
{
var args = arguments ;
2016-07-11 22:09:38 +08:00
if ( typeof arguments [ 0 ] === 'object' )
2016-07-11 21:47:42 +08:00
{
args = arguments [ 0 ] ;
}
2017-01-14 23:13:22 +08:00
var format = args [ 0 ] ,
2015-09-06 19:07:46 +08:00
i = 1 ;
2017-01-14 23:13:22 +08:00
return format . replace ( /%((%)|s|d)/g , function ( m ) {
2015-09-06 19:07:46 +08:00
// m is the matched format, e.g. %s, %d
2016-07-11 21:47:42 +08:00
var val ;
2015-09-06 19:07:46 +08:00
if ( m [ 2 ] ) {
val = m [ 2 ] ;
} else {
val = args [ i ] ;
// A switch statement so that the formatter can be extended.
2015-09-09 02:48:18 +08:00
switch ( m )
{
2015-09-06 19:07:46 +08:00
case '%d' :
val = parseFloat ( val ) ;
if ( isNaN ( val ) ) {
val = 0 ;
}
break ;
2016-07-11 21:47:42 +08:00
default :
// Default is %s
2015-09-06 19:07:46 +08:00
}
++ i ;
}
return val ;
} ) ;
2015-09-19 17:21:13 +08:00
} ,
2015-09-22 04:43:00 +08:00
2015-09-19 17:21:13 +08:00
/ * *
* get value of cookie , if it was set , empty string otherwise
*
2017-01-14 22:29:12 +08:00
* @ see { @ link http : //www.w3schools.com/js/js_cookies.asp}
* @ name helper . getCookie
* @ function
* @ param { string } cname
* @ return { string }
2015-09-19 17:21:13 +08:00
* /
getCookie : function ( cname ) {
2017-02-05 21:47:03 +08:00
var name = cname + '=' ,
ca = document . cookie . split ( ';' ) ;
2016-08-09 20:46:32 +08:00
for ( var i = 0 ; i < ca . length ; ++ i ) {
2015-09-19 17:21:13 +08:00
var c = ca [ i ] ;
2017-02-05 21:47:03 +08:00
while ( c . charAt ( 0 ) === ' ' )
{
c = c . substring ( 1 ) ;
}
2016-07-11 21:47:42 +08:00
if ( c . indexOf ( name ) === 0 )
{
return c . substring ( name . length , c . length ) ;
}
2015-09-19 17:21:13 +08:00
}
return '' ;
2016-07-19 22:12:11 +08:00
} ,
2017-02-05 21:47:03 +08:00
/ * *
* get the current script location ( without search or hash part of the URL ) ,
* eg . http : //example.com/path/?aaaa#bbbb --> http://example.com/path/
*
* @ name helper . scriptLocation
* @ function
* @ return { string } current script location
* /
scriptLocation : function ( )
{
var scriptLocation = window . location . href . substring (
0 ,
window . location . href . length - window . location . search . length - window . location . hash . length
2017-02-05 23:45:11 +08:00
) ,
hashIndex = scriptLocation . indexOf ( '?' ) ;
2017-02-05 21:47:03 +08:00
if ( hashIndex !== - 1 )
{
scriptLocation = scriptLocation . substring ( 0 , hashIndex ) ;
}
return scriptLocation ;
} ,
/ * *
* get the pastes unique identifier from the URL ,
* eg . http : //example.com/path/?c05354954c49a487#c05354954c49a487 returns c05354954c49a487
*
* @ name helper . pasteId
* @ function
* @ return { string } unique identifier
* /
pasteId : function ( )
{
return window . location . search . substring ( 1 ) ;
} ,
/ * *
* return the deciphering key stored in anchor part of the URL
*
* @ name helper . pageKey
* @ function
* @ return { string } key
* /
pageKey : function ( )
{
2017-02-06 01:03:42 +08:00
var key = window . location . hash . substring ( 1 ) ,
i = key . indexOf ( '&' ) ;
2017-02-05 21:47:03 +08:00
// Some web 2.0 services and redirectors add data AFTER the anchor
// (such as &utm_source=...). We will strip any additional data.
if ( i > - 1 )
{
key = key . substring ( 0 , i ) ;
}
return key ;
} ,
2016-07-19 22:12:11 +08:00
/ * *
2017-01-14 22:29:12 +08:00
* convert all applicable characters to HTML entities
2016-07-19 22:12:11 +08:00
*
2017-01-14 22:29:12 +08:00
* @ 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
2016-07-19 22:12:11 +08:00
* /
htmlEntities : function ( str ) {
return String ( str ) . replace (
/[&<>"'`=\/]/g , function ( s ) {
return helper . entityMap [ s ] ;
} ) ;
} ,
/ * *
* character to HTML entity lookup table
2017-01-14 22:29:12 +08:00
*
* @ see { @ link https : //github.com/janl/mustache.js/blob/master/mustache.js#L60}
* @ name helper . entityMap
* @ enum { Object }
* @ readonly
2016-07-19 22:12:11 +08:00
* /
entityMap : {
'&' : '&' ,
'<' : '<' ,
'>' : '>' ,
'"' : '"' ,
"'" : ''' ,
'/' : '/' ,
'`' : '`' ,
'=' : '='
2015-09-22 04:43:00 +08:00
}
2015-09-05 23:12:11 +08:00
} ;
2015-09-06 19:07:46 +08:00
/ * *
* internationalization methods
2017-01-14 22:29:12 +08:00
*
* @ name i18n
* @ class
2015-09-06 19:07:46 +08:00
* /
var i18n = {
2015-09-06 21:54:43 +08:00
/ * *
* supported languages , minus the built in 'en'
2017-01-14 22:29:12 +08:00
*
* @ name i18n . supportedLanguages
* @ prop { string [ ] }
* @ readonly
2015-09-06 21:54:43 +08:00
* /
2017-01-11 03:37:14 +08:00
supportedLanguages : [ 'de' , 'es' , 'fr' , 'it' , 'no' , 'pl' , 'oc' , 'ru' , 'sl' , 'zh' ] ,
2015-09-06 21:54:43 +08:00
2015-09-06 19:07:46 +08:00
/ * *
2017-01-14 22:29:12 +08:00
* translate a string , alias for i18n . translate ( )
2015-09-06 19:07:46 +08:00
*
2017-01-14 22:29:12 +08:00
* @ name i18n . _
* @ function
* @ param { string } messageId
* @ param { ... * } args - one or multiple parameters injected into placeholders
* @ return { string }
2015-09-06 19:07:46 +08:00
* /
_ : function ( )
{
return this . translate ( arguments ) ;
} ,
/ * *
* translate a string
*
2017-01-14 22:29:12 +08:00
* @ name i18n . translate
* @ function
* @ param { string } messageId
* @ param { ... * } args - one or multiple parameters injected into placeholders
* @ return { string }
2015-09-06 19:07:46 +08:00
* /
translate : function ( )
{
2016-07-11 22:09:38 +08:00
var args = arguments , messageId ;
if ( typeof arguments [ 0 ] === 'object' )
2016-07-11 21:47:42 +08:00
{
args = arguments [ 0 ] ;
}
2016-07-11 22:09:38 +08:00
var usesPlurals = $ . isArray ( args [ 0 ] ) ;
if ( usesPlurals )
2015-09-06 21:54:43 +08: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 21:47:42 +08:00
if ( messageId . length === 0 )
{
return messageId ;
}
2015-09-06 19:07:46 +08:00
if ( ! this . translations . hasOwnProperty ( messageId ) )
{
2016-07-11 21:47:42 +08:00
if ( this . language !== 'en' )
{
console . debug (
'Missing ' + this . language + ' translation for: ' + messageId
) ;
}
2015-09-06 21:54:43 +08:00
this . translations [ messageId ] = args [ 0 ] ;
}
if ( usesPlurals && $ . isArray ( this . translations [ messageId ] ) )
{
2016-07-11 21:47:42 +08:00
var n = parseInt ( args [ 1 ] || 1 , 10 ) ,
2015-09-09 02:48:18 +08:00
key = this . getPluralForm ( n ) ,
2015-09-06 21:54:43 +08:00
maxKey = this . translations [ messageId ] . length - 1 ;
2016-07-11 21:47:42 +08:00
if ( key > maxKey )
{
key = maxKey ;
}
2015-09-06 21:54:43 +08:00
args [ 0 ] = this . translations [ messageId ] [ key ] ;
args [ 1 ] = n ;
}
else
{
args [ 0 ] = this . translations [ messageId ] ;
2015-09-06 19:07:46 +08:00
}
return helper . sprintf ( args ) ;
} ,
2015-09-09 02:48:18 +08:00
/ * *
* per language functions to use to determine the plural form
*
2017-01-14 22:29:12 +08:00
* @ see { @ link http : //localization-guide.readthedocs.org/en/latest/l10n/pluralforms.html}
* @ name i18n . getPluralForm
* @ function
* @ param { number } n
* @ return { number } array key
2015-09-09 02:48:18 +08:00
* /
getPluralForm : function ( n ) {
switch ( this . language )
{
case 'fr' :
2017-01-08 14:56:56 +08:00
case 'oc' :
2016-04-27 02:21:30 +08:00
case 'zh' :
2015-09-09 02:48:18 +08:00
return ( n > 1 ? 1 : 0 ) ;
case 'pl' :
2017-01-01 21:35:39 +08:00
return ( n === 1 ? 0 : ( n % 10 >= 2 && n % 10 <= 4 && ( n % 100 < 10 || n % 100 >= 20 ) ? 1 : 2 ) ) ;
2016-12-16 17:21:15 +08: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 21:35:39 +08:00
case 'sl' :
return ( n % 100 === 1 ? 1 : ( n % 100 === 2 ? 2 : ( n % 100 === 3 || n % 100 === 4 ? 3 : 0 ) ) ) ;
2017-01-11 03:37:14 +08:00
// de, en, es, it, no
2015-09-09 02:48:18 +08:00
default :
2016-07-05 23:23:25 +08:00
return ( n !== 1 ? 1 : 0 ) ;
2015-09-09 02:48:18 +08:00
}
} ,
2015-09-06 21:54:43 +08:00
/ * *
2017-01-31 03:29:04 +08:00
* load translations into cache , then trigger controller initialization
2015-09-06 21:54:43 +08:00
*
2017-01-14 22:29:12 +08:00
* @ name i18n . loadTranslations
* @ function
2015-09-06 21:54:43 +08:00
* /
2017-01-31 03:29:04 +08:00
loadTranslations : function ( )
2015-09-06 19:07:46 +08:00
{
2017-02-05 21:47:03 +08:00
var language = helper . getCookie ( 'lang' ) ;
if ( language . length === 0 )
{
language = ( navigator . language || navigator . userLanguage ) . substring ( 0 , 2 ) ;
}
2015-09-06 19:07:46 +08:00
// note that 'en' is built in, so no translation is necessary
2017-01-31 03:29:04 +08:00
if ( i18n . supportedLanguages . indexOf ( language ) === - 1 )
2015-09-09 02:48:18 +08:00
{
2017-01-31 03:29:04 +08:00
controller . init ( ) ;
2015-09-09 02:48:18 +08:00
}
else
{
$ . getJSON ( 'i18n/' + language + '.json' , function ( data ) {
i18n . language = language ;
i18n . translations = data ;
2017-01-31 03:29:04 +08:00
controller . init ( ) ;
2015-09-09 02:48:18 +08:00
} ) ;
}
2015-09-06 19:07:46 +08:00
} ,
2015-09-06 21:54:43 +08:00
/ * *
* built in language
2017-01-14 22:29:12 +08:00
*
* @ name i18n . language
* @ prop { string }
2015-09-06 21:54:43 +08:00
* /
language : 'en' ,
/ * *
* translation cache
2017-01-14 22:29:12 +08:00
*
* @ name i18n . translations
* @ enum { Object }
2015-09-06 21:54:43 +08:00
* /
2015-09-06 19:07:46 +08:00
translations : { }
2016-01-31 16:56:06 +08:00
} ;
2015-09-06 19:07:46 +08:00
2015-09-05 23:12:11 +08:00
/ * *
* filter methods
2017-01-14 22:29:12 +08:00
*
* @ name filter
* @ class
2015-09-05 23:12:11 +08:00
* /
var filter = {
/ * *
2017-01-14 22:29:12 +08:00
* compress a message ( deflate compression ) , returns base64 encoded data
2015-09-05 23:12:11 +08:00
*
2017-01-14 22:29:12 +08:00
* @ name filter . compress
* @ function
* @ param { string } message
* @ return { string } base64 data
2015-09-05 23:12:11 +08:00
* /
compress : function ( message )
{
return Base64 . toBase64 ( RawDeflate . deflate ( Base64 . utob ( message ) ) ) ;
} ,
/ * *
2017-01-14 22:29:12 +08:00
* decompress a message compressed with filter . compress ( )
2015-09-05 23:12:11 +08:00
*
2017-01-14 22:29:12 +08:00
* @ name filter . decompress
* @ function
2017-01-14 23:13:22 +08:00
* @ param { string } data - base64 data
2017-01-14 22:29:12 +08:00
* @ return { string } message
2015-09-05 23:12:11 +08:00
* /
decompress : function ( data )
{
return Base64 . btou ( RawDeflate . inflate ( Base64 . fromBase64 ( data ) ) ) ;
} ,
/ * *
2017-01-14 22:29:12 +08:00
* compress , then encrypt message with given key and password
2015-09-05 23:12:11 +08:00
*
2017-01-14 22:29:12 +08:00
* @ name filter . cipher
* @ function
* @ param { string } key
* @ param { string } password
* @ param { string } message
2017-01-14 23:13:22 +08:00
* @ return { string } data - JSON with encrypted data
2015-09-05 23:12:11 +08:00
* /
cipher : function ( key , password , message )
{
2016-08-09 19:16:15 +08:00
// Galois Counter Mode, keysize 256 bit, authentication tag 128 bit
var options = { mode : 'gcm' , ks : 256 , ts : 128 } ;
2016-07-11 21:47:42 +08:00
if ( ( password || '' ) . trim ( ) . length === 0 )
2015-09-05 23:12:11 +08:00
{
2016-08-09 19:16:15 +08:00
return sjcl . encrypt ( key , this . compress ( message ) , options ) ;
2015-09-05 23:12:11 +08:00
}
2016-08-09 19:16:15 +08:00
return sjcl . encrypt ( key + sjcl . codec . hex . fromBits ( sjcl . hash . sha256 . hash ( password ) ) , this . compress ( message ) , options ) ;
2015-09-05 23:12:11 +08:00
} ,
/ * *
2017-01-14 22:29:12 +08:00
* decrypt message with key , then decompress
2015-09-05 23:12:11 +08:00
*
2017-01-14 22:29:12 +08:00
* @ name filter . decipher
* @ function
* @ param { string } key
* @ param { string } password
2017-01-14 23:13:22 +08:00
* @ param { string } data - JSON with encrypted data
2017-01-14 22:29:12 +08:00
* @ return { string } decrypted message
2015-09-05 23:12:11 +08:00
* /
decipher : function ( key , password , data )
{
2016-07-11 21:47:42 +08:00
if ( data !== undefined )
2015-09-05 23:12:11 +08: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 21:47:42 +08:00
catch ( e )
2015-09-05 23:12:11 +08:00
{ }
2015-09-01 03:14:12 +08:00
}
}
2015-09-05 23:12:11 +08:00
return '' ;
2015-08-22 23:23:41 +08:00
}
2015-09-05 23:12:11 +08:00
} ;
2017-01-14 22:29:12 +08:00
/ * *
* PrivateBin logic
*
2017-01-29 21:32:55 +08:00
* @ name controller
2017-01-14 22:29:12 +08:00
* @ class
* /
2017-01-29 21:32:55 +08:00
var controller = {
2015-09-28 02:34:39 +08:00
/ * *
* headers to send in AJAX requests
2017-01-14 22:29:12 +08:00
*
2017-01-29 21:32:55 +08:00
* @ name controller . headers
2017-01-14 22:29:12 +08:00
* @ enum { Object }
2015-09-28 02:34:39 +08:00
* /
headers : { 'X-Requested-With' : 'JSONHttpRequest' } ,
2016-08-18 21:09:58 +08:00
/ * *
* URL shortners create address
2017-01-14 22:29:12 +08:00
*
2017-01-29 21:32:55 +08:00
* @ name controller . shortenerUrl
2017-01-14 22:29:12 +08:00
* @ prop { string }
2016-08-18 21:09:58 +08:00
* /
shortenerUrl : '' ,
/ * *
* URL of newly created paste
2017-01-14 22:29:12 +08:00
*
2017-01-29 21:32:55 +08:00
* @ name controller . createdPasteUrl
2017-01-14 22:29:12 +08:00
* @ prop { string }
2016-08-18 21:09:58 +08:00
* /
createdPasteUrl : '' ,
2015-09-05 23:12:11 +08:00
/ * *
2016-11-14 01:12:10 +08:00
* ask the user for the password and set it
2017-01-14 22:29:12 +08:00
*
2017-01-29 21:32:55 +08:00
* @ name controller . requestPassword
2017-01-14 22:29:12 +08:00
* @ function
2015-09-05 23:12:11 +08:00
* /
requestPassword : function ( )
{
2016-11-14 01:22:37 +08:00
if ( this . passwordModal . length === 0 ) {
2016-11-14 01:12:10 +08: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 23:12:11 +08:00
} ,
2015-09-12 23:33:16 +08:00
/ * *
* use given format on paste , defaults to plain text
*
2017-01-29 21:32:55 +08:00
* @ name controller . formatPaste
2017-01-14 22:29:12 +08:00
* @ function
* @ param { string } format
* @ param { string } text
2015-09-12 23:33:16 +08:00
* /
2016-05-22 22:18:57 +08:00
formatPaste : function ( format , text )
2015-09-12 23:33:16 +08:00
{
2016-05-22 22:18:57 +08:00
helper . setElementText ( this . clearText , text ) ;
helper . setElementText ( this . prettyPrint , text ) ;
2015-09-12 23:33:16 +08:00
switch ( format || 'plaintext' )
{
case 'markdown' :
2016-07-11 21:47:42 +08:00
if ( typeof showdown === 'object' )
2015-09-12 23:33:16 +08:00
{
2017-01-29 23:19:12 +08:00
var converter = new showdown . Converter ( {
strikethrough : true ,
tables : true ,
tablesHeaderId : true
} ) ;
2015-09-12 23:33:16 +08:00
this . clearText . html (
2016-05-22 22:18:57 +08:00
converter . makeHtml ( text )
2015-09-12 23:33:16 +08:00
) ;
2016-12-14 06:30:28 +08:00
// add table classes from bootstrap css
this . clearText . find ( 'table' ) . addClass ( 'table-condensed table-bordered' ) ;
2016-12-13 00:37:51 +08:00
2015-09-17 02:49:28 +08:00
this . clearText . removeClass ( 'hidden' ) ;
2015-09-12 23:33:16 +08:00
}
2016-12-14 06:30:28 +08:00
this . prettyMessage . addClass ( 'hidden' ) ;
2015-09-12 23:33:16 +08:00
break ;
case 'syntaxhighlighting' :
2016-07-11 21:47:42 +08:00
if ( typeof prettyPrintOne === 'function' )
2016-07-11 17:09:41 +08:00
{
2016-07-11 21:47:42 +08:00
if ( typeof prettyPrint === 'function' )
{
prettyPrint ( ) ;
}
2016-07-11 17:09:41 +08:00
this . prettyPrint . html (
2016-07-19 22:12:11 +08:00
prettyPrintOne (
helper . htmlEntities ( text ) , null , true
)
2016-07-11 17:09:41 +08:00
) ;
2016-07-11 20:15:20 +08:00
}
2016-07-11 21:47:42 +08:00
// fall through, as the rest is the same
2015-09-12 23:33:16 +08:00
default :
2017-01-14 22:29:12 +08:00
// convert URLs to clickable links
2015-09-12 23:33:16 +08:00
helper . urls2links ( this . clearText ) ;
helper . urls2links ( this . prettyPrint ) ;
2015-09-17 02:49:28 +08:00
this . clearText . addClass ( 'hidden' ) ;
2016-07-11 21:47:42 +08:00
if ( format === 'plaintext' )
2015-10-19 04:16:15 +08:00
{
this . prettyPrint . css ( 'white-space' , 'pre-wrap' ) ;
this . prettyPrint . css ( 'word-break' , 'normal' ) ;
this . prettyPrint . removeClass ( 'prettyprint' ) ;
}
2015-09-17 02:49:28 +08:00
this . prettyMessage . removeClass ( 'hidden' ) ;
2015-09-12 23:33:16 +08:00
}
} ,
2015-09-05 23:12:11 +08:00
/ * *
2017-01-14 22:29:12 +08:00
* show decrypted text in the display area , including discussion ( if open )
2015-09-05 23:12:11 +08:00
*
2017-01-29 21:32:55 +08:00
* @ name controller . displayMessages
2017-01-14 22:29:12 +08:00
* @ function
* @ param { Object } [ paste ] - ( optional ) object including comments to display ( items = array with keys ( 'data' , 'meta' ) )
2015-09-05 23:12:11 +08:00
* /
2016-11-14 01:12:10 +08:00
displayMessages : function ( paste )
2015-09-05 23:12:11 +08:00
{
2016-11-14 01:12:10 +08:00
paste = paste || $ . parseJSON ( this . cipherData . text ( ) ) ;
2017-02-05 21:47:03 +08:00
var key = helper . pageKey ( ) ,
password = this . passwordInput . val ( ) ;
2015-09-05 23:12:11 +08:00
if ( ! this . prettyPrint . hasClass ( 'prettyprinted' ) ) {
2016-11-14 01:12:10 +08:00
// Try to decrypt the paste.
2015-09-05 23:12:11 +08:00
try
{
2015-10-18 17:08:28 +08:00
if ( paste . attachment )
2015-09-17 04:51:48 +08:00
{
2015-10-18 17:08:28 +08:00
var attachment = filter . decipher ( key , password , paste . attachment ) ;
2016-07-11 21:47:42 +08:00
if ( attachment . length === 0 )
2015-09-18 18:33:10 +08:00
{
2016-07-11 21:47:42 +08:00
if ( password . length === 0 )
{
2016-11-14 01:12:10 +08:00
this . requestPassword ( ) ;
return ;
2016-07-11 21:47:42 +08:00
}
2015-10-18 17:08:28 +08:00
attachment = filter . decipher ( key , password , paste . attachment ) ;
2015-09-18 18:33:10 +08:00
}
2016-07-11 21:47:42 +08:00
if ( attachment . length === 0 )
{
throw 'failed to decipher attachment' ;
}
2015-09-18 18:33:10 +08:00
2015-10-18 17:08:28 +08:00
if ( paste . attachmentname )
2015-09-17 04:51:48 +08:00
{
2015-10-18 17:08:28 +08:00
var attachmentname = filter . decipher ( key , password , paste . attachmentname ) ;
2016-07-11 21:47:42 +08:00
if ( attachmentname . length > 0 )
{
this . attachmentLink . attr ( 'download' , attachmentname ) ;
}
2015-09-17 04:51:48 +08:00
}
2015-09-18 18:33:10 +08:00
this . attachmentLink . attr ( 'href' , attachment ) ;
this . attachment . removeClass ( 'hidden' ) ;
2015-09-22 04:43:00 +08:00
2015-09-19 03:41:50 +08:00
// if the attachment is an image, display it
var imagePrefix = 'data:image/' ;
2016-07-11 21:47:42 +08:00
if ( attachment . substring ( 0 , imagePrefix . length ) === imagePrefix )
2015-09-19 03:41:50 +08:00
{
this . image . html (
$ ( document . createElement ( 'img' ) )
. attr ( 'src' , attachment )
. attr ( 'class' , 'img-thumbnail' )
) ;
this . image . removeClass ( 'hidden' ) ;
}
2015-09-17 04:51:48 +08:00
}
2015-10-18 17:08:28 +08:00
var cleartext = filter . decipher ( key , password , paste . data ) ;
2016-07-11 21:47:42 +08:00
if ( cleartext . length === 0 && password . length === 0 && ! paste . attachment )
2015-09-18 18:33:10 +08:00
{
2016-11-14 01:12:10 +08:00
this . requestPassword ( ) ;
return ;
2015-09-18 18:33:10 +08:00
}
2016-07-11 21:47:42 +08:00
if ( cleartext . length === 0 && ! paste . attachment )
{
throw 'failed to decipher message' ;
}
2015-09-05 23:12:11 +08:00
2015-09-18 18:33:10 +08:00
this . passwordInput . val ( password ) ;
2015-09-19 03:41:50 +08:00
if ( cleartext . length > 0 )
{
2016-08-15 21:04:12 +08:00
$ ( '#pasteFormatter' ) . val ( paste . meta . formatter ) ;
2016-05-22 22:18:57 +08:00
this . formatPaste ( paste . meta . formatter , cleartext ) ;
2015-09-19 03:41:50 +08:00
}
2015-09-05 23:12:11 +08:00
}
catch ( err )
{
2017-02-07 05:39:45 +08:00
this . stateOnlyNewPaste ( ) ;
2015-09-06 19:07:46 +08:00
this . showError ( i18n . _ ( 'Could not decrypt data (Wrong key?)' ) ) ;
2015-09-05 23:12:11 +08:00
return ;
}
}
2012-04-22 03:59:45 +08:00
2017-01-14 22:29:12 +08:00
// display paste expiration / for your eyes only
2015-10-18 17:08:28 +08:00
if ( paste . meta . expire _date )
2015-09-05 23:12:11 +08:00
{
2015-10-18 17:08:28 +08:00
var expiration = helper . secondsToHuman ( paste . meta . remaining _time ) ,
2015-09-06 21:54:43 +08:00
expirationLabel = [
'This document will expire in %d ' + expiration [ 1 ] + '.' ,
'This document will expire in %d ' + expiration [ 1 ] + 's.'
] ;
2015-09-12 23:33:16 +08:00
helper . setMessage ( this . remainingTime , i18n . _ ( expirationLabel , expiration [ 0 ] ) ) ;
2015-09-05 23:12:11 +08:00
this . remainingTime . removeClass ( 'foryoureyesonly' )
2015-09-12 16:38:04 +08:00
. removeClass ( 'hidden' ) ;
2015-09-05 23:12:11 +08:00
}
2015-10-18 17:08:28 +08:00
if ( paste . meta . burnafterreading )
2015-09-05 23:12:11 +08:00
{
2015-10-12 03:22:00 +08:00
// unfortunately many web servers don't support DELETE (and PUT) out of the box
2015-09-28 02:34:39 +08:00
$ . ajax ( {
2015-10-12 03:22:00 +08:00
type : 'POST' ,
2017-02-05 21:47:03 +08:00
url : helper . scriptLocation ( ) + '?' + helper . pasteId ( ) ,
2015-10-12 03:22:00 +08:00
data : { deletetoken : 'burnafterreading' } ,
2015-09-28 02:34:39 +08:00
dataType : 'json' ,
headers : this . headers
} )
2015-09-05 23:12:11 +08:00
. fail ( function ( ) {
2017-01-29 21:32:55 +08:00
controller . showError ( i18n . _ ( 'Could not delete the paste, it was not stored in burn after reading mode.' ) ) ;
2015-09-05 23:12:11 +08:00
} ) ;
2015-09-12 23:33:16 +08:00
helper . setMessage ( this . remainingTime , i18n . _ (
2015-09-12 16:38:04 +08:00
'FOR YOUR EYES ONLY. Don\'t close this window, this message can\'t be displayed again.'
2015-09-12 23:33:16 +08:00
) ) ;
2015-09-05 23:12:11 +08:00
this . remainingTime . addClass ( 'foryoureyesonly' )
2015-09-12 16:38:04 +08:00
. removeClass ( 'hidden' ) ;
2017-01-14 22:29:12 +08:00
// discourage cloning (as it can't really be prevented)
2015-09-05 23:12:11 +08:00
this . cloneButton . addClass ( 'hidden' ) ;
}
2012-04-23 22:30:02 +08:00
2017-01-14 22:29:12 +08:00
// if the discussion is opened on this paste, display it
2015-10-18 17:08:28 +08:00
if ( paste . meta . opendiscussion )
2015-09-05 23:12:11 +08:00
{
this . comments . html ( '' ) ;
// iterate over comments
2015-10-18 17:08:28 +08:00
for ( var i = 0 ; i < paste . comments . length ; ++ i )
2015-09-05 23:12:11 +08:00
{
2017-02-05 21:47:03 +08:00
var place = this . comments ,
comment = paste . comments [ i ] ,
commenttext = filter . decipher ( key , password , comment . data ) ,
// if parent comment exists, display below (CSS will automatically shift it to the right)
cname = '#comment_' + comment . parentid ,
divComment = $ ( '<article><div class="comment" id="comment_' + comment . id
+ '"><div class="commentmeta"><span class="nickname"></span>'
+ '<span class="commentdate"></span></div>'
+ '<div class="commentdata"></div>'
+ '<button class="btn btn-default btn-sm">'
+ i18n . _ ( 'Reply' ) + '</button></div></article>' ) ,
divCommentData = divComment . find ( 'div.commentdata' ) ;
2015-09-05 23:12:11 +08:00
2017-01-14 22:29:12 +08:00
// if the element exists in page
2015-09-05 23:12:11 +08:00
if ( $ ( cname ) . length )
{
place = $ ( cname ) ;
}
2015-10-18 17:38:48 +08:00
divComment . find ( 'button' ) . click ( { commentid : comment . id } , $ . proxy ( this . openReply , this ) ) ;
2017-02-05 21:47:03 +08:00
helper . setElementText ( divCommentData , commenttext ) ;
helper . urls2links ( divCommentData ) ;
2015-09-05 23:12:11 +08:00
2017-01-14 22:29:12 +08:00
// try to get optional nickname
2015-09-05 23:12:11 +08:00
var nick = filter . decipher ( key , password , comment . meta . nickname ) ;
if ( nick . length > 0 )
{
divComment . find ( 'span.nickname' ) . text ( nick ) ;
}
else
{
2015-09-06 19:07:46 +08:00
divComment . find ( 'span.nickname' ) . html ( '<i>' + i18n . _ ( 'Anonymous' ) + '</i>' ) ;
2015-09-05 23:12:11 +08:00
}
divComment . find ( 'span.commentdate' )
2015-09-06 19:07:46 +08:00
. text ( ' (' + ( new Date ( comment . meta . postdate * 1000 ) . toLocaleString ( ) ) + ')' )
2015-10-18 17:38:48 +08:00
. attr ( 'title' , 'CommentID: ' + comment . id ) ;
2015-09-05 23:12:11 +08:00
2017-01-14 22:29:12 +08:00
// if an avatar is available, display it
2015-09-05 23:12:11 +08:00
if ( comment . meta . vizhash )
{
divComment . find ( 'span.nickname' )
2015-09-06 19:07:46 +08:00
. before (
'<img src="' + comment . meta . vizhash + '" class="vizhash" title="' +
i18n . _ ( 'Anonymous avatar (Vizhash of the IP address)' ) + '" /> '
) ;
2015-09-05 23:12:11 +08:00
}
place . append ( divComment ) ;
}
2015-09-06 19:07:46 +08:00
var divComment = $ (
'<div class="comment"><button class="btn btn-default btn-sm">' +
i18n . _ ( 'Add comment' ) + '</button></div>'
) ;
2017-02-05 21:47:03 +08:00
divComment . find ( 'button' ) . click ( { commentid : helper . pasteId ( ) } , $ . proxy ( this . openReply , this ) ) ;
2015-09-05 23:12:11 +08:00
this . comments . append ( divComment ) ;
this . discussion . removeClass ( 'hidden' ) ;
}
} ,
/ * *
2017-01-14 22:29:12 +08:00
* open the comment entry when clicking the "Reply" button of a comment
2015-09-05 23:12:11 +08:00
*
2017-01-29 21:32:55 +08:00
* @ name controller . openReply
2017-01-14 22:29:12 +08:00
* @ function
* @ param { Event } event
2015-09-05 23:12:11 +08:00
* /
openReply : function ( event )
{
event . preventDefault ( ) ;
2017-01-14 22:29:12 +08:00
// remove any other reply area
2015-09-05 23:12:11 +08:00
$ ( 'div.reply' ) . remove ( ) ;
2017-02-05 21:47:03 +08:00
var source = $ ( event . target ) ,
commentid = event . data . commentid ,
hint = i18n . _ ( 'Optional nickname...' ) ,
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><br />' +
'<div id="replystatus"></div><button id="replybutton" ' +
'class="btn btn-default btn-sm">' + i18n . _ ( 'Post comment' ) +
'</button></div>'
) ;
reply . find ( 'button' ) . click (
{ parentid : commentid } ,
$ . proxy ( this . sendComment , this )
2015-09-06 19:07:46 +08:00
) ;
2015-09-05 23:12:11 +08:00
source . after ( reply ) ;
2016-08-15 21:38:21 +08:00
this . replyStatus = $ ( '#replystatus' ) ;
2015-09-05 23:12:11 +08:00
$ ( '#replymessage' ) . focus ( ) ;
} ,
/ * *
2017-01-14 22:29:12 +08:00
* send a reply in a discussion
2015-09-05 23:12:11 +08:00
*
2017-01-29 21:32:55 +08:00
* @ name controller . sendComment
2017-01-14 22:29:12 +08:00
* @ function
* @ param { Event } event
2015-09-05 23:12:11 +08:00
* /
sendComment : function ( event )
{
event . preventDefault ( ) ;
this . errorMessage . addClass ( 'hidden' ) ;
2017-01-14 22:29:12 +08:00
// do not send if no data
2015-09-05 23:12:11 +08:00
var replyMessage = $ ( '#replymessage' ) ;
2016-07-11 21:47:42 +08:00
if ( replyMessage . val ( ) . length === 0 )
{
return ;
}
2015-09-05 23:12:11 +08:00
2015-09-06 19:07:46 +08:00
this . showStatus ( i18n . _ ( 'Sending comment...' ) , true ) ;
2017-02-05 21:47:03 +08:00
var parentid = event . data . parentid ,
key = helper . pageKey ( ) ,
cipherdata = filter . cipher ( key , this . passwordInput . val ( ) , replyMessage . val ( ) ) ,
ciphernickname = '' ,
nick = $ ( '#nickname' ) . val ( ) ;
if ( nick . length > 0 )
2015-09-05 23:12:11 +08:00
{
2017-02-05 21:47:03 +08:00
ciphernickname = filter . cipher ( key , this . passwordInput . val ( ) , nick ) ;
2015-09-05 23:12:11 +08:00
}
var data _to _send = {
data : cipherdata ,
parentid : parentid ,
2017-02-05 21:47:03 +08:00
pasteid : helper . pasteId ( ) ,
2015-09-05 23:12:11 +08:00
nickname : ciphernickname
} ;
2015-09-28 02:34:39 +08:00
$ . ajax ( {
type : 'POST' ,
2017-02-05 21:47:03 +08:00
url : helper . scriptLocation ( ) ,
2015-09-28 02:34:39 +08:00
data : data _to _send ,
dataType : 'json' ,
headers : this . headers ,
success : function ( data )
2015-09-05 23:12:11 +08:00
{
2016-07-11 21:47:42 +08:00
if ( data . status === 0 )
2015-09-05 23:12:11 +08:00
{
2017-01-29 21:32:55 +08:00
controller . showStatus ( i18n . _ ( 'Comment posted.' ) ) ;
2015-09-28 02:34:39 +08:00
$ . ajax ( {
type : 'GET' ,
2017-02-05 21:47:03 +08:00
url : helper . scriptLocation ( ) + '?' + helper . pasteId ( ) ,
2015-09-28 02:34:39 +08:00
dataType : 'json' ,
2017-01-29 21:32:55 +08:00
headers : controller . headers ,
2015-09-28 02:34:39 +08:00
success : function ( data )
{
2016-07-11 21:47:42 +08:00
if ( data . status === 0 )
2015-09-28 02:34:39 +08:00
{
2017-01-29 21:32:55 +08:00
controller . displayMessages ( data ) ;
2015-09-28 02:34:39 +08:00
}
2016-07-11 21:47:42 +08:00
else if ( data . status === 1 )
2015-09-28 02:34:39 +08:00
{
2017-01-29 21:32:55 +08:00
controller . showError ( i18n . _ ( 'Could not refresh display: %s' , data . message ) ) ;
2015-09-28 02:34:39 +08:00
}
else
{
2017-01-29 21:32:55 +08:00
controller . showError ( i18n . _ ( 'Could not refresh display: %s' , i18n . _ ( 'unknown status' ) ) ) ;
2015-09-28 02:34:39 +08:00
}
}
} )
. fail ( function ( ) {
2017-01-29 21:32:55 +08:00
controller . showError ( i18n . _ ( 'Could not refresh display: %s' , i18n . _ ( 'server error or not responding' ) ) ) ;
2015-09-28 02:34:39 +08:00
} ) ;
}
2016-07-11 21:47:42 +08:00
else if ( data . status === 1 )
2015-09-28 02:34:39 +08:00
{
2017-01-29 21:32:55 +08:00
controller . showError ( i18n . _ ( 'Could not post comment: %s' , data . message ) ) ;
2015-09-28 02:34:39 +08:00
}
else
{
2017-01-29 21:32:55 +08:00
controller . showError ( i18n . _ ( 'Could not post comment: %s' , i18n . _ ( 'unknown status' ) ) ) ;
2015-09-28 02:34:39 +08:00
}
2015-09-02 04:33:07 +08:00
}
2015-09-28 02:34:39 +08:00
} )
2015-09-02 04:33:07 +08:00
. fail ( function ( ) {
2017-01-29 21:32:55 +08:00
controller . showError ( i18n . _ ( 'Could not post comment: %s' , i18n . _ ( 'server error or not responding' ) ) ) ;
2015-09-02 04:33:07 +08:00
} ) ;
2015-09-05 23:12:11 +08:00
} ,
/ * *
2017-01-14 22:29:12 +08:00
* send a new paste to server
2015-09-05 23:12:11 +08:00
*
2017-01-29 21:32:55 +08:00
* @ name controller . sendData
2017-01-14 22:29:12 +08:00
* @ function
* @ param { Event } event
2015-09-05 23:12:11 +08:00
* /
sendData : function ( event )
2015-09-02 04:33:07 +08:00
{
2015-09-05 23:12:11 +08:00
event . preventDefault ( ) ;
2015-09-19 20:19:42 +08:00
var file = document . getElementById ( 'file' ) ,
files = ( file && file . files ) ? file . files : null ; // FileList object
2015-09-05 23:12:11 +08:00
2017-01-14 22:29:12 +08:00
// do not send if no data.
2016-07-11 21:47:42 +08:00
if ( this . message . val ( ) . length === 0 && ! ( files && files [ 0 ] ) )
{
return ;
}
2015-09-05 23:12:11 +08:00
2017-01-14 22:29:12 +08:00
// if sjcl has not collected enough entropy yet, display a message
2015-09-05 23:12:11 +08:00
if ( ! sjcl . random . isReady ( ) )
{
2015-09-06 19:07:46 +08:00
this . showStatus ( i18n . _ ( 'Sending paste (Please move your mouse for more entropy)...' ) , true ) ;
2015-09-05 23:12:11 +08:00
sjcl . random . addEventListener ( 'seeded' , function ( ) {
this . sendData ( event ) ;
} ) ;
return ;
}
2013-02-24 23:29:13 +08:00
2015-09-12 16:23:12 +08:00
$ ( '.navbar-toggle' ) . click ( ) ;
this . password . addClass ( 'hidden' ) ;
2015-09-06 19:07:46 +08:00
this . showStatus ( i18n . _ ( 'Sending paste...' ) , true ) ;
2015-09-05 23:12:11 +08:00
2017-02-07 03:16:03 +08:00
this . stateSubmittingPaste ( ) ;
2017-02-05 21:47:03 +08:00
var randomkey = sjcl . codec . base64 . fromBits ( sjcl . random . randomWords ( 8 , 0 ) , 0 ) ,
password = this . passwordInput . val ( ) ;
2015-09-17 04:51:48 +08:00
if ( files && files [ 0 ] )
{
if ( typeof FileReader === undefined )
{
2017-02-06 05:09:46 +08:00
// revert loading status…
this . stateNewPaste ( ) ;
2015-09-17 04:51:48 +08:00
this . showError ( i18n . _ ( 'Your browser does not support uploading encrypted files. Please use a newer browser.' ) ) ;
return ;
}
var reader = new FileReader ( ) ;
2017-01-14 22:29:12 +08:00
// closure to capture the file information
2015-09-17 04:51:48 +08:00
reader . onload = ( function ( theFile )
{
return function ( e ) {
2017-01-29 21:32:55 +08:00
controller . sendDataContinue (
2015-09-17 04:51:48 +08:00
randomkey ,
2015-09-18 18:33:10 +08:00
filter . cipher ( randomkey , password , e . target . result ) ,
filter . cipher ( randomkey , password , theFile . name )
2015-09-17 04:51:48 +08:00
) ;
2016-01-31 16:56:06 +08:00
} ;
2015-09-17 04:51:48 +08:00
} ) ( files [ 0 ] ) ;
reader . readAsDataURL ( files [ 0 ] ) ;
}
else if ( this . attachmentLink . attr ( 'href' ) )
{
this . sendDataContinue (
randomkey ,
2015-09-18 18:33:10 +08:00
filter . cipher ( randomkey , password , this . attachmentLink . attr ( 'href' ) ) ,
this . attachmentLink . attr ( 'download' )
2015-09-17 04:51:48 +08:00
) ;
}
else
{
2015-09-18 18:33:10 +08:00
this . sendDataContinue ( randomkey , '' , '' ) ;
2015-09-17 04:51:48 +08:00
}
} ,
/ * *
2017-01-14 22:29:12 +08:00
* send a new paste to server , step 2
2015-09-17 04:51:48 +08:00
*
2017-01-29 21:32:55 +08:00
* @ name controller . sendDataContinue
2017-01-14 22:29:12 +08:00
* @ function
* @ param { string } randomkey
* @ param { string } cipherdata _attachment
* @ param { string } cipherdata _attachment _name
2015-09-17 04:51:48 +08:00
* /
2015-09-18 18:33:10 +08:00
sendDataContinue : function ( randomkey , cipherdata _attachment , cipherdata _attachment _name )
2015-09-17 04:51:48 +08:00
{
2017-02-05 21:47:03 +08:00
var cipherdata = filter . cipher ( randomkey , this . passwordInput . val ( ) , this . message . val ( ) ) ,
data _to _send = {
data : cipherdata ,
expire : $ ( '#pasteExpiration' ) . val ( ) ,
formatter : $ ( '#pasteFormatter' ) . val ( ) ,
burnafterreading : this . burnAfterReading . is ( ':checked' ) ? 1 : 0 ,
opendiscussion : this . openDiscussion . is ( ':checked' ) ? 1 : 0
} ;
2015-09-17 04:51:48 +08:00
if ( cipherdata _attachment . length > 0 )
{
data _to _send . attachment = cipherdata _attachment ;
2015-09-18 18:33:10 +08:00
if ( cipherdata _attachment _name . length > 0 )
{
data _to _send . attachmentname = cipherdata _attachment _name ;
}
2015-09-17 04:51:48 +08:00
}
2015-09-28 02:34:39 +08:00
$ . ajax ( {
type : 'POST' ,
2017-02-05 21:47:03 +08:00
url : helper . scriptLocation ( ) ,
2015-09-28 02:34:39 +08:00
data : data _to _send ,
dataType : 'json' ,
headers : this . headers ,
success : function ( data )
2015-09-05 23:12:11 +08:00
{
2016-07-11 21:47:42 +08:00
if ( data . status === 0 ) {
2017-01-29 21:32:55 +08:00
controller . stateExistingPaste ( ) ;
2017-02-05 21:47:03 +08:00
var url = helper . scriptLocation ( ) + '?' + data . id + '#' + randomkey ,
deleteUrl = helper . scriptLocation ( ) + '?pasteid=' + data . id + '&deletetoken=' + data . deletetoken ;
2017-01-29 21:32:55 +08:00
controller . showStatus ( '' ) ;
controller . errorMessage . addClass ( 'hidden' ) ;
2017-02-06 04:22:09 +08:00
// show new URL in browser bar
2017-02-06 01:53:57 +08:00
history . pushState ( { type : 'newpaste' } , document . title , url ) ;
2015-09-28 02:34:39 +08:00
2016-01-31 16:56:06 +08: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
2017-01-29 21:32:55 +08:00
) + controller . shortenUrl ( url )
2016-01-31 16:56:06 +08:00
) ;
2017-02-06 04:22:09 +08:00
// save newly created element
controller . pasteUrl = $ ( '#pasteurl' ) ;
// and add click event
controller . pasteUrl . click ( $ . proxy ( controller . pasteLinkClick , controller ) ) ;
2016-08-18 21:09:58 +08:00
var shortenButton = $ ( '#shortenbutton' ) ;
if ( shortenButton ) {
2017-01-29 21:48:56 +08:00
shortenButton . click ( $ . proxy ( controller . sendToShortener , controller ) ) ;
2016-08-18 21:09:58 +08:00
}
2015-09-28 02:34:39 +08:00
$ ( '#deletelink' ) . html ( '<a href="' + deleteUrl + '">' + i18n . _ ( 'Delete data' ) + '</a>' ) ;
2017-01-29 21:32:55 +08:00
controller . pasteResult . removeClass ( 'hidden' ) ;
2017-01-14 22:29:12 +08:00
// we pre-select the link so that the user only has to [Ctrl]+[c] the link
2015-09-28 02:34:39 +08:00
helper . selectText ( 'pasteurl' ) ;
2017-01-29 21:32:55 +08:00
controller . showStatus ( '' ) ;
controller . formatPaste ( data _to _send . formatter , controller . message . val ( ) ) ;
2015-09-28 02:34:39 +08:00
}
2016-07-11 21:47:42 +08:00
else if ( data . status === 1 )
2015-09-28 02:34:39 +08:00
{
2017-02-06 05:09:46 +08:00
// revert loading status…
controller . stateNewPaste ( ) ;
2017-01-29 21:32:55 +08:00
controller . showError ( i18n . _ ( 'Could not create paste: %s' , data . message ) ) ;
2015-09-28 02:34:39 +08:00
}
else
{
2017-02-06 05:09:46 +08:00
// revert loading status…
controller . stateNewPaste ( ) ;
2017-01-29 21:32:55 +08:00
controller . showError ( i18n . _ ( 'Could not create paste: %s' , i18n . _ ( 'unknown status' ) ) ) ;
2015-09-28 02:34:39 +08:00
}
2015-09-05 23:12:11 +08:00
}
2015-09-28 02:34:39 +08:00
} )
. fail ( function ( )
{
2017-02-06 05:09:46 +08:00
// revert loading status…
this . stateNewPaste ( ) ;
2017-01-29 21:32:55 +08:00
controller . showError ( i18n . _ ( 'Could not create paste: %s' , i18n . _ ( 'server error or not responding' ) ) ) ;
2015-09-05 23:12:11 +08:00
} ) ;
} ,
2012-04-22 03:59:45 +08:00
2016-01-31 16:56:06 +08:00
/ * *
2017-01-14 22:29:12 +08:00
* check if a URL shortener was defined and create HTML containing a link to it
2016-01-31 16:56:06 +08:00
*
2017-01-29 21:32:55 +08:00
* @ name controller . shortenUrl
2017-01-14 22:29:12 +08:00
* @ function
* @ param { string } url
* @ return { string } html
2016-01-31 16:56:06 +08:00
* /
shortenUrl : function ( url )
{
var shortenerHtml = $ ( '#shortenbutton' ) ;
if ( shortenerHtml ) {
2016-08-18 21:09:58 +08:00
this . shortenerUrl = shortenerHtml . data ( 'shortener' ) ;
this . createdPasteUrl = url ;
2016-01-31 16:56:06 +08:00
return ' ' + $ ( '<div />' ) . append ( shortenerHtml . clone ( ) ) . html ( ) ;
}
return '' ;
} ,
2015-09-05 23:12:11 +08:00
/ * *
2017-01-14 22:29:12 +08:00
* put the screen in "New paste" mode
*
2017-01-29 21:32:55 +08:00
* @ name controller . stateNewPaste
2017-01-14 22:29:12 +08:00
* @ function
2015-09-05 23:12:11 +08:00
* /
stateNewPaste : function ( )
{
this . message . text ( '' ) ;
2015-09-18 16:49:39 +08:00
this . attachment . addClass ( 'hidden' ) ;
2015-09-05 23:12:11 +08: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' ) ;
2017-02-06 05:09:46 +08:00
this . loadingIndicator . addClass ( 'hidden' ) ;
2015-09-05 23:12:11 +08:00
this . sendButton . removeClass ( 'hidden' ) ;
this . expiration . removeClass ( 'hidden' ) ;
2015-09-12 23:33:16 +08:00
this . formatter . removeClass ( 'hidden' ) ;
2015-09-05 23:12:11 +08:00
this . burnAfterReadingOption . removeClass ( 'hidden' ) ;
this . openDisc . removeClass ( 'hidden' ) ;
this . newButton . removeClass ( 'hidden' ) ;
this . password . removeClass ( 'hidden' ) ;
2015-09-17 04:51:48 +08:00
this . attach . removeClass ( 'hidden' ) ;
2015-09-05 23:12:11 +08:00
this . message . removeClass ( 'hidden' ) ;
2016-07-11 17:09:41 +08:00
this . preview . removeClass ( 'hidden' ) ;
2015-09-05 23:12:11 +08:00
this . message . focus ( ) ;
} ,
2017-02-06 05:09:46 +08:00
/ * *
* put the screen in mode after submitting a paste
*
* @ name controller . stateSubmittingPaste
* @ function
* /
stateSubmittingPaste : function ( )
{
this . message . text ( '' ) ;
this . attachment . addClass ( 'hidden' ) ;
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 . addClass ( 'hidden' ) ;
this . expiration . addClass ( 'hidden' ) ;
this . formatter . addClass ( 'hidden' ) ;
this . burnAfterReadingOption . addClass ( 'hidden' ) ;
this . openDisc . addClass ( 'hidden' ) ;
this . newButton . addClass ( 'hidden' ) ;
this . password . addClass ( 'hidden' ) ;
this . attach . addClass ( 'hidden' ) ;
this . message . addClass ( 'hidden' ) ;
this . preview . addClass ( 'hidden' ) ;
this . loadingIndicator . removeClass ( 'hidden' ) ;
} ,
2017-02-07 05:39:45 +08:00
/ * *
* put the screen in a state where the only option is to submit a
* new paste
*
* @ name controller . stateOnlyNewPaste
* @ function
* /
stateOnlyNewPaste : function ( )
{
this . message . text ( '' ) ;
this . attachment . addClass ( 'hidden' ) ;
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 . addClass ( 'hidden' ) ;
this . expiration . addClass ( 'hidden' ) ;
this . formatter . addClass ( 'hidden' ) ;
this . burnAfterReadingOption . addClass ( 'hidden' ) ;
this . openDisc . addClass ( 'hidden' ) ;
this . password . addClass ( 'hidden' ) ;
this . attach . addClass ( 'hidden' ) ;
this . message . addClass ( 'hidden' ) ;
this . preview . addClass ( 'hidden' ) ;
this . loadingIndicator . addClass ( 'hidden' ) ;
this . newButton . removeClass ( 'hidden' ) ;
} ,
2015-09-05 23:12:11 +08:00
/ * *
2017-01-14 22:29:12 +08:00
* put the screen in "Existing paste" mode
2016-07-11 17:09:41 +08:00
*
2017-01-29 21:32:55 +08:00
* @ name controller . stateExistingPaste
2017-01-14 22:29:12 +08:00
* @ function
* @ param { boolean } [ preview = false ] - ( optional ) tell if the preview tabs should be displayed , defaults to false
2015-09-05 23:12:11 +08:00
* /
2016-07-11 17:09:41 +08:00
stateExistingPaste : function ( preview )
2015-09-05 23:12:11 +08:00
{
2016-07-11 17:09:41 +08:00
preview = preview || false ;
2012-04-22 03:59:45 +08:00
2016-07-11 17:09:41 +08:00
if ( ! preview )
2015-09-05 23:12:11 +08:00
{
2017-01-14 22:29:12 +08:00
// no "clone" for IE<10.
2016-07-11 17:09:41 +08:00
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 23:12:11 +08:00
}
this . pasteResult . addClass ( 'hidden' ) ;
this . message . addClass ( 'hidden' ) ;
this . clearText . addClass ( 'hidden' ) ;
2015-09-19 03:41:50 +08:00
this . prettyMessage . addClass ( 'hidden' ) ;
2017-02-06 05:09:46 +08:00
this . loadingIndicator . addClass ( 'hidden' ) ;
2015-09-05 23:12:11 +08:00
} ,
/ * *
2017-01-14 22:29:12 +08:00
* when "burn after reading" is checked , disable discussion
*
2017-01-29 21:32:55 +08:00
* @ name controller . changeBurnAfterReading
2017-01-14 22:29:12 +08:00
* @ function
2015-09-05 23:12:11 +08:00
* /
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 17:31:34 +08:00
/ * *
2017-01-14 22:29:12 +08:00
* when discussion is checked , disable "burn after reading"
*
2017-01-29 21:32:55 +08:00
* @ name controller . changeOpenDisc
2017-01-14 22:29:12 +08:00
* @ function
2016-08-11 17:31:34 +08:00
* /
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 21:09:58 +08:00
/ * *
2017-01-14 22:29:12 +08:00
* forward to URL shortener
2016-08-18 21:09:58 +08:00
*
2017-01-29 21:32:55 +08:00
* @ name controller . sendToShortener
2017-01-14 22:29:12 +08:00
* @ function
* @ param { Event } event
2016-08-18 21:09:58 +08:00
* /
sendToShortener : function ( event )
{
event . preventDefault ( ) ;
window . location . href = this . shortenerUrl + encodeURIComponent ( this . createdPasteUrl ) ;
} ,
2015-09-05 23:12:11 +08:00
/ * *
2017-01-14 22:29:12 +08:00
* reload the page
2015-09-05 23:12:11 +08:00
*
2017-02-07 03:39:52 +08:00
* This takes the user to the PrivateBin home page .
*
2017-01-29 21:32:55 +08:00
* @ name controller . reloadPage
2017-01-14 22:29:12 +08:00
* @ function
* @ param { Event } event
2015-09-05 23:12:11 +08:00
* /
reloadPage : function ( event )
{
event . preventDefault ( ) ;
2017-02-05 21:47:03 +08:00
window . location . href = helper . scriptLocation ( ) ;
2015-09-05 23:12:11 +08:00
} ,
/ * *
2017-01-14 22:29:12 +08:00
* return raw text
2015-09-05 23:12:11 +08:00
*
2017-01-29 21:32:55 +08:00
* @ name controller . rawText
2017-01-14 22:29:12 +08:00
* @ function
* @ param { Event } event
2015-09-05 23:12:11 +08:00
* /
rawText : function ( event )
{
event . preventDefault ( ) ;
2016-08-15 21:04:12 +08:00
var paste = $ ( '#pasteFormatter' ) . val ( ) === 'markdown' ?
this . prettyPrint . text ( ) : this . clearText . text ( ) ;
2016-08-16 15:51:36 +08:00
history . pushState (
2017-02-05 21:47:03 +08:00
null , document . title , helper . scriptLocation ( ) + '?' +
helper . pasteId ( ) + '#' + helper . pageKey ( )
2016-08-16 15:51:36 +08:00
) ;
// 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 23:12:11 +08:00
var newDoc = document . open ( 'text/html' , 'replace' ) ;
2016-12-25 20:04:06 +08:00
newDoc . write ( '<pre>' + helper . htmlEntities ( paste ) + '</pre>' ) ;
2015-09-05 23:12:11 +08:00
newDoc . close ( ) ;
} ,
/ * *
2017-01-14 22:29:12 +08:00
* clone the current paste
2015-09-05 23:12:11 +08:00
*
2017-01-29 21:32:55 +08:00
* @ name controller . clonePaste
2017-01-14 22:29:12 +08:00
* @ function
* @ param { Event } event
2015-09-05 23:12:11 +08:00
* /
clonePaste : function ( event )
{
event . preventDefault ( ) ;
this . stateNewPaste ( ) ;
2012-04-22 03:59:45 +08:00
2017-01-14 22:29:12 +08:00
// erase the id and the key in url
2017-02-05 21:47:03 +08:00
history . replaceState ( null , document . title , helper . scriptLocation ( ) ) ;
2012-04-22 03:59:45 +08:00
2017-01-14 22:29:12 +08:00
this . showStatus ( '' ) ;
2015-09-17 04:51:48 +08:00
if ( this . attachmentLink . attr ( 'href' ) )
{
this . clonedFile . removeClass ( 'hidden' ) ;
this . fileWrap . addClass ( 'hidden' ) ;
}
2016-08-15 21:04:12 +08:00
this . message . text (
$ ( '#pasteFormatter' ) . val ( ) === 'markdown' ?
this . prettyPrint . text ( ) : this . clearText . text ( )
) ;
2015-09-12 16:23:12 +08:00
$ ( '.navbar-toggle' ) . click ( ) ;
2015-09-05 23:12:11 +08:00
} ,
2012-04-22 03:59:45 +08:00
2016-08-09 20:46:32 +08:00
/ * *
2017-01-14 22:29:12 +08:00
* set the expiration on bootstrap templates
2016-08-09 20:46:32 +08:00
*
2017-01-29 21:32:55 +08:00
* @ name controller . setExpiration
2017-01-14 22:29:12 +08:00
* @ function
* @ param { Event } event
2016-08-09 20:46:32 +08:00
* /
setExpiration : function ( event )
{
event . preventDefault ( ) ;
var target = $ ( event . target ) ;
$ ( '#pasteExpiration' ) . val ( target . data ( 'expiration' ) ) ;
$ ( '#pasteExpirationDisplay' ) . text ( target . text ( ) ) ;
} ,
/ * *
2017-01-14 22:29:12 +08:00
* set the format on bootstrap templates
2016-08-09 20:46:32 +08:00
*
2017-01-29 21:32:55 +08:00
* @ name controller . setFormat
2017-01-14 22:29:12 +08:00
* @ function
* @ param { Event } event
2016-08-09 20:46:32 +08:00
* /
setFormat : function ( event )
{
event . preventDefault ( ) ;
var target = $ ( event . target ) ;
$ ( '#pasteFormatter' ) . val ( target . data ( 'format' ) ) ;
$ ( '#pasteFormatterDisplay' ) . text ( target . text ( ) ) ;
2016-08-11 17:40:37 +08:00
if ( this . messagePreview . parent ( ) . hasClass ( 'active' ) ) {
this . viewPreview ( event ) ;
}
2016-08-09 20:46:32 +08:00
} ,
/ * *
2017-01-14 22:29:12 +08:00
* set the language in a cookie and reload the page
2016-08-09 20:46:32 +08:00
*
2017-01-29 21:32:55 +08:00
* @ name controller . setLanguage
2017-01-14 22:29:12 +08:00
* @ function
* @ param { Event } event
2016-08-09 20:46:32 +08:00
* /
setLanguage : function ( event )
{
document . cookie = 'lang=' + $ ( event . target ) . data ( 'lang' ) ;
this . reloadPage ( event ) ;
} ,
2015-10-16 04:06:01 +08:00
/ * *
2017-01-14 22:29:12 +08:00
* support input of tab character
2015-10-16 04:06:01 +08:00
*
2017-01-29 21:32:55 +08:00
* @ name controller . supportTabs
2017-01-14 22:29:12 +08:00
* @ function
* @ param { Event } event
2015-10-16 04:06:01 +08:00
* /
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 17:09:41 +08:00
/ * *
2017-01-14 22:29:12 +08:00
* view the editor tab
2016-07-11 17:09:41 +08:00
*
2017-01-29 21:32:55 +08:00
* @ name controller . viewEditor
2017-01-14 22:29:12 +08:00
* @ function
* @ param { Event } event
2016-07-11 17:09:41 +08:00
* /
viewEditor : function ( event )
{
event . preventDefault ( ) ;
this . messagePreview . parent ( ) . removeClass ( 'active' ) ;
this . messageEdit . parent ( ) . addClass ( 'active' ) ;
this . message . focus ( ) ;
this . stateNewPaste ( ) ;
} ,
/ * *
2017-01-14 22:29:12 +08:00
* view the preview tab
2016-07-11 17:09:41 +08:00
*
2017-01-29 21:32:55 +08:00
* @ name controller . viewPreview
2017-01-14 22:29:12 +08:00
* @ function
* @ param { Event } event
2016-07-11 17:09:41 +08:00
* /
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 ( ) ) ;
} ,
2017-02-05 21:47:03 +08:00
/ * *
* handle history ( pop ) state changes
*
* currently this does only handle redirects to the home page .
*
* @ name controller . historyChange
* @ function
* @ param { Event } event
* /
historyChange : function ( event )
{
var currentLocation = helper . scriptLocation ( ) ;
if ( event . originalEvent . state === null && // no state object passed
event . originalEvent . target . location . href === currentLocation && // target location is home page
window . location . href === currentLocation // and we are not already on the home page
) {
// redirect to home page
window . location . href = currentLocation ;
}
} ,
2017-02-06 04:22:09 +08:00
/ * *
* Forces opening the paste if the link does not do this automatically .
*
* This is necessary as browsers will not reload the page when it is
* already loaded ( which is fake as it is set via history . pushState ( ) ) .
*
* @ name controller . pasteLinkClick
* @ function
* @ param { Event } event
* /
pasteLinkClick : function ( event )
{
2017-02-06 04:35:28 +08:00
// check if location is (already) shown in URL bar
2017-02-06 04:22:09 +08:00
if ( window . location . href === this . pasteUrl . attr ( 'href' ) ) {
2017-02-07 03:39:52 +08:00
// if so we need to load link by reloading the current site
window . location . reload ( true ) ;
2017-02-06 04:22:09 +08:00
}
} ,
2015-09-05 23:12:11 +08:00
/ * *
2017-01-14 22:29:12 +08:00
* create a new paste
*
2017-01-29 21:32:55 +08:00
* @ name controller . newPaste
2017-01-14 22:29:12 +08:00
* @ function
2015-09-05 23:12:11 +08:00
* /
newPaste : function ( )
{
this . stateNewPaste ( ) ;
2017-01-14 22:29:12 +08:00
this . showStatus ( '' ) ;
2015-09-05 23:12:11 +08:00
this . message . text ( '' ) ;
2016-08-15 20:25:52 +08:00
this . changeBurnAfterReading ( ) ;
this . changeOpenDisc ( ) ;
2015-09-05 23:12:11 +08:00
} ,
2015-09-17 04:51:48 +08:00
/ * *
2017-01-14 22:29:12 +08:00
* removes an attachment
*
2017-01-29 21:32:55 +08:00
* @ name controller . removeAttachment
2017-01-14 22:29:12 +08:00
* @ function
2015-09-17 04:51:48 +08:00
* /
removeAttachment : function ( )
{
this . clonedFile . addClass ( 'hidden' ) ;
// removes the saved decrypted file data
2015-09-18 18:33:10 +08:00
this . attachmentLink . attr ( 'href' , '' ) ;
2015-09-17 04:51:48 +08: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-14 01:12:10 +08:00
/ * *
2017-01-14 22:29:12 +08:00
* decrypt using the password from the modal dialog
*
2017-01-29 21:32:55 +08:00
* @ name controller . decryptPasswordModal
2017-01-14 22:29:12 +08:00
* @ function
2016-11-14 01:12:10 +08:00
* /
decryptPasswordModal : function ( )
{
this . passwordInput . val ( this . passwordDecrypt . val ( ) ) ;
this . displayMessages ( ) ;
} ,
/ * *
2017-01-14 22:29:12 +08:00
* submit a password in the modal dialog
2016-11-14 01:12:10 +08:00
*
2017-01-29 21:32:55 +08:00
* @ name controller . submitPasswordModal
2017-01-14 22:29:12 +08:00
* @ function
* @ param { Event } event
2016-11-14 01:12:10 +08:00
* /
submitPasswordModal : function ( event )
{
event . preventDefault ( ) ;
this . passwordModal . modal ( 'hide' ) ;
} ,
2015-09-05 23:12:11 +08:00
/ * *
2017-01-14 22:29:12 +08:00
* display an error message ,
* we use the same function for paste and reply to comments
2015-09-05 23:12:11 +08:00
*
2017-01-29 21:32:55 +08:00
* @ name controller . showError
2017-01-14 22:29:12 +08:00
* @ function
* @ param { string } message - text to display
2015-09-05 23:12:11 +08:00
* /
showError : function ( message )
{
if ( this . status . length )
{
this . status . addClass ( 'errorMessage' ) . text ( message ) ;
}
else
{
2015-09-05 23:21:05 +08:00
this . errorMessage . removeClass ( 'hidden' ) ;
2015-09-12 23:33:16 +08:00
helper . setMessage ( this . errorMessage , message ) ;
2015-09-05 23:12:11 +08:00
}
2016-08-15 21:38:21 +08: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 23:12:11 +08:00
} ,
/ * *
2017-01-14 22:29:12 +08:00
* display a status message ,
* we use the same function for paste and reply to comments
2015-09-05 23:12:11 +08:00
*
2017-01-29 21:32:55 +08:00
* @ name controller . showStatus
2017-01-14 22:29:12 +08:00
* @ function
* @ param { string } message - text to display
* @ param { boolean } [ spin = false ] - ( optional ) tell if the "spinning" animation should be displayed , defaults to false
2015-09-05 23:12:11 +08:00
* /
showStatus : function ( message , spin )
{
2017-01-14 22:29:12 +08:00
if ( spin || false )
{
var img = '<img src="img/busy.gif" style="width:16px;height:9px;margin:0 4px 0 0;" />' ;
this . status . prepend ( img ) ;
if ( typeof this . replyStatus !== 'undefined' ) {
this . replyStatus . prepend ( img ) ;
}
}
2016-08-15 21:38:21 +08:00
if ( typeof this . replyStatus !== 'undefined' ) {
this . replyStatus . removeClass ( 'errorMessage' ) . text ( message ) ;
}
2015-09-05 23:12:11 +08:00
if ( ! message )
{
this . status . html ( ' ' ) ;
return ;
}
2016-07-11 21:47:42 +08:00
if ( message === '' )
2015-09-05 23:12:11 +08:00
{
this . status . html ( ' ' ) ;
return ;
}
this . status . removeClass ( 'errorMessage' ) . text ( message ) ;
} ,
2012-04-23 22:30:02 +08:00
2015-09-05 23:12:11 +08:00
/ * *
* bind events to DOM elements
2017-01-14 22:29:12 +08:00
*
2017-01-29 21:32:55 +08:00
* @ name controller . bindEvents
2017-01-14 22:29:12 +08:00
* @ function
2015-09-05 23:12:11 +08:00
* /
bindEvents : function ( )
{
this . burnAfterReading . change ( $ . proxy ( this . changeBurnAfterReading , this ) ) ;
2016-08-11 17:31:34 +08:00
this . openDisc . change ( $ . proxy ( this . changeOpenDisc , this ) ) ;
2015-09-05 23:12:11 +08:00
this . sendButton . click ( $ . proxy ( this . sendData , this ) ) ;
this . cloneButton . click ( $ . proxy ( this . clonePaste , this ) ) ;
this . rawTextButton . click ( $ . proxy ( this . rawText , this ) ) ;
2015-09-17 04:51:48 +08:00
this . fileRemoveButton . click ( $ . proxy ( this . removeAttachment , this ) ) ;
2015-09-05 23:12:11 +08:00
$ ( '.reloadlink' ) . click ( $ . proxy ( this . reloadPage , this ) ) ;
2015-10-16 04:06:01 +08:00
this . message . keydown ( this . supportTabs ) ;
2016-07-11 17:09:41 +08:00
this . messageEdit . click ( $ . proxy ( this . viewEditor , this ) ) ;
this . messagePreview . click ( $ . proxy ( this . viewPreview , this ) ) ;
2016-08-09 20:46:32 +08: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-14 01:12:10 +08:00
// handle modal password request on decryption
2017-01-14 23:13:22 +08:00
this . passwordModal . on ( 'shown.bs.modal' , $ . proxy ( this . passwordDecrypt . focus , this ) ) ;
2016-11-14 01:12:10 +08:00
this . passwordModal . on ( 'hidden.bs.modal' , $ . proxy ( this . decryptPasswordModal , this ) ) ;
this . passwordForm . submit ( $ . proxy ( this . submitPasswordModal , this ) ) ;
2017-02-02 02:24:56 +08:00
$ ( window ) . on ( 'popstate' , $ . proxy ( this . historyChange , this ) ) ;
2015-09-05 23:12:11 +08:00
} ,
/ * *
* main application
2017-01-14 22:29:12 +08:00
*
2017-01-29 21:32:55 +08:00
* @ name controller . init
2017-01-14 22:29:12 +08:00
* @ function
2015-09-05 23:12:11 +08:00
* /
init : function ( )
{
// hide "no javascript" message
$ ( '#noscript' ) . hide ( ) ;
// preload jQuery wrapped DOM elements and bind events
2015-09-17 04:51:48 +08:00
this . attach = $ ( '#attach' ) ;
this . attachment = $ ( '#attachment' ) ;
this . attachmentLink = $ ( '#attachment a' ) ;
2015-09-05 23:12:11 +08:00
this . burnAfterReading = $ ( '#burnafterreading' ) ;
this . burnAfterReadingOption = $ ( '#burnafterreadingoption' ) ;
this . cipherData = $ ( '#cipherdata' ) ;
this . clearText = $ ( '#cleartext' ) ;
this . cloneButton = $ ( '#clonebutton' ) ;
2015-09-17 04:51:48 +08:00
this . clonedFile = $ ( '#clonedfile' ) ;
2015-09-05 23:12:11 +08:00
this . comments = $ ( '#comments' ) ;
this . discussion = $ ( '#discussion' ) ;
this . errorMessage = $ ( '#errormessage' ) ;
this . expiration = $ ( '#expiration' ) ;
2015-09-17 04:51:48 +08:00
this . fileRemoveButton = $ ( '#fileremovebutton' ) ;
this . fileWrap = $ ( '#filewrap' ) ;
2015-09-12 23:33:16 +08:00
this . formatter = $ ( '#formatter' ) ;
2015-09-19 03:41:50 +08:00
this . image = $ ( '#image' ) ;
2017-02-06 05:09:46 +08:00
this . loadingIndicator = $ ( '#loadingindicator' ) ;
2015-09-05 23:12:11 +08:00
this . message = $ ( '#message' ) ;
2016-07-11 17:09:41 +08:00
this . messageEdit = $ ( '#messageedit' ) ;
this . messagePreview = $ ( '#messagepreview' ) ;
2015-09-05 23:12:11 +08:00
this . newButton = $ ( '#newbutton' ) ;
this . openDisc = $ ( '#opendisc' ) ;
this . openDiscussion = $ ( '#opendiscussion' ) ;
this . password = $ ( '#password' ) ;
this . passwordInput = $ ( '#passwordinput' ) ;
2016-11-14 01:12:10 +08:00
this . passwordModal = $ ( '#passwordmodal' ) ;
this . passwordForm = $ ( '#passwordform' ) ;
this . passwordDecrypt = $ ( '#passworddecrypt' ) ;
2015-09-05 23:12:11 +08:00
this . pasteResult = $ ( '#pasteresult' ) ;
2017-02-06 04:22:09 +08:00
// this.pasteUrl is saved in sendDataContinue() if/after it is
// actually created
2015-09-05 23:12:11 +08:00
this . prettyMessage = $ ( '#prettymessage' ) ;
this . prettyPrint = $ ( '#prettyprint' ) ;
2016-07-11 17:09:41 +08:00
this . preview = $ ( '#preview' ) ;
2015-09-05 23:12:11 +08:00
this . rawTextButton = $ ( '#rawtextbutton' ) ;
this . remainingTime = $ ( '#remainingtime' ) ;
this . sendButton = $ ( '#sendbutton' ) ;
this . status = $ ( '#status' ) ;
this . bindEvents ( ) ;
2017-01-14 22:29:12 +08:00
// display status returned by php code, if any (eg. paste was properly deleted)
2015-09-05 23:12:11 +08:00
if ( this . status . text ( ) . length > 0 )
{
2017-01-14 22:29:12 +08:00
this . showStatus ( this . status . text ( ) ) ;
2015-09-05 23:12:11 +08:00
return ;
}
2012-04-23 22:30:02 +08:00
2017-01-14 22:29:12 +08:00
// keep line height even if content empty
2015-09-05 23:12:11 +08:00
this . status . html ( ' ' ) ;
2012-04-23 22:30:02 +08:00
2017-01-14 22:29:12 +08:00
// display an existing paste
2015-09-05 23:12:11 +08:00
if ( this . cipherData . text ( ) . length > 1 )
{
2017-01-14 22:29:12 +08:00
// missing decryption key in URL?
2016-07-11 21:47:42 +08:00
if ( window . location . hash . length === 0 )
2015-09-05 23:12:11 +08:00
{
2015-09-06 19:07:46 +08: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 23:12:11 +08:00
return ;
}
2012-04-23 22:30:02 +08:00
2017-01-14 22:29:12 +08:00
// show proper elements on screen
2015-09-05 23:12:11 +08:00
this . stateExistingPaste ( ) ;
2016-11-14 01:12:10 +08:00
this . displayMessages ( ) ;
2015-09-05 23:12:11 +08:00
}
2017-01-14 22:29:12 +08:00
// display error message from php code
2015-09-05 23:12:11 +08:00
else if ( this . errorMessage . text ( ) . length > 1 )
{
this . showError ( this . errorMessage . text ( ) ) ;
}
2017-01-14 22:29:12 +08:00
// create a new paste
2015-09-05 23:12:11 +08:00
else
{
this . newPaste ( ) ;
}
2012-04-22 18:45:45 +08:00
}
2013-11-01 08:15:14 +08:00
}
2017-01-29 21:32:55 +08:00
/ * *
2017-01-29 21:48:56 +08:00
* main application start , called when DOM is fully loaded and
* runs controller initalization after translations are loaded
2017-01-29 21:32:55 +08:00
* /
2017-01-31 03:29:04 +08:00
$ ( i18n . loadTranslations ) ;
2017-01-29 21:32:55 +08:00
2017-01-22 17:42:11 +08:00
return {
helper : helper ,
i18n : i18n ,
filter : filter ,
2017-01-29 21:32:55 +08:00
controller : controller
2017-01-22 17:42:11 +08:00
} ;
} ( jQuery , sjcl , Base64 , RawDeflate ) ;