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}
2024-05-05 18:43:33 +00:00
* @ version 1.7 . 2
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
2019-09-22 19:18:19 +00:00
// global Base64, DOMPurify, FileReader, RawDeflate, history, navigator, prettyPrint, prettyPrintOne, showdown, kjua
2019-08-15 23:28:42 +00:00
jQuery . fn . draghover = function ( ) {
2019-09-14 07:41:52 +00:00
'use strict' ;
2019-08-15 23:28:42 +00:00
return this . each ( function ( ) {
let collection = $ ( ) ,
self = $ ( this ) ;
2020-04-23 09:25:24 +00:00
2019-08-15 23:28:42 +00:00
self . on ( 'dragenter' , function ( e ) {
if ( collection . length === 0 ) {
self . trigger ( 'draghoverstart' ) ;
}
collection = collection . add ( e . target ) ;
} ) ;
2020-04-23 09:25:24 +00:00
2019-08-15 23:28:42 +00:00
self . on ( 'dragleave drop' , function ( e ) {
collection = collection . not ( e . target ) ;
if ( collection . length === 0 ) {
self . trigger ( 'draghoverend' ) ;
}
} ) ;
} ) ;
} ;
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' ;
2018-12-27 20:32:13 +00:00
/ * *
* zlib library interface
*
* @ private
* /
let z ;
2022-03-13 18:56:12 +00:00
/ * *
* DOMpurify settings for HTML content
*
* @ private
* /
const purifyHtmlConfig = {
ALLOWED _URI _REGEXP : /^(?:(?:(?:f|ht)tps?|mailto|magnet):)/i ,
SAFE _FOR _JQUERY : true ,
USE _PROFILES : {
html : true
}
} ;
/ * *
* DOMpurify settings for SVG content
*
* @ private
* /
const purifySvgConfig = {
USE _PROFILES : {
svg : true ,
svgFilters : true
}
} ;
2024-01-27 17:26:19 +00:00
/ * *
* URL fragment prefix requiring load confirmation
*
* @ private
* /
2024-02-06 19:22:47 +00:00
const loadConfirmPrefix = '#-' ;
2024-01-27 17:26:19 +00:00
2019-05-25 11:20:39 +00:00
/ * *
* CryptoData class
*
2023-12-03 12:41:17 +00:00
* bundles helper functions used in both paste and comment formats
2019-05-25 11:20:39 +00:00
*
* @ name CryptoData
* @ class
* /
function CryptoData ( data ) {
this . v = 1 ;
// store all keys in the default locations for drop-in replacement
for ( let key in data ) {
this [ key ] = data [ key ] ;
}
/ * *
* gets the cipher data ( cipher text + adata )
*
2023-12-03 12:41:17 +00:00
* @ name CryptoData . getCipherData
2019-05-25 11:20:39 +00:00
* @ function
* @ return { Array } | { string }
* /
this . getCipherData = function ( )
{
return this . v === 1 ? this . data : [ this . ct , this . adata ] ;
}
}
2019-05-25 08:10:59 +00:00
/ * *
* Paste class
2019-05-25 11:20:39 +00:00
*
2023-12-03 12:41:17 +00:00
* bundles helper functions around the paste formats
2019-05-25 08:10:59 +00:00
*
* @ name Paste
* @ class
* /
function Paste ( data ) {
2019-05-25 11:20:39 +00:00
// inherit constructor and methods of CryptoData
CryptoData . call ( this , data ) ;
/ * *
* gets the used formatter
*
* @ name Paste . getFormat
* @ function
* @ return { string }
* /
this . getFormat = function ( )
{
return this . v === 1 ? this . meta . formatter : this . adata [ 1 ] ;
}
/ * *
* gets the remaining seconds before the paste expires
*
* returns 0 if there is no expiration
*
* @ name Paste . getTimeToLive
* @ function
* @ return { string }
* /
this . getTimeToLive = function ( )
{
return ( this . v === 1 ? this . meta . remaining _time : this . meta . time _to _live ) || 0 ;
}
/ * *
* is burn - after - reading enabled
*
* @ name Paste . isBurnAfterReadingEnabled
* @ function
* @ return { bool }
* /
this . isBurnAfterReadingEnabled = function ( )
{
return ( this . v === 1 ? this . meta . burnafterreading : this . adata [ 3 ] ) ;
}
/ * *
* are discussions enabled
*
* @ name Paste . isDiscussionEnabled
* @ function
* @ return { bool }
* /
this . isDiscussionEnabled = function ( )
{
return ( this . v === 1 ? this . meta . opendiscussion : this . adata [ 2 ] ) ;
}
}
/ * *
* Comment class
*
2023-12-03 12:41:17 +00:00
* bundles helper functions around the comment formats
2019-05-25 11:20:39 +00:00
*
* @ name Comment
* @ class
* /
function Comment ( data ) {
// inherit constructor and methods of CryptoData
CryptoData . call ( this , data ) ;
/ * *
* gets the UNIX timestamp of the comment creation
*
2023-12-03 12:41:17 +00:00
* @ name Comment . getCreated
2019-05-25 11:20:39 +00:00
* @ function
* @ return { int }
* /
this . getCreated = function ( )
{
2024-05-04 12:38:41 +00:00
return this . meta [ this . v === 1 ? 'postdate' : 'created' ] || 0 ;
2019-05-25 11:20:39 +00:00
}
/ * *
* gets the icon of the comment submitter
*
2023-12-03 12:41:17 +00:00
* @ name Comment . getIcon
2019-05-25 11:20:39 +00:00
* @ function
* @ return { string }
* /
this . getIcon = function ( )
{
return this . meta [ this . v === 1 ? 'vizhash' : 'icon' ] || '' ;
2019-05-25 08:10:59 +00:00
}
}
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
* /
2018-12-29 17:40:59 +00:00
const Helper = ( function ( ) {
const me = { } ;
2017-02-08 19:12:22 +00:00
2020-01-18 06:30:01 +00:00
/ * *
* character to HTML entity lookup table
*
* @ see { @ link https : //github.com/janl/mustache.js/blob/master/mustache.js#L60}
* @ name Helper . entityMap
* @ private
* @ enum { Object }
* @ readonly
* /
2020-02-02 06:08:38 +00:00
const entityMap = {
2020-01-18 06:30:01 +00:00
'&' : '&' ,
'<' : '<' ,
'>' : '>' ,
'"' : '"' ,
2024-01-08 09:36:59 +00:00
'\'' : ''' ,
2020-01-18 06:30:01 +00:00
'/' : '/' ,
'`' : '`' ,
'=' : '='
} ;
2020-03-01 07:54:48 +00:00
/ * *
* number of seconds in a minute
*
* @ name Helper . minute
* @ private
* @ enum { number }
* @ readonly
* /
const minute = 60 ;
/ * *
* number of seconds in an hour
*
* = 60 * 60 seconds
*
* @ name Helper . minute
* @ private
* @ enum { number }
* @ readonly
* /
const hour = 3600 ;
/ * *
* number of seconds in a day
*
* = 60 * 60 * 24 seconds
*
* @ name Helper . day
* @ private
* @ enum { number }
* @ readonly
* /
const day = 86400 ;
2020-05-30 09:55:41 +00:00
/ * *
* number of seconds in a week
*
* = 60 * 60 * 24 * 7 seconds
*
* @ name Helper . week
* @ private
* @ enum { number }
* @ readonly
* /
const week = 604800 ;
2020-03-01 07:54:48 +00:00
/ * *
* number of seconds in a month ( 30 days , an approximation )
*
* = 60 * 60 * 24 * 30 seconds
*
* @ name Helper . month
* @ private
* @ enum { number }
* @ readonly
* /
const month = 2592000 ;
/ * *
* number of seconds in a non - leap year
*
* = 60 * 60 * 24 * 365 seconds
*
* @ name Helper . year
* @ private
* @ enum { number }
* @ readonly
* /
const year = 31536000 ;
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 }
* /
2018-12-29 17:40:59 +00:00
let 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
{
2018-12-29 17:40:59 +00:00
let v ;
2020-03-01 07:54:48 +00:00
if ( seconds < minute )
2015-09-05 15:12:11 +00:00
{
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
}
2020-03-01 07:54:48 +00:00
if ( seconds < hour )
2015-09-05 15:12:11 +00:00
{
2020-03-01 07:54:48 +00:00
v = Math . floor ( seconds / minute ) ;
2015-09-06 13:54:43 +00:00
return [ v , 'minute' ] ;
2015-09-05 15:12:11 +00:00
}
2020-03-01 07:54:48 +00:00
if ( seconds < day )
2015-09-05 15:12:11 +00:00
{
2020-03-01 07:54:48 +00:00
v = Math . floor ( seconds / hour ) ;
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:
2020-03-01 07:54:48 +00:00
if ( seconds < ( 2 * month ) )
2015-09-05 15:12:11 +00:00
{
2020-03-01 07:54:48 +00:00
v = Math . floor ( seconds / day ) ;
2015-09-06 13:54:43 +00:00
return [ v , 'day' ] ;
2015-09-05 15:12:11 +00:00
}
2020-03-01 07:54:48 +00:00
v = Math . floor ( seconds / month ) ;
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
2020-03-01 07:54:48 +00:00
/ * *
* converts a duration string into seconds
*
* The string is expected to be optional digits , followed by a time .
* Supported times are : min , hour , day , month , year , never
* Examples : 5 min , 13 hour , never
*
* @ name Helper . durationToSeconds
* @ function
* @ param { String } duration
* @ return { number }
* /
me . durationToSeconds = function ( duration )
{
2020-05-30 09:55:41 +00:00
let pieces = duration . split ( /(\D+)/ ) ,
2020-03-01 07:54:48 +00:00
factor = pieces [ 0 ] || 0 ,
timespan = pieces [ 1 ] || pieces [ 0 ] ;
switch ( timespan )
{
case 'min' :
return factor * minute ;
case 'hour' :
return factor * hour ;
case 'day' :
return factor * day ;
2020-05-30 09:55:41 +00:00
case 'week' :
return factor * week ;
2020-03-01 07:54:48 +00:00
case 'month' :
return factor * month ;
case 'year' :
return factor * year ;
case 'never' :
return 0 ;
default :
return factor ;
}
} ;
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
{
2018-12-29 17:40:59 +00:00
let 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
/ * *
2020-03-06 21:18:38 +00:00
* convert URLs to clickable links in the provided element .
2019-05-25 11:20:39 +00:00
*
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
2020-03-06 21:18:38 +00:00
* @ param { HTMLElement } element
2015-09-05 15:12:11 +00:00
* /
2020-03-06 21:18:38 +00:00
me . urls2links = function ( element )
2015-09-05 15:12:11 +00:00
{
2020-03-06 21:18:38 +00:00
element . html (
2020-05-30 10:05:20 +00:00
DOMPurify . sanitize (
element . html ( ) . replace (
/(((https?|ftp):\/\/[\w?!=&.\/-;#@~%+*-]+(?![\w\s?!&.\/;#~%"=-]>))|((magnet):[\w?=&.\/-;#@~%+*-]+))/ig ,
2020-06-02 12:50:14 +00:00
'<a href="$1" rel="nofollow noopener noreferrer">$1</a>'
2022-03-13 18:56:12 +00:00
) ,
purifyHtmlConfig
2020-03-06 21:18:38 +00:00
)
2020-03-06 19:57:15 +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
{
2018-12-29 17:40:59 +00:00
const args = Array . prototype . slice . call ( arguments ) ;
let 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 ) {
2018-12-29 17:40:59 +00:00
let val = args [ i ] ;
2020-01-25 08:16:14 +00:00
if ( m === '%d' ) {
val = parseFloat ( val ) ;
if ( isNaN ( val ) ) {
val = 0 ;
}
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 ) {
2018-12-29 17:40:59 +00:00
const name = cname + '=' ,
ca = document . cookie . split ( ';' ) ;
for ( let i = 0 ; i < ca . length ; ++ i ) {
let 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
2019-05-25 11:20:39 +00:00
/ * *
* wrap an object into a Paste , used for mocking in the unit tests
*
* @ name Helper . PasteFactory
* @ function
* @ param { object } data
* @ return { Paste }
* /
me . PasteFactory = function ( data )
{
return new Paste ( data ) ;
} ;
/ * *
* wrap an object into a Comment , used for mocking in the unit tests
*
* @ name Helper . CommentFactory
* @ function
* @ param { object } data
* @ return { Comment }
* /
me . CommentFactory = function ( data )
{
return new Comment ( data ) ;
} ;
2020-01-18 06:30:01 +00:00
/ * *
* convert all applicable characters to HTML entities
*
2020-01-18 06:36:43 +00:00
* @ see { @ link https : //cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html}
2020-01-18 06:30:01 +00:00
* @ name Helper . htmlEntities
* @ function
* @ param { string } str
* @ return { string } escaped HTML
* /
me . htmlEntities = function ( str ) {
return String ( str ) . replace (
/[&<>"'`=\/]/g , function ( s ) {
return entityMap [ s ] ;
2020-01-18 06:36:43 +00:00
}
) ;
2020-01-18 06:30:01 +00:00
}
2019-08-21 21:36:22 +00:00
/ * *
* calculate expiration date given initial date and expiration period
2020-04-23 09:25:24 +00:00
*
2019-08-21 21:36:22 +00:00
* @ name Helper . calculateExpirationDate
* @ function
* @ param { Date } initialDate - may not be empty
* @ param { string | number } expirationDisplayStringOrSecondsToExpire - may not be empty
* @ return { Date }
* /
me . calculateExpirationDate = function ( initialDate , expirationDisplayStringOrSecondsToExpire ) {
2020-03-01 07:54:48 +00:00
let expirationDate = new Date ( initialDate ) ,
secondsToExpiration = expirationDisplayStringOrSecondsToExpire ;
2019-08-21 21:36:22 +00:00
if ( typeof expirationDisplayStringOrSecondsToExpire === 'string' ) {
2020-03-01 07:54:48 +00:00
secondsToExpiration = me . durationToSeconds ( expirationDisplayStringOrSecondsToExpire ) ;
2019-08-21 21:36:22 +00:00
}
2020-04-23 09:25:24 +00:00
2019-08-21 21:36:22 +00:00
if ( typeof secondsToExpiration !== 'number' ) {
throw new Error ( 'Cannot calculate expiration date.' ) ;
}
if ( secondsToExpiration === 0 ) {
return null ;
}
expirationDate = expirationDate . setUTCSeconds ( expirationDate . getUTCSeconds ( ) + secondsToExpiration ) ;
return expirationDate ;
2020-01-04 10:34:16 +00:00
} ;
2020-02-02 06:08:38 +00:00
/ * *
* resets state , used for unit testing
*
* @ name Helper . reset
* @ function
* /
me . reset = function ( )
{
baseUri = null ;
} ;
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
* /
2018-12-29 17:40:59 +00:00
const I18n = ( function ( ) {
const me = { } ;
2017-02-08 19:12:22 +00:00
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
* /
2018-12-29 17:40:59 +00:00
const languageLoadedEvent = 'languageLoaded' ;
2017-02-14 21:21:55 +00:00
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
* /
2024-01-27 18:15:40 +00:00
const supportedLanguages = [ 'ar' , 'bg' , 'ca' , 'co' , 'cs' , 'de' , 'el' , 'es' , 'et' , 'fi' , 'fr' , 'he' , 'hu' , 'id' , 'it' , 'ja' , 'jbo' , 'lt' , 'no' , 'nl' , 'pl' , 'pt' , 'oc' , 'ro' , 'ru' , 'sk' , 'sl' , 'th' , 'tr' , 'uk' , '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
* /
2018-12-29 17:40:59 +00:00
let 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 }
* /
2018-12-29 17:40:59 +00:00
let 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
*
2020-01-25 08:07:29 +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
2020-01-25 08:16:14 +00:00
* loaded ) language is not downloaded yet , this will make sure the string
* is replaced when it eventually gets loaded . Using this is both simpler
* and more secure , as it avoids potential XSS when inserting text .
* The next parameter is the message ID , matching the ones found in
* the translation files under the i18n directory .
* Any additional parameters will get inserted into the message ID in
* place of % s ( strings ) or % d ( digits ) , applying the appropriate plural
* in case of digits . See also Helper . sprintf ( ) .
2017-02-14 21:21:55 +00:00
*
* @ name I18n . translate
2017-01-14 14:29:12 +00:00
* @ function
2020-01-25 08:07:29 +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
2020-01-25 08:07:29 +00:00
* @ 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
2018-12-29 17:40:59 +00:00
let args = Array . prototype . slice . call ( arguments ) ,
2017-02-14 21:21:55 +00:00
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
2018-12-29 17:40:59 +00:00
let 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
2018-12-29 17:40:59 +00:00
let orgArguments = arguments ;
2017-02-14 21:21:55 +00:00
$ ( document ) . on ( languageLoadedEvent , function ( ) {
// 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 ] ) ) {
2018-12-29 17:40:59 +00:00
let 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
2020-01-18 06:20:05 +00:00
// messageID may contain links, but should be from a trusted source (code or translation JSON files)
2020-01-13 18:17:30 +00:00
let containsLinks = args [ 0 ] . indexOf ( '<a' ) !== - 1 ;
2020-01-18 09:44:35 +00:00
// prevent double encoding, when we insert into a text node
2020-01-31 21:42:42 +00:00
if ( containsLinks || $element === null ) {
2020-01-18 09:44:35 +00:00
for ( let i = 0 ; i < args . length ; ++ i ) {
// parameters (i > 0) may never contain HTML as they may come from untrusted parties
2020-02-02 06:08:38 +00:00
if ( ( containsLinks ? i > 1 : i > 0 ) || ! containsLinks ) {
2020-01-18 09:44:35 +00:00
args [ i ] = Helper . htmlEntities ( args [ i ] ) ;
}
2020-01-18 06:20:05 +00:00
}
}
2017-02-14 21:21:55 +00:00
// format string
2018-12-29 17:40:59 +00:00
let output = Helper . sprintf . apply ( this , args ) ;
2017-02-14 21:21:55 +00:00
2020-01-18 09:44:35 +00:00
if ( containsLinks ) {
// only allow tags/attributes we actually use in translations
output = DOMPurify . sanitize (
output , {
2020-02-01 07:46:59 +00:00
ALLOWED _TAGS : [ 'a' , 'i' , 'span' ] ,
2020-01-18 09:44:35 +00:00
ALLOWED _ATTR : [ 'href' , 'id' ]
}
) ;
}
2020-01-25 08:07:29 +00:00
// if $element is given, insert translation
if ( $element !== null ) {
if ( containsLinks ) {
$element . html ( output ) ;
} else {
// text node takes care of entity encoding
2020-01-31 21:42:42 +00:00
$element . text ( output ) ;
2020-01-25 08:07:29 +00:00
}
return '' ;
2015-09-06 11:07:46 +00:00
}
2017-02-14 21:21:55 +00:00
2020-01-25 08:07:29 +00:00
return output ;
2018-01-06 08:26:10 +00:00
} ;
2015-09-06 11:07:46 +00:00
2023-09-19 05:29:00 +00:00
/ * *
* get currently loaded language
*
* @ name I18n . getLanguage
* @ function
* @ return { string }
* /
me . getLanguage = function ( )
{
return language ;
} ;
2015-09-08 18:48:18 +00:00
/ * *
* per language functions to use to determine the plural form
*
2022-04-05 05:22:07 +00:00
* @ see { @ link https : //docs.translatehouse.org/projects/localization-guide/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
{
2023-08-01 12:00:45 +00:00
case 'ar' :
return n === 0 ? 0 : ( n === 1 ? 1 : ( n === 2 ? 2 : ( n % 100 >= 3 && n % 100 <= 10 ? 3 : ( n % 100 >= 11 ? 4 : 5 ) ) ) ) ;
2019-06-23 10:06:36 +00:00
case 'cs' :
2022-09-29 03:34:49 +00:00
case 'sk' :
return n === 1 ? 0 : ( n >= 2 && n <= 4 ? 1 : 2 ) ;
2022-02-24 19:03:48 +00:00
case 'co' :
2015-09-08 18:48:18 +00:00
case 'fr' :
2017-01-08 06:56:56 +00:00
case 'oc' :
2022-04-28 18:05:57 +00:00
case 'tr' :
2016-04-26 18:21:30 +00:00
case 'zh' :
2018-02-21 21:51:31 +00:00
return n > 1 ? 1 : 0 ;
2021-01-07 20:16:03 +00:00
case 'he' :
return n === 1 ? 0 : ( n === 2 ? 1 : ( ( n < 0 || n > 10 ) && ( n % 10 === 0 ) ? 2 : 3 ) ) ;
2021-03-09 04:54:06 +00:00
case 'id' :
2023-08-01 12:00:45 +00:00
case 'ja' :
2022-02-12 15:17:09 +00:00
case 'jbo' :
2022-11-07 06:12:40 +00:00
case 'th' :
2021-03-09 04:54:06 +00:00
return 0 ;
2021-01-07 20:16:03 +00:00
case 'lt' :
return n % 10 === 1 && n % 100 !== 11 ? 0 : ( ( n % 10 >= 2 && n % 100 < 10 || n % 100 >= 20 ) ? 1 : 2 ) ;
2015-09-08 18:48:18 +00:00
case 'pl' :
2022-09-29 03:34:49 +00:00
return n === 1 ? 0 : ( n % 10 >= 2 && n % 10 <= 4 && ( n % 100 < 10 || n % 100 >= 20 ) ? 1 : 2 ) ;
2024-01-27 18:15:40 +00:00
case 'ro' :
return n === 1 ? 0 : ( ( n === 0 || ( n % 100 > 0 && n % 100 < 20 ) ) ? 1 : 2 ) ;
2016-12-16 09:21:15 +00:00
case 'ru' :
2019-10-18 09:31:40 +00:00
case 'uk' :
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 ) ) ;
2022-09-29 19:15:00 +00:00
// bg, ca, de, el, en, es, et, fi, 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
{
2018-12-29 17:40:59 +00:00
let 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 ) {
2023-09-19 05:29:00 +00:00
newLanguage = ( navigator . language || navigator . userLanguage || 'en' ) ;
if ( newLanguage . indexOf ( '-' ) > 0 ) {
newLanguage = newLanguage . split ( '-' ) [ 0 ] ;
}
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
* /
2018-12-29 17:40:59 +00:00
const CryptTool = ( function ( ) {
const me = { } ;
2017-02-08 19:12:22 +00:00
2019-05-15 19:20:54 +00:00
/ * *
* base58 encoder & decoder
*
* @ private
* /
let base58 = new baseX ( '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' ) ;
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
*
2018-12-29 17:40:59 +00:00
* @ name CryptTool . utf8To16
2018-09-01 17:42:22 +00:00
* @ function
* @ private
* @ param { string } message UTF - 8 string
* @ return { string } UTF - 16 string
* /
2018-12-29 17:40:59 +00:00
function utf8To16 ( message )
2018-09-01 17:42:22 +00:00
{
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
*
2018-12-29 17:40:59 +00:00
* @ name CryptTool . utf16To8
2018-08-05 06:56:03 +00:00
* @ function
* @ private
* @ param { string } message UTF - 16 string
* @ return { string } UTF - 8 string
* /
2018-12-29 17:40:59 +00:00
function utf16To8 ( message )
2018-08-05 06:56:03 +00:00
{
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-12-29 17:40:59 +00:00
* @ name CryptTool . arraybufferToString
2018-09-01 17:42:22 +00:00
* @ function
* @ private
* @ param { ArrayBuffer } messageArray
* @ return { string } message
* /
2018-12-29 17:40:59 +00:00
function arraybufferToString ( messageArray )
2018-09-01 17:42:22 +00:00
{
2018-12-29 17:40:59 +00:00
const array = new Uint8Array ( messageArray ) ;
let message = '' ,
i = 0 ;
while ( i < array . length ) {
message += String . fromCharCode ( array [ i ++ ] ) ;
2018-09-01 17:42:22 +00:00
}
return message ;
}
/ * *
* convert UTF - 8 string into a Uint8Array
*
* Iterates over the bytes of the message , writing them to the array
*
2018-12-29 17:40:59 +00:00
* @ name CryptTool . stringToArraybuffer
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-12-29 17:40:59 +00:00
function stringToArraybuffer ( message )
2018-08-05 06:56:03 +00:00
{
2018-12-29 17:40:59 +00:00
const messageArray = new Uint8Array ( message . length ) ;
for ( let i = 0 ; i < message . length ; ++ i ) {
messageArray [ i ] = message . charCodeAt ( i ) ;
2018-09-01 17:42:22 +00:00
}
return messageArray ;
2018-08-05 06:56:03 +00:00
}
/ * *
2018-12-27 20:32:13 +00:00
* compress a string ( deflate compression ) , returns buffer
2015-09-05 15:12:11 +00:00
*
2017-03-13 19:24:18 +00:00
* @ name CryptTool . compress
2018-12-27 20:32:13 +00:00
* @ async
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
2018-12-27 20:32:13 +00:00
* @ param { string } mode
making webassembly optional, ensuring retry button works when wrong password is provided
Tested configurations:
- browser with WASM support (Firefox 68.0.2)
- creates paste with zlib compression, no password
- creates paste with zlib compression, with password
- reads paste with zlib compression, no password
- reads paste with zlib compression, with password + retry button works
- reads paste without compression, no password
- reads paste without compression, with password + retry button works
- browser without WASM support (Chromium 76.0.3809.100, started via `chromium-browser --js-flags=--noexpose_wasm`)
- creates paste without compression, no password, but shows WASM warning
- creates paste without compression, with password, but shows WASM warning
- fails to read paste with zlib compression, no password + shows WASM error
- fails to read paste with zlib compression, with password + shows WASM error
- reads paste without compression, no password
- reads paste without compression, with password + retry button works
2019-09-08 06:21:54 +00:00
* @ param { object } zlib
* @ throws { string }
2018-12-27 20:32:13 +00:00
* @ return { ArrayBuffer } data
2015-09-05 15:12:11 +00:00
* /
making webassembly optional, ensuring retry button works when wrong password is provided
Tested configurations:
- browser with WASM support (Firefox 68.0.2)
- creates paste with zlib compression, no password
- creates paste with zlib compression, with password
- reads paste with zlib compression, no password
- reads paste with zlib compression, with password + retry button works
- reads paste without compression, no password
- reads paste without compression, with password + retry button works
- browser without WASM support (Chromium 76.0.3809.100, started via `chromium-browser --js-flags=--noexpose_wasm`)
- creates paste without compression, no password, but shows WASM warning
- creates paste without compression, with password, but shows WASM warning
- fails to read paste with zlib compression, no password + shows WASM error
- fails to read paste with zlib compression, with password + shows WASM error
- reads paste without compression, no password
- reads paste without compression, with password + retry button works
2019-09-08 06:21:54 +00:00
async function compress ( message , mode , zlib )
2015-09-05 15:12:11 +00:00
{
2018-12-29 17:40:59 +00:00
message = stringToArraybuffer (
utf16To8 ( message )
2018-12-27 20:32:13 +00:00
) ;
if ( mode === 'zlib' ) {
making webassembly optional, ensuring retry button works when wrong password is provided
Tested configurations:
- browser with WASM support (Firefox 68.0.2)
- creates paste with zlib compression, no password
- creates paste with zlib compression, with password
- reads paste with zlib compression, no password
- reads paste with zlib compression, with password + retry button works
- reads paste without compression, no password
- reads paste without compression, with password + retry button works
- browser without WASM support (Chromium 76.0.3809.100, started via `chromium-browser --js-flags=--noexpose_wasm`)
- creates paste without compression, no password, but shows WASM warning
- creates paste without compression, with password, but shows WASM warning
- fails to read paste with zlib compression, no password + shows WASM error
- fails to read paste with zlib compression, with password + shows WASM error
- reads paste without compression, no password
- reads paste without compression, with password + retry button works
2019-09-08 06:21:54 +00:00
if ( typeof zlib === 'undefined' ) {
throw 'Error compressing paste, due to missing WebAssembly support.'
}
2019-08-27 05:38:27 +00:00
return zlib . deflate ( message ) . buffer ;
2018-08-05 06:56:03 +00:00
}
2018-12-27 20:32:13 +00:00
return message ;
2017-02-12 17:08:08 +00:00
}
2015-09-05 15:12:11 +00:00
/ * *
2018-12-27 20:32:13 +00:00
* decompress potentially base64 encoded , deflate compressed buffer , returns string
2015-09-05 15:12:11 +00:00
*
2017-03-13 19:24:18 +00:00
* @ name CryptTool . decompress
2018-12-27 20:32:13 +00:00
* @ async
2017-01-14 14:29:12 +00:00
* @ function
2017-02-12 17:08:08 +00:00
* @ private
2018-12-27 20:32:13 +00:00
* @ param { ArrayBuffer } data
* @ param { string } mode
making webassembly optional, ensuring retry button works when wrong password is provided
Tested configurations:
- browser with WASM support (Firefox 68.0.2)
- creates paste with zlib compression, no password
- creates paste with zlib compression, with password
- reads paste with zlib compression, no password
- reads paste with zlib compression, with password + retry button works
- reads paste without compression, no password
- reads paste without compression, with password + retry button works
- browser without WASM support (Chromium 76.0.3809.100, started via `chromium-browser --js-flags=--noexpose_wasm`)
- creates paste without compression, no password, but shows WASM warning
- creates paste without compression, with password, but shows WASM warning
- fails to read paste with zlib compression, no password + shows WASM error
- fails to read paste with zlib compression, with password + shows WASM error
- reads paste without compression, no password
- reads paste without compression, with password + retry button works
2019-09-08 06:21:54 +00:00
* @ param { object } zlib
* @ throws { string }
2017-01-14 14:29:12 +00:00
* @ return { string } message
2015-09-05 15:12:11 +00:00
* /
making webassembly optional, ensuring retry button works when wrong password is provided
Tested configurations:
- browser with WASM support (Firefox 68.0.2)
- creates paste with zlib compression, no password
- creates paste with zlib compression, with password
- reads paste with zlib compression, no password
- reads paste with zlib compression, with password + retry button works
- reads paste without compression, no password
- reads paste without compression, with password + retry button works
- browser without WASM support (Chromium 76.0.3809.100, started via `chromium-browser --js-flags=--noexpose_wasm`)
- creates paste without compression, no password, but shows WASM warning
- creates paste without compression, with password, but shows WASM warning
- fails to read paste with zlib compression, no password + shows WASM error
- fails to read paste with zlib compression, with password + shows WASM error
- reads paste without compression, no password
- reads paste without compression, with password + retry button works
2019-09-08 06:21:54 +00:00
async function decompress ( data , mode , zlib )
2015-09-05 15:12:11 +00:00
{
2018-12-27 20:32:13 +00:00
if ( mode === 'zlib' || mode === 'none' ) {
if ( mode === 'zlib' ) {
2019-08-27 05:38:27 +00:00
if ( typeof zlib === 'undefined' ) {
2024-05-06 08:07:02 +00:00
throw 'Error decompressing paste, your browser does not support WebAssembly. Please use another browser to view this paste.'
2019-08-27 05:38:27 +00:00
}
data = zlib . inflate (
2018-12-29 17:40:59 +00:00
new Uint8Array ( data )
) . buffer ;
2018-12-27 20:32:13 +00:00
}
2018-12-29 17:40:59 +00:00
return utf8To16 (
arraybufferToString ( data )
2018-12-27 20:32:13 +00:00
) ;
}
2018-08-05 06:56:03 +00:00
// detect presence of Base64.js, indicating legacy ZeroBin paste
if ( typeof Base64 === 'undefined' ) {
2018-12-29 17:40:59 +00:00
return utf8To16 (
2018-12-27 20:32:13 +00:00
RawDeflate . inflate (
2018-12-29 17:40:59 +00:00
utf8To16 (
2018-12-27 20:32:13 +00:00
atob (
2018-12-29 17:40:59 +00:00
arraybufferToString ( data )
2018-12-27 20:32:13 +00:00
)
)
)
) ;
2018-08-05 06:56:03 +00:00
} else {
2018-12-27 20:32:13 +00:00
return Base64 . btou (
RawDeflate . inflate (
Base64 . fromBase64 (
2018-12-29 17:40:59 +00:00
arraybufferToString ( data )
2018-12-27 20:32:13 +00:00
)
)
) ;
2018-08-05 06:56:03 +00:00
}
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 )
{
2019-06-22 13:44:54 +00:00
let bytes = '' ;
const byteArray = new Uint8Array ( length ) ;
window . crypto . getRandomValues ( byteArray ) ;
for ( let i = 0 ; i < length ; ++ i ) {
bytes += String . fromCharCode ( byteArray [ i ] ) ;
2018-09-01 17:42:22 +00:00
}
2019-06-22 13:44:54 +00:00
return bytes ;
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-12-25 16:34:39 +00:00
* @ param { array } spec cryptographic specification
2018-10-20 20:34:36 +00:00
* @ return { CryptoKey } derived key
2015-09-05 15:12:11 +00:00
* /
2018-12-25 16:34:39 +00:00
async function deriveKey ( key , password , spec )
2015-09-05 15:12:11 +00:00
{
2018-12-29 17:40:59 +00:00
let keyArray = stringToArraybuffer ( key ) ;
2018-12-28 04:49:34 +00:00
if ( password . length > 0 ) {
// version 1 pastes did append the passwords SHA-256 hash in hex
if ( spec [ 7 ] === 'rawdeflate' ) {
let passwordBuffer = await window . crypto . subtle . digest (
{ name : 'SHA-256' } ,
2018-12-29 17:40:59 +00:00
stringToArraybuffer (
utf16To8 ( password )
)
2019-08-27 05:38:27 +00:00
) . catch ( Alert . showError ) ;
2018-12-28 04:49:34 +00:00
password = Array . prototype . map . call (
2018-12-29 17:40:59 +00:00
new Uint8Array ( passwordBuffer ) ,
x => ( '00' + x . toString ( 16 ) ) . slice ( - 2 )
2018-12-28 04:49:34 +00:00
) . join ( '' ) ;
}
2018-12-29 17:40:59 +00:00
let passwordArray = stringToArraybuffer ( password ) ,
2018-12-25 16:34:39 +00:00
newKeyArray = new Uint8Array ( keyArray . length + passwordArray . length ) ;
newKeyArray . set ( keyArray , 0 ) ;
newKeyArray . set ( passwordArray , keyArray . length ) ;
keyArray = newKeyArray ;
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
2019-08-27 05:38:27 +00:00
) . catch ( Alert . showError ) ;
2018-09-01 17:42:22 +00:00
// derive a stronger key for use with AES
2018-12-25 16:34:39 +00:00
return window . crypto . subtle . deriveKey (
2018-09-01 17:42:22 +00:00
{
name : 'PBKDF2' , // we use PBKDF2 for key derivation
2018-12-29 17:40:59 +00:00
salt : stringToArraybuffer ( spec [ 1 ] ) , // salt used in HMAC
2018-12-25 16:34:39 +00:00
iterations : spec [ 2 ] , // 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-12-25 16:34:39 +00:00
name : 'AES-' + spec [ 6 ] . toUpperCase ( ) , // can be any supported AES algorithm ("AES-CTR", "AES-CBC", "AES-CMAC", "AES-GCM", "AES-CFB", "AES-KW", "ECDH", "DH" or "HMAC")
length : spec [ 3 ] // can be 128, 192 or 256
2018-09-01 17:42:22 +00:00
} ,
false , // the key may not be exported
2018-12-28 04:49:34 +00:00
[ 'encrypt' , 'decrypt' ] // we may only use it for en- and decryption
2019-08-27 05:38:27 +00:00
) . catch ( Alert . showError ) ;
2018-10-20 20:34:36 +00:00
}
2018-10-20 21:08:13 +00:00
/ * *
2018-12-25 16:34:39 +00:00
* gets crypto settings from specification and authenticated data
2018-10-20 21:08:13 +00:00
*
* @ name CryptTool . cryptoSettings
* @ function
* @ private
2018-12-25 16:34:39 +00:00
* @ param { string } adata authenticated data
* @ param { array } spec cryptographic specification
2018-10-20 21:08:13 +00:00
* @ return { object } crypto settings
* /
2018-12-25 16:34:39 +00:00
function cryptoSettings ( adata , spec )
2018-10-20 21:08:13 +00:00
{
return {
2018-12-25 16:34:39 +00:00
name : 'AES-' + spec [ 6 ] . toUpperCase ( ) , // can be any supported AES algorithm ("AES-CTR", "AES-CBC", "AES-CMAC", "AES-GCM", "AES-CFB", "AES-KW", "ECDH", "DH" or "HMAC")
2018-12-29 17:40:59 +00:00
iv : stringToArraybuffer ( spec [ 0 ] ) , // the initialization vector you used to encrypt
additionalData : stringToArraybuffer ( adata ) , // the addtional data you used during encryption (if any)
2018-12-25 16:34:39 +00:00
tagLength : spec [ 4 ] // the length of the tag you used to encrypt (if any)
2018-10-20 21:08:13 +00:00
} ;
}
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
2018-12-25 16:34:39 +00:00
* @ param { array } adata
2018-12-27 20:32:13 +00:00
* @ return { array } encrypted message in base64 encoding & adata containing encryption spec
2018-12-25 16:34:39 +00:00
* /
me . cipher = async function ( key , password , message , adata )
{
making webassembly optional, ensuring retry button works when wrong password is provided
Tested configurations:
- browser with WASM support (Firefox 68.0.2)
- creates paste with zlib compression, no password
- creates paste with zlib compression, with password
- reads paste with zlib compression, no password
- reads paste with zlib compression, with password + retry button works
- reads paste without compression, no password
- reads paste without compression, with password + retry button works
- browser without WASM support (Chromium 76.0.3809.100, started via `chromium-browser --js-flags=--noexpose_wasm`)
- creates paste without compression, no password, but shows WASM warning
- creates paste without compression, with password, but shows WASM warning
- fails to read paste with zlib compression, no password + shows WASM error
- fails to read paste with zlib compression, with password + shows WASM error
- reads paste without compression, no password
- reads paste without compression, with password + retry button works
2019-09-08 06:21:54 +00:00
let zlib = ( await z ) ;
2018-12-25 16:34:39 +00:00
// AES in Galois Counter Mode, keysize 256 bit,
// authentication tag 128 bit, 10000 iterations in key derivation
2019-08-27 05:38:27 +00:00
const compression = (
making webassembly optional, ensuring retry button works when wrong password is provided
Tested configurations:
- browser with WASM support (Firefox 68.0.2)
- creates paste with zlib compression, no password
- creates paste with zlib compression, with password
- reads paste with zlib compression, no password
- reads paste with zlib compression, with password + retry button works
- reads paste without compression, no password
- reads paste without compression, with password + retry button works
- browser without WASM support (Chromium 76.0.3809.100, started via `chromium-browser --js-flags=--noexpose_wasm`)
- creates paste without compression, no password, but shows WASM warning
- creates paste without compression, with password, but shows WASM warning
- fails to read paste with zlib compression, no password + shows WASM error
- fails to read paste with zlib compression, with password + shows WASM error
- reads paste without compression, no password
- reads paste without compression, with password + retry button works
2019-09-08 06:21:54 +00:00
typeof zlib === 'undefined' ?
2019-08-27 05:38:27 +00:00
'none' : // client lacks support for WASM
making webassembly optional, ensuring retry button works when wrong password is provided
Tested configurations:
- browser with WASM support (Firefox 68.0.2)
- creates paste with zlib compression, no password
- creates paste with zlib compression, with password
- reads paste with zlib compression, no password
- reads paste with zlib compression, with password + retry button works
- reads paste without compression, no password
- reads paste without compression, with password + retry button works
- browser without WASM support (Chromium 76.0.3809.100, started via `chromium-browser --js-flags=--noexpose_wasm`)
- creates paste without compression, no password, but shows WASM warning
- creates paste without compression, with password, but shows WASM warning
- fails to read paste with zlib compression, no password + shows WASM error
- fails to read paste with zlib compression, with password + shows WASM error
- reads paste without compression, no password
- reads paste without compression, with password + retry button works
2019-09-08 06:21:54 +00:00
( $ ( 'body' ) . data ( 'compression' ) || 'zlib' )
2019-08-27 05:38:27 +00:00
) ,
spec = [
getRandomBytes ( 16 ) , // initialization vector
getRandomBytes ( 8 ) , // salt
100000 , // iterations
256 , // key size
128 , // tag size
'aes' , // algorithm
'gcm' , // algorithm mode
compression // compression
] , encodedSpec = [ ] ;
2018-12-27 20:32:13 +00:00
for ( let i = 0 ; i < spec . length ; ++ i ) {
encodedSpec [ i ] = i < 2 ? btoa ( spec [ i ] ) : spec [ i ] ;
}
2018-12-25 16:34:39 +00:00
if ( adata . length === 0 ) {
// comment
adata = encodedSpec ;
} else if ( adata [ 0 ] === null ) {
// paste
adata [ 0 ] = encodedSpec ;
}
2018-09-01 17:42:22 +00:00
// finally, encrypt message
2018-12-25 16:34:39 +00:00
return [
btoa (
2018-12-29 17:40:59 +00:00
arraybufferToString (
2018-12-25 16:34:39 +00:00
await window . crypto . subtle . encrypt (
cryptoSettings ( JSON . stringify ( adata ) , spec ) ,
await deriveKey ( key , password , spec ) ,
making webassembly optional, ensuring retry button works when wrong password is provided
Tested configurations:
- browser with WASM support (Firefox 68.0.2)
- creates paste with zlib compression, no password
- creates paste with zlib compression, with password
- reads paste with zlib compression, no password
- reads paste with zlib compression, with password + retry button works
- reads paste without compression, no password
- reads paste without compression, with password + retry button works
- browser without WASM support (Chromium 76.0.3809.100, started via `chromium-browser --js-flags=--noexpose_wasm`)
- creates paste without compression, no password, but shows WASM warning
- creates paste without compression, with password, but shows WASM warning
- fails to read paste with zlib compression, no password + shows WASM error
- fails to read paste with zlib compression, with password + shows WASM error
- reads paste without compression, no password
- reads paste without compression, with password + retry button works
2019-09-08 06:21:54 +00:00
await compress ( message , compression , zlib )
2019-08-27 05:38:27 +00:00
) . catch ( Alert . showError )
2018-12-25 16:34:39 +00:00
)
) ,
adata
] ;
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
2018-12-25 16:34:39 +00:00
* @ param { string | object } data encrypted message
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
{
making webassembly optional, ensuring retry button works when wrong password is provided
Tested configurations:
- browser with WASM support (Firefox 68.0.2)
- creates paste with zlib compression, no password
- creates paste with zlib compression, with password
- reads paste with zlib compression, no password
- reads paste with zlib compression, with password + retry button works
- reads paste without compression, no password
- reads paste without compression, with password + retry button works
- browser without WASM support (Chromium 76.0.3809.100, started via `chromium-browser --js-flags=--noexpose_wasm`)
- creates paste without compression, no password, but shows WASM warning
- creates paste without compression, with password, but shows WASM warning
- fails to read paste with zlib compression, no password + shows WASM error
- fails to read paste with zlib compression, with password + shows WASM error
- reads paste without compression, no password
- reads paste without compression, with password + retry button works
2019-09-08 06:21:54 +00:00
let adataString , spec , cipherMessage , plaintext ;
let zlib = ( await z ) ;
2018-12-25 16:34:39 +00:00
if ( data instanceof Array ) {
// version 2
adataString = JSON . stringify ( data [ 1 ] ) ;
2019-05-31 05:05:40 +00:00
// clone the array instead of passing the reference
spec = ( data [ 1 ] [ 0 ] instanceof Array ? data [ 1 ] [ 0 ] : data [ 1 ] ) . slice ( ) ;
2018-12-25 16:34:39 +00:00
cipherMessage = data [ 0 ] ;
} else if ( typeof data === 'string' ) {
// version 1
let object = JSON . parse ( data ) ;
adataString = atob ( object . adata ) ;
2019-05-31 05:05:40 +00:00
spec = [
2018-12-25 16:34:39 +00:00
object . iv ,
object . salt ,
object . iter ,
object . ks ,
object . ts ,
object . cipher ,
object . mode ,
'rawdeflate'
] ;
cipherMessage = object . ct ;
} else {
throw 'unsupported message format' ;
}
spec [ 0 ] = atob ( spec [ 0 ] ) ;
spec [ 1 ] = atob ( spec [ 1 ] ) ;
making webassembly optional, ensuring retry button works when wrong password is provided
Tested configurations:
- browser with WASM support (Firefox 68.0.2)
- creates paste with zlib compression, no password
- creates paste with zlib compression, with password
- reads paste with zlib compression, no password
- reads paste with zlib compression, with password + retry button works
- reads paste without compression, no password
- reads paste without compression, with password + retry button works
- browser without WASM support (Chromium 76.0.3809.100, started via `chromium-browser --js-flags=--noexpose_wasm`)
- creates paste without compression, no password, but shows WASM warning
- creates paste without compression, with password, but shows WASM warning
- fails to read paste with zlib compression, no password + shows WASM error
- fails to read paste with zlib compression, with password + shows WASM error
- reads paste without compression, no password
- reads paste without compression, with password + retry button works
2019-09-08 06:21:54 +00:00
if ( spec [ 7 ] === 'zlib' ) {
if ( typeof zlib === 'undefined' ) {
2024-05-06 08:07:02 +00:00
throw 'Error decompressing paste, your browser does not support WebAssembly. Please use another browser to view this paste.'
making webassembly optional, ensuring retry button works when wrong password is provided
Tested configurations:
- browser with WASM support (Firefox 68.0.2)
- creates paste with zlib compression, no password
- creates paste with zlib compression, with password
- reads paste with zlib compression, no password
- reads paste with zlib compression, with password + retry button works
- reads paste without compression, no password
- reads paste without compression, with password + retry button works
- browser without WASM support (Chromium 76.0.3809.100, started via `chromium-browser --js-flags=--noexpose_wasm`)
- creates paste without compression, no password, but shows WASM warning
- creates paste without compression, with password, but shows WASM warning
- fails to read paste with zlib compression, no password + shows WASM error
- fails to read paste with zlib compression, with password + shows WASM error
- reads paste without compression, no password
- reads paste without compression, with password + retry button works
2019-09-08 06:21:54 +00:00
}
}
2018-09-01 17:42:22 +00:00
try {
making webassembly optional, ensuring retry button works when wrong password is provided
Tested configurations:
- browser with WASM support (Firefox 68.0.2)
- creates paste with zlib compression, no password
- creates paste with zlib compression, with password
- reads paste with zlib compression, no password
- reads paste with zlib compression, with password + retry button works
- reads paste without compression, no password
- reads paste without compression, with password + retry button works
- browser without WASM support (Chromium 76.0.3809.100, started via `chromium-browser --js-flags=--noexpose_wasm`)
- creates paste without compression, no password, but shows WASM warning
- creates paste without compression, with password, but shows WASM warning
- fails to read paste with zlib compression, no password + shows WASM error
- fails to read paste with zlib compression, with password + shows WASM error
- reads paste without compression, no password
- reads paste without compression, with password + retry button works
2019-09-08 06:21:54 +00:00
plaintext = await window . crypto . subtle . decrypt (
cryptoSettings ( adataString , spec ) ,
await deriveKey ( key , password , spec ) ,
stringToArraybuffer (
atob ( cipherMessage )
)
2018-10-20 20:05:35 +00:00
) ;
2018-09-01 17:42:22 +00:00
} catch ( err ) {
making webassembly optional, ensuring retry button works when wrong password is provided
Tested configurations:
- browser with WASM support (Firefox 68.0.2)
- creates paste with zlib compression, no password
- creates paste with zlib compression, with password
- reads paste with zlib compression, no password
- reads paste with zlib compression, with password + retry button works
- reads paste without compression, no password
- reads paste without compression, with password + retry button works
- browser without WASM support (Chromium 76.0.3809.100, started via `chromium-browser --js-flags=--noexpose_wasm`)
- creates paste without compression, no password, but shows WASM warning
- creates paste without compression, with password, but shows WASM warning
- fails to read paste with zlib compression, no password + shows WASM error
- fails to read paste with zlib compression, with password + shows WASM error
- reads paste without compression, no password
- reads paste without compression, with password + retry button works
2019-09-08 06:21:54 +00:00
console . error ( err ) ;
2018-09-01 17:42:22 +00:00
return '' ;
2015-08-31 19:14:12 +00:00
}
making webassembly optional, ensuring retry button works when wrong password is provided
Tested configurations:
- browser with WASM support (Firefox 68.0.2)
- creates paste with zlib compression, no password
- creates paste with zlib compression, with password
- reads paste with zlib compression, no password
- reads paste with zlib compression, with password + retry button works
- reads paste without compression, no password
- reads paste without compression, with password + retry button works
- browser without WASM support (Chromium 76.0.3809.100, started via `chromium-browser --js-flags=--noexpose_wasm`)
- creates paste without compression, no password, but shows WASM warning
- creates paste without compression, with password, but shows WASM warning
- fails to read paste with zlib compression, no password + shows WASM error
- fails to read paste with zlib compression, with password + shows WASM error
- reads paste without compression, no password
- reads paste without compression, with password + retry button works
2019-09-08 06:21:54 +00:00
try {
return await decompress ( plaintext , spec [ 7 ] , zlib ) ;
} catch ( err ) {
Alert . showError ( err ) ;
return err ;
}
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 }
2019-05-15 19:20:54 +00:00
* @ return { string } raw bytes
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-12-29 17:40:59 +00:00
return getRandomBytes ( 32 ) ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-13 20:12:00 +00:00
/ * *
2019-05-15 19:20:54 +00:00
* base58 encode a DOMString ( UTF - 16 )
2017-02-13 20:12:00 +00:00
*
2019-05-15 19:20:54 +00:00
* @ name CryptTool . base58encode
2017-02-13 20:12:00 +00:00
* @ function
2019-05-15 19:20:54 +00:00
* @ param { string } input
* @ return { string } output
2017-02-13 20:12:00 +00:00
* /
2019-05-15 19:20:54 +00:00
me . base58encode = function ( input )
2017-02-13 20:12:00 +00:00
{
2019-05-15 19:20:54 +00:00
return base58 . encode (
stringToArraybuffer ( input )
) ;
}
2017-02-13 20:12:00 +00:00
/ * *
2019-05-15 19:20:54 +00:00
* base58 decode a DOMString ( UTF - 16 )
2017-02-13 20:12:00 +00:00
*
2019-05-15 19:20:54 +00:00
* @ name CryptTool . base58decode
2017-02-13 20:12:00 +00:00
* @ function
2019-05-15 19:20:54 +00:00
* @ param { string } input
* @ return { string } output
2017-02-13 20:12:00 +00:00
* /
2019-05-15 19:20:54 +00:00
me . base58decode = function ( input )
2017-02-13 20:12:00 +00:00
{
2019-05-15 19:20:54 +00:00
return arraybufferToString (
base58 . decode ( input )
) ;
}
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
* /
2018-12-29 17:40:59 +00:00
const Model = ( function ( ) {
const me = { } ;
2017-02-12 20:13:04 +00:00
2018-12-29 17:40:59 +00:00
let id = null ,
pasteData = null ,
symmetricKey = null ,
2017-02-17 19:46:10 +00:00
$templates ;
2017-02-12 20:13:04 +00:00
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
2018-12-25 16:34:39 +00:00
ServerInteraction . prepare ( ) ;
2019-06-01 21:49:40 +00:00
ServerInteraction . setUrl ( Helper . baseUri ( ) + '?pasteid=' + me . getPasteId ( ) ) ;
2017-04-11 14:34:13 +00:00
2018-12-25 16:34:39 +00:00
ServerInteraction . setFailure ( function ( status , data ) {
2017-04-11 14:34:13 +00:00
// revert loading status…
Alert . hideLoading ( ) ;
TopNav . showViewButtons ( ) ;
// show error message
2018-12-25 16:34:39 +00:00
Alert . showError ( ServerInteraction . parseUploadError ( status , data , 'get paste data' ) ) ;
2018-05-22 09:43:44 +00:00
} ) ;
2018-12-25 16:34:39 +00:00
ServerInteraction . setSuccess ( function ( status , data ) {
2019-05-25 11:20:39 +00:00
pasteData = new Paste ( data ) ;
2017-04-11 14:34:13 +00:00
if ( typeof callback === 'function' ) {
2019-05-25 11:20:39 +00:00
return callback ( pasteData ) ;
2017-04-11 14:34:13 +00:00
}
2018-05-22 09:43:44 +00:00
} ) ;
2018-12-25 16:34:39 +00:00
ServerInteraction . 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
{
2019-01-21 23:07:28 +00:00
const idRegEx = /^[a-z0-9]{16}$/ ;
2017-02-17 19:46:10 +00:00
2019-01-21 23:07:28 +00:00
// return cached value
if ( id !== null ) {
return id ;
}
// do use URL interface, if possible
2019-06-20 20:30:49 +00:00
const url = new URL ( window . location ) ;
2019-01-21 23:07:28 +00:00
2019-06-20 20:30:49 +00:00
for ( const param of url . searchParams ) {
const key = param [ 0 ] ;
const value = param [ 1 ] ;
2019-01-21 23:07:28 +00:00
2019-06-20 20:30:49 +00:00
if ( value === '' && idRegEx . test ( key ) ) {
// safe, as the whole regex is matched
id = key ;
return key ;
2017-02-17 19:46:10 +00:00
}
2019-01-21 23:07:28 +00:00
}
2019-06-20 20:30:49 +00:00
if ( id === null ) {
2019-01-21 23:07:28 +00:00
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
}
/ * *
2018-12-29 17:40:59 +00:00
* returns true , when the URL has a delete token and the current call was used for deleting a paste .
2018-05-22 09:41:35 +00:00
*
* @ 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 ) {
2024-01-27 17:26:19 +00:00
let startPos = 1 ;
if ( window . location . hash . startsWith ( loadConfirmPrefix ) ) {
startPos = loadConfirmPrefix . length ;
2017-02-17 19:46:10 +00:00
}
2024-01-27 17:26:19 +00:00
let newKey = window . location . hash . substring ( startPos ) ;
2017-02-17 19:46:10 +00:00
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.
2018-12-29 17:40:59 +00:00
let ampersandPos = newKey . indexOf ( '&' ) ;
2017-02-14 21:21:55 +00:00
if ( ampersandPos > - 1 )
2015-09-05 15:12:11 +00:00
{
2018-12-29 17:40:59 +00:00
newKey = newKey . substring ( 0 , ampersandPos ) ;
2017-02-14 21:21:55 +00:00
}
2024-01-27 17:26:19 +00:00
if ( newKey === '' ) {
throw 'no encryption key given' ;
}
2018-12-29 17:40:59 +00:00
2019-05-19 06:36:18 +00:00
// version 2 uses base58, version 1 uses base64 without decoding
2019-05-15 19:20:54 +00:00
try {
2019-05-19 07:54:40 +00:00
// base58 encode strips NULL bytes at the beginning of the
// string, so we re-add them if necessary
symmetricKey = CryptTool . base58decode ( newKey ) . padStart ( 32 , '\u0000' ) ;
2019-05-15 19:20:54 +00:00
} catch ( e ) {
2019-05-19 06:36:18 +00:00
symmetricKey = newKey ;
2017-02-14 21:21:55 +00:00
}
}
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
2018-12-29 17:40:59 +00:00
let $element = $templates . find ( '#' + name + 'template' ) . clone ( true ) ;
2017-02-17 19:46:10 +00:00
// 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
* /
2018-12-29 17:40:59 +00:00
const UiHelper = ( function ( ) {
const 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
{
2018-12-29 17:40:59 +00:00
let 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
{
2018-12-29 17:40:59 +00:00
let elementTop = $element . offset ( ) . top ,
viewportTop = $ ( window ) . scrollTop ( ) ,
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
{
2018-12-29 17:40:59 +00:00
let $body = $ ( 'html, body' ) ,
2017-02-17 19:46:10 +00:00
margin = 50 ,
2018-12-29 17:40:59 +00:00
callbackCalled = false ,
dest = 0 ;
2017-02-17 19:46:10 +00:00
2018-12-29 17:40:59 +00:00
// calculate destination place
2017-02-17 19:46:10 +00:00
// 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
* /
2018-12-29 17:40:59 +00:00
const Alert = ( function ( ) {
const me = { } ;
2017-02-13 10:35:04 +00:00
2018-12-29 17:40:59 +00:00
let $errorMessage ,
2017-02-17 21:46:18 +00:00
$loadingIndicator ,
2017-03-12 13:16:08 +00:00
$statusMessage ,
2018-12-29 17:40:59 +00:00
$remainingTime ,
currentIcon ,
customHandler ;
2017-02-15 21:59:55 +00:00
2018-12-29 17:40:59 +00:00
const alertType = [
'loading' , // not in bootstrap CSS, but using a plausible value here
'info' , // status icon
2019-08-28 18:29:23 +00:00
'warning' , // warning icon
2018-12-29 17:40:59 +00:00
'danger' // error icon
2017-02-17 19:46:10 +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 ] ;
2019-06-18 17:45:52 +00:00
} else if ( args instanceof Error ) {
// extract message into array if needed
args = [ args . message ] ;
2017-02-17 19:46:10 +00:00
}
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' ) {
2018-12-29 17:40:59 +00:00
let handlerResult = customHandler ( alertType [ id ] , $element , args , icon ) ;
2017-02-17 19:46:10 +00:00
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
}
}
2019-08-28 18:29:23 +00:00
let $translationTarget = $element ;
// handle icon, if template uses one
const $glyphIcon = $element . find ( ':first' ) ;
if ( $glyphIcon . length ) {
// if there is an icon, we need to provide an inner element
// to translate the message into, instead of the parent
$translationTarget = $ ( '<span>' ) ;
$element . html ( ' ' ) . prepend ( $glyphIcon ) . append ( $translationTarget ) ;
if ( icon !== null && // icon was passed
icon !== currentIcon [ id ] // and it differs from current icon
) {
// 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
2019-08-28 18:29:23 +00:00
args . unshift ( $translationTarget ) ;
2017-03-12 16:08:12 +00:00
// 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-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
2019-08-27 05:38:27 +00:00
/ * *
* display a warning message
*
* This automatically passes the text to I18n for translation .
*
* @ name Alert . showWarning
* @ function
* @ 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
* /
me . showWarning = function ( message , icon )
{
2019-08-28 18:29:23 +00:00
$errorMessage . find ( ':first' )
. removeClass ( currentIcon [ 3 ] )
. addClass ( currentIcon [ 2 ] ) ;
2019-08-27 05:38:27 +00:00
handleNotification ( 2 , $errorMessage , message , icon ) ;
} ;
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
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-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
// 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-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
2019-08-27 05:38:27 +00:00
'glyphicon-warning-sign' , // warning icon
2017-10-23 19:33:07 +00:00
'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
* /
2018-12-29 17:40:59 +00:00
const PasteStatus = ( function ( ) {
const me = { } ;
2017-02-14 21:21:55 +00:00
2018-12-29 17:40:59 +00:00
let $pasteSuccess ,
2017-02-14 21:21:55 +00:00
$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
{
2019-08-14 01:44:53 +00:00
if ( $shortenButton . hasClass ( 'buttondisabled' ) ) {
return ;
}
$ . ajax ( {
type : 'GET' ,
url : ` ${ $shortenButton . data ( 'shortener' ) } ${ encodeURIComponent ( $pasteUrl . attr ( 'href' ) ) } ` ,
headers : { 'Accept' : 'text/html, application/xhtml+xml, application/xml, application/json' } ,
processData : false ,
timeout : 10000 ,
xhrFields : {
withCredentials : false
} ,
2024-01-04 22:08:17 +00:00
success : PasteStatus . extractUrl
2019-08-14 01:44:53 +00:00
} )
. fail ( function ( data , textStatus , errorThrown ) {
console . error ( textStatus , errorThrown ) ;
2019-08-27 05:38:27 +00:00
// we don't know why it failed, could be CORS of the external
// server not setup properly, in which case we follow old
// behavior to open it in new tab
2019-08-14 01:44:53 +00:00
window . open (
` ${ $shortenButton . data ( 'shortener' ) } ${ encodeURIComponent ( $pasteUrl . attr ( 'href' ) ) } ` ,
'_blank' ,
'noopener, noreferrer'
2019-08-29 15:21:56 +00:00
) ;
2019-08-14 01:44:53 +00:00
} ) ;
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
{
2020-01-18 09:44:35 +00:00
I18n . _ (
$ ( '#pastelink' ) ,
'Your paste is <a id="pasteurl" href="%s">%s</a> <span id="copyhint">(Hit [Ctrl]+[c] to copy)</span>' ,
url , url
2017-02-13 20:12:00 +00:00
) ;
// save newly created element
$pasteUrl = $ ( '#pasteurl' ) ;
// and add click event
$pasteUrl . click ( pasteLinkClick ) ;
2019-08-14 01:44:53 +00:00
// delete link
2020-01-25 07:13:36 +00:00
$ ( '#deletelink' ) . html ( '<a href="' + deleteUrl + '"></a>' ) ;
I18n . _ ( $ ( '#deletelink a' ) . first ( ) , 'Delete data' ) ;
2017-02-13 10:35:04 +00:00
2019-08-15 00:36:44 +00:00
// enable shortener button
$shortenButton . removeClass ( 'buttondisabled' ) ;
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
2024-01-04 22:08:17 +00:00
/ * *
* extracts URLs from given string
*
* if at least one is found , it disables the shortener button and
* replaces the paste URL
*
* @ name PasteStatus . extractUrl
* @ function
* @ param { string } response
* /
me . extractUrl = function ( response )
{
if ( typeof response === 'object' ) {
response = JSON . stringify ( response ) ;
}
if ( typeof response === 'string' && response . length > 0 ) {
2024-01-07 16:45:01 +00:00
const shortUrlMatcher = /https?:\/\/[^\s"<]+/g ; // JSON API will have URL in quotes, XML in tags
2024-01-05 05:28:19 +00:00
const shortUrl = ( response . match ( shortUrlMatcher ) || [ ] ) . filter ( function ( urlRegExMatch ) {
2024-01-05 05:40:12 +00:00
if ( typeof URL . canParse === 'function' ) {
return URL . canParse ( urlRegExMatch ) ;
}
// polyfill for older browsers (< 120) & node (< 19.9 & < 18.17)
2024-01-04 22:23:47 +00:00
try {
2024-01-05 05:28:19 +00:00
return ! ! new URL ( urlRegExMatch ) ;
2024-01-04 22:23:47 +00:00
} catch ( error ) {
return false ;
}
2024-01-04 22:08:17 +00:00
} ) . sort ( function ( a , b ) {
2024-01-07 16:45:01 +00:00
return a . length - b . length ; // shortest first
2024-01-04 22:08:17 +00:00
} ) [ 0 ] ;
if ( typeof shortUrl === 'string' && shortUrl . length > 0 ) {
// we disable the button to avoid calling shortener again
$shortenButton . addClass ( 'buttondisabled' ) ;
// update link
$pasteUrl . text ( shortUrl ) ;
$pasteUrl . prop ( 'href' , shortUrl ) ;
// we pre-select the link so that the user only has to [Ctrl]+[c] the link
Helper . selectText ( $pasteUrl [ 0 ] ) ;
return ;
}
}
Alert . showError ( 'Cannot parse response from URL shortener.' ) ;
} ;
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
2019-05-25 11:20:39 +00:00
* @ param { Paste } paste
2017-02-13 20:12:00 +00:00
* /
2019-05-15 05:44:03 +00:00
me . showRemainingTime = function ( paste )
2017-02-13 20:12:00 +00:00
{
2019-05-25 11:20:39 +00:00
if ( paste . isBurnAfterReadingEnabled ( ) ) {
2017-02-14 21:21:55 +00:00
// 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' ) ;
2019-05-25 11:20:39 +00:00
} else if ( paste . getTimeToLive ( ) > 0 ) {
2017-02-14 21:21:55 +00:00
// display paste expiration
2019-05-25 11:20:39 +00:00
let expiration = Helper . secondsToHuman ( paste . getTimeToLive ( ) ) ,
2017-02-14 21:21:55 +00:00
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
* /
2018-12-29 17:40:59 +00:00
const Prompt = ( function ( ) {
const me = { } ;
2017-02-12 17:08:08 +00:00
2018-12-29 17:40:59 +00:00
let $passwordDecrypt ,
2017-02-13 10:35:04 +00:00
$passwordForm ,
2018-12-29 17:40:59 +00:00
$passwordModal ,
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
* /
2024-01-27 17:26:19 +00:00
function submitPasswordModal ( event )
2017-03-13 19:24:18 +00:00
{
event . preventDefault ( ) ;
// get input
password = $passwordDecrypt . val ( ) ;
// hide modal
$passwordModal . modal ( 'hide' ) ;
2024-01-27 17:26:19 +00:00
PasteDecrypter . run ( ) ;
}
2024-01-08 11:28:41 +00:00
2024-01-27 17:26:19 +00:00
/ * *
* Request users confirmation to load possibly burn after reading paste
*
* @ name Prompt . requestLoadConfirmation
* @ function
* /
me . requestLoadConfirmation = function ( )
{
const $loadconfirmmodal = $ ( '#loadconfirmmodal' ) ;
if ( $loadconfirmmodal . length > 0 ) {
const $loadconfirmOpenNow = $loadconfirmmodal . find ( '#loadconfirm-open-now' ) ;
$loadconfirmOpenNow . off ( 'click.loadPaste' ) ;
$loadconfirmOpenNow . on ( 'click.loadPaste' , PasteDecrypter . run ) ;
const $loadconfirmClose = $loadconfirmmodal . find ( '.close' ) ;
$loadconfirmClose . off ( 'click.close' ) ;
$loadconfirmClose . on ( 'click.close' , Controller . newPaste ) ;
$loadconfirmmodal . modal ( 'show' ) ;
} else {
if ( window . confirm (
2024-03-21 22:25:17 +00:00
I18n . _ ( 'This secret message can only be displayed once. Would you like to see it now?' )
2024-01-27 17:26:19 +00:00
) ) {
PasteDecrypter . run ( ) ;
} else {
Controller . newPaste ( ) ;
2024-01-08 11:28:41 +00:00
}
2024-01-08 09:36:59 +00:00
}
2017-03-13 19:24:18 +00:00
}
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
} ) ;
2024-04-21 09:01:40 +00:00
$passwordModal . modal ( 'show' ) ;
2024-02-06 19:22:47 +00:00
// focus password input
2024-01-27 17:26:19 +00:00
$passwordDecrypt . focus ( ) ;
2024-02-06 19:22:47 +00:00
// then re-focus it, when modal causes it to loose focus again
setTimeout ( function ( ) {
$passwordDecrypt . focus ( ) ;
} , 500 ) ;
2017-02-17 19:46:10 +00:00
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
2024-02-06 19:22:47 +00:00
// bind events - 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
* /
2018-12-29 17:40:59 +00:00
const Editor = ( function ( ) {
const me = { } ;
2017-02-12 17:08:08 +00:00
2018-12-29 17:40:59 +00:00
let $editorTabs ,
2017-02-13 10:35:04 +00:00
$messageEdit ,
2024-04-23 20:11:58 +00:00
$messageEditParent ,
2017-02-13 10:35:04 +00:00
$messagePreview ,
2024-04-23 20:11:58 +00:00
$messagePreviewParent ,
2018-12-29 17:40:59 +00:00
$message ,
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
{
2018-12-29 17:40:59 +00:00
const keyCode = event . keyCode || event . which ;
2015-10-15 20:06:01 +00:00
// 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
2018-12-29 17:40:59 +00:00
const val = this . value ,
start = this . selectionStart ,
end = this . selectionEnd ;
2015-10-15 20:06:01 +00:00
// 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' ) ;
2024-04-23 20:11:58 +00:00
$messageEditParent . addClass ( 'active' ) ;
2017-02-13 10:35:04 +00:00
$messagePreview . removeClass ( 'active' ) ;
2024-04-23 20:11:58 +00:00
$messagePreviewParent . removeClass ( 'active' ) ;
2017-02-13 10:35:04 +00:00
2024-04-18 19:36:43 +00:00
$messageEdit . attr ( 'aria-selected' , 'true' ) ;
$messagePreview . attr ( 'aria-selected' , 'false' ) ;
2020-01-25 17:47:18 +00:00
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' ) ;
2024-04-23 20:11:58 +00:00
$messageEditParent . removeClass ( 'active' ) ;
2017-02-13 10:35:04 +00:00
$messagePreview . addClass ( 'active' ) ;
2024-04-23 20:11:58 +00:00
$messagePreviewParent . addClass ( 'active' ) ;
2017-02-13 10:35:04 +00:00
2024-04-18 19:36:43 +00:00
$messageEdit . attr ( 'aria-selected' , 'false' ) ;
$messagePreview . attr ( 'aria-selected' , 'true' ) ;
2020-01-25 17:47:18 +00:00
2017-02-13 10:35:04 +00:00
// 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 ( ) ) {
2019-06-15 07:35:26 +00:00
const attachment = AttachmentViewer . getAttachment ( ) ;
AttachmentViewer . handleBlobAttachmentPreview (
AttachmentViewer . getAttachmentPreview ( ) ,
attachment [ 0 ] , attachment [ 1 ]
) ;
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
*
2020-05-30 09:47:33 +00:00
* @ name Editor . hide
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
2024-04-18 19:36:43 +00:00
// bind click events to tab switchers (a), and save parents (li)
$messageEdit = $ ( '#messageedit' ) . click ( viewEditor ) ;
2024-04-23 20:11:58 +00:00
$messageEditParent = $messageEdit . parent ( ) ;
2024-04-18 19:36:43 +00:00
$messagePreview = $ ( '#messagepreview' ) . click ( viewPreview ) ;
2024-04-23 20:11:58 +00:00
$messagePreviewParent = $messagePreview . 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
* /
2018-12-29 17:40:59 +00:00
const PasteViewer = ( function ( ) {
const me = { } ;
2017-02-12 17:08:08 +00:00
2018-12-29 17:40:59 +00:00
let $placeholder ,
2017-02-13 10:35:04 +00:00
$prettyMessage ,
2017-02-17 21:46:18 +00:00
$prettyPrint ,
2018-12-29 17:40:59 +00:00
$plainText ,
text ,
2017-02-13 10:35:04 +00:00
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
2020-02-29 07:45:56 +00:00
if ( format === 'markdown' ) {
const converter = new showdown . Converter ( {
strikethrough : true ,
tables : true ,
tablesHeaderId : true ,
simplifiedAutoLink : true ,
excludeTrailingPunctuationFromURLs : true
} ) ;
// let showdown convert the HTML and sanitize HTML *afterwards*!
$plainText . html (
DOMPurify . sanitize (
2022-03-13 18:56:12 +00:00
converter . makeHtml ( text ) ,
purifyHtmlConfig
2020-02-29 07:45:56 +00:00
)
) ;
// add table classes from bootstrap css
$plainText . find ( 'table' ) . addClass ( 'table-condensed table-bordered' ) ;
} else {
if ( format === '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
2020-03-06 21:18:38 +00:00
$prettyPrint . html (
prettyPrintOne (
Helper . htmlEntities ( text ) , null , true
)
2017-02-13 10:35:04 +00:00
) ;
2020-02-29 07:45:56 +00:00
} else {
// = 'plaintext'
2020-03-06 21:18:38 +00:00
$prettyPrint . text ( text ) ;
2020-02-29 07:45:56 +00:00
}
2020-03-06 21:18:38 +00:00
Helper . urls2links ( $prettyPrint ) ;
2020-02-29 07:45:56 +00:00
$prettyPrint . css ( 'white-space' , 'pre-wrap' ) ;
$prettyPrint . css ( 'word-break' , 'normal' ) ;
$prettyPrint . removeClass ( 'prettyprint' ) ;
2017-02-13 10:35:04 +00:00
}
}
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
2024-04-18 19:36:43 +00:00
if ( format === 'markdown' ) {
$plainText . removeClass ( 'hidden' ) ;
$prettyMessage . addClass ( 'hidden' ) ;
} else {
$plainText . addClass ( 'hidden' ) ;
$prettyMessage . removeClass ( 'hidden' ) ;
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 ;
2024-04-18 19:36:43 +00:00
// update preview
if ( Editor . isPreview ( ) ) {
PasteViewer . run ( ) ;
}
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 ) {
2018-12-25 16:34:39 +00:00
return ;
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
// 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
* /
2018-12-29 17:40:59 +00:00
const AttachmentViewer = ( function ( ) {
const me = { } ;
2017-02-14 21:21:55 +00:00
2018-12-29 17:40:59 +00:00
let $attachmentLink ,
$attachmentPreview ,
$attachment ,
attachmentData ,
file ,
$fileInput ,
$dragAndDropFileName ,
2019-08-15 23:28:42 +00:00
attachmentHasPreview = false ,
$dropzone ;
2017-02-14 21:21:55 +00:00
/ * *
2022-03-13 18:56:12 +00:00
* get blob URL from string data and mime type
*
* @ name AttachmentViewer . getBlobUrl
* @ private
* @ function
* @ param { string } data - raw data of attachment
* @ param { string } data - mime type of attachment
* @ return { string } objectURL
* /
function getBlobUrl ( data , mimeType )
{
// Transform into a Blob
const buf = new Uint8Array ( data . length ) ;
for ( let i = 0 ; i < data . length ; ++ i ) {
buf [ i ] = data . charCodeAt ( i ) ;
}
const blob = new window . Blob (
[ buf ] ,
{
type : mimeType
}
) ;
2022-03-27 05:58:25 +00:00
// Get blob URL
2022-03-13 18:56:12 +00:00
return window . URL . createObjectURL ( blob ) ;
}
/ * *
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 )
{
2022-03-13 19:18:51 +00:00
// skip, if attachments got disabled
if ( ! $attachmentLink || ! $attachmentPreview ) return ;
2022-03-13 18:56:12 +00:00
// data URI format: data:[<mimeType>][;base64],<data>
2019-06-12 01:37:17 +00:00
// position in data URI string of where data begins
const base64Start = attachmentData . indexOf ( ',' ) + 1 ;
2022-03-13 18:56:12 +00:00
// position in data URI string of where mimeType ends
const mimeTypeEnd = attachmentData . indexOf ( ';' ) ;
2019-06-12 01:37:17 +00:00
2022-03-13 18:56:12 +00:00
// extract mimeType
const mimeType = attachmentData . substring ( 5 , mimeTypeEnd ) ;
2019-06-12 01:37:17 +00:00
// extract data and convert to binary
2020-06-30 18:10:56 +00:00
const rawData = attachmentData . substring ( base64Start ) ;
const decodedData = rawData . length > 0 ? atob ( rawData ) : '' ;
2019-06-12 01:37:17 +00:00
2022-03-13 18:56:12 +00:00
let blobUrl = getBlobUrl ( decodedData , mimeType ) ;
$attachmentLink . attr ( 'href' , blobUrl ) ;
2017-02-14 21:21:55 +00:00
if ( typeof fileName !== 'undefined' ) {
$attachmentLink . attr ( 'download' , fileName ) ;
}
2022-03-13 18:56:12 +00:00
// sanitize SVG preview
// prevents executing embedded scripts when CSP is not set and user
// right-clicks/long-taps and opens the SVG in a new tab - prevented
// in the preview by use of an img tag, which disables scripts, too
2022-03-27 06:27:24 +00:00
if ( mimeType . match ( /^image\/.*svg/i ) ) {
2022-03-13 18:56:12 +00:00
const sanitizedData = DOMPurify . sanitize (
decodedData ,
purifySvgConfig
) ;
blobUrl = getBlobUrl ( sanitizedData , mimeType ) ;
}
me . handleBlobAttachmentPreview ( $attachmentPreview , blobUrl , mimeType ) ;
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 ( )
{
2022-03-13 19:18:51 +00:00
// skip, if attachments got disabled
if ( ! $attachment || ! $attachmentPreview ) return ;
2017-02-14 21:21:55 +00:00
$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 ( '' ) ;
2019-08-18 02:17:35 +00:00
$dragAndDropFileName . text ( '' ) ;
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 ;
}
2018-12-29 17:40:59 +00:00
const 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 ) ;
2019-12-25 08:14:32 +00:00
// update text - ensuring no HTML is inserted into the text node
2020-01-04 10:34:16 +00:00
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 ( ) ;
2019-08-27 05:38:27 +00:00
Alert . showWarning ( '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
2018-12-29 17:40:59 +00:00
const 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
2019-08-15 23:28:42 +00:00
if ( typeof loadedFile !== 'undefined' ) {
2019-08-16 17:38:08 +00:00
file = loadedFile ;
2019-08-15 23:28:42 +00:00
fileReader . onload = function ( event ) {
const dataURL = event . target . result ;
attachmentData = dataURL ;
2017-05-13 17:46:22 +00:00
2019-08-15 23:28:42 +00:00
if ( Editor . isPreview ( ) ) {
me . handleAttachmentPreview ( $attachmentPreview , dataURL ) ;
$attachmentPreview . removeClass ( 'hidden' ) ;
}
TopNav . highlightFileupload ( ) ;
} ;
fileReader . readAsDataURL ( loadedFile ) ;
2019-08-16 17:38:08 +00:00
} else {
me . removeAttachmentData ( ) ;
2019-08-15 23:28:42 +00:00
}
2018-05-22 08:19:53 +00:00
}
2017-05-13 17:46:22 +00:00
2019-06-12 02:29:19 +00:00
/ * *
* handle the preview of files decoded to blob that can either be an image , video , audio or pdf element
*
* @ name AttachmentViewer . handleBlobAttachmentPreview
* @ function
* @ argument { jQuery } $targetElement element where the preview should be appended
* @ argument { string } file as a blob URL
* @ argument { string } mime type
* /
me . handleBlobAttachmentPreview = function ( $targetElement , blobUrl , mimeType ) {
if ( blobUrl ) {
attachmentHasPreview = true ;
2022-03-27 06:27:24 +00:00
if ( mimeType . match ( /^image\//i ) ) {
2019-06-12 02:29:19 +00:00
$targetElement . html (
$ ( document . createElement ( 'img' ) )
. attr ( 'src' , blobUrl )
. attr ( 'class' , 'img-thumbnail' )
) ;
2022-03-27 06:27:24 +00:00
} else if ( mimeType . match ( /^video\//i ) ) {
2019-06-12 02:29:19 +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' , blobUrl ) )
) ;
2022-03-27 06:27:24 +00:00
} else if ( mimeType . match ( /^audio\//i ) ) {
2019-06-12 02:29:19 +00:00
$targetElement . html (
$ ( document . createElement ( 'audio' ) )
. attr ( 'controls' , 'true' )
. attr ( 'autoplay' , 'true' )
. append ( $ ( document . createElement ( 'source' ) )
. attr ( 'type' , mimeType )
. attr ( 'src' , blobUrl ) )
) ;
} else if ( mimeType . match ( /\/pdf/i ) ) {
// Fallback for browsers, that don't support the vh unit
2019-08-15 23:28:42 +00:00
const clientHeight = $ ( window ) . height ( ) ;
2019-06-12 02:29:19 +00:00
$targetElement . html (
$ ( document . createElement ( 'embed' ) )
. attr ( 'src' , blobUrl )
. attr ( 'type' , 'application/pdf' )
. attr ( 'class' , 'pdfPreview' )
. css ( 'height' , clientHeight )
) ;
} else {
attachmentHasPreview = false ;
}
}
} ;
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 ;
}
2019-08-15 23:28:42 +00:00
const handleDragEnterOrOver = function ( event ) {
2017-05-13 17:46:22 +00:00
event . stopPropagation ( ) ;
event . preventDefault ( ) ;
2019-08-18 02:17:35 +00:00
return false ;
2017-05-13 17:46:22 +00:00
} ;
2019-08-15 23:28:42 +00:00
const handleDrop = function ( event ) {
2018-12-29 17:40:59 +00:00
const evt = event . originalEvent ;
2017-05-15 20:05:52 +00:00
evt . stopPropagation ( ) ;
evt . preventDefault ( ) ;
2017-05-13 17:46:22 +00:00
2019-08-25 16:00:59 +00:00
if ( TopNav . isAttachmentReadonly ( ) ) {
2019-08-18 02:17:35 +00:00
return false ;
}
2017-05-15 20:05:52 +00:00
if ( $fileInput ) {
2018-12-29 17:40:59 +00:00
const 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
}
} ;
2019-08-15 23:28:42 +00:00
$ ( document ) . draghover ( ) . on ( {
2019-08-18 02:17:35 +00:00
'draghoverstart' : function ( e ) {
2019-08-25 16:00:59 +00:00
if ( TopNav . isAttachmentReadonly ( ) ) {
2019-08-18 02:17:35 +00:00
e . stopPropagation ( ) ;
e . preventDefault ( ) ;
return false ;
}
2019-08-15 23:28:42 +00:00
// show dropzone to indicate drop support
$dropzone . removeClass ( 'hidden' ) ;
} ,
'draghoverend' : function ( ) {
$dropzone . addClass ( 'hidden' ) ;
}
} ) ;
$ ( document ) . on ( 'drop' , handleDrop ) ;
$ ( document ) . on ( 'dragenter dragover' , handleDragEnterOrOver ) ;
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 ) {
2018-12-29 17:40:59 +00:00
const items = ( event . clipboardData || event . originalEvent . clipboardData ) . items ;
2020-05-17 05:35:19 +00:00
const lastItem = items [ items . length - 1 ] ;
if ( lastItem . kind === 'file' ) {
if ( TopNav . isAttachmentReadonly ( ) ) {
event . stopPropagation ( ) ;
event . preventDefault ( ) ;
return false ;
} else {
readFileData ( lastItem . 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' ) ;
2019-08-28 18:29:23 +00:00
$dragAndDropFileName = $ ( '#dragAndDropFileName' ) ;
$dropzone = $ ( '#dropzone' ) ;
2020-01-04 12:33:03 +00:00
$attachmentLink = $ ( '#attachment a' ) || $ ( '<a>' ) ;
2019-08-28 18:29:23 +00:00
if ( $attachment . length ) {
2017-05-15 20:05:52 +00:00
$attachmentPreview = $ ( '#attachmentPreview' ) ;
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
* /
2018-12-29 17:40:59 +00:00
const DiscussionViewer = ( function ( ) {
const me = { } ;
2017-02-14 21:21:55 +00:00
2018-12-29 17:40:59 +00:00
let $commentTail ,
2017-02-17 19:46:10 +00:00
$discussion ,
$reply ,
$replyMessage ,
$replyNickname ,
2017-02-17 21:46:18 +00:00
$replyStatus ,
2018-12-29 17:40:59 +00:00
$commentContainer ,
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 )
{
2018-12-29 17:40:59 +00:00
const $source = $ ( event . target ) ;
2017-03-13 19:24:18 +00:00
// 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
2019-05-25 11:20:39 +00:00
* @ param { Comment } comment
2017-02-17 19:46:10 +00:00
* @ 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
2018-12-29 17:40:59 +00:00
const $commentEntry = Model . getTemplate ( 'comment' ) ;
2017-02-17 19:46:10 +00:00
$commentEntry . prop ( 'id' , 'comment_' + comment . id ) ;
2018-12-29 17:40:59 +00:00
const $commentEntryData = $commentEntry . find ( 'div.commentdata' ) ;
2017-02-17 19:46:10 +00:00
// set & parse text
2020-03-06 21:18:38 +00:00
$commentEntryData . text ( commentText ) ;
Helper . urls2links ( $commentEntryData ) ;
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
2024-05-04 12:38:41 +00:00
const created = comment . getCreated ( ) ;
const commentDate = created == 0 ? '' : ' (' + ( new Date ( created * 1000 ) . toLocaleString ( ) ) + ')' ;
2017-02-17 19:46:10 +00:00
$commentEntry . find ( 'span.commentdate' )
2024-05-04 12:38:41 +00:00
. text ( commentDate )
. attr ( 'title' , 'CommentID: ' + comment . id ) ;
2017-02-17 19:46:10 +00:00
// if an avatar is available, display it
2019-05-25 11:20:39 +00:00
const icon = comment . getIcon ( ) ;
if ( icon ) {
2017-02-17 19:46:10 +00:00
$commentEntry . find ( 'span.nickname' )
2017-03-12 16:08:12 +00:00
. before (
2019-05-25 11:20:39 +00:00
'<img src="' + icon + '" class="vizhash" /> '
2017-03-12 16:08:12 +00:00
) ;
$ ( 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)
2018-12-29 17:40:59 +00:00
let $place = $commentContainer ;
2018-01-02 10:42:03 +00:00
// if parent comment exists
2018-12-29 17:40:59 +00:00
const $parentComment = $ ( '#comment_' + comment . parentid ) ;
2018-01-02 10:42:03 +00:00
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 )
{
2018-12-29 17:40:59 +00:00
const $comment = $ ( '#comment_' + commentId ) ;
2017-02-17 19:46:10 +00:00
// in case comment does not exist, cancel
if ( $comment . length === 0 ) {
return ;
}
2018-07-01 06:59:55 +00:00
$comment . addClass ( 'highlight' ) ;
2018-12-29 17:40:59 +00:00
const highlightComment = function ( ) {
2017-02-17 19:46:10 +00:00
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
* /
2018-12-29 17:40:59 +00:00
const TopNav = ( function ( window , document ) {
const me = { } ;
2017-02-12 20:13:04 +00:00
2018-12-29 17:40:59 +00:00
let createButtonsDisplayed = false ,
viewButtonsDisplayed = false ,
2021-04-05 11:24:53 +00:00
burnAfterReadingDefault = false ,
openDiscussionDefault = false ,
2018-12-29 17:40:59 +00:00
$attach ,
2017-02-12 17:08:08 +00:00
$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 ,
2021-04-17 06:51:25 +00:00
$downloadTextButton ,
2017-12-25 13:59:15 +00:00
$qrCodeLink ,
2019-08-21 21:36:22 +00:00
$emailLink ,
2017-04-11 20:21:30 +00:00
$sendButton ,
2018-12-29 17:40:59 +00:00
$retryButton ,
2019-08-21 21:36:22 +00:00
pasteExpiration = null ,
2017-04-11 20:21:30 +00:00
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
2018-12-29 17:40:59 +00:00
const 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
}
/ * *
2019-08-18 02:17:35 +00:00
* set the format on bootstrap templates in dropdown from user interaction
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
2018-12-29 17:40:59 +00:00
const $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
2018-12-29 17:40:59 +00:00
const newFormat = $target . data ( 'format' ) ;
2017-02-13 10:35:04 +00:00
$ ( '#pasteFormatterDisplay' ) . text ( $target . text ( ) ) ;
2017-02-14 21:21:55 +00:00
PasteViewer . setFormat ( newFormat ) ;
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
{
2023-06-18 11:47:54 +00:00
if ( me . getBurnAfterReading ( ) ) {
2017-02-13 10:35:04 +00:00
$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
{
2023-06-18 11:47:54 +00:00
if ( me . getOpenDiscussion ( ) ) {
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
}
2020-04-23 10:07:08 +00:00
/ * *
* Clear the attachment input in the top navigation .
*
* @ name TopNav . clearAttachmentInput
* @ function
* /
function clearAttachmentInput ( )
{
// hide UI for selected files
// our up-to-date jQuery can handle it :)
$fileWrap . find ( 'input' ) . val ( '' ) ;
}
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' ) ;
2018-12-29 17:40:59 +00:00
let paste = PasteViewer . getText ( ) ;
2017-02-15 21:59:55 +00:00
// 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 ( ) + '#' +
2019-05-15 19:20:54 +00:00
CryptTool . base58encode ( 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)
2018-12-29 17:40:59 +00:00
const $head = $ ( 'head' ) . children ( ) . not ( 'noscript, script, link[type="text/css"]' ) ,
newDoc = document . open ( 'text/html' , 'replace' ) ;
2017-02-17 21:26:39 +00:00
newDoc . write ( '<!DOCTYPE html><html><head>' ) ;
2018-12-29 17:40:59 +00:00
for ( let i = 0 ; i < $head . length ; ++ i ) {
2017-02-17 21:26:39 +00:00
newDoc . write ( $head [ i ] . outerHTML ) ;
}
2022-03-13 18:56:12 +00:00
newDoc . write (
'</head><body><pre>' +
DOMPurify . sanitize (
Helper . htmlEntities ( paste ) ,
purifyHtmlConfig
) +
'</pre></body></html>'
) ;
2017-02-12 17:08:08 +00:00
newDoc . close ( ) ;
}
2021-04-14 01:11:58 +00:00
/ * *
* download text
*
* @ name TopNav . downloadText
* @ private
* @ function
* /
function downloadText ( )
{
2021-04-17 07:08:11 +00:00
var filename = 'paste-' + Model . getPasteId ( ) + '.txt' ;
2021-04-17 06:51:25 +00:00
var text = PasteViewer . getText ( ) ;
2021-04-14 01:11:58 +00:00
2021-04-17 06:51:25 +00:00
var element = document . createElement ( 'a' ) ;
element . setAttribute ( 'href' , 'data:text/plain;charset=utf-8,' + encodeURIComponent ( text ) ) ;
element . setAttribute ( 'download' , filename ) ;
2021-04-14 01:11:58 +00:00
2021-04-17 06:51:25 +00:00
element . style . display = 'none' ;
document . body . appendChild ( element ) ;
2021-04-14 01:11:58 +00:00
2021-04-17 06:51:25 +00:00
element . click ( ) ;
2021-04-14 01:11:58 +00:00
2021-04-17 06:51:25 +00:00
document . body . removeChild ( element ) ;
}
2021-04-14 01:11:58 +00:00
2017-02-12 17:08:08 +00:00
/ * *
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 )
{
2024-05-04 10:12:31 +00:00
document . cookie = 'lang=' + $ ( event . target ) . data ( 'lang' ) + '; SameSite=Lax; Secure' ;
2017-02-14 21:21:55 +00:00
UiHelper . reloadHome ( ) ;
2024-01-27 17:26:19 +00:00
event . preventDefault ( ) ;
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 ( ) ;
2020-04-23 10:07:08 +00:00
clearAttachmentInput ( ) ;
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
{
2018-12-29 17:40:59 +00:00
const qrCanvas = kjua ( {
2017-12-25 13:59:15 +00:00
render : 'canvas' ,
text : window . location . href
} ) ;
$ ( '#qrcode-display' ) . html ( qrCanvas ) ;
2024-04-21 09:01:40 +00:00
// only necessary for bootstrap 5, other templates won't have this
if ( bootstrap . Tooltip . VERSION ) {
$ ( '#qrcodemodal' ) . modal ( 'show' ) ;
}
2018-01-06 08:58:19 +00:00
}
2017-12-25 13:59:15 +00:00
2019-08-21 21:36:22 +00:00
/ * *
* Template Email body .
2020-04-23 09:25:24 +00:00
*
2019-08-21 21:36:22 +00:00
* @ name TopNav . templateEmailBody
2020-04-23 09:25:24 +00:00
* @ private
* @ param { string } expirationDateString
* @ param { bool } isBurnafterreading
2019-08-21 21:36:22 +00:00
* /
function templateEmailBody ( expirationDateString , isBurnafterreading )
{
const EOL = '\n' ;
const BULLET = ' - ' ;
let emailBody = '' ;
if ( expirationDateString !== null || isBurnafterreading ) {
emailBody += I18n . _ ( 'Notice:' ) ;
emailBody += EOL ;
if ( expirationDateString !== null ) {
emailBody += EOL ;
emailBody += BULLET ;
2020-05-30 09:52:15 +00:00
// avoid DOMPurify mess with forward slash in expirationDateString
emailBody += Helper . sprintf (
I18n . _ (
'This link will expire after %s.' ,
'%s'
) ,
2019-08-21 21:36:22 +00:00
expirationDateString
) ;
}
if ( isBurnafterreading ) {
emailBody += EOL ;
emailBody += BULLET ;
emailBody += I18n . _ (
'This link can only be accessed once, do not use back or refresh button in your browser.'
) ;
}
emailBody += EOL ;
emailBody += EOL ;
}
emailBody += I18n . _ ( 'Link:' ) ;
emailBody += EOL ;
2024-03-10 16:26:30 +00:00
emailBody += $ ( '#pasteurl' ) . attr ( 'href' ) ; // might have been shortened
2019-08-21 21:36:22 +00:00
return emailBody ;
}
/ * *
* Trigger Email send .
2020-04-23 09:25:24 +00:00
*
2019-08-21 21:36:22 +00:00
* @ name TopNav . triggerEmailSend
2020-04-23 09:25:24 +00:00
* @ private
* @ param { string } emailBody
2019-08-21 21:36:22 +00:00
* /
function triggerEmailSend ( emailBody )
{
window . open (
` mailto:?body= ${ encodeURIComponent ( emailBody ) } ` ,
'_self' ,
'noopener, noreferrer'
) ;
}
/ * *
* Send Email with current paste ( URL ) .
*
* @ name TopNav . sendEmail
* @ private
* @ function
* @ param { Date | null } expirationDate date of expiration
* @ param { bool } isBurnafterreading whether it is burn after reading
* /
function sendEmail ( expirationDate , isBurnafterreading )
{
const expirationDateRoundedToSecond = new Date ( expirationDate ) ;
// round down at least 30 seconds to make up for the delay of request
expirationDateRoundedToSecond . setUTCSeconds (
expirationDateRoundedToSecond . getUTCSeconds ( ) - 30
) ;
expirationDateRoundedToSecond . setUTCSeconds ( 0 ) ;
const $emailconfirmmodal = $ ( '#emailconfirmmodal' ) ;
if ( $emailconfirmmodal . length > 0 ) {
if ( expirationDate !== null ) {
const $emailconfirmTimezoneCurrent = $emailconfirmmodal . find ( '#emailconfirm-timezone-current' ) ;
const $emailconfirmTimezoneUtc = $emailconfirmmodal . find ( '#emailconfirm-timezone-utc' ) ;
$emailconfirmTimezoneCurrent . off ( 'click.sendEmailCurrentTimezone' ) ;
2019-11-21 17:25:32 +00:00
$emailconfirmTimezoneCurrent . on ( 'click.sendEmailCurrentTimezone' , ( ) => {
const emailBody = templateEmailBody ( expirationDateRoundedToSecond . toLocaleString ( ) , isBurnafterreading ) ;
$emailconfirmmodal . modal ( 'hide' ) ;
triggerEmailSend ( emailBody ) ;
} ) ;
2019-08-21 21:36:22 +00:00
$emailconfirmTimezoneUtc . off ( 'click.sendEmailUtcTimezone' ) ;
2019-11-21 17:25:32 +00:00
$emailconfirmTimezoneUtc . on ( 'click.sendEmailUtcTimezone' , ( ) => {
const emailBody = templateEmailBody ( expirationDateRoundedToSecond . toLocaleString (
undefined ,
// we don't use Date.prototype.toUTCString() because we would like to avoid GMT
{ timeZone : 'UTC' , dateStyle : 'long' , timeStyle : 'long' }
) , isBurnafterreading ) ;
$emailconfirmmodal . modal ( 'hide' ) ;
triggerEmailSend ( emailBody ) ;
} ) ;
2019-08-21 21:36:22 +00:00
$emailconfirmmodal . modal ( 'show' ) ;
} else {
triggerEmailSend ( templateEmailBody ( null , isBurnafterreading ) ) ;
}
} else {
let emailBody = '' ;
if ( expirationDate !== null ) {
const expirationDateString = window . confirm (
I18n . _ ( 'Recipient may become aware of your timezone, convert time to UTC?' )
) ? expirationDateRoundedToSecond . toLocaleString (
undefined ,
// we don't use Date.prototype.toUTCString() because we would like to avoid GMT
{ timeZone : 'UTC' , dateStyle : 'long' , timeStyle : 'long' }
) : expirationDateRoundedToSecond . toLocaleString ( ) ;
emailBody = templateEmailBody ( expirationDateString , isBurnafterreading ) ;
} else {
emailBody = templateEmailBody ( null , isBurnafterreading ) ;
}
triggerEmailSend ( emailBody ) ;
}
}
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 ) {
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' ) ;
2021-04-14 01:11:58 +00:00
$downloadTextButton . 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 ) {
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' ) ;
2021-04-14 01:11:58 +00:00
$downloadTextButton . addClass ( 'hidden' ) ;
2017-12-25 13:59:15 +00:00
$qrCodeLink . addClass ( 'hidden' ) ;
2019-08-21 21:36:22 +00:00
me . hideEmailButton ( ) ;
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 ) {
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 ) {
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' ) ;
}
2019-08-21 21:36:22 +00:00
/ * *
* show the "email" button
2020-04-23 09:25:24 +00:00
*
2019-08-21 21:36:22 +00:00
* @ name TopNav . showEmailbutton
* @ function
* @ param { int | undefined } optionalRemainingTimeInSeconds
* /
me . showEmailButton = function ( optionalRemainingTimeInSeconds )
{
try {
// we cache expiration date in closure to avoid inaccurate expiration datetime
const expirationDate = Helper . calculateExpirationDate (
new Date ( ) ,
typeof optionalRemainingTimeInSeconds === 'number' ? optionalRemainingTimeInSeconds : TopNav . getExpiration ( )
) ;
const isBurnafterreading = TopNav . getBurnAfterReading ( ) ;
$emailLink . removeClass ( 'hidden' ) ;
$emailLink . off ( 'click.sendEmail' ) ;
2019-11-21 17:25:32 +00:00
$emailLink . on ( 'click.sendEmail' , ( ) => {
sendEmail ( expirationDate , isBurnafterreading ) ;
} ) ;
2019-08-21 21:36:22 +00:00
} catch ( error ) {
console . error ( error ) ;
2020-01-25 07:13:36 +00:00
Alert . showError ( 'Cannot calculate expiration date.' ) ;
2019-08-21 21:36:22 +00:00
}
}
/ * *
* hide the "email" button
2020-04-23 09:25:24 +00:00
*
2019-08-21 21:36:22 +00:00
* @ name TopNav . hideEmailButton
* @ function
* /
me . hideEmailButton = function ( )
{
$emailLink . addClass ( 'hidden' ) ;
$emailLink . off ( 'click.sendEmail' ) ;
}
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
2021-04-14 01:11:58 +00:00
/ * *
* only hides the download text button
*
* @ name TopNav . hideRawButton
* @ function
* /
me . hideDownloadButton = function ( )
{
$downloadTextButton . addClass ( 'hidden' ) ;
} ;
2019-08-21 21:36:22 +00:00
/ * *
* only hides the qr code button
2020-04-23 09:25:24 +00:00
*
2019-08-21 21:36:22 +00:00
* @ name TopNav . hideQrCodeButton
* @ function
* /
me . hideQrCodeButton = function ( )
{
$qrCodeLink . addClass ( 'hidden' ) ;
}
/ * *
* hide all irrelevant buttons when viewing burn after reading paste
2020-04-23 09:25:24 +00:00
*
2019-08-21 21:36:22 +00:00
* @ name TopNav . hideBurnAfterReadingButtons
* @ function
* /
me . hideBurnAfterReadingButtons = function ( )
{
me . hideCloneButton ( ) ;
me . hideQrCodeButton ( ) ;
me . hideEmailButton ( ) ;
}
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
2019-08-18 02:17:35 +00:00
/ * *
* hides the custom attachment
2020-04-23 09:25:24 +00:00
*
2019-08-18 02:17:35 +00:00
* @ name TopNav . hideCustomAttachment
* @ function
* /
me . hideCustomAttachment = function ( )
{
$customAttachment . addClass ( 'hidden' ) ;
$fileWrap . removeClass ( 'hidden' ) ;
} ;
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
2020-04-23 09:25:24 +00:00
/ * *
2020-04-23 10:07:08 +00:00
* Reset the top navigation back to it ' s default values .
2020-04-23 09:25:24 +00:00
*
2020-04-23 10:07:08 +00:00
* @ name TopNav . resetInput
2020-04-23 09:25:24 +00:00
* @ function
* /
2020-04-23 10:07:08 +00:00
me . resetInput = function ( )
2020-04-23 09:25:24 +00:00
{
2020-04-23 10:07:08 +00:00
clearAttachmentInput ( ) ;
2021-04-05 11:24:53 +00:00
$burnAfterReading . prop ( 'checked' , burnAfterReadingDefault ) ;
$openDiscussion . prop ( 'checked' , openDiscussionDefault ) ;
2021-04-05 11:47:37 +00:00
if ( openDiscussionDefault || ! burnAfterReadingDefault ) $openDiscussionOption . removeClass ( 'buttondisabled' ) ;
if ( burnAfterReadingDefault || ! openDiscussionDefault ) $burnAfterReadingOption . removeClass ( 'buttondisabled' ) ;
pasteExpiration = Model . getExpirationDefault ( ) || pasteExpiration ;
$ ( '#pasteExpiration>option' ) . each ( function ( ) {
const $this = $ ( this ) ;
if ( $this . val ( ) === pasteExpiration ) {
$ ( '#pasteExpirationDisplay' ) . text ( $this . text ( ) ) ;
}
} ) ;
2020-04-23 09:25:24 +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 ( )
{
2024-04-18 19:36:43 +00:00
return Model . getExpirationDefault ( ) || 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 ( )
{
2018-12-29 17:40:59 +00:00
const $file = $ ( '#file' ) ;
2017-02-13 20:12:00 +00:00
// 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
*
2019-08-21 21:36:22 +00:00
* @ name TopNav . getBurnAfterReading
2017-02-13 20:12:00 +00:00
* @ function
* @ return { bool }
* /
me . getBurnAfterReading = function ( )
{
2023-06-18 11:47:54 +00:00
return $burnAfterReading . prop ( '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 ( )
{
2023-06-18 11:47:54 +00:00
return $openDiscussion . prop ( '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 ( )
{
2019-10-25 17:05:09 +00:00
// when password is disabled $passwordInput.val() will return undefined
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 ;
}
2019-08-15 23:28:42 +00:00
/ * *
* Highlight file upload
2020-04-23 09:25:24 +00:00
*
2019-08-15 23:28:42 +00:00
* @ name TopNav . highlightFileupload
* @ function
* /
me . highlightFileupload = function ( )
{
// visually indicate file uploaded
const $attachDropdownToggle = $attach . children ( '.dropdown-toggle' ) ;
if ( $attachDropdownToggle . attr ( 'aria-expanded' ) === 'false' ) {
$attachDropdownToggle . click ( ) ;
}
$fileWrap . addClass ( 'highlight' ) ;
setTimeout ( function ( ) {
$fileWrap . removeClass ( 'highlight' ) ;
} , 300 ) ;
}
2019-08-18 02:17:35 +00:00
/ * *
* set the format on bootstrap templates in dropdown programmatically
2020-04-23 09:25:24 +00:00
*
2019-08-18 02:17:35 +00:00
* @ name TopNav . setFormat
* @ function
* /
me . setFormat = function ( format )
{
$formatter . parent ( ) . find ( ` a[data-format=" ${ format } "] ` ) . click ( ) ;
}
2019-08-25 16:00:59 +00:00
/ * *
* returns if attachment dropdown is readonly , not editable
2020-04-23 09:25:24 +00:00
*
2019-08-25 16:00:59 +00:00
* @ name TopNav . isAttachmentReadonly
* @ function
* @ return { bool }
* /
me . isAttachmentReadonly = function ( )
{
2020-05-30 09:48:15 +00:00
return ! createButtonsDisplayed || $attach . hasClass ( 'hidden' ) ;
2019-08-25 16:00:59 +00:00
}
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' ) ;
2021-04-14 01:11:58 +00:00
$downloadTextButton = $ ( '#downloadtextbutton' ) ;
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' ) ;
2019-08-21 21:36:22 +00:00
$emailLink = $ ( '#emaillink' ) ;
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 ) ;
2021-04-14 01:11:58 +00:00
$downloadTextButton . click ( downloadText ) ;
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 ) ;
2024-04-18 19:36:43 +00:00
$ ( '#pasteFormatter' ) . on ( 'change' , function ( ) {
PasteViewer . setFormat ( Model . getFormatDefault ( ) ) ;
} ) ;
2017-02-13 10:35:04 +00:00
2017-02-12 17:08:08 +00:00
// initiate default state of checkboxes
changeBurnAfterReading ( ) ;
2017-02-13 10:35:04 +00:00
changeOpenDiscussion ( ) ;
2021-04-05 11:24:53 +00:00
// get default values from template or fall back to set value
burnAfterReadingDefault = me . getBurnAfterReading ( ) ;
openDiscussionDefault = me . getOpenDiscussion ( ) ;
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
*
2018-12-25 16:34:39 +00:00
* @ name ServerInteraction
2017-02-12 17:08:08 +00:00
* @ class
* /
2018-12-29 17:40:59 +00:00
const ServerInteraction = ( function ( ) {
const me = { } ;
2017-02-12 17:08:08 +00:00
2018-12-29 17:40:59 +00:00
let successFunc = null ,
2017-02-14 21:21:55 +00:00
failureFunc = null ,
2018-12-25 16:34:39 +00:00
symmetricKey = null ,
2017-02-14 21:21:55 +00:00
url ,
data ,
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
*
2018-12-25 16:34:39 +00:00
* @ name ServerInteraction . 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
*
2018-12-25 16:34:39 +00:00
* @ name ServerInteraction . 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 }
* /
2018-12-29 17:40:59 +00:00
const 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
*
2018-12-25 16:34:39 +00:00
* @ name ServerInteraction . 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 )
{
if ( successFunc !== null ) {
2019-05-25 11:20:39 +00:00
// add useful data to result
result . encryptionKey = symmetricKey ;
2017-02-13 20:12:00 +00:00
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
*
2018-12-25 16:34:39 +00:00
* @ name ServerInteraction . 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
*
2018-12-25 16:34:39 +00:00
* @ name ServerInteraction . 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
{
2019-05-11 08:39:42 +00:00
let isPost = Object . keys ( data ) . length > 0 ,
ajaxParams = {
2019-05-13 20:31:52 +00:00
type : isPost ? 'POST' : 'GET' ,
url : url ,
headers : ajaxHeaders ,
dataType : 'json' ,
success : function ( result ) {
if ( result . status === 0 ) {
success ( 0 , result ) ;
} else if ( result . status === 1 ) {
fail ( 1 , result ) ;
} else {
fail ( 2 , result ) ;
}
2017-02-13 20:12:00 +00:00
}
2019-05-13 20:31:52 +00:00
} ;
2019-05-11 08:39:42 +00:00
if ( isPost ) {
2019-05-13 20:31:52 +00:00
ajaxParams . data = JSON . stringify ( data ) ;
2019-05-11 08:39:42 +00:00
}
$ . ajax ( ajaxParams ) . fail ( function ( jqXHR , textStatus , errorThrown ) {
2017-02-13 20:12:00 +00:00
console . error ( textStatus , errorThrown ) ;
fail ( 3 , jqXHR ) ;
} ) ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-13 20:12:00 +00:00
2019-04-16 05:45:04 +00:00
/ * *
* return currently set data , used in unit testing
*
* @ name ServerInteraction . getData
* @ function
* /
me . getData = function ( )
{
return data ;
} ;
2017-02-13 20:12:00 +00:00
/ * *
* set success function
*
2018-12-25 16:34:39 +00:00
* @ name ServerInteraction . 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
2018-12-25 16:34:39 +00:00
* encryption key ( not recommended , it is automatically generated ) .
2017-02-17 19:46:10 +00:00
*
* Note : Call this after prepare ( ) as prepare ( ) resets these values .
*
2018-12-25 16:34:39 +00:00
* @ name ServerInteraction . setCryptValues
2017-02-17 19:46:10 +00:00
* @ 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
*
2018-12-25 16:34:39 +00:00
* @ name ServerInteraction . 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
*
2018-12-25 16:34:39 +00:00
* @ name ServerInteraction . 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 .
*
2018-12-25 16:34:39 +00:00
* @ name ServerInteraction . 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
*
2018-12-25 16:34:39 +00:00
* @ name ServerInteraction . setCipherMessage
2018-10-08 19:03:10 +00:00
* @ async
2017-02-13 20:12:00 +00:00
* @ function
2018-12-25 16:34:39 +00:00
* @ param { object } cipherMessage
2017-02-13 20:12:00 +00:00
* /
2018-12-25 16:34:39 +00:00
me . setCipherMessage = async function ( cipherMessage )
2017-02-13 20:12:00 +00:00
{
2018-12-25 16:34:39 +00:00
if (
symmetricKey === null ||
( typeof symmetricKey === 'string' && symmetricKey === '' )
) {
symmetricKey = CryptTool . getSymmetricKey ( ) ;
}
if ( ! data . hasOwnProperty ( 'adata' ) ) {
data [ 'adata' ] = [ ] ;
}
let cipherResult = await CryptTool . cipher ( symmetricKey , password , JSON . stringify ( cipherMessage ) , data [ 'adata' ] ) ;
data [ 'v' ] = 2 ;
data [ 'ct' ] = cipherResult [ 0 ] ;
data [ 'adata' ] = cipherResult [ 1 ] ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-13 20:12:00 +00:00
/ * *
* set the additional metadata to send unencrypted
*
2018-12-25 16:34:39 +00:00
* @ name ServerInteraction . 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
/ * *
2018-12-25 16:34:39 +00:00
* Helper , which parses shows a general error message based on the result of the ServerInteraction
2017-02-13 20:12:00 +00:00
*
2018-12-25 16:34:39 +00:00
* @ name ServerInteraction . parseUploadError
2017-02-17 19:46:10 +00:00
* @ 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 ) {
2018-12-29 17:40:59 +00:00
let 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
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
*
2018-12-25 16:34:39 +00:00
* Does upload , encryption is done transparently by ServerInteraction .
2017-02-17 19:46:10 +00:00
*
2017-03-13 19:24:18 +00:00
* @ name PasteEncrypter
2017-02-13 20:12:00 +00:00
* @ class
* /
2018-12-29 17:40:59 +00:00
const PasteEncrypter = ( function ( ) {
const me = { } ;
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
* /
2024-01-27 17:26:19 +00:00
function showCreatedPaste ( status , data ) {
2017-02-15 21:59:55 +00:00
Alert . hideLoading ( ) ;
2017-02-14 21:21:55 +00:00
Alert . hideMessages ( ) ;
2017-02-13 20:12:00 +00:00
// show notification
2024-01-27 17:26:19 +00:00
const baseUri = Helper . baseUri ( ) + '?' ,
url = baseUri + data . id + ( TopNav . getBurnAfterReading ( ) ? loadConfirmPrefix : '#' ) + CryptTool . base58encode ( data . encryptionKey ) ,
deleteUrl = baseUri + 'pasteid=' + data . id + '&deletetoken=' + data . deletetoken ;
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 ( ) ;
2019-08-21 21:36:22 +00:00
// this cannot be grouped with showViewButtons due to remaining time calculation
TopNav . showEmailButton ( ) ;
2017-02-14 21:21:55 +00:00
TopNav . hideRawButton ( ) ;
2021-04-17 06:51:25 +00:00
TopNav . hideDownloadButton ( ) ;
2017-02-14 21:21:55 +00:00
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-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
2018-12-29 17:40:59 +00:00
const plainText = DiscussionViewer . getReplyMessage ( ) ,
nickname = DiscussionViewer . getReplyNickname ( ) ,
parentid = DiscussionViewer . getReplyCommentId ( ) ;
2017-02-17 19:46:10 +00:00
// 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 ;
}
2018-12-25 16:34:39 +00:00
// prepare server interaction
ServerInteraction . prepare ( ) ;
ServerInteraction . setCryptParameters ( Prompt . getPassword ( ) , Model . getPasteKey ( ) ) ;
2017-02-17 19:46:10 +00:00
// set success/fail functions
2018-12-25 16:34:39 +00:00
ServerInteraction . setSuccess ( showUploadedComment ) ;
ServerInteraction . setFailure ( function ( status , data ) {
2017-02-17 19:46:10 +00:00
// 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 (
2018-12-25 16:34:39 +00:00
ServerInteraction . parseUploadError ( status , data , 'post comment' )
2018-01-06 12:32:07 +00:00
) ;
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
2018-12-25 16:34:39 +00:00
ServerInteraction . setUnencryptedData ( 'pasteid' , Model . getPasteId ( ) ) ;
2017-02-17 19:46:10 +00:00
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
2018-12-25 16:34:39 +00:00
ServerInteraction . setUnencryptedData ( 'parentid' , Model . getPasteId ( ) ) ;
2017-02-17 19:46:10 +00:00
} else {
2018-12-25 16:34:39 +00:00
ServerInteraction . setUnencryptedData ( 'parentid' , parentid ) ;
2017-02-17 19:46:10 +00:00
}
2018-12-25 16:34:39 +00:00
// prepare cypher message
let cipherMessage = {
'comment' : plainText
} ;
2018-10-20 09:40:37 +00:00
if ( nickname . length > 0 ) {
2018-12-25 16:34:39 +00:00
cipherMessage [ 'nickname' ] = nickname ;
2018-08-04 20:30:01 +00:00
}
2017-02-17 19:46:10 +00:00
2018-12-25 16:34:39 +00:00
await ServerInteraction . setCipherMessage ( cipherMessage ) . catch ( Alert . showError ) ;
2019-05-15 05:44:03 +00:00
ServerInteraction . run ( ) ;
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
2018-12-29 17:40:59 +00:00
const plainText = Editor . getText ( ) ,
format = PasteViewer . getFormat ( ) ,
// the methods may return different values if no files are attached (null, undefined or false)
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 ;
}
2018-12-25 16:34:39 +00:00
// prepare server interaction
ServerInteraction . prepare ( ) ;
2024-01-27 17:26:19 +00:00
ServerInteraction . setCryptParameters ( TopNav . getPassword ( ) ) ;
2017-02-13 20:12:00 +00:00
// set success/fail functions
2018-12-25 16:34:39 +00:00
ServerInteraction . setSuccess ( showCreatedPaste ) ;
ServerInteraction . 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 (
2018-12-25 16:34:39 +00:00
ServerInteraction . parseUploadError ( status , data , 'create paste' )
2018-01-06 12:32:07 +00:00
) ;
2017-02-12 17:08:08 +00:00
} ) ;
2017-02-13 20:12:00 +00:00
// fill it with unencrypted submitted options
2018-12-25 16:34:39 +00:00
ServerInteraction . setUnencryptedData ( 'adata' , [
null , format ,
TopNav . getOpenDiscussion ( ) ? 1 : 0 ,
TopNav . getBurnAfterReading ( ) ? 1 : 0
] ) ;
ServerInteraction . setUnencryptedData ( 'meta' , { 'expire' : TopNav . getExpiration ( ) } ) ;
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 ) ;
2018-12-25 16:34:39 +00:00
// prepare cypher message
let file = AttachmentViewer . getAttachmentData ( ) ,
cipherMessage = {
'paste' : plainText
} ;
if ( typeof file !== 'undefined' && file !== null ) {
cipherMessage [ 'attachment' ] = file ;
cipherMessage [ 'attachment_name' ] = AttachmentViewer . getFile ( ) . name ;
} else if ( AttachmentViewer . hasAttachment ( ) ) {
// fall back to cloned part
let attachment = AttachmentViewer . getAttachment ( ) ;
cipherMessage [ 'attachment' ] = attachment [ 0 ] ;
cipherMessage [ 'attachment_name' ] = attachment [ 1 ] ;
2019-08-18 02:17:35 +00:00
// we need to retrieve data from blob if browser already parsed it in memory
if ( typeof attachment [ 0 ] === 'string' && attachment [ 0 ] . startsWith ( 'blob:' ) ) {
Alert . showStatus (
[
'Retrieving cloned file \'%s\' from memory...' ,
attachment [ 1 ]
] ,
'copy'
) ;
try {
const blobData = await $ . ajax ( {
type : 'GET' ,
url : ` ${ attachment [ 0 ] } ` ,
processData : false ,
timeout : 10000 ,
xhrFields : {
withCredentials : false ,
responseType : 'blob'
}
} ) ;
if ( blobData instanceof window . Blob ) {
const fileReading = new Promise ( function ( resolve , reject ) {
const fileReader = new FileReader ( ) ;
fileReader . onload = function ( event ) {
resolve ( event . target . result ) ;
} ;
fileReader . onerror = function ( error ) {
reject ( error ) ;
}
fileReader . readAsDataURL ( blobData ) ;
} ) ;
cipherMessage [ 'attachment' ] = await fileReading ;
} else {
2019-08-27 05:38:27 +00:00
const error = 'Cannot process attachment data.' ;
Alert . showError ( error ) ;
throw new TypeError ( error ) ;
2019-08-18 02:17:35 +00:00
}
} catch ( error ) {
console . error ( error ) ;
2019-08-27 05:38:27 +00:00
Alert . showError ( 'Cannot retrieve attachment.' ) ;
2019-08-18 02:17:35 +00:00
throw error ;
}
}
2018-12-25 16:34:39 +00:00
}
2018-10-08 19:03:10 +00:00
2018-12-25 16:34:39 +00:00
// encrypt message
await ServerInteraction . setCipherMessage ( cipherMessage ) . catch ( Alert . showError ) ;
2018-10-08 19:03:10 +00:00
// send data
2018-12-25 16:34:39 +00:00
ServerInteraction . run ( ) ;
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
* /
2018-12-29 17:40:59 +00:00
const PasteDecrypter = ( function ( ) {
const me = { } ;
2017-02-14 21:21:55 +00:00
/ * *
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 ) {
making webassembly optional, ensuring retry button works when wrong password is provided
Tested configurations:
- browser with WASM support (Firefox 68.0.2)
- creates paste with zlib compression, no password
- creates paste with zlib compression, with password
- reads paste with zlib compression, no password
- reads paste with zlib compression, with password + retry button works
- reads paste without compression, no password
- reads paste without compression, with password + retry button works
- browser without WASM support (Chromium 76.0.3809.100, started via `chromium-browser --js-flags=--noexpose_wasm`)
- creates paste without compression, no password, but shows WASM warning
- creates paste without compression, with password, but shows WASM warning
- fails to read paste with zlib compression, no password + shows WASM error
- fails to read paste with zlib compression, with password + shows WASM error
- reads paste without compression, no password
- reads paste without compression, with password + retry button works
2019-09-08 06:21:54 +00:00
return false ;
2017-02-17 19:46:10 +00:00
}
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
2019-05-25 11:20:39 +00:00
* @ param { Paste } paste - paste data in object form
2017-03-13 19:24:18 +00:00
* @ 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-17 19:46:10 +00:00
* /
2018-12-29 17:40:59 +00:00
async function decryptPaste ( paste , key , password )
2017-02-17 19:46:10 +00:00
{
2019-05-25 11:20:39 +00:00
let pastePlain = await decryptOrPromptPassword (
2018-12-29 17:40:59 +00:00
key , password ,
2019-05-25 11:20:39 +00:00
paste . getCipherData ( )
2018-12-29 17:40:59 +00:00
) ;
if ( pastePlain === false ) {
if ( password . length === 0 ) {
throw 'waiting on user to provide a password' ;
} else {
making webassembly optional, ensuring retry button works when wrong password is provided
Tested configurations:
- browser with WASM support (Firefox 68.0.2)
- creates paste with zlib compression, no password
- creates paste with zlib compression, with password
- reads paste with zlib compression, no password
- reads paste with zlib compression, with password + retry button works
- reads paste without compression, no password
- reads paste without compression, with password + retry button works
- browser without WASM support (Chromium 76.0.3809.100, started via `chromium-browser --js-flags=--noexpose_wasm`)
- creates paste without compression, no password, but shows WASM warning
- creates paste without compression, with password, but shows WASM warning
- fails to read paste with zlib compression, no password + shows WASM error
- fails to read paste with zlib compression, with password + shows WASM error
- reads paste without compression, no password
- reads paste without compression, with password + retry button works
2019-09-08 06:21:54 +00:00
Alert . hideLoading ( ) ;
// reset password, so it can be re-entered
Prompt . reset ( ) ;
TopNav . showRetryButton ( ) ;
throw 'Could not decrypt data. Did you enter a wrong password? Retry with the button at the top.' ;
2017-02-17 19:46:10 +00:00
}
2018-12-29 17:40:59 +00:00
}
2017-02-14 21:21:55 +00:00
2019-05-25 11:20:39 +00:00
if ( paste . v > 1 ) {
2018-12-29 17:40:59 +00:00
// version 2 paste
const pasteMessage = JSON . parse ( pastePlain ) ;
if ( pasteMessage . hasOwnProperty ( 'attachment' ) && pasteMessage . hasOwnProperty ( 'attachment_name' ) ) {
AttachmentViewer . setAttachment ( pasteMessage . attachment , pasteMessage . attachment _name ) ;
2019-05-19 11:31:17 +00:00
AttachmentViewer . showAttachment ( ) ;
}
2019-05-25 11:20:39 +00:00
pastePlain = pasteMessage . paste ;
2019-05-19 11:31:17 +00:00
} else {
// version 1 paste
if ( paste . hasOwnProperty ( 'attachment' ) && paste . hasOwnProperty ( 'attachmentname' ) ) {
Promise . all ( [
CryptTool . decipher ( key , password , paste . attachment ) ,
CryptTool . decipher ( key , password , paste . attachmentname )
] ) . then ( ( attachment ) => {
AttachmentViewer . setAttachment ( attachment [ 0 ] , attachment [ 1 ] ) ;
AttachmentViewer . showAttachment ( ) ;
} ) ;
2017-02-14 21:21:55 +00:00
}
2018-12-29 17:40:59 +00:00
}
2019-05-25 11:20:39 +00:00
PasteViewer . setFormat ( paste . getFormat ( ) ) ;
PasteViewer . setText ( pastePlain ) ;
2018-12-29 17:40:59 +00:00
PasteViewer . run ( ) ;
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
2019-05-25 11:20:39 +00:00
* @ param { Paste } paste - paste data in object form
2017-03-13 19:24:18 +00:00
* @ 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-12-29 17:40:59 +00:00
const 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 ) {
2019-05-25 11:20:39 +00:00
const comment = new Comment ( paste . comments [ i ] ) ,
commentPromise = CryptTool . decipher ( key , password , comment . getCipherData ( ) ) ;
paste . comments [ i ] = comment ;
if ( comment . v > 1 ) {
2018-12-29 17:40:59 +00:00
// version 2 comment
commentDecryptionPromises . push (
2019-05-25 11:20:39 +00:00
commentPromise . then ( function ( commentJson ) {
const commentMessage = JSON . parse ( commentJson ) ;
return [
commentMessage . comment || '' ,
commentMessage . nickname || ''
] ;
} )
2018-12-29 17:40:59 +00:00
) ;
} else {
// version 1 comment
commentDecryptionPromises . push (
Promise . all ( [
2019-05-25 11:20:39 +00:00
commentPromise ,
paste . comments [ i ] . meta . hasOwnProperty ( 'nickname' ) ?
2018-12-29 17:40:59 +00:00
CryptTool . decipher ( key , password , paste . comments [ i ] . meta . nickname ) :
Promise . resolve ( '' )
] )
) ;
}
2017-02-17 19:46:10 +00:00
}
2019-05-25 11:20:39 +00:00
return Promise . all ( commentDecryptionPromises ) . then ( function ( 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 08:20:32 +00:00
DiscussionViewer . addComment (
2019-05-25 11:20:39 +00:00
paste . comments [ i ] ,
2018-10-20 11:54:17 +00:00
plaintexts [ i ] [ 0 ] ,
plaintexts [ i ] [ 1 ]
2018-10-20 08:20:32 +00:00
) ;
}
} ) ;
2017-02-14 21:21:55 +00:00
}
/ * *
* show decrypted text in the display area , including discussion ( if open )
*
* @ name PasteDecrypter . run
* @ function
2019-05-25 11:20:39 +00:00
* @ param { Paste } [ paste ] - ( optional ) object including comments to display ( items = array with keys ( 'data' , 'meta' ) )
2017-02-14 21:21:55 +00:00
* /
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
2024-01-27 17:26:19 +00:00
if ( typeof paste === 'undefined' || paste . type === 'click' ) {
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 ) ;
} ) ;
2018-12-29 17:40:59 +00:00
// decrypt paste & attachments
2019-05-19 11:31:17 +00:00
decryptionPromises . push ( decryptPaste ( paste , key , password ) ) ;
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
2019-05-25 11:20:39 +00:00
if ( paste . isDiscussionEnabled ( ) ) {
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
2019-05-15 05:44:03 +00:00
PasteStatus . showRemainingTime ( paste ) ;
2018-10-20 11:54:17 +00:00
2018-12-29 17:40:59 +00:00
Promise . all ( decryptionPromises )
. then ( ( ) => {
Alert . hideLoading ( ) ;
TopNav . showViewButtons ( ) ;
2019-08-18 02:17:35 +00:00
// discourage cloning (it cannot really be prevented)
if ( paste . isBurnAfterReadingEnabled ( ) ) {
2019-08-21 21:36:22 +00:00
TopNav . hideBurnAfterReadingButtons ( ) ;
} else {
// we have to pass in remaining_time here
TopNav . showEmailButton ( paste . getTimeToLive ( ) ) ;
2019-08-18 02:17:35 +00:00
}
2020-01-08 18:48:42 +00:00
// only offer adding comments, after paste was successfully decrypted
if ( paste . isDiscussionEnabled ( ) ) {
DiscussionViewer . finishDiscussion ( ) ;
}
2018-12-29 17:40:59 +00:00
} )
. catch ( ( err ) => {
// wait for the user to type in the password,
// then PasteDecrypter.run will be called again
making webassembly optional, ensuring retry button works when wrong password is provided
Tested configurations:
- browser with WASM support (Firefox 68.0.2)
- creates paste with zlib compression, no password
- creates paste with zlib compression, with password
- reads paste with zlib compression, no password
- reads paste with zlib compression, with password + retry button works
- reads paste without compression, no password
- reads paste without compression, with password + retry button works
- browser without WASM support (Chromium 76.0.3809.100, started via `chromium-browser --js-flags=--noexpose_wasm`)
- creates paste without compression, no password, but shows WASM warning
- creates paste without compression, with password, but shows WASM warning
- fails to read paste with zlib compression, no password + shows WASM error
- fails to read paste with zlib compression, with password + shows WASM error
- reads paste without compression, no password
- reads paste without compression, with password + retry button works
2019-09-08 06:21:54 +00:00
Alert . showError ( err ) ;
2018-12-29 17:40:59 +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
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
* /
2018-12-29 17:40:59 +00:00
const Controller = ( function ( window , document ) {
const me = { } ;
2017-02-14 21:21:55 +00:00
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 ( ) ;
2020-04-23 10:07:08 +00:00
TopNav . resetInput ( ) ;
2017-02-15 21:59:55 +00:00
TopNav . showCreateButtons ( ) ;
2019-08-18 02:17:35 +00:00
// newPaste could be called when user is on paste clone editing view
TopNav . hideCustomAttachment ( ) ;
AttachmentViewer . clearDragAndDrop ( ) ;
AttachmentViewer . removeAttachmentData ( ) ;
2017-02-15 21:59:55 +00:00
Alert . hideLoading ( ) ;
2024-01-27 17:26:19 +00:00
// only push new state if we are coming from a different one
if ( Helper . baseUri ( ) != window . location ) {
history . pushState ( { type : 'create' } , document . title , Helper . baseUri ( ) ) ;
}
2019-08-15 00:36:44 +00:00
// clear discussion
DiscussionViewer . prepareNewDiscussion ( ) ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-12 20:13:04 +00:00
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 ) {
2019-08-27 05:38:27 +00:00
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?)' ) ;
2017-02-17 19:46:10 +00:00
return ;
}
}
2024-01-27 17:26:19 +00:00
// check if we should request loading confirmation
if ( window . location . hash . startsWith ( loadConfirmPrefix ) ) {
Prompt . requestLoadConfirmation ( ) ;
return ;
}
2017-02-17 19:46:10 +00:00
// 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
2018-12-29 17:40:59 +00:00
const orgPosition = $ ( window ) . scrollTop ( ) ;
2017-02-17 19:46:10 +00:00
2017-04-11 14:34:13 +00:00
Model . getPasteData ( function ( data ) {
2018-12-25 16:34:39 +00:00
ServerInteraction . prepare ( ) ;
2019-06-01 21:49:40 +00:00
ServerInteraction . setUrl ( Helper . baseUri ( ) + '?pasteid=' + Model . getPasteId ( ) ) ;
2017-02-17 19:46:10 +00:00
2018-12-25 16:34:39 +00:00
ServerInteraction . setFailure ( function ( status , data ) {
2018-04-30 18:01:38 +00:00
// 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 (
2018-12-25 16:34:39 +00:00
ServerInteraction . parseUploadError ( status , data , 'refresh display' )
2018-04-30 18:01:38 +00:00
) ;
} ) ;
2018-12-25 16:34:39 +00:00
ServerInteraction . setSuccess ( function ( status , data ) {
2019-05-25 08:10:59 +00:00
PasteDecrypter . run ( new Paste ( 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 ( ) ;
} ) ;
2018-12-25 16:34:39 +00:00
ServerInteraction . 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 ( ) ) ;
2019-08-18 02:17:35 +00:00
// also clone the format
TopNav . setFormat ( PasteViewer . getFormat ( ) ) ;
2017-02-15 21:59:55 +00:00
PasteViewer . hide ( ) ;
Editor . show ( ) ;
TopNav . showCreateButtons ( ) ;
2019-08-15 00:36:44 +00:00
// clear discussion
DiscussionViewer . prepareNewDiscussion ( ) ;
2018-01-06 08:26:10 +00:00
} ;
2017-02-12 17:08:08 +00:00
2019-09-14 07:41:52 +00:00
/ * *
* try initializing zlib or display a warning if it fails ,
* extracted from main init to allow unit testing
*
* @ name Controller . initZ
* @ function
* /
me . initZ = function ( )
{
z = zlib . catch ( function ( ) {
if ( $ ( 'body' ) . data ( 'compression' ) !== 'none' ) {
Alert . showWarning ( 'Your browser doesn\'t support WebAssembly, used for zlib compression. You can create uncompressed documents, but can\'t read compressed ones.' ) ;
}
} ) ;
}
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
* /
2019-09-14 07:41:52 +00:00
me . init = function ( )
2017-02-12 17:08:08 +00:00
{
// first load translations
2017-02-14 21:21:55 +00:00
I18n . loadTranslations ( ) ;
2017-02-12 17:08:08 +00:00
2020-05-31 18:33:22 +00:00
// Add a hook to make all links open a new window
DOMPurify . addHook ( 'afterSanitizeAttributes' , function ( node ) {
// set all elements owning target to target=_blank
if ( 'target' in node && node . id !== 'pasteurl' ) {
node . setAttribute ( 'target' , '_blank' ) ;
}
// set non-HTML/MathML links to xlink:show=new
2022-02-12 15:17:09 +00:00
if ( ! node . hasAttribute ( 'target' )
&& ( node . hasAttribute ( 'xlink:href' )
2020-05-31 18:33:22 +00:00
|| node . hasAttribute ( 'href' ) ) ) {
node . setAttribute ( 'xlink:show' , 'new' ) ;
}
if ( 'rel' in node ) {
node . setAttribute ( 'rel' , 'nofollow noopener noreferrer' ) ;
}
} ) ;
2019-08-21 21:36:22 +00:00
// center all modals
$ ( '.modal' ) . on ( 'show.bs.modal' , function ( e ) {
$ ( e . target ) . css ( {
display : 'flex'
} ) ;
} ) ;
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 ( ) ;
PasteStatus . init ( ) ;
PasteViewer . init ( ) ;
Prompt . init ( ) ;
2017-02-17 21:46:18 +00:00
TopNav . init ( ) ;
UiHelper . init ( ) ;
2019-09-14 07:41:52 +00:00
// check for legacy browsers before going any further
2019-09-18 05:31:32 +00:00
if ( ! Legacy . Check . getInit ( ) ) {
2019-09-14 07:41:52 +00:00
// Legacy check didn't complete, wait and try again
setTimeout ( init , 500 ) ;
return ;
}
2019-09-18 05:31:32 +00:00
if ( ! Legacy . Check . getStatus ( ) ) {
2019-06-27 19:18:46 +00:00
// something major is wrong, stop right away
return ;
}
2019-09-14 07:41:52 +00:00
me . initZ ( ) ;
2017-02-08 19:11:04 +00:00
2020-05-30 10:00:17 +00:00
// if delete token is passed (i.e. paste has been deleted by this
// access), there is nothing more to do
if ( Model . hasDeleteToken ( ) ) {
return ;
}
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 ( ) ;
}
2020-05-30 09:57:27 +00:00
// always reload on back button to invalidate cache(protect burn after read paste)
window . addEventListener ( 'popstate' , ( ) => {
window . location . reload ( ) ;
} ) ;
2018-05-22 09:41:35 +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 ,
2018-12-25 16:34:39 +00:00
ServerInteraction : ServerInteraction ,
2017-02-17 21:46:18 +00:00
PasteEncrypter : PasteEncrypter ,
PasteDecrypter : PasteDecrypter ,
Controller : Controller
2017-01-22 09:42:11 +00:00
} ;
2018-10-20 17:53:21 +00:00
} ) ( jQuery , RawDeflate ) ;