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/ # Ignore data/ and tmp/
data/ data/
tmp/ tmp/
tst/log/
.settings/ .settings/
.buildpath .buildpath
.project .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 For Administrators
------------------ ------------------
In the index.php in the main folder you can define a different PATH. This is 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 useful if you want to secure your installation and want to move the
configuration, data files, templates and PHP libraries (directories cfg, lib configuration, data files, templates and PHP libraries (directories cfg, data,
and tpl) outside of your document root. This new location must still be lib and tpl) outside of your document root. This new location must still be
accessible to your webserver / PHP process. accessible to your webserver / PHP process.
> ### PATH Example ### > ### 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 divided into multiple sections, which are enclosed in square brackets. In the
"[main]" section you can enable or disable the discussion feature, set 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 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. your ZeroBin.
Finally the "[model]" and "[model_options]" sections let you configure your 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 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, 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 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, 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 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 want to provide a table prefix, if you have to share the zerobin database with
another application. The table prefix option is called "tbl". another application. The table prefix option is called "tbl".
> ### Note ### > ### Note ###
> The "zerobin_db" model has only been tested with sqlite and MySQL, although > 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 > 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. > 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: 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 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) 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 $parentid // the id of the parent of this comment, may be the paste id itself
$commentid = substr(hash('md5', $paste['data']), 0, 16); $commentid = substr(hash('md5', $paste['data']), 0, 16);
$paste['data'] // text $comment['data'] // text
$paste['meta']['nickname'] // text or null (if anonymous) $comment['meta']['nickname'] // text or null (if anonymous)
$paste['meta']['vizhash'] // text or null (if anonymous) $comment['meta']['vizhash'] // text or null (if anonymous)
$paste['meta']['postdate'] // int UNIX timestamp $comment['meta']['postdate'] // int UNIX timestamp
} }

View file

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

View file

@ -32,7 +32,13 @@
} }
/* Put a border around prettyprinted code snippets. */ /* 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 */ /* Specify class=linenums on a pre to get line numbering */
ol.linenums { ol.linenums {

View file

@ -4,32 +4,33 @@
/* CSS Reset from YUI 3.4.1 (build 4118) - Copyright 2011 Yahoo! Inc. All rights reserved. /* 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/ */ 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 { html {
background-color: #455463; background-color: #455463;
color:white; color: #fff;
min-height: 100%; min-height: 100%;
background-image: linear-gradient(bottom, #0F1823 0%, #455463 100%); background-image: linear-gradient(bottom, #0f1823 0%, #455463 100%);
background-image: -o-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: -moz-linear-gradient(bottom, #0f1823 0%, #455463 100%);
background-image: -webkit-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: -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: -webkit-gradient(linear, left bottom, left top, color-stop(0, #0f1823), color-stop(1, #455463));
} }
body { body {
font-family: Arial, Helvetica, sans-serif; font-family: Arial, Helvetica, sans-serif;
font-size: 0.8em; font-size: 0.8em;
margin-bottom: 15px; margin-bottom: 15px;
padding-left:60px; padding-right:60px; padding-left: 60px;
padding-right: 60px;
} }
a { color:#0F388F; } a { color: #0f388f; }
h1 { h1 {
font-size: 3.5em; font-size: 3.5em;
font-weight:700; font-weight: bold;
color: #000; color: #000;
position: relative; position: relative;
display: inline; display: inline;
@ -52,14 +53,17 @@ display:inline;
font-style: italic; font-style: italic;
font-weight: bold; font-weight: bold;
position: relative; position: relative;
bottom:8px;} bottom: 8px;
}
h3 { h3 {
color: #94a3b4; color: #94a3b4;
font-size: 0.7em; font-size: 0.7em;
display: inline; display: inline;
margin-top: 10px;
position: relative; position: relative;
bottom:8px;} bottom: 8px;
}
#aboutbox { #aboutbox {
font-size: 0.85em; font-size: 0.85em;
@ -72,11 +76,11 @@ float:right;
width: 60%; width: 60%;
} }
div#aboutbox a { color: #94a3b4; } #aboutbox a { color: #94a3b4; }
textarea#message,div#cleartext,.replymessage { #message, #cleartext, .replymessage {
clear: both; clear: both;
color:black; color: #000;
background-color: #fff; background-color: #fff;
white-space: pre-wrap; white-space: pre-wrap;
font-family: Consolas, "Lucida Console", "DejaVu Sans Mono", Monaco, monospace; font-family: Consolas, "Lucida Console", "DejaVu Sans Mono", Monaco, monospace;
@ -91,113 +95,139 @@ box-sizing:border-box;
width: 100%; width: 100%;
} }
div#status { #status {
clear: both; clear: both;
padding: 5px 10px; padding: 5px 10px;
} }
div#pastelink { #pastelink {
background-color: #1F2833; background-color: #1F2833;
color:white; color: #fff;
padding: 4px 12px; padding: 4px 12px;
clear: both; clear: both;
-moz-box-shadow: inset 0px 2px 2px #000; -moz-box-shadow: inset 0 2px 2px #000;
-webkit-box-shadow: inset 0px 2px 2px #000; -webkit-box-shadow: inset 0 2px 2px #000;
box-shadow: inset 0px 2px 5px #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; color: #fff;
background-color:#323B47; background-color: #323b47;
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: center left; background-position: center left;
padding: 4px 8px; padding: 4px 8px;
font-size: 1em; font-size: 1em;
margin-right: 5px; margin-right: 5px;
display: inline; display: inline;
background-image: linear-gradient(bottom, #323B47 0%, #51606E 100%); background-image: linear-gradient(bottom, #323b47 0, #51606e 100%);
background-image: -o-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: -moz-linear-gradient(bottom, #323b47 0, #51606e 100%);
background-image: -webkit-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: -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: -webkit-gradient(linear, left bottom, left top, color-stop(0, #323b47), color-stop(1, #51606e));
border: 1px solid #28343F; border: 1px solid #28343F;
-moz-box-shadow: inset 0px 1px 2px #647384; -moz-box-shadow: inset 0 1px 2px #647384;
-webkit-box-shadow: inset 0px 1px 2px #647384; -webkit-box-shadow: inset 0 1px 2px #647384;
box-shadow: inset 0px 1px 2px #647384; box-shadow: inset 0 1px 2px #647384;
-webkit-border-radius: 3px; -webkit-border-radius: 3px;
-moz-border-radius: 3px; -moz-border-radius: 3px;
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 { button:hover {
background-image: linear-gradient(bottom, #424B57 0%, #61707E 100%); background-image: linear-gradient(bottom, #424b57 0%, #61707e 100%);
background-image: -o-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: -moz-linear-gradient(bottom, #424b57 0%, #61707e 100%);
background-image: -webkit-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: -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: -webkit-gradient(linear, left bottom, left top, color-stop(0, #424b57), color-stop(1, #61707e));
} }
button:active { button:active {
background-image: linear-gradient(bottom, #51606E 0%, #323B47 100%); background-image: linear-gradient(bottom, #51606e 0, #323b47 100%);
background-image: -o-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: -moz-linear-gradient(bottom, #51606e 0, #323b47 100%);
background-image: -webkit-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: -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: -webkit-gradient(linear, left bottom, left top, color-stop(0, #51606e), color-stop(1, #323b47));
position:relative; position:relative;
top:1px; top:1px;
} }
button:disabled, .buttondisabled { button:disabled, .buttondisabled {
background: #ccc; background: #ccc;
color: #888; color: #888;
top:0px; top: 0;
} }
button img { button img {
margin-right: 8px; margin-right: 8px;
position: relative; position: relative;
top: 2px; top: 2px;
} }
div#expiration, div#language, div#opendisc { #expiration, #language, #opendisc {
background-color:#414D5A; background-color: #414d5a;
padding: 6px 8px; padding: 6px 8px;
margin:0px 5px 0px 0px;; margin: 0 5px 0 0;
position: relative; position: relative;
bottom: 1px; /* WTF ? Why is this shifted by 1 pixel ? */ bottom: 1px; /* WTF ? Why is this shifted by 1 pixel ? */
} }
div#expiration select, div#language select {
#expiration select, #language select {
color: #eee; color: #eee;
background: transparent; background: transparent;
border: none; border: none;
} }
div#expiration select option, div#language select option { #expiration select option, #language select option {
color:#eee; color:#eee;
background: #414D5A; background: #414d5a;
background-color:#414D5A;
} }
div#remainingtime { #remainingtime {
color: #94a3b4; color: #94a3b4;
display: inline; display: inline;
font-size: 0.85em; 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 { .foryoureyesonly {
color: yellow !important; color: #ff0 !important;
font-size: 1em !important; font-size: 1em !important;
font-weight: bold !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 { .nonworking {
background-color: #fff; background-color: #fff;
color: #000; color: #000;
@ -211,8 +241,10 @@ border-radius:4px;
padding: 5px; padding: 5px;
} }
div#ienotice { .hidden { display: none !important; }
background-color:#7E98AF;
#ienotice {
background-color: #7e98af;
color: #000; color: #000;
font-size: 0.85em; font-size: 0.85em;
padding: 3px 5px; padding: 3px 5px;
@ -223,24 +255,18 @@ border-radius:4px;
display: none; display: none;
} }
div#ienotice a { #ienotice a { color: #000; }
color:black;
}
div#oldienotice { #oldienotice { display: none; }
display:none;
}
.errorMessage { .errorMessage {
background-color:#FF7979 !important; background-color: #f77 !important;
color:#FF0; color:#ff0;
} }
/* --- discussion related CSS ------- */ /* --- discussion related CSS ------- */
#discussion { /* Discussion container */
div#discussion { /* Discussion container */
margin-top: 20px; margin-top: 20px;
width: 100%; width: 100%;
margin-left: -30px; margin-left: -30px;
@ -249,17 +275,16 @@ min-width:200px;
h4 { h4 {
font-size: 1.2em; font-size: 1.2em;
color: #94A3B4; color: #94a3b4;
font-style: italic; font-style: italic;
font-weight: bold; font-weight: bold;
position: relative; position: relative;
margin-left: 30px; margin-left: 30px;
} }
.comment /* One single reply */
div.comment /* One single reply */
{ {
background-color:#CECED6; background-color: #ceced6;
color: #000; color: #000;
white-space: pre-wrap; white-space: pre-wrap;
font-family: Consolas,"Lucida Console","DejaVu Sans Mono",Monaco,monospace; 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; min-width: 200px;
overflow: auto; overflow: auto;
} }
/* FIXME: Add min-width */
div.reply { .reply { margin: 5px 0 0 30px; }
margin: 5px 0px 0px 30px;
}
div#replystatus { #replystatus {
display: inline; display: inline;
padding: 1px 7px; padding: 1px 7px;
font-family: Arial, Helvetica, sans-serif; font-family: Arial, Helvetica, sans-serif;
} }
div.comment button { .comment button {
color: #446; color: #446;
background-color: #aab; background-color: #aab;
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: center left; background-position: center left;
padding:0px 2px; padding: 0 2px;
font-size: 0.73em; font-size: 0.73em;
margin: 3px 5px 3px 0px; margin: 3px 5px 3px 0;
display: inline; display: inline;
background-image: 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: -o-linear-gradient(bottom, #aab 0, #ccc 100%);
background-image: -moz-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: -webkit-linear-gradient(bottom, #aab 0, #ccc 100%);
background-image: -ms-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)); background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #aab), color-stop(1, #ccc));
border: 1px solid #ccd; border: 1px solid #ccd;
-moz-box-shadow: inset 0px 1px 2px #ddd; -moz-box-shadow: inset 0 1px 2px #ddd;
-webkit-box-shadow: inset 0px 1px 2px #fff; -webkit-box-shadow: inset 0 1px 2px #fff;
box-shadow: inset 0px 1px 2px #eee; box-shadow: inset 0 1px 2px #eee;
-webkit-border-radius: 3px; -webkit-border-radius: 3px;
-moz-border-radius: 3px; -moz-border-radius: 3px;
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%); .comment button:hover {
background-image: -o-linear-gradient(bottom, #ccd 0%, #fff 100%); background-image: linear-gradient(bottom, #ccd 0, #fff 100%);
background-image: -moz-linear-gradient(bottom, #ccd 0%, #fff 100%); background-image: -o-linear-gradient(bottom, #ccd 0, #fff 100%);
background-image: -webkit-linear-gradient(bottom, #ccd 0%, #fff 100%); background-image: -moz-linear-gradient(bottom, #ccd 0, #fff 100%);
background-image: -ms-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)); 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%); .comment button:active {
background-image: -o-linear-gradient(bottom, #fff 0%, #889 100%); background-image: linear-gradient(bottom, #fff 0, #889 100%);
background-image: -moz-linear-gradient(bottom, #fff 0%, #889 100%); background-image: -o-linear-gradient(bottom, #fff 0, #889 100%);
background-image: -webkit-linear-gradient(bottom, #fff 0%, #889 100%); background-image: -moz-linear-gradient(bottom, #fff 0, #889 100%);
background-image: -ms-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)); background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #fff), color-stop(1, #889));
position:relative; position:relative;
top:1px; top:1px;
} }
div.comment input { .comment input { padding: 2px; }
padding:2px;
}
textarea#replymessage { #replymessage { margin-top: 5px; }
margin-top:5px;
}
div.commentmeta { .commentmeta {
color: #fff; color: #fff;
background-color:#8EA0B2; background-color: #8ea0b2;
margin-bottom: 3px; margin-bottom: 3px;
padding:0px 0px 0px 3px; padding: 0 0 0 3px;
} }
span.commentdate { .commentdate { color: #bfcede; }
color: #BFCEDE;
}
img.vizhash { img.vizhash {
width: 16px; 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 prettyPrint(opt_whenDone) {
function byTagName(tn) { return document.getElementsByTagName(tn); } function byTagName(tn) { return document.getElementsByTagName(tn); }
// fetch a list of nodes to rewrite // 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 = []; var elements = [];
for (var i = 0; i < codeSegments.length; ++i) { for (var i = 0; i < codeSegments.length; ++i) {
for (var j = 0, n = codeSegments[i].length; j < n; ++j) { 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")) { if ($('div#oldienotice').is(":visible")) {
// IE<10 do not support white-space:pre-wrap; so we have to do this BIG UGLY STINKING THING. // 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.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: // for other (sane) browsers:
else { else {
@ -112,9 +112,9 @@ function displayMessages(key, comments) {
try { // Try to decrypt the paste. try { // Try to decrypt the paste.
var cleartext = zeroDecipher(key, comments[0].data); var cleartext = zeroDecipher(key, comments[0].data);
} catch(err) { } catch(err) {
$('div#cleartext').hide(); $('div#cleartext').addClass('hidden');
$('div#prettymessage').hide(); $('div#prettymessage').addClass('hidden');
$('button#clonebutton').hide(); $('button#clonebutton').addClass('hidden');
showError('Could not decrypt data (Wrong key ?)'); showError('Could not decrypt data (Wrong key ?)');
return; 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.expire_date) $('div#remainingtime').removeClass('foryoureyesonly').text('This document will expire in '+secondsToHuman(comments[0].meta.remaining_time)+'.').show();
if (comments[0].meta.burnafterreading) { 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(); $('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. // If the discussion is opened on this paste, display it.
@ -148,10 +148,10 @@ function displayMessages(key, comments) {
if ($(cname).length) { if ($(cname).length) {
place = $(cname); 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>' + '<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>' + '<button onclick="open_reply($(this),\'' + comment.meta.commentid + '\');return false;">Reply</button>'
+ '</div>'); + '</div></article>');
setElementText(divComment.find('div.commentdata'), cleartext); setElementText(divComment.find('div.commentdata'), cleartext);
// Convert URLs to clickable links in comment. // Convert URLs to clickable links in comment.
urls2links(divComment.find('div.commentdata')); urls2links(divComment.find('div.commentdata'));
@ -171,7 +171,7 @@ function displayMessages(key, comments) {
place.append(divComment); place.append(divComment);
} }
$('div#comments').append('<div class="comment"><button onclick="open_reply($(this),\'' + pasteID() + '\');return false;">Add comment</button></div>'); $('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">' source.after('<div class="reply">'
+ '<input type="text" id="nickname" title="Optional nickname..." value="Optional nickname..." />' + '<input type="text" id="nickname" title="Optional nickname..." value="Optional nickname..." />'
+ '<textarea id="replymessage" class="replymessage" cols="80" rows="7"></textarea>' + '<textarea id="replymessage" class="replymessage" cols="80" rows="7"></textarea>'
+ '<br><button id="replybutton" onclick="send_comment(\'' + commentid + '\');return false;">Post comment</button>' + '<br /><button id="replybutton" onclick="send_comment(\'' + commentid + '\');return false;">Post comment</button>'
+ '<div id="replystatus">&nbsp;</div>' + '<div id="replystatus"> </div>'
+ '</div>'); + '</div>');
$('input#nickname').focus(function() { $('input#nickname').focus(function() {
if ($(this).val() == $(this).attr('title')) { if ($(this).val() == $(this).attr('title')) {
@ -281,46 +281,46 @@ function send_data() {
* Put the screen in "New paste" mode. * Put the screen in "New paste" mode.
*/ */
function stateNewPaste() { function stateNewPaste() {
$('button#sendbutton').show(); $('button#sendbutton').removeClass('hidden');
$('button#clonebutton').hide(); $('button#clonebutton').addClass('hidden');
$('div#expiration').show(); $('div#expiration').removeClass('hidden');
$('div#remainingtime').hide(); $('div#remainingtime').addClass('hidden');
$('div#language').hide(); // $('#language').show(); $('div#language').addClass('hidden'); // $('#language').removeClass('hidden');
$('input#password').hide(); //$('#password').show(); $('input#password').addClass('hidden'); //$('#password').removeClass('hidden');
$('div#opendisc').show(); $('div#opendisc').removeClass('hidden');
$('button#newbutton').show(); $('button#newbutton').removeClass('hidden');
$('div#pastelink').hide(); $('div#pastelink').addClass('hidden');
$('textarea#message').text(''); $('textarea#message').text('');
$('textarea#message').show(); $('textarea#message').removeClass('hidden');
$('div#cleartext').hide(); $('div#cleartext').addClass('hidden');
$('div#message').focus(); $('textarea#message').focus();
$('div#discussion').hide(); $('div#discussion').addClass('hidden');
$('div#prettymessage').hide(); $('div#prettymessage').addClass('hidden');
} }
/** /**
* Put the screen in "Existing paste" mode. * Put the screen in "Existing paste" mode.
*/ */
function stateExistingPaste() { function stateExistingPaste() {
$('button#sendbutton').hide(); $('button#sendbutton').addClass('hidden');
// No "clone" for IE<10. // No "clone" for IE<10.
if ($('div#oldienotice').is(":visible")) { if ($('div#oldienotice').is(":visible")) {
$('button#clonebutton').hide(); $('button#clonebutton').addClass('hidden');
} }
else { else {
$('button#clonebutton').show(); $('button#clonebutton').removeClass('hidden');
} }
$('div#expiration').hide(); $('div#expiration').addClass('hidden');
$('div#language').hide(); $('div#language').addClass('hidden');
$('input#password').hide(); $('input#password').addClass('hidden');
$('div#opendisc').hide(); $('div#opendisc').addClass('hidden');
$('button#newbutton').show(); $('button#newbutton').removeClass('hidden');
$('div#pastelink').hide(); $('div#pastelink').addClass('hidden');
$('textarea#message').hide(); $('textarea#message').addClass('hidden');
$('div#cleartext').hide(); $('div#cleartext').addClass('hidden');
$('div#prettymessage').show(); $('div#prettymessage').removeClass('hidden');
} }
/** /**
@ -361,11 +361,11 @@ function showStatus(message, spin) {
$('div#replystatus').removeClass('errorMessage'); $('div#replystatus').removeClass('errorMessage');
$('div#replystatus').text(message); $('div#replystatus').text(message);
if (!message) { if (!message) {
$('div#status').html('&nbsp'); $('div#status').html(' ');
return; return;
} }
if (message == '') { if (message == '') {
$('div#status').html('&nbsp'); $('div#status').html(' ');
return; return;
} }
$('div#status').removeClass('errorMessage'); $('div#status').removeClass('errorMessage');
@ -419,6 +419,9 @@ function pageKey() {
} }
$(function() { $(function() {
// hide "no javascript" message
$('#noscript').hide();
$('select#pasteExpiration').change(function() { $('select#pasteExpiration').change(function() {
if ($(this).val() == 'burn') { if ($(this).val() == 'burn') {
$('div#opendisc').addClass('buttondisabled'); $('div#opendisc').addClass('buttondisabled');
@ -430,7 +433,6 @@ $(function() {
} }
}); });
// Display an existing paste // Display an existing paste
if ($('div#cipherdata').text().length > 1) { if ($('div#cipherdata').text().length > 1) {
// Missing decryption key in URL ? // Missing decryption key in URL ?

View file

@ -29,7 +29,10 @@ class auto
*/ */
public static function loader($class_name) 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) public static function size_humanreadable($size)
{ {
$i = 0;
$iec = array('B', 'kiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'); $iec = array('B', 'kiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB');
while ( ( $size / 1024 ) > 1 ) { $i = 0;
while ( ( $size / 1024 ) >= 1 ) {
$size = $size / 1024; $size = $size / 1024;
$i++; $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. // Make sure required fields are present and contain base64 data.
foreach($accepted_keys as $k) foreach($accepted_keys as $k)
{ {
if (!array_key_exists($k, $decoded)) return false; if (!(
if (is_null(base64_decode($decoded[$k], $strict=true))) return false; array_key_exists($k, $decoded) &&
base64_decode($decoded[$k], $strict=true)
)) return false;
} }
// Make sure no additionnal keys were added. // Make sure no additionnal keys were added.
if ( if (
count( count(array_keys($decoded)) != count($accepted_keys)
array_intersect(
array_keys($decoded),
$accepted_keys
)
) != 3
) return false; ) return false;
// FIXME: Reject data if entropy is too low? // FIXME: Reject data if entropy is too low?

View file

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

View file

@ -397,7 +397,21 @@ class zerobin
*/ */
private function _view() 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; $page = new RainTPL;
// We escape it here because ENT_NOQUOTES can't be used in RainTPL templates. // We escape it here because ENT_NOQUOTES can't be used in RainTPL templates.
$page->assign('CIPHERDATA', htmlspecialchars($this->_data, ENT_NOQUOTES)); $page->assign('CIPHERDATA', htmlspecialchars($this->_data, ENT_NOQUOTES));

View file

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

View file

@ -37,13 +37,13 @@ class zerobin_data extends zerobin_abstract
if ( if (
is_array($options) && is_array($options) &&
array_key_exists('dir', $options) array_key_exists('dir', $options)
) self::$_dir = $options['dir'] . '/'; ) self::$_dir = $options['dir'] . DIRECTORY_SEPARATOR;
// if needed initialize the singleton // if needed initialize the singleton
if(null === parent::$_instance) { if(!(self::$_instance instanceof zerobin_data)) {
parent::$_instance = new self; self::$_instance = new self;
self::_init(); self::_init();
} }
return parent::$_instance; return self::$_instance;
} }
/** /**
@ -59,7 +59,7 @@ class zerobin_data extends zerobin_abstract
$storagedir = self::_dataid2path($pasteid); $storagedir = self::_dataid2path($pasteid);
if (is_file($storagedir . $pasteid)) return false; if (is_file($storagedir . $pasteid)) return false;
if (!is_dir($storagedir)) mkdir($storagedir, 0705, true); 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 * @access public
* @param string $pasteid * @param string $pasteid
* @return string * @return stdClass|false
*/ */
public function read($pasteid) public function read($pasteid)
{ {
if(!$this->exists($pasteid)) return json_decode( if(!$this->exists($pasteid)) return false;
'{"data":"","meta":{"burnafterreading":true,"postdate":0}}'
);
return json_decode( return json_decode(
file_get_contents(self::_dataid2path($pasteid) . $pasteid) file_get_contents(self::_dataid2path($pasteid) . $pasteid)
); );
@ -193,7 +191,7 @@ class zerobin_data extends zerobin_abstract
{ {
return is_file( return is_file(
self::_dataid2discussionpath($pasteid) . 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) public static function getInstance($options = null)
{ {
// if needed initialize the singleton // if needed initialize the singleton
if(null === self::$_instance) { if(!(self::$_instance instanceof zerobin_db)) {
parent::$_instance = new self; self::$_instance = new self;
} }
if (is_array($options)) if (is_array($options))
@ -175,6 +175,16 @@ class zerobin_db extends zerobin_abstract
*/ */
public function create($pasteid, $paste) 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 ( if (
!array_key_exists('opendiscussion', $paste['meta']) !array_key_exists('opendiscussion', $paste['meta'])
) $paste['meta']['opendiscussion'] = false; ) $paste['meta']['opendiscussion'] = false;
@ -199,31 +209,36 @@ class zerobin_db extends zerobin_abstract
* *
* @access public * @access public
* @param string $pasteid * @param string $pasteid
* @return string * @return stdClass|false
*/ */
public function read($pasteid) public function read($pasteid)
{ {
if ( if (
!array_key_exists($pasteid, self::$_cache) !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 = ?', 'SELECT * FROM ' . self::$_prefix . 'paste WHERE dataid = ?',
array($pasteid), true array($pasteid), true
); );
if(false !== $paste) {
// create object // create object
$paste = new stdClass; self::$_cache[$pasteid] = new stdClass;
$paste->data = self::$_cache[$pasteid]['data']; self::$_cache[$pasteid]->data = $paste['data'];
$paste->meta = new stdClass; self::$_cache[$pasteid]->meta = new stdClass;
$paste->meta->postdate = (int) self::$_cache[$pasteid]['postdate']; self::$_cache[$pasteid]->meta->postdate = (int) $paste['postdate'];
$paste->meta->expire_date = (int) self::$_cache[$pasteid]['expiredate']; self::$_cache[$pasteid]->meta->expire_date = (int) $paste['expiredate'];
if ( if (
self::$_cache[$pasteid]['opendiscussion'] $paste['opendiscussion']
) $paste->meta->opendiscussion = true; ) self::$_cache[$pasteid]->meta->opendiscussion = true;
if ( if (
self::$_cache[$pasteid]['burnafterreading'] $paste['burnafterreading']
) $paste->meta->burnafterreading = true; ) 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 = ?', 'DELETE FROM ' . self::$_prefix . 'comment WHERE pasteid = ?',
array($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 ( if (
!array_key_exists($pasteid, self::$_cache) !array_key_exists($pasteid, self::$_cache)
) self::$_cache[$pasteid] = self::_select( ) self::$_cache[$pasteid] = $this->read($pasteid);
'SELECT * FROM ' . self::$_prefix . 'paste WHERE dataid = ?',
array($pasteid), true
);
return (bool) self::$_cache[$pasteid]; return (bool) self::$_cache[$pasteid];
} }
@ -278,9 +293,9 @@ class zerobin_db extends zerobin_abstract
return self::_exec( return self::_exec(
'INSERT INTO ' . self::$_prefix . 'comment VALUES(?,?,?,?,?,?,?)', 'INSERT INTO ' . self::$_prefix . 'comment VALUES(?,?,?,?,?,?,?)',
array( array(
$commentid,
$pasteid, $pasteid,
$parentid, $parentid,
$commentid,
$comment['data'], $comment['data'],
$comment['meta']['nickname'], $comment['meta']['nickname'],
$comment['meta']['vizhash'], $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> <head>
<meta charset="utf-8" />
<title>ZeroBin</title> <title>ZeroBin</title>
<link type="text/css" rel="stylesheet" href="css/zerobin.css?{$VERSION|rawurlencode}#" /> <link type="text/css" rel="stylesheet" href="css/zerobin.css?{$VERSION|rawurlencode}#" />
<link type="text/css" rel="stylesheet" href="css/prettify.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/rawinflate.js#"></script>
<script src="js/prettify.js#"></script> <script src="js/prettify.js#"></script>
<script src="js/zerobin.js?{$VERSION|rawurlencode}#"></script> <script src="js/zerobin.js?{$VERSION|rawurlencode}#"></script>
<!--[if lt IE 10]> <!--[if lt IE 10]>
<style> body {padding-left:60px;padding-right:60px;} div#ienotice {display:block;} </style> <style> body {padding-left:60px;padding-right:60px;} div#ienotice {display:block;} </style>
<![endif]--> <![endif]-->
<!--[if lt IE 10]> <!--[if lt IE 10]>
<style> div#ienotice {display:block; } div#oldienotice {display:block; } </style> <style> div#ienotice {display:block; } div#oldienotice {display:block; } </style>
<![endif]--> <![endif]-->
</head> </head>
<body> <body>
<header>
<div id="aboutbox"> <div id="aboutbox">
ZeroBin is a minimalist, opensource online pastebin where the server has zero knowledge of pasted data. 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. 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 /> 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. Data may be deleted anytime. Kittens will die if you abuse this service.
</div> </div>
<h1 title="ZeroBin" onclick="window.location.href=scriptLocation();return false;">ZeroBin</h1><br> <h1 title="ZeroBin" onclick="window.location.href=scriptLocation();return false;">ZeroBin</h1><br />
<h2>Because ignorance is bliss</h2><br> <h2>Because ignorance is bliss</h2><br />
<h3>{$VERSION}</h3> <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="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.mozilla.org/firefox/">Firefox</a>,
<a href="http://www.opera.com/">Opera</a>, <a href="http://www.opera.com/">Opera</a>,
<a href="http://www.google.com/chrome">Chrome</a>, <a href="http://www.google.com/chrome">Chrome</a>,
<a href="http://www.apple.com/safari">Safari</a>... <a href="http://www.apple.com/safari">Safari</a>...
</div> </div>
<div id="status">&nbsp;</div> </header>
<div id="errormessage" style="display:none">{$ERRORMESSAGE|htmlspecialchars}</div> <section>
<article>
<div id="status"> </div>
<div id="errormessage" class="hidden">{$ERRORMESSAGE|htmlspecialchars}</div>
<div id="toolbar"> <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="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;" style="display:none;"><img src="img/icon_send.png#" width="18" height="15" />Send</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;" style="display:none;"><img src="img/icon_clone.png#" width="15" height="17" />Clone</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" style="display:none;">Expire: <div id="expiration" class="hidden">Expire:
<select id="pasteExpiration" name="pasteExpiration"> <select id="pasteExpiration" name="pasteExpiration">
<option value="burn">Burn after reading</option> <option value="burn">Burn after reading</option>
<option value="5min">5 minutes</option> <option value="5min">5 minutes</option>
@ -58,8 +62,8 @@
<option value="never">Never</option> <option value="never">Never</option>
</select> </select>
</div> </div>
<div id="remainingtime" style="display:none;"></div> <div id="remainingtime" class="hidden"></div>
<div id="language" style="display:none;"> <div id="language" class="hidden">
<select name="language"> <select name="language">
<option value="language" selected="selected">Language</option> <option value="language" selected="selected">Language</option>
<option value="C/C++">C/C++</option> <option value="C/C++">C/C++</option>
@ -67,22 +71,26 @@
<option value="python">Python</option> <option value="python">Python</option>
</select> </select>
</div> </div>
<input id="password" value="Optional password..." style="display:none;" /> <input id="password" value="Optional password..." class="hidden" />
<div id="opendisc" class="button" style="display:none;"> <div id="opendisc" class="button" class="hidden">
<input type="checkbox" id="opendiscussion" name="opendiscussion" {if="!$OPENDISCUSSION"} disabled="disabled"{/if} /> <input type="checkbox" id="opendiscussion" name="opendiscussion" {if="!$OPENDISCUSSION"} disabled="disabled"{/if} />
<label for="opendiscussion">Open discussion</label> <label for="opendiscussion">Open discussion</label>
</div> </div>
</div> </div>
<div id="pastelink" style="display:none;"></div> <div id="pastelink" class="hidden"></div>
<div id="prettymessage" style="display:none;"> <div id="prettymessage" class="hidden">
<pre id="prettyprint" class="prettyprint linenums:1"></pre> <div id="prettyprint" class="prettyprint linenums:1"></div>
</div> </div>
<div id="cleartext" style="display:none;"></div> <div id="cleartext" class="hidden"></div>
<textarea id="message" name="message" cols="80" rows="25" style="display:none;"></textarea> <textarea id="message" name="message" cols="80" rows="25" class="hidden"></textarea>
<div id="discussion" style="display:none;"> </article>
</section>
<section>
<div id="discussion" class="hidden">
<h4>Discussion</h4> <h4>Discussion</h4>
<div id="comments"></div> <div id="comments"></div>
</div> </div>
<div id="cipherdata" style="display:none;">{$CIPHERDATA}</div> </section>
<div id="cipherdata" class="hidden">{$CIPHERDATA}</div>
</body> </body>
</html> </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