diff --git a/.gitignore b/.gitignore index 69808c2..745c9dc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # Ignore data/ and tmp/ data/ tmp/ +tst/log/ .settings/ .buildpath .project diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 28a3ad8..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "uglifyjs"] - path = uglifyjs - url = https://github.com/mishoo/UglifyJS.git diff --git a/CREDITS.md b/CREDITS.md new file mode 100644 index 0000000..e282cef --- /dev/null +++ b/CREDITS.md @@ -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 diff --git a/INSTALL.md b/INSTALL.md index fd9899a..6a0f163 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -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) { @@ -81,16 +82,16 @@ If you want to create your own data models, you might want to know how the array $paste['meta']['opendiscussion'] // true (if false it is unset) $paste['meta']['burnafterreading'] // true (if false it is unset; if true, then opendiscussion is unset) } - + public function createComment($pasteid, $parentid, $commentid, $comment) { $pasteid // the id of the paste this comment belongs to $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 } diff --git a/cfg/conf.ini b/cfg/conf.ini index 5608c85..74acb0f 100644 --- a/cfg/conf.ini +++ b/cfg/conf.ini @@ -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 diff --git a/css/prettify.css b/css/prettify.css index eaa0f86..accf571 100644 --- a/css/prettify.css +++ b/css/prettify.css @@ -32,14 +32,20 @@ } /* 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 { - color: black; - margin-top: 0; - margin-bottom: 0; - list-style: decimal outside; + color: black; + margin-top: 0; + margin-bottom: 0; + list-style: decimal outside; } /* IE indents via margin-left */ /* li.L0, diff --git a/css/zerobin.css b/css/zerobin.css index d487c89..47b8797 100644 --- a/css/zerobin.css +++ b/css/zerobin.css @@ -4,354 +4,374 @@ /* 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; -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)); +html { + background-color: #455463; + 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)); } body { -font-family: Arial, Helvetica, sans-serif; -font-size: 0.8em; -margin-bottom:15px; -padding-left:60px; padding-right:60px; + font-family: Arial, Helvetica, sans-serif; + font-size: 0.8em; + margin-bottom: 15px; + padding-left: 60px; + padding-right: 60px; } -a { color:#0F388F; } +a { color: #0f388f; } h1 { -font-size:3.5em; -font-weight:700; -color:#000; -position:relative; -display:inline; -cursor:pointer; + font-size: 3.5em; + font-weight: bold; + color: #000; + position: relative; + display: inline; + cursor: pointer; } h1:before { -content:attr(title); -position:absolute; -color:rgba(255,255,255,0.15); -top:1px; -left:1px; -cursor:pointer; + content: attr(title); + position: absolute; + color: rgba(255,255,255,0.15); + top: 1px; + left: 1px; + cursor: pointer; } h2 { -color:#000; -font-size:1em; -display:inline; -font-style:italic; -font-weight:bold; -position:relative; -bottom:8px;} + color: #000; + font-size: 1em; + display: inline; + font-style: italic; + font-weight: bold; + position: relative; + bottom: 8px; +} h3 { -color:#94a3b4; -font-size:0.7em; -display:inline; -position:relative; -bottom:8px;} + color: #94a3b4; + font-size: 0.7em; + display: inline; + margin-top: 10px; + position: relative; + bottom: 8px; +} #aboutbox { -font-size:0.85em; -color: #94a3b4; -padding: 4px 8px 4px 16px; -position:relative; -top:10px; -border-left: 2px solid #94a3b4; -float:right; -width:60%; + font-size: 0.85em; + color: #94a3b4; + padding: 4px 8px 4px 16px; + position: relative; + top: 10px; + border-left: 2px solid #94a3b4; + float: right; + width: 60%; } -div#aboutbox a { color: #94a3b4; } +#aboutbox a { color: #94a3b4; } -textarea#message,div#cleartext,.replymessage { -clear:both; -color:black; -background-color:#fff; -white-space:pre-wrap; -font-family:Consolas,"Lucida Console","DejaVu Sans Mono",Monaco,monospace; -font-size:9pt; -border: 1px solid #28343F; -padding:5px; -box-sizing:border-box; --webkit-box-sizing:border-box; --moz-box-sizing:border-box; --ms-box-sizing:border-box; --o-box-sizing:border-box; -width:100%; +#message, #cleartext, .replymessage { + clear: both; + color: #000; + background-color: #fff; + white-space: pre-wrap; + font-family: Consolas, "Lucida Console", "DejaVu Sans Mono", Monaco, monospace; + font-size: 9pt; + border: 1px solid #28343F; + padding: 5px; + box-sizing: border-box; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + -ms-box-sizing: border-box; + -o-box-sizing: border-box; + width: 100%; } -div#status { -clear:both; -padding:5px 10px; +#status { + clear: both; + padding: 5px 10px; } -div#pastelink { -background-color:#1F2833; -color:white; -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; +#pastelink { + background-color: #1F2833; + color: #fff; + padding: 4px 12px; + clear: both; + -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 { -color:#fff; -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)); -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; --webkit-border-radius: 3px; --moz-border-radius: 3px; -border-radius: 3px; --moz-background-clip: padding; -webkit-background-clip: padding-box; background-clip: padding-box; +#pastelink a { color: #fff; } + +#pastelink button { margin-left: 11px } + +#toolbar, #status { margin-bottom: 5px; } + +button, .button, #expiration, #language { + color: #fff; + 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)); + border: 1px solid #28343F; + -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; } + 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)); -position:relative; -top:1px; + 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; + background: #ccc; + color: #888; + top: 0; } + button img { -margin-right:8px; -position:relative; -top:2px; + margin-right: 8px; + position: relative; + top: 2px; } -div#expiration, div#language, div#opendisc { -background-color:#414D5A; -padding:6px 8px; -margin:0px 5px 0px 0px;; -position: relative; -bottom:1px; /* WTF ? Why is this shifted by 1 pixel ? */ +#expiration, #language, #opendisc { + background-color: #414d5a; + padding: 6px 8px; + margin: 0 5px 0 0; + position: relative; + bottom: 1px; /* WTF ? Why is this shifted by 1 pixel ? */ } -div#expiration select, div#language select { -color:#eee; -background: transparent; -border: none; + +#expiration select, #language select { + color: #eee; + background: transparent; + border: none; } -div#expiration select option, div#language select option { -color:#eee; -background: #414D5A; -background-color:#414D5A; +#expiration select option, #language select option { + color:#eee; + background: #414d5a; } -div#remainingtime { -color: #94a3b4; -display:inline; -font-size:0.85em; +#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; -font-size: 1em !important; -font-weight:bold !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; -width:100%; -text-align:center; -font-weight:bold; -font-size:10pt; --webkit-border-radius:4px; --moz-border-radius:4px; -border-radius:4px; -padding:5px; + background-color: #fff; + color: #000; + width: 100%; + text-align: center; + font-weight: bold; + font-size: 10pt; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + padding: 5px; } -div#ienotice { -background-color:#7E98AF; -color:#000; -font-size:0.85em; -padding:3px 5px; -text-align:center; --webkit-border-radius:4px; --moz-border-radius:4px; -border-radius:4px; -display:none; +.hidden { display: none !important; } + +#ienotice { + background-color: #7e98af; + color: #000; + font-size: 0.85em; + padding: 3px 5px; + text-align: center; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + 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 */ -margin-top:20px; -width:100%; -margin-left:-30px; -min-width:200px; +#discussion { /* Discussion container */ + margin-top: 20px; + width: 100%; + margin-left: -30px; + min-width: 200px; } h4 { -font-size:1.2em; -color: #94A3B4; -font-style:italic; -font-weight:bold; -position:relative; -margin-left:30px; + font-size: 1.2em; + 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; -color:#000; -white-space:pre-wrap; -font-family:Consolas,"Lucida Console","DejaVu Sans Mono",Monaco,monospace; -font-size:9pt; -border-left: 1px solid #859AAE; -border-top: 1px solid #859AAE; -padding:5px 0px 5px 5px; -margin-left:30px; --moz-box-shadow: -3px -3px 5px rgba(0,0,0,0.15); --webkit-box-shadow: -3px -3px 5px rgba(0,0,0,0.15); -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; + background-color: #ceced6; + color: #000; + white-space: pre-wrap; + font-family: Consolas,"Lucida Console","DejaVu Sans Mono",Monaco,monospace; + font-size: 9pt; + border-left: 1px solid #859AAE; + border-top: 1px solid #859AAE; + padding: 5px 0px 5px 5px; + margin-left: 30px; + -moz-box-shadow: -3px -3px 5px rgba(0,0,0,0.15); + -webkit-box-shadow: -3px -3px 5px rgba(0,0,0,0.15); + box-shadow: -3px -3px 5px rgba(0,0,0,0.15); + min-width: 200px; + overflow: auto; } -div#replystatus { -display:inline; -padding:1px 7px; -font-family: Arial, Helvetica, sans-serif; +.reply { margin: 5px 0 0 30px; } + +#replystatus { + display: inline; + padding: 1px 7px; + font-family: Arial, Helvetica, sans-serif; } -div.comment button { -color:#446; -background-color:#aab; -background-repeat:no-repeat; -background-position:center left; -padding:0px 2px; -font-size:0.73em; -margin: 3px 5px 3px 0px; -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: -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; --webkit-border-radius: 3px; --moz-border-radius: 3px; -border-radius: 3px; --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%); -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%); -background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #fff), color-stop(1, #889)); -position:relative; -top:1px; +.comment button { + color: #446; + background-color: #aab; + background-repeat: no-repeat; + background-position: center left; + padding: 0 2px; + font-size: 0.73em; + 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: -webkit-gradient(linear, left bottom, left top, color-stop(0, #aab), color-stop(1, #ccc)); + border: 1px solid #ccd; + -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; } -div.comment input { -padding:2px; +.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)); } -textarea#replymessage { -margin-top:5px; +.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.commentmeta { -color: #fff; -background-color:#8EA0B2; -margin-bottom:3px; -padding:0px 0px 0px 3px; +.comment input { padding: 2px; } + +#replymessage { margin-top: 5px; } + +.commentmeta { + color: #fff; + background-color: #8ea0b2; + margin-bottom: 3px; + padding: 0 0 0 3px; } -span.commentdate { -color: #BFCEDE; -} +.commentdate { color: #bfcede; } img.vizhash { -width:16px; -height:16px; -position:relative; -top:2px; -left:-3px; + width: 16px; + height: 16px; + position: relative; + top: 2px; + left: -3px; } \ No newline at end of file diff --git a/img/icon_clone.png b/img/icon_clone.png index 94843ac..3d558ca 100644 Binary files a/img/icon_clone.png and b/img/icon_clone.png differ diff --git a/install.sh b/install.sh deleted file mode 100755 index 59644f8..0000000 --- a/install.sh +++ /dev/null @@ -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 \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 diff --git a/js/prettify.js b/js/prettify.js index 04ed32d..15f225a 100644 --- a/js/prettify.js +++ b/js/prettify.js @@ -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) { diff --git a/js/zerobin.js b/js/zerobin.js index d243dc5..7ed65cd 100644 --- a/js/zerobin.js +++ b/js/zerobin.js @@ -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
")); + element.html(element.text().replace(/{BIG_UGLY_STINKING_THING__OH_GOD_I_HATE_IE}/ig,"\n
")); } // 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 = $('
' + var divComment = $('
' + '
' + '' - + '
'); + + '
'); 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#discussion').show(); + $('div#discussion').removeClass('hidden'); } } @@ -185,8 +185,8 @@ function open_reply(source, commentid) { source.after('
' + '' + '' - + '
' - + '
 
' + + '
' + + '
' + '
'); $('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(' '); + $('div#status').html(' '); return; } if (message == '') { - $('div#status').html(' '); + $('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 ? diff --git a/lib/auto.php b/lib/auto.php index 53c62c9..ac3f169 100644 --- a/lib/auto.php +++ b/lib/auto.php @@ -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; } } - diff --git a/lib/filter.php b/lib/filter.php index fcc3d61..68c4f59 100644 --- a/lib/filter.php +++ b/lib/filter.php @@ -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]; } } diff --git a/lib/sjcl.php b/lib/sjcl.php index 0b4c381..9bfe1fd 100644 --- a/lib/sjcl.php +++ b/lib/sjcl.php @@ -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? diff --git a/lib/trafficlimiter.php b/lib/trafficlimiter.php index 2fcfce4..3a2e8cd 100644 --- a/lib/trafficlimiter.php +++ b/lib/trafficlimiter.php @@ -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 { diff --git a/lib/zerobin.php b/lib/zerobin.php index 1758100..2860577 100644 --- a/lib/zerobin.php +++ b/lib/zerobin.php @@ -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)); diff --git a/lib/zerobin/abstract.php b/lib/zerobin/abstract.php index 118f474..234fb7f 100644 --- a/lib/zerobin/abstract.php +++ b/lib/zerobin/abstract.php @@ -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. diff --git a/lib/zerobin/data.php b/lib/zerobin/data.php index ed326f2..a9e42db 100644 --- a/lib/zerobin/data.php +++ b/lib/zerobin/data.php @@ -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 ); } diff --git a/lib/zerobin/db.php b/lib/zerobin/db.php index 6bc2bcb..714dbd8 100644 --- a/lib/zerobin/db.php +++ b/lib/zerobin/db.php @@ -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( - 'SELECT * FROM ' . self::$_prefix . 'paste WHERE dataid = ?', - array($pasteid), true - ); + ) { + self::$_cache[$pasteid] = false; + $paste = self::_select( + 'SELECT * FROM ' . self::$_prefix . 'paste WHERE dataid = ?', + array($pasteid), true + ); - // 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']; - if ( - self::$_cache[$pasteid]['opendiscussion'] - ) $paste->meta->opendiscussion = true; - if ( - self::$_cache[$pasteid]['burnafterreading'] - ) $paste->meta->burnafterreading = true; + if(false !== $paste) { + // create object + 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 ( + $paste['opendiscussion'] + ) self::$_cache[$pasteid]->meta->opendiscussion = true; + if ( + $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'], diff --git a/tpl/page.html b/tpl/page.html index cdc6c38..70d396a 100644 --- a/tpl/page.html +++ b/tpl/page.html @@ -1,88 +1,96 @@ - - -ZeroBin - - - - - - - - - - - - - - - - -
- ZeroBin is a minimalist, opensource online pastebin where the server has zero knowledge of pasted data. - Data is encrypted/decrypted in the browser using 256 bits AES. - More information on the project page.
- Note: This is a test service: - Data may be deleted anytime. Kittens will die if you abuse this service. -
-

ZeroBin


-

Because ignorance is bliss


-

{$VERSION}

- -
ZeroBin requires a modern browser to work.
-
Still using Internet Explorer ?  Do yourself a favor, switch to a modern browser: - Firefox, - Opera, - Chrome, - Safari... -
-
 
- -
- - - - - - - - -
- - - - - - + + + + + + ZeroBin + + + + + + + + + + + + + +
+
+ ZeroBin is a minimalist, opensource online pastebin where the server has zero knowledge of pasted data. + Data is encrypted/decrypted in the browser using 256 bits AES. + More information on the project page.
+ Note: This is a test service: + Data may be deleted anytime. Kittens will die if you abuse this service. +
+

ZeroBin


+

Because ignorance is bliss


+

{$VERSION}

+
Javascript is required for ZeroBin to work.
Sorry for the inconvenience.
+
ZeroBin requires a modern browser to work.
+
Still using Internet Explorer ? Do yourself a favor, switch to a modern browser: + Firefox, + Opera, + Chrome, + Safari... +
+
+
+
+
+ +
+ + + + + + + +
+ + +
+
+ + + + +
+
+
+ +
+ diff --git a/tst/README.md b/tst/README.md new file mode 100644 index 0000000..ad21697 --- /dev/null +++ b/tst/README.md @@ -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 diff --git a/tst/RainTPL.php b/tst/RainTPL.php new file mode 100644 index 0000000..bbea6f6 --- /dev/null +++ b/tst/RainTPL.php @@ -0,0 +1,77 @@ + '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' + ); + } +} diff --git a/tst/auto.php b/tst/auto.php new file mode 100644 index 0000000..f8cb4c0 --- /dev/null +++ b/tst/auto.php @@ -0,0 +1,8 @@ +assertFalse(auto::loader('foo2501bar42'), 'calling non existent class'); + } +} diff --git a/tst/bootstrap.php b/tst/bootstrap.php new file mode 100644 index 0000000..117a0aa --- /dev/null +++ b/tst/bootstrap.php @@ -0,0 +1,31 @@ +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 . '".'); + } + } +} \ No newline at end of file diff --git a/tst/filter.php b/tst/filter.php new file mode 100644 index 0000000..198a13f --- /dev/null +++ b/tst/filter.php @@ -0,0 +1,47 @@ +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)); + } +} diff --git a/tst/phpunit.xml b/tst/phpunit.xml new file mode 100644 index 0000000..035b040 --- /dev/null +++ b/tst/phpunit.xml @@ -0,0 +1,17 @@ + + + ./ + + + + ../lib + + ../lib/zerobin/abstract.php + + + + + + + + \ No newline at end of file diff --git a/tst/sjcl.php b/tst/sjcl.php new file mode 100644 index 0000000..b90f054 --- /dev/null +++ b/tst/sjcl.php @@ -0,0 +1,14 @@ +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'); + } +} diff --git a/tst/trafficlimiter.php b/tst/trafficlimiter.php new file mode 100644 index 0000000..68947b9 --- /dev/null +++ b/tst/trafficlimiter.php @@ -0,0 +1,30 @@ +_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'); + } +} diff --git a/tst/vizhash16x16.php b/tst/vizhash16x16.php new file mode 100644 index 0000000..cf73011 --- /dev/null +++ b/tst/vizhash16x16.php @@ -0,0 +1,41 @@ +_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')); + } +} diff --git a/tst/zerobin/data.php b/tst/zerobin/data.php new file mode 100644 index 0000000..7e85285 --- /dev/null +++ b/tst/zerobin/data.php @@ -0,0 +1,70 @@ + '{"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'); + } +} diff --git a/tst/zerobin/db.php b/tst/zerobin/db.php new file mode 100644 index 0000000..132f290 --- /dev/null +++ b/tst/zerobin/db.php @@ -0,0 +1,68 @@ + '{"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'); + } +} diff --git a/uglifyjs b/uglifyjs deleted file mode 160000 index ef4d776..0000000 --- a/uglifyjs +++ /dev/null @@ -1 +0,0 @@ -Subproject commit ef4d776aedee6cbc8959a8e76403b82523615d3a