@ -45,6 +45,18 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
var Helper = ( function ( ) {
var Helper = ( function ( ) {
var me = { } ;
var me = { } ;
/ * *
* blacklist of UserAgents ( parts ) known to belong to a bot
*
* @ private
* @ enum { Object }
* @ readonly
* /
var BadBotUA = [
'Bot' ,
'bot'
] ;
/ * *
/ * *
* cache for script location
* cache for script location
*
*
@ -121,7 +133,7 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
* URLs to handle :
* URLs to handle :
* < pre >
* < pre >
* magnet : ? xt . 1 = urn : sha1 : YNCKHTQCWBTRNJIV4WNAE52SJUQCZO5C & xt . 2 = urn : sha1 : TXGCZQTH26NL6OUQAJJPFALHG2LTGBC7
* magnet : ? xt . 1 = urn : sha1 : YNCKHTQCWBTRNJIV4WNAE52SJUQCZO5C & xt . 2 = urn : sha1 : TXGCZQTH26NL6OUQAJJPFALHG2LTGBC7
* http : //example.com:8800/zero/?6f09182b8ea51997#WtLEUO5Epj9UHAV9JFs+6pUQZp13TuspAUjnF+iM+dM=
* http s : //example.com:8800/zero/?6f09182b8ea51997#WtLEUO5Epj9UHAV9JFs+6pUQZp13TuspAUjnF+iM+dM=
* http : //user:example.com@localhost:8800/zero/?6f09182b8ea51997#WtLEUO5Epj9UHAV9JFs+6pUQZp13TuspAUjnF+iM+dM=
* http : //user:example.com@localhost:8800/zero/?6f09182b8ea51997#WtLEUO5Epj9UHAV9JFs+6pUQZp13TuspAUjnF+iM+dM=
* < / p r e >
* < / p r e >
*
*
@ -204,7 +216,7 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
/ * *
/ * *
* get the current location ( without search or hash part of the URL ) ,
* get the current location ( without search or hash part of the URL ) ,
* eg . http : //example.com/path/?aaaa#bbbb --> http ://example.com/path/
* eg . http s : //example.com/path/?aaaa#bbbb --> http s ://example.com/path/
*
*
* @ name Helper . baseUri
* @ name Helper . baseUri
* @ function
* @ function
@ -232,6 +244,26 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
baseUri = null ;
baseUri = null ;
} ;
} ;
/ * *
* checks whether this is a bot we dislike
*
* @ name Helper . isBadBot
* @ function
* @ return { bool }
* /
me . isBadBot = function ( ) {
// check whether a bot user agent part can be found in the current
// user agent
var arrayLength = BadBotUA . length ;
for ( var i = 0 ; i < arrayLength ; i ++ ) {
if ( navigator . userAgent . indexOf ( BadBotUA ) >= 0 ) {
return true ;
}
}
return false ;
}
return me ;
return me ;
} ) ( ) ;
} ) ( ) ;
@ -358,7 +390,7 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
// file is loaded
// file is loaded
}
}
// for all other lang a uges than English for which this behaviour
// for all other lang ua ges than English for which this behaviour
// is expected as it is built-in, log error
// is expected as it is built-in, log error
if ( language !== null && language !== 'en' ) {
if ( language !== null && language !== 'en' ) {
console . error ( 'Missing translation for: \'' + messageId + '\' in language ' + language ) ;
console . error ( 'Missing translation for: \'' + messageId + '\' in language ' + language ) ;
@ -623,7 +655,7 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
var Model = ( function ( ) {
var Model = ( function ( ) {
var me = { } ;
var me = { } ;
var $cipherData ,
var pasteData = null ,
$templates ;
$templates ;
var id = null , symmetricKey = null ;
var id = null , symmetricKey = null ;
@ -653,32 +685,53 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
} ;
} ;
/ * *
/ * *
* check if cipher data was supplied
* returns the paste data ( including the cipher data )
*
*
* @ name Model . getCipherData
* @ name Model . getPasteData
* @ function
* @ return boolean
* /
me . hasCipherData = function ( )
{
return me . getCipherData ( ) . length > 0 ;
} ;
/ * *
* returns the cipher data
*
* @ name Model . getCipherData
* @ function
* @ function
* @ 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
* @ return string
* @ return string
* /
* /
me . getCipherData = function ( )
me . getPasteData = function ( callback , useCache )
{
{
return $cipherData . text ( ) ;
// use cache if possible/allowed
if ( useCache !== false && pasteData !== null ) {
//execute callback
if ( typeof callback === 'function' ) {
return callback ( pasteData ) ;
}
// alternatively just using inline
return pasteData ;
}
// reload data
Uploader . prepare ( ) ;
Uploader . setUrl ( Helper . baseUri ( ) + '?' + me . getPasteId ( ) ) ;
Uploader . setFailure ( function ( status , data ) {
// revert loading status…
Alert . hideLoading ( ) ;
TopNav . showViewButtons ( ) ;
// show error message
Alert . showError ( Uploader . parseUploadError ( status , data , 'getting paste data' ) ) ;
} ) ;
Uploader . setSuccess ( function ( status , data ) {
pasteData = data ;
if ( typeof callback === 'function' ) {
return callback ( data ) ;
}
} ) ;
Uploader . run ( ) ;
} ;
} ;
/ * *
/ * *
* get the pastes unique identifier from the URL ,
* get the pastes unique identifier from the URL ,
* eg . http : //example.com/path/?c05354954c49a487#dfdsdgdgdfgdf returns c05354954c49a487
* eg . http s : //example.com/path/?c05354954c49a487#dfdsdgdgdfgdf returns c05354954c49a487
*
*
* @ name Model . getPasteId
* @ name Model . getPasteId
* @ function
* @ function
@ -688,6 +741,7 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
me . getPasteId = function ( )
me . getPasteId = function ( )
{
{
if ( id === null ) {
if ( id === null ) {
// Attention: This also returns the delete token inside of the ID, if it is specified
id = window . location . search . substring ( 1 ) ;
id = window . location . search . substring ( 1 ) ;
if ( id === '' ) {
if ( id === '' ) {
@ -696,7 +750,19 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
}
}
return id ;
return id ;
} ;
}
/ * *
* Returns true , when the URL has a delete token and the current call was used for deleting a paste .
*
* @ name Model . hasDeleteToken
* @ function
* @ return { bool }
* /
me . hasDeleteToken = function ( )
{
return window . location . search . indexOf ( 'deletetoken' ) !== - 1 ;
}
/ * *
/ * *
* return the deciphering key stored in anchor part of the URL
* return the deciphering key stored in anchor part of the URL
@ -751,7 +817,7 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
* /
* /
me . reset = function ( )
me . reset = function ( )
{
{
$cipher Data = $templates = id = symmetricKey = null ;
paste Data = $templates = id = symmetricKey = null ;
} ;
} ;
/ * *
/ * *
@ -764,7 +830,6 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
* /
* /
me . init = function ( )
me . init = function ( )
{
{
$cipherData = $ ( '#cipherdata' ) ;
$templates = $ ( '#templates' ) ;
$templates = $ ( '#templates' ) ;
} ;
} ;
@ -1259,8 +1324,8 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
if ( pasteMetaData . burnafterreading ) {
if ( pasteMetaData . burnafterreading ) {
// display paste "for your eyes only" if it is deleted
// display paste "for your eyes only" if it is deleted
// actually remove paste, before we claim it is deleted
// the paste has been deleted when the JSON with the ciphertext
Controller . removePaste ( Model . getPasteId ( ) , 'burnafterreading' ) ;
// has been downloaded
Alert . showRemaining ( "FOR YOUR EYES ONLY. Don't close this window, this message can't be displayed again." ) ;
Alert . showRemaining ( "FOR YOUR EYES ONLY. Don't close this window, this message can't be displayed again." ) ;
$remainingTime . addClass ( 'foryoureyesonly' ) ;
$remainingTime . addClass ( 'foryoureyesonly' ) ;
@ -1402,6 +1467,21 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
return password ;
return password ;
} ;
} ;
/ * *
* resets the password to an empty string
*
* @ name Prompt . reset
* @ function
* /
me . reset = function ( )
{
// reset internal
password = '' ;
// and also reset UI
$passwordDecrypt . val ( '' ) ;
}
/ * *
/ * *
* init status manager
* init status manager
*
*
@ -2149,7 +2229,6 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
loadedFile = $fileInput [ 0 ] . files [ 0 ] ;
loadedFile = $fileInput [ 0 ] . files [ 0 ] ;
$dragAndDropFileName . text ( '' ) ;
$dragAndDropFileName . text ( '' ) ;
} else {
} else {
// TODO: cannot set original $fileWrap here for security reasons…
$dragAndDropFileName . text ( loadedFile . name ) ;
$dragAndDropFileName . text ( loadedFile . name ) ;
}
}
@ -2295,6 +2374,10 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
if ( items . hasOwnProperty ( i ) ) {
if ( items . hasOwnProperty ( i ) ) {
var item = items [ i ] ;
var item = items [ i ] ;
if ( item . kind === 'file' ) {
if ( item . kind === 'file' ) {
//Clear the file input:
$fileInput . wrap ( '<form>' ) . closest ( 'form' ) . get ( 0 ) . reset ( ) ;
$fileInput . unwrap ( ) ;
readFileData ( item . getAsFile ( ) ) ;
readFileData ( item . getAsFile ( ) ) ;
}
}
}
}
@ -2685,9 +2768,11 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
$passwordInput ,
$passwordInput ,
$rawTextButton ,
$rawTextButton ,
$qrCodeLink ,
$qrCodeLink ,
$sendButton ;
$sendButton ,
$retryButton ;
var pasteExpiration = '1week' ;
var pasteExpiration = '1week' ,
retryButtonCallback ;
/ * *
/ * *
* set the expiration on bootstrap templates in dropdown
* set the expiration on bootstrap templates in dropdown
@ -2836,6 +2921,19 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
Controller . newPaste ( ) ;
Controller . newPaste ( ) ;
}
}
/ * *
* retrys some callback registered before
*
* @ name TopNav . clickRetryButton
* @ private
* @ function
* @ param { Event } event
* /
function clickRetryButton ( event )
{
retryButtonCallback ( event ) ;
}
/ * *
/ * *
* removes the existing attachment
* removes the existing attachment
*
*
@ -2915,8 +3013,8 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
return ;
return ;
}
}
$newButton . addClass ( 'hidden' ) ;
$cloneButton . addClass ( 'hidden' ) ;
$cloneButton . addClass ( 'hidden' ) ;
$newButton . addClass ( 'hidden' ) ;
$rawTextButton . addClass ( 'hidden' ) ;
$rawTextButton . addClass ( 'hidden' ) ;
$qrCodeLink . addClass ( 'hidden' ) ;
$qrCodeLink . addClass ( 'hidden' ) ;
@ -2948,14 +3046,14 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
return ;
return ;
}
}
$sendButton . removeClass ( 'hidden' ) ;
$attach . removeClass ( 'hidden' ) ;
$burnAfterReadingOption . removeClass ( 'hidden' ) ;
$expiration . removeClass ( 'hidden' ) ;
$expiration . removeClass ( 'hidden' ) ;
$formatter . removeClass ( 'hidden' ) ;
$formatter . removeClass ( 'hidden' ) ;
$burnAfterReadingOption . removeClass ( 'hidden' ) ;
$openDiscussionOption . removeClass ( 'hidden' ) ;
$newButton . removeClass ( 'hidden' ) ;
$newButton . removeClass ( 'hidden' ) ;
$openDiscussionOption . removeClass ( 'hidden' ) ;
$password . removeClass ( 'hidden' ) ;
$password . removeClass ( 'hidden' ) ;
$ attach . removeClass ( 'hidden' ) ;
$ sendButton . removeClass ( 'hidden' ) ;
createButtonsDisplayed = true ;
createButtonsDisplayed = true ;
} ;
} ;
@ -2996,6 +3094,28 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
$newButton . removeClass ( 'hidden' ) ;
$newButton . removeClass ( 'hidden' ) ;
} ;
} ;
/ * *
* 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' ) ;
}
/ * *
/ * *
* only hides the clone button
* only hides the clone button
*
*
@ -3140,6 +3260,18 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
return $customAttachment ;
return $customAttachment ;
} ;
} ;
/ * *
* Set a function to call when the retry button is clicked .
*
* @ name TopNav . setRetryCallback
* @ function
* @ param { function } callback
* /
me . setRetryCallback = function ( callback )
{
retryButtonCallback = callback ;
}
/ * *
/ * *
* init navigation manager
* init navigation manager
*
*
@ -3165,6 +3297,7 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
$password = $ ( '#password' ) ;
$password = $ ( '#password' ) ;
$passwordInput = $ ( '#passwordinput' ) ;
$passwordInput = $ ( '#passwordinput' ) ;
$rawTextButton = $ ( '#rawtextbutton' ) ;
$rawTextButton = $ ( '#rawtextbutton' ) ;
$retryButton = $ ( '#retrybutton' ) ;
$sendButton = $ ( '#sendbutton' ) ;
$sendButton = $ ( '#sendbutton' ) ;
$qrCodeLink = $ ( '#qrcodelink' ) ;
$qrCodeLink = $ ( '#qrcodelink' ) ;
@ -3180,6 +3313,7 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
$sendButton . click ( PasteEncrypter . sendPaste ) ;
$sendButton . click ( PasteEncrypter . sendPaste ) ;
$cloneButton . click ( Controller . clonePaste ) ;
$cloneButton . click ( Controller . clonePaste ) ;
$rawTextButton . click ( rawText ) ;
$rawTextButton . click ( rawText ) ;
$retryButton . click ( clickRetryButton ) ;
$fileRemoveButton . click ( removeAttachment ) ;
$fileRemoveButton . click ( removeAttachment ) ;
$qrCodeLink . click ( displayQrCode ) ;
$qrCodeLink . click ( displayQrCode ) ;
@ -3815,7 +3949,7 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
var me = { } ;
var me = { } ;
/ * *
/ * *
* decrypt data or prompts for password in cv ase of failure
* decrypt data or prompts for password in c ase of failure
*
*
* @ name PasteDecrypter . decryptOrPromptPassword
* @ name PasteDecrypter . decryptOrPromptPassword
* @ private
* @ private
@ -3833,18 +3967,23 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
// if it fails, request password
// if it fails, request password
if ( plaindata . length === 0 && password . length === 0 ) {
if ( plaindata . length === 0 && password . length === 0 ) {
// try to get cached password first
// show prompt
password = Prompt . getPassword ( ) ;
Prompt . requestPassword ( ) ;
// if password is there, re-try
// if password is there instantly (legacy method), re-try encryption
if ( password . length === 0 ) {
if ( Prompt . getPassword ( ) . length !== 0 ) {
password = Prompt . requestPassword ( ) ;
// recursive
// note: an infinite loop is prevented as the previous if
// clause checks whether a password is already set and ignores
// errors when a password has been passed
return decryptOrPromptPassword ( key , password , cipherdata ) ;
}
}
// recursive
// note: an infinite loop is prevented as the previous if
// if password could not be received yet, the new modal is used,
// clause checks whether a password is already set and ignores
// which uses asyncronous event-driven methods to get the password.
// errors when a password has been passed
// Thus, we cannot do anything yet, we need to wait for the user
return decryptOrPromptPassword . apply ( key , password , cipherdata ) ;
// input.
return false ;
}
}
// if all tries failed, we can only return an error
// if all tries failed, we can only return an error
@ -3858,7 +3997,7 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
/ * *
/ * *
* decrypt the actual paste text
* decrypt the actual paste text
*
*
* @ name PasteDecrypter . decrypt OrPromptPassword
* @ name PasteDecrypter . decrypt Paste
* @ private
* @ private
* @ function
* @ function
* @ param { object } paste - paste data in object form
* @ param { object } paste - paste data in object form
@ -3981,7 +4120,9 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
Alert . showLoading ( 'Decrypting paste…' , 'cloud-download' ) ;
Alert . showLoading ( 'Decrypting paste…' , 'cloud-download' ) ;
if ( typeof paste === 'undefined' ) {
if ( typeof paste === 'undefined' ) {
paste = $ . parseJSON ( Model . getCipherData ( ) ) ;
// get cipher data and wait until it is available
Model . getPasteData ( me . run ) ;
return ;
}
}
var key = Model . getPasteKey ( ) ,
var key = Model . getPasteKey ( ) ,
@ -4006,10 +4147,11 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
// ignore empty paste, as this is allowed when pasting attachments
// ignore empty paste, as this is allowed when pasting attachments
decryptPaste ( paste , key , password , true ) ;
decryptPaste ( paste , key , password , true ) ;
} else {
} else {
decryptPaste ( paste , key , password ) ;
if ( decryptPaste ( paste , key , password ) === false ) {
return false ;
}
}
}
// shows the remaining time (until) deletion
// shows the remaining time (until) deletion
PasteStatus . showRemainingTime ( paste . meta ) ;
PasteStatus . showRemainingTime ( paste . meta ) ;
@ -4025,7 +4167,15 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
// log and show error
// log and show error
console . error ( err ) ;
console . error ( err ) ;
Alert . showError ( 'Could not decrypt data (Wrong key?)' ) ;
Alert . showError ( 'Could not decrypt data. Did you enter a wrong password? Retry with the button at the top.' ) ;
// reset password, so it can be re-entered and sow retry button
Prompt . reset ( ) ;
TopNav . setRetryCallback ( function ( ) {
TopNav . hideRetryButton ( ) ;
me . run ( paste ) ;
} ) ;
TopNav . showRetryButton ( ) ;
}
}
} ;
} ;
@ -4090,6 +4240,18 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
Alert . hideLoading ( ) ;
Alert . hideLoading ( ) ;
} ;
} ;
/ * *
* shows how we much we love bots that execute JS ; )
*
* @ name Controller . showBadBotMessage
* @ function
* /
me . showBadBotMessage = function ( )
{
TopNav . hideAllButtons ( ) ;
Alert . showError ( 'I love you too, bot…' ) ;
}
/ * *
/ * *
* shows the loaded paste
* shows the loaded paste
*
*
@ -4099,7 +4261,6 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
me . showPaste = function ( )
me . showPaste = function ( )
{
{
try {
try {
Model . getPasteId ( ) ;
Model . getPasteKey ( ) ;
Model . getPasteKey ( ) ;
} catch ( err ) {
} catch ( err ) {
console . error ( err ) ;
console . error ( err ) ;
@ -4127,29 +4288,36 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
// save window position to restore it later
// save window position to restore it later
var orgPosition = $ ( window ) . scrollTop ( ) ;
var orgPosition = $ ( window ) . scrollTop ( ) ;
Uploader . prepare ( ) ;
Model . getPasteData ( function ( data ) {
Uploader . setUrl ( Helper . baseUri ( ) + '?' + Model . getPasteId ( ) ) ;
Uploader . prepare ( ) ;
Uploader . setUrl ( Helper . baseUri ( ) + '?' + Model . getPasteId ( ) ) ;
Uploader . setFailure ( function ( status , data ) {
Uploader . setFailure ( function ( status , data ) {
// revert loading status…
// revert loading status…
Alert . hideLoading ( ) ;
Alert . hideLoading ( ) ;
TopNav . showViewButtons ( ) ;
TopNav . showViewButtons ( ) ;
// show error message
// show error message
Alert . showError (
Alert . showError (
Uploader . parseUploadError ( status , data , 'refresh display' )
Uploader . parseUploadError ( status , data , 'refresh display' )
) ;
) ;
} ) ;
} ) ;
Uploader . setSuccess ( function ( status , data ) {
Uploader . setSuccess ( function ( status , data ) {
PasteDecrypter . run ( data ) ;
PasteDecrypter . run ( data ) ;
// restore position
// restore position
window . scrollTo ( 0 , orgPosition ) ;
window . scrollTo ( 0 , orgPosition ) ;
callback ( ) ;
PasteDecrypter . run ( data ) ;
} ) ;
Uploader . run ( ) ;
// NOTE: could create problems as callback may be called
} ;
// asyncronously if PasteDecrypter e.g. needs to wait for a
// password being entered
callback ( ) ;
} ) ;
Uploader . run ( ) ;
} , false ) ; // this false is important as it circumvents the cache
}
/ * *
/ * *
* clone the current paste
* clone the current paste
@ -4207,6 +4375,7 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
* @ function
* @ function
* @ param { string } pasteId
* @ param { string } pasteId
* @ param { string } deleteToken
* @ param { string } deleteToken
* @ deprecated not used anymore , de we still need it ?
* /
* /
me . removePaste = function ( pasteId , deleteToken ) {
me . removePaste = function ( pasteId , deleteToken ) {
// unfortunately many web servers don't support DELETE (and PUT) out of the box
// unfortunately many web servers don't support DELETE (and PUT) out of the box
@ -4251,14 +4420,30 @@ jQuery.PrivateBin = (function($, sjcl, Base64, RawDeflate) {
UiHelper . init ( ) ;
UiHelper . init ( ) ;
Uploader . init ( ) ;
Uploader . init ( ) ;
// display an existing paste
// check whether existing paste needs to be shown
if ( Model . hasCipherData ( ) ) {
try {
return me . showPaste ( ) ;
Model . getPasteId ( ) ;
} catch ( e ) {
// otherwise create a new paste
return me . newPaste ( ) ;
}
}
// otherwise create a new paste
// if delete token is passed (i.e. paste has been deleted by this access)
me . newPaste ( ) ;
// there is no more stuf we need to do
} ;
if ( Model . hasDeleteToken ( ) ) {
return ;
}
// prevent bots from viewing a paste and potentially deleting data
// when burn-after-reading is set
// see https://github.com/elrido/ZeroBin/issues/11
if ( Helper . isBadBot ( ) ) {
return me . showBadBotMessage ( ) ;
}
// display an existing paste
return me . showPaste ( ) ;
}
return me ;
return me ;
} ) ( window , document ) ;
} ) ( window , document ) ;