removed leftovers from submodule uglifyjs, added credits file,

cleaned up CSS, changed template to output clean XHTML 5,
added unit tests for 60% of the code, found a few bugs by doing
that and fixed them
This commit is contained in:
Simon Rupf 2012-08-26 00:49:11 +02:00
parent f37303d858
commit 907538875b
32 changed files with 961 additions and 511 deletions

1
.gitignore vendored
View file

@ -1,6 +1,7 @@
# Ignore data/ and tmp/
data/
tmp/
tst/log/
.settings/
.buildpath
.project

3
.gitmodules vendored
View file

@ -1,3 +0,0 @@
[submodule "uglifyjs"]
path = uglifyjs
url = https://github.com/mishoo/UglifyJS.git

8
CREDITS.md Normal file
View file

@ -0,0 +1,8 @@
Credits
=======
Sébastien Sauvage - original idea and main developer
Alexey Gladkov - syntax highlighting
Greg Knaddison - robots.txt
MrKooky - XHTML5 markup, CSS cleanup
Simon Rupf - MVC refactoring, configuration support and unit tests

View file

@ -1,13 +1,13 @@
Documentation
=============
Installation
============
For Administrators
------------------
In the index.php in the main folder you can define a different PATH. This is
useful if you want to secure your installation and want to move the
configuration, data files, templates and PHP libraries (directories cfg, lib
and tpl) outside of your document root. This new location must still be
configuration, data files, templates and PHP libraries (directories cfg, data,
lib and tpl) outside of your document root. This new location must still be
accessible to your webserver / PHP process.
> ### PATH Example ###
@ -26,7 +26,7 @@ In the file "cfg/conf.ini" you can configure ZeroBin. The config file is
divided into multiple sections, which are enclosed in square brackets. In the
"[main]" section you can enable or disable the discussion feature, set the
limit of stored pastes and comments in bytes. The "[traffic]" section lets you
set a time limit in seconds. Users may not post more often the this limit to
set a time limit in seconds. Users may not post more often then this limit to
your ZeroBin.
Finally the "[model]" and "[model_options]" sections let you configure your
@ -35,16 +35,16 @@ favourite way of storing the pastes and discussions on your server.
data folder. This is the recommended setup for low traffic sites. Under high
load, in distributed setups or if you are not allowed to store files locally,
you might want to switch to the "zerobin_db" model. This lets you store your
data in a database. Basically all databases, that are supported by PDO (PHP
data in a database. Basically all databases that are supported by PDO (PHP
data objects) may be used. Automatic table creation is provided for pdo_ibm,
pdo_informix, pdo_mssql, pdo_mysql, pdo_oci, pdo_pgsql and pdo_sqlite. You may
want to provide a table prefix, if you have to share the zerobin database with
another application. The table prefix option is called "tbl".
> ### Note ###
> The "zerobin_db" model has only been tested with sqlite and MySQL, although
> it would not be recommended to use sqlite in a production environment. If you
> gain any experience running ZeroBin on other RDBMS, let us know.
> The "zerobin_db" model has only been tested with SQLite and MySQL, although
> it would not be recommended to use SQLite in a production environment. If you
> gain any experience running ZeroBin on other RDBMS, please let us know.
For reference or if you want to create the table schema for yourself:
@ -69,7 +69,8 @@ For reference or if you want to create the table schema for yourself:
For Developers
--------------
If you want to create your own data models, you might want to know how the arrays, that you have to store, look like:
If you want to create your own data models, you might want to know how the
arrays, that you have to store, look like:
public function create($pasteid, $paste)
{
@ -88,9 +89,9 @@ If you want to create your own data models, you might want to know how the array
$parentid // the id of the parent of this comment, may be the paste id itself
$commentid = substr(hash('md5', $paste['data']), 0, 16);
$paste['data'] // text
$paste['meta']['nickname'] // text or null (if anonymous)
$paste['meta']['vizhash'] // text or null (if anonymous)
$paste['meta']['postdate'] // int UNIX timestamp
$comment['data'] // text
$comment['meta']['nickname'] // text or null (if anonymous)
$comment['meta']['vizhash'] // text or null (if anonymous)
$comment['meta']['postdate'] // int UNIX timestamp
}

View file

@ -38,8 +38,8 @@ dir = PATH "data"
;[model]
; example of DB configuration for SQLite
;[model_options]
;class = zerobin_db
;[model_options]
;dsn = "sqlite:" PATH "data/db.sq3"
;usr = null
;pwd = null

View file

@ -32,7 +32,13 @@
}
/* Put a border around prettyprinted code snippets. */
pre.prettyprint { padding: 2px; border: 1px solid #888; background-color:white; white-space:pre-wrap; }
.prettyprint {
padding: 2px;
border: 1px solid #888;
background-color: white;
white-space: pre-wrap;
clear: both;
}
/* Specify class=linenums on a pre to get line numbering */
ol.linenums {

View file

@ -4,32 +4,33 @@
/* CSS Reset from YUI 3.4.1 (build 4118) - Copyright 2011 Yahoo! Inc. All rights reserved.
Licensed under the BSD License. - http://yuilibrary.com/license/ */
html{color:#000;background:#FFF}body,div,dl,dt,dd,ul,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote,th,td{margin:0;padding:0}table{border-collapse:collapse;border-spacing:0}fieldset,img{border:0}address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal}ol,ul{list-style:none}caption,th{text-align:left}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal}q:before,q:after{content:''}abbr,acronym{border:0;font-variant:normal}sup{vertical-align:text-top}sub{vertical-align:text-bottom}input,textarea,select{font-family:inherit;font-size:inherit;font-weight:inherit}input,textarea,select{*font-size:100%}legend{color:#000}
html{color:#000;background:#fff}body,div,dl,dt,dd,ul,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote,th,td{margin:0;padding:0}table{border-collapse:collapse;border-spacing:0}fieldset,img{border:0}address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal}ol,ul{list-style:none}caption,th{text-align:left}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal}q:before,q:after{content:''}abbr,acronym{border:0;font-variant:normal}sup{vertical-align:text-top}sub{vertical-align:text-bottom}input,textarea,select{font-family:inherit;font-size:inherit;font-weight:inherit}input,textarea,select{*font-size:100%}legend{color:#000}
html {
background-color: #455463;
color:white;
color: #fff;
min-height: 100%;
background-image: linear-gradient(bottom, #0F1823 0%, #455463 100%);
background-image: -o-linear-gradient(bottom, #0F1823 0%, #455463 100%);
background-image: -moz-linear-gradient(bottom, #0F1823 0%, #455463 100%);
background-image: -webkit-linear-gradient(bottom, #0F1823 0%, #455463 100%);
background-image: -ms-linear-gradient(bottom, #0F1823 0%, #455463 100%);
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #0F1823), color-stop(1, #455463));
background-image: linear-gradient(bottom, #0f1823 0%, #455463 100%);
background-image: -o-linear-gradient(bottom, #0f1823 0%, #455463 100%);
background-image: -moz-linear-gradient(bottom, #0f1823 0%, #455463 100%);
background-image: -webkit-linear-gradient(bottom, #0f1823 0%, #455463 100%);
background-image: -ms-linear-gradient(bottom, #0f1823 0%, #455463 100%);
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #0f1823), color-stop(1, #455463));
}
body {
font-family: Arial, Helvetica, sans-serif;
font-size: 0.8em;
margin-bottom: 15px;
padding-left:60px; padding-right:60px;
padding-left: 60px;
padding-right: 60px;
}
a { color:#0F388F; }
a { color: #0f388f; }
h1 {
font-size: 3.5em;
font-weight:700;
font-weight: bold;
color: #000;
position: relative;
display: inline;
@ -52,14 +53,17 @@ display:inline;
font-style: italic;
font-weight: bold;
position: relative;
bottom:8px;}
bottom: 8px;
}
h3 {
color: #94a3b4;
font-size: 0.7em;
display: inline;
margin-top: 10px;
position: relative;
bottom:8px;}
bottom: 8px;
}
#aboutbox {
font-size: 0.85em;
@ -72,11 +76,11 @@ float:right;
width: 60%;
}
div#aboutbox a { color: #94a3b4; }
#aboutbox a { color: #94a3b4; }
textarea#message,div#cleartext,.replymessage {
#message, #cleartext, .replymessage {
clear: both;
color:black;
color: #000;
background-color: #fff;
white-space: pre-wrap;
font-family: Consolas, "Lucida Console", "DejaVu Sans Mono", Monaco, monospace;
@ -91,113 +95,139 @@ box-sizing:border-box;
width: 100%;
}
div#status {
#status {
clear: both;
padding: 5px 10px;
}
div#pastelink {
#pastelink {
background-color: #1F2833;
color:white;
color: #fff;
padding: 4px 12px;
clear: both;
-moz-box-shadow: inset 0px 2px 2px #000;
-webkit-box-shadow: inset 0px 2px 2px #000;
box-shadow: inset 0px 2px 5px #000;
-moz-box-shadow: inset 0 2px 2px #000;
-webkit-box-shadow: inset 0 2px 2px #000;
box-shadow: inset 0 2px 2px #000;
}
div#pastelink a { color:white; }
div#pastelink button { margin-left:11px }
div#toolbar, div#status { margin-bottom:5px; }
button,.button,div#expiration,div#language {
#pastelink a { color: #fff; }
#pastelink button { margin-left: 11px }
#toolbar, #status { margin-bottom: 5px; }
button, .button, #expiration, #language {
color: #fff;
background-color:#323B47;
background-color: #323b47;
background-repeat: no-repeat;
background-position: center left;
padding: 4px 8px;
font-size: 1em;
margin-right: 5px;
display: inline;
background-image: linear-gradient(bottom, #323B47 0%, #51606E 100%);
background-image: -o-linear-gradient(bottom, #323B47 0%, #51606E 100%);
background-image: -moz-linear-gradient(bottom, #323B47 0%, #51606E 100%);
background-image: -webkit-linear-gradient(bottom, #323B47 0%, #51606E 100%);
background-image: -ms-linear-gradient(bottom, #323B47 0%, #51606E 100%);
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #323B47), color-stop(1, #51606E));
background-image: linear-gradient(bottom, #323b47 0, #51606e 100%);
background-image: -o-linear-gradient(bottom, #323b47 0, #51606e 100%);
background-image: -moz-linear-gradient(bottom, #323b47 0, #51606e 100%);
background-image: -webkit-linear-gradient(bottom, #323b47 0, #51606e 100%);
background-image: -ms-linear-gradient(bottom, #323b47 0, #51606e 100%);
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #323b47), color-stop(1, #51606e));
border: 1px solid #28343F;
-moz-box-shadow: inset 0px 1px 2px #647384;
-webkit-box-shadow: inset 0px 1px 2px #647384;
box-shadow: inset 0px 1px 2px #647384;
-moz-box-shadow: inset 0 1px 2px #647384;
-webkit-box-shadow: inset 0 1px 2px #647384;
box-shadow: inset 0 1px 2px #647384;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
-moz-background-clip: padding; -webkit-background-clip: padding-box; background-clip: padding-box;
-moz-background-clip: padding;
-webkit-background-clip: padding-box;
background-clip: padding-box;
}
button:hover {
background-image: linear-gradient(bottom, #424B57 0%, #61707E 100%);
background-image: -o-linear-gradient(bottom, #424B57 0%, #61707E 100%);
background-image: -moz-linear-gradient(bottom, #424B57 0%, #61707E 100%);
background-image: -webkit-linear-gradient(bottom, #424B57 0%, #61707E 100%);
background-image: -ms-linear-gradient(bottom, #424B57 0%, #61707E 100%);
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #424B57), color-stop(1, #61707E));
background-image: linear-gradient(bottom, #424b57 0%, #61707e 100%);
background-image: -o-linear-gradient(bottom, #424b57 0%, #61707e 100%);
background-image: -moz-linear-gradient(bottom, #424b57 0%, #61707e 100%);
background-image: -webkit-linear-gradient(bottom, #424b57 0%, #61707e 100%);
background-image: -ms-linear-gradient(bottom, #424b57 0%, #61707e 100%);
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #424b57), color-stop(1, #61707e));
}
button:active {
background-image: linear-gradient(bottom, #51606E 0%, #323B47 100%);
background-image: -o-linear-gradient(bottom, #51606E 0%, #323B47 100%);
background-image: -moz-linear-gradient(bottom, #51606E 0%, #323B47 100%);
background-image: -webkit-linear-gradient(bottom, #51606E 0%, #323B47 100%);
background-image: -ms-linear-gradient(bottom, #51606E 0%, #323B47 100%);
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #51606E), color-stop(1, #323B47));
background-image: linear-gradient(bottom, #51606e 0, #323b47 100%);
background-image: -o-linear-gradient(bottom, #51606e 0, #323b47 100%);
background-image: -moz-linear-gradient(bottom, #51606e 0, #323b47 100%);
background-image: -webkit-linear-gradient(bottom, #51606e 0, #323b47 100%);
background-image: -ms-linear-gradient(bottom, #51606e 0, #323b47 100%);
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #51606e), color-stop(1, #323b47));
position:relative;
top:1px;
}
button:disabled, .buttondisabled {
background: #ccc;
color: #888;
top:0px;
top: 0;
}
button img {
margin-right: 8px;
position: relative;
top: 2px;
}
div#expiration, div#language, div#opendisc {
background-color:#414D5A;
#expiration, #language, #opendisc {
background-color: #414d5a;
padding: 6px 8px;
margin:0px 5px 0px 0px;;
margin: 0 5px 0 0;
position: relative;
bottom: 1px; /* WTF ? Why is this shifted by 1 pixel ? */
}
div#expiration select, div#language select {
#expiration select, #language select {
color: #eee;
background: transparent;
border: none;
}
div#expiration select option, div#language select option {
#expiration select option, #language select option {
color:#eee;
background: #414D5A;
background-color:#414D5A;
background: #414d5a;
}
div#remainingtime {
#remainingtime {
color: #94a3b4;
display: inline;
font-size: 0.85em;
}
#newbutton {
float: right;
margin-right: 0;
margin-bottom: 5px;
display: inline;
}
input {
color: #777;
font-size: 1em;
padding: 6px;
border: 1px solid #28343f;
}
.blink {
text-decoration: blink;
font-size: 0.8em;
color: #a4b3c4;
}
.foryoureyesonly {
color: yellow !important;
color: #ff0 !important;
font-size: 1em !important;
font-weight: bold !important;
}
button#newbutton { float:right; margin-right:0px;margin-bottom:5px; display:inline; }
input { color:#777; font-size:1em; padding:6px; border: 1px solid #28343F; }
.nonworking {
background-color: #fff;
color: #000;
@ -211,8 +241,10 @@ border-radius:4px;
padding: 5px;
}
div#ienotice {
background-color:#7E98AF;
.hidden { display: none !important; }
#ienotice {
background-color: #7e98af;
color: #000;
font-size: 0.85em;
padding: 3px 5px;
@ -223,24 +255,18 @@ border-radius:4px;
display: none;
}
div#ienotice a {
color:black;
}
#ienotice a { color: #000; }
div#oldienotice {
display:none;
}
#oldienotice { display: none; }
.errorMessage {
background-color:#FF7979 !important;
color:#FF0;
background-color: #f77 !important;
color:#ff0;
}
/* --- discussion related CSS ------- */
div#discussion { /* Discussion container */
#discussion { /* Discussion container */
margin-top: 20px;
width: 100%;
margin-left: -30px;
@ -249,17 +275,16 @@ min-width:200px;
h4 {
font-size: 1.2em;
color: #94A3B4;
color: #94a3b4;
font-style: italic;
font-weight: bold;
position: relative;
margin-left: 30px;
}
div.comment /* One single reply */
.comment /* One single reply */
{
background-color:#CECED6;
background-color: #ceced6;
color: #000;
white-space: pre-wrap;
font-family: Consolas,"Lucida Console","DejaVu Sans Mono",Monaco,monospace;
@ -274,79 +299,74 @@ box-shadow: -3px -3px 5px rgba(0,0,0,0.15);
min-width: 200px;
overflow: auto;
}
/* FIXME: Add min-width */
div.reply {
margin: 5px 0px 0px 30px;
}
.reply { margin: 5px 0 0 30px; }
div#replystatus {
#replystatus {
display: inline;
padding: 1px 7px;
font-family: Arial, Helvetica, sans-serif;
}
div.comment button {
.comment button {
color: #446;
background-color: #aab;
background-repeat: no-repeat;
background-position: center left;
padding:0px 2px;
padding: 0 2px;
font-size: 0.73em;
margin: 3px 5px 3px 0px;
margin: 3px 5px 3px 0;
display: inline;
background-image: linear-gradient(bottom, #aab 0%, #ccc 100%);
background-image: -o-linear-gradient(bottom, #aab 0%, #ccc 100%);
background-image: -moz-linear-gradient(bottom, #aab 0%, #ccc 100%);
background-image: -webkit-linear-gradient(bottom, #aab 0%, #ccc 100%);
background-image: -ms-linear-gradient(bottom, #aab 0%, #ccc 100%);
background-image: linear-gradient(bottom, #aab 0, #ccc 100%);
background-image: -o-linear-gradient(bottom, #aab 0, #ccc 100%);
background-image: -moz-linear-gradient(bottom, #aab 0, #ccc 100%);
background-image: -webkit-linear-gradient(bottom, #aab 0, #ccc 100%);
background-image: -ms-linear-gradient(bottom, #aab 0, #ccc 100%);
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #aab), color-stop(1, #ccc));
border: 1px solid #ccd;
-moz-box-shadow: inset 0px 1px 2px #ddd;
-webkit-box-shadow: inset 0px 1px 2px #fff;
box-shadow: inset 0px 1px 2px #eee;
-moz-box-shadow: inset 0 1px 2px #ddd;
-webkit-box-shadow: inset 0 1px 2px #fff;
box-shadow: inset 0 1px 2px #eee;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
-moz-background-clip: padding; -webkit-background-clip: padding-box; background-clip: padding-box;
-moz-background-clip: padding;
-webkit-background-clip: padding-box;
background-clip: padding-box;
}
div.comment button:hover {
background-image: linear-gradient(bottom, #ccd 0%, #fff 100%);
background-image: -o-linear-gradient(bottom, #ccd 0%, #fff 100%);
background-image: -moz-linear-gradient(bottom, #ccd 0%, #fff 100%);
background-image: -webkit-linear-gradient(bottom, #ccd 0%, #fff 100%);
background-image: -ms-linear-gradient(bottom, #ccd 0%, #fff 100%);
.comment button:hover {
background-image: linear-gradient(bottom, #ccd 0, #fff 100%);
background-image: -o-linear-gradient(bottom, #ccd 0, #fff 100%);
background-image: -moz-linear-gradient(bottom, #ccd 0, #fff 100%);
background-image: -webkit-linear-gradient(bottom, #ccd 0, #fff 100%);
background-image: -ms-linear-gradient(bottom, #ccd 0, #fff 100%);
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #ccd), color-stop(1, #fff));
}
div.comment button:active {
background-image: linear-gradient(bottom, #fff 0%, #889 100%);
background-image: -o-linear-gradient(bottom, #fff 0%, #889 100%);
background-image: -moz-linear-gradient(bottom, #fff 0%, #889 100%);
background-image: -webkit-linear-gradient(bottom, #fff 0%, #889 100%);
background-image: -ms-linear-gradient(bottom, #fff 0%, #889 100%);
.comment button:active {
background-image: linear-gradient(bottom, #fff 0, #889 100%);
background-image: -o-linear-gradient(bottom, #fff 0, #889 100%);
background-image: -moz-linear-gradient(bottom, #fff 0, #889 100%);
background-image: -webkit-linear-gradient(bottom, #fff 0, #889 100%);
background-image: -ms-linear-gradient(bottom, #fff 0, #889 100%);
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #fff), color-stop(1, #889));
position:relative;
top:1px;
}
div.comment input {
padding:2px;
}
.comment input { padding: 2px; }
textarea#replymessage {
margin-top:5px;
}
#replymessage { margin-top: 5px; }
div.commentmeta {
.commentmeta {
color: #fff;
background-color:#8EA0B2;
background-color: #8ea0b2;
margin-bottom: 3px;
padding:0px 0px 0px 3px;
padding: 0 0 0 3px;
}
span.commentdate {
color: #BFCEDE;
}
.commentdate { color: #bfcede; }
img.vizhash {
width: 16px;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 391 B

After

Width:  |  Height:  |  Size: 381 B

View file

@ -1,39 +0,0 @@
#!/bin/sh -eu
myname="$(readlink -ev "$0")"
compiler='uglifyjs/bin/uglifyjs'
SOURCES='cfg css img lib tpl index.php'
JSDIR='js'
if [ "$#" -eq 0 ]; then
printf 'Usage: %s <destdir>\n' "${0##*/}"
exit
fi
destdir="$1"
shift
if [ ! -d "$destdir" ]; then
printf 'Error: %s: Not directory\n' "$destdir"
exit 1
fi
destdir="$(readlink -ev "$destdir")"
cd "${myname%/*}"
cp -aurt "$destdir" -- $SOURCES
mkdir -p -- "$destdir/js"
for src in "$JSDIR"/*.js; do
[ -f "$src" ] ||
continue
printf 'Processing %s ... ' "$src"
rc='done'
$compiler -nc -c -o "$destdir/js/${src##*/}" "$src" || rc='fail'
printf '%s\n' "$rc"
[ "$rc" = 'done' ] ||
exit 1
done

View file

@ -1347,7 +1347,7 @@ var REGEXP_PRECEDER_PATTERN = '(?:^^\\.?|[+-]|\\!|\\!=|\\!==|\\#|\\%|\\%=|&|&&|&
function prettyPrint(opt_whenDone) {
function byTagName(tn) { return document.getElementsByTagName(tn); }
// fetch a list of nodes to rewrite
var codeSegments = [byTagName('pre'), byTagName('code'), byTagName('xmp')];
var codeSegments = [byTagName('pre'), byTagName('code'), byTagName('xmp'), byTagName('div')];
var elements = [];
for (var i = 0; i < codeSegments.length; ++i) {
for (var j = 0, n = codeSegments[i].length; j < n; ++j) {

View file

@ -94,7 +94,7 @@ function setElementText(element, text) {
if ($('div#oldienotice').is(":visible")) {
// 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>"));
element.html(element.text().replace(/{BIG_UGLY_STINKING_THING__OH_GOD_I_HATE_IE}/ig,"\n<br />"));
}
// for other (sane) browsers:
else {
@ -112,9 +112,9 @@ function displayMessages(key, comments) {
try { // Try to decrypt the paste.
var cleartext = zeroDecipher(key, comments[0].data);
} catch(err) {
$('div#cleartext').hide();
$('div#prettymessage').hide();
$('button#clonebutton').hide();
$('div#cleartext').addClass('hidden');
$('div#prettymessage').addClass('hidden');
$('button#clonebutton').addClass('hidden');
showError('Could not decrypt data (Wrong key ?)');
return;
}
@ -127,7 +127,7 @@ function displayMessages(key, comments) {
if (comments[0].meta.expire_date) $('div#remainingtime').removeClass('foryoureyesonly').text('This document will expire in '+secondsToHuman(comments[0].meta.remaining_time)+'.').show();
if (comments[0].meta.burnafterreading) {
$('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).
$('button#clonebutton').addClass('hidden'); // Discourage cloning (as it can't really be prevented).
}
// If the discussion is opened on this paste, display it.
@ -148,10 +148,10 @@ function displayMessages(key, comments) {
if ($(cname).length) {
place = $(cname);
}
var divComment = $('<div class="comment" id="comment_' + comment.meta.commentid+'">'
var divComment = $('<article><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>');
+ '</div></article>');
setElementText(divComment.find('div.commentdata'), cleartext);
// Convert URLs to clickable links in comment.
urls2links(divComment.find('div.commentdata'));
@ -171,7 +171,7 @@ function displayMessages(key, comments) {
place.append(divComment);
}
$('div#comments').append('<div class="comment"><button onclick="open_reply($(this),\'' + pasteID() + '\');return false;">Add comment</button></div>');
$('div#discussion').show();
$('div#discussion').removeClass('hidden');
}
}
@ -185,8 +185,8 @@ function open_reply(source, commentid) {
source.after('<div class="reply">'
+ '<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">&nbsp;</div>'
+ '<br /><button id="replybutton" onclick="send_comment(\'' + commentid + '\');return false;">Post comment</button>'
+ '<div id="replystatus"> </div>'
+ '</div>');
$('input#nickname').focus(function() {
if ($(this).val() == $(this).attr('title')) {
@ -281,46 +281,46 @@ function send_data() {
* Put the screen in "New paste" mode.
*/
function stateNewPaste() {
$('button#sendbutton').show();
$('button#clonebutton').hide();
$('div#expiration').show();
$('div#remainingtime').hide();
$('div#language').hide(); // $('#language').show();
$('input#password').hide(); //$('#password').show();
$('div#opendisc').show();
$('button#newbutton').show();
$('div#pastelink').hide();
$('button#sendbutton').removeClass('hidden');
$('button#clonebutton').addClass('hidden');
$('div#expiration').removeClass('hidden');
$('div#remainingtime').addClass('hidden');
$('div#language').addClass('hidden'); // $('#language').removeClass('hidden');
$('input#password').addClass('hidden'); //$('#password').removeClass('hidden');
$('div#opendisc').removeClass('hidden');
$('button#newbutton').removeClass('hidden');
$('div#pastelink').addClass('hidden');
$('textarea#message').text('');
$('textarea#message').show();
$('div#cleartext').hide();
$('div#message').focus();
$('div#discussion').hide();
$('div#prettymessage').hide();
$('textarea#message').removeClass('hidden');
$('div#cleartext').addClass('hidden');
$('textarea#message').focus();
$('div#discussion').addClass('hidden');
$('div#prettymessage').addClass('hidden');
}
/**
* Put the screen in "Existing paste" mode.
*/
function stateExistingPaste() {
$('button#sendbutton').hide();
$('button#sendbutton').addClass('hidden');
// No "clone" for IE<10.
if ($('div#oldienotice').is(":visible")) {
$('button#clonebutton').hide();
$('button#clonebutton').addClass('hidden');
}
else {
$('button#clonebutton').show();
$('button#clonebutton').removeClass('hidden');
}
$('div#expiration').hide();
$('div#language').hide();
$('input#password').hide();
$('div#opendisc').hide();
$('button#newbutton').show();
$('div#pastelink').hide();
$('textarea#message').hide();
$('div#cleartext').hide();
$('div#prettymessage').show();
$('div#expiration').addClass('hidden');
$('div#language').addClass('hidden');
$('input#password').addClass('hidden');
$('div#opendisc').addClass('hidden');
$('button#newbutton').removeClass('hidden');
$('div#pastelink').addClass('hidden');
$('textarea#message').addClass('hidden');
$('div#cleartext').addClass('hidden');
$('div#prettymessage').removeClass('hidden');
}
/**
@ -361,11 +361,11 @@ function showStatus(message, spin) {
$('div#replystatus').removeClass('errorMessage');
$('div#replystatus').text(message);
if (!message) {
$('div#status').html('&nbsp');
$('div#status').html(' ');
return;
}
if (message == '') {
$('div#status').html('&nbsp');
$('div#status').html(' ');
return;
}
$('div#status').removeClass('errorMessage');
@ -419,6 +419,9 @@ function pageKey() {
}
$(function() {
// hide "no javascript" message
$('#noscript').hide();
$('select#pasteExpiration').change(function() {
if ($(this).val() == 'burn') {
$('div#opendisc').addClass('buttondisabled');
@ -430,7 +433,6 @@ $(function() {
}
});
// Display an existing paste
if ($('div#cipherdata').text().length > 1) {
// Missing decryption key in URL ?

View file

@ -29,7 +29,10 @@ class auto
*/
public static function loader($class_name)
{
require_once PATH . 'lib/' . str_replace('_', '/', $class_name) . '.php';
$filename = PATH . 'lib/' . str_replace('_', '/', $class_name) . '.php';
if(is_readable($filename)) {
return include $filename;
}
return false;
}
}

View file

@ -42,12 +42,12 @@ class filter
*/
public static function size_humanreadable($size)
{
$i = 0;
$iec = array('B', 'kiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB');
while ( ( $size / 1024 ) > 1 ) {
$i = 0;
while ( ( $size / 1024 ) >= 1 ) {
$size = $size / 1024;
$i++;
}
return number_format($size, 2, ".", " ") . ' ' . $iec[$i];
return number_format($size, ($i ? 2 : 0), '.', ' ') . ' ' . $iec[$i];
}
}

View file

@ -39,18 +39,15 @@ class sjcl
// Make sure required fields are present and contain base64 data.
foreach($accepted_keys as $k)
{
if (!array_key_exists($k, $decoded)) return false;
if (is_null(base64_decode($decoded[$k], $strict=true))) return false;
if (!(
array_key_exists($k, $decoded) &&
base64_decode($decoded[$k], $strict=true)
)) return false;
}
// Make sure no additionnal keys were added.
if (
count(
array_intersect(
array_keys($decoded),
$accepted_keys
)
) != 3
count(array_keys($decoded)) != count($accepted_keys)
) return false;
// FIXME: Reject data if entropy is too low?

View file

@ -92,18 +92,19 @@ class trafficlimiter
}
require $file;
$now = time();
$tl = $GLOBALS['traffic_limiter'];
// purge file of expired IPs to keep it small
foreach($tl as $key => $time)
{
if ($time + 10 < time())
if ($time + self::$_limit < $now)
{
unset($tl[$key]);
}
}
if (array_key_exists($ip, $tl) && ($tl[$ip] + 10 >= time()))
if (array_key_exists($ip, $tl) && ($tl[$ip] + self::$_limit >= $now))
{
$result = false;
} else {

View file

@ -397,7 +397,21 @@ class zerobin
*/
private function _view()
{
header('Content-Type: text/html; charset=utf-8');
// set headers to disable caching and return valid XHTML, if supported
$content = (
array_key_exists('HTTP_ACCEPT', $_SERVER) &&
!empty($_SERVER['HTTP_ACCEPT']) &&
stristr($_SERVER['HTTP_ACCEPT'], 'application/xhtml+xml') !== false
) ? 'application/xhtml+xml' : 'text/html';
$time = gmdate('D, d M Y H:i:s \G\M\T');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Pragma: no-cache');
header('Expires: ' . $time);
header('Last-Modified: ' . $time);
header('Vary: Accept');
header('Content-Type: ' . $content . ';charset=UTF-8');
$page = new RainTPL;
// We escape it here because ENT_NOQUOTES can't be used in RainTPL templates.
$page->assign('CIPHERDATA', htmlspecialchars($this->_data, ENT_NOQUOTES));

View file

@ -51,7 +51,7 @@ abstract class zerobin_abstract
* @static
* @return zerobin_abstract
*/
abstract public static function getInstance($options);
public static function getInstance($options) {}
/**
* Create a paste.

View file

@ -37,13 +37,13 @@ class zerobin_data extends zerobin_abstract
if (
is_array($options) &&
array_key_exists('dir', $options)
) self::$_dir = $options['dir'] . '/';
) self::$_dir = $options['dir'] . DIRECTORY_SEPARATOR;
// if needed initialize the singleton
if(null === parent::$_instance) {
parent::$_instance = new self;
if(!(self::$_instance instanceof zerobin_data)) {
self::$_instance = new self;
self::_init();
}
return parent::$_instance;
return self::$_instance;
}
/**
@ -59,7 +59,7 @@ class zerobin_data extends zerobin_abstract
$storagedir = self::_dataid2path($pasteid);
if (is_file($storagedir . $pasteid)) return false;
if (!is_dir($storagedir)) mkdir($storagedir, 0705, true);
return file_put_contents($storagedir . $pasteid, json_encode($paste));
return (bool) file_put_contents($storagedir . $pasteid, json_encode($paste));
}
/**
@ -67,13 +67,11 @@ class zerobin_data extends zerobin_abstract
*
* @access public
* @param string $pasteid
* @return string
* @return stdClass|false
*/
public function read($pasteid)
{
if(!$this->exists($pasteid)) return json_decode(
'{"data":"","meta":{"burnafterreading":true,"postdate":0}}'
);
if(!$this->exists($pasteid)) return false;
return json_decode(
file_get_contents(self::_dataid2path($pasteid) . $pasteid)
);
@ -193,7 +191,7 @@ class zerobin_data extends zerobin_abstract
{
return is_file(
self::_dataid2discussionpath($pasteid) .
$pasteid . '.' . $dataid . '.' . $parentid
$pasteid . '.' . $commentid . '.' . $parentid
);
}

View file

@ -56,8 +56,8 @@ class zerobin_db extends zerobin_abstract
public static function getInstance($options = null)
{
// if needed initialize the singleton
if(null === self::$_instance) {
parent::$_instance = new self;
if(!(self::$_instance instanceof zerobin_db)) {
self::$_instance = new self;
}
if (is_array($options))
@ -175,6 +175,16 @@ class zerobin_db extends zerobin_abstract
*/
public function create($pasteid, $paste)
{
if (
array_key_exists($pasteid, self::$_cache)
) {
if(false !== self::$_cache[$pasteid]) {
return false;
} else {
unset(self::$_cache[$pasteid]);
}
}
if (
!array_key_exists('opendiscussion', $paste['meta'])
) $paste['meta']['opendiscussion'] = false;
@ -199,31 +209,36 @@ class zerobin_db extends zerobin_abstract
*
* @access public
* @param string $pasteid
* @return string
* @return stdClass|false
*/
public function read($pasteid)
{
if (
!array_key_exists($pasteid, self::$_cache)
) self::$_cache[$pasteid] = self::_select(
) {
self::$_cache[$pasteid] = false;
$paste = self::_select(
'SELECT * FROM ' . self::$_prefix . 'paste WHERE dataid = ?',
array($pasteid), true
);
if(false !== $paste) {
// create object
$paste = new stdClass;
$paste->data = self::$_cache[$pasteid]['data'];
$paste->meta = new stdClass;
$paste->meta->postdate = (int) self::$_cache[$pasteid]['postdate'];
$paste->meta->expire_date = (int) self::$_cache[$pasteid]['expiredate'];
self::$_cache[$pasteid] = new stdClass;
self::$_cache[$pasteid]->data = $paste['data'];
self::$_cache[$pasteid]->meta = new stdClass;
self::$_cache[$pasteid]->meta->postdate = (int) $paste['postdate'];
self::$_cache[$pasteid]->meta->expire_date = (int) $paste['expiredate'];
if (
self::$_cache[$pasteid]['opendiscussion']
) $paste->meta->opendiscussion = true;
$paste['opendiscussion']
) self::$_cache[$pasteid]->meta->opendiscussion = true;
if (
self::$_cache[$pasteid]['burnafterreading']
) $paste->meta->burnafterreading = true;
$paste['burnafterreading']
) self::$_cache[$pasteid]->meta->burnafterreading = true;
}
}
return $paste;
return self::$_cache[$pasteid];
}
/**
@ -243,6 +258,9 @@ class zerobin_db extends zerobin_abstract
'DELETE FROM ' . self::$_prefix . 'comment WHERE pasteid = ?',
array($pasteid)
);
if (
array_key_exists($pasteid, self::$_cache)
) unset(self::$_cache[$pasteid]);
}
/**
@ -256,10 +274,7 @@ class zerobin_db extends zerobin_abstract
{
if (
!array_key_exists($pasteid, self::$_cache)
) self::$_cache[$pasteid] = self::_select(
'SELECT * FROM ' . self::$_prefix . 'paste WHERE dataid = ?',
array($pasteid), true
);
) self::$_cache[$pasteid] = $this->read($pasteid);
return (bool) self::$_cache[$pasteid];
}
@ -278,9 +293,9 @@ class zerobin_db extends zerobin_abstract
return self::_exec(
'INSERT INTO ' . self::$_prefix . 'comment VALUES(?,?,?,?,?,?,?)',
array(
$commentid,
$pasteid,
$parentid,
$commentid,
$comment['data'],
$comment['meta']['nickname'],
$comment['meta']['vizhash'],

View file

@ -1,5 +1,8 @@
<html>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta charset="utf-8" />
<title>ZeroBin</title>
<link type="text/css" rel="stylesheet" href="css/zerobin.css?{$VERSION|rawurlencode}#" />
<link type="text/css" rel="stylesheet" href="css/prettify.css?{$VERSION|rawurlencode}#" />
@ -10,42 +13,43 @@
<script src="js/rawinflate.js#"></script>
<script src="js/prettify.js#"></script>
<script src="js/zerobin.js?{$VERSION|rawurlencode}#"></script>
<!--[if lt IE 10]>
<style> body {padding-left:60px;padding-right:60px;} div#ienotice {display:block;} </style>
<![endif]-->
<!--[if lt IE 10]>
<style> div#ienotice {display:block; } div#oldienotice {display:block; } </style>
<![endif]-->
</head>
<body>
<header>
<div id="aboutbox">
ZeroBin is a minimalist, opensource online pastebin where the server has zero knowledge of pasted data.
Data is encrypted/decrypted <i>in the browser</i> using 256 bits AES.
More information on the <a href="http://sebsauvage.net/wiki/doku.php?id=php:zerobin">project page</a>.<br />
<span style="text-decoration:blink;font-size:10pt;color:#a4b3c4;">&#9654;</span> Note: This is a test service:
<span class="blink"></span> Note: This is a test service:
Data may be deleted anytime. Kittens will die if you abuse this service.
</div>
<h1 title="ZeroBin" onclick="window.location.href=scriptLocation();return false;">ZeroBin</h1><br>
<h2>Because ignorance is bliss</h2><br>
<h1 title="ZeroBin" onclick="window.location.href=scriptLocation();return false;">ZeroBin</h1><br />
<h2>Because ignorance is bliss</h2><br />
<h3>{$VERSION}</h3>
<noscript><div class="nonworking">Javascript is required for ZeroBin to work.<br>Sorry for the inconvenience.</div></noscript>
<div id="noscript" class="nonworking">Javascript is required for ZeroBin to work.<br />Sorry for the inconvenience.</div>
<div id="oldienotice" class="nonworking">ZeroBin requires a modern browser to work.</div>
<div id="ienotice">Still using Internet Explorer ? &nbsp;Do yourself a favor, switch to a modern browser:
<div id="ienotice">Still using Internet Explorer ? Do yourself a favor, switch to a modern browser:
<a href="http://www.mozilla.org/firefox/">Firefox</a>,
<a href="http://www.opera.com/">Opera</a>,
<a href="http://www.google.com/chrome">Chrome</a>,
<a href="http://www.apple.com/safari">Safari</a>...
</div>
<div id="status">&nbsp;</div>
<div id="errormessage" style="display:none">{$ERRORMESSAGE|htmlspecialchars}</div>
</header>
<section>
<article>
<div id="status"> </div>
<div id="errormessage" class="hidden">{$ERRORMESSAGE|htmlspecialchars}</div>
<div id="toolbar">
<button id="newbutton" onclick="window.location.href=scriptLocation();return false;" style="display:none;"><img src="img/icon_new.png#" width="11" height="15" />New</button>
<button id="sendbutton" onclick="send_data();return false;" style="display:none;"><img src="img/icon_send.png#" width="18" height="15" />Send</button>
<button id="clonebutton" onclick="clonePaste();return false;" style="display:none;"><img src="img/icon_clone.png#" width="15" height="17" />Clone</button>
<div id="expiration" style="display:none;">Expire:
<button id="newbutton" onclick="window.location.href=scriptLocation();return false;" class="hidden"><img src="img/icon_new.png#" width="11" height="15" />New</button>
<button id="sendbutton" onclick="send_data();return false;" class="hidden"><img src="img/icon_send.png#" width="18" height="15" />Send</button>
<button id="clonebutton" onclick="clonePaste();return false;" class="hidden"><img src="img/icon_clone.png#" width="15" height="17" />Clone</button>
<div id="expiration" class="hidden">Expire:
<select id="pasteExpiration" name="pasteExpiration">
<option value="burn">Burn after reading</option>
<option value="5min">5 minutes</option>
@ -58,8 +62,8 @@
<option value="never">Never</option>
</select>
</div>
<div id="remainingtime" style="display:none;"></div>
<div id="language" style="display:none;">
<div id="remainingtime" class="hidden"></div>
<div id="language" class="hidden">
<select name="language">
<option value="language" selected="selected">Language</option>
<option value="C/C++">C/C++</option>
@ -67,22 +71,26 @@
<option value="python">Python</option>
</select>
</div>
<input id="password" value="Optional password..." style="display:none;" />
<div id="opendisc" class="button" style="display:none;">
<input id="password" value="Optional password..." class="hidden" />
<div id="opendisc" class="button" class="hidden">
<input type="checkbox" id="opendiscussion" name="opendiscussion" {if="!$OPENDISCUSSION"} disabled="disabled"{/if} />
<label for="opendiscussion">Open discussion</label>
</div>
</div>
<div id="pastelink" style="display:none;"></div>
<div id="prettymessage" style="display:none;">
<pre id="prettyprint" class="prettyprint linenums:1"></pre>
<div id="pastelink" class="hidden"></div>
<div id="prettymessage" class="hidden">
<div id="prettyprint" class="prettyprint linenums:1"></div>
</div>
<div id="cleartext" style="display:none;"></div>
<textarea id="message" name="message" cols="80" rows="25" style="display:none;"></textarea>
<div id="discussion" style="display:none;">
<div id="cleartext" class="hidden"></div>
<textarea id="message" name="message" cols="80" rows="25" class="hidden"></textarea>
</article>
</section>
<section>
<div id="discussion" class="hidden">
<h4>Discussion</h4>
<div id="comments"></div>
</div>
<div id="cipherdata" style="display:none;">{$CIPHERDATA}</div>
</section>
<div id="cipherdata" class="hidden">{$CIPHERDATA}</div>
</body>
</html>

16
tst/README.md Normal file
View file

@ -0,0 +1,16 @@
Running unit tests
==================
In order to run these tests, you will need to install the following packages
and its dependencies:
* phpunit
* php5-gd
* php5-sqlite
* php5-xdebug
Example for Debian and Ubuntu:
$ sudo aptitude install phpunit php5-mysql php5-xdebug
To run the tests, just change into this directory and run phpunit:
$ cd ZeroBin/tst
$ phpunit

77
tst/RainTPL.php Normal file
View file

@ -0,0 +1,77 @@
<?php
class RainTPLTest extends PHPUnit_Framework_TestCase
{
private static $data = '{"iv":"EN39/wd5Nk8HAiSG2K5AsQ","salt":"QKN1DBXe5PI","ct":"8hA83xDdXjD7K2qfmw5NdA"}';
private static $error = 'foo bar';
private static $version = 'Version 1.2.3';
private $_content;
public function setUp()
{
/* Setup Routine */
$page = new RainTPL;
$page::configure(array('cache_dir' => 'tmp/'));
// We escape it here because ENT_NOQUOTES can't be used in RainTPL templates.
$page->assign('CIPHERDATA', htmlspecialchars(self::$data, ENT_NOQUOTES));
$page->assign('ERRORMESSAGE', self::$error);
$page->assign('OPENDISCUSSION', false);
$page->assign('VERSION', self::$version);
ob_start();
$page->draw('page');
$this->_content = ob_get_contents();
// run a second time from cache
$page->cache('page');
$page->draw('page');
ob_end_clean();
}
public function tearDown()
{
/* Tear Down Routine */
helper::rmdir(PATH . 'tmp');
}
public function testTemplateRendersCorrectly()
{
$this->assertTag(
array(
'id' => 'cipherdata',
'content' => htmlspecialchars(self::$data, ENT_NOQUOTES)
),
$this->_content,
'outputs data correctly'
);
$this->assertTag(
array(
'id' => 'errormessage',
'content' => self::$error
),
$this->_content,
'outputs error correctly'
);
$this->assertTag(
array(
'id' => 'opendiscussion',
'attributes' => array(
'disabled' => 'disabled'
),
),
$this->_content,
'disables discussions if configured'
);
// testing version number in JS address, since other instances may not be present in different templates
$this->assertTag(
array(
'tag' => 'script',
'attributes' => array(
'src' => 'js/zerobin.js?' . rawurlencode(self::$version)
),
),
$this->_content,
'outputs version correctly'
);
}
}

8
tst/auto.php Normal file
View file

@ -0,0 +1,8 @@
<?php
class autoTest extends PHPUnit_Framework_TestCase
{
public function testAutoloaderReturnsFalseWhenCallingNonExistingClass()
{
$this->assertFalse(auto::loader('foo2501bar42'), 'calling non existent class');
}
}

31
tst/bootstrap.php Normal file
View file

@ -0,0 +1,31 @@
<?php
error_reporting( E_ALL | E_STRICT );
// change this, if your php files and data is outside of your webservers document root
define('PATH', '..' . DIRECTORY_SEPARATOR);
require PATH . 'lib/auto.php';
class helper
{
public static function rmdir($path)
{
$path .= DIRECTORY_SEPARATOR;
$dir = dir($path);
while(false !== ($file = $dir->read())) {
if($file != '.' && $file != '..') {
if(is_dir($path . $file)) {
self::rmdir($path . $file);
} elseif(is_file($path . $file)) {
if(!@unlink($path . $file)) {
throw new Exception('Error deleting file "' . $path . $file . '".');
}
}
}
}
$dir->close();
if(!@rmdir($path)) {
throw new Exception('Error deleting directory "' . $path . '".');
}
}
}

47
tst/filter.php Normal file
View file

@ -0,0 +1,47 @@
<?php
class filterTest extends PHPUnit_Framework_TestCase
{
public function testFilterStripsSlashesDeeply()
{
$this->assertEquals(
array("f'oo", "b'ar", array("fo'o", "b'ar")),
filter::stripslashes_deep(array("f\\'oo", "b\\'ar", array("fo\\'o", "b\\'ar")))
);
}
public function testFilterMakesSizesHumanlyReadable()
{
$this->assertEquals('1 B', filter::size_humanreadable(1));
$this->assertEquals('1 000 B', filter::size_humanreadable(1000));
$this->assertEquals('1.00 kiB', filter::size_humanreadable(1024));
$this->assertEquals('1.21 kiB', filter::size_humanreadable(1234));
$exponent = 1024;
$this->assertEquals('1 000.00 kiB', filter::size_humanreadable(1000 * $exponent));
$this->assertEquals('1.00 MiB', filter::size_humanreadable(1024 * $exponent));
$this->assertEquals('1.21 MiB', filter::size_humanreadable(1234 * $exponent));
$exponent *= 1024;
$this->assertEquals('1 000.00 MiB', filter::size_humanreadable(1000 * $exponent));
$this->assertEquals('1.00 GiB', filter::size_humanreadable(1024 * $exponent));
$this->assertEquals('1.21 GiB', filter::size_humanreadable(1234 * $exponent));
$exponent *= 1024;
$this->assertEquals('1 000.00 GiB', filter::size_humanreadable(1000 * $exponent));
$this->assertEquals('1.00 TiB', filter::size_humanreadable(1024 * $exponent));
$this->assertEquals('1.21 TiB', filter::size_humanreadable(1234 * $exponent));
$exponent *= 1024;
$this->assertEquals('1 000.00 TiB', filter::size_humanreadable(1000 * $exponent));
$this->assertEquals('1.00 PiB', filter::size_humanreadable(1024 * $exponent));
$this->assertEquals('1.21 PiB', filter::size_humanreadable(1234 * $exponent));
$exponent *= 1024;
$this->assertEquals('1 000.00 PiB', filter::size_humanreadable(1000 * $exponent));
$this->assertEquals('1.00 EiB', filter::size_humanreadable(1024 * $exponent));
$this->assertEquals('1.21 EiB', filter::size_humanreadable(1234 * $exponent));
$exponent *= 1024;
$this->assertEquals('1 000.00 EiB', filter::size_humanreadable(1000 * $exponent));
$this->assertEquals('1.00 ZiB', filter::size_humanreadable(1024 * $exponent));
$this->assertEquals('1.21 ZiB', filter::size_humanreadable(1234 * $exponent));
$exponent *= 1024;
$this->assertEquals('1 000.00 ZiB', filter::size_humanreadable(1000 * $exponent));
$this->assertEquals('1.00 YiB', filter::size_humanreadable(1024 * $exponent));
$this->assertEquals('1.21 YiB', filter::size_humanreadable(1234 * $exponent));
}
}

17
tst/phpunit.xml Normal file
View file

@ -0,0 +1,17 @@
<phpunit bootstrap="bootstrap.php" colors="true">
<testsuite name="ZeroBin Test Suite">
<directory suffix=".php">./</directory>
</testsuite>
<filter>
<whitelist>
<directory suffix=".php">../lib</directory>
<exclude>
<file>../lib/zerobin/abstract.php</file>
</exclude>
</whitelist>
</filter>
<logging>
<log type="coverage-html" target="log/coverage-report" charset="UTF-8" yui="true" highlight="true" lowUpperBound="50" highLowerBound="80" />
<log type="testdox-html" target="log/testdox.html" />
</logging>
</phpunit>

14
tst/sjcl.php Normal file
View file

@ -0,0 +1,14 @@
<?php
class sjclTest extends PHPUnit_Framework_TestCase
{
public function testSjclValidatorValidatesCorrectly()
{
$this->assertTrue(sjcl::isValid('{"iv":"83Ax/OdUav3SanDW9dcQPg","salt":"Gx1vA2/gQ3U","ct":"j7ImByuE5xCqD2YXm6aSyA"}'), 'valid sjcl');
$this->assertFalse(sjcl::isValid('{"iv":"$","salt":"Gx1vA2/gQ3U","ct":"j7ImByuE5xCqD2YXm6aSyA"}'), 'invalid base64 encoding of iv');
$this->assertFalse(sjcl::isValid('{"iv":"83Ax/OdUav3SanDW9dcQPg","salt":"$","ct":"j7ImByuE5xCqD2YXm6aSyA"}'), 'invalid base64 encoding of salt');
$this->assertFalse(sjcl::isValid('{"iv":"83Ax/OdUav3SanDW9dcQPg","salt":"Gx1vA2/gQ3U","ct":"$"}'), 'invalid base64 encoding of ct');
$this->assertFalse(sjcl::isValid('{"iv":"MTIzNDU2Nzg5MDEyMzQ1Njc4OTA=","salt":"Gx1vA2/gQ3U","ct":"j7ImByuE5xCqD2YXm6aSyA"}'), 'iv to long');
$this->assertFalse(sjcl::isValid('{"iv":"83Ax/OdUav3SanDW9dcQPg","salt":"MTIzNDU2Nzg5MDEyMzQ1Njc4OTA=","ct":"j7ImByuE5xCqD2YXm6aSyA"}'), 'salt to long');
$this->assertFalse(sjcl::isValid('{"iv":"83Ax/OdUav3SanDW9dcQPg","salt":"Gx1vA2/gQ3U","ct":"j7ImByuE5xCqD2YXm6aSyA","foo":"MTIzNDU2Nzg5MDEyMzQ1Njc4OTA="}'), 'invalid additional key');
}
}

30
tst/trafficlimiter.php Normal file
View file

@ -0,0 +1,30 @@
<?php
class trafficlimiterTest extends PHPUnit_Framework_TestCase
{
private $_path;
public function setUp()
{
/* Setup Routine */
$this->_path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'trafficlimit' . DIRECTORY_SEPARATOR;
trafficlimiter::setPath($this->_path);
}
public function tearDown()
{
/* Tear Down Routine */
helper::rmdir($this->_path);
}
public function testTrafficGetsLimited()
{
trafficlimiter::setLimit(4);
$this->assertTrue(trafficlimiter::canPass('127.0.0.1'), 'first request may pass');
sleep(2);
$this->assertFalse(trafficlimiter::canPass('127.0.0.1'), 'second request is to fast, may not pass');
sleep(3);
$this->assertTrue(trafficlimiter::canPass('127.0.0.1'), 'third request waited long enough and may pass');
$this->assertTrue(trafficlimiter::canPass('2001:1620:2057:dead:beef::cafe:babe'), 'fourth request has different ip and may pass');
$this->assertFalse(trafficlimiter::canPass('127.0.0.1'), 'fifth request is to fast, may not pass');
}
}

41
tst/vizhash16x16.php Normal file
View file

@ -0,0 +1,41 @@
<?php
class vizhash16x16Test extends PHPUnit_Framework_TestCase
{
private $_dataDirCreated;
private $_file;
private $_path;
public function setUp()
{
/* Setup Routine */
$this->_path = PATH . 'data' . DIRECTORY_SEPARATOR;
$this->_dataDirCreated = !is_dir($this->_path);
if($this->_dataDirCreated) mkdir($this->_path);
$this->_file = $this->_path . 'vizhash.png';
}
public function tearDown()
{
/* Tear Down Routine */
if($this->_dataDirCreated) {
helper::rmdir($this->_path);
} else {
if(!@unlink($this->_file)) {
throw new Exception('Error deleting file "' . $this->_file . '".');
}
}
}
public function testVizhashGeneratesUniquePngsPerIp()
{
$vz = new vizhash16x16();
$pngdata = $vz->generate('127.0.0.1');
file_put_contents($this->_file, $pngdata);
$finfo = new finfo(FILEINFO_MIME_TYPE);
$this->assertEquals('image/png', $finfo->file($this->_file));
$this->assertNotEquals($pngdata, $vz->generate('2001:1620:2057:dead:beef::cafe:babe'));
$this->assertEquals($pngdata, $vz->generate('127.0.0.1'));
}
}

70
tst/zerobin/data.php Normal file
View file

@ -0,0 +1,70 @@
<?php
class zerobin_dataTest extends PHPUnit_Framework_TestCase
{
private static $pasteid = '501f02e9eeb8bcec';
private static $paste = array(
'data' => '{"iv":"EN39/wd5Nk8HAiSG2K5AsQ","salt":"QKN1DBXe5PI","ct":"8hA83xDdXjD7K2qfmw5NdA"}',
'meta' => array(
'postdate' => 1344803344,
'expire_date' => 1344803644,
'opendiscussion' => true,
),
);
private static $commentid = 'c47efb4741195f42';
private static $comment = array(
'data' => '{"iv":"Pd4pOKWkmDTT9uPwVwd5Ag","salt":"ZIUhFTliVz4","ct":"6nOCU3peNDclDDpFtJEBKA"}',
'meta' => array(
'nickname' => '{"iv":"76MkAtOGC4oFogX/aSMxRA","salt":"ZIUhFTliVz4","ct":"b6Ae/U1xJdsX/+lATud4sQ"}',
'vizhash' => 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABGUlEQVQokWOsl5/94983CNKQMjnxaOePf98MeKwPfNjkLZ3AgARab6b9+PeNEVnDj3/ff/z7ZiHnzsDA8Pv7H2TVPJw8EAYLAwb48OaVgIgYKycLsrYv378wMDB8//qdCVMDRA9EKSsnCwRBxNsepaLboMFlyMDAICAi9uHNK24GITQ/MDAwoNhgIGMLtwGrzegaLjw5jMz9+vUdnN17uwDCQDhJgk0O07yvX9+teDX1x79v6DYIsIjgcgMaYGFgYOBg4kJx2JejkAiBxAw+PzAwMNz4dp6wDXDw4MdNNOl0rWYsNkD89OLXI/xmo9sgzatJjAYmBgYGDiauD3/ePP18nVgb4MF89+M5ZX6js293wUMpnr8KTQMAxsCJnJ30apMAAAAASUVORK5CYII=',
'postdate' => 1344803528,
),
);
private $_model;
private $_path;
public function setUp()
{
/* Setup Routine */
$this->_path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'zerobin_data';
$this->_model = zerobin_data::getInstance(array('dir' => $this->_path));
}
public function tearDown()
{
/* Tear Down Routine */
helper::rmdir($this->_path);
}
public function testFileBasedDataStoreWorks()
{
// storing pastes
$this->assertFalse($this->_model->exists(self::$pasteid), 'paste does not yet exist');
$this->assertTrue($this->_model->create(self::$pasteid, self::$paste), 'store new paste');
$this->assertTrue($this->_model->exists(self::$pasteid), 'paste exists after storing it');
$this->assertFalse($this->_model->create(self::$pasteid, self::$paste), 'unable to store the same paste twice');
$this->assertEquals(json_decode(json_encode(self::$paste)), $this->_model->read(self::$pasteid));
// storing comments
$this->assertFalse($this->_model->existsComment(self::$pasteid, self::$pasteid, self::$commentid), 'comment does not yet exist');
$this->assertTrue($this->_model->createComment(self::$pasteid, self::$pasteid, self::$commentid, self::$comment) !== false, 'store comment');
$this->assertTrue($this->_model->existsComment(self::$pasteid, self::$pasteid, self::$commentid), 'comment exists after storing it');
$comment = json_decode(json_encode(self::$comment));
$comment->meta->commentid = self::$commentid;
$comment->meta->parentid = self::$pasteid;
$this->assertEquals(
array($comment->meta->postdate => $comment),
$this->_model->readComments(self::$pasteid)
);
// deleting pastes
$this->_model->delete(self::$pasteid);
$this->assertFalse($this->_model->exists(self::$pasteid), 'paste successfully deleted');
$this->assertFalse($this->_model->existsComment(self::$pasteid, self::$pasteid, self::$commentid), 'comment was deleted with paste');
$this->assertFalse($this->_model->read(self::$pasteid), 'paste can no longer be found');
}
}

68
tst/zerobin/db.php Normal file
View file

@ -0,0 +1,68 @@
<?php
class zerobin_dbTest extends PHPUnit_Framework_TestCase
{
private static $pasteid = '501f02e9eeb8bcec';
private static $paste = array(
'data' => '{"iv":"EN39/wd5Nk8HAiSG2K5AsQ","salt":"QKN1DBXe5PI","ct":"8hA83xDdXjD7K2qfmw5NdA"}',
'meta' => array(
'postdate' => 1344803344,
'expire_date' => 1344803644,
'opendiscussion' => true,
),
);
private static $commentid = 'c47efb4741195f42';
private static $comment = array(
'data' => '{"iv":"Pd4pOKWkmDTT9uPwVwd5Ag","salt":"ZIUhFTliVz4","ct":"6nOCU3peNDclDDpFtJEBKA"}',
'meta' => array(
'nickname' => '{"iv":"76MkAtOGC4oFogX/aSMxRA","salt":"ZIUhFTliVz4","ct":"b6Ae/U1xJdsX/+lATud4sQ"}',
'vizhash' => 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABGUlEQVQokWOsl5/94983CNKQMjnxaOePf98MeKwPfNjkLZ3AgARab6b9+PeNEVnDj3/ff/z7ZiHnzsDA8Pv7H2TVPJw8EAYLAwb48OaVgIgYKycLsrYv378wMDB8//qdCVMDRA9EKSsnCwRBxNsepaLboMFlyMDAICAi9uHNK24GITQ/MDAwoNhgIGMLtwGrzegaLjw5jMz9+vUdnN17uwDCQDhJgk0O07yvX9+teDX1x79v6DYIsIjgcgMaYGFgYOBg4kJx2JejkAiBxAw+PzAwMNz4dp6wDXDw4MdNNOl0rWYsNkD89OLXI/xmo9sgzatJjAYmBgYGDiauD3/ePP18nVgb4MF89+M5ZX6js293wUMpnr8KTQMAxsCJnJ30apMAAAAASUVORK5CYII=',
'postdate' => 1344803528,
),
);
private $_model;
public function setUp()
{
/* Setup Routine */
$this->_model = zerobin_db::getInstance(
array(
'dsn' => 'sqlite::memory:',
'usr' => null,
'pwd' => null,
'opt' => array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION),
)
);
}
public function testDatabaseBasedDataStoreWorks()
{
// storing pastes
$this->assertFalse($this->_model->exists(self::$pasteid), 'paste does not yet exist');
$this->assertTrue($this->_model->create(self::$pasteid, self::$paste), 'store new paste');
$this->assertTrue($this->_model->exists(self::$pasteid), 'paste exists after storing it');
$this->assertFalse($this->_model->create(self::$pasteid, self::$paste), 'unable to store the same paste twice');
$this->assertEquals(json_decode(json_encode(self::$paste)), $this->_model->read(self::$pasteid));
// storing comments
$this->assertFalse($this->_model->existsComment(self::$pasteid, self::$pasteid, self::$commentid), 'comment does not yet exist');
$this->assertTrue($this->_model->createComment(self::$pasteid, self::$pasteid, self::$commentid, self::$comment) !== false, 'store comment');
$this->assertTrue($this->_model->existsComment(self::$pasteid, self::$pasteid, self::$commentid), 'comment exists after storing it');
$comment = json_decode(json_encode(self::$comment));
$comment->meta->commentid = self::$commentid;
$comment->meta->parentid = self::$pasteid;
$this->assertEquals(
array($comment->meta->postdate => $comment),
$this->_model->readComments(self::$pasteid)
);
// deleting pastes
$this->_model->delete(self::$pasteid);
$this->assertFalse($this->_model->exists(self::$pasteid), 'paste successfully deleted');
$this->assertFalse($this->_model->existsComment(self::$pasteid, self::$pasteid, self::$commentid), 'comment was deleted with paste');
$this->assertFalse($this->_model->read(self::$pasteid), 'paste can no longer be found');
}
}

@ -1 +0,0 @@
Subproject commit ef4d776aedee6cbc8959a8e76403b82523615d3a