2012-04-22 12:45:45 +02:00
/ * *
* ZeroBin 0.15
*
* @ link http : //sebsauvage.net/wiki/doku.php?id=php:zerobin
* @ author sebsauvage
* /
2012-04-21 21:59:45 +02:00
// Immediately start random number generator collector.
sjcl . random . startCollectors ( ) ;
2012-04-22 12:45:45 +02:00
/ * *
* Converts a duration ( in seconds ) into human readable format .
*
* @ param int seconds
* @ return string
* /
2012-04-21 21:59:45 +02:00
function secondsToHuman ( seconds )
{
if ( seconds < 60 ) { var v = Math . floor ( seconds ) ; return v + ' second' + ( ( v > 1 ) ? 's' : '' ) ; }
if ( seconds < 60 * 60 ) { var v = Math . floor ( seconds / 60 ) ; return v + ' minute' + ( ( v > 1 ) ? 's' : '' ) ; }
if ( seconds < 60 * 60 * 24 ) { var v = Math . floor ( seconds / ( 60 * 60 ) ) ; return v + ' hour' + ( ( v > 1 ) ? 's' : '' ) ; }
// If less than 2 months, display in days:
if ( seconds < 60 * 60 * 24 * 60 ) { var v = Math . floor ( seconds / ( 60 * 60 * 24 ) ) ; return v + ' day' + ( ( v > 1 ) ? 's' : '' ) ; }
var v = Math . floor ( seconds / ( 60 * 60 * 24 * 30 ) ) ; return v + ' month' + ( ( v > 1 ) ? 's' : '' ) ;
}
2012-04-22 12:45:45 +02:00
/ * *
* Compress a message ( deflate compression ) . Returns base64 encoded data .
*
* @ param string message
* @ return base64 string data
* /
function compress ( message ) {
return Base64 . toBase64 ( RawDeflate . deflate ( Base64 . utob ( message ) ) ) ;
}
2012-04-21 21:59:45 +02:00
2012-04-22 12:45:45 +02:00
/ * *
* Decompress a message compressed with compress ( ) .
* /
function decompress ( data ) {
return Base64 . btou ( RawDeflate . inflate ( Base64 . fromBase64 ( data ) ) ) ;
}
/ * *
* Compress , then encrypt message with key .
*
* @ param string key
* @ param string message
* @ return encrypted string data
* /
function zeroCipher ( key , message ) {
2012-04-21 21:59:45 +02:00
return sjcl . encrypt ( key , compress ( message ) ) ;
}
2012-04-22 12:45:45 +02:00
/ * *
* Decrypt message with key , then decompress .
*
* @ param key
* @ param encrypted string data
* @ return string readable message
* /
function zeroDecipher ( key , data ) {
2012-04-21 21:59:45 +02:00
return decompress ( sjcl . decrypt ( key , data ) ) ;
}
2012-04-22 12:45:45 +02:00
/ * *
* @ return the current script location ( without search or hash part of the URL ) .
* eg . http : //server.com/zero/?aaaa#bbbb --> http://server.com/zero/
* /
function scriptLocation ( ) {
2012-04-21 21:59:45 +02:00
return window . location . href . substring ( 0 , window . location . href . length
- window . location . search . length - window . location . hash . length ) ;
}
2012-04-22 12:45:45 +02:00
/ * *
* @ return the paste unique identifier from the URL
* eg . 'c05354954c49a487'
* /
function pasteID ( ) {
2012-04-21 21:59:45 +02:00
return window . location . search . substring ( 1 ) ;
}
2012-04-22 12:45:45 +02:00
/ * *
* Set text of a DOM element ( required for IE )
* This is equivalent to element . text ( text )
* @ param object element : a DOM element .
* @ param string text : the text to enter .
* /
function setElementText ( element , text ) {
// For IE<10.
if ( $ ( 'div#oldienotice' ) . is ( ":visible" ) ) {
2012-04-21 21:59:45 +02:00
// IE<10 do not support white-space:pre-wrap; so we have to do this BIG UGLY STINKING THING.
element . text ( text . replace ( /\n/ig , '{BIG_UGLY_STINKING_THING__OH_GOD_I_HATE_IE}' ) ) ;
element . html ( element . text ( ) . replace ( /{BIG_UGLY_STINKING_THING__OH_GOD_I_HATE_IE}/ig , "\r\n<br>" ) ) ;
}
2012-04-22 12:45:45 +02:00
// for other (sane) browsers:
else {
2012-04-21 21:59:45 +02:00
element . text ( text ) ;
}
}
2012-04-22 12:45:45 +02:00
/ * *
* Show decrypted text in the display area , including discussion ( if open )
*
* @ param string key : decryption key
* @ param array comments : Array of messages to display ( items = array with keys ( 'data' , 'meta' )
* /
function displayMessages ( key , comments ) {
2012-04-21 21:59:45 +02:00
try { // Try to decrypt the paste.
2012-04-22 12:45:45 +02:00
var cleartext = zeroDecipher ( key , comments [ 0 ] . data ) ;
2012-04-21 21:59:45 +02:00
} catch ( err ) {
$ ( 'div#cleartext' ) . hide ( ) ;
$ ( 'button#clonebutton' ) . hide ( ) ;
showError ( 'Could not decrypt data (Wrong key ?)' ) ;
return ;
}
2012-04-22 12:45:45 +02:00
setElementText ( $ ( 'div#cleartext' ) , cleartext ) ;
2012-04-21 21:59:45 +02:00
urls2links ( $ ( 'div#cleartext' ) ) ; // Convert URLs to clickable links.
// Display paste expiration.
if ( comments [ 0 ] . meta . expire _date ) $ ( 'div#remainingtime' ) . removeClass ( 'foryoureyesonly' ) . text ( 'This document will expire in ' + secondsToHuman ( comments [ 0 ] . meta . remaining _time ) + '.' ) . show ( ) ;
2012-04-22 12:45:45 +02:00
if ( comments [ 0 ] . meta . burnafterreading ) {
2012-04-21 21:59:45 +02:00
$ ( 'div#remainingtime' ) . addClass ( 'foryoureyesonly' ) . text ( 'FOR YOUR EYES ONLY. Don\'t close this window, this message can\'t be displayed again.' ) . show ( ) ;
$ ( 'button#clonebutton' ) . hide ( ) ; // Discourage cloning (as it can't really be prevented).
}
// If the discussion is opened on this paste, display it.
2012-04-22 12:45:45 +02:00
if ( comments [ 0 ] . meta . opendiscussion ) {
2012-04-21 21:59:45 +02:00
$ ( 'div#comments' ) . html ( '' ) ;
2012-04-22 12:45:45 +02:00
// For each comment.
for ( var i = 1 ; i < comments . length ; i ++ ) {
2012-04-21 21:59:45 +02:00
var comment = comments [ i ] ;
var cleartext = "[Could not decrypt comment ; Wrong key ?]" ;
2012-04-22 12:45:45 +02:00
try {
cleartext = zeroDecipher ( key , comment . data ) ;
} catch ( err ) { }
2012-04-21 21:59:45 +02:00
var place = $ ( 'div#comments' ) ;
// If parent comment exists, display below (CSS will automatically shift it right.)
var cname = 'div#comment_' + comment . meta . parentid
2012-04-22 12:45:45 +02:00
// If the element exists in page
if ( $ ( cname ) . length ) {
place = $ ( cname ) ;
}
var divComment = $ ( '<div class="comment" id="comment_' + comment . meta . commentid + '">'
+ '<div class="commentmeta"><span class="nickname"></span><span class="commentdate"></span></div><div class="commentdata"></div>'
+ '<button onclick="open_reply($(this),\'' + comment . meta . commentid + '\');return false;">Reply</button>'
+ '</div>' ) ;
setElementText ( divComment . find ( 'div.commentdata' ) , cleartext ) ;
// Convert URLs to clickable links in comment.
urls2links ( divComment . find ( 'div.commentdata' ) ) ;
2012-04-21 21:59:45 +02:00
divComment . find ( 'span.nickname' ) . html ( '<i>(Anonymous)</i>' ) ;
2012-04-22 12:45:45 +02:00
2012-04-21 21:59:45 +02:00
// Try to get optional nickname:
2012-04-22 12:45:45 +02:00
try {
divComment . find ( 'span.nickname' ) . text ( zeroDecipher ( key , comment . meta . nickname ) ) ;
} catch ( err ) { }
divComment . find ( 'span.commentdate' ) . text ( ' (' + ( new Date ( comment . meta . postdate * 1000 ) . toUTCString ( ) ) + ')' ) . attr ( 'title' , 'CommentID: ' + comment . meta . commentid ) ;
2012-04-21 21:59:45 +02:00
// If an avatar is available, display it.
2012-04-22 12:45:45 +02:00
if ( comment . meta . vizhash ) {
divComment . find ( 'span.nickname' ) . before ( '<img src="' + comment . meta . vizhash + '" class="vizhash" title="Anonymous avatar (Vizhash of the IP address)" />' ) ;
}
2012-04-21 21:59:45 +02:00
place . append ( divComment ) ;
}
2012-04-22 12:45:45 +02:00
$ ( 'div#comments' ) . append ( '<div class="comment"><button onclick="open_reply($(this),\'' + pasteID ( ) + '\');return false;">Add comment</button></div>' ) ;
2012-04-21 21:59:45 +02:00
$ ( 'div#discussion' ) . show ( ) ;
}
}
2012-04-22 12:45:45 +02:00
/ * *
* Open the comment entry when clicking the "Reply" button of a comment .
* @ param object source : element which emitted the event .
* @ param string commentid = identifier of the comment we want to reply to .
* /
function open _reply ( source , commentid ) {
2012-04-21 21:59:45 +02:00
$ ( 'div.reply' ) . remove ( ) ; // Remove any other reply area.
source . after ( '<div class="reply">'
2012-04-22 12:45:45 +02:00
+ '<input type="text" id="nickname" title="Optional nickname..." value="Optional nickname..." />'
+ '<textarea id="replymessage" class="replymessage" cols="80" rows="7"></textarea>'
+ '<br><button id="replybutton" onclick="send_comment(\'' + commentid + '\');return false;">Post comment</button>'
+ '<div id="replystatus"> </div>'
+ '</div>' ) ;
2012-04-21 21:59:45 +02:00
$ ( 'input#nickname' ) . focus ( function ( ) {
2012-04-22 12:45:45 +02:00
$ ( this ) . css ( 'color' , '#000' ) ;
if ( $ ( this ) . val ( ) == $ ( this ) . attr ( 'title' ) ) {
$ ( this ) . val ( '' ) ;
}
} ) ;
2012-04-21 21:59:45 +02:00
$ ( 'textarea#replymessage' ) . focus ( ) ;
}
2012-04-22 12:45:45 +02:00
/ * *
* Send a reply in a discussion .
* @ param string parentid : the comment identifier we want to send a reply to .
* /
function send _comment ( parentid ) {
// Do not send if no data.
if ( $ ( 'textarea#replymessage' ) . val ( ) . length == 0 ) {
return ;
}
showStatus ( 'Sending comment...' , spin = true ) ;
var cipherdata = zeroCipher ( pageKey ( ) , $ ( 'textarea#replymessage' ) . val ( ) ) ;
var ciphernickname = '' ;
2012-04-21 21:59:45 +02:00
var nick = $ ( 'input#nickname' ) . val ( ) ;
2012-04-22 12:45:45 +02:00
if ( nick != '' && nick != 'Optional nickname...' ) {
ciphernickname = ciphernickname = zeroCipher ( pageKey ( ) , nick ) ;
}
var data _to _send = { data : cipherdata ,
parentid : parentid ,
pasteid : pasteID ( ) ,
nickname : ciphernickname
} ;
2012-04-21 21:59:45 +02:00
2012-04-22 12:45:45 +02:00
$
. post ( scriptLocation ( ) , data _to _send , 'json' )
. error ( function ( ) {
showError ( 'Comment could not be sent (serveur error or not responding).' ) ;
} )
. success ( function ( data ) {
if ( data . status == 0 ) {
showStatus ( 'Comment posted.' ) ;
location . reload ( ) ;
}
else if ( data . status == 1 ) {
showError ( 'Could not post comment: ' + data . message ) ;
}
else {
showError ( 'Could not post comment.' ) ;
}
} ) ;
}
/ * *
* Send a new paste to server
* /
function send _data ( ) {
// Do not send if no data.
if ( $ ( 'textarea#message' ) . val ( ) . length == 0 ) {
return ;
}
showStatus ( 'Sending paste...' , spin = true ) ;
var randomkey = sjcl . codec . base64 . fromBits ( sjcl . random . randomWords ( 8 , 0 ) , 0 ) ;
var cipherdata = zeroCipher ( randomkey , $ ( 'textarea#message' ) . val ( ) ) ;
var data _to _send = { data : cipherdata ,
expire : $ ( 'select#pasteExpiration' ) . val ( ) ,
opendiscussion : $ ( 'input#opendiscussion' ) . is ( ':checked' ) ? 1 : 0
} ;
$
. post ( scriptLocation ( ) , data _to _send , 'json' )
. error ( function ( ) {
showError ( 'Data could not be sent (serveur error or not responding).' ) ;
} )
. success ( function ( data ) {
if ( data . status == 0 ) {
stateExistingPaste ( ) ;
var url = scriptLocation ( ) + "?" + data . id + '#' + randomkey ;
showStatus ( '' ) ;
$ ( 'div#pastelink' ) . html ( 'Your paste is <a href="' + url + '">' + url + '</a>' ) ;
$ ( 'div#pastelink' )
. append ( ' <button id="shortenbutton" onclick="document.location=\'' + shortenUrl ( url ) + '\'"><img src="lib/icon_shorten.png" width="13" height="15" />Shorten URL</button>' )
. show ( ) ;
setElementText ( $ ( 'div#cleartext' ) , $ ( 'textarea#message' ) . val ( ) ) ;
urls2links ( $ ( 'div#cleartext' ) ) ;
showStatus ( '' ) ;
}
else if ( data . status == 1 ) {
showError ( 'Could not create paste: ' + data . message ) ;
}
else {
showError ( 'Could not create paste.' ) ;
}
} ) ;
2012-04-21 21:59:45 +02:00
}
2012-04-22 12:45:45 +02:00
/ * *
* Put the screen in "New paste" mode .
* /
function stateNewPaste ( ) {
$ ( '#sendbutton' ) . show ( ) ;
$ ( '#clonebutton' ) . hide ( ) ;
2012-04-21 21:59:45 +02:00
$ ( 'div#expiration' ) . show ( ) ;
$ ( 'div#remainingtime' ) . hide ( ) ;
$ ( 'div#language' ) . hide ( ) ; // $('#language').show();
$ ( 'input#password' ) . hide ( ) ; //$('#password').show();
$ ( 'div#opendisc' ) . show ( ) ;
2012-04-22 12:45:45 +02:00
$ ( '#newbutton' ) . show ( ) ;
2012-04-21 21:59:45 +02:00
$ ( 'div#pastelink' ) . hide ( ) ;
$ ( 'textarea#message' ) . text ( '' ) ;
$ ( 'textarea#message' ) . show ( ) ;
$ ( 'div#cleartext' ) . hide ( ) ;
$ ( 'div#message' ) . focus ( ) ;
$ ( 'div#discussion' ) . hide ( ) ;
}
2012-04-22 12:45:45 +02:00
/ * *
* Put the screen in "Existing paste" mode .
* /
function stateExistingPaste ( ) {
$ ( '#sendbutton' ) . hide ( ) ;
// No "clone" for IE<10.
if ( ! $ ( 'div#oldienotice' ) . is ( ":visible" ) ) {
$ ( 'button#clonebutton' ) . show ( ) ;
}
/ * *
* @ FIXME
* /
$ ( '#clonebutton' ) . show ( ) ;
2012-04-21 21:59:45 +02:00
$ ( 'div#expiration' ) . hide ( ) ;
$ ( 'div#language' ) . hide ( ) ;
$ ( 'input#password' ) . hide ( ) ;
$ ( 'div#opendisc' ) . hide ( ) ;
2012-04-22 12:45:45 +02:00
$ ( '#newbutton' ) . show ( ) ;
2012-04-21 21:59:45 +02:00
$ ( 'div#pastelink' ) . hide ( ) ;
$ ( 'textarea#message' ) . hide ( ) ;
$ ( 'div#cleartext' ) . show ( ) ;
}
2012-04-22 12:45:45 +02:00
/ * *
* Clone the current paste .
* /
function clonePaste ( ) {
2012-04-21 21:59:45 +02:00
stateNewPaste ( ) ;
showStatus ( '' ) ;
$ ( 'textarea#message' ) . text ( $ ( 'div#cleartext' ) . text ( ) ) ;
}
2012-04-22 12:45:45 +02:00
/ * *
* Create a new paste .
* /
function newPaste ( ) {
2012-04-21 21:59:45 +02:00
stateNewPaste ( ) ;
showStatus ( '' ) ;
$ ( 'textarea#message' ) . text ( '' ) ;
}
2012-04-22 12:45:45 +02:00
/ * *
* Display an error message
* ( We use the same function for paste and reply to comments )
* /
function showError ( message ) {
2012-04-21 21:59:45 +02:00
$ ( 'div#status' ) . addClass ( 'errorMessage' ) . text ( message ) ;
$ ( 'div#replystatus' ) . addClass ( 'errorMessage' ) . text ( message ) ;
}
2012-04-22 12:45:45 +02:00
/ * *
* Display status
* ( We use the same function for paste and reply to comments )
*
* @ param string message : text to display
* @ param boolean spin ( optional ) : tell if the "spinning" animation should be displayed .
* /
function showStatus ( message , spin ) {
2012-04-21 21:59:45 +02:00
$ ( 'div#replystatus' ) . removeClass ( 'errorMessage' ) ;
$ ( 'div#replystatus' ) . text ( message ) ;
2012-04-22 12:45:45 +02:00
if ( ! message ) {
$ ( 'div#status' ) . html ( ' ' ) ;
return ;
}
if ( message == '' ) {
$ ( 'div#status' ) . html ( ' ' ) ;
return ;
}
2012-04-21 21:59:45 +02:00
$ ( 'div#status' ) . removeClass ( 'errorMessage' ) ;
$ ( 'div#status' ) . text ( message ) ;
2012-04-22 12:45:45 +02:00
if ( spin ) {
var img = '<img src="img/busy.gif" style="width:16px;height:9px;margin:0px 4px 0px 0px;" />' ;
2012-04-21 21:59:45 +02:00
$ ( 'div#status' ) . prepend ( img ) ;
$ ( 'div#replystatus' ) . prepend ( img ) ;
}
}
2012-04-22 12:45:45 +02:00
/ * *
* Generate link to URL shortener .
* /
function shortenUrl ( url ) {
return 'http://snipurl.com/site/snip?link=' + encodeURIComponent ( url ) ;
2012-04-21 21:59:45 +02:00
}
2012-04-22 12:45:45 +02:00
/ * *
* Convert URLs to clickable links .
* URLs to handle :
* < code >
* magnet : ? xt . 1 = urn : sha1 : YNCKHTQCWBTRNJIV4WNAE52SJUQCZO5C & xt . 2 = urn : sha1 : TXGCZQTH26NL6OUQAJJPFALHG2LTGBC7
* http : //localhost:8800/zero/?6f09182b8ea51997#WtLEUO5Epj9UHAV9JFs+6pUQZp13TuspAUjnF+iM+dM=
* http : //user:password@localhost:8800/zero/?6f09182b8ea51997#WtLEUO5Epj9UHAV9JFs+6pUQZp13TuspAUjnF+iM+dM=
* < / c o d e >
*
* @ param object element : a jQuery DOM element .
* @ FIXME : add ppa & apt links .
* /
function urls2links ( element ) {
2012-04-21 21:59:45 +02:00
var re = /((http|https|ftp):\/\/[\w?=&.\/-;#@~%+-]+(?![\w\s?&.\/;#~%"=-]*>))/ig ;
element . html ( element . html ( ) . replace ( re , '<a href="$1" rel="nofollow">$1</a>' ) ) ;
var re = /((magnet):[\w?=&.\/-;#@~%+-]+)/ig ;
element . html ( element . html ( ) . replace ( re , '<a href="$1">$1</a>' ) ) ;
}
2012-04-22 12:45:45 +02:00
/ * *
* Return the deciphering key stored in anchor part of the URL
* /
function pageKey ( ) {
2012-04-21 21:59:45 +02:00
var key = window . location . hash . substring ( 1 ) ; // Get key
// Some stupid web 2.0 services and redirectors add data AFTER the anchor
// (such as &utm_source=...).
// We will strip any additional data.
// First, strip everything after the equal sign (=) which signals end of base64 string.
i = key . indexOf ( '=' ) ; if ( i > - 1 ) { key = key . substring ( 0 , i + 1 ) ; }
// If the equal sign was not present, some parameters may remain:
i = key . indexOf ( '&' ) ; if ( i > - 1 ) { key = key . substring ( 0 , i ) ; }
// Then add trailing equal sign if it's missing
if ( key . charAt ( key . length - 1 ) !== '=' ) key += '=' ;
return key ;
}
2012-04-22 12:45:45 +02:00
$ ( function ( ) {
2012-04-21 21:59:45 +02:00
$ ( 'select#pasteExpiration' ) . change ( function ( ) {
2012-04-22 12:45:45 +02:00
if ( $ ( this ) . val ( ) == 'burn' ) {
$ ( 'div#opendisc' ) . addClass ( 'buttondisabled' ) ;
$ ( 'input#opendiscussion' ) . attr ( 'disabled' , true ) ;
}
else {
$ ( 'div#opendisc' ) . removeClass ( 'buttondisabled' ) ;
$ ( 'input#opendiscussion' ) . removeAttr ( 'disabled' ) ;
}
2012-04-21 21:59:45 +02:00
} ) ;
2012-04-22 12:45:45 +02:00
// Display an existing paste
if ( $ ( 'div#cipherdata' ) . text ( ) . length > 1 ) {
// Missing decryption key in URL ?
if ( window . location . hash . length == 0 ) {
2012-04-21 21:59:45 +02:00
showError ( 'Cannot decrypt paste: Decryption key missing in URL (Did you use a redirector or an URL shortener which strips part of the URL ?)' ) ;
return ;
}
2012-04-22 12:45:45 +02:00
// List of messages to display
var messages = jQuery . parseJSON ( $ ( 'div#cipherdata' ) . text ( ) ) ;
// Show proper elements on screen.
stateExistingPaste ( ) ;
displayMessages ( pageKey ( ) , messages ) ;
2012-04-21 21:59:45 +02:00
}
2012-04-22 12:45:45 +02:00
// Display error message from php code.
else if ( $ ( 'div#errormessage' ) . text ( ) . length > 1 ) {
2012-04-21 21:59:45 +02:00
showError ( $ ( 'div#errormessage' ) . text ( ) ) ;
}
2012-04-22 12:45:45 +02:00
// Create a new paste.
else {
2012-04-21 21:59:45 +02:00
newPaste ( ) ;
}
} ) ;