2012-04-22 10:45:45 +00:00
/ * *
2016-07-11 09:58:15 +00:00
* PrivateBin
2012-04-23 14:30:02 +00:00
*
2012-04-30 20:58:08 +00:00
* a zero - knowledge paste bin
*
2017-01-14 14:29:12 +00: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}
2018-08-11 17:29:58 +00:00
* @ version 1.2 . 1
2017-01-14 14:29:12 +00:00
* @ name PrivateBin
* @ namespace
2012-04-22 10:45:45 +00:00
* /
2012-04-21 19:59:45 +00:00
2016-07-11 13:47:42 +00:00
/** global: Base64 */
2017-11-22 06:03:29 +00:00
/** global: DOMPurify */
2016-07-11 13:47:42 +00:00
/** global: FileReader */
/** global: RawDeflate */
/** global: history */
/** global: navigator */
/** global: prettyPrint */
/** global: prettyPrintOne */
/** global: showdown */
2017-11-26 14:59:12 +00:00
/** global: kjua */
2015-09-06 11:07:46 +00:00
2017-02-14 21:21:55 +00:00
// main application start, called when DOM is fully loaded
jQuery ( document ) . ready ( function ( ) {
2018-02-21 21:51:31 +00:00
'use strict' ;
2017-02-14 21:21:55 +00:00
// run main controller
$ . PrivateBin . Controller . init ( ) ;
} ) ;
2018-10-20 17:53:21 +00:00
jQuery . PrivateBin = ( function ( $ , RawDeflate ) {
2017-02-12 17:08:08 +00:00
'use strict' ;
2015-09-05 15:12:11 +00:00
/ * *
2017-02-14 21:21:55 +00:00
* static Helper methods
2017-01-14 14:29:12 +00:00
*
2017-03-13 19:24:18 +00:00
* @ name Helper
2017-01-14 14:29:12 +00:00
* @ class
2015-09-05 15:12:11 +00:00
* /
2017-03-05 11:11:55 +00:00
var Helper = ( function ( ) {
2017-02-08 19:12:22 +00:00
var me = { } ;
2017-04-11 14:34:13 +00:00
/ * *
2017-04-13 08:46:09 +00:00
* blacklist of UserAgents ( parts ) known to belong to a bot
2017-04-11 14:34:13 +00:00
*
* @ private
* @ enum { Object }
* @ readonly
* /
var BadBotUA = [
'Bot' ,
'bot'
] ;
2017-02-08 19:12:22 +00:00
/ * *
* cache for script location
*
2017-03-13 19:24:18 +00:00
* @ name Helper . baseUri
2017-02-08 19:12:22 +00:00
* @ private
* @ enum { string | null }
* /
2017-02-14 21:21:55 +00:00
var baseUri = null ;
2017-02-08 19:12:22 +00:00
2015-09-05 15:12:11 +00:00
/ * *
2017-01-14 14:29:12 +00:00
* converts a duration ( in seconds ) into human friendly approximation
2015-09-05 15:12:11 +00:00
*
2017-02-14 21:21:55 +00:00
* @ name Helper . secondsToHuman
2017-01-14 14:29:12 +00:00
* @ function
* @ param { number } seconds
* @ return { Array }
2015-09-05 15:12:11 +00:00
* /
2017-02-08 19:12:22 +00:00
me . secondsToHuman = function ( seconds )
2015-09-05 15:12:11 +00:00
{
2016-07-11 13:47:42 +00:00
var v ;
2015-09-05 15:12:11 +00:00
if ( seconds < 60 )
{
2016-07-11 13:47:42 +00:00
v = Math . floor ( seconds ) ;
2015-09-06 13:54:43 +00:00
return [ v , 'second' ] ;
2015-09-05 15:12:11 +00:00
}
if ( seconds < 60 * 60 )
{
2016-07-11 13:47:42 +00:00
v = Math . floor ( seconds / 60 ) ;
2015-09-06 13:54:43 +00:00
return [ v , 'minute' ] ;
2015-09-05 15:12:11 +00:00
}
if ( seconds < 60 * 60 * 24 )
{
2016-07-11 13:47:42 +00:00
v = Math . floor ( seconds / ( 60 * 60 ) ) ;
2015-09-06 13:54:43 +00:00
return [ v , 'hour' ] ;
2015-09-05 15:12:11 +00:00
}
// If less than 2 months, display in days:
if ( seconds < 60 * 60 * 24 * 60 )
{
2016-07-11 13:47:42 +00:00
v = Math . floor ( seconds / ( 60 * 60 * 24 ) ) ;
2015-09-06 13:54:43 +00:00
return [ v , 'day' ] ;
2015-09-05 15:12:11 +00:00
}
2016-07-11 13:47:42 +00:00
v = Math . floor ( seconds / ( 60 * 60 * 24 * 30 ) ) ;
2015-09-06 13:54:43 +00:00
return [ v , 'month' ] ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-08 19:12:22 +00:00
2015-09-05 15:12:11 +00:00
/ * *
2017-01-14 14:29:12 +00:00
* text range selection
2015-09-05 15:12:11 +00:00
*
2017-01-14 14:29:12 +00:00
* @ see { @ link https : //stackoverflow.com/questions/985272/jquery-selecting-text-in-an-element-akin-to-highlighting-with-your-mouse}
2017-02-14 21:21:55 +00:00
* @ name Helper . selectText
2017-01-14 14:29:12 +00:00
* @ function
2017-02-08 19:12:22 +00:00
* @ param { HTMLElement } element
2015-09-05 15:12:11 +00:00
* /
2017-02-08 19:12:22 +00:00
me . selectText = function ( element )
2015-09-05 15:12:11 +00:00
{
2017-02-08 19:12:22 +00:00
var range , selection ;
2015-09-05 15:12:11 +00:00
// MS
2017-02-14 21:21:55 +00:00
if ( document . body . createTextRange ) {
2017-02-08 19:12:22 +00:00
range = document . body . createTextRange ( ) ;
range . moveToElementText ( element ) ;
2015-09-05 15:12:11 +00:00
range . select ( ) ;
2017-11-13 20:57:49 +00:00
} else if ( window . getSelection ) {
2015-09-05 15:12:11 +00:00
selection = window . getSelection ( ) ;
2017-02-08 19:12:22 +00:00
range = document . createRange ( ) ;
range . selectNodeContents ( element ) ;
2015-09-05 15:12:11 +00:00
selection . removeAllRanges ( ) ;
selection . addRange ( range ) ;
}
2018-01-06 08:26:10 +00:00
} ;
2015-09-12 15:33:16 +00:00
2015-09-05 15:12:11 +00:00
/ * *
2017-01-14 14:29:12 +00:00
* convert URLs to clickable links .
2015-09-05 15:12:11 +00:00
* URLs to handle :
2017-01-14 14:29:12 +00:00
* < pre >
2015-09-05 15:12:11 +00:00
* magnet : ? xt . 1 = urn : sha1 : YNCKHTQCWBTRNJIV4WNAE52SJUQCZO5C & xt . 2 = urn : sha1 : TXGCZQTH26NL6OUQAJJPFALHG2LTGBC7
2017-04-11 14:34:13 +00:00
* https : //example.com:8800/zero/?6f09182b8ea51997#WtLEUO5Epj9UHAV9JFs+6pUQZp13TuspAUjnF+iM+dM=
2017-01-14 14:29:12 +00:00
* http : //user:example.com@localhost:8800/zero/?6f09182b8ea51997#WtLEUO5Epj9UHAV9JFs+6pUQZp13TuspAUjnF+iM+dM=
* < / p r e >
2015-09-05 15:12:11 +00:00
*
2017-02-14 21:21:55 +00:00
* @ name Helper . urls2links
2017-01-14 14:29:12 +00:00
* @ function
2017-11-22 21:27:38 +00:00
* @ param { string } html
* @ return { string }
2015-09-05 15:12:11 +00:00
* /
2017-11-22 21:27:38 +00:00
me . urls2links = function ( html )
2015-09-05 15:12:11 +00:00
{
2017-11-22 21:27:38 +00:00
return html . replace (
2018-08-11 05:33:33 +00:00
/(((https?|ftp):\/\/[\w?!=&.\/-;#@~%+*-]+(?![\w\s?!&.\/;#~%"=-]*>))|((magnet):[\w?=&.\/-;#@~%+*-]+))/ig ,
2017-11-22 21:27:38 +00:00
'<a href="$1" rel="nofollow">$1</a>'
2015-09-05 15:12:11 +00:00
) ;
2018-01-06 08:26:10 +00:00
} ;
2015-09-06 11:07:46 +00:00
/ * *
* minimal sprintf emulation for % s and % d formats
*
2017-02-11 18:34:51 +00:00
* Note that this function needs the parameters in the same order as the
* format strings appear in the string , contrary to the original .
*
2017-01-14 14:29:12 +00:00
* @ see { @ link https : //stackoverflow.com/questions/610406/javascript-equivalent-to-printf-string-format#4795914}
2017-02-14 21:21:55 +00:00
* @ name Helper . sprintf
2017-01-14 14:29:12 +00:00
* @ function
* @ param { string } format
* @ param { ... * } args - one or multiple parameters injected into format string
* @ return { string }
2015-09-06 11:07:46 +00:00
* /
2017-02-08 19:12:22 +00:00
me . sprintf = function ( )
2015-09-06 11:07:46 +00:00
{
2017-02-14 21:21:55 +00:00
var args = Array . prototype . slice . call ( arguments ) ;
2017-01-14 15:13:22 +00:00
var format = args [ 0 ] ,
2015-09-06 11:07:46 +00:00
i = 1 ;
2017-02-11 18:34:51 +00:00
return format . replace ( /%(s|d)/g , function ( m ) {
2015-09-06 11:07:46 +00:00
// m is the matched format, e.g. %s, %d
2017-02-11 18:34:51 +00:00
var val = args [ i ] ;
// A switch statement so that the formatter can be extended.
switch ( m )
{
case '%d' :
val = parseFloat ( val ) ;
if ( isNaN ( val ) ) {
val = 0 ;
}
break ;
default :
// Default is %s
2015-09-06 11:07:46 +00:00
}
2017-02-11 18:34:51 +00:00
++ i ;
2015-09-06 11:07:46 +00:00
return val ;
} ) ;
2018-01-06 08:26:10 +00:00
} ;
2015-09-21 20:43:00 +00:00
2015-09-19 09:21:13 +00:00
/ * *
* get value of cookie , if it was set , empty string otherwise
*
2017-01-14 14:29:12 +00:00
* @ see { @ link http : //www.w3schools.com/js/js_cookies.asp}
2017-02-14 21:21:55 +00:00
* @ name Helper . getCookie
2017-01-14 14:29:12 +00:00
* @ function
2017-02-12 16:11:21 +00:00
* @ param { string } cname - may not be empty
2017-01-14 14:29:12 +00:00
* @ return { string }
2015-09-19 09:21:13 +00:00
* /
2017-02-08 19:12:22 +00:00
me . getCookie = function ( cname ) {
2017-02-05 13:47:03 +00:00
var name = cname + '=' ,
ca = document . cookie . split ( ';' ) ;
2016-08-09 12:46:32 +00:00
for ( var i = 0 ; i < ca . length ; ++ i ) {
2015-09-19 09:21:13 +00:00
var c = ca [ i ] ;
2017-02-05 13:47:03 +00:00
while ( c . charAt ( 0 ) === ' ' )
{
c = c . substring ( 1 ) ;
}
2016-07-11 13:47:42 +00:00
if ( c . indexOf ( name ) === 0 )
{
return c . substring ( name . length , c . length ) ;
}
2015-09-19 09:21:13 +00:00
}
return '' ;
2018-01-06 08:26:10 +00:00
} ;
2016-07-19 14:12:11 +00:00
2017-02-05 13:47:03 +00:00
/ * *
2017-02-14 21:21:55 +00:00
* get the current location ( without search or hash part of the URL ) ,
2017-04-11 14:34:13 +00:00
* eg . https : //example.com/path/?aaaa#bbbb --> https://example.com/path/
2017-02-05 13:47:03 +00:00
*
2017-02-14 21:21:55 +00:00
* @ name Helper . baseUri
2017-02-05 13:47:03 +00:00
* @ function
2017-02-14 21:21:55 +00:00
* @ return { string }
2017-02-05 13:47:03 +00:00
* /
2017-02-14 21:21:55 +00:00
me . baseUri = function ( )
2017-02-05 13:47:03 +00:00
{
2017-02-08 19:12:22 +00:00
// check for cached version
2017-02-14 21:21:55 +00:00
if ( baseUri !== null ) {
return baseUri ;
2017-02-05 13:47:03 +00:00
}
2018-08-04 15:25:59 +00:00
baseUri = window . location . origin + window . location . pathname ;
2017-02-14 21:21:55 +00:00
return baseUri ;
2018-01-06 08:26:10 +00:00
} ;
2016-07-19 14:12:11 +00:00
/ * *
2017-03-05 11:11:55 +00:00
* resets state , used for unit testing
2017-01-14 14:29:12 +00:00
*
2017-03-13 19:24:18 +00:00
* @ name Helper . reset
2017-03-05 11:11:55 +00:00
* @ function
2016-07-19 14:12:11 +00:00
* /
2017-03-05 11:11:55 +00:00
me . reset = function ( )
{
baseUri = null ;
2018-01-06 08:26:10 +00:00
} ;
2017-03-05 11:11:55 +00:00
2017-04-11 14:34:13 +00:00
/ * *
* checks whether this is a bot we dislike
*
* @ name Helper . isBadBot
* @ function
* @ return { bool }
* /
me . isBadBot = function ( ) {
// check whether a bot user agent part can be found in the current
// user agent
var arrayLength = BadBotUA . length ;
2018-08-04 20:30:01 +00:00
for ( var i = 0 ; i < arrayLength ; ++ i ) {
2017-04-11 14:34:13 +00:00
if ( navigator . userAgent . indexOf ( BadBotUA ) >= 0 ) {
return true ;
}
}
return false ;
}
2017-02-08 19:12:22 +00:00
return me ;
2017-03-05 11:11:55 +00:00
} ) ( ) ;
2015-09-05 15:12:11 +00:00
2015-09-06 11:07:46 +00:00
/ * *
2017-02-14 21:21:55 +00:00
* internationalization module
2017-01-14 14:29:12 +00:00
*
2017-03-13 19:24:18 +00:00
* @ name I18n
2017-01-14 14:29:12 +00:00
* @ class
2015-09-06 11:07:46 +00:00
* /
2017-03-26 07:24:42 +00:00
var I18n = ( function ( ) {
2017-02-08 19:12:22 +00:00
var me = { } ;
2017-02-14 21:21:55 +00:00
/ * *
* const for string of loaded language
*
2017-03-13 19:24:18 +00:00
* @ name I18n . languageLoadedEvent
2017-02-14 21:21:55 +00:00
* @ private
* @ prop { string }
* @ readonly
* /
var languageLoadedEvent = 'languageLoaded' ;
2015-09-06 13:54:43 +00:00
/ * *
* supported languages , minus the built in 'en'
2017-01-14 14:29:12 +00:00
*
2017-03-13 19:24:18 +00:00
* @ name I18n . supportedLanguages
2017-02-08 19:12:22 +00:00
* @ private
2017-01-14 14:29:12 +00:00
* @ prop { string [ ] }
* @ readonly
2015-09-06 13:54:43 +00:00
* /
2018-05-31 15:07:42 +00:00
var supportedLanguages = [ 'de' , 'es' , 'fr' , 'it' , 'hu' , 'no' , 'nl' , 'pl' , 'pt' , 'oc' , 'ru' , 'sl' , 'zh' ] ;
2017-02-08 19:12:22 +00:00
/ * *
* built in language
*
2017-03-13 19:24:18 +00:00
* @ name I18n . language
2017-02-08 19:12:22 +00:00
* @ private
2017-02-14 21:21:55 +00:00
* @ prop { string | null }
2017-02-08 19:12:22 +00:00
* /
2017-02-14 21:21:55 +00:00
var language = null ;
2017-02-08 19:12:22 +00:00
/ * *
* translation cache
*
2017-03-13 19:24:18 +00:00
* @ name I18n . translations
2017-02-08 19:12:22 +00:00
* @ private
* @ enum { Object }
* /
var translations = { } ;
2015-09-06 13:54:43 +00:00
2015-09-06 11:07:46 +00:00
/ * *
2017-03-13 19:24:18 +00:00
* translate a string , alias for I18n . translate
2015-09-06 11:07:46 +00:00
*
2017-02-14 21:21:55 +00:00
* @ name I18n . _
2017-01-14 14:29:12 +00:00
* @ function
2017-02-14 21:21:55 +00:00
* @ param { jQuery } $element - optional
2017-01-14 14:29:12 +00:00
* @ param { string } messageId
* @ param { ... * } args - one or multiple parameters injected into placeholders
* @ return { string }
2015-09-06 11:07:46 +00:00
* /
2017-02-08 19:12:22 +00:00
me . _ = function ( )
2015-09-06 11:07:46 +00:00
{
2017-02-14 21:21:55 +00:00
return me . translate . apply ( this , arguments ) ;
2018-01-06 08:26:10 +00:00
} ;
2015-09-06 11:07:46 +00:00
/ * *
* translate a string
*
2017-02-14 21:21:55 +00:00
* Optionally pass a jQuery element as the first parameter , to automatically
* let the text of this element be replaced . In case the ( asynchronously
* loaded ) language is not downloadet yet , this will make sure the string
* is replaced when it is actually loaded .
* So for easy translations passing the jQuery object to apply it to is
* more save , especially when they are loaded in the beginning .
*
* @ name I18n . translate
2017-01-14 14:29:12 +00:00
* @ function
2017-02-14 21:21:55 +00:00
* @ param { jQuery } $element - optional
2017-01-14 14:29:12 +00:00
* @ param { string } messageId
* @ param { ... * } args - one or multiple parameters injected into placeholders
* @ return { string }
2015-09-06 11:07:46 +00:00
* /
2017-02-08 19:12:22 +00:00
me . translate = function ( )
2015-09-06 11:07:46 +00:00
{
2017-02-14 21:21:55 +00:00
// convert parameters to array
var args = Array . prototype . slice . call ( arguments ) ,
messageId ,
$element = null ;
// parse arguments
if ( args [ 0 ] instanceof jQuery ) {
// optional jQuery element as first parameter
$element = args [ 0 ] ;
args . shift ( ) ;
2016-07-11 13:47:42 +00:00
}
2017-02-14 21:21:55 +00:00
// extract messageId from arguments
2016-07-11 14:09:38 +00:00
var usesPlurals = $ . isArray ( args [ 0 ] ) ;
2017-02-14 21:21:55 +00:00
if ( usesPlurals ) {
2015-09-06 13:54:43 +00:00
// use the first plural form as messageId, otherwise the singular
2018-02-21 21:51:31 +00:00
messageId = args [ 0 ] . length > 1 ? args [ 0 ] [ 1 ] : args [ 0 ] [ 0 ] ;
2017-02-14 21:21:55 +00:00
} else {
2015-09-06 13:54:43 +00:00
messageId = args [ 0 ] ;
}
2017-02-14 21:21:55 +00:00
if ( messageId . length === 0 ) {
2016-07-11 13:47:42 +00:00
return messageId ;
}
2017-02-14 21:21:55 +00:00
// if no translation string cannot be found (in translations object)
2017-03-12 16:08:12 +00:00
if ( ! translations . hasOwnProperty ( messageId ) || language === null ) {
2017-02-14 21:21:55 +00:00
// if language is still loading and we have an elemt assigned
if ( language === null && $element !== null ) {
// handle the error by attaching the language loaded event
var orgArguments = arguments ;
$ ( document ) . on ( languageLoadedEvent , function ( ) {
2017-02-15 21:59:55 +00:00
// log to show that the previous error could be mitigated
2017-10-30 06:04:59 +00:00
console . warn ( 'Fix missing translation of \'' + messageId + '\' with now loaded language ' + language ) ;
2017-02-14 21:21:55 +00:00
// re-execute this function
me . translate . apply ( this , orgArguments ) ;
} ) ;
// and fall back to English for now until the real language
// file is loaded
}
2017-04-11 20:21:30 +00:00
// for all other languages than English for which this behaviour
2017-02-14 21:21:55 +00:00
// is expected as it is built-in, log error
2017-03-12 16:08:12 +00:00
if ( language !== null && language !== 'en' ) {
2017-02-14 21:21:55 +00:00
console . error ( 'Missing translation for: \'' + messageId + '\' in language ' + language ) ;
// fallback to English
2016-07-11 13:47:42 +00:00
}
2017-02-14 21:21:55 +00:00
// save English translation (should be the same on both sides)
2017-02-08 19:12:22 +00:00
translations [ messageId ] = args [ 0 ] ;
2015-09-06 13:54:43 +00:00
}
2017-02-14 21:21:55 +00:00
// lookup plural translation
if ( usesPlurals && $ . isArray ( translations [ messageId ] ) ) {
2016-07-11 13:47:42 +00:00
var n = parseInt ( args [ 1 ] || 1 , 10 ) ,
2017-02-08 19:12:22 +00:00
key = me . getPluralForm ( n ) ,
maxKey = translations [ messageId ] . length - 1 ;
2017-02-14 21:21:55 +00:00
if ( key > maxKey ) {
2016-07-11 13:47:42 +00:00
key = maxKey ;
}
2017-02-08 19:12:22 +00:00
args [ 0 ] = translations [ messageId ] [ key ] ;
2015-09-06 13:54:43 +00:00
args [ 1 ] = n ;
2017-02-14 21:21:55 +00:00
} else {
// lookup singular translation
2017-02-08 19:12:22 +00:00
args [ 0 ] = translations [ messageId ] ;
2015-09-06 13:54:43 +00:00
}
2017-02-14 21:21:55 +00:00
// format string
var output = Helper . sprintf . apply ( this , args ) ;
// if $element is given, apply text to element
if ( $element !== null ) {
2017-03-12 16:08:12 +00:00
// get last text node of element
var content = $element . contents ( ) ;
if ( content . length > 1 ) {
content [ content . length - 1 ] . nodeValue = ' ' + output ;
} else {
$element . text ( output ) ;
}
2015-09-06 11:07:46 +00:00
}
2017-02-14 21:21:55 +00:00
return output ;
2018-01-06 08:26:10 +00:00
} ;
2015-09-06 11:07:46 +00:00
2015-09-08 18:48:18 +00:00
/ * *
* per language functions to use to determine the plural form
*
2017-01-14 14:29:12 +00:00
* @ see { @ link http : //localization-guide.readthedocs.org/en/latest/l10n/pluralforms.html}
2017-02-14 21:21:55 +00:00
* @ name I18n . getPluralForm
2017-01-14 14:29:12 +00:00
* @ function
2017-02-15 21:59:55 +00:00
* @ param { int } n
* @ return { int } array key
2015-09-08 18:48:18 +00:00
* /
2017-02-08 19:12:22 +00:00
me . getPluralForm = function ( n ) {
switch ( language )
2015-09-08 18:48:18 +00:00
{
case 'fr' :
2017-01-08 06:56:56 +00:00
case 'oc' :
2016-04-26 18:21:30 +00:00
case 'zh' :
2018-02-21 21:51:31 +00:00
return n > 1 ? 1 : 0 ;
2015-09-08 18:48:18 +00:00
case 'pl' :
2018-02-21 21:51:31 +00:00
return n === 1 ? 0 : ( n % 10 >= 2 && n % 10 <= 4 && ( n % 100 < 10 || n % 100 >= 20 ) ? 1 : 2 ) ;
2016-12-16 09:21:15 +00:00
case 'ru' :
2018-02-21 21:51:31 +00:00
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 13:35:39 +00:00
case 'sl' :
2018-02-21 21:51:31 +00:00
return n % 100 === 1 ? 1 : ( n % 100 === 2 ? 2 : ( n % 100 === 3 || n % 100 === 4 ? 3 : 0 ) ) ;
2018-05-31 19:04:35 +00:00
// de, en, es, hu, it, nl, no, pt
2015-09-08 18:48:18 +00:00
default :
2018-02-21 21:51:31 +00:00
return n !== 1 ? 1 : 0 ;
2015-09-08 18:48:18 +00:00
}
2018-01-06 08:26:10 +00:00
} ;
2015-09-08 18:48:18 +00:00
2015-09-06 13:54:43 +00:00
/ * *
2017-02-14 21:21:55 +00:00
* load translations into cache
2015-09-06 13:54:43 +00:00
*
2017-02-14 21:21:55 +00:00
* @ name I18n . loadTranslations
2017-01-14 14:29:12 +00:00
* @ function
2015-09-06 13:54:43 +00:00
* /
2017-02-08 19:12:22 +00:00
me . loadTranslations = function ( )
2015-09-06 11:07:46 +00:00
{
2017-02-14 21:21:55 +00:00
var newLanguage = Helper . getCookie ( 'lang' ) ;
2017-02-08 12:20:51 +00:00
2017-02-08 19:12:22 +00:00
// auto-select language based on browser settings
2017-02-14 21:21:55 +00:00
if ( newLanguage . length === 0 ) {
2018-04-07 02:53:00 +00:00
newLanguage = ( navigator . language || navigator . userLanguage || 'en' ) . substring ( 0 , 2 ) ;
2017-02-05 13:47:03 +00:00
}
2015-09-06 11:07:46 +00:00
2017-02-14 21:21:55 +00:00
// if language is already used skip update
2017-02-12 17:08:08 +00:00
if ( newLanguage === language ) {
2017-02-08 19:12:22 +00:00
return ;
2015-09-08 18:48:18 +00:00
}
2015-09-06 13:54:43 +00:00
2017-02-14 21:21:55 +00:00
// if language is built-in (English) skip update
if ( newLanguage === 'en' ) {
language = 'en' ;
return ;
2015-09-08 18:48:18 +00:00
}
2015-09-06 11:07:46 +00:00
2017-02-08 19:12:22 +00:00
// if language is not supported, show error
2017-02-12 17:08:08 +00:00
if ( supportedLanguages . indexOf ( newLanguage ) === - 1 ) {
2017-02-08 19:12:22 +00:00
console . error ( 'Language \'%s\' is not supported. Translation failed, fallback to English.' , newLanguage ) ;
2017-02-14 21:21:55 +00:00
language = 'en' ;
return ;
2017-02-08 19:12:22 +00:00
}
2015-09-06 13:54:43 +00:00
2017-02-14 21:21:55 +00:00
// load strings from JSON
2017-02-08 19:12:22 +00:00
$ . getJSON ( 'i18n/' + newLanguage + '.json' , function ( data ) {
language = newLanguage ;
translations = data ;
2017-02-14 21:21:55 +00:00
$ ( document ) . triggerHandler ( languageLoadedEvent ) ;
2017-02-08 19:12:22 +00:00
} ) . fail ( function ( data , textStatus , errorMsg ) {
console . error ( 'Language \'%s\' could not be loaded (%s: %s). Translation failed, fallback to English.' , newLanguage , textStatus , errorMsg ) ;
2017-02-14 21:21:55 +00:00
language = 'en' ;
2017-02-08 19:12:22 +00:00
} ) ;
2018-01-06 08:26:10 +00:00
} ;
2015-09-06 11:07:46 +00:00
2017-03-25 08:41:24 +00:00
/ * *
* resets state , used for unit testing
*
* @ name I18n . reset
* @ function
* /
2017-03-26 07:24:42 +00:00
me . reset = function ( mockLanguage , mockTranslations )
2017-03-25 08:41:24 +00:00
{
2017-03-26 07:24:42 +00:00
language = mockLanguage || null ;
translations = mockTranslations || { } ;
2018-01-06 08:26:10 +00:00
} ;
2017-03-25 08:41:24 +00:00
2017-02-08 19:12:22 +00:00
return me ;
2017-03-26 07:24:42 +00:00
} ) ( ) ;
2015-09-06 11:07:46 +00:00
2015-09-05 15:12:11 +00:00
/ * *
2017-02-12 20:13:04 +00:00
* handles everything related to en / decryption
2017-01-14 14:29:12 +00:00
*
2017-03-13 19:24:18 +00:00
* @ name CryptTool
2017-01-14 14:29:12 +00:00
* @ class
2015-09-05 15:12:11 +00:00
* /
2017-02-14 21:21:55 +00:00
var CryptTool = ( function ( ) {
2017-02-08 19:12:22 +00:00
var me = { } ;
2018-09-01 17:42:22 +00:00
/ * *
* convert UTF - 8 string stored in a DOMString to a standard UTF - 16 DOMString
*
* Iterates over the bytes of the message , converting them all hexadecimal
* percent encoded representations , then URI decodes them all
*
* @ name CryptTool . btou
* @ function
* @ private
* @ param { string } message UTF - 8 string
* @ return { string } UTF - 16 string
* /
function btou ( message )
{
return decodeURIComponent (
message . split ( '' ) . map (
function ( character )
{
return '%' + ( '00' + character . charCodeAt ( 0 ) . toString ( 16 ) ) . slice ( - 2 ) ;
}
) . join ( '' )
) ;
}
2015-09-05 15:12:11 +00:00
/ * *
2018-08-05 06:56:03 +00:00
* convert DOMString ( UTF - 16 ) to a UTF - 8 string stored in a DOMString
*
* URI encodes the message , then finds the percent encoded characters
* and transforms these hexadecimal representation back into bytes
*
* @ name CryptTool . utob
* @ function
* @ private
* @ param { string } message UTF - 16 string
* @ return { string } UTF - 8 string
* /
function utob ( message )
{
return encodeURIComponent ( message ) . replace (
/%([0-9A-F]{2})/g ,
function ( match , hexCharacter )
{
return String . fromCharCode ( '0x' + hexCharacter ) ;
}
) ;
}
/ * *
2018-09-01 17:42:22 +00:00
* convert ArrayBuffer into a UTF - 8 string
2018-08-05 06:56:03 +00:00
*
2018-09-01 17:42:22 +00:00
* Iterates over the bytes of the array , catenating them into a string
2018-08-05 06:56:03 +00:00
*
2018-09-01 17:42:22 +00:00
* @ name CryptTool . ArrToStr
* @ function
* @ private
* @ param { ArrayBuffer } messageArray
* @ return { string } message
* /
function ArrToStr ( messageArray )
{
var array = new Uint8Array ( messageArray ) ,
len = array . length ,
message = '' ,
i = 0 ;
while ( i < len ) {
var c = array [ i ++ ] ;
message += String . fromCharCode ( c ) ;
}
return message ;
}
/ * *
* convert UTF - 8 string into a Uint8Array
*
* Iterates over the bytes of the message , writing them to the array
*
* @ name CryptTool . StrToArr
2018-08-05 06:56:03 +00:00
* @ function
* @ private
* @ param { string } message UTF - 8 string
2018-09-01 17:42:22 +00:00
* @ return { Uint8Array } array
2018-08-05 06:56:03 +00:00
* /
2018-09-01 17:42:22 +00:00
function StrToArr ( message )
2018-08-05 06:56:03 +00:00
{
2018-09-01 17:42:22 +00:00
var messageUtf8 = message ,
messageLen = messageUtf8 . length ,
messageArray = new Uint8Array ( messageLen ) ;
for ( var i = 0 ; i < messageLen ; ++ i ) {
messageArray [ i ] = messageUtf8 . charCodeAt ( i ) ;
}
return messageArray ;
2018-08-05 06:56:03 +00:00
}
/ * *
* compress a string , returns base64 encoded data ( deflate compression )
2015-09-05 15:12:11 +00:00
*
2017-03-13 19:24:18 +00:00
* @ name CryptTool . compress
2017-01-14 14:29:12 +00:00
* @ function
2017-02-12 17:08:08 +00:00
* @ private
2017-01-14 14:29:12 +00:00
* @ param { string } message
* @ return { string } base64 data
2015-09-05 15:12:11 +00:00
* /
2017-02-12 17:08:08 +00:00
function compress ( message )
2015-09-05 15:12:11 +00:00
{
2018-08-05 06:56:03 +00:00
// detect presence of Base64.js, indicating legacy ZeroBin paste
if ( typeof Base64 === 'undefined' ) {
return btoa ( utob ( RawDeflate . deflate ( utob ( message ) ) ) ) ;
} else {
return Base64 . toBase64 ( RawDeflate . deflate ( Base64 . utob ( message ) ) ) ;
}
2017-02-12 17:08:08 +00:00
}
2015-09-05 15:12:11 +00:00
/ * *
2018-08-05 06:56:03 +00:00
* decompress a base64 encoded data ( deflate compression ) , returns string
2015-09-05 15:12:11 +00:00
*
2017-03-13 19:24:18 +00:00
* @ name CryptTool . decompress
2017-01-14 14:29:12 +00:00
* @ function
2017-02-12 17:08:08 +00:00
* @ private
2018-08-05 06:56:03 +00:00
* @ param { string } data base64 data
2017-01-14 14:29:12 +00:00
* @ return { string } message
2015-09-05 15:12:11 +00:00
* /
2017-02-12 17:08:08 +00:00
function decompress ( data )
2015-09-05 15:12:11 +00:00
{
2018-08-05 06:56:03 +00:00
// detect presence of Base64.js, indicating legacy ZeroBin paste
if ( typeof Base64 === 'undefined' ) {
return btou ( RawDeflate . inflate ( btou ( atob ( data ) ) ) ) ;
} else {
return Base64 . btou ( RawDeflate . inflate ( Base64 . fromBase64 ( data ) ) ) ;
}
2017-02-12 17:08:08 +00:00
}
2015-09-05 15:12:11 +00:00
2018-09-01 17:42:22 +00:00
/ * *
* returns specified number of random bytes
*
* @ name CryptTool . getRandomBytes
* @ function
* @ private
* @ param { int } length number of random bytes to fetch
* @ throws { string }
* @ return { string } random bytes
* /
function getRandomBytes ( length )
{
if (
typeof window !== 'undefined' &&
typeof Uint8Array !== 'undefined' &&
String . fromCodePoint &&
(
typeof window . crypto !== 'undefined' ||
typeof window . msCrypto !== 'undefined'
)
) {
// modern browser environment
var bytes = '' ,
byteArray = new Uint8Array ( length ) ,
crypto = window . crypto || window . msCrypto ;
crypto . getRandomValues ( byteArray ) ;
for ( var i = 0 ; i < length ; ++ i ) {
bytes += String . fromCharCode ( byteArray [ i ] ) ;
}
return bytes ;
} else {
// legacy browser or unsupported environment
throw 'No supported crypto API detected, you may read pastes and comments, but can\'t create pastes or add new comments.' ;
}
2018-10-08 18:36:50 +00:00
}
2018-09-01 17:42:22 +00:00
2015-09-05 15:12:11 +00:00
/ * *
2018-10-20 20:34:36 +00:00
* derive cryptographic key from key string and password
2015-09-05 15:12:11 +00:00
*
2018-10-20 20:34:36 +00:00
* @ name CryptTool . deriveKey
2018-10-20 15:57:21 +00:00
* @ async
2017-01-14 14:29:12 +00:00
* @ function
2018-10-20 20:34:36 +00:00
* @ private
2017-01-14 14:29:12 +00:00
* @ param { string } key
* @ param { string } password
2018-10-20 21:08:13 +00:00
* @ param { object } object cryptographic message
2018-10-20 20:34:36 +00:00
* @ return { CryptoKey } derived key
2015-09-05 15:12:11 +00:00
* /
2018-10-20 21:08:13 +00:00
async function deriveKey ( key , password , object )
2015-09-05 15:12:11 +00:00
{
2018-10-20 20:34:36 +00:00
let keyArray = StrToArr ( key ) ;
2018-09-01 17:42:22 +00:00
if ( ( password || '' ) . trim ( ) . length > 0 ) {
2018-10-20 17:53:21 +00:00
keyArray += await window . crypto . subtle . digest (
{ name : 'SHA-256' } ,
StrToArr ( password )
) ;
2015-09-05 15:12:11 +00:00
}
2018-09-01 17:42:22 +00:00
// import raw key
2018-10-20 17:53:21 +00:00
const importedKey = await window . crypto . subtle . importKey (
2018-09-01 17:42:22 +00:00
'raw' , // only 'raw' is allowed
2018-10-20 17:53:21 +00:00
keyArray ,
2018-09-01 17:42:22 +00:00
{ name : 'PBKDF2' } , // we use PBKDF2 for key derivation
false , // the key may not be exported
2018-10-08 18:36:50 +00:00
[ 'deriveKey' ] // we may only use it for key derivation
2018-10-20 20:05:35 +00:00
) ;
2018-09-01 17:42:22 +00:00
// derive a stronger key for use with AES
2018-10-20 20:34:36 +00:00
return await window . crypto . subtle . deriveKey (
2018-09-01 17:42:22 +00:00
{
name : 'PBKDF2' , // we use PBKDF2 for key derivation
2018-10-20 21:08:13 +00:00
salt : StrToArr ( atob ( object . salt ) ) , // salt used in HMAC
iterations : object . iter , // amount of iterations to apply
2018-10-08 18:36:50 +00:00
hash : { name : 'SHA-256' } // can be "SHA-1", "SHA-256", "SHA-384" or "SHA-512"
2018-09-01 17:42:22 +00:00
} ,
importedKey ,
{
2018-10-20 21:08:13 +00:00
name : 'AES-' + object . mode . toUpperCase ( ) , // can be any supported AES algorithm ("AES-CTR", "AES-CBC", "AES-CMAC", "AES-GCM", "AES-CFB", "AES-KW", "ECDH", "DH" or "HMAC")
length : object . ks // can be 128, 192 or 256
2018-09-01 17:42:22 +00:00
} ,
false , // the key may not be exported
2018-10-08 18:36:50 +00:00
[ 'encrypt' ] // we may only use it for decryption
2018-10-20 20:05:35 +00:00
) ;
2018-10-20 20:34:36 +00:00
}
2018-10-20 21:08:13 +00:00
/ * *
* gets crypto settings from given object
*
* @ name CryptTool . cryptoSettings
* @ function
* @ private
* @ param { object } object cryptographic message
* @ return { object } crypto settings
* /
function cryptoSettings ( object )
{
return {
name : 'AES-' + object . mode . toUpperCase ( ) , // can be any supported AES algorithm ("AES-CTR", "AES-CBC", "AES-CMAC", "AES-GCM", "AES-CFB", "AES-KW", "ECDH", "DH" or "HMAC")
iv : StrToArr ( atob ( object . iv ) ) , // the initialization vector you used to encrypt
additionalData : StrToArr ( atob ( object . adata ) ) , // the addtional data you used during encryption (if any)
tagLength : object . ts // the length of the tag you used to encrypt (if any)
} ;
}
2018-10-20 20:34:36 +00:00
/ * *
* compress , then encrypt message with given key and password
*
* @ name CryptTool . cipher
* @ async
* @ function
* @ param { string } key
* @ param { string } password
* @ param { string } message
* @ return { string } data - JSON with encrypted data
* /
me . cipher = async function ( key , password , message )
{
// AES in Galois Counter Mode, keysize 256 bit, authentication tag 128 bit, 10000 iterations in key derivation
const iv = getRandomBytes ( 16 ) ;
let object = {
iv : btoa ( iv ) ,
v : 1 ,
iter : 10000 ,
ks : 256 ,
ts : 128 ,
mode : 'gcm' ,
adata : '' , // if used, base64 encode it with btoa()
cipher : 'aes' ,
salt : btoa ( getRandomBytes ( 8 ) )
} ;
2018-09-01 17:42:22 +00:00
// finally, encrypt message
2018-10-20 17:53:21 +00:00
const encrypted = await window . crypto . subtle . encrypt (
2018-10-20 21:08:13 +00:00
cryptoSettings ( object ) ,
await deriveKey ( key , password , object ) ,
2018-09-01 17:42:22 +00:00
StrToArr ( compress ( message ) ) // compressed plain text to encrypt
2018-10-20 20:05:35 +00:00
) ;
2018-09-01 20:22:10 +00:00
object . ct = btoa ( ArrToStr ( encrypted ) ) ;
return JSON . stringify ( object ) ;
2018-01-06 08:26:10 +00:00
} ;
2015-09-05 15:12:11 +00:00
/ * *
2017-01-14 14:29:12 +00:00
* decrypt message with key , then decompress
2015-09-05 15:12:11 +00:00
*
2017-02-14 21:21:55 +00:00
* @ name CryptTool . decipher
2018-10-20 15:57:21 +00:00
* @ async
2017-01-14 14:29:12 +00:00
* @ function
* @ param { string } key
* @ param { string } password
2017-01-14 15:13:22 +00:00
* @ param { string } data - JSON with encrypted data
2018-01-06 12:32:07 +00:00
* @ return { string } decrypted message , empty if decryption failed
2015-09-05 15:12:11 +00:00
* /
2018-09-01 17:42:22 +00:00
me . decipher = async function ( key , password , data )
2015-09-05 15:12:11 +00:00
{
2018-09-01 17:42:22 +00:00
try {
2018-10-20 20:34:36 +00:00
const object = JSON . parse ( data ) ;
return decompress (
ArrToStr (
await window . crypto . subtle . decrypt (
2018-10-20 21:08:13 +00:00
cryptoSettings ( object ) ,
await deriveKey ( key , password , object ) ,
2018-10-20 20:34:36 +00:00
StrToArr ( atob ( object . ct ) ) // cipher text to decrypt
)
)
2018-10-20 20:05:35 +00:00
) ;
2018-09-01 17:42:22 +00:00
} catch ( err ) {
return '' ;
2015-08-31 19:14:12 +00:00
}
2018-01-06 08:26:10 +00:00
} ;
2017-02-08 12:20:51 +00:00
2017-02-13 20:12:00 +00:00
/ * *
* returns a random symmetric key
*
2018-08-04 20:30:01 +00:00
* generates 256 bit long keys ( 8 Bits * 32 ) for AES with 256 bit long blocks
*
2017-02-14 21:21:55 +00:00
* @ name CryptTool . getSymmetricKey
2017-02-13 20:12:00 +00:00
* @ function
2018-08-04 20:30:01 +00:00
* @ throws { string }
* @ return { string } base64 encoded key
2017-02-13 20:12:00 +00:00
* /
2017-03-24 23:58:59 +00:00
me . getSymmetricKey = function ( )
2017-02-13 20:12:00 +00:00
{
2018-09-01 17:42:22 +00:00
return btoa ( getRandomBytes ( 32 ) ) ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-13 20:12:00 +00:00
2017-02-08 19:12:22 +00:00
return me ;
2017-02-12 17:08:08 +00:00
} ) ( ) ;
2015-09-05 15:12:11 +00:00
2017-01-14 14:29:12 +00:00
/ * *
2017-02-15 21:59:55 +00:00
* ( Model ) Data source ( aka MVC )
2017-01-14 14:29:12 +00:00
*
2017-03-13 19:24:18 +00:00
* @ name Model
2017-01-14 14:29:12 +00:00
* @ class
* /
2017-02-25 08:35:55 +00:00
var Model = ( function ( ) {
2017-02-12 20:13:04 +00:00
var me = { } ;
2017-04-11 14:34:13 +00:00
var pasteData = null ,
2017-02-17 19:46:10 +00:00
$templates ;
2017-02-12 20:13:04 +00:00
2017-02-14 21:21:55 +00:00
var id = null , symmetricKey = null ;
2015-09-27 18:34:39 +00:00
/ * *
2017-02-14 21:21:55 +00:00
* returns the expiration set in the HTML
2017-01-14 14:29:12 +00:00
*
2017-02-15 21:59:55 +00:00
* @ name Model . getExpirationDefault
2017-02-14 21:21:55 +00:00
* @ function
* @ return string
2015-09-27 18:34:39 +00:00
* /
2017-02-14 21:21:55 +00:00
me . getExpirationDefault = function ( )
{
return $ ( '#pasteExpiration' ) . val ( ) ;
2018-01-06 08:26:10 +00:00
} ;
2015-09-27 18:34:39 +00:00
2016-08-18 13:09:58 +00:00
/ * *
2017-02-14 21:21:55 +00:00
* returns the format set in the HTML
2017-01-14 14:29:12 +00:00
*
2017-02-15 21:59:55 +00:00
* @ name Model . getFormatDefault
2017-02-14 21:21:55 +00:00
* @ function
* @ return string
2016-08-18 13:09:58 +00:00
* /
2017-02-14 21:21:55 +00:00
me . getFormatDefault = function ( )
{
return $ ( '#pasteFormatter' ) . val ( ) ;
2018-01-06 08:26:10 +00:00
} ;
2016-08-18 13:09:58 +00:00
/ * *
2018-04-30 18:01:38 +00:00
* returns the paste data ( including the cipher data )
2017-01-14 14:29:12 +00:00
*
2017-04-11 14:34:13 +00:00
* @ name Model . getPasteData
2017-01-14 14:29:12 +00:00
* @ function
2017-04-11 14:34:13 +00:00
* @ param { function } callback ( optional ) Called when data is available
* @ param { function } useCache ( optional ) Whether to use the cache or
* force a data reload . Default : true
2017-02-12 20:13:04 +00:00
* @ return string
2015-09-05 15:12:11 +00:00
* /
2017-04-11 14:34:13 +00:00
me . getPasteData = function ( callback , useCache )
2015-09-05 15:12:11 +00:00
{
2017-04-11 14:34:13 +00:00
// use cache if possible/allowed
if ( useCache !== false && pasteData !== null ) {
//execute callback
if ( typeof callback === 'function' ) {
return callback ( pasteData ) ;
}
// alternatively just using inline
return pasteData ;
}
// reload data
Uploader . prepare ( ) ;
Uploader . setUrl ( Helper . baseUri ( ) + '?' + me . getPasteId ( ) ) ;
Uploader . setFailure ( function ( status , data ) {
// revert loading status…
Alert . hideLoading ( ) ;
TopNav . showViewButtons ( ) ;
// show error message
2018-07-21 06:44:04 +00:00
Alert . showError ( Uploader . parseUploadError ( status , data , 'get paste data' ) ) ;
2018-05-22 09:43:44 +00:00
} ) ;
2017-04-11 14:34:13 +00:00
Uploader . setSuccess ( function ( status , data ) {
pasteData = data ;
if ( typeof callback === 'function' ) {
return callback ( data ) ;
}
2018-05-22 09:43:44 +00:00
} ) ;
2017-04-11 14:34:13 +00:00
Uploader . run ( ) ;
2018-01-06 08:26:10 +00:00
} ;
2015-09-05 15:12:11 +00:00
2015-09-12 15:33:16 +00:00
/ * *
2017-02-14 21:21:55 +00:00
* get the pastes unique identifier from the URL ,
2017-04-11 14:34:13 +00:00
* eg . https : //example.com/path/?c05354954c49a487#dfdsdgdgdfgdf returns c05354954c49a487
2015-09-12 15:33:16 +00:00
*
2017-02-15 21:59:55 +00:00
* @ name Model . getPasteId
2017-01-14 14:29:12 +00:00
* @ function
2017-02-14 21:21:55 +00:00
* @ return { string } unique identifier
2017-02-17 19:46:10 +00:00
* @ throws { string }
2015-09-12 15:33:16 +00:00
* /
2017-02-14 21:21:55 +00:00
me . getPasteId = function ( )
2015-09-12 15:33:16 +00:00
{
2017-02-14 21:21:55 +00:00
if ( id === null ) {
2018-05-22 09:41:35 +00:00
// Attention: This also returns the delete token inside of the ID, if it is specified
2017-02-14 21:21:55 +00:00
id = window . location . search . substring ( 1 ) ;
2017-02-17 19:46:10 +00:00
if ( id === '' ) {
throw 'no paste id given' ;
}
2015-09-12 15:33:16 +00:00
}
2017-02-14 21:21:55 +00:00
return id ;
2018-05-22 09:41:35 +00:00
}
/ * *
* Returns true , when the URL has a delete token and the current call was used for deleting a paste .
*
* @ name Model . hasDeleteToken
* @ function
* @ return { bool }
* /
me . hasDeleteToken = function ( )
{
return window . location . search . indexOf ( 'deletetoken' ) !== - 1 ;
}
2015-09-12 15:33:16 +00:00
2015-09-05 15:12:11 +00:00
/ * *
2017-02-14 21:21:55 +00:00
* return the deciphering key stored in anchor part of the URL
2015-09-05 15:12:11 +00:00
*
2017-02-15 21:59:55 +00:00
* @ name Model . getPasteKey
2017-01-14 14:29:12 +00:00
* @ function
2017-02-17 19:46:10 +00:00
* @ return { string | null } key
* @ throws { string }
2015-09-05 15:12:11 +00:00
* /
2017-02-14 21:21:55 +00:00
me . getPasteKey = function ( )
2015-09-05 15:12:11 +00:00
{
2017-02-14 21:21:55 +00:00
if ( symmetricKey === null ) {
symmetricKey = window . location . hash . substring ( 1 ) ;
2017-02-17 19:46:10 +00:00
if ( symmetricKey === '' ) {
throw 'no encryption key given' ;
}
2017-02-14 21:21:55 +00:00
// Some web 2.0 services and redirectors add data AFTER the anchor
// (such as &utm_source=...). We will strip any additional data.
var ampersandPos = symmetricKey . indexOf ( '&' ) ;
if ( ampersandPos > - 1 )
2015-09-05 15:12:11 +00:00
{
2017-02-14 21:21:55 +00:00
symmetricKey = symmetricKey . substring ( 0 , ampersandPos ) ;
}
}
2015-09-18 10:33:10 +00:00
2017-02-14 21:21:55 +00:00
return symmetricKey ;
2018-01-06 08:26:10 +00:00
} ;
2015-09-05 15:12:11 +00:00
/ * *
2017-02-17 19:46:10 +00:00
* returns a jQuery copy of the HTML template
2015-09-05 15:12:11 +00:00
*
2017-02-17 19:46:10 +00:00
* @ name Model . getTemplate
2017-01-14 14:29:12 +00:00
* @ function
2017-02-17 19:46:10 +00:00
* @ param { string } name - the name of the template
* @ return { jQuery }
2015-09-05 15:12:11 +00:00
* /
2017-02-17 19:46:10 +00:00
me . getTemplate = function ( name )
2015-09-05 15:12:11 +00:00
{
2017-02-17 19:46:10 +00:00
// find template
var $element = $templates . find ( '#' + name + 'template' ) . clone ( true ) ;
// change ID to avoid collisions (one ID should really be unique)
return $element . prop ( 'id' , name ) ;
2018-01-06 08:26:10 +00:00
} ;
2015-09-05 15:12:11 +00:00
/ * *
2017-02-25 08:35:55 +00:00
* resets state , used for unit testing
2015-09-05 15:12:11 +00:00
*
2017-02-25 08:35:55 +00:00
* @ name Model . reset
2017-01-14 14:29:12 +00:00
* @ function
2015-09-05 15:12:11 +00:00
* /
2017-02-25 08:35:55 +00:00
me . reset = function ( )
2015-09-05 15:12:11 +00:00
{
2017-04-11 14:34:13 +00:00
pasteData = $templates = id = symmetricKey = null ;
2018-01-06 08:26:10 +00:00
} ;
2015-09-05 15:12:11 +00:00
/ * *
2017-02-12 20:13:04 +00:00
* init navigation manager
2015-09-05 15:12:11 +00:00
*
2017-02-12 20:13:04 +00:00
* preloads jQuery elements
*
2017-02-15 21:59:55 +00:00
* @ name Model . init
2017-01-14 14:29:12 +00:00
* @ function
2015-09-05 15:12:11 +00:00
* /
2017-02-12 20:13:04 +00:00
me . init = function ( )
2015-09-01 20:33:07 +00:00
{
2017-02-17 19:46:10 +00:00
$templates = $ ( '#templates' ) ;
2018-01-06 08:26:10 +00:00
} ;
2015-09-05 15:12:11 +00:00
2017-02-12 20:13:04 +00:00
return me ;
2017-02-25 08:35:55 +00:00
} ) ( ) ;
2017-02-06 19:16:03 +00:00
2017-02-08 19:11:04 +00:00
/ * *
2017-02-14 21:21:55 +00:00
* Helper functions for user interface
*
* everything directly UI - related , which fits nowhere else
2017-02-08 19:11:04 +00:00
*
2017-03-13 19:24:18 +00:00
* @ name UiHelper
2017-02-08 19:12:22 +00:00
* @ class
2017-02-08 19:11:04 +00:00
* /
2017-10-22 11:39:23 +00:00
var UiHelper = ( function ( ) {
2017-02-08 19:12:22 +00:00
var me = { } ;
2015-09-16 20:51:48 +00:00
/ * *
2017-02-13 10:35:04 +00:00
* handle history ( pop ) state changes
*
* currently this does only handle redirects to the home page .
2015-09-16 20:51:48 +00:00
*
2017-03-13 19:24:18 +00:00
* @ name UiHelper . historyChange
2017-02-14 21:21:55 +00:00
* @ private
2017-01-14 14:29:12 +00:00
* @ function
2017-02-13 10:35:04 +00:00
* @ param { Event } event
2015-09-16 20:51:48 +00:00
* /
2017-02-14 21:21:55 +00:00
function historyChange ( event )
2015-09-16 20:51:48 +00:00
{
2017-02-14 21:21:55 +00:00
var currentLocation = Helper . baseUri ( ) ;
2017-02-13 10:35:04 +00:00
if ( event . originalEvent . state === null && // no state object passed
2017-10-22 07:56:44 +00:00
event . target . location . href === currentLocation && // target location is home page
2017-02-13 10:35:04 +00:00
window . location . href === currentLocation // and we are not already on the home page
) {
// redirect to home page
window . location . href = currentLocation ;
2015-09-16 20:51:48 +00:00
}
2017-02-25 08:35:55 +00:00
}
2012-04-21 19:59:45 +00:00
2016-01-31 08:56:06 +00:00
/ * *
2017-02-13 10:35:04 +00:00
* reload the page
2016-01-31 08:56:06 +00:00
*
2017-02-14 21:21:55 +00:00
* This takes the user to the PrivateBin homepage .
2017-02-13 10:35:04 +00:00
*
2017-02-14 21:21:55 +00:00
* @ name UiHelper . reloadHome
2017-01-14 14:29:12 +00:00
* @ function
2016-01-31 08:56:06 +00:00
* /
2017-02-14 21:21:55 +00:00
me . reloadHome = function ( )
2016-01-31 08:56:06 +00:00
{
2017-02-14 21:21:55 +00:00
window . location . href = Helper . baseUri ( ) ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-08 12:20:51 +00:00
2015-09-05 15:12:11 +00:00
/ * *
2017-02-17 19:46:10 +00:00
* checks whether the element is currently visible in the viewport ( so
* the user can actually see it )
2017-01-14 14:29:12 +00:00
*
2017-03-13 19:24:18 +00:00
* @ see { @ link https : //stackoverflow.com/a/40658647}
2017-02-17 19:46:10 +00:00
* @ name UiHelper . isVisible
2017-01-14 14:29:12 +00:00
* @ function
2017-02-17 19:46:10 +00:00
* @ param { jQuery } $element The link hash to move to .
2015-09-05 15:12:11 +00:00
* /
2017-02-17 19:46:10 +00:00
me . isVisible = function ( $element )
2015-09-05 15:12:11 +00:00
{
2017-02-17 19:46:10 +00:00
var elementTop = $element . offset ( ) . top ;
var viewportTop = $ ( window ) . scrollTop ( ) ;
var viewportBottom = viewportTop + $ ( window ) . height ( ) ;
2018-02-21 21:51:31 +00:00
return elementTop > viewportTop && elementTop < viewportBottom ;
2018-01-06 08:26:10 +00:00
} ;
2015-09-05 15:12:11 +00:00
2017-02-05 21:09:46 +00:00
/ * *
2017-02-17 19:46:10 +00:00
* scrolls to a specific element
2017-02-05 21:09:46 +00:00
*
2017-03-13 19:24:18 +00:00
* @ see { @ link https : //stackoverflow.com/questions/4198041/jquery-smooth-scroll-to-an-anchor#answer-12714767}
2017-02-17 19:46:10 +00:00
* @ name UiHelper . scrollTo
2017-02-05 21:09:46 +00:00
* @ function
2017-02-17 19:46:10 +00:00
* @ param { jQuery } $element The link hash to move to .
* @ param { ( number | string ) } animationDuration passed to jQuery . animate , when set to 0 the animation is skipped
* @ param { string } animationEffect passed to jQuery . animate
* @ param { function } finishedCallback function to call after animation finished
2017-02-05 21:09:46 +00:00
* /
2017-02-17 19:46:10 +00:00
me . scrollTo = function ( $element , animationDuration , animationEffect , finishedCallback )
2017-02-05 21:09:46 +00:00
{
2017-02-17 19:46:10 +00:00
var $body = $ ( 'html, body' ) ,
margin = 50 ,
callbackCalled = false ;
//calculate destination place
var dest = 0 ;
// if it would scroll out of the screen at the bottom only scroll it as
// far as the screen can go
if ( $element . offset ( ) . top > $ ( document ) . height ( ) - $ ( window ) . height ( ) ) {
dest = $ ( document ) . height ( ) - $ ( window ) . height ( ) ;
} else {
dest = $element . offset ( ) . top - margin ;
}
// skip animation if duration is set to 0
if ( animationDuration === 0 ) {
window . scrollTo ( 0 , dest ) ;
} else {
// stop previous animation
$body . stop ( ) ;
// scroll to destination
$body . animate ( {
scrollTop : dest
} , animationDuration , animationEffect ) ;
}
// as we have finished we can enable scrolling again
$body . queue ( function ( next ) {
if ( ! callbackCalled ) {
// call user function if needed
if ( typeof finishedCallback !== 'undefined' ) {
finishedCallback ( ) ;
}
2017-02-05 21:09:46 +00:00
2017-02-17 19:46:10 +00:00
// prevent calling this function twice
callbackCalled = true ;
}
next ( ) ;
} ) ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-05 21:09:46 +00:00
2017-10-22 07:56:44 +00:00
/ * *
* trigger a history ( pop ) state change
*
* used to test the UiHelper . historyChange private function
*
* @ name UiHelper . mockHistoryChange
* @ function
2017-10-22 08:39:18 +00:00
* @ param { string } state ( optional ) state to mock
2017-10-22 07:56:44 +00:00
* /
2017-10-22 08:39:18 +00:00
me . mockHistoryChange = function ( state )
2017-10-22 07:56:44 +00:00
{
2017-10-22 08:39:18 +00:00
if ( typeof state === 'undefined' ) {
state = null ;
}
historyChange ( $ . Event ( 'popstate' , { originalEvent : new PopStateEvent ( 'popstate' , { state : state } ) , target : window } ) ) ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-05 21:09:46 +00:00
2017-02-06 21:39:45 +00:00
/ * *
2017-02-14 21:21:55 +00:00
* initialize
2017-02-06 21:39:45 +00:00
*
2017-02-14 21:21:55 +00:00
* @ name UiHelper . init
2017-02-06 21:39:45 +00:00
* @ function
* /
2017-02-13 10:35:04 +00:00
me . init = function ( )
2017-02-06 21:39:45 +00:00
{
2017-02-14 21:21:55 +00:00
// update link to home page
$ ( '.reloadlink' ) . prop ( 'href' , Helper . baseUri ( ) ) ;
2017-02-13 10:35:04 +00:00
2017-02-14 21:21:55 +00:00
$ ( window ) . on ( 'popstate' , historyChange ) ;
2018-01-06 08:26:10 +00:00
} ;
2015-09-12 15:33:16 +00:00
2017-02-13 10:35:04 +00:00
return me ;
2017-10-22 11:39:23 +00:00
} ) ( ) ;
2017-02-06 21:39:45 +00:00
2017-02-13 10:35:04 +00:00
/ * *
2017-02-14 21:21:55 +00:00
* Alert / error manager
2017-02-13 10:35:04 +00:00
*
2017-03-13 19:24:18 +00:00
* @ name Alert
2017-02-13 10:35:04 +00:00
* @ class
* /
2017-03-24 23:58:59 +00:00
var Alert = ( function ( ) {
2017-02-13 10:35:04 +00:00
var me = { } ;
2017-02-14 21:21:55 +00:00
var $errorMessage ,
2017-02-17 21:46:18 +00:00
$loadingIndicator ,
2017-03-12 13:16:08 +00:00
$statusMessage ,
$remainingTime ;
2017-02-15 21:59:55 +00:00
2017-10-23 19:33:07 +00:00
var currentIcon ;
2017-02-15 21:59:55 +00:00
2017-02-17 19:46:10 +00:00
var alertType = [
'loading' , // not in bootstrap, but using a good value here
'info' , // status icon
'warning' , // not used yet
'danger' // error icon
] ;
var customHandler ;
2017-02-06 21:39:45 +00:00
2015-09-05 15:12:11 +00:00
/ * *
2017-02-15 21:59:55 +00:00
* forwards a request to the i18n module and shows the element
2016-07-11 09:09:41 +00:00
*
2017-03-13 19:24:18 +00:00
* @ name Alert . handleNotification
2017-02-15 21:59:55 +00:00
* @ private
2017-01-14 14:29:12 +00:00
* @ function
2017-02-15 21:59:55 +00:00
* @ param { int } id - id of notification
* @ param { jQuery } $element - jQuery object
* @ param { string | array } args
* @ param { string | null } icon - optional , icon
2015-09-05 15:12:11 +00:00
* /
2017-02-15 21:59:55 +00:00
function handleNotification ( id , $element , args , icon )
{
2017-02-17 19:46:10 +00:00
// basic parsing/conversion of parameters
if ( typeof icon === 'undefined' ) {
icon = null ;
}
if ( typeof args === 'undefined' ) {
args = null ;
} else if ( typeof args === 'string' ) {
// convert string to array if needed
args = [ args ] ;
}
2017-03-12 13:16:08 +00:00
// pass to custom handler if defined
2017-02-17 19:46:10 +00:00
if ( typeof customHandler === 'function' ) {
var handlerResult = customHandler ( alertType [ id ] , $element , args , icon ) ;
if ( handlerResult === true ) {
2018-01-02 14:38:37 +00:00
// if it returns true, skip own handler
2017-02-17 19:46:10 +00:00
return ;
2016-07-11 09:09:41 +00:00
}
2017-02-17 19:46:10 +00:00
if ( handlerResult instanceof jQuery ) {
// continue processing with new element
$element = handlerResult ;
icon = null ; // icons not supported in this case
}
}
2017-02-15 21:59:55 +00:00
// handle icon
2017-02-17 19:46:10 +00:00
if ( icon !== null && // icon was passed
2017-02-15 21:59:55 +00:00
icon !== currentIcon [ id ] // and it differs from current icon
) {
var $glyphIcon = $element . find ( ':first' ) ;
// remove (previous) icon
$glyphIcon . removeClass ( currentIcon [ id ] ) ;
// any other thing as a string (e.g. 'null') (only) removes the icon
if ( typeof icon === 'string' ) {
// set new icon
currentIcon [ id ] = 'glyphicon-' + icon ;
$glyphIcon . addClass ( currentIcon [ id ] ) ;
2016-07-11 09:09:41 +00:00
}
2017-02-15 21:59:55 +00:00
}
2016-07-11 09:09:41 +00:00
2017-02-15 21:59:55 +00:00
// show text
2017-02-17 19:46:10 +00:00
if ( args !== null ) {
2017-03-12 16:08:12 +00:00
// add jQuery object to it as first parameter
args . unshift ( $element ) ;
// pass it to I18n
I18n . _ . apply ( this , args ) ;
2015-09-05 15:12:11 +00:00
}
2017-02-15 21:59:55 +00:00
// show notification
$element . removeClass ( 'hidden' ) ;
}
2015-09-05 15:12:11 +00:00
/ * *
2017-02-13 20:12:00 +00:00
* display a status message
2017-02-08 19:12:22 +00:00
*
2017-02-15 21:59:55 +00:00
* This automatically passes the text to I18n for translation .
2017-01-14 14:29:12 +00:00
*
2017-02-14 21:21:55 +00:00
* @ name Alert . showStatus
2017-01-14 14:29:12 +00:00
* @ function
2017-02-15 21:59:55 +00:00
* @ param { string | array } message string , use an array for % s / % d options
* @ param { string | null } icon optional , the icon to show ,
* default : leave previous icon
2015-09-05 15:12:11 +00:00
* /
2018-01-06 12:32:07 +00:00
me . showStatus = function ( message , icon )
2015-09-05 15:12:11 +00:00
{
2017-10-30 06:04:59 +00:00
console . info ( 'status shown: ' , message ) ;
2017-02-15 21:59:55 +00:00
handleNotification ( 1 , $statusMessage , message , icon ) ;
2018-01-06 08:26:10 +00:00
} ;
2015-09-05 15:12:11 +00:00
2016-08-11 09:31:34 +00:00
/ * *
2017-02-15 21:59:55 +00:00
* display an error message
2017-01-14 14:29:12 +00:00
*
2017-02-15 21:59:55 +00:00
* This automatically passes the text to I18n for translation .
*
* @ name Alert . showError
2017-01-14 14:29:12 +00:00
* @ function
2017-02-15 21:59:55 +00:00
* @ param { string | array } message string , use an array for % s / % d options
* @ param { string | null } icon optional , the icon to show , default :
* leave previous icon
2016-08-11 09:31:34 +00:00
* /
2018-01-06 12:32:07 +00:00
me . showError = function ( message , icon )
2016-08-11 09:31:34 +00:00
{
2017-02-15 21:59:55 +00:00
console . error ( 'error message shown: ' , message ) ;
handleNotification ( 3 , $errorMessage , message , icon ) ;
2018-01-06 08:26:10 +00:00
} ;
2016-08-11 09:31:34 +00:00
2016-08-18 13:09:58 +00:00
/ * *
2017-03-12 13:16:08 +00:00
* display remaining message
*
* This automatically passes the text to I18n for translation .
2016-08-18 13:09:58 +00:00
*
2017-03-12 13:16:08 +00:00
* @ name Alert . showRemaining
2017-01-14 14:29:12 +00:00
* @ function
2017-03-12 13:16:08 +00:00
* @ param { string | array } message string , use an array for % s / % d options
2016-08-18 13:09:58 +00:00
* /
2017-03-12 13:16:08 +00:00
me . showRemaining = function ( message )
2016-08-18 13:09:58 +00:00
{
2017-10-30 06:04:59 +00:00
console . info ( 'remaining message shown: ' , message ) ;
2017-03-12 13:16:08 +00:00
handleNotification ( 1 , $remainingTime , message ) ;
2018-01-06 08:26:10 +00:00
} ;
2016-08-18 13:09:58 +00:00
2015-09-05 15:12:11 +00:00
/ * *
2017-02-15 21:59:55 +00:00
* shows a loading message , optionally with a percentage
2015-09-05 15:12:11 +00:00
*
2017-02-15 21:59:55 +00:00
* This automatically passes all texts to the i10s module .
2017-02-06 19:39:52 +00:00
*
2017-02-15 21:59:55 +00:00
* @ name Alert . showLoading
2017-01-14 14:29:12 +00:00
* @ function
2017-02-15 21:59:55 +00:00
* @ param { string | array | null } message optional , use an array for % s / % d options , default : 'Loading…'
* @ param { string | null } icon optional , the icon to show , default : leave previous icon
2015-09-05 15:12:11 +00:00
* /
2018-01-06 12:32:07 +00:00
me . showLoading = function ( message , icon )
2015-09-05 15:12:11 +00:00
{
2017-02-15 21:59:55 +00:00
if ( typeof message !== 'undefined' && message !== null ) {
2017-10-30 06:04:59 +00:00
console . info ( 'status changed: ' , message ) ;
2017-02-15 21:59:55 +00:00
}
// default message text
if ( typeof message === 'undefined' ) {
message = 'Loading…' ;
}
handleNotification ( 0 , $loadingIndicator , message , icon ) ;
2017-02-17 19:46:10 +00:00
// show loading status (cursor)
$ ( 'body' ) . addClass ( 'loading' ) ;
2018-01-06 08:26:10 +00:00
} ;
2015-09-05 15:12:11 +00:00
/ * *
2017-02-15 21:59:55 +00:00
* hides the loading message
2015-09-05 15:12:11 +00:00
*
2017-02-15 21:59:55 +00:00
* @ name Alert . hideLoading
2017-01-14 14:29:12 +00:00
* @ function
2015-09-05 15:12:11 +00:00
* /
2017-02-15 21:59:55 +00:00
me . hideLoading = function ( )
2015-09-05 15:12:11 +00:00
{
2017-02-15 21:59:55 +00:00
$loadingIndicator . addClass ( 'hidden' ) ;
2017-02-17 19:46:10 +00:00
// hide loading cursor
$ ( 'body' ) . removeClass ( 'loading' ) ;
2018-01-06 08:26:10 +00:00
} ;
2015-09-05 15:12:11 +00:00
/ * *
2017-02-15 21:59:55 +00:00
* hides any status / error messages
*
* This does not include the loading message .
2015-09-05 15:12:11 +00:00
*
2017-02-15 21:59:55 +00:00
* @ name Alert . hideMessages
2017-01-14 14:29:12 +00:00
* @ function
2015-09-05 15:12:11 +00:00
* /
2017-02-15 21:59:55 +00:00
me . hideMessages = function ( )
2015-09-05 15:12:11 +00:00
{
2017-02-17 19:46:10 +00:00
// also possible: $('.statusmessage').addClass('hidden');
2017-02-15 21:59:55 +00:00
$statusMessage . addClass ( 'hidden' ) ;
$errorMessage . addClass ( 'hidden' ) ;
2018-01-06 08:26:10 +00:00
} ;
2012-04-21 19:59:45 +00:00
2016-08-09 12:46:32 +00:00
/ * *
2017-02-17 19:46:10 +00:00
* set a custom handler , which gets all notifications .
*
* This handler gets the following arguments :
* alertType ( see array ) , $element , args , icon
* If it returns true , the own processing will be stopped so the message
* will not be displayed . Otherwise it will continue .
* As an aditional feature it can return q jQuery element , which will
* then be used to add the message there . Icons are not supported in
* that case and will be ignored .
* Pass 'null' to reset / delete the custom handler .
* Note that there is no notification when a message is supposed to get
* hidden .
*
* @ name Alert . setCustomHandler
2017-01-14 14:29:12 +00:00
* @ function
2017-02-17 19:46:10 +00:00
* @ param { function | null } newHandler
2016-08-09 12:46:32 +00:00
* /
2017-02-17 19:46:10 +00:00
me . setCustomHandler = function ( newHandler )
2016-08-09 12:46:32 +00:00
{
2017-02-17 19:46:10 +00:00
customHandler = newHandler ;
2018-01-06 08:26:10 +00:00
} ;
2016-08-09 12:46:32 +00:00
/ * *
2017-02-14 21:21:55 +00:00
* init status manager
2017-02-13 10:35:04 +00:00
*
2017-02-14 21:21:55 +00:00
* preloads jQuery elements
2016-08-09 12:46:32 +00:00
*
2017-02-14 21:21:55 +00:00
* @ name Alert . init
2017-01-14 14:29:12 +00:00
* @ function
2016-08-09 12:46:32 +00:00
* /
2017-02-14 21:21:55 +00:00
me . init = function ( )
2016-08-09 12:46:32 +00:00
{
2017-02-17 19:46:10 +00:00
// hide "no javascript" error message
2017-02-14 21:21:55 +00:00
$ ( '#noscript' ) . hide ( ) ;
2016-08-11 09:40:37 +00:00
2017-02-17 19:46:10 +00:00
// not a reset, but first set of the elements
2017-02-14 21:21:55 +00:00
$errorMessage = $ ( '#errormessage' ) ;
2017-02-15 21:59:55 +00:00
$loadingIndicator = $ ( '#loadingindicator' ) ;
2017-02-17 21:46:18 +00:00
$statusMessage = $ ( '#status' ) ;
2017-03-12 13:16:08 +00:00
$remainingTime = $ ( '#remainingtime' ) ;
2017-10-23 19:33:07 +00:00
currentIcon = [
'glyphicon-time' , // loading icon
'glyphicon-info-sign' , // status icon
'' , // reserved for warning, not used yet
'glyphicon-alert' // error icon
] ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-08 19:12:22 +00:00
2017-02-14 21:21:55 +00:00
return me ;
2017-03-24 23:58:59 +00:00
} ) ( ) ;
2017-02-14 21:21:55 +00:00
/ * *
* handles paste status / result
*
2017-03-13 19:24:18 +00:00
* @ name PasteStatus
2017-02-14 21:21:55 +00:00
* @ class
* /
2017-11-13 20:57:49 +00:00
var PasteStatus = ( function ( ) {
2017-02-14 21:21:55 +00:00
var me = { } ;
var $pasteSuccess ,
$pasteUrl ,
2017-02-17 21:46:18 +00:00
$remainingTime ,
$shortenButton ;
2016-08-09 12:46:32 +00:00
/ * *
2017-02-13 20:12:00 +00:00
* forward to URL shortener
2016-08-09 12:46:32 +00:00
*
2017-03-13 19:24:18 +00:00
* @ name PasteStatus . sendToShortener
2017-02-13 20:12:00 +00:00
* @ private
2017-01-14 14:29:12 +00:00
* @ function
2016-08-09 12:46:32 +00:00
* /
2018-01-06 12:32:07 +00:00
function sendToShortener ( )
2016-08-09 12:46:32 +00:00
{
2018-02-21 21:51:31 +00:00
window . location . href = $shortenButton . data ( 'shortener' ) +
encodeURIComponent ( $pasteUrl . attr ( 'href' ) ) ;
2017-02-13 20:12:00 +00:00
}
/ * *
2017-02-14 21:21:55 +00: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 ( ) ) .
2017-02-13 20:12:00 +00:00
*
2017-03-13 19:24:18 +00:00
* @ name PasteStatus . pasteLinkClick
2017-02-14 21:21:55 +00:00
* @ function
* /
2018-01-06 12:32:07 +00:00
function pasteLinkClick ( )
2017-02-14 21:21:55 +00:00
{
// check if location is (already) shown in URL bar
if ( window . location . href === $pasteUrl . attr ( 'href' ) ) {
// if so we need to load link by reloading the current site
window . location . reload ( true ) ;
}
}
/ * *
* creates a notification after a successfull paste upload
2017-02-13 20:12:00 +00:00
*
2017-02-14 21:21:55 +00:00
* @ name PasteStatus . createPasteNotification
2017-02-13 20:12:00 +00:00
* @ function
* @ param { string } url
* @ param { string } deleteUrl
* /
me . createPasteNotification = function ( url , deleteUrl )
2017-02-12 17:08:08 +00:00
{
2017-02-25 08:35:55 +00:00
$ ( '#pastelink' ) . html (
2017-02-14 21:21:55 +00:00
I18n . _ (
2017-02-13 20:12:00 +00:00
'Your paste is <a id="pasteurl" href="%s">%s</a> <span id="copyhint">(Hit [Ctrl]+[c] to copy)</span>' ,
url , url
)
) ;
// save newly created element
$pasteUrl = $ ( '#pasteurl' ) ;
// and add click event
$pasteUrl . click ( pasteLinkClick ) ;
// shorten button
2017-02-14 21:21:55 +00:00
$ ( '#deletelink' ) . html ( '<a href="' + deleteUrl + '">' + I18n . _ ( 'Delete data' ) + '</a>' ) ;
2017-02-13 10:35:04 +00:00
2017-02-13 20:12:00 +00:00
// show result
$pasteSuccess . removeClass ( 'hidden' ) ;
// we pre-select the link so that the user only has to [Ctrl]+[c] the link
2017-02-14 21:21:55 +00:00
Helper . selectText ( $pasteUrl [ 0 ] ) ;
2018-01-06 08:26:10 +00:00
} ;
2012-04-21 19:59:45 +00:00
2017-02-13 20:12:00 +00:00
/ * *
2017-02-14 21:21:55 +00:00
* shows the remaining time
2017-02-13 20:12:00 +00:00
*
2017-02-15 21:59:55 +00:00
* @ name PasteStatus . showRemainingTime
2017-02-13 20:12:00 +00:00
* @ function
2017-02-14 21:21:55 +00:00
* @ param { object } pasteMetaData
2017-02-13 20:12:00 +00:00
* /
2017-02-14 21:21:55 +00:00
me . showRemainingTime = function ( pasteMetaData )
2017-02-13 20:12:00 +00:00
{
2017-02-14 21:21:55 +00:00
if ( pasteMetaData . burnafterreading ) {
// display paste "for your eyes only" if it is deleted
2017-04-13 08:46:09 +00:00
// the paste has been deleted when the JSON with the ciphertext
2017-04-11 14:34:13 +00:00
// has been downloaded
2017-02-14 21:21:55 +00:00
2018-10-08 18:36:50 +00:00
Alert . showRemaining ( 'FOR YOUR EYES ONLY. Don\'t close this window, this message can\'t be displayed again.' ) ;
2017-02-14 21:21:55 +00:00
$remainingTime . addClass ( 'foryoureyesonly' ) ;
// discourage cloning (it cannot really be prevented)
TopNav . hideCloneButton ( ) ;
} else if ( pasteMetaData . expire _date ) {
// display paste expiration
var expiration = Helper . secondsToHuman ( pasteMetaData . remaining _time ) ,
expirationLabel = [
'This document will expire in %d ' + expiration [ 1 ] + '.' ,
'This document will expire in %d ' + expiration [ 1 ] + 's.'
] ;
2017-03-12 16:08:12 +00:00
Alert . showRemaining ( [ expirationLabel , expiration [ 0 ] ] ) ;
2017-11-14 05:52:12 +00:00
$remainingTime . removeClass ( 'foryoureyesonly' ) ;
2017-02-14 21:21:55 +00:00
} else {
// never expires
return ;
2017-02-13 20:12:00 +00:00
}
2017-02-14 21:21:55 +00:00
// in the end, display notification
$remainingTime . removeClass ( 'hidden' ) ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-13 20:12:00 +00:00
2017-02-15 21:59:55 +00:00
/ * *
* hides the remaining time and successful upload notification
*
2018-03-01 05:43:30 +00:00
* @ name PasteStatus . hideMessages
2017-02-15 21:59:55 +00:00
* @ function
* /
me . hideMessages = function ( )
{
$remainingTime . addClass ( 'hidden' ) ;
$pasteSuccess . addClass ( 'hidden' ) ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-15 21:59:55 +00:00
2017-02-08 19:12:22 +00:00
/ * *
2017-02-13 10:35:04 +00:00
* init status manager
2017-02-08 19:12:22 +00:00
*
2017-02-13 10:35:04 +00:00
* preloads jQuery elements
*
2017-03-13 19:24:18 +00:00
* @ name PasteStatus . init
2017-02-08 19:12:22 +00:00
* @ function
* /
2017-02-12 17:08:08 +00:00
me . init = function ( )
2017-02-08 19:12:22 +00:00
{
2017-11-16 07:57:08 +00:00
$pasteSuccess = $ ( '#pastesuccess' ) ;
2017-02-14 21:21:55 +00:00
// $pasteUrl is saved in me.createPasteNotification() after creation
$remainingTime = $ ( '#remainingtime' ) ;
2017-02-17 21:46:18 +00:00
$shortenButton = $ ( '#shortenbutton' ) ;
2017-02-08 19:12:22 +00:00
2017-02-13 20:12:00 +00:00
// bind elements
$shortenButton . click ( sendToShortener ) ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-08 12:20:51 +00:00
2017-02-12 17:08:08 +00:00
return me ;
2017-11-13 20:57:49 +00:00
} ) ( ) ;
2017-02-12 17:08:08 +00:00
/ * *
2017-02-14 21:21:55 +00:00
* password prompt
2017-02-12 17:08:08 +00:00
*
2017-03-13 19:24:18 +00:00
* @ name Prompt
2017-02-12 17:08:08 +00:00
* @ class
* /
2017-03-24 23:58:59 +00:00
var Prompt = ( function ( ) {
2017-02-12 17:08:08 +00:00
var me = { } ;
2017-02-17 21:46:18 +00:00
var $passwordDecrypt ,
2017-02-13 10:35:04 +00:00
$passwordForm ,
2017-02-17 21:46:18 +00:00
$passwordModal ;
2017-02-13 10:35:04 +00:00
2017-03-12 15:06:17 +00:00
var password = '' ;
2017-02-14 21:21:55 +00:00
2017-03-13 19:24:18 +00:00
/ * *
* submit a password in the modal dialog
*
* @ name Prompt . submitPasswordModal
* @ private
* @ function
* @ param { Event } event
* /
function submitPasswordModal ( event )
{
event . preventDefault ( ) ;
// get input
password = $passwordDecrypt . val ( ) ;
// hide modal
$passwordModal . modal ( 'hide' ) ;
PasteDecrypter . run ( ) ;
}
2017-02-08 19:12:22 +00:00
/ * *
2017-02-13 10:35:04 +00:00
* ask the user for the password and set it
2017-02-08 19:12:22 +00:00
*
2017-03-13 19:24:18 +00:00
* @ name Prompt . requestPassword
2017-02-08 19:12:22 +00:00
* @ function
* /
2017-02-13 10:35:04 +00:00
me . requestPassword = function ( )
2017-02-08 19:12:22 +00:00
{
2017-02-17 19:46:10 +00:00
// show new bootstrap method (if available)
if ( $passwordModal . length !== 0 ) {
$passwordModal . modal ( {
backdrop : 'static' ,
keyboard : false
} ) ;
return ;
}
2017-02-15 21:59:55 +00:00
2017-02-17 19:46:10 +00:00
// fallback to old method for page template
2018-07-21 06:44:04 +00:00
password = prompt ( I18n . _ ( 'Please enter the password for this paste:' ) , '' ) ;
if ( password === null ) {
2017-02-17 19:46:10 +00:00
throw 'password prompt canceled' ;
}
if ( password . length === 0 ) {
2017-11-20 07:49:25 +00:00
// recurse…
2017-02-17 19:46:10 +00:00
return me . requestPassword ( ) ;
}
2018-07-21 06:44:04 +00:00
PasteDecrypter . run ( ) ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-14 21:21:55 +00:00
/ * *
2017-04-11 14:34:13 +00:00
* get the cached password
2017-02-14 21:21:55 +00:00
*
2017-02-15 21:59:55 +00:00
* If you do not get a password with this function
* ( returns an empty string ) , use requestPassword .
2017-02-14 21:21:55 +00:00
*
* @ name Prompt . getPassword
* @ function
2017-02-15 21:59:55 +00:00
* @ return { string }
2017-02-14 21:21:55 +00:00
* /
me . getPassword = function ( )
{
return password ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-05 21:09:46 +00:00
2017-04-11 20:21:30 +00:00
/ * *
* resets the password to an empty string
*
* @ name Prompt . reset
* @ function
* /
me . reset = function ( )
{
// reset internal
password = '' ;
// and also reset UI
$passwordDecrypt . val ( '' ) ;
}
2017-02-08 19:12:22 +00:00
/ * *
2017-02-13 10:35:04 +00:00
* init status manager
2017-02-08 19:12:22 +00:00
*
2017-02-13 10:35:04 +00:00
* preloads jQuery elements
*
2017-03-13 19:24:18 +00:00
* @ name Prompt . init
2017-02-08 19:12:22 +00:00
* @ function
* /
2017-02-13 10:35:04 +00:00
me . init = function ( )
2017-02-08 19:12:22 +00:00
{
2017-02-13 10:35:04 +00:00
$passwordDecrypt = $ ( '#passworddecrypt' ) ;
2017-02-17 21:46:18 +00:00
$passwordForm = $ ( '#passwordform' ) ;
$passwordModal = $ ( '#passwordmodal' ) ;
2017-02-08 19:12:22 +00:00
2017-02-13 10:35:04 +00:00
// bind events
2017-02-08 19:12:22 +00:00
2017-02-13 10:35:04 +00:00
// focus password input when it is shown
2017-02-17 19:46:10 +00:00
$passwordModal . on ( 'shown.bs.Model' , function ( ) {
2017-02-13 10:35:04 +00:00
$passwordDecrypt . focus ( ) ;
} ) ;
2017-02-15 21:59:55 +00:00
// handle Model password submission
2017-02-17 19:46:10 +00:00
$passwordForm . submit ( submitPasswordModal ) ;
2018-01-06 08:26:10 +00:00
} ;
2015-09-05 15:12:11 +00:00
2017-02-12 17:08:08 +00:00
return me ;
2017-03-24 23:58:59 +00:00
} ) ( ) ;
2017-02-12 17:08:08 +00:00
/ * *
2017-02-14 21:21:55 +00:00
* Manage paste / message input , and preview tab
*
* Note that the actual preview is handled by PasteViewer .
2017-02-12 17:08:08 +00:00
*
2017-03-13 19:24:18 +00:00
* @ name Editor
2017-02-12 17:08:08 +00:00
* @ class
* /
2017-03-24 23:58:59 +00:00
var Editor = ( function ( ) {
2017-02-12 17:08:08 +00:00
var me = { } ;
2017-02-17 21:46:18 +00:00
var $editorTabs ,
2017-02-13 10:35:04 +00:00
$messageEdit ,
$messagePreview ,
2017-02-17 21:46:18 +00:00
$message ;
2017-02-13 10:35:04 +00:00
var isPreview = false ;
2016-08-09 12:46:32 +00:00
2015-10-15 20:06:01 +00:00
/ * *
2017-01-14 14:29:12 +00:00
* support input of tab character
2015-10-15 20:06:01 +00:00
*
2017-02-14 21:21:55 +00:00
* @ name Editor . supportTabs
2017-01-14 14:29:12 +00:00
* @ function
* @ param { Event } event
2017-02-15 21:59:55 +00:00
* @ this $message ( but not used , so it is jQuery - free , possibly faster )
2015-10-15 20:06:01 +00:00
* /
2017-02-13 10:35:04 +00:00
function supportTabs ( event )
2015-10-15 20:06:01 +00:00
{
var keyCode = event . keyCode || event . which ;
// tab was pressed
2017-02-15 21:59:55 +00:00
if ( keyCode === 9 ) {
2015-10-15 20:06:01 +00:00
// 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 ;
2017-02-15 21:59:55 +00:00
// prevent the textarea to lose focus
event . preventDefault ( ) ;
2015-10-15 20:06:01 +00:00
}
2017-02-13 10:35:04 +00:00
}
2015-10-15 20:06:01 +00:00
2016-07-11 09:09:41 +00:00
/ * *
2017-02-14 21:21:55 +00:00
* view the Editor tab
2016-07-11 09:09:41 +00:00
*
2017-02-14 21:21:55 +00:00
* @ name Editor . viewEditor
2017-01-14 14:29:12 +00:00
* @ function
2017-02-13 10:35:04 +00:00
* @ param { Event } event - optional
2016-07-11 09:09:41 +00:00
* /
2017-02-13 10:35:04 +00:00
function viewEditor ( event )
2016-07-11 09:09:41 +00:00
{
2017-02-13 10:35:04 +00:00
// toggle buttons
$messageEdit . addClass ( 'active' ) ;
$messagePreview . removeClass ( 'active' ) ;
2017-02-14 21:21:55 +00:00
PasteViewer . hide ( ) ;
2017-02-13 10:35:04 +00:00
// reshow input
$message . removeClass ( 'hidden' ) ;
me . focusInput ( ) ;
// finish
isPreview = false ;
2017-02-14 21:21:55 +00:00
// prevent jumping of page to top
if ( typeof event !== 'undefined' ) {
event . preventDefault ( ) ;
}
2017-02-13 10:35:04 +00:00
}
2016-07-11 09:09:41 +00:00
/ * *
2017-01-14 14:29:12 +00:00
* view the preview tab
2016-07-11 09:09:41 +00:00
*
2017-02-14 21:21:55 +00:00
* @ name Editor . viewPreview
2017-01-14 14:29:12 +00:00
* @ function
* @ param { Event } event
2016-07-11 09:09:41 +00:00
* /
2017-02-13 10:35:04 +00:00
function viewPreview ( event )
2016-07-11 09:09:41 +00:00
{
2017-02-13 10:35:04 +00:00
// toggle buttons
$messageEdit . removeClass ( 'active' ) ;
$messagePreview . addClass ( 'active' ) ;
// hide input as now preview is shown
$message . addClass ( 'hidden' ) ;
// show preview
2017-02-14 21:21:55 +00:00
PasteViewer . setText ( $message . val ( ) ) ;
2017-05-15 20:05:52 +00:00
if ( AttachmentViewer . hasAttachmentData ( ) ) {
var attachmentData = AttachmentViewer . getAttachmentData ( ) || AttachmentViewer . getAttachmentLink ( ) . attr ( 'href' ) ;
AttachmentViewer . handleAttachmentPreview ( AttachmentViewer . getAttachmentPreview ( ) , attachmentData ) ;
2017-05-13 19:27:41 +00:00
}
2017-02-14 21:21:55 +00:00
PasteViewer . run ( ) ;
2017-02-13 10:35:04 +00:00
// finish
isPreview = true ;
2017-02-14 21:21:55 +00:00
// prevent jumping of page to top
if ( typeof event !== 'undefined' ) {
event . preventDefault ( ) ;
}
2017-02-13 10:35:04 +00:00
}
2016-07-11 09:09:41 +00:00
2017-02-05 13:47:03 +00:00
/ * *
2017-02-13 10:35:04 +00:00
* get the state of the preview
2017-02-05 13:47:03 +00:00
*
2017-02-14 21:21:55 +00:00
* @ name Editor . isPreview
2017-02-05 13:47:03 +00:00
* @ function
* /
2017-02-13 10:35:04 +00:00
me . isPreview = function ( )
2017-02-05 13:47:03 +00:00
{
2017-02-13 10:35:04 +00:00
return isPreview ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-05 13:47:03 +00:00
2017-02-05 20:22:09 +00:00
/ * *
2017-02-14 21:21:55 +00:00
* reset the Editor view
2017-02-05 20:22:09 +00:00
*
2017-02-14 21:21:55 +00:00
* @ name Editor . resetInput
2017-02-05 20:22:09 +00:00
* @ function
* /
2017-02-13 10:35:04 +00:00
me . resetInput = function ( )
2017-02-05 20:22:09 +00:00
{
2017-02-13 10:35:04 +00:00
// go back to input
if ( isPreview ) {
viewEditor ( ) ;
2017-02-05 20:22:09 +00:00
}
2017-02-13 10:35:04 +00:00
// clear content
$message . val ( '' ) ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-05 20:22:09 +00:00
2015-09-05 15:12:11 +00:00
/ * *
2017-02-14 21:21:55 +00:00
* shows the Editor
2017-01-14 14:29:12 +00:00
*
2017-02-14 21:21:55 +00:00
* @ name Editor . show
2017-01-14 14:29:12 +00:00
* @ function
2015-09-05 15:12:11 +00:00
* /
2017-02-13 10:35:04 +00:00
me . show = function ( )
2015-09-05 15:12:11 +00:00
{
2017-02-13 10:35:04 +00:00
$message . removeClass ( 'hidden' ) ;
$editorTabs . removeClass ( 'hidden' ) ;
2018-01-06 08:26:10 +00:00
} ;
2015-09-05 15:12:11 +00:00
2015-09-16 20:51:48 +00:00
/ * *
2017-02-14 21:21:55 +00:00
* hides the Editor
2017-01-14 14:29:12 +00:00
*
2017-02-14 21:21:55 +00:00
* @ name Editor . reset
2017-01-14 14:29:12 +00:00
* @ function
2015-09-16 20:51:48 +00:00
* /
2017-02-13 10:35:04 +00:00
me . hide = function ( )
2015-09-16 20:51:48 +00:00
{
2017-02-13 10:35:04 +00:00
$message . addClass ( 'hidden' ) ;
$editorTabs . addClass ( 'hidden' ) ;
2018-01-06 08:26:10 +00:00
} ;
2015-09-16 20:51:48 +00:00
2016-11-13 17:12:10 +00:00
/ * *
2017-02-13 10:35:04 +00:00
* focuses the message input
2017-01-14 14:29:12 +00:00
*
2017-02-14 21:21:55 +00:00
* @ name Editor . focusInput
2017-01-14 14:29:12 +00:00
* @ function
2016-11-13 17:12:10 +00:00
* /
2017-02-13 10:35:04 +00:00
me . focusInput = function ( )
2016-11-13 17:12:10 +00:00
{
2017-02-13 10:35:04 +00:00
$message . focus ( ) ;
2018-01-06 08:26:10 +00:00
} ;
2016-11-13 17:12:10 +00:00
/ * *
2017-02-15 21:59:55 +00:00
* sets a new text
2016-11-13 17:12:10 +00:00
*
2017-02-15 21:59:55 +00:00
* @ name Editor . setText
2017-01-14 14:29:12 +00:00
* @ function
2017-02-15 21:59:55 +00:00
* @ param { string } newText
2016-11-13 17:12:10 +00:00
* /
2017-02-15 21:59:55 +00:00
me . setText = function ( newText )
2016-11-13 17:12:10 +00:00
{
2017-02-15 21:59:55 +00:00
$message . val ( newText ) ;
2018-01-06 08:26:10 +00:00
} ;
2016-11-13 17:12:10 +00:00
2015-09-05 15:12:11 +00:00
/ * *
2017-02-13 10:35:04 +00:00
* returns the current text
2015-09-05 15:12:11 +00:00
*
2017-02-14 21:21:55 +00:00
* @ name Editor . getText
2017-01-14 14:29:12 +00:00
* @ function
2017-02-13 10:35:04 +00:00
* @ return { string }
2015-09-05 15:12:11 +00:00
* /
2017-02-13 10:35:04 +00:00
me . getText = function ( )
2015-09-05 15:12:11 +00:00
{
2018-01-06 09:57:54 +00:00
return $message . val ( ) ;
2018-01-06 08:26:10 +00:00
} ;
2015-09-05 15:12:11 +00:00
/ * *
2017-02-12 17:08:08 +00:00
* init status manager
2015-09-05 15:12:11 +00:00
*
2017-02-12 17:08:08 +00:00
* preloads jQuery elements
*
2017-02-14 21:21:55 +00:00
* @ name Editor . init
2017-01-14 14:29:12 +00:00
* @ function
2015-09-05 15:12:11 +00:00
* /
2017-02-12 17:08:08 +00:00
me . init = function ( )
2015-09-05 15:12:11 +00:00
{
2017-02-13 10:35:04 +00:00
$editorTabs = $ ( '#editorTabs' ) ;
2017-02-17 21:46:18 +00:00
$message = $ ( '#message' ) ;
2017-02-12 17:08:08 +00:00
2017-02-13 10:35:04 +00:00
// bind events
$message . keydown ( supportTabs ) ;
2017-02-12 20:13:04 +00:00
2017-02-13 10:35:04 +00:00
// bind click events to tab switchers (a), but save parent of them
// (li)
$messageEdit = $ ( '#messageedit' ) . click ( viewEditor ) . parent ( ) ;
$messagePreview = $ ( '#messagepreview' ) . click ( viewPreview ) . parent ( ) ;
2018-01-06 08:26:10 +00:00
} ;
2016-07-11 09:09:41 +00:00
2017-02-12 17:08:08 +00:00
return me ;
2017-03-24 23:58:59 +00:00
} ) ( ) ;
2017-02-12 17:08:08 +00:00
/ * *
2017-02-14 21:21:55 +00:00
* ( view ) Parse and show paste .
2017-02-12 17:08:08 +00:00
*
2017-03-13 19:24:18 +00:00
* @ name PasteViewer
2017-02-12 17:08:08 +00:00
* @ class
* /
2017-03-24 23:58:59 +00:00
var PasteViewer = ( function ( ) {
2017-02-12 17:08:08 +00:00
var me = { } ;
2017-02-17 21:46:18 +00:00
var $placeholder ,
2017-02-13 10:35:04 +00:00
$prettyMessage ,
2017-02-17 21:46:18 +00:00
$prettyPrint ,
$plainText ;
2017-02-13 10:35:04 +00:00
var text ,
format = 'plaintext' ,
isDisplayed = false ,
isChanged = true ; // by default true as nothing was parsed yet
2012-04-23 14:30:02 +00:00
2015-09-05 15:12:11 +00:00
/ * *
2017-02-13 10:35:04 +00:00
* apply the set format on paste and displays it
2017-01-14 14:29:12 +00:00
*
2017-03-13 19:24:18 +00:00
* @ name PasteViewer . parsePaste
2017-02-13 10:35:04 +00:00
* @ private
2017-01-14 14:29:12 +00:00
* @ function
2015-09-05 15:12:11 +00:00
* /
2017-02-13 10:35:04 +00:00
function parsePaste ( )
2015-09-05 15:12:11 +00:00
{
2017-02-13 10:35:04 +00:00
// skip parsing if no text is given
if ( text === '' ) {
return ;
2017-02-12 17:08:08 +00:00
}
2016-08-09 12:46:32 +00:00
2018-01-03 20:18:33 +00:00
// escape HTML entities, link URLs, sanitize
var escapedLinkedText = Helper . urls2links (
$ ( '<div />' ) . text ( text ) . html ( )
) ,
sanitizedLinkedText = DOMPurify . sanitize ( escapedLinkedText ) ;
2017-11-22 21:27:38 +00:00
$plainText . html ( sanitizedLinkedText ) ;
$prettyPrint . html ( sanitizedLinkedText ) ;
2016-08-09 12:46:32 +00:00
2017-02-13 10:35:04 +00:00
switch ( format ) {
case 'markdown' :
var converter = new showdown . Converter ( {
strikethrough : true ,
tables : true ,
2018-07-01 18:22:21 +00:00
tablesHeaderId : true ,
simplifiedAutoLink : true ,
excludeTrailingPunctuationFromURLs : true
2017-02-13 10:35:04 +00:00
} ) ;
2017-11-21 20:22:51 +00:00
// let showdown convert the HTML and sanitize HTML *afterwards*!
2017-02-14 21:21:55 +00:00
$plainText . html (
2018-01-01 09:25:07 +00:00
DOMPurify . sanitize ( converter . makeHtml ( text ) )
2017-02-13 10:35:04 +00:00
) ;
// add table classes from bootstrap css
2017-02-14 21:21:55 +00:00
$plainText . find ( 'table' ) . addClass ( 'table-condensed table-bordered' ) ;
2017-02-13 10:35:04 +00:00
break ;
case 'syntaxhighlighting' :
2017-11-22 21:27:38 +00:00
// yes, this is really needed to initialize the environment
2017-02-13 10:35:04 +00:00
if ( typeof prettyPrint === 'function' )
{
prettyPrint ( ) ;
}
2016-11-13 17:12:10 +00:00
2017-02-13 10:35:04 +00:00
$prettyPrint . html (
2017-11-22 21:27:38 +00:00
DOMPurify . sanitize (
2018-01-03 20:18:33 +00:00
prettyPrintOne ( escapedLinkedText , null , true )
2017-02-13 10:35:04 +00:00
)
) ;
// fall through, as the rest is the same
default : // = 'plaintext'
$prettyPrint . css ( 'white-space' , 'pre-wrap' ) ;
$prettyPrint . css ( 'word-break' , 'normal' ) ;
$prettyPrint . removeClass ( 'prettyprint' ) ;
}
}
2015-09-05 15:12:11 +00:00
/ * *
2017-02-13 10:35:04 +00:00
* displays the paste
2017-01-14 14:29:12 +00:00
*
2017-03-13 19:24:18 +00:00
* @ name PasteViewer . showPaste
2017-02-13 10:35:04 +00:00
* @ private
2017-01-14 14:29:12 +00:00
* @ function
2015-09-05 15:12:11 +00:00
* /
2017-02-13 10:35:04 +00:00
function showPaste ( )
2015-09-05 15:12:11 +00:00
{
2017-02-13 10:35:04 +00:00
// instead of "nothing" better display a placeholder
if ( text === '' ) {
2018-01-06 08:26:10 +00:00
$placeholder . removeClass ( 'hidden' ) ;
2015-09-05 15:12:11 +00:00
return ;
}
2017-02-13 10:35:04 +00:00
// otherwise hide the placeholder
2018-01-06 08:26:10 +00:00
$placeholder . addClass ( 'hidden' ) ;
2012-04-23 14:30:02 +00:00
2017-02-13 10:35:04 +00:00
switch ( format ) {
case 'markdown' :
2017-02-14 21:21:55 +00:00
$plainText . removeClass ( 'hidden' ) ;
2017-02-13 10:35:04 +00:00
$prettyMessage . addClass ( 'hidden' ) ;
break ;
default :
2017-02-14 21:21:55 +00:00
$plainText . addClass ( 'hidden' ) ;
2017-02-13 10:35:04 +00:00
$prettyMessage . removeClass ( 'hidden' ) ;
break ;
2015-09-05 15:12:11 +00:00
}
2017-02-13 10:35:04 +00:00
}
2017-02-08 19:12:22 +00:00
/ * *
2017-02-13 10:35:04 +00:00
* sets the format in which the text is shown
2017-02-08 19:12:22 +00:00
*
2017-02-14 21:21:55 +00:00
* @ name PasteViewer . setFormat
2017-02-08 19:12:22 +00:00
* @ function
2017-11-21 09:53:33 +00:00
* @ param { string } newFormat the new format
2017-02-08 19:12:22 +00:00
* /
2017-02-13 10:35:04 +00:00
me . setFormat = function ( newFormat )
2017-02-08 19:12:22 +00:00
{
2017-02-15 21:59:55 +00:00
// skip if there is no update
if ( format === newFormat ) {
return ;
2015-09-05 15:12:11 +00:00
}
2017-02-15 21:59:55 +00:00
2017-11-21 09:53:33 +00:00
// needs to update display too, if we switch from or to Markdown
2017-02-15 21:59:55 +00:00
if ( format === 'markdown' || newFormat === 'markdown' ) {
isDisplayed = false ;
2015-09-05 15:12:11 +00:00
}
2017-02-15 21:59:55 +00:00
format = newFormat ;
isChanged = true ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-08 19:11:04 +00:00
2017-02-08 19:12:22 +00:00
/ * *
2017-02-13 10:35:04 +00:00
* returns the current format
2017-02-08 19:12:22 +00:00
*
2017-03-13 19:24:18 +00:00
* @ name PasteViewer . getFormat
2017-02-08 19:12:22 +00:00
* @ function
2017-02-13 10:35:04 +00:00
* @ return { string }
2017-02-08 19:12:22 +00:00
* /
2017-02-13 10:35:04 +00:00
me . getFormat = function ( )
2017-02-08 19:12:22 +00:00
{
2017-02-13 10:35:04 +00:00
return format ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-08 19:11:04 +00:00
2017-02-14 21:21:55 +00:00
/ * *
* returns whether the current view is pretty printed
*
* @ name PasteViewer . isPrettyPrinted
* @ function
* @ return { bool }
* /
me . isPrettyPrinted = function ( )
{
return $prettyPrint . hasClass ( 'prettyprinted' ) ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-14 21:21:55 +00:00
2017-02-08 19:12:22 +00:00
/ * *
2017-02-13 10:35:04 +00:00
* sets the text to show
2017-02-08 19:12:22 +00:00
*
2017-02-15 21:59:55 +00:00
* @ name PasteViewer . setText
2017-02-08 19:12:22 +00:00
* @ function
2017-02-14 21:21:55 +00:00
* @ param { string } newText the text to show
2017-02-08 19:12:22 +00:00
* /
2017-02-13 10:35:04 +00:00
me . setText = function ( newText )
2017-02-08 19:12:22 +00:00
{
2017-02-13 10:35:04 +00:00
if ( text !== newText ) {
text = newText ;
isChanged = true ;
}
2018-01-06 08:26:10 +00:00
} ;
2017-02-08 19:11:04 +00:00
2017-02-15 21:59:55 +00:00
/ * *
* gets the current cached text
*
* @ name PasteViewer . getText
* @ function
* @ return { string }
* /
2017-03-24 23:58:59 +00:00
me . getText = function ( )
2017-02-15 21:59:55 +00:00
{
return text ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-15 21:59:55 +00:00
2017-02-08 19:12:22 +00:00
/ * *
2017-02-13 10:35:04 +00:00
* show / update the parsed text ( preview )
2017-02-08 19:12:22 +00:00
*
2017-02-14 21:21:55 +00:00
* @ name PasteViewer . run
2017-02-08 19:12:22 +00:00
* @ function
* /
2017-02-14 21:21:55 +00:00
me . run = function ( )
2017-02-08 19:12:22 +00:00
{
2017-02-13 10:35:04 +00:00
if ( isChanged ) {
parsePaste ( ) ;
isChanged = false ;
}
if ( ! isDisplayed ) {
showPaste ( ) ;
isDisplayed = true ;
}
2018-01-06 08:26:10 +00:00
} ;
2017-02-08 19:11:04 +00:00
2017-02-08 19:12:22 +00:00
/ * *
2017-02-13 10:35:04 +00:00
* hide parsed text ( preview )
2017-02-08 19:12:22 +00:00
*
2017-02-14 21:21:55 +00:00
* @ name PasteViewer . hide
2017-02-08 19:12:22 +00:00
* @ function
* /
2017-02-13 10:35:04 +00:00
me . hide = function ( )
2017-02-08 19:12:22 +00:00
{
2017-02-13 10:35:04 +00:00
if ( ! isDisplayed ) {
2017-02-14 21:21:55 +00:00
console . warn ( 'PasteViewer was called to hide the parsed view, but it is already hidden.' ) ;
2017-02-13 10:35:04 +00:00
}
2017-02-14 21:21:55 +00:00
$plainText . addClass ( 'hidden' ) ;
2017-02-13 10:35:04 +00:00
$prettyMessage . addClass ( 'hidden' ) ;
$placeholder . addClass ( 'hidden' ) ;
2017-04-02 16:58:11 +00:00
AttachmentViewer . hideAttachmentPreview ( ) ;
2017-02-13 10:35:04 +00:00
isDisplayed = false ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-08 19:11:04 +00:00
2017-02-08 19:12:22 +00:00
/ * *
2017-02-12 17:08:08 +00:00
* init status manager
2017-02-08 19:12:22 +00:00
*
2017-02-12 17:08:08 +00:00
* preloads jQuery elements
*
2017-03-13 19:24:18 +00:00
* @ name PasteViewer . init
2017-02-08 19:12:22 +00:00
* @ function
* /
2017-02-12 17:08:08 +00:00
me . init = function ( )
2017-02-08 19:12:22 +00:00
{
2017-02-13 10:35:04 +00:00
$placeholder = $ ( '#placeholder' ) ;
2017-02-17 21:46:18 +00:00
$plainText = $ ( '#plaintext' ) ;
2017-02-13 10:35:04 +00:00
$prettyMessage = $ ( '#prettymessage' ) ;
$prettyPrint = $ ( '#prettyprint' ) ;
2017-02-12 17:08:08 +00:00
2017-02-13 10:35:04 +00:00
// check requirements
if ( typeof prettyPrintOne !== 'function' ) {
2017-02-15 21:59:55 +00:00
Alert . showError ( [
'The library %s is not available. This may cause display errors.' ,
'pretty print'
] ) ;
2017-02-13 10:35:04 +00:00
}
if ( typeof showdown !== 'object' ) {
2017-02-15 21:59:55 +00:00
Alert . showError ( [
'The library %s is not available. This may cause display errors.' ,
'showdown'
] ) ;
2017-02-13 10:35:04 +00:00
}
// get default option from template/HTML or fall back to set value
2017-02-15 21:59:55 +00:00
format = Model . getFormatDefault ( ) || format ;
2017-11-21 09:53:33 +00:00
text = '' ;
isDisplayed = false ;
isChanged = true ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-14 21:21:55 +00:00
return me ;
2017-03-24 23:58:59 +00:00
} ) ( ) ;
2017-02-14 21:21:55 +00:00
/ * *
* ( view ) Show attachment and preview if possible
*
2017-03-13 19:24:18 +00:00
* @ name AttachmentViewer
2017-02-14 21:21:55 +00:00
* @ class
* /
2017-11-28 05:38:10 +00:00
var AttachmentViewer = ( function ( ) {
2017-02-14 21:21:55 +00:00
var me = { } ;
2017-05-20 14:04:10 +00:00
var $attachmentLink ;
var $attachmentPreview ;
var $attachment ;
var attachmentData ;
var file ;
var $fileInput ;
var $dragAndDropFileName ;
2017-03-24 23:58:59 +00:00
var attachmentHasPreview = false ;
2017-02-14 21:21:55 +00:00
/ * *
* sets the attachment but does not yet show it
*
* @ name AttachmentViewer . setAttachment
* @ function
* @ param { string } attachmentData - base64 - encoded data of file
* @ param { string } fileName - optional , file name
* /
me . setAttachment = function ( attachmentData , fileName )
{
2018-04-08 22:36:55 +00:00
// IE does not support setting a data URI on an a element
// Convert dataURI to a Blob and use msSaveBlob to download
if ( window . Blob && navigator . msSaveBlob ) {
2018-04-09 15:57:58 +00:00
$attachmentLink . off ( 'click' ) . on ( 'click' , function ( ) {
2018-04-09 04:44:37 +00:00
// data URI format: data:[<mediaType>][;base64],<data>
2018-04-07 06:29:36 +00:00
2018-04-09 04:44:37 +00:00
// position in data URI string of where data begins
var base64Start = attachmentData . indexOf ( ',' ) + 1 ;
// position in data URI string of where mediaType ends
var mediaTypeEnd = attachmentData . indexOf ( ';' ) ;
2018-04-08 22:36:55 +00:00
2018-04-09 04:44:37 +00:00
// extract mediaType
var mediaType = attachmentData . substring ( 5 , mediaTypeEnd ) ;
// extract data and convert to binary
2018-08-04 20:30:01 +00:00
var decodedData = atob ( attachmentData . substring ( base64Start ) ) ;
2018-04-07 06:29:36 +00:00
2018-04-09 04:44:37 +00:00
// Transform into a Blob
var decodedDataLength = decodedData . length ;
var buf = new Uint8Array ( decodedDataLength ) ;
2018-04-07 06:29:36 +00:00
2018-08-04 20:30:01 +00:00
for ( var i = 0 ; i < decodedDataLength ; ++ i ) {
2018-04-09 04:44:37 +00:00
buf [ i ] = decodedData . charCodeAt ( i ) ;
}
2018-04-07 06:29:36 +00:00
2018-04-09 04:44:37 +00:00
var blob = new window . Blob ( [ buf ] , { type : mediaType } ) ;
navigator . msSaveBlob ( blob , fileName ) ;
2018-04-08 22:36:55 +00:00
} ) ;
2018-04-07 06:29:36 +00:00
} else {
$attachmentLink . attr ( 'href' , attachmentData ) ;
}
2017-02-14 21:21:55 +00:00
if ( typeof fileName !== 'undefined' ) {
$attachmentLink . attr ( 'download' , fileName ) ;
}
2017-05-15 20:05:52 +00:00
me . handleAttachmentPreview ( $attachmentPreview , attachmentData ) ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-14 21:21:55 +00:00
/ * *
* displays the attachment
*
* @ name AttachmentViewer . showAttachment
* @ function
* /
me . showAttachment = function ( )
{
$attachment . removeClass ( 'hidden' ) ;
if ( attachmentHasPreview ) {
$attachmentPreview . removeClass ( 'hidden' ) ;
}
2018-01-06 08:26:10 +00:00
} ;
2017-02-14 21:21:55 +00:00
/ * *
2017-02-15 21:59:55 +00:00
* removes the attachment
*
2017-08-12 11:26:43 +00:00
* This automatically hides the attachment containers too , to
2017-02-15 21:59:55 +00:00
* prevent an inconsistent display .
2017-02-14 21:21:55 +00:00
*
2017-02-15 21:59:55 +00:00
* @ name AttachmentViewer . removeAttachment
2017-02-14 21:21:55 +00:00
* @ function
* /
me . removeAttachment = function ( )
{
2017-05-15 20:05:52 +00:00
if ( ! $attachment . length ) {
2017-05-13 19:27:41 +00:00
return ;
}
2017-02-15 21:59:55 +00:00
me . hideAttachment ( ) ;
me . hideAttachmentPreview ( ) ;
2017-08-12 11:26:43 +00:00
$attachmentLink . removeAttr ( 'href' ) ;
$attachmentLink . removeAttr ( 'download' ) ;
2018-04-09 15:57:58 +00:00
$attachmentLink . off ( 'click' ) ;
2017-02-15 21:59:55 +00:00
$attachmentPreview . html ( '' ) ;
2017-04-02 16:58:11 +00:00
2018-05-21 17:32:01 +00:00
AttachmentViewer . removeAttachmentData ( ) ;
} ;
/ * *
* removes the attachment data
*
* This removes the data , which would be uploaded otherwise .
*
* @ name AttachmentViewer . removeAttachmentData
* @ function
* /
me . removeAttachmentData = function ( )
{
2017-05-15 20:05:52 +00:00
file = undefined ;
attachmentData = undefined ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-14 21:21:55 +00:00
2018-05-21 22:41:02 +00:00
/ * *
* Cleares the drag & drop data .
*
* @ name AttachmentViewer . clearDragAndDrop
* @ function
* /
me . clearDragAndDrop = function ( )
{
$dragAndDropFileName . text ( '' ) ;
} ;
2017-02-15 21:59:55 +00:00
/ * *
* hides the attachment
*
2017-03-13 19:24:18 +00:00
* This will not hide the preview ( see AttachmentViewer . hideAttachmentPreview
* for that ) nor will it hide the attachment link if it was moved somewhere
* else ( see AttachmentViewer . moveAttachmentTo ) .
2017-02-15 21:59:55 +00:00
*
* @ name AttachmentViewer . hideAttachment
* @ function
* /
me . hideAttachment = function ( )
{
$attachment . addClass ( 'hidden' ) ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-14 21:21:55 +00:00
2017-02-15 21:59:55 +00:00
/ * *
* hides the attachment preview
*
* @ name AttachmentViewer . hideAttachmentPreview
* @ function
* /
me . hideAttachmentPreview = function ( )
{
2017-05-15 20:05:52 +00:00
if ( $attachmentPreview ) {
$attachmentPreview . addClass ( 'hidden' ) ;
2017-05-13 19:27:41 +00:00
}
2018-01-06 08:26:10 +00:00
} ;
2017-02-14 21:21:55 +00:00
/ * *
2018-05-21 17:32:01 +00:00
* checks if there is an attachment displayed
2017-02-14 21:21:55 +00:00
*
* @ name AttachmentViewer . hasAttachment
* @ function
* /
me . hasAttachment = function ( )
{
2017-05-15 20:05:52 +00:00
if ( ! $attachment . length ) {
2017-05-13 19:27:41 +00:00
return false ;
}
2017-03-25 17:44:20 +00:00
var link = $attachmentLink . prop ( 'href' ) ;
2017-05-15 20:05:52 +00:00
return ( typeof link !== 'undefined' && link !== '' ) ;
} ;
/ * *
2018-05-21 17:32:01 +00:00
* checks if there is attachment data ( for preview ! ) available
*
* It returns true , when there is data that needs to be encrypted .
2017-05-15 20:05:52 +00:00
*
* @ name AttachmentViewer . hasAttachmentData
* @ function
* /
me . hasAttachmentData = function ( )
{
if ( $attachment . length ) {
return true ;
}
return false ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-14 21:21:55 +00:00
/ * *
* return the attachment
*
* @ name AttachmentViewer . getAttachment
* @ function
* @ returns { array }
* /
me . getAttachment = function ( )
{
return [
2017-02-15 21:59:55 +00:00
$attachmentLink . prop ( 'href' ) ,
$attachmentLink . prop ( 'download' )
2017-02-14 21:21:55 +00:00
] ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-14 21:21:55 +00:00
2017-02-15 21:59:55 +00:00
/ * *
* moves the attachment link to another element
*
* It is advisable to hide the attachment afterwards ( AttachmentViewer . hideAttachment )
*
2017-03-13 19:24:18 +00:00
* @ name AttachmentViewer . moveAttachmentTo
2017-02-15 21:59:55 +00:00
* @ function
* @ param { jQuery } $element - the wrapper / container element where this should be moved to
* @ param { string } label - the text to show ( % s will be replaced with the file name ) , will automatically be translated
* /
me . moveAttachmentTo = function ( $element , label )
{
// move elemement to new place
$attachmentLink . appendTo ( $element ) ;
// update text
I18n . _ ( $attachmentLink , label , $attachmentLink . attr ( 'download' ) ) ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-15 21:59:55 +00:00
2017-02-14 21:21:55 +00:00
/ * *
2018-07-22 08:24:39 +00:00
* read file data as data URL using the FileReader API
2017-02-14 21:21:55 +00:00
*
2018-04-29 09:57:03 +00:00
* @ name AttachmentViewer . readFileData
2018-05-21 22:43:24 +00:00
* @ private
2017-02-14 21:21:55 +00:00
* @ function
2018-07-22 08:24:39 +00:00
* @ param { object } loadedFile ( optional ) loaded file object
2018-04-29 09:57:03 +00:00
* @ see { @ link https : //developer.mozilla.org/en-US/docs/Web/API/FileReader#readAsDataURL()}
2017-02-14 21:21:55 +00:00
* /
2018-05-22 08:19:53 +00:00
function readFileData ( loadedFile ) {
2017-05-13 17:46:22 +00:00
if ( typeof FileReader === 'undefined' ) {
// revert loading status…
2017-05-15 20:05:52 +00:00
me . hideAttachment ( ) ;
me . hideAttachmentPreview ( ) ;
Alert . showError ( 'Your browser does not support uploading encrypted files. Please use a newer browser.' ) ;
2017-05-13 17:46:22 +00:00
return ;
}
2017-02-14 21:21:55 +00:00
2017-05-13 17:46:22 +00:00
var fileReader = new FileReader ( ) ;
2017-05-20 14:04:10 +00:00
if ( loadedFile === undefined ) {
loadedFile = $fileInput [ 0 ] . files [ 0 ] ;
$dragAndDropFileName . text ( '' ) ;
2017-05-13 17:46:22 +00:00
} else {
2017-05-20 14:04:10 +00:00
$dragAndDropFileName . text ( loadedFile . name ) ;
2017-05-13 17:46:22 +00:00
}
2017-02-14 21:21:55 +00:00
2017-05-20 14:04:10 +00:00
file = loadedFile ;
2017-02-14 21:21:55 +00:00
2017-05-13 17:46:22 +00:00
fileReader . onload = function ( event ) {
var dataURL = event . target . result ;
2017-05-15 20:05:52 +00:00
attachmentData = dataURL ;
2017-05-13 17:46:22 +00:00
if ( Editor . isPreview ( ) ) {
2017-05-15 20:05:52 +00:00
me . handleAttachmentPreview ( $attachmentPreview , dataURL ) ;
2018-04-29 09:57:03 +00:00
$attachmentPreview . removeClass ( 'hidden' ) ;
2017-05-13 17:46:22 +00:00
}
} ;
2017-05-20 14:04:10 +00:00
fileReader . readAsDataURL ( loadedFile ) ;
2018-05-22 08:19:53 +00:00
}
2017-05-13 17:46:22 +00:00
/ * *
2018-04-29 09:57:03 +00:00
* handle the preview of files that can either be an image , video , audio or pdf element
*
* @ name AttachmentViewer . handleAttachmentPreview
* @ function
2018-07-22 08:24:39 +00:00
* @ argument { jQuery } $targetElement element where the preview should be appended
* @ argument { string } file as a data URL
2017-05-13 17:46:22 +00:00
* /
2017-05-15 20:05:52 +00:00
me . handleAttachmentPreview = function ( $targetElement , data ) {
2017-05-13 17:46:22 +00:00
if ( data ) {
2018-04-29 09:57:03 +00:00
// source: https://developer.mozilla.org/en-US/docs/Web/API/FileReader#readAsDataURL()
var mimeType = data . slice (
data . indexOf ( 'data:' ) + 5 ,
data . indexOf ( ';base64,' )
) ;
2017-05-13 17:46:22 +00:00
2018-04-29 09:57:03 +00:00
attachmentHasPreview = true ;
2017-05-13 17:46:22 +00:00
if ( mimeType . match ( /image\//i ) ) {
2018-04-29 09:57:03 +00:00
$targetElement . html (
$ ( document . createElement ( 'img' ) )
. attr ( 'src' , data )
. attr ( 'class' , 'img-thumbnail' )
) ;
2017-05-13 17:46:22 +00:00
} else if ( mimeType . match ( /video\//i ) ) {
2018-04-29 09:57:03 +00:00
$targetElement . html (
$ ( document . createElement ( 'video' ) )
. attr ( 'controls' , 'true' )
. attr ( 'autoplay' , 'true' )
. attr ( 'class' , 'img-thumbnail' )
. append ( $ ( document . createElement ( 'source' ) )
. attr ( 'type' , mimeType )
. attr ( 'src' , data ) )
) ;
2017-05-13 17:46:22 +00:00
} else if ( mimeType . match ( /audio\//i ) ) {
2018-04-29 09:57:03 +00:00
$targetElement . html (
$ ( document . createElement ( 'audio' ) )
. attr ( 'controls' , 'true' )
. attr ( 'autoplay' , 'true' )
. append ( $ ( document . createElement ( 'source' ) )
. attr ( 'type' , mimeType )
. attr ( 'src' , data ) )
) ;
2017-05-13 17:46:22 +00:00
} else if ( mimeType . match ( /\/pdf/i ) ) {
2018-04-29 09:57:03 +00:00
// PDFs are only displayed if the filesize is smaller than about 1MB (after base64 encoding).
// Bigger filesizes currently cause crashes in various browsers.
// See also: https://code.google.com/p/chromium/issues/detail?id=69227
// Firefox crashes with files that are about 1.5MB
// The performance with 1MB files is bearable
if ( data . length > 1398488 ) {
2018-05-21 22:41:02 +00:00
Alert . showError ( 'File too large, to display a preview. Please download the attachment.' ) ; //TODO: is this error really neccessary?
2018-04-29 09:57:03 +00:00
return ;
}
2017-05-13 17:46:22 +00:00
2018-04-29 09:57:03 +00:00
// Fallback for browsers, that don't support the vh unit
var clientHeight = $ ( window ) . height ( ) ;
2017-05-13 17:46:22 +00:00
2018-04-29 09:57:03 +00:00
$targetElement . html (
$ ( document . createElement ( 'embed' ) )
. attr ( 'src' , data )
. attr ( 'type' , 'application/pdf' )
. attr ( 'class' , 'pdfPreview' )
. css ( 'height' , clientHeight )
2017-05-13 17:46:22 +00:00
) ;
2018-04-29 09:57:03 +00:00
} else {
attachmentHasPreview = false ;
}
2017-05-15 20:05:52 +00:00
}
2017-05-13 17:46:22 +00:00
} ;
/ * *
2018-04-29 09:57:03 +00:00
* attaches the file attachment drag & drop handler to the page
*
* @ name AttachmentViewer . addDragDropHandler
2018-05-21 22:43:24 +00:00
* @ private
2018-04-29 09:57:03 +00:00
* @ function
2017-05-13 17:46:22 +00:00
* /
2018-05-22 08:19:53 +00:00
function addDragDropHandler ( ) {
2017-05-15 20:05:52 +00:00
if ( typeof $fileInput === 'undefined' || $fileInput . length === 0 ) {
2017-05-13 17:46:22 +00:00
return ;
}
var ignoreDragDrop = function ( event ) {
event . stopPropagation ( ) ;
event . preventDefault ( ) ;
} ;
var drop = function ( event ) {
2017-05-15 20:05:52 +00:00
var evt = event . originalEvent ;
evt . stopPropagation ( ) ;
evt . preventDefault ( ) ;
2017-05-13 17:46:22 +00:00
2017-05-15 20:05:52 +00:00
if ( $fileInput ) {
var file = evt . dataTransfer . files [ 0 ] ;
2017-05-13 17:46:22 +00:00
//Clear the file input:
2017-05-15 20:05:52 +00:00
$fileInput . wrap ( '<form>' ) . closest ( 'form' ) . get ( 0 ) . reset ( ) ;
$fileInput . unwrap ( ) ;
2017-05-13 17:46:22 +00:00
//Only works in Chrome:
//fileInput[0].files = e.dataTransfer.files;
2018-05-21 22:43:24 +00:00
readFileData ( file ) ;
2017-05-13 17:46:22 +00:00
}
} ;
2017-05-15 20:05:52 +00:00
$ ( document ) . on ( 'drop' , drop ) ;
$ ( document ) . on ( 'dragenter' , ignoreDragDrop ) ;
$ ( document ) . on ( 'dragover' , ignoreDragDrop ) ;
2018-05-21 22:41:02 +00:00
$fileInput . on ( 'change' , function ( ) {
2018-05-21 22:43:24 +00:00
readFileData ( ) ;
2017-05-13 17:46:22 +00:00
} ) ;
2018-05-22 08:19:53 +00:00
}
2017-05-13 17:46:22 +00:00
2017-05-13 19:43:32 +00:00
/ * *
2018-04-29 09:57:03 +00:00
* attaches the clipboard attachment handler to the page
*
* @ name AttachmentViewer . addClipboardEventHandler
2018-05-21 22:43:24 +00:00
* @ private
2018-04-29 09:57:03 +00:00
* @ function
2017-05-13 19:43:32 +00:00
* /
2018-05-22 08:19:53 +00:00
function addClipboardEventHandler ( ) {
$ ( document ) . on ( 'paste' , function ( event ) {
var items = ( event . clipboardData || event . originalEvent . clipboardData ) . items ;
2018-08-04 20:30:01 +00:00
for ( var i = 0 ; i < items . length ; ++ i ) {
if ( items [ i ] . kind === 'file' ) {
//Clear the file input:
$fileInput . wrap ( '<form>' ) . closest ( 'form' ) . get ( 0 ) . reset ( ) ;
$fileInput . unwrap ( ) ;
readFileData ( items [ i ] . getAsFile ( ) ) ;
2018-05-22 08:19:53 +00:00
}
}
} ) ;
}
2017-05-15 20:05:52 +00:00
2018-04-29 09:57:03 +00:00
/ * *
* getter for attachment data
*
* @ name AttachmentViewer . getAttachmentData
* @ function
* @ return { jQuery }
* /
2017-05-15 20:05:52 +00:00
me . getAttachmentData = function ( ) {
return attachmentData ;
} ;
2018-04-29 09:57:03 +00:00
/ * *
* getter for attachment link
*
* @ name AttachmentViewer . getAttachmentLink
* @ function
* @ return { jQuery }
* /
2017-05-15 20:05:52 +00:00
me . getAttachmentLink = function ( ) {
return $attachmentLink ;
} ;
2018-04-29 09:57:03 +00:00
/ * *
* getter for attachment preview
*
* @ name AttachmentViewer . getAttachmentPreview
* @ function
* @ return { jQuery }
* /
2017-05-15 20:05:52 +00:00
me . getAttachmentPreview = function ( ) {
return $attachmentPreview ;
} ;
2018-04-29 09:57:03 +00:00
/ * *
* getter for file data , returns the file contents
*
* @ name AttachmentViewer . getFile
* @ function
* @ return { string }
* /
2017-05-15 20:05:52 +00:00
me . getFile = function ( ) {
return file ;
2017-05-13 19:43:32 +00:00
} ;
2017-02-14 21:21:55 +00:00
/ * *
* initiate
*
* preloads jQuery elements
*
* @ name AttachmentViewer . init
* @ function
* /
me . init = function ( )
{
$attachment = $ ( '#attachment' ) ;
2017-05-15 20:05:52 +00:00
if ( $attachment . length ) {
$attachmentLink = $ ( '#attachment a' ) ;
$attachmentPreview = $ ( '#attachmentPreview' ) ;
$dragAndDropFileName = $ ( '#dragAndDropFileName' ) ;
2017-05-13 17:46:22 +00:00
2017-05-15 20:05:52 +00:00
$fileInput = $ ( '#file' ) ;
2018-05-21 22:43:24 +00:00
addDragDropHandler ( ) ;
addClipboardEventHandler ( ) ;
2017-05-13 19:27:41 +00:00
}
2017-02-25 08:35:55 +00:00
}
2017-02-14 21:21:55 +00:00
return me ;
2017-11-28 05:38:10 +00:00
} ) ( ) ;
2017-02-14 21:21:55 +00:00
/ * *
* ( view ) Shows discussion thread and handles replies
*
2017-03-13 19:24:18 +00:00
* @ name DiscussionViewer
2017-02-14 21:21:55 +00:00
* @ class
* /
2017-12-18 13:47:17 +00:00
var DiscussionViewer = ( function ( ) {
2017-02-14 21:21:55 +00:00
var me = { } ;
2017-02-17 21:46:18 +00:00
var $commentTail ,
2017-02-17 19:46:10 +00:00
$discussion ,
$reply ,
$replyMessage ,
$replyNickname ,
2017-02-17 21:46:18 +00:00
$replyStatus ,
$commentContainer ;
2017-02-17 19:46:10 +00:00
var replyCommentId ;
2017-02-14 21:21:55 +00:00
/ * *
2017-02-17 19:46:10 +00:00
* initializes the templates
2017-02-14 21:21:55 +00:00
*
2017-03-13 19:24:18 +00:00
* @ name DiscussionViewer . initTemplates
2017-02-17 19:46:10 +00:00
* @ private
2017-02-14 21:21:55 +00:00
* @ function
* /
2017-02-17 19:46:10 +00:00
function initTemplates ( )
2017-02-14 21:21:55 +00:00
{
2017-02-17 19:46:10 +00:00
$reply = Model . getTemplate ( 'reply' ) ;
$replyMessage = $reply . find ( '#replymessage' ) ;
$replyNickname = $reply . find ( '#nickname' ) ;
$replyStatus = $reply . find ( '#replystatus' ) ;
// cache jQuery elements
$commentTail = Model . getTemplate ( 'commenttail' ) ;
}
2017-02-14 21:21:55 +00:00
2017-03-13 19:24:18 +00:00
/ * *
* open the comment entry when clicking the "Reply" button of a comment
*
* @ name DiscussionViewer . openReply
* @ private
* @ function
* @ param { Event } event
* /
function openReply ( event )
{
var $source = $ ( event . target ) ;
// clear input
$replyMessage . val ( '' ) ;
$replyNickname . val ( '' ) ;
// get comment id from source element
replyCommentId = $source . parent ( ) . prop ( 'id' ) . split ( '_' ) [ 1 ] ;
// move to correct position
$source . after ( $reply ) ;
// show
$reply . removeClass ( 'hidden' ) ;
$replyMessage . focus ( ) ;
event . preventDefault ( ) ;
}
2017-02-14 21:21:55 +00:00
/ * *
2017-02-17 19:46:10 +00:00
* custom handler for displaying notifications in own status message area
2017-02-14 21:21:55 +00:00
*
2017-02-17 19:46:10 +00:00
* @ name DiscussionViewer . handleNotification
2017-02-14 21:21:55 +00:00
* @ function
2017-02-17 19:46:10 +00:00
* @ param { string } alertType
* @ return { bool | jQuery }
2017-02-14 21:21:55 +00:00
* /
2018-01-06 12:32:07 +00:00
me . handleNotification = function ( alertType )
2017-02-14 21:21:55 +00:00
{
2017-02-17 19:46:10 +00:00
// ignore loading messages
if ( alertType === 'loading' ) {
return false ;
}
2017-02-14 21:21:55 +00:00
2017-02-17 19:46:10 +00:00
if ( alertType === 'danger' ) {
$replyStatus . removeClass ( 'alert-info' ) ;
$replyStatus . addClass ( 'alert-danger' ) ;
$replyStatus . find ( ':first' ) . removeClass ( 'glyphicon-alert' ) ;
$replyStatus . find ( ':first' ) . addClass ( 'glyphicon-info-sign' ) ;
} else {
$replyStatus . removeClass ( 'alert-danger' ) ;
$replyStatus . addClass ( 'alert-info' ) ;
$replyStatus . find ( ':first' ) . removeClass ( 'glyphicon-info-sign' ) ;
$replyStatus . find ( ':first' ) . addClass ( 'glyphicon-alert' ) ;
}
return $replyStatus ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-14 21:21:55 +00:00
2017-02-17 19:46:10 +00:00
/ * *
* adds another comment
*
* @ name DiscussionViewer . addComment
* @ function
* @ param { object } comment
* @ param { string } commentText
2018-01-02 10:42:03 +00:00
* @ param { string } nickname
2017-02-17 19:46:10 +00:00
* /
2018-01-02 10:42:03 +00:00
me . addComment = function ( comment , commentText , nickname )
2017-02-17 19:46:10 +00:00
{
if ( commentText === '' ) {
commentText = 'comment decryption failed' ;
}
// create new comment based on template
var $commentEntry = Model . getTemplate ( 'comment' ) ;
$commentEntry . prop ( 'id' , 'comment_' + comment . id ) ;
var $commentEntryData = $commentEntry . find ( 'div.commentdata' ) ;
// set & parse text
2017-11-22 21:27:38 +00:00
$commentEntryData . html (
DOMPurify . sanitize (
2018-01-01 09:25:07 +00:00
Helper . urls2links ( commentText )
2017-11-22 21:27:38 +00:00
)
) ;
2017-02-17 19:46:10 +00:00
// set nickname
2017-03-12 16:08:12 +00:00
if ( nickname . length > 0 ) {
2017-02-17 19:46:10 +00:00
$commentEntry . find ( 'span.nickname' ) . text ( nickname ) ;
} else {
2017-03-12 16:08:12 +00:00
$commentEntry . find ( 'span.nickname' ) . html ( '<i></i>' ) ;
I18n . _ ( $commentEntry . find ( 'span.nickname i' ) , 'Anonymous' ) ;
2017-02-17 19:46:10 +00:00
}
// set date
$commentEntry . find ( 'span.commentdate' )
. text ( ' (' + ( new Date ( comment . meta . postdate * 1000 ) . toLocaleString ( ) ) + ')' )
. attr ( 'title' , 'CommentID: ' + comment . id ) ;
// if an avatar is available, display it
if ( comment . meta . vizhash ) {
$commentEntry . find ( 'span.nickname' )
2017-03-12 16:08:12 +00:00
. before (
'<img src="' + comment . meta . vizhash + '" class="vizhash" /> '
) ;
$ ( document ) . on ( 'languageLoaded' , function ( ) {
$commentEntry . find ( 'img.vizhash' )
. prop ( 'title' , I18n . _ ( 'Avatar generated from IP address' ) ) ;
} ) ;
2017-02-17 19:46:10 +00:00
}
2018-01-02 10:42:03 +00:00
// starting point (default value/fallback)
var $place = $commentContainer ;
// if parent comment exists
var $parentComment = $ ( '#comment_' + comment . parentid ) ;
if ( $parentComment . length ) {
// use parent as position for new comment, so it is shifted
// to the right
$place = $parentComment ;
}
2017-02-17 19:46:10 +00:00
// finally append comment
$place . append ( $commentEntry ) ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-17 19:46:10 +00:00
/ * *
* finishes the discussion area after last comment
*
* @ name DiscussionViewer . finishDiscussion
* @ function
* /
me . finishDiscussion = function ( )
{
// add 'add new comment' area
$commentContainer . append ( $commentTail ) ;
// show discussions
$discussion . removeClass ( 'hidden' ) ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-17 19:46:10 +00:00
/ * *
* removes the old discussion and prepares everything for creating a new
* one .
*
2017-12-18 13:47:17 +00:00
* @ name DiscussionViewer . prepareNewDiscussion
2017-02-17 19:46:10 +00:00
* @ function
* /
2017-12-18 13:47:17 +00:00
me . prepareNewDiscussion = function ( )
2017-02-17 19:46:10 +00:00
{
$commentContainer . html ( '' ) ;
$discussion . addClass ( 'hidden' ) ;
// (re-)init templates
initTemplates ( ) ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-17 19:46:10 +00:00
/ * *
2017-12-18 13:47:17 +00:00
* returns the users message from the reply form
2017-02-17 19:46:10 +00:00
*
2017-12-18 13:47:17 +00:00
* @ name DiscussionViewer . getReplyMessage
2017-02-17 19:46:10 +00:00
* @ function
2017-12-18 13:47:17 +00:00
* @ return { String }
2017-02-17 19:46:10 +00:00
* /
2017-12-18 13:47:17 +00:00
me . getReplyMessage = function ( )
2017-02-17 19:46:10 +00:00
{
2017-12-18 13:47:17 +00:00
return $replyMessage . val ( ) ;
2018-01-06 08:26:10 +00:00
} ;
2017-12-18 13:47:17 +00:00
/ * *
* returns the users nickname ( if any ) from the reply form
*
* @ name DiscussionViewer . getReplyNickname
* @ function
* @ return { String }
* /
me . getReplyNickname = function ( )
{
return $replyNickname . val ( ) ;
2018-01-06 08:26:10 +00:00
} ;
2017-12-18 13:47:17 +00:00
/ * *
* returns the id of the parent comment the user is replying to
*
* @ name DiscussionViewer . getReplyCommentId
* @ function
* @ return { int | undefined }
* /
me . getReplyCommentId = function ( )
{
return replyCommentId ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-17 19:46:10 +00:00
/ * *
* highlights a specific comment and scrolls to it if necessary
*
* @ name DiscussionViewer . highlightComment
* @ function
* @ param { string } commentId
* @ param { bool } fadeOut - whether to fade out the comment
* /
me . highlightComment = function ( commentId , fadeOut )
{
var $comment = $ ( '#comment_' + commentId ) ;
// in case comment does not exist, cancel
if ( $comment . length === 0 ) {
return ;
}
2018-07-01 06:59:55 +00:00
$comment . addClass ( 'highlight' ) ;
2017-02-17 19:46:10 +00:00
var highlightComment = function ( ) {
if ( fadeOut === true ) {
setTimeout ( function ( ) {
$comment . removeClass ( 'highlight' ) ;
} , 300 ) ;
}
2018-01-06 09:57:54 +00:00
} ;
2017-02-17 19:46:10 +00:00
if ( UiHelper . isVisible ( $comment ) ) {
return highlightComment ( ) ;
}
UiHelper . scrollTo ( $comment , 100 , 'swing' , highlightComment ) ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-14 21:21:55 +00:00
/ * *
* initiate
*
* preloads jQuery elements
*
2017-02-15 21:59:55 +00:00
* @ name DiscussionViewer . init
2017-02-14 21:21:55 +00:00
* @ function
* /
me . init = function ( )
{
2017-02-17 19:46:10 +00:00
// bind events to templates (so they are later cloned)
$ ( '#commenttailtemplate, #commenttemplate' ) . find ( 'button' ) . on ( 'click' , openReply ) ;
$ ( '#replytemplate' ) . find ( 'button' ) . on ( 'click' , PasteEncrypter . sendComment ) ;
$commentContainer = $ ( '#commentcontainer' ) ;
2017-02-14 21:21:55 +00:00
$discussion = $ ( '#discussion' ) ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-08 19:11:04 +00:00
2017-02-12 17:08:08 +00:00
return me ;
2017-12-18 13:47:17 +00:00
} ) ( ) ;
2017-02-12 17:08:08 +00:00
/ * *
* Manage top ( navigation ) bar
*
2017-03-13 19:24:18 +00:00
* @ name TopNav
* @ param { object } window
* @ param { object } document
2017-02-12 17:08:08 +00:00
* @ class
* /
2017-02-14 21:21:55 +00:00
var TopNav = ( function ( window , document ) {
2017-02-12 17:08:08 +00:00
var me = { } ;
2017-02-12 20:13:04 +00:00
var createButtonsDisplayed = false ;
var viewButtonsDisplayed = false ;
2017-02-12 17:08:08 +00:00
var $attach ,
$burnAfterReading ,
$burnAfterReadingOption ,
$cloneButton ,
2017-02-15 21:59:55 +00:00
$customAttachment ,
2017-02-12 17:08:08 +00:00
$expiration ,
$fileRemoveButton ,
2017-02-17 21:59:16 +00:00
$fileWrap ,
2017-02-12 17:08:08 +00:00
$formatter ,
$newButton ,
$openDiscussion ,
2017-02-17 21:46:18 +00:00
$openDiscussionOption ,
2017-02-12 20:13:04 +00:00
$password ,
2017-02-13 20:12:00 +00:00
$passwordInput ,
2017-02-12 17:08:08 +00:00
$rawTextButton ,
2017-12-25 13:59:15 +00:00
$qrCodeLink ,
2017-04-11 20:21:30 +00:00
$sendButton ,
$retryButton ;
2017-02-12 17:08:08 +00:00
2017-04-11 20:21:30 +00:00
var pasteExpiration = '1week' ,
retryButtonCallback ;
2017-02-13 10:35:04 +00:00
2017-02-12 17:08:08 +00:00
/ * *
2017-02-13 10:35:04 +00:00
* set the expiration on bootstrap templates in dropdown
2017-02-12 17:08:08 +00:00
*
2017-02-14 21:21:55 +00:00
* @ name TopNav . updateExpiration
2017-03-13 19:24:18 +00:00
* @ private
2017-02-12 17:08:08 +00:00
* @ function
* @ param { Event } event
* /
2017-02-13 10:35:04 +00:00
function updateExpiration ( event )
2017-02-12 17:08:08 +00:00
{
2017-02-13 10:35:04 +00:00
// get selected option
2017-02-12 17:08:08 +00:00
var target = $ ( event . target ) ;
2017-02-13 10:35:04 +00:00
// update dropdown display and save new expiration time
2017-02-12 17:08:08 +00:00
$ ( '#pasteExpirationDisplay' ) . text ( target . text ( ) ) ;
2017-02-13 10:35:04 +00:00
pasteExpiration = target . data ( 'expiration' ) ;
event . preventDefault ( ) ;
2017-02-12 17:08:08 +00:00
}
/ * *
2017-02-13 10:35:04 +00:00
* set the format on bootstrap templates in dropdown
2017-02-12 17:08:08 +00:00
*
2017-02-14 21:21:55 +00:00
* @ name TopNav . updateFormat
2017-03-13 19:24:18 +00:00
* @ private
2017-02-12 17:08:08 +00:00
* @ function
* @ param { Event } event
* /
2017-02-13 10:35:04 +00:00
function updateFormat ( event )
2017-02-12 17:08:08 +00:00
{
2017-02-13 10:35:04 +00:00
// get selected option
var $target = $ ( event . target ) ;
2017-02-12 17:08:08 +00:00
2017-02-13 10:35:04 +00:00
// update dropdown display and save new format
var newFormat = $target . data ( 'format' ) ;
$ ( '#pasteFormatterDisplay' ) . text ( $target . text ( ) ) ;
2017-02-14 21:21:55 +00:00
PasteViewer . setFormat ( newFormat ) ;
2017-02-13 10:35:04 +00:00
// update preview
2017-02-14 21:21:55 +00:00
if ( Editor . isPreview ( ) ) {
PasteViewer . run ( ) ;
2017-02-12 17:08:08 +00:00
}
2017-02-13 10:35:04 +00:00
2017-02-12 17:08:08 +00:00
event . preventDefault ( ) ;
2017-02-13 10:35:04 +00:00
}
2017-02-08 19:12:22 +00:00
/ * *
2017-02-12 17:08:08 +00:00
* when "burn after reading" is checked , disable discussion
2017-02-08 19:12:22 +00:00
*
2017-02-14 21:21:55 +00:00
* @ name TopNav . changeBurnAfterReading
2017-03-13 19:24:18 +00:00
* @ private
2017-02-08 19:12:22 +00:00
* @ function
* /
2017-02-12 17:08:08 +00:00
function changeBurnAfterReading ( )
2017-02-08 19:11:04 +00:00
{
2017-02-13 10:35:04 +00:00
if ( $burnAfterReading . is ( ':checked' ) ) {
$openDiscussionOption . addClass ( 'buttondisabled' ) ;
$openDiscussion . prop ( 'checked' , false ) ;
// if button is actually disabled, force-enable it and uncheck other button
$burnAfterReadingOption . removeClass ( 'buttondisabled' ) ;
} else {
$openDiscussionOption . removeClass ( 'buttondisabled' ) ;
2017-02-08 19:12:22 +00:00
}
2017-02-12 17:08:08 +00:00
}
2015-09-05 15:12:11 +00:00
2017-02-08 19:12:22 +00:00
/ * *
2017-02-12 17:08:08 +00:00
* when discussion is checked , disable "burn after reading"
2017-02-08 19:12:22 +00:00
*
2017-02-14 21:21:55 +00:00
* @ name TopNav . changeOpenDiscussion
2017-03-13 19:24:18 +00:00
* @ private
2017-02-08 19:12:22 +00:00
* @ function
* /
2017-02-13 10:35:04 +00:00
function changeOpenDiscussion ( )
2015-09-05 15:12:11 +00:00
{
2017-02-13 10:35:04 +00:00
if ( $openDiscussion . is ( ':checked' ) ) {
2017-02-12 17:08:08 +00:00
$burnAfterReadingOption . addClass ( 'buttondisabled' ) ;
2017-02-13 10:35:04 +00:00
$burnAfterReading . prop ( 'checked' , false ) ;
// if button is actually disabled, force-enable it and uncheck other button
$openDiscussionOption . removeClass ( 'buttondisabled' ) ;
} else {
2017-02-12 17:08:08 +00:00
$burnAfterReadingOption . removeClass ( 'buttondisabled' ) ;
2017-02-08 19:12:22 +00:00
}
2017-02-12 17:08:08 +00:00
}
/ * *
* return raw text
*
2017-02-14 21:21:55 +00:00
* @ name TopNav . rawText
2017-03-13 19:24:18 +00:00
* @ private
2017-02-12 17:08:08 +00:00
* @ function
* /
2018-01-06 12:32:07 +00:00
function rawText ( )
2017-02-12 17:08:08 +00:00
{
2017-02-17 21:26:39 +00:00
TopNav . hideAllButtons ( ) ;
2018-01-06 12:32:07 +00:00
Alert . showLoading ( 'Showing raw text…' , 'time' ) ;
2017-02-15 21:59:55 +00:00
var paste = PasteViewer . getText ( ) ;
// push a new state to allow back navigation with browser back button
2017-02-12 17:08:08 +00:00
history . pushState (
2017-02-15 21:59:55 +00:00
{ type : 'raw' } ,
document . title ,
// recreate paste URL
Helper . baseUri ( ) + '?' + Model . getPasteId ( ) + '#' +
Model . getPasteKey ( )
2017-02-12 17:08:08 +00:00
) ;
2017-02-15 21:59:55 +00:00
2017-02-12 17:08:08 +00: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)
2017-02-17 21:26:39 +00:00
var $head = $ ( 'head' ) . children ( ) . not ( 'noscript, script, link[type="text/css"]' ) ;
2017-02-12 17:08:08 +00:00
var newDoc = document . open ( 'text/html' , 'replace' ) ;
2017-02-17 21:26:39 +00:00
newDoc . write ( '<!DOCTYPE html><html><head>' ) ;
2018-08-04 20:30:01 +00:00
for ( var i = 0 ; i < $head . length ; ++ i ) {
2017-02-17 21:26:39 +00:00
newDoc . write ( $head [ i ] . outerHTML ) ;
}
2018-09-02 07:14:36 +00:00
newDoc . write ( '</head><body><pre>' + DOMPurify . sanitize ( $ ( '<div />' ) . text ( paste ) . html ( ) ) + '</pre></body></html>' ) ;
2017-02-12 17:08:08 +00:00
newDoc . close ( ) ;
}
/ * *
2017-02-14 21:21:55 +00:00
* saves the language in a cookie and reloads the page
2017-02-12 17:08:08 +00:00
*
2017-02-14 21:21:55 +00:00
* @ name TopNav . setLanguage
2017-03-13 19:24:18 +00:00
* @ private
2017-02-12 17:08:08 +00:00
* @ function
* @ param { Event } event
* /
function setLanguage ( event )
{
document . cookie = 'lang=' + $ ( event . target ) . data ( 'lang' ) ;
2017-02-14 21:21:55 +00:00
UiHelper . reloadHome ( ) ;
2017-02-12 17:08:08 +00:00
}
2017-02-15 21:59:55 +00:00
/ * *
* hides all messages and creates a new paste
*
2017-03-13 19:24:18 +00:00
* @ name TopNav . clickNewPaste
2017-02-15 21:59:55 +00:00
* @ private
* @ function
* /
2018-01-06 12:32:07 +00:00
function clickNewPaste ( )
2017-02-15 21:59:55 +00:00
{
Controller . hideStatusMessages ( ) ;
Controller . newPaste ( ) ;
}
2017-04-11 20:21:30 +00:00
/ * *
* retrys some callback registered before
*
* @ name TopNav . clickRetryButton
* @ private
* @ function
* @ param { Event } event
* /
function clickRetryButton ( event )
{
2017-04-11 20:36:25 +00:00
retryButtonCallback ( event ) ;
2017-04-11 20:21:30 +00:00
}
2017-02-15 21:59:55 +00:00
/ * *
* removes the existing attachment
*
2017-03-13 19:24:18 +00:00
* @ name TopNav . removeAttachment
2017-02-15 21:59:55 +00:00
* @ private
* @ function
* @ param { Event } event
* /
function removeAttachment ( event )
{
// if custom attachment is used, remove it first
if ( ! $customAttachment . hasClass ( 'hidden' ) ) {
AttachmentViewer . removeAttachment ( ) ;
$customAttachment . addClass ( 'hidden' ) ;
$fileWrap . removeClass ( 'hidden' ) ;
}
2018-05-21 17:32:01 +00:00
// in any case, remove saved attachment data
AttachmentViewer . removeAttachmentData ( ) ;
2018-05-21 22:41:02 +00:00
// hide UI for selected files
2017-02-15 21:59:55 +00:00
// our up-to-date jQuery can handle it :)
$fileWrap . find ( 'input' ) . val ( '' ) ;
2018-05-21 22:41:02 +00:00
AttachmentViewer . clearDragAndDrop ( ) ;
2017-02-15 21:59:55 +00:00
// pevent '#' from appearing in the URL
event . preventDefault ( ) ;
}
2017-02-08 19:12:22 +00:00
/ * *
2017-12-25 13:59:15 +00:00
* Shows the QR code of the current paste ( URL ) .
*
* @ name TopNav . displayQrCode
2018-03-03 06:55:27 +00:00
* @ private
2017-12-25 13:59:15 +00:00
* @ function
* /
2018-01-06 12:32:07 +00:00
function displayQrCode ( )
2017-12-25 13:59:15 +00:00
{
var qrCanvas = kjua ( {
render : 'canvas' ,
text : window . location . href
} ) ;
$ ( '#qrcode-display' ) . html ( qrCanvas ) ;
2018-01-06 08:58:19 +00:00
}
2017-12-25 13:59:15 +00:00
2017-02-08 19:12:22 +00:00
/ * *
2018-02-25 08:45:51 +00:00
* Shows all navigation elements for viewing an existing paste
2017-02-08 19:12:22 +00:00
*
2017-02-17 21:26:39 +00:00
* @ name TopNav . showViewButtons
2017-02-08 19:12:22 +00:00
* @ function
* /
2017-02-12 17:08:08 +00:00
me . showViewButtons = function ( )
2015-09-05 15:12:11 +00:00
{
2017-02-12 20:13:04 +00:00
if ( viewButtonsDisplayed ) {
2017-10-30 06:04:59 +00:00
console . warn ( 'showViewButtons: view buttons are already displayed' ) ;
2017-02-12 20:13:04 +00:00
return ;
}
2017-02-17 19:46:10 +00:00
$newButton . removeClass ( 'hidden' ) ;
2017-02-12 17:08:08 +00:00
$cloneButton . removeClass ( 'hidden' ) ;
$rawTextButton . removeClass ( 'hidden' ) ;
2017-12-25 13:59:15 +00:00
$qrCodeLink . removeClass ( 'hidden' ) ;
2017-02-12 20:13:04 +00:00
viewButtonsDisplayed = true ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-08 19:12:22 +00:00
2017-02-12 17:08:08 +00:00
/ * *
2018-02-25 08:45:51 +00:00
* Hides all navigation elements for viewing an existing paste
2017-02-12 17:08:08 +00:00
*
2017-02-17 21:26:39 +00:00
* @ name TopNav . hideViewButtons
2017-02-12 17:08:08 +00:00
* @ function
* /
me . hideViewButtons = function ( )
{
2017-02-12 20:13:04 +00:00
if ( ! viewButtonsDisplayed ) {
2017-10-30 06:04:59 +00:00
console . warn ( 'hideViewButtons: view buttons are already hidden' ) ;
2017-02-12 20:13:04 +00:00
return ;
}
2017-02-12 17:08:08 +00:00
$cloneButton . addClass ( 'hidden' ) ;
2017-04-11 20:21:30 +00:00
$newButton . addClass ( 'hidden' ) ;
2017-02-12 17:08:08 +00:00
$rawTextButton . addClass ( 'hidden' ) ;
2017-12-25 13:59:15 +00:00
$qrCodeLink . addClass ( 'hidden' ) ;
2017-02-12 20:13:04 +00:00
viewButtonsDisplayed = false ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-08 19:12:22 +00:00
2017-02-17 21:26:39 +00:00
/ * *
* Hides all elements belonging to existing pastes
*
* @ name TopNav . hideAllButtons
* @ function
* /
me . hideAllButtons = function ( )
{
me . hideViewButtons ( ) ;
me . hideCreateButtons ( ) ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-17 21:26:39 +00:00
2017-02-12 17:08:08 +00:00
/ * *
* shows all elements needed when creating a new paste
*
2017-02-17 19:46:10 +00:00
* @ name TopNav . showCreateButtons
2017-02-12 17:08:08 +00:00
* @ function
* /
me . showCreateButtons = function ( )
{
2017-02-12 20:13:04 +00:00
if ( createButtonsDisplayed ) {
2017-10-30 06:04:59 +00:00
console . warn ( 'showCreateButtons: create buttons are already displayed' ) ;
2017-02-12 20:13:04 +00:00
return ;
}
2017-04-11 20:21:30 +00:00
$attach . removeClass ( 'hidden' ) ;
$burnAfterReadingOption . removeClass ( 'hidden' ) ;
2017-02-12 17:08:08 +00:00
$expiration . removeClass ( 'hidden' ) ;
$formatter . removeClass ( 'hidden' ) ;
$newButton . removeClass ( 'hidden' ) ;
2017-04-11 20:21:30 +00:00
$openDiscussionOption . removeClass ( 'hidden' ) ;
2017-02-12 17:08:08 +00:00
$password . removeClass ( 'hidden' ) ;
2017-04-11 20:21:30 +00:00
$sendButton . removeClass ( 'hidden' ) ;
2017-02-12 20:13:04 +00:00
createButtonsDisplayed = true ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-08 19:12:22 +00:00
2017-02-12 17:08:08 +00:00
/ * *
* shows all elements needed when creating a new paste
*
2017-02-17 19:46:10 +00:00
* @ name TopNav . hideCreateButtons
2017-02-12 17:08:08 +00:00
* @ function
* /
me . hideCreateButtons = function ( )
{
2017-02-12 20:13:04 +00:00
if ( ! createButtonsDisplayed ) {
2017-10-30 06:04:59 +00:00
console . warn ( 'hideCreateButtons: create buttons are already hidden' ) ;
2017-02-12 20:13:04 +00:00
return ;
}
2017-02-14 21:21:55 +00:00
$newButton . addClass ( 'hidden' ) ;
2017-02-12 17:08:08 +00:00
$sendButton . addClass ( 'hidden' ) ;
$expiration . addClass ( 'hidden' ) ;
$formatter . addClass ( 'hidden' ) ;
$burnAfterReadingOption . addClass ( 'hidden' ) ;
2017-02-13 10:35:04 +00:00
$openDiscussionOption . addClass ( 'hidden' ) ;
2017-02-12 17:08:08 +00:00
$password . addClass ( 'hidden' ) ;
$attach . addClass ( 'hidden' ) ;
2017-02-12 20:13:04 +00:00
2017-02-14 21:21:55 +00:00
createButtonsDisplayed = false ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-14 21:21:55 +00:00
/ * *
* only shows the "new paste" button
*
2017-02-17 19:46:10 +00:00
* @ name TopNav . showNewPasteButton
2017-02-14 21:21:55 +00:00
* @ function
* /
me . showNewPasteButton = function ( )
{
$newButton . removeClass ( 'hidden' ) ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-14 21:21:55 +00:00
2017-04-11 20:21:30 +00:00
/ * *
* only shows the "retry" button
*
* @ name TopNav . showRetryButton
* @ function
* /
me . showRetryButton = function ( )
{
$retryButton . removeClass ( 'hidden' ) ;
}
/ * *
* hides the "retry" button
*
* @ name TopNav . hideRetryButton
* @ function
* /
me . hideRetryButton = function ( )
{
$retryButton . addClass ( 'hidden' ) ;
}
2017-02-14 21:21:55 +00:00
/ * *
* only hides the clone button
*
* @ name TopNav . hideCloneButton
* @ function
* /
me . hideCloneButton = function ( )
{
$cloneButton . addClass ( 'hidden' ) ;
2018-01-06 08:26:10 +00:00
} ;
2012-04-23 14:30:02 +00:00
2017-02-12 17:08:08 +00:00
/ * *
2017-02-14 21:21:55 +00:00
* only hides the raw text button
2017-02-12 17:08:08 +00:00
*
2017-02-14 21:21:55 +00:00
* @ name TopNav . hideRawButton
2017-02-12 17:08:08 +00:00
* @ function
* /
2017-02-14 21:21:55 +00:00
me . hideRawButton = function ( )
2017-02-12 17:08:08 +00:00
{
2017-02-14 21:21:55 +00:00
$rawTextButton . addClass ( 'hidden' ) ;
2018-01-06 08:26:10 +00:00
} ;
2012-04-23 14:30:02 +00:00
2017-02-08 19:12:22 +00:00
/ * *
2017-02-15 21:59:55 +00:00
* hides the file selector in attachment
2017-02-08 19:12:22 +00:00
*
2017-02-15 21:59:55 +00:00
* @ name TopNav . hideFileSelector
2017-02-08 19:12:22 +00:00
* @ function
* /
2017-02-15 21:59:55 +00:00
me . hideFileSelector = function ( )
2017-02-08 19:11:04 +00:00
{
2017-02-15 21:59:55 +00:00
$fileWrap . addClass ( 'hidden' ) ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-12 17:08:08 +00:00
2017-02-15 21:59:55 +00:00
2017-02-12 17:08:08 +00:00
/ * *
2017-02-15 21:59:55 +00:00
* shows the custom attachment
2017-02-12 17:08:08 +00:00
*
2017-02-15 21:59:55 +00:00
* @ name TopNav . showCustomAttachment
2017-02-12 17:08:08 +00:00
* @ function
* /
2017-02-15 21:59:55 +00:00
me . showCustomAttachment = function ( )
2017-02-12 17:08:08 +00:00
{
2017-02-15 21:59:55 +00:00
$customAttachment . removeClass ( 'hidden' ) ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-13 20:12:00 +00:00
/ * *
2018-03-04 10:47:58 +00:00
* collapses the navigation bar , only if expanded
2017-02-13 20:12:00 +00:00
*
2017-02-14 21:21:55 +00:00
* @ name TopNav . collapseBar
2017-02-13 20:12:00 +00:00
* @ function
* /
me . collapseBar = function ( )
{
2018-03-04 12:19:49 +00:00
if ( $ ( '#navbar' ) . attr ( 'aria-expanded' ) === 'true' ) {
2018-03-03 06:55:27 +00:00
$ ( '.navbar-toggle' ) . click ( ) ;
2017-02-13 20:12:00 +00:00
}
2018-01-06 08:26:10 +00:00
} ;
2017-02-08 19:12:22 +00:00
2017-02-13 10:35:04 +00:00
/ * *
* returns the currently set expiration time
*
2017-02-14 21:21:55 +00:00
* @ name TopNav . getExpiration
2017-02-13 10:35:04 +00:00
* @ function
* @ return { int }
* /
me . getExpiration = function ( )
{
return pasteExpiration ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-13 10:35:04 +00:00
2017-02-13 20:12:00 +00:00
/ * *
* returns the currently selected file ( s )
*
2017-02-14 21:21:55 +00:00
* @ name TopNav . getFileList
2017-02-13 20:12:00 +00:00
* @ function
* @ return { FileList | null }
* /
me . getFileList = function ( )
{
var $file = $ ( '#file' ) ;
// if no file given, return null
if ( ! $file . length || ! $file [ 0 ] . files . length ) {
return null ;
}
2018-01-06 12:32:07 +00:00
// ensure the selected file is still accessible
2017-02-13 20:12:00 +00:00
if ( ! ( $file [ 0 ] . files && $file [ 0 ] . files [ 0 ] ) ) {
return null ;
}
return $file [ 0 ] . files ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-13 20:12:00 +00:00
/ * *
* returns the state of the burn after reading checkbox
*
2017-02-14 21:21:55 +00:00
* @ name TopNav . getExpiration
2017-02-13 20:12:00 +00:00
* @ function
* @ return { bool }
* /
me . getBurnAfterReading = function ( )
{
return $burnAfterReading . is ( ':checked' ) ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-13 20:12:00 +00:00
/ * *
* returns the state of the discussion checkbox
*
2017-02-14 21:21:55 +00:00
* @ name TopNav . getOpenDiscussion
2017-02-13 20:12:00 +00:00
* @ function
* @ return { bool }
* /
me . getOpenDiscussion = function ( )
{
return $openDiscussion . is ( ':checked' ) ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-13 20:12:00 +00:00
/ * *
* returns the entered password
*
2017-02-14 21:21:55 +00:00
* @ name TopNav . getPassword
2017-02-13 20:12:00 +00:00
* @ function
* @ return { string }
* /
me . getPassword = function ( )
{
return $passwordInput . val ( ) ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-13 20:12:00 +00:00
2017-02-15 21:59:55 +00:00
/ * *
* returns the element where custom attachments can be placed
*
* Used by AttachmentViewer when an attachment is cloned here .
*
* @ name TopNav . getCustomAttachment
* @ function
* @ return { jQuery }
* /
me . getCustomAttachment = function ( )
{
return $customAttachment ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-15 21:59:55 +00:00
2017-04-11 20:21:30 +00:00
/ * *
* Set a function to call when the retry button is clicked .
*
* @ name TopNav . setRetryCallback
* @ function
* @ param { function } callback
* /
me . setRetryCallback = function ( callback )
{
retryButtonCallback = callback ;
}
2017-02-12 17:08:08 +00:00
/ * *
* init navigation manager
*
* preloads jQuery elements
*
2017-02-14 21:21:55 +00:00
* @ name TopNav . init
2017-02-12 17:08:08 +00:00
* @ function
* /
me . init = function ( )
{
2017-02-08 19:12:22 +00:00
$attach = $ ( '#attach' ) ;
$burnAfterReading = $ ( '#burnafterreading' ) ;
$burnAfterReadingOption = $ ( '#burnafterreadingoption' ) ;
$cloneButton = $ ( '#clonebutton' ) ;
2017-02-15 21:59:55 +00:00
$customAttachment = $ ( '#customattachment' ) ;
2017-02-08 19:12:22 +00:00
$expiration = $ ( '#expiration' ) ;
$fileRemoveButton = $ ( '#fileremovebutton' ) ;
2017-02-17 21:46:18 +00:00
$fileWrap = $ ( '#filewrap' ) ;
2017-02-08 19:12:22 +00:00
$formatter = $ ( '#formatter' ) ;
$newButton = $ ( '#newbutton' ) ;
$openDiscussion = $ ( '#opendiscussion' ) ;
2017-02-17 21:46:18 +00:00
$openDiscussionOption = $ ( '#opendiscussionoption' ) ;
2017-02-12 20:13:04 +00:00
$password = $ ( '#password' ) ;
2017-02-13 20:12:00 +00:00
$passwordInput = $ ( '#passwordinput' ) ;
2017-02-08 19:12:22 +00:00
$rawTextButton = $ ( '#rawtextbutton' ) ;
2017-04-11 20:21:30 +00:00
$retryButton = $ ( '#retrybutton' ) ;
2017-02-08 19:12:22 +00:00
$sendButton = $ ( '#sendbutton' ) ;
2017-12-25 13:59:15 +00:00
$qrCodeLink = $ ( '#qrcodelink' ) ;
2013-11-01 00:15:14 +00:00
2017-02-12 17:08:08 +00:00
// bootstrap template drop down
2017-02-17 19:46:10 +00:00
$ ( '#language ul.dropdown-menu li a' ) . click ( setLanguage ) ;
2017-02-12 17:08:08 +00:00
// page template drop down
2017-02-17 19:46:10 +00:00
$ ( '#language select option' ) . click ( setLanguage ) ;
2017-02-12 17:08:08 +00:00
// bind events
$burnAfterReading . change ( changeBurnAfterReading ) ;
2017-02-13 10:35:04 +00:00
$openDiscussionOption . change ( changeOpenDiscussion ) ;
2017-02-15 21:59:55 +00:00
$newButton . click ( clickNewPaste ) ;
2017-03-13 19:24:18 +00:00
$sendButton . click ( PasteEncrypter . sendPaste ) ;
2017-02-14 21:21:55 +00:00
$cloneButton . click ( Controller . clonePaste ) ;
2017-02-12 20:13:04 +00:00
$rawTextButton . click ( rawText ) ;
2017-04-11 20:21:30 +00:00
$retryButton . click ( clickRetryButton ) ;
2017-02-15 21:59:55 +00:00
$fileRemoveButton . click ( removeAttachment ) ;
2017-12-25 13:59:15 +00:00
$qrCodeLink . click ( displayQrCode ) ;
2017-02-12 17:08:08 +00:00
2017-02-13 10:35:04 +00:00
// bootstrap template drop downs
$ ( 'ul.dropdown-menu li a' , $ ( '#expiration' ) . parent ( ) ) . click ( updateExpiration ) ;
$ ( 'ul.dropdown-menu li a' , $ ( '#formatter' ) . parent ( ) ) . click ( updateFormat ) ;
2017-02-12 17:08:08 +00:00
// initiate default state of checkboxes
changeBurnAfterReading ( ) ;
2017-02-13 10:35:04 +00:00
changeOpenDiscussion ( ) ;
// get default value from template or fall back to set value
2017-02-15 21:59:55 +00:00
pasteExpiration = Model . getExpirationDefault ( ) || pasteExpiration ;
2018-03-01 05:43:30 +00:00
createButtonsDisplayed = false ;
viewButtonsDisplayed = false ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-12 17:08:08 +00:00
return me ;
} ) ( window , document ) ;
/ * *
2017-02-13 20:12:00 +00:00
* Responsible for AJAX requests , transparently handles encryption …
2017-02-12 17:08:08 +00:00
*
2017-03-13 19:24:18 +00:00
* @ name Uploader
2017-02-12 17:08:08 +00:00
* @ class
* /
2017-02-14 21:21:55 +00:00
var Uploader = ( function ( ) {
2017-02-12 17:08:08 +00:00
var me = { } ;
2017-02-13 20:12:00 +00:00
var successFunc = null ,
2017-02-14 21:21:55 +00:00
failureFunc = null ,
url ,
data ,
2017-02-17 19:46:10 +00:00
symmetricKey ,
2017-02-13 20:12:00 +00:00
password ;
2017-02-14 21:21:55 +00:00
/ * *
* public variable ( 'constant' ) for errors to prevent magic numbers
*
2017-03-13 19:24:18 +00:00
* @ name Uploader . error
2017-02-14 21:21:55 +00:00
* @ readonly
* @ enum { Object }
* /
2017-02-13 20:12:00 +00:00
me . error = {
okay : 0 ,
custom : 1 ,
unknown : 2 ,
serverError : 3
} ;
2017-02-12 17:08:08 +00:00
/ * *
2017-02-13 20:12:00 +00:00
* ajaxHeaders to send in AJAX requests
2017-02-12 17:08:08 +00:00
*
2017-03-13 19:24:18 +00:00
* @ name Uploader . ajaxHeaders
2017-02-12 17:08:08 +00:00
* @ private
2017-02-13 20:12:00 +00:00
* @ readonly
2017-02-12 17:08:08 +00:00
* @ enum { Object }
* /
2017-02-13 20:12:00 +00:00
var ajaxHeaders = { 'X-Requested-With' : 'JSONHttpRequest' } ;
2017-02-12 17:08:08 +00:00
/ * *
2017-02-13 20:12:00 +00:00
* called after successful upload
2017-02-12 17:08:08 +00:00
*
2017-03-13 19:24:18 +00:00
* @ name Uploader . checkCryptParameters
2017-02-17 19:46:10 +00:00
* @ private
* @ function
* @ throws { string }
* /
function checkCryptParameters ( )
{
// workaround for this nasty 'bug' in ECMAScript
// see https://stackoverflow.com/questions/18808226/why-is-typeof-null-object
var typeOfKey = typeof symmetricKey ;
if ( symmetricKey === null ) {
typeOfKey = 'null' ;
}
// in case of missing preparation, throw error
switch ( typeOfKey ) {
case 'string' :
// already set, all right
return ;
case 'null' :
// needs to be generated auto-generate
symmetricKey = CryptTool . getSymmetricKey ( ) ;
break ;
default :
2018-08-04 20:30:01 +00:00
console . error ( 'current invalid symmetricKey: ' , symmetricKey ) ;
2017-02-17 19:46:10 +00:00
throw 'symmetricKey is invalid, probably the module was not prepared' ;
}
// password is optional
}
/ * *
* called after successful upload
*
2017-03-13 19:24:18 +00:00
* @ name Uploader . success
2017-02-17 19:46:10 +00:00
* @ private
2017-02-13 20:12:00 +00:00
* @ function
* @ param { int } status
2017-03-24 23:58:59 +00:00
* @ param { int } result - optional
2017-02-12 17:08:08 +00:00
* /
2017-02-13 20:12:00 +00:00
function success ( status , result )
{
// add useful data to result
2017-02-17 19:46:10 +00:00
result . encryptionKey = symmetricKey ;
2017-02-13 20:12:00 +00:00
result . requestData = data ;
if ( successFunc !== null ) {
successFunc ( status , result ) ;
}
}
2017-02-12 17:08:08 +00:00
/ * *
2017-02-13 20:12:00 +00:00
* called after a upload failure
2017-02-12 17:08:08 +00:00
*
2017-03-13 19:24:18 +00:00
* @ name Uploader . fail
2017-02-17 19:46:10 +00:00
* @ private
2017-02-13 20:12:00 +00:00
* @ function
* @ param { int } status - internal code
2017-03-24 23:58:59 +00:00
* @ param { int } result - original error code
2017-02-12 17:08:08 +00:00
* /
2017-02-13 20:12:00 +00:00
function fail ( status , result )
{
if ( failureFunc !== null ) {
failureFunc ( status , result ) ;
}
}
/ * *
* actually uploads the data
*
2017-02-14 21:21:55 +00:00
* @ name Uploader . run
2017-02-13 20:12:00 +00:00
* @ function
* /
2017-02-14 21:21:55 +00:00
me . run = function ( )
2017-02-13 20:12:00 +00:00
{
$ . ajax ( {
type : 'POST' ,
url : url ,
data : data ,
dataType : 'json' ,
headers : ajaxHeaders ,
success : function ( result ) {
if ( result . status === 0 ) {
success ( 0 , result ) ;
} else if ( result . status === 1 ) {
fail ( 1 , result ) ;
} else {
fail ( 2 , result ) ;
}
}
} )
. fail ( function ( jqXHR , textStatus , errorThrown ) {
console . error ( textStatus , errorThrown ) ;
fail ( 3 , jqXHR ) ;
} ) ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-13 20:12:00 +00:00
/ * *
* set success function
*
2017-03-13 20:11:26 +00:00
* @ name Uploader . setUrl
2017-02-14 21:21:55 +00:00
* @ function
2017-03-24 23:58:59 +00:00
* @ param { function } newUrl
2017-02-14 21:21:55 +00:00
* /
me . setUrl = function ( newUrl )
{
url = newUrl ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-14 21:21:55 +00:00
2017-02-17 19:46:10 +00:00
/ * *
* sets the password to use ( first value ) and optionally also the
* encryption key ( not recommend , it is automatically generated ) .
*
* Note : Call this after prepare ( ) as prepare ( ) resets these values .
*
* @ name Uploader . setCryptValues
* @ function
* @ param { string } newPassword
* @ param { string } newKey - optional
* /
me . setCryptParameters = function ( newPassword , newKey )
{
password = newPassword ;
if ( typeof newKey !== 'undefined' ) {
symmetricKey = newKey ;
}
2018-01-06 08:26:10 +00:00
} ;
2017-02-17 19:46:10 +00:00
2017-02-14 21:21:55 +00:00
/ * *
* set success function
*
* @ name Uploader . setSuccess
2017-02-13 20:12:00 +00:00
* @ function
* @ param { function } func
* /
me . setSuccess = function ( func )
{
successFunc = func ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-13 20:12:00 +00:00
/ * *
* set failure function
*
2017-03-13 20:11:26 +00:00
* @ name Uploader . setFailure
2017-02-13 20:12:00 +00:00
* @ function
* @ param { function } func
* /
me . setFailure = function ( func )
{
failureFunc = func ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-13 20:12:00 +00:00
/ * *
* prepares a new upload
*
2017-02-17 19:46:10 +00:00
* Call this when doing a new upload to reset any data from potential
* previous uploads . Must be called before any other method of this
* module .
*
2017-02-14 21:21:55 +00:00
* @ name Uploader . prepare
2017-02-13 20:12:00 +00:00
* @ function
* @ return { object }
* /
2017-02-17 19:46:10 +00:00
me . prepare = function ( )
2017-02-13 20:12:00 +00:00
{
2017-02-14 21:21:55 +00:00
// entropy should already be checked!
2017-02-13 20:12:00 +00:00
2017-02-17 19:46:10 +00:00
// reset password
password = '' ;
// reset key, so it a new one is generated when it is used
symmetricKey = null ;
2017-02-13 20:12:00 +00:00
// reset data
2017-02-14 21:21:55 +00:00
successFunc = null ;
failureFunc = null ;
2017-02-17 19:46:10 +00:00
url = Helper . baseUri ( ) ;
2017-02-13 20:12:00 +00:00
data = { } ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-13 20:12:00 +00:00
/ * *
* encrypts and sets the data
*
2017-02-14 21:21:55 +00:00
* @ name Uploader . setData
2018-10-08 19:03:10 +00:00
* @ async
2017-02-13 20:12:00 +00:00
* @ function
* @ param { string } index
* @ param { mixed } element
* /
2018-10-08 19:03:10 +00:00
me . setData = async function ( index , element )
2017-02-13 20:12:00 +00:00
{
2017-02-17 19:46:10 +00:00
checkCryptParameters ( ) ;
2018-10-08 19:03:10 +00:00
data [ index ] = await CryptTool . cipher ( symmetricKey , password , element ) ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-13 20:12:00 +00:00
/ * *
* set the additional metadata to send unencrypted
*
2017-02-14 21:21:55 +00:00
* @ name Uploader . setUnencryptedData
2017-02-13 20:12:00 +00:00
* @ function
* @ param { string } index
* @ param { mixed } element
* /
me . setUnencryptedData = function ( index , element )
{
data [ index ] = element ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-13 20:12:00 +00:00
/ * *
* set the additional metadata to send unencrypted passed at once
*
2017-02-14 21:21:55 +00:00
* @ name Uploader . setUnencryptedData
2017-02-13 20:12:00 +00:00
* @ function
* @ param { object } newData
* /
me . setUnencryptedBulkData = function ( newData )
{
$ . extend ( data , newData ) ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-13 20:12:00 +00:00
2017-02-17 19:46:10 +00:00
/ * *
* Helper , which parses shows a general error message based on the result of the Uploader
*
* @ name Uploader . parseUploadError
* @ function
* @ param { int } status
* @ param { object } data
* @ param { string } doThisThing - a human description of the action , which was tried
* @ return { array }
* /
me . parseUploadError = function ( status , data , doThisThing ) {
2017-03-24 23:58:59 +00:00
var errorArray ;
2017-02-17 19:46:10 +00:00
switch ( status ) {
2018-01-06 08:26:10 +00:00
case me . error . custom :
2017-02-17 19:46:10 +00:00
errorArray = [ 'Could not ' + doThisThing + ': %s' , data . message ] ;
break ;
2018-01-06 08:26:10 +00:00
case me . error . unknown :
2017-02-17 19:46:10 +00:00
errorArray = [ 'Could not ' + doThisThing + ': %s' , I18n . _ ( 'unknown status' ) ] ;
break ;
2018-01-06 08:26:10 +00:00
case me . error . serverError :
2017-03-24 23:58:59 +00:00
errorArray = [ 'Could not ' + doThisThing + ': %s' , I18n . _ ( 'server error or not responding' ) ] ;
break ;
2017-02-17 19:46:10 +00:00
default :
errorArray = [ 'Could not ' + doThisThing + ': %s' , I18n . _ ( 'unknown error' ) ] ;
break ;
}
return errorArray ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-17 19:46:10 +00:00
2017-02-13 20:12:00 +00:00
/ * *
2017-02-14 21:21:55 +00:00
* init Uploader
2017-02-13 20:12:00 +00:00
*
2017-02-14 21:21:55 +00:00
* @ name Uploader . init
2017-02-13 20:12:00 +00:00
* @ function
* /
me . init = function ( )
{
// nothing yet
2018-01-06 08:26:10 +00:00
} ;
2017-02-13 20:12:00 +00:00
return me ;
} ) ( ) ;
/ * *
2017-02-14 21:21:55 +00:00
* ( controller ) Responsible for encrypting paste and sending it to server .
2017-02-13 20:12:00 +00:00
*
2017-02-17 19:46:10 +00:00
* Does upload , encryption is done transparently by Uploader .
*
2017-03-13 19:24:18 +00:00
* @ name PasteEncrypter
2017-02-13 20:12:00 +00:00
* @ class
* /
2017-02-14 21:21:55 +00:00
var PasteEncrypter = ( function ( ) {
2017-02-13 20:12:00 +00:00
var me = { } ;
2017-02-14 21:21:55 +00:00
var requirementsChecked = false ;
2017-02-13 20:12:00 +00:00
/ * *
2017-02-17 19:46:10 +00:00
* called after successful paste upload
2017-02-13 20:12:00 +00:00
*
2017-03-13 19:24:18 +00:00
* @ name PasteEncrypter . showCreatedPaste
2017-02-14 21:21:55 +00:00
* @ private
2017-02-13 20:12:00 +00:00
* @ function
* @ param { int } status
2017-02-17 19:46:10 +00:00
* @ param { object } data
2017-02-13 20:12:00 +00:00
* /
function showCreatedPaste ( status , data ) {
2017-02-15 21:59:55 +00:00
Alert . hideLoading ( ) ;
2017-02-13 20:12:00 +00:00
2017-02-14 21:21:55 +00:00
var url = Helper . baseUri ( ) + '?' + data . id + '#' + data . encryptionKey ,
deleteUrl = Helper . baseUri ( ) + '?pasteid=' + data . id + '&deletetoken=' + data . deletetoken ;
2017-02-13 20:12:00 +00:00
2017-02-14 21:21:55 +00:00
Alert . hideMessages ( ) ;
2017-02-13 20:12:00 +00:00
// show notification
2018-01-06 08:26:10 +00:00
PasteStatus . createPasteNotification ( url , deleteUrl ) ;
2017-02-13 20:12:00 +00:00
// show new URL in browser bar
history . pushState ( { type : 'newpaste' } , document . title , url ) ;
2017-02-14 21:21:55 +00:00
TopNav . showViewButtons ( ) ;
TopNav . hideRawButton ( ) ;
Editor . hide ( ) ;
2017-02-13 20:12:00 +00:00
// parse and show text
2017-03-13 19:24:18 +00:00
// (preparation already done in me.sendPaste())
2017-02-14 21:21:55 +00:00
PasteViewer . run ( ) ;
}
2017-02-17 19:46:10 +00:00
/ * *
* called after successful comment upload
*
2017-03-13 19:24:18 +00:00
* @ name PasteEncrypter . showUploadedComment
2017-02-17 19:46:10 +00:00
* @ private
* @ function
* @ param { int } status
* @ param { object } data
* /
function showUploadedComment ( status , data ) {
// show success message
2018-01-06 12:32:07 +00:00
Alert . showStatus ( 'Comment posted.' ) ;
2017-02-17 19:46:10 +00:00
// reload paste
Controller . refreshPaste ( function ( ) {
// highlight sent comment
DiscussionViewer . highlightComment ( data . id , true ) ;
// reset error handler
Alert . setCustomHandler ( null ) ;
} ) ;
}
2017-02-14 21:21:55 +00:00
/ * *
* adds attachments to the Uploader
*
2017-03-13 19:24:18 +00:00
* @ name PasteEncrypter . encryptAttachments
2017-02-14 21:21:55 +00:00
* @ private
* @ function
* @ param { function } callback - excuted when action is successful
* /
2017-04-02 16:58:11 +00:00
function encryptAttachments ( callback ) {
2017-05-15 20:05:52 +00:00
var file = AttachmentViewer . getAttachmentData ( ) ;
2017-02-14 21:21:55 +00:00
2018-10-08 19:03:10 +00:00
let encryptAttachmentPromise , encryptAttachmentNamePromise ;
2017-04-02 16:58:11 +00:00
if ( typeof file !== 'undefined' && file !== null ) {
2017-05-15 20:05:52 +00:00
var fileName = AttachmentViewer . getFile ( ) . name ;
2017-02-14 21:21:55 +00:00
2018-10-08 19:03:10 +00:00
// run concurrently to encrypt everything
encryptAttachmentPromise = Uploader . setData ( 'attachment' , file ) ;
encryptAttachmentNamePromise = Uploader . setData ( 'attachmentname' , fileName ) ;
2017-02-14 21:21:55 +00:00
} else if ( AttachmentViewer . hasAttachment ( ) ) {
// fall back to cloned part
var attachment = AttachmentViewer . getAttachment ( ) ;
2018-10-08 19:03:10 +00:00
encryptAttachmentPromise = Uploader . setData ( 'attachment' , attachment [ 0 ] ) ;
encryptAttachmentNamePromise = Uploader . setData ( 'attachmentname' , attachment [ 1 ] ) ;
2017-02-14 21:21:55 +00:00
} else {
// if there are no attachments, this is of course still successful
2017-02-15 21:59:55 +00:00
return callback ( ) ;
2017-02-14 21:21:55 +00:00
}
2018-10-08 19:03:10 +00:00
// TODO: change this callback to also use Promises instead,
// this here just waits
2018-10-08 19:04:13 +00:00
return Promise . all ( [ encryptAttachmentPromise , encryptAttachmentNamePromise ] ) . then ( ( ) => {
2018-10-08 19:03:10 +00:00
// run callback
return callback ( ) ;
} ) ;
2017-02-13 20:12:00 +00:00
}
2017-02-12 17:08:08 +00:00
/ * *
* send a reply in a discussion
*
2017-02-14 21:21:55 +00:00
* @ name PasteEncrypter . sendComment
2018-10-20 15:57:21 +00:00
* @ async
2017-02-12 17:08:08 +00:00
* @ function
* /
2018-10-08 19:03:10 +00:00
me . sendComment = async function ( )
2017-02-12 17:08:08 +00:00
{
2017-02-17 19:46:10 +00:00
Alert . hideMessages ( ) ;
Alert . setCustomHandler ( DiscussionViewer . handleNotification ) ;
// UI loading state
2017-02-17 21:26:39 +00:00
TopNav . hideAllButtons ( ) ;
2018-01-06 12:32:07 +00:00
Alert . showLoading ( 'Sending comment…' , 'cloud-upload' ) ;
2017-02-17 19:46:10 +00:00
2017-12-18 13:47:17 +00:00
// get data
var plainText = DiscussionViewer . getReplyMessage ( ) ,
nickname = DiscussionViewer . getReplyNickname ( ) ,
2017-02-17 19:46:10 +00:00
parentid = DiscussionViewer . getReplyCommentId ( ) ;
// do not send if there is no data
if ( plainText . length === 0 ) {
// revert loading status…
Alert . hideLoading ( ) ;
Alert . setCustomHandler ( null ) ;
TopNav . showViewButtons ( ) ;
2017-02-12 17:08:08 +00:00
return ;
}
2017-02-17 19:46:10 +00:00
// prepare Uploader
Uploader . prepare ( ) ;
Uploader . setCryptParameters ( Prompt . getPassword ( ) , Model . getPasteKey ( ) ) ;
// set success/fail functions
Uploader . setSuccess ( showUploadedComment ) ;
Uploader . setFailure ( function ( status , data ) {
// revert loading status…
Alert . hideLoading ( ) ;
TopNav . showViewButtons ( ) ;
2018-10-20 09:40:37 +00:00
// …show error message…
2018-01-06 12:32:07 +00:00
Alert . showError (
Uploader . parseUploadError ( status , data , 'post comment' )
) ;
2017-02-17 20:48:21 +00:00
2018-10-20 09:40:37 +00:00
// …and reset error handler
2017-02-17 20:48:21 +00:00
Alert . setCustomHandler ( null ) ;
2017-02-12 17:08:08 +00:00
} ) ;
2017-02-17 19:46:10 +00:00
// fill it with unencrypted params
Uploader . setUnencryptedData ( 'pasteid' , Model . getPasteId ( ) ) ;
if ( typeof parentid === 'undefined' ) {
// if parent id is not set, this is the top-most comment, so use
2018-01-06 12:32:07 +00:00
// paste id as parent, as the root element of the discussion tree
2017-02-17 19:46:10 +00:00
Uploader . setUnencryptedData ( 'parentid' , Model . getPasteId ( ) ) ;
} else {
Uploader . setUnencryptedData ( 'parentid' , parentid ) ;
}
2018-10-20 09:40:37 +00:00
// start promises to encrypt data…
let dataPromises = [ ] ;
dataPromises . push ( Uploader . setData ( 'data' , plainText ) ) ;
if ( nickname . length > 0 ) {
dataPromises . push ( Uploader . setData ( 'nickname' , nickname ) ) ;
2018-08-04 20:30:01 +00:00
}
2017-02-17 19:46:10 +00:00
2018-10-20 09:40:37 +00:00
// …and upload when they are all done
Promise . all ( dataPromises ) . then ( ( ) => {
Uploader . run ( ) ;
} ) . catch ( ( e ) => {
Alert . showError ( e ) ;
} ) ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-12 17:08:08 +00:00
/ * *
2017-02-13 20:12:00 +00:00
* sends a new paste to server
2017-02-12 17:08:08 +00:00
*
2017-03-13 19:24:18 +00:00
* @ name PasteEncrypter . sendPaste
2018-10-20 15:57:21 +00:00
* @ async
2017-02-12 17:08:08 +00:00
* @ function
* /
2018-10-08 19:03:10 +00:00
me . sendPaste = async function ( )
2017-02-12 17:08:08 +00:00
{
2017-02-15 21:59:55 +00:00
// hide previous (error) messages
2017-02-17 19:46:10 +00:00
Controller . hideStatusMessages ( ) ;
2017-02-15 21:59:55 +00:00
2017-02-13 20:12:00 +00:00
// UI loading state
2017-02-17 21:26:39 +00:00
TopNav . hideAllButtons ( ) ;
2018-01-06 12:32:07 +00:00
Alert . showLoading ( 'Sending paste…' , 'cloud-upload' ) ;
2017-02-14 21:21:55 +00:00
TopNav . collapseBar ( ) ;
2017-02-12 17:08:08 +00:00
2017-02-13 20:12:00 +00:00
// get data
2017-02-14 21:21:55 +00:00
var plainText = Editor . getText ( ) ,
format = PasteViewer . getFormat ( ) ,
2018-05-21 17:32:01 +00:00
// the methods may return different values if no files are attached (null, undefined or false)
2017-05-15 20:05:52 +00:00
files = TopNav . getFileList ( ) || AttachmentViewer . getFile ( ) || AttachmentViewer . hasAttachment ( ) ;
2017-02-13 20:12:00 +00:00
// do not send if there is no data
2018-05-21 17:32:01 +00:00
if ( plainText . length === 0 && ! files ) {
2017-02-13 20:12:00 +00:00
// revert loading status…
2017-02-15 21:59:55 +00:00
Alert . hideLoading ( ) ;
2017-02-14 21:21:55 +00:00
TopNav . showCreateButtons ( ) ;
2017-02-12 17:08:08 +00:00
return ;
}
2017-02-14 21:21:55 +00:00
// prepare Uploader
2017-02-17 19:46:10 +00:00
Uploader . prepare ( ) ;
Uploader . setCryptParameters ( TopNav . getPassword ( ) ) ;
2017-02-13 20:12:00 +00:00
// set success/fail functions
2017-02-14 21:21:55 +00:00
Uploader . setSuccess ( showCreatedPaste ) ;
Uploader . setFailure ( function ( status , data ) {
2017-02-12 17:08:08 +00:00
// revert loading status…
2017-02-15 21:59:55 +00:00
Alert . hideLoading ( ) ;
2017-02-14 21:21:55 +00:00
TopNav . showCreateButtons ( ) ;
2017-02-13 20:12:00 +00:00
// show error message
2018-01-06 12:32:07 +00:00
Alert . showError (
Uploader . parseUploadError ( status , data , 'create paste' )
) ;
2017-02-12 17:08:08 +00:00
} ) ;
2017-02-13 20:12:00 +00:00
// fill it with unencrypted submitted options
2017-02-14 21:21:55 +00:00
Uploader . setUnencryptedBulkData ( {
expire : TopNav . getExpiration ( ) ,
2017-02-13 20:12:00 +00:00
formatter : format ,
2017-02-14 21:21:55 +00:00
burnafterreading : TopNav . getBurnAfterReading ( ) ? 1 : 0 ,
opendiscussion : TopNav . getOpenDiscussion ( ) ? 1 : 0
2017-02-13 20:12:00 +00:00
} ) ;
2017-02-12 17:08:08 +00:00
2017-02-13 20:12:00 +00:00
// prepare PasteViewer for later preview
2017-02-14 21:21:55 +00:00
PasteViewer . setText ( plainText ) ;
PasteViewer . setFormat ( format ) ;
// encrypt attachments
2018-10-08 19:03:10 +00:00
const encryptAttachmentsPromise = encryptAttachments (
2017-02-14 21:21:55 +00:00
function ( ) {
2018-10-08 19:03:10 +00:00
// TODO: remove, is not needed anymore as we use Promises
2017-02-14 21:21:55 +00:00
}
) ;
2018-10-08 19:03:10 +00:00
// encrypt plain text
const encryptDataPromise = Uploader . setData ( 'data' , plainText ) ;
await Promise . all ( [ encryptAttachmentsPromise , encryptDataPromise ] ) . catch ( Alert . showError ) ;
// send data
Uploader . run ( ) ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-14 21:21:55 +00:00
/ * *
* initialize
*
* @ name PasteEncrypter . init
* @ function
* /
me . init = function ( )
{
// nothing yet
2018-01-06 08:26:10 +00:00
} ;
2017-02-14 21:21:55 +00:00
return me ;
} ) ( ) ;
/ * *
* ( controller ) Responsible for decrypting cipherdata and passing data to view .
*
2017-02-17 19:46:10 +00:00
* Only decryption , no download .
*
2017-03-13 19:24:18 +00:00
* @ name PasteDecrypter
2017-02-14 21:21:55 +00:00
* @ class
* /
var PasteDecrypter = ( function ( ) {
var me = { } ;
/ * *
2017-04-11 14:34:13 +00:00
* decrypt data or prompts for password in case of failure
2017-02-14 21:21:55 +00:00
*
2017-03-13 19:24:18 +00:00
* @ name PasteDecrypter . decryptOrPromptPassword
2017-02-14 21:21:55 +00:00
* @ private
2018-10-20 07:56:05 +00:00
* @ async
2017-02-14 21:21:55 +00:00
* @ function
2017-03-13 19:24:18 +00:00
* @ param { string } key
* @ param { string } password - optional , may be an empty string
* @ param { string } cipherdata
2017-02-17 19:46:10 +00:00
* @ throws { string }
2017-03-13 19:24:18 +00:00
* @ return { false | string } false , when unsuccessful or string ( decrypted data )
2017-02-14 21:21:55 +00:00
* /
2018-10-20 07:56:05 +00:00
async function decryptOrPromptPassword ( key , password , cipherdata )
2017-02-14 21:21:55 +00:00
{
// try decryption without password
2018-10-20 15:57:21 +00:00
const plaindata = await CryptTool . decipher ( key , password , cipherdata ) ;
2017-02-14 21:21:55 +00:00
// if it fails, request password
2017-02-17 19:46:10 +00:00
if ( plaindata . length === 0 && password . length === 0 ) {
2017-04-11 14:34:13 +00:00
// show prompt
Prompt . requestPassword ( ) ;
// Thus, we cannot do anything yet, we need to wait for the user
// input.
return false ;
2017-02-14 21:21:55 +00:00
}
2017-02-17 19:46:10 +00:00
// if all tries failed, we can only return an error
if ( plaindata . length === 0 ) {
throw 'failed to decipher data' ;
}
return plaindata ;
}
/ * *
* decrypt the actual paste text
*
2017-04-11 14:34:13 +00:00
* @ name PasteDecrypter . decryptPaste
2017-02-17 19:46:10 +00:00
* @ private
2018-10-20 10:40:08 +00:00
* @ async
2017-02-17 19:46:10 +00:00
* @ function
2017-03-13 19:24:18 +00:00
* @ param { object } paste - paste data in object form
* @ param { string } key
* @ param { string } password
* @ param { bool } ignoreError - ignore decryption errors iof set to true
2017-02-17 19:46:10 +00:00
* @ throws { string }
2018-10-20 10:40:08 +00:00
* @ return { Promise }
2017-02-17 19:46:10 +00:00
* /
2018-10-20 10:40:08 +00:00
async function decryptPaste ( paste , key , password , ignoreError )
2017-02-17 19:46:10 +00:00
{
2018-10-20 07:56:05 +00:00
let decyptionPromise ;
2017-02-17 19:46:10 +00:00
if ( ignoreError === true ) {
2018-10-20 07:56:05 +00:00
decyptionPromise = CryptTool . decipher ( key , password , paste . data ) ;
2017-02-17 19:46:10 +00:00
} else {
2018-10-20 07:56:05 +00:00
decyptionPromise = decryptOrPromptPassword ( key , password , paste . data ) ;
}
2018-10-20 10:40:08 +00:00
return decyptionPromise . then ( ( plaintext ) => {
2018-10-20 08:20:32 +00:00
if ( plaintext !== false ) {
// on success show paste
PasteViewer . setFormat ( paste . meta . formatter ) ;
PasteViewer . setText ( plaintext ) ;
// trigger to show the text (attachment loaded afterwards)
PasteViewer . run ( ) ;
2017-02-17 19:46:10 +00:00
}
2018-10-20 07:56:05 +00:00
} ) . catch ( ( err ) => {
2018-10-20 09:40:37 +00:00
displayDecryptionError ( 'failed to decipher paste text: ' + err ) ;
2018-10-20 07:56:05 +00:00
} ) ;
2017-02-14 21:21:55 +00:00
}
/ * *
* decrypts any attachment
*
2017-03-13 19:24:18 +00:00
* @ name PasteDecrypter . decryptAttachment
2017-02-14 21:21:55 +00:00
* @ private
2018-10-20 10:40:08 +00:00
* @ async
2017-02-14 21:21:55 +00:00
* @ function
2017-03-13 19:24:18 +00:00
* @ param { object } paste - paste data in object form
* @ param { string } key
* @ param { string } password
2017-02-17 19:46:10 +00:00
* @ throws { string }
2018-10-20 10:40:08 +00:00
* @ return { Promise }
2017-02-14 21:21:55 +00:00
* /
2018-10-20 10:40:08 +00:00
async function decryptAttachment ( paste , key , password )
2017-02-14 21:21:55 +00:00
{
2018-10-20 15:57:21 +00:00
const attachmentPromise = decryptOrPromptPassword ( key , password , paste . attachment ) ,
attachmentNamePromise = decryptOrPromptPassword ( key , password , paste . attachmentname ) ;
2018-10-20 07:56:05 +00:00
attachmentPromise . catch ( ( err ) => {
2018-10-20 09:40:37 +00:00
displayDecryptionError ( 'failed to decipher attachment: ' + err ) ;
2018-10-20 07:56:05 +00:00
} )
attachmentNamePromise . catch ( ( err ) => {
2018-10-20 09:40:37 +00:00
displayDecryptionError ( 'failed to decipher attachment name: ' + err ) ;
2018-10-20 07:56:05 +00:00
} )
2018-10-20 10:40:08 +00:00
return Promise . all ( [ attachmentPromise , attachmentNamePromise ] ) . then ( ( results ) => {
2018-10-20 08:20:32 +00:00
if ( ! results . some ( ( result ) => {
2018-10-20 07:56:05 +00:00
return result === false ;
} ) ) {
2018-10-20 08:20:32 +00:00
AttachmentViewer . setAttachment ( results [ 0 ] , results [ 1 ] ) ;
AttachmentViewer . showAttachment ( ) ;
2017-02-14 21:21:55 +00:00
}
2018-10-20 08:20:32 +00:00
} ) ;
2017-02-17 19:46:10 +00:00
}
/ * *
* decrypts all comments and shows them
*
2017-03-13 19:24:18 +00:00
* @ name PasteDecrypter . decryptComments
2017-02-17 19:46:10 +00:00
* @ private
2018-10-20 10:40:08 +00:00
* @ async
2017-02-17 19:46:10 +00:00
* @ function
2017-03-13 19:24:18 +00:00
* @ param { object } paste - paste data in object form
* @ param { string } key
* @ param { string } password
2018-10-20 10:40:08 +00:00
* @ return { Promise }
2017-02-17 19:46:10 +00:00
* /
2018-10-20 10:40:08 +00:00
async function decryptComments ( paste , key , password )
2017-02-17 19:46:10 +00:00
{
2018-10-20 08:20:32 +00:00
// remove potential previous discussion
2017-12-18 13:47:17 +00:00
DiscussionViewer . prepareNewDiscussion ( ) ;
2017-02-17 19:46:10 +00:00
2018-10-20 08:20:32 +00:00
let commentDecryptionPromises = [ ] ;
2017-02-17 19:46:10 +00:00
// iterate over comments
2018-10-20 15:57:21 +00:00
for ( let i = 0 ; i < paste . comments . length ; ++ i ) {
2018-10-20 09:40:37 +00:00
commentDecryptionPromises . push (
2018-10-20 11:54:17 +00:00
Promise . all ( [
CryptTool . decipher ( key , password , paste . comments [ i ] . data ) ,
paste . comments [ i ] . meta . nickname ?
CryptTool . decipher ( key , password , paste . comments [ i ] . meta . nickname ) :
Promise . resolve ( '' )
] )
2017-02-17 19:46:10 +00:00
) ;
}
2018-10-20 10:40:08 +00:00
return Promise . all ( commentDecryptionPromises ) . then ( ( plaintexts ) => {
2018-10-20 15:57:21 +00:00
for ( let i = 0 ; i < paste . comments . length ; ++ i ) {
2018-10-20 11:54:17 +00:00
if ( plaintexts [ i ] [ 0 ] . length === 0 ) {
2018-10-20 09:40:37 +00:00
continue ;
}
2018-10-20 15:57:21 +00:00
const comment = paste . comments [ i ] ;
2018-10-20 08:20:32 +00:00
DiscussionViewer . addComment (
comment ,
2018-10-20 11:54:17 +00:00
plaintexts [ i ] [ 0 ] ,
plaintexts [ i ] [ 1 ]
2018-10-20 08:20:32 +00:00
) ;
}
DiscussionViewer . finishDiscussion ( ) ;
} ) ;
2017-02-14 21:21:55 +00:00
}
2018-10-20 09:40:37 +00:00
/ * *
* displays and logs decryption errors
*
* @ name PasteDecrypter . displayDecryptionError
* @ private
* @ function
* @ param { string } message
* /
function displayDecryptionError ( message )
{
Alert . hideLoading ( ) ;
// log detailed error, but display generic translation
console . error ( message ) ;
Alert . showError ( 'Could not decrypt data. Did you enter a wrong password? Retry with the button at the top.' ) ;
// reset password, so it can be re-entered
Prompt . reset ( ) ;
TopNav . showRetryButton ( ) ;
}
2017-02-14 21:21:55 +00:00
/ * *
* show decrypted text in the display area , including discussion ( if open )
*
* @ name PasteDecrypter . run
* @ function
* @ param { Object } [ paste ] - ( optional ) object including comments to display ( items = array with keys ( 'data' , 'meta' ) )
* /
me . run = function ( paste )
{
2017-03-12 15:06:17 +00:00
Alert . hideMessages ( ) ;
2018-01-06 12:32:07 +00:00
Alert . showLoading ( 'Decrypting paste…' , 'cloud-download' ) ;
2017-02-14 21:21:55 +00:00
if ( typeof paste === 'undefined' ) {
2017-04-11 14:34:13 +00:00
// get cipher data and wait until it is available
Model . getPasteData ( me . run ) ;
return ;
2017-02-14 21:21:55 +00:00
}
2018-10-20 09:40:37 +00:00
let key = Model . getPasteKey ( ) ,
password = Prompt . getPassword ( ) ,
2018-10-20 15:57:21 +00:00
decryptionPromises = [ ] ;
2017-02-14 21:21:55 +00:00
2018-10-20 09:40:37 +00:00
TopNav . setRetryCallback ( function ( ) {
TopNav . hideRetryButton ( ) ;
me . run ( paste ) ;
} ) ;
// decrypt attachments
if ( paste . attachment && AttachmentViewer . hasAttachmentData ( ) ) {
// try to decrypt paste and if it fails (because the password is
// missing) return to let JS continue and wait for user
2018-10-20 15:57:21 +00:00
decryptionPromises . push (
2018-10-20 07:56:05 +00:00
decryptAttachment ( paste , key , password ) . then ( ( attachementIsDecrypted ) => {
if ( attachementIsDecrypted ) {
// ignore empty paste, as this is allowed when pasting attachments
2018-10-20 09:40:37 +00:00
return decryptPaste ( paste , key , password , true ) ;
2017-05-13 19:27:41 +00:00
}
2018-10-20 09:40:37 +00:00
} )
) ;
} else {
2018-10-20 15:57:21 +00:00
decryptionPromises . push ( decryptPaste ( paste , key , password ) )
2018-10-20 09:40:37 +00:00
}
2017-03-13 18:30:44 +00:00
2018-10-20 09:40:37 +00:00
// if the discussion is opened on this paste, display it
if ( paste . meta . opendiscussion ) {
2018-10-20 15:57:21 +00:00
decryptionPromises . push ( decryptComments ( paste , key , password ) ) ;
2018-10-20 09:40:37 +00:00
}
2017-03-13 18:30:44 +00:00
2018-10-20 11:54:17 +00:00
// shows the remaining time (until) deletion
PasteStatus . showRemainingTime ( paste . meta ) ;
2018-10-20 15:57:21 +00:00
Promise . all ( decryptionPromises ) . then ( ( ) => {
2017-03-13 18:30:44 +00:00
Alert . hideLoading ( ) ;
TopNav . showViewButtons ( ) ;
2018-10-20 09:40:37 +00:00
} ) ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-13 20:12:00 +00:00
2017-02-14 21:21:55 +00:00
/ * *
* initialize
*
* @ name PasteDecrypter . init
* @ function
* /
me . init = function ( )
{
// nothing yet
2018-01-06 08:26:10 +00:00
} ;
2017-02-12 17:08:08 +00:00
2017-02-14 21:21:55 +00:00
return me ;
} ) ( ) ;
2013-11-01 00:15:14 +00:00
2017-01-29 13:32:55 +00:00
/ * *
2017-02-14 21:21:55 +00:00
* ( controller ) main PrivateBin logic
*
2017-03-13 19:24:18 +00:00
* @ name Controller
2017-02-14 21:21:55 +00:00
* @ param { object } window
* @ param { object } document
* @ class
2017-01-29 13:32:55 +00:00
* /
2017-02-14 21:21:55 +00:00
var Controller = ( function ( window , document ) {
var me = { } ;
2017-02-15 21:59:55 +00:00
/ * *
* hides all status messages no matter which module showed them
*
* @ name Controller . hideStatusMessages
* @ function
* /
me . hideStatusMessages = function ( )
{
PasteStatus . hideMessages ( ) ;
Alert . hideMessages ( ) ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-15 21:59:55 +00:00
2017-02-12 20:13:04 +00:00
/ * *
* creates a new paste
*
2017-02-14 21:21:55 +00:00
* @ name Controller . newPaste
2017-02-12 20:13:04 +00:00
* @ function
* /
me . newPaste = function ( )
{
2017-02-17 19:46:10 +00:00
// Important: This *must not* run Alert.hideMessages() as previous
// errors from viewing a paste should be shown.
2017-02-17 21:26:39 +00:00
TopNav . hideAllButtons ( ) ;
2018-01-06 12:32:07 +00:00
Alert . showLoading ( 'Preparing new paste…' , 'time' ) ;
2017-02-15 21:59:55 +00:00
2017-02-17 19:46:10 +00:00
PasteStatus . hideMessages ( ) ;
2017-02-14 21:21:55 +00:00
PasteViewer . hide ( ) ;
Editor . resetInput ( ) ;
Editor . show ( ) ;
Editor . focusInput ( ) ;
2017-05-13 19:27:41 +00:00
AttachmentViewer . removeAttachment ( ) ;
2017-02-15 21:59:55 +00:00
TopNav . showCreateButtons ( ) ;
Alert . hideLoading ( ) ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-12 20:13:04 +00:00
2017-04-11 14:34:13 +00:00
/ * *
* shows how we much we love bots that execute JS ; )
*
* @ name Controller . showBadBotMessage
* @ function
* /
me . showBadBotMessage = function ( )
{
TopNav . hideAllButtons ( ) ;
Alert . showError ( 'I love you too, bot…' ) ;
}
2017-02-17 19:46:10 +00:00
/ * *
* shows the loaded paste
*
* @ name Controller . showPaste
* @ function
* /
me . showPaste = function ( )
{
try {
Model . getPasteKey ( ) ;
} catch ( err ) {
console . error ( err ) ;
// missing decryption key (or paste ID) in URL?
if ( window . location . hash . length === 0 ) {
Alert . showError ( 'Cannot decrypt paste: Decryption key missing in URL (Did you use a redirector or an URL shortener which strips part of the URL?)' ) ;
return ;
}
}
// show proper elements on screen
PasteDecrypter . run ( ) ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-17 19:46:10 +00:00
/ * *
* refreshes the loaded paste to show potential new data
*
* @ name Controller . refreshPaste
* @ function
2017-03-13 19:24:18 +00:00
* @ param { function } callback
2017-02-17 19:46:10 +00:00
* /
me . refreshPaste = function ( callback )
{
// save window position to restore it later
var orgPosition = $ ( window ) . scrollTop ( ) ;
2017-04-11 14:34:13 +00:00
Model . getPasteData ( function ( data ) {
2018-04-30 18:01:38 +00:00
Uploader . prepare ( ) ;
Uploader . setUrl ( Helper . baseUri ( ) + '?' + Model . getPasteId ( ) ) ;
2017-02-17 19:46:10 +00:00
2018-04-30 18:01:38 +00:00
Uploader . setFailure ( function ( status , data ) {
// revert loading status…
Alert . hideLoading ( ) ;
TopNav . showViewButtons ( ) ;
2017-02-17 19:46:10 +00:00
2018-04-30 18:01:38 +00:00
// show error message
Alert . showError (
Uploader . parseUploadError ( status , data , 'refresh display' )
) ;
} ) ;
Uploader . setSuccess ( function ( status , data ) {
PasteDecrypter . run ( data ) ;
2017-02-17 19:46:10 +00:00
2018-04-30 18:01:38 +00:00
// restore position
window . scrollTo ( 0 , orgPosition ) ;
2017-04-11 14:34:13 +00:00
2018-04-30 18:01:38 +00:00
// NOTE: could create problems as callback may be called
// asyncronously if PasteDecrypter e.g. needs to wait for a
// password being entered
callback ( ) ;
} ) ;
Uploader . run ( ) ;
2017-04-11 14:34:13 +00:00
} , false ) ; // this false is important as it circumvents the cache
2017-02-25 08:35:55 +00:00
}
2017-02-17 19:46:10 +00:00
2017-02-12 17:08:08 +00:00
/ * *
* clone the current paste
*
2017-02-14 21:21:55 +00:00
* @ name Controller . clonePaste
2017-02-12 17:08:08 +00:00
* @ function
* /
2018-01-06 12:32:07 +00:00
me . clonePaste = function ( )
2017-02-12 17:08:08 +00:00
{
2017-02-15 21:59:55 +00:00
TopNav . collapseBar ( ) ;
2017-02-17 21:26:39 +00:00
TopNav . hideAllButtons ( ) ;
2017-02-15 21:59:55 +00:00
// hide messages from previous paste
me . hideStatusMessages ( ) ;
2017-02-12 17:08:08 +00:00
// erase the id and the key in url
2017-02-15 21:59:55 +00:00
history . pushState ( { type : 'clone' } , document . title , Helper . baseUri ( ) ) ;
2017-02-12 17:08:08 +00:00
2017-02-15 21:59:55 +00:00
if ( AttachmentViewer . hasAttachment ( ) ) {
AttachmentViewer . moveAttachmentTo (
TopNav . getCustomAttachment ( ) ,
'Cloned: \'%s\''
) ;
TopNav . hideFileSelector ( ) ;
AttachmentViewer . hideAttachment ( ) ;
// NOTE: it also looks nice without removing the attachment
// but for a consistent display we remove it…
AttachmentViewer . hideAttachmentPreview ( ) ;
TopNav . showCustomAttachment ( ) ;
// show another status message to make the user aware that the
// file was cloned too!
Alert . showStatus (
[
'The cloned file \'%s\' was attached to this paste.' ,
AttachmentViewer . getAttachment ( ) [ 1 ]
2018-01-06 12:32:07 +00:00
] ,
'copy'
) ;
2017-02-15 21:59:55 +00:00
}
2018-01-06 09:57:54 +00:00
Editor . setText ( PasteViewer . getText ( ) ) ;
2017-02-15 21:59:55 +00:00
PasteViewer . hide ( ) ;
Editor . show ( ) ;
TopNav . showCreateButtons ( ) ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-12 17:08:08 +00:00
/ * *
* application start
*
2017-02-14 21:21:55 +00:00
* @ name Controller . init
2017-02-12 17:08:08 +00:00
* @ function
* /
me . init = function ( )
{
// first load translations
2017-02-14 21:21:55 +00:00
I18n . loadTranslations ( ) ;
2017-02-12 17:08:08 +00:00
2018-01-01 09:25:07 +00:00
DOMPurify . setConfig ( { SAFE _FOR _JQUERY : true } ) ;
2017-02-12 20:13:04 +00:00
// initialize other modules/"classes"
2017-02-14 21:21:55 +00:00
Alert . init ( ) ;
2017-02-15 21:59:55 +00:00
Model . init ( ) ;
2017-02-17 21:46:18 +00:00
AttachmentViewer . init ( ) ;
DiscussionViewer . init ( ) ;
2017-02-14 21:21:55 +00:00
Editor . init ( ) ;
2017-02-17 21:46:18 +00:00
PasteDecrypter . init ( ) ;
PasteEncrypter . init ( ) ;
2017-02-14 21:21:55 +00:00
PasteStatus . init ( ) ;
PasteViewer . init ( ) ;
Prompt . init ( ) ;
2017-02-17 21:46:18 +00:00
TopNav . init ( ) ;
UiHelper . init ( ) ;
Uploader . init ( ) ;
2017-02-08 19:11:04 +00:00
2017-04-11 14:34:13 +00:00
// check whether existing paste needs to be shown
try {
Model . getPasteId ( ) ;
} catch ( e ) {
// otherwise create a new paste
return me . newPaste ( ) ;
}
2018-05-22 09:41:35 +00:00
// if delete token is passed (i.e. paste has been deleted by this access)
// there is no more stuf we need to do
if ( Model . hasDeleteToken ( ) ) {
return ;
}
2017-04-11 14:34:13 +00:00
// prevent bots from viewing a paste and potentially deleting data
// when burn-after-reading is set
// see https://github.com/elrido/ZeroBin/issues/11
if ( Helper . isBadBot ( ) ) {
return me . showBadBotMessage ( ) ;
2017-02-13 20:12:00 +00:00
}
2018-04-30 18:01:38 +00:00
// display an existing paste
2017-04-11 14:34:13 +00:00
return me . showPaste ( ) ;
2017-02-25 08:35:55 +00:00
}
2017-02-08 19:12:22 +00:00
return me ;
} ) ( window , document ) ;
2017-01-29 13:32:55 +00:00
2017-01-22 09:42:11 +00:00
return {
2017-02-14 21:21:55 +00:00
Helper : Helper ,
I18n : I18n ,
CryptTool : CryptTool ,
2017-02-17 21:46:18 +00:00
Model : Model ,
UiHelper : UiHelper ,
2017-02-14 21:21:55 +00:00
Alert : Alert ,
2017-02-17 21:46:18 +00:00
PasteStatus : PasteStatus ,
Prompt : Prompt ,
Editor : Editor ,
2017-02-17 19:46:10 +00:00
PasteViewer : PasteViewer ,
2017-02-17 21:46:18 +00:00
AttachmentViewer : AttachmentViewer ,
DiscussionViewer : DiscussionViewer ,
TopNav : TopNav ,
Uploader : Uploader ,
PasteEncrypter : PasteEncrypter ,
PasteDecrypter : PasteDecrypter ,
Controller : Controller
2017-01-22 09:42:11 +00:00
} ;
2018-10-20 17:53:21 +00:00
} ) ( jQuery , RawDeflate ) ;