Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b3ad32665 | ||
|
|
0997520c1d | ||
|
|
2dc4422a27 | ||
|
|
3fe7e77390 | ||
|
|
ce107c928e | ||
|
|
24a4328c55 | ||
|
|
bba485ef6d | ||
|
|
d8ae1be2ff | ||
|
|
42a9c92b5e | ||
|
|
9d27e7a65d | ||
|
|
d42975580a | ||
|
|
176dff3b70 | ||
|
|
5a9879623f | ||
|
|
740d62005e | ||
|
|
40019624fd | ||
|
|
e3f4aa982c | ||
|
|
ca07398b66 | ||
|
|
f96b0c0afe | ||
|
|
14d08ec56d | ||
|
|
22d0b1ec22 | ||
|
|
f21567133c | ||
|
|
b92b38cee8 | ||
|
|
87b41a0c3d | ||
|
|
2e3bacb699 | ||
|
|
5d61b90d6b | ||
|
|
512b3d1172 | ||
|
|
1d6cfb7f3b | ||
|
|
9e6e29bc93 | ||
|
|
e5b096ed8c | ||
|
|
add980d36f | ||
|
|
7ec94e0db5 | ||
|
|
6b7dc44039 | ||
|
|
ce3f10f143 | ||
|
|
694138c5d4 | ||
|
|
211d3e4622 | ||
|
|
d04eab52c9 | ||
|
|
22b4c89227 | ||
|
|
6d24ff824e |
15
CHANGELOG.md
15
CHANGELOG.md
@@ -1,5 +1,20 @@
|
||||
# ZeroBin version history #
|
||||
|
||||
* **0.22 (2015-11-09)**:
|
||||
* ADDED: Tab character input support
|
||||
* ADDED: Dark bootstrap theme
|
||||
* ADDED: Option to hide clone button on expiring pastes
|
||||
* ADDED: Option to set a different default language then English and/or enforce it as the only language
|
||||
* ADDED: Database now contains version to allow automatic update of structure, only if necessary; removing database structure check on each request
|
||||
* ADDED: Favicons
|
||||
* FIXING: Regressions in database layer, prohibiting pastes from being stored
|
||||
* FIXING: Fixing "missing" comments when they were posted during the same second to the same paste
|
||||
* FIXING: JS failing when password input disabled
|
||||
* CHANGED: Switching positions of "New" and "Send" button, highlighting the latter to improve workflow
|
||||
* CHANGED: Renamed config file to make updates easier
|
||||
* CHANGED: Switching to JSON-based REST-API
|
||||
* CHANGED: Database structure to store attachments, allowing larger attachments to be stored (depending on maximum BLOB size of database backend)
|
||||
* CHANGED: Refactored data model, traffic limiting & request handling
|
||||
* **0.21.1 (2015-09-21)**:
|
||||
* FIXING: lost meta data when using DB model instead of flat files
|
||||
* FIXING: mobile navbar getting triggered on load
|
||||
|
||||
@@ -8,6 +8,8 @@ MrKooky - HTML5 markup, CSS cleanup
|
||||
Simon Rupf - MVC refactoring, configuration, i18n and unit tests
|
||||
Hexalyse - Password protection
|
||||
Viktor Stanchev - File upload support
|
||||
azlux - Tab character input support
|
||||
Adam Fisher - Favicons
|
||||
|
||||
Translations:
|
||||
Hexalyse - French
|
||||
|
||||
31
INSTALL.md
31
INSTALL.md
@@ -16,8 +16,10 @@ and extract it in your web hosts folder were you want to install your ZeroBin in
|
||||
|
||||
### Configuration
|
||||
|
||||
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 file `cfg/conf.ini` you can configure ZeroBin. A `cfg/conf.ini.sample`
|
||||
is provided containing all options on default values. You can copy it to
|
||||
`cfg/conf.ini` and adapt it as needed. 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
|
||||
@@ -74,20 +76,31 @@ any experience running ZeroBin on other RDBMS, please let us know.
|
||||
For reference or if you want to create the table schema for yourself:
|
||||
|
||||
CREATE TABLE prefix_paste (
|
||||
dataid CHAR(16),
|
||||
data TEXT,
|
||||
dataid CHAR(16) NOT NULL,
|
||||
data BLOB,
|
||||
postdate INT,
|
||||
expiredate INT,
|
||||
opendiscussion INT,
|
||||
burnafterreading INT
|
||||
burnafterreading INT,
|
||||
meta TEXT,
|
||||
attachment MEDIUMBLOB,
|
||||
attachmentname BLOB,
|
||||
PRIMARY KEY (dataid)
|
||||
);
|
||||
|
||||
CREATE TABLE prefix_comment (
|
||||
dataid CHAR(16),
|
||||
pasteid CHAR(16),
|
||||
parentid CHAR(16),
|
||||
data TEXT,
|
||||
nickname VARCHAR(255),
|
||||
vizhash TEXT,
|
||||
postdate INT
|
||||
data BLOB,
|
||||
nickname BLOB,
|
||||
vizhash BLOB,
|
||||
postdate INT,
|
||||
PRIMARY KEY (dataid)
|
||||
);
|
||||
CREATE INDEX parent ON prefix_comment(pasteid);
|
||||
|
||||
CREATE TABLE prefix_config (
|
||||
id CHAR(16) NOT NULL, value TEXT, PRIMARY KEY (id)
|
||||
);
|
||||
INSERT INTO prefix_config VALUES('VERSION', '0.22');
|
||||
|
||||
11
README.md
11
README.md
@@ -1,4 +1,4 @@
|
||||
# ZeroBin 0.21.1
|
||||
# ZeroBin 0.22
|
||||
|
||||
ZeroBin is a minimalist, opensource online pastebin where the server has zero
|
||||
knowledge of pasted data.
|
||||
@@ -29,6 +29,15 @@ without loosing any data.
|
||||
|
||||
- As a user you have to trust the server administrator, your internet provider
|
||||
and any country the traffic passes not to inject any malicious javascript code.
|
||||
Ideally, the ZeroBin installation used would provide HTTPS, secured by
|
||||
[HSTS](https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security) and
|
||||
[HKPH](https://en.wikipedia.org/wiki/HTTP_Public_Key_Pinning) using a
|
||||
certificate either validated by a trusted third party (check the certificate
|
||||
when first using a new ZeroBin instance) or self-signed by the server operator,
|
||||
validated using a
|
||||
[DNSSEC](https://en.wikipedia.org/wiki/Domain_Name_System_Security_Extensions) protected
|
||||
[DANE](https://en.wikipedia.org/wiki/DNS-based_Authentication_of_Named_Entities)
|
||||
record.
|
||||
|
||||
- The "key" used to encrypt the paste is part of the URL. If you publicly post
|
||||
the URL of a paste that is not password-protected, everybody can read it.
|
||||
|
||||
1
cfg/.gitignore
vendored
Normal file
1
cfg/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/conf.ini
|
||||
@@ -5,7 +5,7 @@
|
||||
; @link http://sebsauvage.net/wiki/doku.php?id=php:zerobin
|
||||
; @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
|
||||
; @license http://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
|
||||
; @version 0.21.1
|
||||
; @version 0.22
|
||||
|
||||
[main]
|
||||
; enable or disable the discussion feature, defaults to true
|
||||
@@ -48,11 +48,19 @@ base64version = "2.1.9"
|
||||
; a session cookie to store the choice until the browser is closed.
|
||||
languageselection = false
|
||||
|
||||
; set the language your installs defaults to, defaults to English
|
||||
; if this is set and language selection is disabled, this will be the only language
|
||||
; languagedefault = "en"
|
||||
|
||||
[expire]
|
||||
; expire value that is selected per default
|
||||
; make sure the value exists in [expire_options]
|
||||
default = "1week"
|
||||
|
||||
; optionally the "clone" button can be disabled on expiring pastes
|
||||
; note that this only hides the button, copy & paste is still possible
|
||||
; clone = false
|
||||
|
||||
[expire_options]
|
||||
; Set each one of these to the number of seconds in the expiration period,
|
||||
; or 0 if it should never expire
|
||||
8061
css/bootstrap/darkstrap-0.9.3.css
Normal file
8061
css/bootstrap/darkstrap-0.9.3.css
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
/* ZeroBin 0.21.1 - http://sebsauvage.net/wiki/doku.php?id=php:zerobin */
|
||||
/* ZeroBin 0.22 - http://sebsauvage.net/wiki/doku.php?id=php:zerobin */
|
||||
|
||||
|
||||
body {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* ZeroBin 0.21.1 - http://sebsauvage.net/wiki/doku.php?id=php:zerobin */
|
||||
/* ZeroBin 0.22 - http://sebsauvage.net/wiki/doku.php?id=php:zerobin */
|
||||
|
||||
|
||||
/* CSS Reset from YUI 3.4.1 (build 4118) - Copyright 2011 Yahoo! Inc. All rights reserved.
|
||||
@@ -394,6 +394,10 @@ img.vizhash {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
#prettyprint.prettyprinted {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#cleartext {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
BIN
favicon.ico
Normal file
BIN
favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.7 KiB |
BIN
img/favicons/android-chrome-192x192.png
Normal file
BIN
img/favicons/android-chrome-192x192.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
BIN
img/favicons/apple-touch-icon.png
Normal file
BIN
img/favicons/apple-touch-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 819 B |
BIN
img/favicons/favicon-16x16.png
Normal file
BIN
img/favicons/favicon-16x16.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 278 B |
BIN
img/favicons/favicon-32x32.png
Normal file
BIN
img/favicons/favicon-32x32.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 373 B |
BIN
img/favicons/favicon-96x96.png
Normal file
BIN
img/favicons/favicon-96x96.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 666 B |
@@ -7,7 +7,7 @@
|
||||
* @link http://sebsauvage.net/wiki/doku.php?id=php:zerobin
|
||||
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
|
||||
* @license http://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
|
||||
* @version 0.21
|
||||
* @version 0.21.1
|
||||
*/
|
||||
|
||||
// change this, if your php files and data is outside of your webservers document root
|
||||
|
||||
16
js/comment.jsonld
Normal file
16
js/comment.jsonld
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"@context": {
|
||||
"so": "http://schema.org/",
|
||||
"status": "so:Integer",
|
||||
"id": "so:name",
|
||||
"parentid": "so:name",
|
||||
"url: {
|
||||
"@id": "so:url",
|
||||
"@type": "@id"
|
||||
},
|
||||
"data": "so:Text",
|
||||
"meta": {
|
||||
"@id": "?jsonld=commentmeta"
|
||||
}
|
||||
}
|
||||
}
|
||||
8
js/commentmeta.jsonld
Normal file
8
js/commentmeta.jsonld
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"@context": {
|
||||
"so": "http://schema.org/",
|
||||
"postdate": "so:Integer",
|
||||
"nickname": "so:Text",
|
||||
"vizhash": "so:Text"
|
||||
}
|
||||
}
|
||||
24
js/paste.jsonld
Normal file
24
js/paste.jsonld
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"@context": {
|
||||
"so": "http://schema.org/",
|
||||
"status": {"@id": "so:Integer"},
|
||||
"id": {"@id": "so:name"},
|
||||
"deletetoken": {"@id": "so:Text"},
|
||||
"url": {
|
||||
"@type": "@id",
|
||||
"@id": "so:url"
|
||||
},
|
||||
"data": {"@id": "so:Text"},
|
||||
"attachment": {"@id": "so:Text"},
|
||||
"attachmentname": {"@id": "so:Text"},
|
||||
"meta": {
|
||||
"@id": "?jsonld=pastemeta"
|
||||
},
|
||||
"comments": {
|
||||
"@id": "?jsonld=comment",
|
||||
"@container": "@list"
|
||||
},
|
||||
"comment_count": {"@id": "so:Integer"},
|
||||
"comment_offset": {"@id": "so:Integer"}
|
||||
}
|
||||
}
|
||||
11
js/pastemeta.jsonld
Normal file
11
js/pastemeta.jsonld
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"@context": {
|
||||
"so": "http://schema.org/",
|
||||
"formatter": {"@id": "so:Text"},
|
||||
"postdate": {"@id": "so:Integer"},
|
||||
"opendiscussion": {"@id": "so:True"},
|
||||
"burnafterreading": {"@id": "so:True"},
|
||||
"expire_date": {"@id": "so:Integer"},
|
||||
"remaining_time": {"@id": "so:Integer"}
|
||||
}
|
||||
}
|
||||
241
js/zerobin.js
241
js/zerobin.js
@@ -6,7 +6,7 @@
|
||||
* @link http://sebsauvage.net/wiki/doku.php?id=php:zerobin
|
||||
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
|
||||
* @license http://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
|
||||
* @version 0.21.1
|
||||
* @version 0.22
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
@@ -443,7 +443,7 @@ $(function() {
|
||||
*/
|
||||
cipher: function(key, password, message)
|
||||
{
|
||||
password = password.trim();
|
||||
password = (password || '').trim();
|
||||
if (password.length == 0)
|
||||
{
|
||||
return sjcl.encrypt(key, this.compress(message));
|
||||
@@ -482,6 +482,11 @@ $(function() {
|
||||
};
|
||||
|
||||
var zerobin = {
|
||||
/**
|
||||
* headers to send in AJAX requests
|
||||
*/
|
||||
headers: {'X-Requested-With': 'JSONHttpRequest'},
|
||||
|
||||
/**
|
||||
* Get the current script location (without search or hash part of the URL).
|
||||
* eg. http://server.com/zero/?aaaa#bbbb --> http://server.com/zero/
|
||||
@@ -578,37 +583,42 @@ $(function() {
|
||||
helper.urls2links(this.clearText);
|
||||
helper.urls2links(this.prettyPrint);
|
||||
this.clearText.addClass('hidden');
|
||||
if (format == 'plaintext')
|
||||
{
|
||||
this.prettyPrint.css('white-space', 'pre-wrap');
|
||||
this.prettyPrint.css('word-break', 'normal');
|
||||
this.prettyPrint.removeClass('prettyprint');
|
||||
}
|
||||
this.prettyMessage.removeClass('hidden');
|
||||
}
|
||||
if (format == 'plaintext') this.prettyPrint.removeClass('prettyprint');
|
||||
},
|
||||
|
||||
/**
|
||||
* Show decrypted text in the display area, including discussion (if open)
|
||||
*
|
||||
* @param string key : decryption key
|
||||
* @param array comments : Array of messages to display (items = array with keys ('data','meta')
|
||||
* @param object paste : paste object including comments to display (items = array with keys ('data','meta')
|
||||
*/
|
||||
displayMessages: function(key, comments)
|
||||
displayMessages: function(key, paste)
|
||||
{
|
||||
// Try to decrypt the paste.
|
||||
var password = this.passwordInput.val();
|
||||
if (!this.prettyPrint.hasClass('prettyprinted')) {
|
||||
try
|
||||
{
|
||||
if (comments[0].attachment)
|
||||
if (paste.attachment)
|
||||
{
|
||||
var attachment = filter.decipher(key, password, comments[0].attachment);
|
||||
var attachment = filter.decipher(key, password, paste.attachment);
|
||||
if (attachment.length == 0)
|
||||
{
|
||||
if (password.length == 0) password = this.requestPassword();
|
||||
attachment = filter.decipher(key, password, comments[0].attachment);
|
||||
attachment = filter.decipher(key, password, paste.attachment);
|
||||
}
|
||||
if (attachment.length == 0) throw 'failed to decipher attachment';
|
||||
|
||||
if (comments[0].attachmentname)
|
||||
if (paste.attachmentname)
|
||||
{
|
||||
var attachmentname = filter.decipher(key, password, comments[0].attachmentname);
|
||||
var attachmentname = filter.decipher(key, password, paste.attachmentname);
|
||||
if (attachmentname.length > 0) this.attachmentLink.attr('download', attachmentname);
|
||||
}
|
||||
this.attachmentLink.attr('href', attachment);
|
||||
@@ -626,20 +636,20 @@ $(function() {
|
||||
this.image.removeClass('hidden');
|
||||
}
|
||||
}
|
||||
var cleartext = filter.decipher(key, password, comments[0].data);
|
||||
if (cleartext.length == 0 && password.length == 0 && !comments[0].attachment)
|
||||
var cleartext = filter.decipher(key, password, paste.data);
|
||||
if (cleartext.length == 0 && password.length == 0 && !paste.attachment)
|
||||
{
|
||||
password = this.requestPassword();
|
||||
cleartext = filter.decipher(key, password, comments[0].data);
|
||||
cleartext = filter.decipher(key, password, paste.data);
|
||||
}
|
||||
if (cleartext.length == 0 && !comments[0].attachment) throw 'failed to decipher message';
|
||||
if (cleartext.length == 0 && !paste.attachment) throw 'failed to decipher message';
|
||||
|
||||
this.passwordInput.val(password);
|
||||
if (cleartext.length > 0)
|
||||
{
|
||||
helper.setElementText(this.clearText, cleartext);
|
||||
helper.setElementText(this.prettyPrint, cleartext);
|
||||
this.formatPaste(comments[0].meta.formatter);
|
||||
this.formatPaste(paste.meta.formatter);
|
||||
}
|
||||
}
|
||||
catch(err)
|
||||
@@ -653,9 +663,9 @@ $(function() {
|
||||
}
|
||||
|
||||
// Display paste expiration / for your eyes only.
|
||||
if (comments[0].meta.expire_date)
|
||||
if (paste.meta.expire_date)
|
||||
{
|
||||
var expiration = helper.secondsToHuman(comments[0].meta.remaining_time),
|
||||
var expiration = helper.secondsToHuman(paste.meta.remaining_time),
|
||||
expirationLabel = [
|
||||
'This document will expire in %d ' + expiration[1] + '.',
|
||||
'This document will expire in %d ' + expiration[1] + 's.'
|
||||
@@ -664,9 +674,16 @@ $(function() {
|
||||
this.remainingTime.removeClass('foryoureyesonly')
|
||||
.removeClass('hidden');
|
||||
}
|
||||
if (comments[0].meta.burnafterreading)
|
||||
if (paste.meta.burnafterreading)
|
||||
{
|
||||
$.get(this.scriptLocation() + '?pasteid=' + this.pasteID() + '&deletetoken=burnafterreading', 'json')
|
||||
// unfortunately many web servers don't support DELETE (and PUT) out of the box
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: this.scriptLocation() + '?' + this.pasteID(),
|
||||
data: {deletetoken: 'burnafterreading'},
|
||||
dataType: 'json',
|
||||
headers: this.headers
|
||||
})
|
||||
.fail(function() {
|
||||
zerobin.showError(i18n._('Could not delete the paste, it was not stored in burn after reading mode.'));
|
||||
});
|
||||
@@ -680,15 +697,15 @@ $(function() {
|
||||
}
|
||||
|
||||
// If the discussion is opened on this paste, display it.
|
||||
if (comments[0].meta.opendiscussion)
|
||||
if (paste.meta.opendiscussion)
|
||||
{
|
||||
this.comments.html('');
|
||||
|
||||
// iterate over comments
|
||||
for (var i = 1; i < comments.length; i++)
|
||||
for (var i = 0; i < paste.comments.length; ++i)
|
||||
{
|
||||
var place = this.comments;
|
||||
var comment=comments[i];
|
||||
var comment = paste.comments[i];
|
||||
var cleartext = '[' + i18n._('Could not decrypt comment; Wrong key?') + ']';
|
||||
try
|
||||
{
|
||||
@@ -697,18 +714,18 @@ $(function() {
|
||||
catch(err)
|
||||
{}
|
||||
// If parent comment exists, display below (CSS will automatically shift it right.)
|
||||
var cname = '#comment_' + comment.meta.parentid;
|
||||
var cname = '#comment_' + comment.parentid;
|
||||
|
||||
// If the element exists in page
|
||||
if ($(cname).length)
|
||||
{
|
||||
place = $(cname);
|
||||
}
|
||||
var divComment = $('<article><div class="comment" id="comment_' + comment.meta.commentid+'">'
|
||||
var divComment = $('<article><div class="comment" id="comment_' + comment.id + '">'
|
||||
+ '<div class="commentmeta"><span class="nickname"></span><span class="commentdate"></span></div><div class="commentdata"></div>'
|
||||
+ '<button class="btn btn-default btn-sm">' + i18n._('Reply') + '</button>'
|
||||
+ '</div></article>');
|
||||
divComment.find('button').click({commentid: comment.meta.commentid}, $.proxy(this.openReply, this));
|
||||
divComment.find('button').click({commentid: comment.id}, $.proxy(this.openReply, this));
|
||||
helper.setElementText(divComment.find('div.commentdata'), cleartext);
|
||||
// Convert URLs to clickable links in comment.
|
||||
helper.urls2links(divComment.find('div.commentdata'));
|
||||
@@ -725,7 +742,7 @@ $(function() {
|
||||
}
|
||||
divComment.find('span.commentdate')
|
||||
.text(' (' + (new Date(comment.meta.postdate * 1000).toLocaleString()) + ')')
|
||||
.attr('title', 'CommentID: ' + comment.meta.commentid);
|
||||
.attr('title', 'CommentID: ' + comment.id);
|
||||
|
||||
// If an avatar is available, display it.
|
||||
if (comment.meta.vizhash)
|
||||
@@ -805,39 +822,52 @@ $(function() {
|
||||
nickname: ciphernickname
|
||||
};
|
||||
|
||||
$.post(this.scriptLocation(), data_to_send, function(data)
|
||||
{
|
||||
if (data.status == 0)
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: this.scriptLocation(),
|
||||
data: data_to_send,
|
||||
dataType: 'json',
|
||||
headers: this.headers,
|
||||
success: function(data)
|
||||
{
|
||||
zerobin.showStatus(i18n._('Comment posted.'), false);
|
||||
$.get(zerobin.scriptLocation() + '?' + zerobin.pasteID() + '&json', function(data)
|
||||
if (data.status == 0)
|
||||
{
|
||||
if (data.status == 0)
|
||||
{
|
||||
zerobin.displayMessages(zerobin.pageKey(), data.messages);
|
||||
}
|
||||
else if (data.status == 1)
|
||||
{
|
||||
zerobin.showError(i18n._('Could not refresh display: %s', data.message));
|
||||
}
|
||||
else
|
||||
{
|
||||
zerobin.showError(i18n._('Could not refresh display: %s', i18n._('unknown status')));
|
||||
}
|
||||
}, 'json')
|
||||
.fail(function() {
|
||||
zerobin.showError(i18n._('Could not refresh display: %s', i18n._('server error or not responding')));
|
||||
});
|
||||
zerobin.showStatus(i18n._('Comment posted.'), false);
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: zerobin.scriptLocation() + '?' + zerobin.pasteID(),
|
||||
dataType: 'json',
|
||||
headers: zerobin.headers,
|
||||
success: function(data)
|
||||
{
|
||||
if (data.status == 0)
|
||||
{
|
||||
zerobin.displayMessages(zerobin.pageKey(), data);
|
||||
}
|
||||
else if (data.status == 1)
|
||||
{
|
||||
zerobin.showError(i18n._('Could not refresh display: %s', data.message));
|
||||
}
|
||||
else
|
||||
{
|
||||
zerobin.showError(i18n._('Could not refresh display: %s', i18n._('unknown status')));
|
||||
}
|
||||
}
|
||||
})
|
||||
.fail(function() {
|
||||
zerobin.showError(i18n._('Could not refresh display: %s', i18n._('server error or not responding')));
|
||||
});
|
||||
}
|
||||
else if (data.status == 1)
|
||||
{
|
||||
zerobin.showError(i18n._('Could not post comment: %s', data.message));
|
||||
}
|
||||
else
|
||||
{
|
||||
zerobin.showError(i18n._('Could not post comment: %s', i18n._('unknown status')));
|
||||
}
|
||||
}
|
||||
else if (data.status == 1)
|
||||
{
|
||||
zerobin.showError(i18n._('Could not post comment: %s', data.message));
|
||||
}
|
||||
else
|
||||
{
|
||||
zerobin.showError(i18n._('Could not post comment: %s', i18n._('unknown status')));
|
||||
}
|
||||
}, 'json')
|
||||
})
|
||||
.fail(function() {
|
||||
zerobin.showError(i18n._('Could not post comment: %s', i18n._('server error or not responding')));
|
||||
});
|
||||
@@ -932,36 +962,44 @@ $(function() {
|
||||
data_to_send.attachmentname = cipherdata_attachment_name;
|
||||
}
|
||||
}
|
||||
$.post(this.scriptLocation(), data_to_send, function(data)
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: this.scriptLocation(),
|
||||
data: data_to_send,
|
||||
dataType: 'json',
|
||||
headers: this.headers,
|
||||
success: function(data)
|
||||
{
|
||||
if (data.status == 0) {
|
||||
zerobin.stateExistingPaste();
|
||||
var url = zerobin.scriptLocation() + '?' + data.id + '#' + randomkey;
|
||||
var deleteUrl = zerobin.scriptLocation() + '?pasteid=' + data.id + '&deletetoken=' + data.deletetoken;
|
||||
zerobin.showStatus('', false);
|
||||
zerobin.errorMessage.addClass('hidden');
|
||||
|
||||
$('#pastelink').html(i18n._('Your paste is <a id="pasteurl" href="%s">%s</a> <span id="copyhint">(Hit [Ctrl]+[c] to copy)</span>', url, url));
|
||||
$('#deletelink').html('<a href="' + deleteUrl + '">' + i18n._('Delete data') + '</a>');
|
||||
zerobin.pasteResult.removeClass('hidden');
|
||||
// We pre-select the link so that the user only has to [Ctrl]+[c] the link.
|
||||
helper.selectText('pasteurl');
|
||||
zerobin.showStatus('', false);
|
||||
|
||||
helper.setElementText(zerobin.clearText, zerobin.message.val());
|
||||
helper.setElementText(zerobin.prettyPrint, zerobin.message.val());
|
||||
zerobin.formatPaste(data_to_send.formatter);
|
||||
}
|
||||
else if (data.status==1)
|
||||
{
|
||||
zerobin.showError(i18n._('Could not create paste: %s', data.message));
|
||||
}
|
||||
else
|
||||
{
|
||||
zerobin.showError(i18n._('Could not create paste: %s', i18n._('unknown status')));
|
||||
}
|
||||
}
|
||||
})
|
||||
.fail(function()
|
||||
{
|
||||
if (data.status == 0) {
|
||||
zerobin.stateExistingPaste();
|
||||
var url = zerobin.scriptLocation() + '?' + data.id + '#' + randomkey;
|
||||
var deleteUrl = zerobin.scriptLocation() + '?pasteid=' + data.id + '&deletetoken=' + data.deletetoken;
|
||||
zerobin.showStatus('', false);
|
||||
zerobin.errorMessage.addClass('hidden');
|
||||
|
||||
$('#pastelink').html(i18n._('Your paste is <a id="pasteurl" href="%s">%s</a> <span id="copyhint">(Hit [Ctrl]+[c] to copy)</span>', url, url));
|
||||
$('#deletelink').html('<a href="' + deleteUrl + '">' + i18n._('Delete data') + '</a>');
|
||||
zerobin.pasteResult.removeClass('hidden');
|
||||
// We pre-select the link so that the user only has to [Ctrl]+[c] the link.
|
||||
helper.selectText('pasteurl');
|
||||
zerobin.showStatus('', false);
|
||||
|
||||
helper.setElementText(zerobin.clearText, zerobin.message.val());
|
||||
helper.setElementText(zerobin.prettyPrint, zerobin.message.val());
|
||||
zerobin.formatPaste(data_to_send.formatter);
|
||||
}
|
||||
else if (data.status==1)
|
||||
{
|
||||
zerobin.showError(i18n._('Could not create paste: %s', data.message));
|
||||
}
|
||||
else
|
||||
{
|
||||
zerobin.showError(i18n._('Could not create paste: %s', i18n._('unknown status')));
|
||||
}
|
||||
}, 'json')
|
||||
.fail(function() {
|
||||
zerobin.showError(i18n._('Could not create paste: %s', i18n._('server error or not responding')));
|
||||
});
|
||||
},
|
||||
@@ -1040,7 +1078,7 @@ $(function() {
|
||||
},
|
||||
|
||||
/**
|
||||
* Reload the page
|
||||
* Reload the page.
|
||||
*
|
||||
* @param Event event
|
||||
*/
|
||||
@@ -1051,7 +1089,7 @@ $(function() {
|
||||
},
|
||||
|
||||
/**
|
||||
* Return raw text
|
||||
* Return raw text.
|
||||
*
|
||||
* @param Event event
|
||||
*/
|
||||
@@ -1087,6 +1125,30 @@ $(function() {
|
||||
$('.navbar-toggle').click();
|
||||
},
|
||||
|
||||
/**
|
||||
* Support input of tab character.
|
||||
*
|
||||
* @param Event event
|
||||
*/
|
||||
supportTabs: function(event)
|
||||
{
|
||||
var keyCode = event.keyCode || event.which;
|
||||
// tab was pressed
|
||||
if (keyCode === 9)
|
||||
{
|
||||
// prevent the textarea to lose focus
|
||||
event.preventDefault();
|
||||
// get caret position & selection
|
||||
var val = this.value,
|
||||
start = this.selectionStart,
|
||||
end = this.selectionEnd;
|
||||
// set textarea value to: text before caret + tab + text after caret
|
||||
this.value = val.substring(0, start) + '\t' + val.substring(end);
|
||||
// put caret at right position again
|
||||
this.selectionStart = this.selectionEnd = start + 1;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a new paste.
|
||||
*/
|
||||
@@ -1170,6 +1232,7 @@ $(function() {
|
||||
this.rawTextButton.click($.proxy(this.rawText, this));
|
||||
this.fileRemoveButton.click($.proxy(this.removeAttachment, this));
|
||||
$('.reloadlink').click($.proxy(this.reloadPage, this));
|
||||
this.message.keydown(this.supportTabs);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -1235,12 +1298,12 @@ $(function() {
|
||||
}
|
||||
|
||||
// List of messages to display.
|
||||
var messages = $.parseJSON(this.cipherData.text());
|
||||
var data = $.parseJSON(this.cipherData.text());
|
||||
|
||||
// Show proper elements on screen.
|
||||
this.stateExistingPaste();
|
||||
|
||||
this.displayMessages(this.pageKey(), messages);
|
||||
this.displayMessages(this.pageKey(), data);
|
||||
}
|
||||
// Display error message from php code.
|
||||
else if (this.errorMessage.text().length > 1)
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* @link http://sebsauvage.net/wiki/doku.php?id=php:zerobin
|
||||
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
|
||||
* @license http://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
|
||||
* @version 0.21.1
|
||||
* @version 0.22
|
||||
*/
|
||||
|
||||
spl_autoload_register('auto::loader');
|
||||
|
||||
235
lib/configuration.php
Normal file
235
lib/configuration.php
Normal file
@@ -0,0 +1,235 @@
|
||||
<?php
|
||||
/**
|
||||
* ZeroBin
|
||||
*
|
||||
* a zero-knowledge paste bin
|
||||
*
|
||||
* @link http://sebsauvage.net/wiki/doku.php?id=php:zerobin
|
||||
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
|
||||
* @license http://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
|
||||
* @version 0.22
|
||||
*/
|
||||
|
||||
/**
|
||||
* configuration
|
||||
*
|
||||
* parses configuration file, ensures default values present
|
||||
*/
|
||||
class configuration
|
||||
{
|
||||
/**
|
||||
* parsed configuration
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $_configuration;
|
||||
|
||||
/**
|
||||
* default configuration
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $_defaults = array(
|
||||
'main' => array(
|
||||
'discussion' => true,
|
||||
'opendiscussion' => false,
|
||||
'password' => true,
|
||||
'fileupload' => false,
|
||||
'burnafterreadingselected' => false,
|
||||
'defaultformatter' => 'plaintext',
|
||||
'syntaxhighlightingtheme' => null,
|
||||
'sizelimit' => 2097152,
|
||||
'template' => 'bootstrap',
|
||||
'notice' => '',
|
||||
'base64version' => '2.1.9',
|
||||
'languageselection' => false,
|
||||
'languagedefault' => '',
|
||||
),
|
||||
'expire' => array(
|
||||
'default' => '1week',
|
||||
'clone' => true,
|
||||
),
|
||||
'expire_options' => array(
|
||||
'5min' => 300,
|
||||
'10min' => 600,
|
||||
'1hour' => 3600,
|
||||
'1day' => 86400,
|
||||
'1week' => 604800,
|
||||
'1month' => 2592000,
|
||||
'1year' => 31536000,
|
||||
'never' => 0,
|
||||
),
|
||||
'formatter_options' => array(
|
||||
'plaintext' => 'Plain Text',
|
||||
'syntaxhighlighting' => 'Source Code',
|
||||
'markdown' => 'Markdown',
|
||||
),
|
||||
'traffic' => array(
|
||||
'limit' => 10,
|
||||
'header' => null,
|
||||
'dir' => 'data',
|
||||
),
|
||||
'model' => array(
|
||||
'class' => 'zerobin_data',
|
||||
),
|
||||
'model_options' => array(
|
||||
'dir' => 'data',
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
* parse configuration file and ensure default configuration values are present
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$config = array();
|
||||
$configFile = PATH . 'cfg' . DIRECTORY_SEPARATOR . 'conf.ini';
|
||||
if (is_readable($configFile))
|
||||
{
|
||||
$config = parse_ini_file($configFile, true);
|
||||
foreach (array('main', 'model') as $section) {
|
||||
if (!array_key_exists($section, $config)) {
|
||||
throw new Exception(i18n::_('ZeroBin requires configuration section [%s] to be present in configuration file.', $section), 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
$opts = '_options';
|
||||
foreach ($this->_defaults as $section => $values)
|
||||
{
|
||||
// fill missing sections with default values
|
||||
if (!array_key_exists($section, $config) || count($config[$section]) == 0)
|
||||
{
|
||||
$this->_configuration[$section] = $values;
|
||||
if (array_key_exists('dir', $this->_configuration[$section]))
|
||||
{
|
||||
$this->_configuration[$section]['dir'] = PATH . $this->_configuration[$section]['dir'];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// provide different defaults for database model
|
||||
elseif ($section == 'model_options' && $this->_configuration['model']['class'] == 'zerobin_db')
|
||||
{
|
||||
$values = array(
|
||||
'dsn' => 'sqlite:' . PATH . 'data/db.sq3',
|
||||
'tbl' => null,
|
||||
'usr' => null,
|
||||
'pwd' => null,
|
||||
'opt' => array(PDO::ATTR_PERSISTENT => true),
|
||||
);
|
||||
}
|
||||
|
||||
// "*_options" sections don't require all defaults to be set
|
||||
if (
|
||||
$section !== 'model_options' &&
|
||||
($from = strlen($section) - strlen($opts)) >= 0 &&
|
||||
strpos($section, $opts, $from) !== false
|
||||
)
|
||||
{
|
||||
if (is_int(current($values)))
|
||||
{
|
||||
$config[$section] = array_map('intval', $config[$section]);
|
||||
}
|
||||
$this->_configuration[$section] = $config[$section];
|
||||
}
|
||||
// check for missing keys and set defaults if necessary
|
||||
else
|
||||
{
|
||||
foreach ($values as $key => $val)
|
||||
{
|
||||
if ($key == 'dir')
|
||||
{
|
||||
$val = PATH . $val;
|
||||
}
|
||||
$result = $val;
|
||||
if (array_key_exists($key, $config[$section]))
|
||||
{
|
||||
if ($val === null)
|
||||
{
|
||||
$result = $config[$section][$key];
|
||||
}
|
||||
elseif (is_bool($val))
|
||||
{
|
||||
$val = strtolower($config[$section][$key]);
|
||||
if (in_array($val, array('true', 'yes', 'on')))
|
||||
{
|
||||
$result = true;
|
||||
}
|
||||
elseif (in_array($val, array('false', 'no', 'off')))
|
||||
{
|
||||
$result = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
$result = (bool) $config[$section][$key];
|
||||
}
|
||||
}
|
||||
elseif (is_int($val))
|
||||
{
|
||||
$result = (int) $config[$section][$key];
|
||||
}
|
||||
elseif (is_string($val) && !empty($config[$section][$key]))
|
||||
{
|
||||
$result = (string) $config[$section][$key];
|
||||
}
|
||||
}
|
||||
$this->_configuration[$section][$key] = $result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ensure a valid expire default key is set
|
||||
if (!array_key_exists($this->_configuration['expire']['default'], $this->_configuration['expire_options']))
|
||||
{
|
||||
$this->_configuration['expire']['default'] = key($this->_configuration['expire_options']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get configuration as array
|
||||
*
|
||||
* return array
|
||||
*/
|
||||
public function get()
|
||||
{
|
||||
return $this->_configuration;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* get a key from the configuration, typically the main section or all keys
|
||||
*
|
||||
* @param string $key
|
||||
* @param string $section defaults to main
|
||||
* @throws Exception
|
||||
* return mixed
|
||||
*/
|
||||
public function getKey($key, $section = 'main')
|
||||
{
|
||||
$options = $this->getSection($section);
|
||||
if (!array_key_exists($key, $options))
|
||||
{
|
||||
throw new Exception(i18n::_('Invalid data.') . " $section / $key", 4);
|
||||
}
|
||||
return $this->_configuration[$section][$key];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* get a key from the configuration, typically the main section or all keys
|
||||
*
|
||||
* @param string $key if empty, return all configuration options
|
||||
* @param string $section defaults to main
|
||||
* @throws Exception
|
||||
* return mixed
|
||||
*/
|
||||
public function getSection($section)
|
||||
{
|
||||
if (!array_key_exists($section, $this->_configuration))
|
||||
{
|
||||
throw new Exception(i18n::_('ZeroBin requires configuration section [%s] to be present in configuration file.', $section), 3);
|
||||
}
|
||||
return $this->_configuration[$section];
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
* @link http://sebsauvage.net/wiki/doku.php?id=php:zerobin
|
||||
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
|
||||
* @license http://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
|
||||
* @version 0.21.1
|
||||
* @version 0.22
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -80,19 +80,6 @@ class filter
|
||||
return number_format($size, ($i ? 2 : 0), '.', ' ') . ' ' . i18n::_($iec[$i]);
|
||||
}
|
||||
|
||||
/**
|
||||
* validate paste ID
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
* @param string $dataid
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_valid_paste_id($dataid)
|
||||
{
|
||||
return (bool) preg_match('#\A[a-f\d]{16}\z#', $dataid);
|
||||
}
|
||||
|
||||
/**
|
||||
* fixed time string comparison operation to prevent timing attacks
|
||||
* https://crackstation.net/hashing-security.htm?=rd#slowequals
|
||||
|
||||
27
lib/i18n.php
27
lib/i18n.php
@@ -7,7 +7,7 @@
|
||||
* @link http://sebsauvage.net/wiki/doku.php?id=php:zerobin
|
||||
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
|
||||
* @license http://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
|
||||
* @version 0.21.1
|
||||
* @version 0.22
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -26,6 +26,15 @@ class i18n
|
||||
*/
|
||||
protected static $_language = 'en';
|
||||
|
||||
/**
|
||||
* language fallback
|
||||
*
|
||||
* @access protected
|
||||
* @static
|
||||
* @var string
|
||||
*/
|
||||
protected static $_languageFallback = 'en';
|
||||
|
||||
/**
|
||||
* language labels
|
||||
*
|
||||
@@ -248,6 +257,20 @@ class i18n
|
||||
return array_intersect_key(self::$_languageLabels, array_flip($languages));
|
||||
}
|
||||
|
||||
/**
|
||||
* set the default language
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
* @param string $lang
|
||||
* @return void
|
||||
*/
|
||||
public static function setLanguageFallback($lang)
|
||||
{
|
||||
if (in_array($lang, self::getAvailableLanguages()))
|
||||
self::$_languageFallback = $lang;
|
||||
}
|
||||
|
||||
/**
|
||||
* get language file path
|
||||
*
|
||||
@@ -339,7 +362,7 @@ class i18n
|
||||
}
|
||||
if (count($matches) === 0)
|
||||
{
|
||||
return 'en';
|
||||
return self::$_languageFallback;
|
||||
}
|
||||
krsort($matches);
|
||||
$topmatches = current($matches);
|
||||
|
||||
71
lib/model.php
Normal file
71
lib/model.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
/**
|
||||
* ZeroBin
|
||||
*
|
||||
* a zero-knowledge paste bin
|
||||
*
|
||||
* @link http://sebsauvage.net/wiki/doku.php?id=php:zerobin
|
||||
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
|
||||
* @license http://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
|
||||
* @version 0.22
|
||||
*/
|
||||
|
||||
/**
|
||||
* model
|
||||
*
|
||||
* Factory of ZeroBin instance models.
|
||||
*/
|
||||
class model
|
||||
{
|
||||
/**
|
||||
* Configuration.
|
||||
*
|
||||
* @var configuration
|
||||
*/
|
||||
private $_conf;
|
||||
|
||||
/**
|
||||
* Data storage.
|
||||
*
|
||||
* @var zerobin_abstract
|
||||
*/
|
||||
private $_store = null;
|
||||
|
||||
/**
|
||||
* Factory constructor.
|
||||
*
|
||||
* @param configuration $conf
|
||||
*/
|
||||
public function __construct(configuration $conf)
|
||||
{
|
||||
$this->_conf = $conf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a paste, optionally a specific instance.
|
||||
*
|
||||
* @param string $pasteId
|
||||
* @return model_paste
|
||||
*/
|
||||
public function getPaste($pasteId = null)
|
||||
{
|
||||
$paste = new model_paste($this->_conf, $this->_getStore());
|
||||
if ($pasteId !== null) $paste->setId($pasteId);
|
||||
return $paste;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets, and creates if neccessary, a store object
|
||||
*/
|
||||
private function _getStore()
|
||||
{
|
||||
if ($this->_store === null)
|
||||
{
|
||||
$this->_store = forward_static_call(
|
||||
array($this->_conf->getKey('class', 'model'), 'getInstance'),
|
||||
$this->_conf->getSection('model_options')
|
||||
);
|
||||
}
|
||||
return $this->_store;
|
||||
}
|
||||
}
|
||||
157
lib/model/abstract.php
Normal file
157
lib/model/abstract.php
Normal file
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
/**
|
||||
* ZeroBin
|
||||
*
|
||||
* a zero-knowledge paste bin
|
||||
*
|
||||
* @link http://sebsauvage.net/wiki/doku.php?id=php:zerobin
|
||||
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
|
||||
* @license http://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
|
||||
* @version 0.22
|
||||
*/
|
||||
|
||||
/**
|
||||
* model_abstract
|
||||
*
|
||||
* Abstract model for ZeroBin objects.
|
||||
*/
|
||||
abstract class model_abstract
|
||||
{
|
||||
/**
|
||||
* Instance ID.
|
||||
*
|
||||
* @access protected
|
||||
* @var string
|
||||
*/
|
||||
protected $_id = '';
|
||||
|
||||
/**
|
||||
* Instance data.
|
||||
*
|
||||
* @access protected
|
||||
* @var stdClass
|
||||
*/
|
||||
protected $_data;
|
||||
|
||||
/**
|
||||
* Configuration.
|
||||
*
|
||||
* @access protected
|
||||
* @var configuration
|
||||
*/
|
||||
protected $_conf;
|
||||
|
||||
/**
|
||||
* Data storage.
|
||||
*
|
||||
* @access protected
|
||||
* @var zerobin_abstract
|
||||
*/
|
||||
protected $_store;
|
||||
|
||||
/**
|
||||
* Instance constructor.
|
||||
*
|
||||
* @access public
|
||||
* @param configuration $configuration
|
||||
* @param zerobin_abstract $storage
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(configuration $configuration, zerobin_abstract $storage)
|
||||
{
|
||||
$this->_conf = $configuration;
|
||||
$this->_store = $storage;
|
||||
$this->_data = new stdClass;
|
||||
$this->_data->meta = new stdClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ID.
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set ID.
|
||||
*
|
||||
* @access public
|
||||
* @param string $id
|
||||
* @throws Exception
|
||||
* @return void
|
||||
*/
|
||||
public function setId($id)
|
||||
{
|
||||
if (!self::isValidId($id)) throw new Exception('Invalid paste ID.', 60);
|
||||
$this->_id = $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set data and recalculate ID.
|
||||
*
|
||||
* @access public
|
||||
* @param string $data
|
||||
* @throws Exception
|
||||
* @return void
|
||||
*/
|
||||
public function setData($data)
|
||||
{
|
||||
if (!sjcl::isValid($data)) throw new Exception('Invalid data.', 61);
|
||||
$this->_data->data = $data;
|
||||
|
||||
// We just want a small hash to avoid collisions:
|
||||
// Half-MD5 (64 bits) will do the trick
|
||||
$this->setId(substr(hash('md5', $data), 0, 16));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get instance data.
|
||||
*
|
||||
* @access public
|
||||
* @return stdObject
|
||||
*/
|
||||
abstract public function get();
|
||||
|
||||
/**
|
||||
* Store the instance's data.
|
||||
*
|
||||
* @access public
|
||||
* @throws Exception
|
||||
* @return void
|
||||
*/
|
||||
abstract public function store();
|
||||
|
||||
/**
|
||||
* Delete the current instance.
|
||||
*
|
||||
* @access public
|
||||
* @throws Exception
|
||||
* @return void
|
||||
*/
|
||||
abstract public function delete();
|
||||
|
||||
/**
|
||||
* Test if current instance exists in store.
|
||||
*
|
||||
* @access public
|
||||
* @return bool
|
||||
*/
|
||||
abstract public function exists();
|
||||
|
||||
/**
|
||||
* Validate ID.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
* @param string $id
|
||||
* @return bool
|
||||
*/
|
||||
public static function isValidId($id)
|
||||
{
|
||||
return (bool) preg_match('#\A[a-f\d]{16}\z#', (string) $id);
|
||||
}
|
||||
}
|
||||
189
lib/model/comment.php
Normal file
189
lib/model/comment.php
Normal file
@@ -0,0 +1,189 @@
|
||||
<?php
|
||||
/**
|
||||
* ZeroBin
|
||||
*
|
||||
* a zero-knowledge paste bin
|
||||
*
|
||||
* @link http://sebsauvage.net/wiki/doku.php?id=php:zerobin
|
||||
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
|
||||
* @license http://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
|
||||
* @version 0.22
|
||||
*/
|
||||
|
||||
/**
|
||||
* model_comment
|
||||
*
|
||||
* Model of a ZeroBin comment.
|
||||
*/
|
||||
class model_comment extends model_abstract
|
||||
{
|
||||
/**
|
||||
* Instance's parent.
|
||||
*
|
||||
* @access private
|
||||
* @var model_paste
|
||||
*/
|
||||
private $_paste;
|
||||
|
||||
/**
|
||||
* Get comment data.
|
||||
*
|
||||
* @access public
|
||||
* @throws Exception
|
||||
* @return stdObject
|
||||
*/
|
||||
public function get()
|
||||
{
|
||||
// @todo add support to read specific comment
|
||||
$comments = $this->_store->readComments($this->getPaste()->getId());
|
||||
foreach ($comments as $comment) {
|
||||
if (
|
||||
$comment->parentid == $this->getParentId() &&
|
||||
$comment->id == $this->getId()
|
||||
) {
|
||||
$this->_data = $comment;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $this->_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the comment's data.
|
||||
*
|
||||
* @access public
|
||||
* @throws Exception
|
||||
* @return void
|
||||
*/
|
||||
public function store()
|
||||
{
|
||||
// Make sure paste exists.
|
||||
$pasteid = $this->getPaste()->getId();
|
||||
if (!$this->getPaste()->exists())
|
||||
throw new Exception('Invalid data.', 67);
|
||||
|
||||
// Make sure the discussion is opened in this paste and in configuration.
|
||||
if (!$this->getPaste()->isOpendiscussion() || !$this->_conf->getKey('discussion'))
|
||||
throw new Exception('Invalid data.', 68);
|
||||
|
||||
// Check for improbable collision.
|
||||
if ($this->exists())
|
||||
throw new Exception('You are unlucky. Try again.', 69);
|
||||
|
||||
$this->_data->meta->postdate = time();
|
||||
|
||||
// store comment
|
||||
if (
|
||||
$this->_store->createComment(
|
||||
$pasteid,
|
||||
$this->getParentId(),
|
||||
$this->getId(),
|
||||
json_decode(json_encode($this->_data), true)
|
||||
) === false
|
||||
) throw new Exception('Error saving comment. Sorry.', 70);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the comment.
|
||||
*
|
||||
* @access public
|
||||
* @throws Exception
|
||||
* @return void
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
throw new Exception('To delete a comment, delete its parent paste', 64);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if comment exists in store.
|
||||
*
|
||||
* @access public
|
||||
* @return bool
|
||||
*/
|
||||
public function exists()
|
||||
{
|
||||
return $this->_store->existsComment(
|
||||
$this->getPaste()->getId(),
|
||||
$this->getParentId(),
|
||||
$this->getId()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set paste.
|
||||
*
|
||||
* @access public
|
||||
* @param model_paste $paste
|
||||
* @throws Exception
|
||||
* @return void
|
||||
*/
|
||||
public function setPaste(model_paste $paste)
|
||||
{
|
||||
$this->_paste = $paste;
|
||||
$this->_data->meta->pasteid = $paste->getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get paste.
|
||||
*
|
||||
* @access public
|
||||
* @return model_paste
|
||||
*/
|
||||
public function getPaste()
|
||||
{
|
||||
return $this->_paste;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set parent ID.
|
||||
*
|
||||
* @access public
|
||||
* @param string $id
|
||||
* @throws Exception
|
||||
* @return void
|
||||
*/
|
||||
public function setParentId($id)
|
||||
{
|
||||
if (!self::isValidId($id)) throw new Exception('Invalid paste ID.', 65);
|
||||
$this->_data->meta->parentid = $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get parent ID.
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getParentId()
|
||||
{
|
||||
if (!property_exists($this->_data->meta, 'parentid')) $this->_data->meta->parentid = '';
|
||||
return $this->_data->meta->parentid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set nickname.
|
||||
*
|
||||
* @access public
|
||||
* @param string $nickname
|
||||
* @throws Exception
|
||||
* @return void
|
||||
*/
|
||||
public function setNickname($nickname)
|
||||
{
|
||||
if (!sjcl::isValid($nickname)) throw new Exception('Invalid data.', 66);
|
||||
$this->_data->meta->nickname = $nickname;
|
||||
|
||||
// Generation of the anonymous avatar (Vizhash):
|
||||
// If a nickname is provided, we generate a Vizhash.
|
||||
// (We assume that if the user did not enter a nickname, he/she wants
|
||||
// to be anonymous and we will not generate the vizhash.)
|
||||
$vh = new vizhash16x16();
|
||||
$pngdata = $vh->generate(trafficlimiter::getIp());
|
||||
if ($pngdata != '')
|
||||
{
|
||||
$this->_data->meta->vizhash = 'data:image/png;base64,' . base64_encode($pngdata);
|
||||
}
|
||||
// Once the avatar is generated, we do not keep the IP address, nor its hash.
|
||||
}
|
||||
}
|
||||
303
lib/model/paste.php
Normal file
303
lib/model/paste.php
Normal file
@@ -0,0 +1,303 @@
|
||||
<?php
|
||||
/**
|
||||
* ZeroBin
|
||||
*
|
||||
* a zero-knowledge paste bin
|
||||
*
|
||||
* @link http://sebsauvage.net/wiki/doku.php?id=php:zerobin
|
||||
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
|
||||
* @license http://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
|
||||
* @version 0.22
|
||||
*/
|
||||
|
||||
/**
|
||||
* model_paste
|
||||
*
|
||||
* Model of a ZeroBin paste.
|
||||
*/
|
||||
class model_paste extends model_abstract
|
||||
{
|
||||
/**
|
||||
* Get paste data.
|
||||
*
|
||||
* @access public
|
||||
* @throws Exception
|
||||
* @return stdObject
|
||||
*/
|
||||
public function get()
|
||||
{
|
||||
$this->_data = $this->_store->read($this->getId());
|
||||
// See if paste has expired and delete it if neccessary.
|
||||
if (property_exists($this->_data->meta, 'expire_date'))
|
||||
{
|
||||
if ($this->_data->meta->expire_date < time())
|
||||
{
|
||||
$this->delete();
|
||||
throw new Exception(zerobin::GENERIC_ERROR, 63);
|
||||
}
|
||||
// We kindly provide the remaining time before expiration (in seconds)
|
||||
$this->_data->meta->remaining_time = $this->_data->meta->expire_date - time();
|
||||
}
|
||||
|
||||
// set formatter for for the view.
|
||||
if (!property_exists($this->_data->meta, 'formatter'))
|
||||
{
|
||||
// support < 0.21 syntax highlighting
|
||||
if (property_exists($this->_data->meta, 'syntaxcoloring') && $this->_data->meta->syntaxcoloring === true)
|
||||
{
|
||||
$this->_data->meta->formatter = 'syntaxhighlighting';
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->_data->meta->formatter = $this->_conf->getKey('defaultformatter');
|
||||
}
|
||||
}
|
||||
$this->_data->comments = array_values($this->getComments());
|
||||
$this->_data->comment_count = count($this->_data->comments);
|
||||
$this->_data->comment_offset = 0;
|
||||
$this->_data->{'@context'} = 'js/paste.jsonld';
|
||||
return $this->_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the paste's data.
|
||||
*
|
||||
* @access public
|
||||
* @throws Exception
|
||||
* @return void
|
||||
*/
|
||||
public function store()
|
||||
{
|
||||
// Check for improbable collision.
|
||||
if ($this->exists())
|
||||
throw new Exception('You are unlucky. Try again.', 75);
|
||||
|
||||
$this->_data->meta->postdate = time();
|
||||
|
||||
// store paste
|
||||
if (
|
||||
$this->_store->create(
|
||||
$this->getId(),
|
||||
json_decode(json_encode($this->_data), true)
|
||||
) === false
|
||||
) throw new Exception('Error saving paste. Sorry.', 76);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the paste.
|
||||
*
|
||||
* @access public
|
||||
* @throws Exception
|
||||
* @return void
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
$this->_store->delete($this->getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if paste exists in store.
|
||||
*
|
||||
* @access public
|
||||
* @return bool
|
||||
*/
|
||||
public function exists()
|
||||
{
|
||||
return $this->_store->exists($this->getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a comment, optionally a specific instance.
|
||||
*
|
||||
* @access public
|
||||
* @param string $parentId
|
||||
* @param string $commentId
|
||||
* @throws Exception
|
||||
* @return model_comment
|
||||
*/
|
||||
public function getComment($parentId, $commentId = null)
|
||||
{
|
||||
if (!$this->exists())
|
||||
{
|
||||
throw new Exception('Invalid data.', 62);
|
||||
}
|
||||
$comment = new model_comment($this->_conf, $this->_store);
|
||||
$comment->setPaste($this);
|
||||
$comment->setParentId($parentId);
|
||||
if ($commentId !== null) $comment->setId($commentId);
|
||||
return $comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all comments, if any.
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getComments()
|
||||
{
|
||||
return $this->_store->readComments($this->getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the "delete" token.
|
||||
*
|
||||
* The token is the hmac of the pastes ID signed with the server salt.
|
||||
* The paste can be deleted by calling:
|
||||
* http://example.com/zerobin/?pasteid=<pasteid>&deletetoken=<deletetoken>
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getDeleteToken()
|
||||
{
|
||||
return hash_hmac('sha1', $this->getId(), serversalt::get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set paste's attachment.
|
||||
*
|
||||
* @access public
|
||||
* @param string $attachment
|
||||
* @throws Exception
|
||||
* @return void
|
||||
*/
|
||||
public function setAttachment($attachment)
|
||||
{
|
||||
if (!$this->_conf->getKey('fileupload') || !sjcl::isValid($attachment))
|
||||
throw new Exception('Invalid attachment.', 71);
|
||||
$this->_data->meta->attachment = $attachment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set paste's attachment name.
|
||||
*
|
||||
* @access public
|
||||
* @param string $attachmentname
|
||||
* @throws Exception
|
||||
* @return void
|
||||
*/
|
||||
public function setAttachmentName($attachmentname)
|
||||
{
|
||||
if (!$this->_conf->getKey('fileupload') || !sjcl::isValid($attachmentname))
|
||||
throw new Exception('Invalid attachment.', 72);
|
||||
$this->_data->meta->attachmentname = $attachmentname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set paste expiration.
|
||||
*
|
||||
* @access public
|
||||
* @param string $expiration
|
||||
* @return void
|
||||
*/
|
||||
public function setExpiration($expiration)
|
||||
{
|
||||
$expire_options = $this->_conf->getSection('expire_options');
|
||||
if (array_key_exists($expiration, $expire_options))
|
||||
{
|
||||
$expire = $expire_options[$expiration];
|
||||
}
|
||||
else
|
||||
{
|
||||
// using getKey() to ensure a default value is present
|
||||
$expire = $this->_conf->getKey($this->_conf->getKey('default', 'expire'), 'expire_options');
|
||||
}
|
||||
if ($expire > 0) $this->_data->meta->expire_date = time() + $expire;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set paste's burn-after-reading type.
|
||||
*
|
||||
* @access public
|
||||
* @param string $burnafterreading
|
||||
* @throws Exception
|
||||
* @return void
|
||||
*/
|
||||
public function setBurnafterreading($burnafterreading = '1')
|
||||
{
|
||||
if ($burnafterreading === '0')
|
||||
{
|
||||
$this->_data->meta->burnafterreading = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($burnafterreading !== '1')
|
||||
throw new Exception('Invalid data.', 73);
|
||||
$this->_data->meta->burnafterreading = true;
|
||||
$this->_data->meta->opendiscussion = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set paste's discussion state.
|
||||
*
|
||||
* @access public
|
||||
* @param string $opendiscussion
|
||||
* @throws Exception
|
||||
* @return void
|
||||
*/
|
||||
public function setOpendiscussion($opendiscussion = '1')
|
||||
{
|
||||
if (
|
||||
!$this->_conf->getKey('discussion') ||
|
||||
$this->isBurnafterreading() ||
|
||||
$opendiscussion === '0'
|
||||
)
|
||||
{
|
||||
$this->_data->meta->opendiscussion = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($opendiscussion !== '1')
|
||||
throw new Exception('Invalid data.', 74);
|
||||
$this->_data->meta->opendiscussion = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set paste's format.
|
||||
*
|
||||
* @access public
|
||||
* @param string $format
|
||||
* @throws Exception
|
||||
* @return void
|
||||
*/
|
||||
public function setFormatter($format)
|
||||
{
|
||||
if (!array_key_exists($format, $this->_conf->getSection('formatter_options')))
|
||||
{
|
||||
$format = $this->_conf->getKey('defaultformatter');
|
||||
}
|
||||
$this->_data->meta->formatter = $format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if paste is of burn-after-reading type.
|
||||
*
|
||||
* @access public
|
||||
* @throws Exception
|
||||
* @return boolean
|
||||
*/
|
||||
public function isBurnafterreading()
|
||||
{
|
||||
if (!property_exists($this->_data, 'data')) $this->get();
|
||||
return property_exists($this->_data->meta, 'burnafterreading') &&
|
||||
$this->_data->meta->burnafterreading === true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if paste has discussions enabled.
|
||||
*
|
||||
* @access public
|
||||
* @throws Exception
|
||||
* @return boolean
|
||||
*/
|
||||
public function isOpendiscussion()
|
||||
{
|
||||
if (!property_exists($this->_data, 'data')) $this->get();
|
||||
return property_exists($this->_data->meta, 'opendiscussion') &&
|
||||
$this->_data->meta->opendiscussion === true;
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
* @link http://sebsauvage.net/wiki/doku.php?id=php:zerobin
|
||||
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
|
||||
* @license http://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
|
||||
* @version 0.21.1
|
||||
* @version 0.22
|
||||
*/
|
||||
|
||||
/**
|
||||
|
||||
171
lib/request.php
Normal file
171
lib/request.php
Normal file
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
/**
|
||||
* ZeroBin
|
||||
*
|
||||
* a zero-knowledge paste bin
|
||||
*
|
||||
* @link http://sebsauvage.net/wiki/doku.php?id=php:zerobin
|
||||
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
|
||||
* @license http://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
|
||||
* @version 0.22
|
||||
*/
|
||||
|
||||
/**
|
||||
* request
|
||||
*
|
||||
* parses request parameters and provides helper functions for routing
|
||||
*/
|
||||
class request
|
||||
{
|
||||
/**
|
||||
* Input stream to use for PUT parameter parsing.
|
||||
*
|
||||
* @access private
|
||||
* @var string
|
||||
*/
|
||||
private static $_inputStream = 'php://input';
|
||||
|
||||
/**
|
||||
* Operation to perform.
|
||||
*
|
||||
* @access private
|
||||
* @var string
|
||||
*/
|
||||
private $_operation = 'view';
|
||||
|
||||
/**
|
||||
* Request parameters.
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private $_params = array();
|
||||
|
||||
/**
|
||||
* If we are in a JSON API context.
|
||||
*
|
||||
* @access private
|
||||
* @var bool
|
||||
*/
|
||||
private $_isJsonApi = false;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
// in case stupid admin has left magic_quotes enabled in php.ini (for PHP < 5.4)
|
||||
if (function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc())
|
||||
{
|
||||
$_POST = array_map('filter::stripslashes_deep', $_POST);
|
||||
$_GET = array_map('filter::stripslashes_deep', $_GET);
|
||||
$_COOKIE = array_map('filter::stripslashes_deep', $_COOKIE);
|
||||
}
|
||||
|
||||
// decide if we are in JSON API or HTML context
|
||||
if (
|
||||
(array_key_exists('HTTP_X_REQUESTED_WITH', $_SERVER) &&
|
||||
$_SERVER['HTTP_X_REQUESTED_WITH'] == 'JSONHttpRequest') ||
|
||||
(array_key_exists('HTTP_ACCEPT', $_SERVER) &&
|
||||
strpos($_SERVER['HTTP_ACCEPT'], 'application/json') !== false)
|
||||
)
|
||||
{
|
||||
$this->_isJsonApi = true;
|
||||
}
|
||||
|
||||
// parse parameters, depending on request type
|
||||
switch (array_key_exists('REQUEST_METHOD', $_SERVER) ? $_SERVER['REQUEST_METHOD'] : 'GET')
|
||||
{
|
||||
case 'DELETE':
|
||||
case 'PUT':
|
||||
parse_str(file_get_contents(self::$_inputStream), $this->_params);
|
||||
break;
|
||||
case 'POST':
|
||||
$this->_params = $_POST;
|
||||
break;
|
||||
default:
|
||||
$this->_params = $_GET;
|
||||
}
|
||||
if (
|
||||
!array_key_exists('pasteid', $this->_params) &&
|
||||
!array_key_exists('jsonld', $this->_params) &&
|
||||
array_key_exists('QUERY_STRING', $_SERVER) &&
|
||||
!empty($_SERVER['QUERY_STRING'])
|
||||
)
|
||||
{
|
||||
$this->_params['pasteid'] = $_SERVER['QUERY_STRING'];
|
||||
}
|
||||
|
||||
// prepare operation, depending on current parameters
|
||||
if (
|
||||
(array_key_exists('data', $this->_params) && !empty($this->_params['data'])) ||
|
||||
(array_key_exists('attachment', $this->_params) && !empty($this->_params['attachment']))
|
||||
)
|
||||
{
|
||||
$this->_operation = 'create';
|
||||
}
|
||||
elseif (array_key_exists('pasteid', $this->_params) && !empty($this->_params['pasteid']))
|
||||
{
|
||||
if (array_key_exists('deletetoken', $this->_params) && !empty($this->_params['deletetoken']))
|
||||
{
|
||||
$this->_operation = 'delete';
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->_operation = 'read';
|
||||
}
|
||||
}
|
||||
elseif (array_key_exists('jsonld', $this->_params) && !empty($this->_params['jsonld']))
|
||||
{
|
||||
$this->_operation = 'jsonld';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current operation.
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getOperation()
|
||||
{
|
||||
return $this->_operation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a request parameter.
|
||||
*
|
||||
* @access public
|
||||
* @param string $param
|
||||
* @param string $default
|
||||
* @return string
|
||||
*/
|
||||
public function getParam($param, $default = '')
|
||||
{
|
||||
return array_key_exists($param, $this->_params) ? $this->_params[$param] : $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* If we are in a JSON API context.
|
||||
*
|
||||
* @access public
|
||||
* @return bool
|
||||
*/
|
||||
public function isJsonApiCall()
|
||||
{
|
||||
return $this->_isJsonApi;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the default input stream source, used for unit testing.
|
||||
*
|
||||
* @param string $input
|
||||
*/
|
||||
public static function setInputStream($input)
|
||||
{
|
||||
self::$_inputStream = $input;
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
* @link http://sebsauvage.net/wiki/doku.php?id=php:zerobin
|
||||
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
|
||||
* @license http://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
|
||||
* @version 0.21.1
|
||||
* @version 0.22
|
||||
*/
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* @link http://sebsauvage.net/wiki/doku.php?id=php:zerobin
|
||||
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
|
||||
* @license http://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
|
||||
* @version 0.21.1
|
||||
* @version 0.22
|
||||
*/
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* @link http://sebsauvage.net/wiki/doku.php?id=php:zerobin
|
||||
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
|
||||
* @license http://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
|
||||
* @version 0.21.1
|
||||
* @version 0.22
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -26,6 +26,15 @@ class trafficlimiter extends persistence
|
||||
*/
|
||||
private static $_limit = 10;
|
||||
|
||||
/**
|
||||
* key to fetch IP address
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
* @var string
|
||||
*/
|
||||
private static $_ipKey = 'REMOTE_ADDR';
|
||||
|
||||
/**
|
||||
* set the time limit in seconds
|
||||
*
|
||||
@@ -39,6 +48,40 @@ class trafficlimiter extends persistence
|
||||
self::$_limit = $limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* set configuration options of the traffic limiter
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
* @param configuration $conf
|
||||
* @return void
|
||||
*/
|
||||
public static function setConfiguration(configuration $conf)
|
||||
{
|
||||
self::setLimit($conf->getKey('limit', 'traffic'));
|
||||
self::setPath($conf->getKey('dir', 'traffic'));
|
||||
if (($option = $conf->getKey('header', 'traffic')) !== null)
|
||||
{
|
||||
$httpHeader = 'HTTP_' . $option;
|
||||
if (array_key_exists($httpHeader, $_SERVER) && !empty($_SERVER[$httpHeader]))
|
||||
{
|
||||
self::$_ipKey = $httpHeader;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get the current visitors IP address
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
* @return string
|
||||
*/
|
||||
public static function getIp()
|
||||
{
|
||||
return $_SERVER[self::$_ipKey];
|
||||
}
|
||||
|
||||
/**
|
||||
* traffic limiter
|
||||
*
|
||||
@@ -46,14 +89,15 @@ class trafficlimiter extends persistence
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
* @param string $ip
|
||||
* @throws Exception
|
||||
* @return bool
|
||||
*/
|
||||
public static function canPass($ip)
|
||||
public static function canPass()
|
||||
{
|
||||
// disable limits if set to less then 1
|
||||
if (self::$_limit < 1) return true;
|
||||
$ip = self::getIp();
|
||||
|
||||
// disable limits if set to less then 1
|
||||
if (self::$_limit < 1) return true;
|
||||
|
||||
$file = 'traffic_limiter.php';
|
||||
if (!self::_exists($file))
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
* @link http://sebsauvage.net/wiki/doku.php?id=php:vizhash_gd
|
||||
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
|
||||
* @license http://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
|
||||
* @version 0.0.4 beta ZeroBin 0.21.1
|
||||
* @version 0.0.4 beta ZeroBin 0.22
|
||||
*/
|
||||
|
||||
/**
|
||||
|
||||
596
lib/zerobin.php
596
lib/zerobin.php
@@ -7,7 +7,7 @@
|
||||
* @link http://sebsauvage.net/wiki/doku.php?id=php:zerobin
|
||||
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
|
||||
* @license http://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
|
||||
* @version 0.21.1
|
||||
* @version 0.22
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -22,7 +22,7 @@ class zerobin
|
||||
*
|
||||
* @const string
|
||||
*/
|
||||
const VERSION = '0.21.1';
|
||||
const VERSION = '0.22';
|
||||
|
||||
/**
|
||||
* show the same error message if the paste expired or does not exist
|
||||
@@ -32,14 +32,12 @@ class zerobin
|
||||
const GENERIC_ERROR = 'Paste does not exist, has expired or has been deleted.';
|
||||
|
||||
/**
|
||||
* configuration array
|
||||
* configuration
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
* @var configuration
|
||||
*/
|
||||
private $_conf = array(
|
||||
'model' => 'zerobin_data',
|
||||
);
|
||||
private $_conf;
|
||||
|
||||
/**
|
||||
* data
|
||||
@@ -49,6 +47,14 @@ class zerobin
|
||||
*/
|
||||
private $_data = '';
|
||||
|
||||
/**
|
||||
* does the paste expire
|
||||
*
|
||||
* @access private
|
||||
* @var bool
|
||||
*/
|
||||
private $_doesExpire = false;
|
||||
|
||||
/**
|
||||
* formatter
|
||||
*
|
||||
@@ -82,13 +88,29 @@ class zerobin
|
||||
private $_json = '';
|
||||
|
||||
/**
|
||||
* data storage model
|
||||
* Factory of instance models
|
||||
*
|
||||
* @access private
|
||||
* @var zerobin_abstract
|
||||
* @var model
|
||||
*/
|
||||
private $_model;
|
||||
|
||||
/**
|
||||
* request
|
||||
*
|
||||
* @access private
|
||||
* @var request
|
||||
*/
|
||||
private $_request;
|
||||
|
||||
/**
|
||||
* URL base
|
||||
*
|
||||
* @access private
|
||||
* @var string
|
||||
*/
|
||||
private $_urlbase;
|
||||
|
||||
/**
|
||||
* constructor
|
||||
*
|
||||
@@ -104,40 +126,35 @@ class zerobin
|
||||
throw new Exception(i18n::_('ZeroBin requires php 5.2.6 or above to work. Sorry.'), 1);
|
||||
}
|
||||
|
||||
// in case stupid admin has left magic_quotes enabled in php.ini
|
||||
if (get_magic_quotes_gpc())
|
||||
{
|
||||
$_POST = array_map('filter::stripslashes_deep', $_POST);
|
||||
$_GET = array_map('filter::stripslashes_deep', $_GET);
|
||||
$_COOKIE = array_map('filter::stripslashes_deep', $_COOKIE);
|
||||
}
|
||||
|
||||
// load config from ini file
|
||||
$this->_init();
|
||||
|
||||
// create new paste or comment
|
||||
if (
|
||||
(array_key_exists('data', $_POST) && !empty($_POST['data'])) ||
|
||||
(array_key_exists('attachment', $_POST) && !empty($_POST['attachment']))
|
||||
)
|
||||
switch ($this->_request->getOperation())
|
||||
{
|
||||
$this->_create();
|
||||
}
|
||||
// delete an existing paste
|
||||
elseif (!empty($_GET['deletetoken']) && !empty($_GET['pasteid']))
|
||||
{
|
||||
$this->_delete($_GET['pasteid'], $_GET['deletetoken']);
|
||||
}
|
||||
// display an existing paste
|
||||
elseif (!empty($_SERVER['QUERY_STRING']))
|
||||
{
|
||||
$this->_read($_SERVER['QUERY_STRING']);
|
||||
case 'create':
|
||||
$this->_create();
|
||||
break;
|
||||
case 'delete':
|
||||
$this->_delete(
|
||||
$this->_request->getParam('pasteid'),
|
||||
$this->_request->getParam('deletetoken')
|
||||
);
|
||||
break;
|
||||
case 'read':
|
||||
$this->_read($this->_request->getParam('pasteid'));
|
||||
break;
|
||||
case 'jsonld':
|
||||
$this->_jsonld($this->_request->getParam('jsonld'));
|
||||
return;
|
||||
}
|
||||
|
||||
// output JSON or HTML
|
||||
if (strlen($this->_json))
|
||||
if ($this->_request->isJsonApiCall())
|
||||
{
|
||||
header('Content-type: application/json');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE');
|
||||
header('Access-Control-Allow-Headers: X-Requested-With, Content-Type');
|
||||
echo $this->_json;
|
||||
}
|
||||
else
|
||||
@@ -164,31 +181,20 @@ class zerobin
|
||||
);
|
||||
}
|
||||
|
||||
$this->_conf = parse_ini_file(PATH . 'cfg' . DIRECTORY_SEPARATOR . 'conf.ini', true);
|
||||
foreach (array('main', 'model') as $section) {
|
||||
if (!array_key_exists($section, $this->_conf)) {
|
||||
throw new Exception(i18n::_('ZeroBin requires configuration section [%s] to be present in configuration file.', $section), 2);
|
||||
}
|
||||
}
|
||||
$this->_model = $this->_conf['model']['class'];
|
||||
}
|
||||
$this->_conf = new configuration;
|
||||
$this->_model = new model($this->_conf);
|
||||
$this->_request = new request;
|
||||
$this->_urlbase = array_key_exists('REQUEST_URI', $_SERVER) ? $_SERVER['REQUEST_URI'] : '/';
|
||||
|
||||
/**
|
||||
* get the model, create one if needed
|
||||
*
|
||||
* @access private
|
||||
* @return zerobin_abstract
|
||||
*/
|
||||
private function _model()
|
||||
{
|
||||
// if needed, initialize the model
|
||||
if(is_string($this->_model)) {
|
||||
$this->_model = forward_static_call(
|
||||
array($this->_model, 'getInstance'),
|
||||
$this->_conf['model_options']
|
||||
);
|
||||
// set default language
|
||||
$lang = $this->_conf->getKey('languagedefault');
|
||||
i18n::setLanguageFallback($lang);
|
||||
// force default language, if language selection is disabled and a default is set
|
||||
if (!$this->_conf->getKey('languageselection') && strlen($lang) == 2)
|
||||
{
|
||||
$_COOKIE['lang'] = $lang;
|
||||
setcookie('lang', $lang);
|
||||
}
|
||||
return $this->_model;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -215,34 +221,21 @@ class zerobin
|
||||
{
|
||||
$error = false;
|
||||
|
||||
$has_attachment = array_key_exists('attachment', $_POST);
|
||||
$has_attachmentname = $has_attachment && array_key_exists('attachmentname', $_POST) && !empty($_POST['attachmentname']);
|
||||
$data = array_key_exists('data', $_POST) ? $_POST['data'] : '';
|
||||
$attachment = $has_attachment ? $_POST['attachment'] : '';
|
||||
$attachmentname = $has_attachmentname ? $_POST['attachmentname'] : '';
|
||||
|
||||
// Make sure last paste from the IP address was more than X seconds ago.
|
||||
trafficlimiter::setLimit($this->_conf['traffic']['limit']);
|
||||
trafficlimiter::setPath($this->_conf['traffic']['dir']);
|
||||
$ipKey = 'REMOTE_ADDR';
|
||||
if (array_key_exists('header', $this->_conf['traffic']))
|
||||
{
|
||||
$header = 'HTTP_' . $this->_conf['traffic']['header'];
|
||||
if (array_key_exists($header, $_SERVER) && !empty($_SERVER[$header]))
|
||||
{
|
||||
$ipKey = $header;
|
||||
}
|
||||
}
|
||||
if (!trafficlimiter::canPass($_SERVER[$ipKey])) return $this->_return_message(
|
||||
1,
|
||||
i18n::_(
|
||||
// Ensure last paste from visitors IP address was more than configured amount of seconds ago.
|
||||
trafficlimiter::setConfiguration($this->_conf);
|
||||
if (!trafficlimiter::canPass()) return $this->_return_message(
|
||||
1, i18n::_(
|
||||
'Please wait %d seconds between each post.',
|
||||
$this->_conf['traffic']['limit']
|
||||
$this->_conf->getKey('limit', 'traffic')
|
||||
)
|
||||
);
|
||||
|
||||
// Make sure content is not too big.
|
||||
$sizelimit = (int) $this->_getMainConfig('sizelimit', 2097152);
|
||||
$data = $this->_request->getParam('data');
|
||||
$attachment = $this->_request->getParam('attachment');
|
||||
$attachmentname = $this->_request->getParam('attachmentname');
|
||||
|
||||
// Ensure content is not too big.
|
||||
$sizelimit = $this->_conf->getKey('sizelimit');
|
||||
if (
|
||||
strlen($data) + strlen($attachment) + strlen($attachmentname) > $sizelimit
|
||||
) return $this->_return_message(
|
||||
@@ -253,181 +246,62 @@ class zerobin
|
||||
)
|
||||
);
|
||||
|
||||
// Make sure format is correct.
|
||||
if (!sjcl::isValid($data)) return $this->_return_message(1, 'Invalid data.');
|
||||
|
||||
// Make sure attachments are enabled and format is correct.
|
||||
if($has_attachment)
|
||||
{
|
||||
if (
|
||||
!$this->_getMainConfig('fileupload', false) ||
|
||||
!sjcl::isValid($attachment) ||
|
||||
!($has_attachmentname && sjcl::isValid($attachmentname))
|
||||
) return $this->_return_message(1, 'Invalid attachment.');
|
||||
}
|
||||
|
||||
// Read additional meta-information.
|
||||
$meta = array();
|
||||
|
||||
// Read expiration date
|
||||
if (array_key_exists('expire', $_POST) && !empty($_POST['expire']))
|
||||
{
|
||||
$selected_expire = (string) $_POST['expire'];
|
||||
if (array_key_exists($selected_expire, $this->_conf['expire_options']))
|
||||
{
|
||||
$expire = $this->_conf['expire_options'][$selected_expire];
|
||||
}
|
||||
else
|
||||
{
|
||||
$expire = $this->_conf['expire_options'][$this->_conf['expire']['default']];
|
||||
}
|
||||
if ($expire > 0) $meta['expire_date'] = time() + $expire;
|
||||
}
|
||||
|
||||
// Destroy the paste when it is read.
|
||||
if (array_key_exists('burnafterreading', $_POST) && !empty($_POST['burnafterreading']))
|
||||
{
|
||||
$burnafterreading = $_POST['burnafterreading'];
|
||||
if ($burnafterreading !== '0')
|
||||
{
|
||||
if ($burnafterreading !== '1') $error = true;
|
||||
$meta['burnafterreading'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Read open discussion flag.
|
||||
if (
|
||||
$this->_getMainConfig('discussion', true) &&
|
||||
array_key_exists('opendiscussion', $_POST) &&
|
||||
!empty($_POST['opendiscussion'])
|
||||
)
|
||||
{
|
||||
$opendiscussion = $_POST['opendiscussion'];
|
||||
if ($opendiscussion !== '0')
|
||||
{
|
||||
if ($opendiscussion !== '1') $error = true;
|
||||
$meta['opendiscussion'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Read formatter flag.
|
||||
if (array_key_exists('formatter', $_POST) && !empty($_POST['formatter']))
|
||||
{
|
||||
$formatter = $_POST['formatter'];
|
||||
if (!array_key_exists($formatter, $this->_conf['formatter_options']))
|
||||
{
|
||||
$formatter = $this->_getMainConfig('defaultformatter', 'plaintext');
|
||||
}
|
||||
$meta['formatter'] = $formatter;
|
||||
}
|
||||
|
||||
// You can't have an open discussion on a "Burn after reading" paste:
|
||||
if (isset($meta['burnafterreading'])) unset($meta['opendiscussion']);
|
||||
|
||||
// Optional nickname for comments
|
||||
if (!empty($_POST['nickname']))
|
||||
{
|
||||
// Generation of the anonymous avatar (Vizhash):
|
||||
// If a nickname is provided, we generate a Vizhash.
|
||||
// (We assume that if the user did not enter a nickname, he/she wants
|
||||
// to be anonymous and we will not generate the vizhash.)
|
||||
$nick = $_POST['nickname'];
|
||||
if (!sjcl::isValid($nick))
|
||||
{
|
||||
$error = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
$meta['nickname'] = $nick;
|
||||
$vz = new vizhash16x16();
|
||||
$pngdata = $vz->generate($_SERVER['REMOTE_ADDR']);
|
||||
if ($pngdata != '')
|
||||
{
|
||||
$meta['vizhash'] = 'data:image/png;base64,' . base64_encode($pngdata);
|
||||
}
|
||||
// Once the avatar is generated, we do not keep the IP address, nor its hash.
|
||||
}
|
||||
}
|
||||
|
||||
if ($error) return $this->_return_message(1, 'Invalid data.');
|
||||
|
||||
// Add post date to meta.
|
||||
$meta['postdate'] = time();
|
||||
|
||||
// We just want a small hash to avoid collisions:
|
||||
// Half-MD5 (64 bits) will do the trick
|
||||
$dataid = substr(hash('md5', $data), 0, 16);
|
||||
|
||||
$storage = array('data' => $data);
|
||||
|
||||
// Add meta-information only if necessary.
|
||||
if (count($meta)) $storage['meta'] = $meta;
|
||||
|
||||
// The user posts a comment.
|
||||
if (
|
||||
!empty($_POST['parentid']) &&
|
||||
!empty($_POST['pasteid'])
|
||||
)
|
||||
$pasteid = $this->_request->getParam('pasteid');
|
||||
$parentid = $this->_request->getParam('parentid');
|
||||
if (!empty($pasteid) && !empty($parentid))
|
||||
{
|
||||
$pasteid = (string) $_POST['pasteid'];
|
||||
$parentid = (string) $_POST['parentid'];
|
||||
if (
|
||||
!filter::is_valid_paste_id($pasteid) ||
|
||||
!filter::is_valid_paste_id($parentid)
|
||||
) return $this->_return_message(1, 'Invalid data.');
|
||||
$paste = $this->_model->getPaste($pasteid);
|
||||
if ($paste->exists()) {
|
||||
try {
|
||||
$comment = $paste->getComment($parentid);
|
||||
|
||||
// Comments do not expire (it's the paste that expires)
|
||||
unset($storage['expire_date']);
|
||||
unset($storage['opendiscussion']);
|
||||
$nickname = $this->_request->getParam('nickname');
|
||||
if (!empty($nickname)) $comment->setNickname($nickname);
|
||||
|
||||
// Make sure paste exists.
|
||||
if (
|
||||
!$this->_model()->exists($pasteid)
|
||||
) return $this->_return_message(1, 'Invalid data.');
|
||||
|
||||
// Make sure the discussion is opened in this paste.
|
||||
$paste = $this->_model()->read($pasteid);
|
||||
if (
|
||||
!$paste->meta->opendiscussion
|
||||
) return $this->_return_message(1, 'Invalid data.');
|
||||
|
||||
// Check for improbable collision.
|
||||
if (
|
||||
$this->_model()->existsComment($pasteid, $parentid, $dataid)
|
||||
) return $this->_return_message(1, 'You are unlucky. Try again.');
|
||||
|
||||
// New comment
|
||||
if (
|
||||
$this->_model()->createComment($pasteid, $parentid, $dataid, $storage) === false
|
||||
) return $this->_return_message(1, 'Error saving comment. Sorry.');
|
||||
|
||||
// 0 = no error
|
||||
return $this->_return_message(0, $dataid);
|
||||
$comment->setData($data);
|
||||
$comment->store();
|
||||
} catch(Exception $e) {
|
||||
return $this->_return_message(1, $e->getMessage());
|
||||
}
|
||||
$this->_return_message(0, $comment->getId());
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->_return_message(1, 'Invalid data.');
|
||||
}
|
||||
}
|
||||
// The user posts a standard paste.
|
||||
else
|
||||
{
|
||||
// Check for improbable collision.
|
||||
if (
|
||||
$this->_model()->exists($dataid)
|
||||
) return $this->_return_message(1, 'You are unlucky. Try again.');
|
||||
$paste = $this->_model->getPaste();
|
||||
try {
|
||||
$paste->setData($data);
|
||||
|
||||
// Add attachment and its name, if one was sent
|
||||
if ($has_attachment) $storage['attachment'] = $attachment;
|
||||
if ($has_attachmentname) $storage['attachmentname'] = $attachmentname;
|
||||
if (!empty($attachment))
|
||||
{
|
||||
$paste->setAttachment($attachment);
|
||||
if (!empty($attachmentname))
|
||||
$paste->setAttachmentName($attachmentname);
|
||||
}
|
||||
|
||||
// New paste
|
||||
if (
|
||||
$this->_model()->create($dataid, $storage) === false
|
||||
) return $this->_return_message(1, 'Error saving paste. Sorry.');
|
||||
$expire = $this->_request->getParam('expire');
|
||||
if (!empty($expire)) $paste->setExpiration($expire);
|
||||
|
||||
// Generate the "delete" token.
|
||||
// The token is the hmac of the pasteid signed with the server salt.
|
||||
// The paste can be delete by calling http://example.com/zerobin/?pasteid=<pasteid>&deletetoken=<deletetoken>
|
||||
$deletetoken = hash_hmac('sha1', $dataid, serversalt::get());
|
||||
$burnafterreading = $this->_request->getParam('burnafterreading');
|
||||
if (!empty($burnafterreading)) $paste->setBurnafterreading($burnafterreading);
|
||||
|
||||
// 0 = no error
|
||||
return $this->_return_message(0, $dataid, array('deletetoken' => $deletetoken));
|
||||
$opendiscussion = $this->_request->getParam('opendiscussion');
|
||||
if (!empty($opendiscussion)) $paste->setOpendiscussion($opendiscussion);
|
||||
|
||||
$formatter = $this->_request->getParam('formatter');
|
||||
if (!empty($formatter)) $paste->setFormatter($formatter);
|
||||
|
||||
$paste->store();
|
||||
} catch (Exception $e) {
|
||||
return $this->_return_message(1, $e->getMessage());
|
||||
}
|
||||
$this->_return_message(0, $paste->getId(), array('deletetoken' => $paste->getDeleteToken()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -441,63 +315,48 @@ class zerobin
|
||||
*/
|
||||
private function _delete($dataid, $deletetoken)
|
||||
{
|
||||
// Is this a valid paste identifier?
|
||||
if (!filter::is_valid_paste_id($dataid))
|
||||
{
|
||||
$this->_error = 'Invalid paste ID.';
|
||||
return;
|
||||
}
|
||||
|
||||
// Check that paste exists.
|
||||
if (!$this->_model()->exists($dataid))
|
||||
{
|
||||
$this->_error = self::GENERIC_ERROR;
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the paste itself.
|
||||
$paste = $this->_model()->read($dataid);
|
||||
|
||||
// See if paste has expired.
|
||||
if (
|
||||
isset($paste->meta->expire_date) &&
|
||||
$paste->meta->expire_date < time()
|
||||
)
|
||||
{
|
||||
// Delete the paste
|
||||
$this->_model()->delete($dataid);
|
||||
$this->_error = self::GENERIC_ERROR;
|
||||
return;
|
||||
}
|
||||
|
||||
if ($deletetoken == 'burnafterreading') {
|
||||
if (
|
||||
isset($paste->meta->burnafterreading) &&
|
||||
$paste->meta->burnafterreading
|
||||
)
|
||||
try {
|
||||
$paste = $this->_model->getPaste($dataid);
|
||||
if ($paste->exists())
|
||||
{
|
||||
// Delete the paste
|
||||
$this->_model()->delete($dataid);
|
||||
$this->_return_message(0, $dataid);
|
||||
// accessing this property ensures that the paste would be
|
||||
// deleted if it has already expired
|
||||
$burnafterreading = $paste->isBurnafterreading();
|
||||
if ($deletetoken == 'burnafterreading')
|
||||
{
|
||||
if ($burnafterreading)
|
||||
{
|
||||
$paste->delete();
|
||||
$this->_return_message(0, $dataid);
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->_return_message(1, 'Paste is not of burn-after-reading type.');
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Make sure the token is valid.
|
||||
serversalt::setPath($this->_conf->getKey('dir', 'traffic'));
|
||||
if (filter::slow_equals($deletetoken, $paste->getDeleteToken()))
|
||||
{
|
||||
// Paste exists and deletion token is valid: Delete the paste.
|
||||
$paste->delete();
|
||||
$this->_status = 'Paste was properly deleted.';
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->_error = 'Wrong deletion token. Paste was not deleted.';
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->_return_message(1, 'Paste is not of burn-after-reading type.');
|
||||
$this->_error = self::GENERIC_ERROR;
|
||||
}
|
||||
return;
|
||||
} catch (Exception $e) {
|
||||
$this->_error = $e->getMessage();
|
||||
}
|
||||
|
||||
// Make sure token is valid.
|
||||
serversalt::setPath($this->_conf['traffic']['dir']);
|
||||
if (!filter::slow_equals($deletetoken, hash_hmac('sha1', $dataid, serversalt::get())))
|
||||
{
|
||||
$this->_error = 'Wrong deletion token. Paste was not deleted.';
|
||||
return;
|
||||
}
|
||||
|
||||
// Paste exists and deletion token is valid: Delete the paste.
|
||||
$this->_model()->delete($dataid);
|
||||
$this->_status = 'Paste was properly deleted.';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -509,80 +368,23 @@ class zerobin
|
||||
*/
|
||||
private function _read($dataid)
|
||||
{
|
||||
$isJson = false;
|
||||
if (($pos = strpos($dataid, '&json')) !== false) {
|
||||
$isJson = true;
|
||||
$dataid = substr($dataid, 0, $pos);
|
||||
}
|
||||
|
||||
// Is this a valid paste identifier?
|
||||
if (!filter::is_valid_paste_id($dataid))
|
||||
{
|
||||
$this->_error = 'Invalid paste ID.';
|
||||
return;
|
||||
}
|
||||
|
||||
// Check that paste exists.
|
||||
if ($this->_model()->exists($dataid))
|
||||
{
|
||||
// Get the paste itself.
|
||||
$paste = $this->_model()->read($dataid);
|
||||
|
||||
// See if paste has expired.
|
||||
if (
|
||||
isset($paste->meta->expire_date) &&
|
||||
$paste->meta->expire_date < time()
|
||||
)
|
||||
try {
|
||||
$paste = $this->_model->getPaste($dataid);
|
||||
if ($paste->exists())
|
||||
{
|
||||
// Delete the paste
|
||||
$this->_model()->delete($dataid);
|
||||
$this->_error = self::GENERIC_ERROR;
|
||||
$data = $paste->get();
|
||||
$this->_doesExpire = property_exists($data, 'meta') && property_exists($data->meta, 'expire_date');
|
||||
$this->_data = json_encode($data);
|
||||
}
|
||||
// If no error, return the paste.
|
||||
else
|
||||
{
|
||||
// We kindly provide the remaining time before expiration (in seconds)
|
||||
if (
|
||||
property_exists($paste->meta, 'expire_date')
|
||||
) $paste->meta->remaining_time = $paste->meta->expire_date - time();
|
||||
|
||||
// The paste itself is the first in the list of encrypted messages.
|
||||
$messages = array($paste);
|
||||
|
||||
// If it's a discussion, get all comments.
|
||||
if (
|
||||
property_exists($paste->meta, 'opendiscussion') &&
|
||||
$paste->meta->opendiscussion
|
||||
)
|
||||
{
|
||||
$messages = array_merge(
|
||||
$messages,
|
||||
$this->_model()->readComments($dataid)
|
||||
);
|
||||
}
|
||||
|
||||
// set formatter for for the view.
|
||||
if (!property_exists($paste->meta, 'formatter'))
|
||||
{
|
||||
// support < 0.21 syntax highlighting
|
||||
if (property_exists($paste->meta, 'syntaxcoloring') && $paste->meta->syntaxcoloring === true)
|
||||
{
|
||||
$paste->meta->formatter = 'syntaxhighlighting';
|
||||
}
|
||||
else
|
||||
{
|
||||
$paste->meta->formatter = $this->_getMainConfig('defaultformatter', 'plaintext');
|
||||
}
|
||||
}
|
||||
|
||||
$this->_data = json_encode($messages);
|
||||
$this->_error = self::GENERIC_ERROR;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$this->_error = $e->getMessage();
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->_error = self::GENERIC_ERROR;
|
||||
}
|
||||
if ($isJson)
|
||||
|
||||
if ($this->_request->isJsonApiCall())
|
||||
{
|
||||
if (strlen($this->_error))
|
||||
{
|
||||
@@ -590,7 +392,7 @@ class zerobin
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->_return_message(0, $dataid, array('messages' => $messages));
|
||||
$this->_return_message(0, $dataid, json_decode($this->_data, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -613,17 +415,17 @@ class zerobin
|
||||
|
||||
// label all the expiration options
|
||||
$expire = array();
|
||||
foreach ($this->_conf['expire_options'] as $time => $seconds)
|
||||
foreach ($this->_conf->getSection('expire_options') as $time => $seconds)
|
||||
{
|
||||
$expire[$time] = ($seconds == 0) ? i18n::_(ucfirst($time)): filter::time_humanreadable($time);
|
||||
}
|
||||
|
||||
// translate all the formatter options
|
||||
$formatters = array_map(array('i18n', 'translate'), $this->_conf['formatter_options']);
|
||||
$formatters = array_map(array('i18n', 'translate'), $this->_conf->getSection('formatter_options'));
|
||||
|
||||
// set language cookie if that functionality was enabled
|
||||
$languageselection = '';
|
||||
if ($this->_getMainConfig('languageselection', false))
|
||||
if ($this->_conf->getKey('languageselection'))
|
||||
{
|
||||
$languageselection = i18n::getLanguage();
|
||||
setcookie('lang', $languageselection);
|
||||
@@ -636,42 +438,61 @@ class zerobin
|
||||
$page->assign('ERROR', i18n::_($this->_error));
|
||||
$page->assign('STATUS', i18n::_($this->_status));
|
||||
$page->assign('VERSION', self::VERSION);
|
||||
$page->assign('DISCUSSION', $this->_getMainConfig('discussion', true));
|
||||
$page->assign('OPENDISCUSSION', $this->_getMainConfig('opendiscussion', true));
|
||||
$page->assign('DISCUSSION', $this->_conf->getKey('discussion'));
|
||||
$page->assign('OPENDISCUSSION', $this->_conf->getKey('opendiscussion'));
|
||||
$page->assign('MARKDOWN', array_key_exists('markdown', $formatters));
|
||||
$page->assign('SYNTAXHIGHLIGHTING', array_key_exists('syntaxhighlighting', $formatters));
|
||||
$page->assign('SYNTAXHIGHLIGHTINGTHEME', $this->_getMainConfig('syntaxhighlightingtheme', ''));
|
||||
$page->assign('SYNTAXHIGHLIGHTINGTHEME', $this->_conf->getKey('syntaxhighlightingtheme'));
|
||||
$page->assign('FORMATTER', $formatters);
|
||||
$page->assign('FORMATTERDEFAULT', $this->_getMainConfig('defaultformatter', 'plaintext'));
|
||||
$page->assign('NOTICE', i18n::_($this->_getMainConfig('notice', '')));
|
||||
$page->assign('BURNAFTERREADINGSELECTED', $this->_getMainConfig('burnafterreadingselected', false));
|
||||
$page->assign('PASSWORD', $this->_getMainConfig('password', true));
|
||||
$page->assign('FILEUPLOAD', $this->_getMainConfig('fileupload', false));
|
||||
$page->assign('BASE64JSVERSION', $this->_getMainConfig('base64version', '2.1.9'));
|
||||
$page->assign('FORMATTERDEFAULT', $this->_conf->getKey('defaultformatter'));
|
||||
$page->assign('NOTICE', i18n::_($this->_conf->getKey('notice')));
|
||||
$page->assign('BURNAFTERREADINGSELECTED', $this->_conf->getKey('burnafterreadingselected'));
|
||||
$page->assign('PASSWORD', $this->_conf->getKey('password'));
|
||||
$page->assign('FILEUPLOAD', $this->_conf->getKey('fileupload'));
|
||||
$page->assign('BASE64JSVERSION', $this->_conf->getKey('base64version'));
|
||||
$page->assign('LANGUAGESELECTION', $languageselection);
|
||||
$page->assign('LANGUAGES', i18n::getLanguageLabels(i18n::getAvailableLanguages()));
|
||||
$page->assign('EXPIRE', $expire);
|
||||
$page->assign('EXPIREDEFAULT', $this->_conf['expire']['default']);
|
||||
$page->draw($this->_getMainConfig('template', 'page'));
|
||||
$page->assign('EXPIREDEFAULT', $this->_conf->getKey('default', 'expire'));
|
||||
$page->assign('EXPIRECLONE', !$this->_doesExpire || ($this->_doesExpire && $this->_conf->getKey('clone', 'expire')));
|
||||
$page->draw($this->_conf->getKey('template'));
|
||||
}
|
||||
|
||||
/**
|
||||
* get configuration option from [main] section, optionally set a default
|
||||
* outputs requested JSON-LD context
|
||||
*
|
||||
* @access private
|
||||
* @param string $option
|
||||
* @param mixed $default (optional)
|
||||
* @return mixed
|
||||
* @param string $type
|
||||
* @return void
|
||||
*/
|
||||
private function _getMainConfig($option, $default = false)
|
||||
private function _jsonld($type)
|
||||
{
|
||||
return array_key_exists($option, $this->_conf['main']) ?
|
||||
$this->_conf['main'][$option] :
|
||||
$default;
|
||||
if (
|
||||
$type !== 'paste' && $type !== 'comment' &&
|
||||
$type !== 'pastemeta' && $type !== 'commentmeta'
|
||||
)
|
||||
{
|
||||
$type = '';
|
||||
}
|
||||
$content = '{}';
|
||||
$file = PUBLIC_PATH . DIRECTORY_SEPARATOR . 'js' . DIRECTORY_SEPARATOR . $type . '.jsonld';
|
||||
if (is_readable($file))
|
||||
{
|
||||
$content = str_replace(
|
||||
'?jsonld=',
|
||||
$this->_urlbase . '?jsonld=',
|
||||
file_get_contents($file)
|
||||
);
|
||||
}
|
||||
|
||||
header('Content-type: application/ld+json');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET');
|
||||
echo $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* return JSON encoded message and exit
|
||||
* prepares JSON encoded status message
|
||||
*
|
||||
* @access private
|
||||
* @param bool $status
|
||||
@@ -689,6 +510,7 @@ class zerobin
|
||||
else
|
||||
{
|
||||
$result['id'] = $message;
|
||||
$result['url'] = $this->_urlbase . '?' . $message;
|
||||
}
|
||||
$result += $other;
|
||||
$this->_json = json_encode($result);
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* @link http://sebsauvage.net/wiki/doku.php?id=php:zerobin
|
||||
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
|
||||
* @license http://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
|
||||
* @version 0.21.1
|
||||
* @version 0.22
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -17,12 +17,12 @@
|
||||
*/
|
||||
abstract class zerobin_abstract
|
||||
{
|
||||
/**
|
||||
/**
|
||||
* singleton instance
|
||||
*
|
||||
* @access protected
|
||||
* @static
|
||||
* @var zerobin
|
||||
* @var zerobin_abstract
|
||||
*/
|
||||
protected static $_instance = null;
|
||||
|
||||
@@ -87,7 +87,7 @@ abstract class zerobin_abstract
|
||||
*
|
||||
* @access public
|
||||
* @param string $dataid
|
||||
* @return void
|
||||
* @return bool
|
||||
*/
|
||||
abstract public function exists($pasteid);
|
||||
|
||||
@@ -122,4 +122,24 @@ abstract class zerobin_abstract
|
||||
* @return void
|
||||
*/
|
||||
abstract public function existsComment($pasteid, $parentid, $commentid);
|
||||
|
||||
/**
|
||||
* Get next free slot for comment from postdate.
|
||||
*
|
||||
* @access public
|
||||
* @param array $comments
|
||||
* @param int|string $postdate
|
||||
* @return void
|
||||
*/
|
||||
protected function getOpenSlot(&$comments, $postdate)
|
||||
{
|
||||
if (array_key_exists($postdate, $comments))
|
||||
{
|
||||
$parts = explode('.', $postdate, 2);
|
||||
if (!array_key_exists(1, $parts)) $parts[1] = 0;
|
||||
++$parts[1];
|
||||
return $this->getOpenSlot($comments, implode('.', $parts));
|
||||
}
|
||||
return $postdate;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* @link http://sebsauvage.net/wiki/doku.php?id=php:zerobin
|
||||
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
|
||||
* @license http://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
|
||||
* @version 0.21.1
|
||||
* @version 0.22
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -75,9 +75,20 @@ class zerobin_data extends zerobin_abstract
|
||||
public function read($pasteid)
|
||||
{
|
||||
if(!$this->exists($pasteid)) return false;
|
||||
return json_decode(
|
||||
$paste = json_decode(
|
||||
file_get_contents(self::_dataid2path($pasteid) . $pasteid)
|
||||
);
|
||||
if (property_exists($paste->meta, 'attachment'))
|
||||
{
|
||||
$paste->attachment = $paste->meta->attachment;
|
||||
unset($paste->meta->attachment);
|
||||
if (property_exists($paste->meta, 'attachmentname'))
|
||||
{
|
||||
$paste->attachmentname = $paste->meta->attachmentname;
|
||||
unset($paste->meta->attachmentname);
|
||||
}
|
||||
}
|
||||
return $paste;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -90,7 +101,7 @@ class zerobin_data extends zerobin_abstract
|
||||
public function delete($pasteid)
|
||||
{
|
||||
// Delete the paste itself.
|
||||
unlink(self::_dataid2path($pasteid) . $pasteid);
|
||||
@unlink(self::_dataid2path($pasteid) . $pasteid);
|
||||
|
||||
// Delete discussion if it exists.
|
||||
$discdir = self::_dataid2discussionpath($pasteid);
|
||||
@@ -100,7 +111,7 @@ class zerobin_data extends zerobin_abstract
|
||||
$dir = dir($discdir);
|
||||
while (false !== ($filename = $dir->read()))
|
||||
{
|
||||
if (is_file($discdir.$filename)) unlink($discdir.$filename);
|
||||
if (is_file($discdir.$filename)) @unlink($discdir.$filename);
|
||||
}
|
||||
$dir->close();
|
||||
|
||||
@@ -161,16 +172,17 @@ class zerobin_data extends zerobin_abstract
|
||||
// - pasteid is the paste this reply belongs to.
|
||||
// - commentid is the comment identifier itself.
|
||||
// - parentid is the comment this comment replies to (It can be pasteid)
|
||||
if (is_file($discdir.$filename))
|
||||
if (is_file($discdir . $filename))
|
||||
{
|
||||
$comment = json_decode(file_get_contents($discdir.$filename));
|
||||
$comment = json_decode(file_get_contents($discdir . $filename));
|
||||
$items = explode('.', $filename);
|
||||
// Add some meta information not contained in file.
|
||||
$comment->meta->commentid=$items[1];
|
||||
$comment->meta->parentid=$items[2];
|
||||
$comment->id = $items[1];
|
||||
$comment->parentid = $items[2];
|
||||
|
||||
// Store in array
|
||||
$comments[$comment->meta->postdate]=$comment;
|
||||
$key = $this->getOpenSlot($comments, (int) $comment->meta->postdate);
|
||||
$comments[$key] = $comment;
|
||||
}
|
||||
}
|
||||
$dir->close();
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* @link http://sebsauvage.net/wiki/doku.php?id=php:zerobin
|
||||
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
|
||||
* @license http://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
|
||||
* @version 0.21.1
|
||||
* @version 0.22
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -80,109 +80,63 @@ class zerobin_db extends zerobin_abstract
|
||||
array_key_exists('opt', $options)
|
||||
)
|
||||
{
|
||||
// check if the database contains the required tables
|
||||
// set default options
|
||||
$options['opt'][PDO::ATTR_ERRMODE] = PDO::ERRMODE_EXCEPTION;
|
||||
$options['opt'][PDO::ATTR_EMULATE_PREPARES] = false;
|
||||
$options['opt'][PDO::ATTR_PERSISTENT] = true;
|
||||
$db_tables_exist = true;
|
||||
|
||||
// setup type and dabase connection
|
||||
self::$_type = strtolower(
|
||||
substr($options['dsn'], 0, strpos($options['dsn'], ':'))
|
||||
);
|
||||
switch(self::$_type)
|
||||
{
|
||||
case 'ibm':
|
||||
$sql = 'SELECT tabname FROM SYSCAT.TABLES ';
|
||||
break;
|
||||
case 'informix':
|
||||
$sql = 'SELECT tabname FROM systables ';
|
||||
break;
|
||||
case 'mssql':
|
||||
$sql = "SELECT name FROM sysobjects "
|
||||
. "WHERE type = 'U' ORDER BY name";
|
||||
break;
|
||||
case 'mysql':
|
||||
$sql = 'SHOW TABLES';
|
||||
break;
|
||||
case 'oci':
|
||||
$sql = 'SELECT table_name FROM all_tables';
|
||||
break;
|
||||
case 'pgsql':
|
||||
$sql = "SELECT c.relname AS table_name "
|
||||
. "FROM pg_class c, pg_user u "
|
||||
. "WHERE c.relowner = u.usesysid AND c.relkind = 'r' "
|
||||
. "AND NOT EXISTS (SELECT 1 FROM pg_views WHERE viewname = c.relname) "
|
||||
. "AND c.relname !~ '^(pg_|sql_)' "
|
||||
. "UNION "
|
||||
. "SELECT c.relname AS table_name "
|
||||
. "FROM pg_class c "
|
||||
. "WHERE c.relkind = 'r' "
|
||||
. "AND NOT EXISTS (SELECT 1 FROM pg_views WHERE viewname = c.relname) "
|
||||
. "AND NOT EXISTS (SELECT 1 FROM pg_user WHERE usesysid = c.relowner) "
|
||||
. "AND c.relname !~ '^pg_'";
|
||||
break;
|
||||
case 'sqlite':
|
||||
$sql = "SELECT name FROM sqlite_master WHERE type='table' "
|
||||
. "UNION ALL SELECT name FROM sqlite_temp_master "
|
||||
. "WHERE type='table' ORDER BY name";
|
||||
break;
|
||||
default:
|
||||
throw new Exception(
|
||||
'PDO type ' .
|
||||
self::$_type .
|
||||
' is currently not supported.',
|
||||
5
|
||||
);
|
||||
}
|
||||
$tableQuery = self::_getTableQuery(self::$_type);
|
||||
self::$_db = new PDO(
|
||||
$options['dsn'],
|
||||
$options['usr'],
|
||||
$options['pwd'],
|
||||
$options['opt']
|
||||
);
|
||||
$statement = self::$_db->query($sql);
|
||||
$tables = $statement->fetchAll(PDO::FETCH_COLUMN, 0);
|
||||
|
||||
// create paste table if needed
|
||||
if (!array_key_exists(self::$_prefix . 'paste', $tables))
|
||||
// check if the database contains the required tables
|
||||
$tables = self::$_db->query($tableQuery)->fetchAll(PDO::FETCH_COLUMN, 0);
|
||||
|
||||
// create paste table if necessary
|
||||
if (!in_array(self::$_prefix . 'paste', $tables))
|
||||
{
|
||||
self::$_db->exec(
|
||||
'CREATE TABLE ' . self::$_prefix . 'paste ( ' .
|
||||
'dataid CHAR(16), ' .
|
||||
'data TEXT, ' .
|
||||
'postdate INT, ' .
|
||||
'expiredate INT, ' .
|
||||
'opendiscussion INT, ' .
|
||||
'burnafterreading INT, ' .
|
||||
'meta TEXT );'
|
||||
);
|
||||
self::_createPasteTable();
|
||||
$db_tables_exist = false;
|
||||
}
|
||||
|
||||
// create comment table if necessary
|
||||
if (!in_array(self::$_prefix . 'comment', $tables))
|
||||
{
|
||||
self::_createCommentTable();
|
||||
$db_tables_exist = false;
|
||||
}
|
||||
|
||||
// create config table if necessary
|
||||
$db_version = zerobin::VERSION;
|
||||
if (!in_array(self::$_prefix . 'config', $tables))
|
||||
{
|
||||
self::_createConfigTable();
|
||||
// if we only needed to create the config table, the DB is older then 0.22
|
||||
if ($db_tables_exist) $db_version = '0.21';
|
||||
}
|
||||
// check if the meta column exists
|
||||
else
|
||||
{
|
||||
try {
|
||||
self::$_db->exec('SELECT meta FROM ' . self::$_prefix . 'paste LIMIT 1;');
|
||||
} catch (PDOException $e) {
|
||||
if ($e->getCode() == 'HY000')
|
||||
{
|
||||
self::$_db->exec('ALTER TABLE ' . self::$_prefix . 'paste ADD COLUMN meta TEXT;');
|
||||
}
|
||||
}
|
||||
$db_version = self::_getConfig('VERSION');
|
||||
}
|
||||
|
||||
// create comment table if needed
|
||||
if (!array_key_exists(self::$_prefix . 'comment', $tables))
|
||||
// update database structure if necessary
|
||||
if (version_compare($db_version, zerobin::VERSION, '<'))
|
||||
{
|
||||
self::$_db->exec(
|
||||
'CREATE TABLE ' . self::$_prefix . 'comment ( ' .
|
||||
'dataid CHAR(16), ' .
|
||||
'pasteid CHAR(16), ' .
|
||||
'parentid CHAR(16), ' .
|
||||
'data TEXT, ' .
|
||||
'nickname VARCHAR(255), ' .
|
||||
'vizhash TEXT, ' .
|
||||
'postdate INT );'
|
||||
);
|
||||
self::_upgradeDatabase($db_version);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return parent::$_instance;
|
||||
return self::$_instance;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -206,9 +160,15 @@ class zerobin_db extends zerobin_abstract
|
||||
}
|
||||
|
||||
$opendiscussion = $burnafterreading = false;
|
||||
$attachment = $attachmentname = '';
|
||||
$meta = $paste['meta'];
|
||||
unset($meta['postdate']);
|
||||
unset($meta['expire_date']);
|
||||
$expire_date = 0;
|
||||
if (array_key_exists('expire_date', $paste['meta']))
|
||||
{
|
||||
$expire_date = (int) $paste['meta']['expire_date'];
|
||||
unset($meta['expire_date']);
|
||||
}
|
||||
if (array_key_exists('opendiscussion', $paste['meta']))
|
||||
{
|
||||
$opendiscussion = (bool) $paste['meta']['opendiscussion'];
|
||||
@@ -219,16 +179,28 @@ class zerobin_db extends zerobin_abstract
|
||||
$burnafterreading = (bool) $paste['meta']['burnafterreading'];
|
||||
unset($meta['burnafterreading']);
|
||||
}
|
||||
if (array_key_exists('attachment', $paste['meta']))
|
||||
{
|
||||
$attachment = $paste['meta']['attachment'];
|
||||
unset($meta['attachment']);
|
||||
}
|
||||
if (array_key_exists('attachmentname', $paste['meta']))
|
||||
{
|
||||
$attachmentname = $paste['meta']['attachmentname'];
|
||||
unset($meta['attachmentname']);
|
||||
}
|
||||
return self::_exec(
|
||||
'INSERT INTO ' . self::$_prefix . 'paste VALUES(?,?,?,?,?,?,?)',
|
||||
'INSERT INTO ' . self::$_prefix . 'paste VALUES(?,?,?,?,?,?,?,?,?)',
|
||||
array(
|
||||
$pasteid,
|
||||
$paste['data'],
|
||||
$paste['meta']['postdate'],
|
||||
$paste['meta']['expire_date'],
|
||||
$expire_date,
|
||||
(int) $opendiscussion,
|
||||
(int) $burnafterreading,
|
||||
json_encode($meta),
|
||||
$attachment,
|
||||
$attachmentname,
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -255,9 +227,36 @@ class zerobin_db extends zerobin_abstract
|
||||
// create object
|
||||
self::$_cache[$pasteid] = new stdClass;
|
||||
self::$_cache[$pasteid]->data = $paste['data'];
|
||||
self::$_cache[$pasteid]->meta = json_decode($paste['meta']);
|
||||
|
||||
$meta = json_decode($paste['meta']);
|
||||
if (!is_object($meta)) $meta = new stdClass;
|
||||
|
||||
// support older attachments
|
||||
if (property_exists($meta, 'attachment'))
|
||||
{
|
||||
self::$_cache[$pasteid]->attachment = $meta->attachment;
|
||||
unset($meta->attachment);
|
||||
if (property_exists($meta, 'attachmentname'))
|
||||
{
|
||||
self::$_cache[$pasteid]->attachmentname = $meta->attachmentname;
|
||||
unset($meta->attachmentname);
|
||||
}
|
||||
}
|
||||
// support current attachments
|
||||
elseif (array_key_exists('attachment', $paste) && strlen($paste['attachment']))
|
||||
{
|
||||
self::$_cache[$pasteid]->attachment = $paste['attachment'];
|
||||
if (array_key_exists('attachmentname', $paste) && strlen($paste['attachmentname']))
|
||||
{
|
||||
self::$_cache[$pasteid]->attachmentname = $paste['attachmentname'];
|
||||
}
|
||||
}
|
||||
self::$_cache[$pasteid]->meta = $meta;
|
||||
self::$_cache[$pasteid]->meta->postdate = (int) $paste['postdate'];
|
||||
self::$_cache[$pasteid]->meta->expire_date = (int) $paste['expiredate'];
|
||||
$expire_date = (int) $paste['expiredate'];
|
||||
if (
|
||||
$expire_date > 0
|
||||
) self::$_cache[$pasteid]->meta->expire_date = $expire_date;
|
||||
if (
|
||||
$paste['opendiscussion']
|
||||
) self::$_cache[$pasteid]->meta->opendiscussion = true;
|
||||
@@ -347,24 +346,23 @@ class zerobin_db extends zerobin_abstract
|
||||
array($pasteid)
|
||||
);
|
||||
|
||||
// create object
|
||||
$commentTemplate = new stdClass;
|
||||
$commentTemplate->meta = new stdClass;
|
||||
|
||||
// create comment list
|
||||
$comments = array();
|
||||
if (count($rows))
|
||||
{
|
||||
foreach ($rows as $row)
|
||||
{
|
||||
$i = (int) $row['postdate'];
|
||||
$comments[$i] = clone $commentTemplate;
|
||||
$i = $this->getOpenSlot($comments, (int) $row['postdate']);
|
||||
$comments[$i] = new stdClass;
|
||||
$comments[$i]->id = $row['dataid'];
|
||||
$comments[$i]->parentid = $row['parentid'];
|
||||
$comments[$i]->data = $row['data'];
|
||||
$comments[$i]->meta->nickname = $row['nickname'];
|
||||
$comments[$i]->meta->vizhash = $row['vizhash'];
|
||||
$comments[$i]->meta->postdate = $i;
|
||||
$comments[$i]->meta->commentid = $row['dataid'];
|
||||
$comments[$i]->meta->parentid = $row['parentid'];
|
||||
$comments[$i]->meta = new stdClass;
|
||||
$comments[$i]->meta->postdate = (int) $row['postdate'];
|
||||
if (array_key_exists('nickname', $row))
|
||||
$comments[$i]->meta->nickname = $row['nickname'];
|
||||
if (array_key_exists('vizhash', $row))
|
||||
$comments[$i]->meta->vizhash = $row['vizhash'];
|
||||
}
|
||||
ksort($comments);
|
||||
}
|
||||
@@ -428,4 +426,227 @@ class zerobin_db extends zerobin_abstract
|
||||
$statement->closeCursor();
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* get table list query, depending on the database type
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
* @param string $type
|
||||
* @throws Exception
|
||||
* @return string
|
||||
*/
|
||||
private static function _getTableQuery($type)
|
||||
{
|
||||
switch($type)
|
||||
{
|
||||
case 'ibm':
|
||||
$sql = 'SELECT tabname FROM SYSCAT.TABLES ';
|
||||
break;
|
||||
case 'informix':
|
||||
$sql = 'SELECT tabname FROM systables ';
|
||||
break;
|
||||
case 'mssql':
|
||||
$sql = "SELECT name FROM sysobjects "
|
||||
. "WHERE type = 'U' ORDER BY name";
|
||||
break;
|
||||
case 'mysql':
|
||||
$sql = 'SHOW TABLES';
|
||||
break;
|
||||
case 'oci':
|
||||
$sql = 'SELECT table_name FROM all_tables';
|
||||
break;
|
||||
case 'pgsql':
|
||||
$sql = "SELECT c.relname AS table_name "
|
||||
. "FROM pg_class c, pg_user u "
|
||||
. "WHERE c.relowner = u.usesysid AND c.relkind = 'r' "
|
||||
. "AND NOT EXISTS (SELECT 1 FROM pg_views WHERE viewname = c.relname) "
|
||||
. "AND c.relname !~ '^(pg_|sql_)' "
|
||||
. "UNION "
|
||||
. "SELECT c.relname AS table_name "
|
||||
. "FROM pg_class c "
|
||||
. "WHERE c.relkind = 'r' "
|
||||
. "AND NOT EXISTS (SELECT 1 FROM pg_views WHERE viewname = c.relname) "
|
||||
. "AND NOT EXISTS (SELECT 1 FROM pg_user WHERE usesysid = c.relowner) "
|
||||
. "AND c.relname !~ '^pg_'";
|
||||
break;
|
||||
case 'sqlite':
|
||||
$sql = "SELECT name FROM sqlite_master WHERE type='table' "
|
||||
. "UNION ALL SELECT name FROM sqlite_temp_master "
|
||||
. "WHERE type='table' ORDER BY name";
|
||||
break;
|
||||
default:
|
||||
throw new Exception(
|
||||
"PDO type $type is currently not supported.", 5
|
||||
);
|
||||
}
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* get a value by key from the config table
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
* @param string $key
|
||||
* @throws PDOException
|
||||
* @return string
|
||||
*/
|
||||
private static function _getConfig($key)
|
||||
{
|
||||
$row = self::_select(
|
||||
'SELECT value FROM ' . self::$_prefix . 'config WHERE id = ?',
|
||||
array($key), true
|
||||
);
|
||||
return $row['value'];
|
||||
}
|
||||
|
||||
/**
|
||||
* get the primary key clauses, depending on the database driver
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
* @param string $key
|
||||
* @return array
|
||||
*/
|
||||
private static function _getPrimaryKeyClauses($key = 'dataid')
|
||||
{
|
||||
$main_key = $after_key = '';
|
||||
if (self::$_type === 'mysql')
|
||||
{
|
||||
$after_key = ", PRIMARY KEY ($key)";
|
||||
}
|
||||
else
|
||||
{
|
||||
$main_key = ' PRIMARY KEY';
|
||||
}
|
||||
return array($main_key, $after_key);
|
||||
}
|
||||
|
||||
/**
|
||||
* create the paste table
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
* @return void
|
||||
*/
|
||||
private static function _createPasteTable()
|
||||
{
|
||||
list($main_key, $after_key) = self::_getPrimaryKeyClauses();
|
||||
self::$_db->exec(
|
||||
'CREATE TABLE ' . self::$_prefix . 'paste ( ' .
|
||||
"dataid CHAR(16) NOT NULL$main_key, " .
|
||||
'data BLOB, ' .
|
||||
'postdate INT, ' .
|
||||
'expiredate INT, ' .
|
||||
'opendiscussion INT, ' .
|
||||
'burnafterreading INT, ' .
|
||||
'meta TEXT, ' .
|
||||
'attachment MEDIUMBLOB, ' .
|
||||
"attachmentname BLOB$after_key );"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* create the paste table
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
* @return void
|
||||
*/
|
||||
private static function _createCommentTable()
|
||||
{
|
||||
list($main_key, $after_key) = self::_getPrimaryKeyClauses();
|
||||
self::$_db->exec(
|
||||
'CREATE TABLE ' . self::$_prefix . 'comment ( ' .
|
||||
"dataid CHAR(16) NOT NULL$main_key, " .
|
||||
'pasteid CHAR(16), ' .
|
||||
'parentid CHAR(16), ' .
|
||||
'data BLOB, ' .
|
||||
'nickname BLOB, ' .
|
||||
'vizhash BLOB, ' .
|
||||
"postdate INT$after_key );"
|
||||
);
|
||||
self::$_db->exec(
|
||||
'CREATE INDEX parent ON ' . self::$_prefix . 'comment(pasteid);'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* create the paste table
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
* @return void
|
||||
*/
|
||||
private static function _createConfigTable()
|
||||
{
|
||||
list($main_key, $after_key) = self::_getPrimaryKeyClauses('id');
|
||||
self::$_db->exec(
|
||||
'CREATE TABLE ' . self::$_prefix . 'config ( ' .
|
||||
"id CHAR(16) NOT NULL$main_key, value TEXT$after_key );"
|
||||
);
|
||||
self::_exec(
|
||||
'INSERT INTO ' . self::$_prefix . 'config VALUES(?,?)',
|
||||
array('VERSION', zerobin::VERSION)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* upgrade the database schema from an old version
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
* @param string $oldversion
|
||||
* @return void
|
||||
*/
|
||||
private static function _upgradeDatabase($oldversion)
|
||||
{
|
||||
switch ($oldversion)
|
||||
{
|
||||
case '0.21':
|
||||
// create the meta column if necessary (pre 0.21 change)
|
||||
try {
|
||||
self::$_db->exec('SELECT meta FROM ' . self::$_prefix . 'paste LIMIT 1;', array());
|
||||
} catch (PDOException $e) {
|
||||
self::$_db->exec('ALTER TABLE ' . self::$_prefix . 'paste ADD COLUMN meta TEXT;');
|
||||
}
|
||||
// SQLite only allows one ALTER statement at a time...
|
||||
self::$_db->exec(
|
||||
'ALTER TABLE ' . self::$_prefix . 'paste ADD COLUMN attachment MEDIUMBLOB;'
|
||||
);
|
||||
self::$_db->exec(
|
||||
'ALTER TABLE ' . self::$_prefix . 'paste ADD COLUMN attachmentname BLOB;'
|
||||
);
|
||||
// SQLite doesn't support MODIFY, but it allows TEXT of similar
|
||||
// size as BLOB, so there is no need to change it there
|
||||
if (self::$_type !== 'sqlite')
|
||||
{
|
||||
self::$_db->exec(
|
||||
'ALTER TABLE ' . self::$_prefix . 'paste ' .
|
||||
'ADD PRIMARY KEY (dataid),' .
|
||||
'MODIFY COLUMN data BLOB;'
|
||||
);
|
||||
self::$_db->exec(
|
||||
'ALTER TABLE ' . self::$_prefix . 'comment ' .
|
||||
'ADD PRIMARY KEY (dataid),' .
|
||||
'MODIFY COLUMN data BLOB, ' .
|
||||
'MODIFY COLUMN nickname BLOB, ' .
|
||||
'MODIFY COLUMN vizhash BLOB;'
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
self::$_db->exec(
|
||||
'CREATE UNIQUE INDEX primary ON ' . self::$_prefix . 'paste(dataid);'
|
||||
);
|
||||
self::$_db->exec(
|
||||
'CREATE UNIQUE INDEX primary ON ' . self::$_prefix . 'comment(dataid);'
|
||||
);
|
||||
}
|
||||
self::$_db->exec(
|
||||
'CREATE INDEX parent ON ' . self::$_prefix . 'comment(pasteid);'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,11 @@
|
||||
<!--[if lt IE 10]>
|
||||
<style type="text/css">#ienotice {display:block !important;} #oldienotice {display:block !important;}</style>
|
||||
<![endif]-->
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="img/favicons/apple-touch-icon.png?{$VERSION|rawurlencode}" />
|
||||
<link rel="icon" type="image/png" href="img/favicons/android-chrome-192x192.png?{$VERSION|rawurlencode}" sizes="192x192" />
|
||||
<link rel="icon" type="image/png" href="img/favicons/favicon-32x32.png?{$VERSION|rawurlencode}" sizes="32x32" />
|
||||
<link rel="icon" type="image/png" href="img/favicons/favicon-96x96.png?{$VERSION|rawurlencode}" sizes="96x96" />
|
||||
<link rel="icon" type="image/png" href="img/favicons/favicon-16x16.png?{$VERSION|rawurlencode}" sizes="16x16" />
|
||||
</head>
|
||||
<body role="document" class="navbar-spacing">
|
||||
<nav class="navbar navbar-default navbar-fixed-top">
|
||||
@@ -41,10 +46,10 @@
|
||||
<li>
|
||||
<button id="sendbutton" type="button" class="hidden btn btn-default navbar-btn">
|
||||
<span class="glyphicon glyphicon-upload" aria-hidden="true"></span> {function="t('Send')"}
|
||||
</button>
|
||||
</button>{if="$EXPIRECLONE"}
|
||||
<button id="clonebutton" type="button" class="hidden btn btn-default navbar-btn">
|
||||
<span class="glyphicon glyphicon-duplicate" aria-hidden="true"></span> {function="t('Clone')"}
|
||||
</button>
|
||||
</button>{/if}
|
||||
<button id="rawtextbutton" type="button" class="hidden btn btn-default navbar-btn">
|
||||
<span class="glyphicon glyphicon-text-background" aria-hidden="true"></span> {function="t('Raw text')"}
|
||||
</button>
|
||||
|
||||
198
tpl/bootstrap-dark-page.html
Normal file
198
tpl/bootstrap-dark-page.html
Normal file
@@ -0,0 +1,198 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="robots" content="noindex" />
|
||||
<title>{function="t('ZeroBin')"}</title>
|
||||
<link type="text/css" rel="stylesheet" href="css/bootstrap/bootstrap-theme-3.3.5.css" />
|
||||
<link type="text/css" rel="stylesheet" href="css/bootstrap/darkstrap-0.9.3.css" />
|
||||
<link type="text/css" rel="stylesheet" href="css/bootstrap/zerobin.css?{$VERSION|rawurlencode}" />{if="$SYNTAXHIGHLIGHTING"}
|
||||
<link type="text/css" rel="stylesheet" href="css/prettify/prettify.css?{$VERSION|rawurlencode}" />{if="strlen($SYNTAXHIGHLIGHTINGTHEME)"}
|
||||
<link type="text/css" rel="stylesheet" href="css/prettify/{$SYNTAXHIGHLIGHTINGTHEME}.css?{$VERSION|rawurlencode}" />{/if}{/if}
|
||||
<script type="text/javascript" src="js/jquery-1.11.3.js"></script>
|
||||
<script type="text/javascript" src="js/sjcl-1.0.2.js"></script>
|
||||
<script type="text/javascript" src="js/base64-{$BASE64JSVERSION}.js"></script>
|
||||
<script type="text/javascript" src="js/rawdeflate-0.5.js"></script>
|
||||
<script type="text/javascript" src="js/rawinflate-0.3.js"></script>
|
||||
<script type="text/javascript" src="js/bootstrap-3.3.5.js"></script>{if="$SYNTAXHIGHLIGHTING"}
|
||||
<script type="text/javascript" src="js/prettify.js?{$VERSION|rawurlencode}"></script>{/if}{if="$MARKDOWN"}
|
||||
<script type="text/javascript" src="js/showdown.js?{$VERSION|rawurlencode}"></script>{/if}
|
||||
<script type="text/javascript" src="js/zerobin.js?{$VERSION|rawurlencode}"></script>
|
||||
<!--[if lt IE 10]>
|
||||
<style type="text/css">#ienotice {display:block !important;} #oldienotice {display:block !important;}</style>
|
||||
<![endif]-->
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="img/favicons/apple-touch-icon.png?{$VERSION|rawurlencode}" />
|
||||
<link rel="icon" type="image/png" href="img/favicons/android-chrome-192x192.png?{$VERSION|rawurlencode}" sizes="192x192" />
|
||||
<link rel="icon" type="image/png" href="img/favicons/favicon-32x32.png?{$VERSION|rawurlencode}" sizes="32x32" />
|
||||
<link rel="icon" type="image/png" href="img/favicons/favicon-96x96.png?{$VERSION|rawurlencode}" sizes="96x96" />
|
||||
<link rel="icon" type="image/png" href="img/favicons/favicon-16x16.png?{$VERSION|rawurlencode}" sizes="16x16" />
|
||||
</head>
|
||||
<body role="document">
|
||||
<nav class="navbar navbar-inverse navbar-static-top">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
|
||||
<span class="sr-only">{function="t('Toggle navigation')"}</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="reloadlink navbar-brand" href="/">{function="t('ZeroBin')"}</a>
|
||||
</div>
|
||||
<div id="navbar" class="navbar-collapse collapse">
|
||||
<ul class="nav navbar-nav">
|
||||
<li>
|
||||
<button id="sendbutton" type="button" class="hidden btn btn-warning navbar-btn">
|
||||
<span class="glyphicon glyphicon-upload" aria-hidden="true"></span> {function="t('Send')"}
|
||||
</button>{if="$EXPIRECLONE"}
|
||||
<button id="clonebutton" type="button" class="hidden btn btn-warning navbar-btn">
|
||||
<span class="glyphicon glyphicon-duplicate" aria-hidden="true"></span> {function="t('Clone')"}
|
||||
</button>{/if}
|
||||
<button id="rawtextbutton" type="button" class="hidden btn btn-warning navbar-btn">
|
||||
<span class="glyphicon glyphicon-text-background" aria-hidden="true"></span> {function="t('Raw text')"}
|
||||
</button>
|
||||
</li>
|
||||
<li class="dropdown">
|
||||
<select id="pasteExpiration" name="pasteExpiration" class="hidden">
|
||||
{loop="EXPIRE"}
|
||||
<option value="{$key}"{if="$key == $EXPIREDEFAULT"} selected="selected"{/if}>{$value}</option>{/loop}
|
||||
</select>
|
||||
<a id="expiration" href="#" class="hidden dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{function="t('Expires')"}: <span id="pasteExpirationDisplay">{$EXPIRE[$EXPIREDEFAULT]}</span> <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
{loop="EXPIRE"}
|
||||
<li>
|
||||
<a href="#" onclick="$('#pasteExpiration').val('{$key}');$('#pasteExpirationDisplay').text('{$value}');return false;">
|
||||
{$value}
|
||||
</a>
|
||||
</li>{/loop}
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<div id="burnafterreadingoption" class="navbar-text checkbox hidden">
|
||||
<label>
|
||||
<input type="checkbox" id="burnafterreading" name="burnafterreading" {if="$BURNAFTERREADINGSELECTED"} checked="checked"{/if} />
|
||||
{function="t('Burn after reading')"}
|
||||
</label>
|
||||
</div>
|
||||
</li>{if="$DISCUSSION"}
|
||||
<li>
|
||||
<div id="opendisc" class="navbar-text checkbox hidden">
|
||||
<label>
|
||||
<input type="checkbox" id="opendiscussion" name="opendiscussion" {if="$OPENDISCUSSION"} checked="checked"{/if} />
|
||||
{function="t('Open discussion')"}
|
||||
</label>
|
||||
</div>
|
||||
</li>{/if}{if="$PASSWORD"}
|
||||
<li>
|
||||
<div id="password" class="navbar-form hidden">
|
||||
<input type="password" id="passwordinput" placeholder="{function="t('Password (recommended)')"}" class="form-control" size="19"/>
|
||||
</div>
|
||||
</li>{/if}{if="$FILEUPLOAD"}
|
||||
<li id="attach" class="hidden dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{function="t('Attach a file')"} <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li id="filewrap">
|
||||
<div>
|
||||
<input type="file" id="file" name="file" />
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<a id="fileremovebutton" href="#">
|
||||
{function="t('Remove attachment')"}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>{/if}
|
||||
<li class="dropdown">
|
||||
<select id="pasteFormatter" name="pasteFormatter" class="hidden">
|
||||
{loop="FORMATTER"}
|
||||
<option value="{$key}"{if="$key == $FORMATTERDEFAULT"} selected="selected"{/if}>{$value}</option>{/loop}
|
||||
</select>
|
||||
<a id="formatter" href="#" class="hidden dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{function="t('Format')"}: <span id="pasteFormatterDisplay">{$FORMATTER[$FORMATTERDEFAULT]}</span> <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
{loop="FORMATTER"}
|
||||
<li>
|
||||
<a href="#" onclick="$('#pasteFormatter').val('{$key}');$('#pasteFormatterDisplay').text('{$value}');return false;">
|
||||
{$value}
|
||||
</a>
|
||||
</li>{/loop}
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="nav navbar-nav pull-right">{if="strlen($LANGUAGESELECTION)"}
|
||||
<li id="language" class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><span class="glyphicon glyphicon-flag" aria-hidden="true"></span> {$LANGUAGES[$LANGUAGESELECTION][0]} <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
{loop="LANGUAGES"}
|
||||
<li>
|
||||
<a href="#" class="reloadlink" onclick="document.cookie='lang={$key}';">
|
||||
{$value[0]} ({$value[1]})
|
||||
</a>
|
||||
</li>{/loop}
|
||||
</ul>
|
||||
</li>{/if}
|
||||
<li>
|
||||
<button id="newbutton" type="button" class="reloadlink hidden btn btn-warning navbar-btn">
|
||||
<span class="glyphicon glyphicon-file" aria-hidden="true"></span> {function="t('New')"}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
<header class="container">{if="strlen($NOTICE)"}
|
||||
<div role="alert" class="alert alert-info">
|
||||
<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span> {$NOTICE|htmlspecialchars}
|
||||
</div>{/if}
|
||||
<div id="remainingtime" role="alert" class="hidden alert alert-info">
|
||||
<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>
|
||||
</div>{if="$FILEUPLOAD"}
|
||||
<div id="attachment" role="alert" class="hidden alert alert-info">
|
||||
<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span> <a>{function="t('Download attachment')"}</a> <span id="clonedfile" class="hidden">{function="t('Cloned file attached.')"}</span>
|
||||
</div>{/if}{if="strlen($STATUS)"}
|
||||
<div id="status" role="alert" class="alert alert-success">
|
||||
<span class="glyphicon glyphicon-ok" aria-hidden="true"></span> {$STATUS|htmlspecialchars}
|
||||
</div>{/if}
|
||||
<div id="errormessage" role="alert" class="{if="!strlen($ERROR)"}hidden {/if}alert alert-danger"><span class="glyphicon glyphicon-alert" aria-hidden="true"></span> {$ERROR|htmlspecialchars}</div>
|
||||
<div id="noscript" role="alert" class="nonworking alert alert-error"><span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span> {function="t('Javascript is required for ZeroBin to work.<br />Sorry for the inconvenience.')"}</div>
|
||||
<div id="oldienotice" role="alert" class="hidden nonworking alert alert-danger"><span class="glyphicon glyphicon-alert" aria-hidden="true"></span> {function="t('ZeroBin requires a modern browser to work.')"}</div>
|
||||
<div id="ienotice" role="alert" class="hidden alert alert-error"><span class="glyphicon glyphicon-question-sign" aria-hidden="true"></span> {function="t('Still using Internet Explorer? Do yourself a favor, switch to a modern browser:')"}
|
||||
<a href="http://www.mozilla.org/firefox/">Firefox</a>,
|
||||
<a href="http://www.opera.com/">Opera</a>,
|
||||
<a href="http://www.google.com/chrome">Chrome</a>,
|
||||
<a href="http://www.apple.com/safari">Safari</a>...
|
||||
</div>
|
||||
<div id="pasteresult" role="alert" class="hidden alert alert-success">
|
||||
<span class="glyphicon glyphicon-ok" aria-hidden="true"></span>
|
||||
<div id="deletelink"></div>
|
||||
<div id="pastelink"></div>
|
||||
</div>
|
||||
</header>
|
||||
<section class="container">
|
||||
<article class="row">
|
||||
<div id="image" class="col-md-12 text-center hidden"></div>
|
||||
<div id="prettymessage" class="col-md-12 hidden">
|
||||
<pre id="prettyprint" class="col-md-12 prettyprint linenums:1"></pre>
|
||||
</div>
|
||||
<div id="cleartext" class="col-md-12 hidden"></div>
|
||||
<p class="col-md-12"><textarea id="message" name="message" cols="80" rows="25" class="form-control hidden"></textarea></p>
|
||||
</article>
|
||||
</section>
|
||||
<section class="container">
|
||||
<div id="discussion" class="hidden">
|
||||
<h4>{function="t('Discussion')"}</h4>
|
||||
<div id="comments"></div>
|
||||
</div>
|
||||
</section>
|
||||
<footer class="container">
|
||||
<div class="row">
|
||||
<h4 class="col-md-5 col-xs-8">{function="t('ZeroBin')"} <small>- {function="t('Because ignorance is bliss')"}</small></h4>
|
||||
<p class="col-md-1 col-xs-4 text-center">{$VERSION}</p>
|
||||
<p id="aboutbox" class="col-md-6 col-xs-12">
|
||||
{function="t('ZeroBin is a minimalist, opensource online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted <i>in the browser</i> using 256 bits AES. More information on the <a href="https://github.com/elrido/ZeroBin/wiki">project page</a>.')"}
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
<div id="cipherdata" class="hidden">{$CIPHERDATA}</div>
|
||||
</body>
|
||||
</html>
|
||||
198
tpl/bootstrap-dark.html
Normal file
198
tpl/bootstrap-dark.html
Normal file
@@ -0,0 +1,198 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="robots" content="noindex" />
|
||||
<title>{function="t('ZeroBin')"}</title>
|
||||
<link type="text/css" rel="stylesheet" href="css/bootstrap/bootstrap-theme-3.3.5.css" />
|
||||
<link type="text/css" rel="stylesheet" href="css/bootstrap/darkstrap-0.9.3.css" />
|
||||
<link type="text/css" rel="stylesheet" href="css/bootstrap/zerobin.css?{$VERSION|rawurlencode}" />{if="$SYNTAXHIGHLIGHTING"}
|
||||
<link type="text/css" rel="stylesheet" href="css/prettify/prettify.css?{$VERSION|rawurlencode}" />{if="strlen($SYNTAXHIGHLIGHTINGTHEME)"}
|
||||
<link type="text/css" rel="stylesheet" href="css/prettify/{$SYNTAXHIGHLIGHTINGTHEME}.css?{$VERSION|rawurlencode}" />{/if}{/if}
|
||||
<script type="text/javascript" src="js/jquery-1.11.3.js"></script>
|
||||
<script type="text/javascript" src="js/sjcl-1.0.2.js"></script>
|
||||
<script type="text/javascript" src="js/base64-{$BASE64JSVERSION}.js"></script>
|
||||
<script type="text/javascript" src="js/rawdeflate-0.5.js"></script>
|
||||
<script type="text/javascript" src="js/rawinflate-0.3.js"></script>
|
||||
<script type="text/javascript" src="js/bootstrap-3.3.5.js"></script>{if="$SYNTAXHIGHLIGHTING"}
|
||||
<script type="text/javascript" src="js/prettify.js?{$VERSION|rawurlencode}"></script>{/if}{if="$MARKDOWN"}
|
||||
<script type="text/javascript" src="js/showdown.js?{$VERSION|rawurlencode}"></script>{/if}
|
||||
<script type="text/javascript" src="js/zerobin.js?{$VERSION|rawurlencode}"></script>
|
||||
<!--[if lt IE 10]>
|
||||
<style type="text/css">#ienotice {display:block !important;} #oldienotice {display:block !important;}</style>
|
||||
<![endif]-->
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="img/favicons/apple-touch-icon.png?{$VERSION|rawurlencode}" />
|
||||
<link rel="icon" type="image/png" href="img/favicons/android-chrome-192x192.png?{$VERSION|rawurlencode}" sizes="192x192" />
|
||||
<link rel="icon" type="image/png" href="img/favicons/favicon-32x32.png?{$VERSION|rawurlencode}" sizes="32x32" />
|
||||
<link rel="icon" type="image/png" href="img/favicons/favicon-96x96.png?{$VERSION|rawurlencode}" sizes="96x96" />
|
||||
<link rel="icon" type="image/png" href="img/favicons/favicon-16x16.png?{$VERSION|rawurlencode}" sizes="16x16" />
|
||||
</head>
|
||||
<body role="document">
|
||||
<nav class="navbar navbar-inverse navbar-static-top">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
|
||||
<span class="sr-only">{function="t('Toggle navigation')"}</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="reloadlink navbar-brand" href="/">{function="t('ZeroBin')"}</a>
|
||||
</div>
|
||||
<div id="navbar" class="navbar-collapse collapse">
|
||||
<ul class="nav navbar-nav">
|
||||
<li>
|
||||
<button id="newbutton" type="button" class="reloadlink hidden btn btn-warning navbar-btn">
|
||||
<span class="glyphicon glyphicon-file" aria-hidden="true"></span> {function="t('New')"}
|
||||
</button>{if="$EXPIRECLONE"}
|
||||
<button id="clonebutton" type="button" class="hidden btn btn-warning navbar-btn">
|
||||
<span class="glyphicon glyphicon-duplicate" aria-hidden="true"></span> {function="t('Clone')"}
|
||||
</button>{/if}
|
||||
<button id="rawtextbutton" type="button" class="hidden btn btn-warning navbar-btn">
|
||||
<span class="glyphicon glyphicon-text-background" aria-hidden="true"></span> {function="t('Raw text')"}
|
||||
</button>
|
||||
</li>
|
||||
<li class="dropdown">
|
||||
<select id="pasteExpiration" name="pasteExpiration" class="hidden">
|
||||
{loop="EXPIRE"}
|
||||
<option value="{$key}"{if="$key == $EXPIREDEFAULT"} selected="selected"{/if}>{$value}</option>{/loop}
|
||||
</select>
|
||||
<a id="expiration" href="#" class="hidden dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{function="t('Expires')"}: <span id="pasteExpirationDisplay">{$EXPIRE[$EXPIREDEFAULT]}</span> <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
{loop="EXPIRE"}
|
||||
<li>
|
||||
<a href="#" onclick="$('#pasteExpiration').val('{$key}');$('#pasteExpirationDisplay').text('{$value}');return false;">
|
||||
{$value}
|
||||
</a>
|
||||
</li>{/loop}
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<div id="burnafterreadingoption" class="navbar-text checkbox hidden">
|
||||
<label>
|
||||
<input type="checkbox" id="burnafterreading" name="burnafterreading" {if="$BURNAFTERREADINGSELECTED"} checked="checked"{/if} />
|
||||
{function="t('Burn after reading')"}
|
||||
</label>
|
||||
</div>
|
||||
</li>{if="$DISCUSSION"}
|
||||
<li>
|
||||
<div id="opendisc" class="navbar-text checkbox hidden">
|
||||
<label>
|
||||
<input type="checkbox" id="opendiscussion" name="opendiscussion" {if="$OPENDISCUSSION"} checked="checked"{/if} />
|
||||
{function="t('Open discussion')"}
|
||||
</label>
|
||||
</div>
|
||||
</li>{/if}{if="$PASSWORD"}
|
||||
<li>
|
||||
<div id="password" class="navbar-form hidden">
|
||||
<input type="password" id="passwordinput" placeholder="{function="t('Password (recommended)')"}" class="form-control" size="19"/>
|
||||
</div>
|
||||
</li>{/if}{if="$FILEUPLOAD"}
|
||||
<li id="attach" class="hidden dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{function="t('Attach a file')"} <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li id="filewrap">
|
||||
<div>
|
||||
<input type="file" id="file" name="file" />
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<a id="fileremovebutton" href="#">
|
||||
{function="t('Remove attachment')"}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>{/if}
|
||||
<li class="dropdown">
|
||||
<select id="pasteFormatter" name="pasteFormatter" class="hidden">
|
||||
{loop="FORMATTER"}
|
||||
<option value="{$key}"{if="$key == $FORMATTERDEFAULT"} selected="selected"{/if}>{$value}</option>{/loop}
|
||||
</select>
|
||||
<a id="formatter" href="#" class="hidden dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{function="t('Format')"}: <span id="pasteFormatterDisplay">{$FORMATTER[$FORMATTERDEFAULT]}</span> <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
{loop="FORMATTER"}
|
||||
<li>
|
||||
<a href="#" onclick="$('#pasteFormatter').val('{$key}');$('#pasteFormatterDisplay').text('{$value}');return false;">
|
||||
{$value}
|
||||
</a>
|
||||
</li>{/loop}
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="nav navbar-nav pull-right">{if="strlen($LANGUAGESELECTION)"}
|
||||
<li id="language" class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><span class="glyphicon glyphicon-flag" aria-hidden="true"></span> {$LANGUAGES[$LANGUAGESELECTION][0]} <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
{loop="LANGUAGES"}
|
||||
<li>
|
||||
<a href="#" class="reloadlink" onclick="document.cookie='lang={$key}';">
|
||||
{$value[0]} ({$value[1]})
|
||||
</a>
|
||||
</li>{/loop}
|
||||
</ul>
|
||||
</li>{/if}
|
||||
<li>
|
||||
<button id="sendbutton" type="button" class="hidden btn btn-warning navbar-btn">
|
||||
<span class="glyphicon glyphicon-upload" aria-hidden="true"></span> {function="t('Send')"}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
<header class="container">{if="strlen($NOTICE)"}
|
||||
<div role="alert" class="alert alert-info">
|
||||
<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span> {$NOTICE|htmlspecialchars}
|
||||
</div>{/if}
|
||||
<div id="remainingtime" role="alert" class="hidden alert alert-info">
|
||||
<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>
|
||||
</div>{if="$FILEUPLOAD"}
|
||||
<div id="attachment" role="alert" class="hidden alert alert-info">
|
||||
<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span> <a>{function="t('Download attachment')"}</a> <span id="clonedfile" class="hidden">{function="t('Cloned file attached.')"}</span>
|
||||
</div>{/if}{if="strlen($STATUS)"}
|
||||
<div id="status" role="alert" class="alert alert-success">
|
||||
<span class="glyphicon glyphicon-ok" aria-hidden="true"></span> {$STATUS|htmlspecialchars}
|
||||
</div>{/if}
|
||||
<div id="errormessage" role="alert" class="{if="!strlen($ERROR)"}hidden {/if}alert alert-danger"><span class="glyphicon glyphicon-alert" aria-hidden="true"></span> {$ERROR|htmlspecialchars}</div>
|
||||
<div id="noscript" role="alert" class="nonworking alert alert-error"><span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span> {function="t('Javascript is required for ZeroBin to work.<br />Sorry for the inconvenience.')"}</div>
|
||||
<div id="oldienotice" role="alert" class="hidden nonworking alert alert-danger"><span class="glyphicon glyphicon-alert" aria-hidden="true"></span> {function="t('ZeroBin requires a modern browser to work.')"}</div>
|
||||
<div id="ienotice" role="alert" class="hidden alert alert-error"><span class="glyphicon glyphicon-question-sign" aria-hidden="true"></span> {function="t('Still using Internet Explorer? Do yourself a favor, switch to a modern browser:')"}
|
||||
<a href="http://www.mozilla.org/firefox/">Firefox</a>,
|
||||
<a href="http://www.opera.com/">Opera</a>,
|
||||
<a href="http://www.google.com/chrome">Chrome</a>,
|
||||
<a href="http://www.apple.com/safari">Safari</a>...
|
||||
</div>
|
||||
<div id="pasteresult" role="alert" class="hidden alert alert-success">
|
||||
<span class="glyphicon glyphicon-ok" aria-hidden="true"></span>
|
||||
<div id="deletelink"></div>
|
||||
<div id="pastelink"></div>
|
||||
</div>
|
||||
</header>
|
||||
<section class="container">
|
||||
<article class="row">
|
||||
<div id="image" class="col-md-12 text-center hidden"></div>
|
||||
<div id="prettymessage" class="col-md-12 hidden">
|
||||
<pre id="prettyprint" class="col-md-12 prettyprint linenums:1"></pre>
|
||||
</div>
|
||||
<div id="cleartext" class="col-md-12 hidden"></div>
|
||||
<p class="col-md-12"><textarea id="message" name="message" cols="80" rows="25" class="form-control hidden"></textarea></p>
|
||||
</article>
|
||||
</section>
|
||||
<section class="container">
|
||||
<div id="discussion" class="hidden">
|
||||
<h4>{function="t('Discussion')"}</h4>
|
||||
<div id="comments"></div>
|
||||
</div>
|
||||
</section>
|
||||
<footer class="container">
|
||||
<div class="row">
|
||||
<h4 class="col-md-5 col-xs-8">{function="t('ZeroBin')"} <small>- {function="t('Because ignorance is bliss')"}</small></h4>
|
||||
<p class="col-md-1 col-xs-4 text-center">{$VERSION}</p>
|
||||
<p id="aboutbox" class="col-md-6 col-xs-12">
|
||||
{function="t('ZeroBin is a minimalist, opensource online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted <i>in the browser</i> using 256 bits AES. More information on the <a href="https://github.com/elrido/ZeroBin/wiki">project page</a>.')"}
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
<div id="cipherdata" class="hidden">{$CIPHERDATA}</div>
|
||||
</body>
|
||||
</html>
|
||||
198
tpl/bootstrap-page.html
Normal file
198
tpl/bootstrap-page.html
Normal file
@@ -0,0 +1,198 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="robots" content="noindex" />
|
||||
<title>{function="t('ZeroBin')"}</title>
|
||||
<link type="text/css" rel="stylesheet" href="css/bootstrap/bootstrap-3.3.5.css" />
|
||||
<link type="text/css" rel="stylesheet" href="css/bootstrap/bootstrap-theme-3.3.5.css" />
|
||||
<link type="text/css" rel="stylesheet" href="css/bootstrap/zerobin.css?{$VERSION|rawurlencode}" />{if="$SYNTAXHIGHLIGHTING"}
|
||||
<link type="text/css" rel="stylesheet" href="css/prettify/prettify.css?{$VERSION|rawurlencode}" />{if="strlen($SYNTAXHIGHLIGHTINGTHEME)"}
|
||||
<link type="text/css" rel="stylesheet" href="css/prettify/{$SYNTAXHIGHLIGHTINGTHEME}.css?{$VERSION|rawurlencode}" />{/if}{/if}
|
||||
<script type="text/javascript" src="js/jquery-1.11.3.js"></script>
|
||||
<script type="text/javascript" src="js/sjcl-1.0.2.js"></script>
|
||||
<script type="text/javascript" src="js/base64-{$BASE64JSVERSION}.js"></script>
|
||||
<script type="text/javascript" src="js/rawdeflate-0.5.js"></script>
|
||||
<script type="text/javascript" src="js/rawinflate-0.3.js"></script>
|
||||
<script type="text/javascript" src="js/bootstrap-3.3.5.js"></script>{if="$SYNTAXHIGHLIGHTING"}
|
||||
<script type="text/javascript" src="js/prettify.js?{$VERSION|rawurlencode}"></script>{/if}{if="$MARKDOWN"}
|
||||
<script type="text/javascript" src="js/showdown.js?{$VERSION|rawurlencode}"></script>{/if}
|
||||
<script type="text/javascript" src="js/zerobin.js?{$VERSION|rawurlencode}"></script>
|
||||
<!--[if lt IE 10]>
|
||||
<style type="text/css">#ienotice {display:block !important;} #oldienotice {display:block !important;}</style>
|
||||
<![endif]-->
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="img/favicons/apple-touch-icon.png?{$VERSION|rawurlencode}" />
|
||||
<link rel="icon" type="image/png" href="img/favicons/android-chrome-192x192.png?{$VERSION|rawurlencode}" sizes="192x192" />
|
||||
<link rel="icon" type="image/png" href="img/favicons/favicon-32x32.png?{$VERSION|rawurlencode}" sizes="32x32" />
|
||||
<link rel="icon" type="image/png" href="img/favicons/favicon-96x96.png?{$VERSION|rawurlencode}" sizes="96x96" />
|
||||
<link rel="icon" type="image/png" href="img/favicons/favicon-16x16.png?{$VERSION|rawurlencode}" sizes="16x16" />
|
||||
</head>
|
||||
<body role="document">
|
||||
<nav class="navbar navbar-default navbar-static-top">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
|
||||
<span class="sr-only">{function="t('Toggle navigation')"}</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="reloadlink navbar-brand" href="/">{function="t('ZeroBin')"}</a>
|
||||
</div>
|
||||
<div id="navbar" class="navbar-collapse collapse">
|
||||
<ul class="nav navbar-nav">
|
||||
<li>
|
||||
<button id="sendbutton" type="button" class="hidden btn btn-default navbar-btn">
|
||||
<span class="glyphicon glyphicon-upload" aria-hidden="true"></span> {function="t('Send')"}
|
||||
</button>{if="$EXPIRECLONE"}
|
||||
<button id="clonebutton" type="button" class="hidden btn btn-default navbar-btn">
|
||||
<span class="glyphicon glyphicon-duplicate" aria-hidden="true"></span> {function="t('Clone')"}
|
||||
</button>{/if}
|
||||
<button id="rawtextbutton" type="button" class="hidden btn btn-default navbar-btn">
|
||||
<span class="glyphicon glyphicon-text-background" aria-hidden="true"></span> {function="t('Raw text')"}
|
||||
</button>
|
||||
</li>
|
||||
<li class="dropdown">
|
||||
<select id="pasteExpiration" name="pasteExpiration" class="hidden">
|
||||
{loop="EXPIRE"}
|
||||
<option value="{$key}"{if="$key == $EXPIREDEFAULT"} selected="selected"{/if}>{$value}</option>{/loop}
|
||||
</select>
|
||||
<a id="expiration" href="#" class="hidden dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{function="t('Expires')"}: <span id="pasteExpirationDisplay">{$EXPIRE[$EXPIREDEFAULT]}</span> <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
{loop="EXPIRE"}
|
||||
<li>
|
||||
<a href="#" onclick="$('#pasteExpiration').val('{$key}');$('#pasteExpirationDisplay').text('{$value}');return false;">
|
||||
{$value}
|
||||
</a>
|
||||
</li>{/loop}
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<div id="burnafterreadingoption" class="navbar-text checkbox hidden">
|
||||
<label>
|
||||
<input type="checkbox" id="burnafterreading" name="burnafterreading" {if="$BURNAFTERREADINGSELECTED"} checked="checked"{/if} />
|
||||
{function="t('Burn after reading')"}
|
||||
</label>
|
||||
</div>
|
||||
</li>{if="$DISCUSSION"}
|
||||
<li>
|
||||
<div id="opendisc" class="navbar-text checkbox hidden">
|
||||
<label>
|
||||
<input type="checkbox" id="opendiscussion" name="opendiscussion" {if="$OPENDISCUSSION"} checked="checked"{/if} />
|
||||
{function="t('Open discussion')"}
|
||||
</label>
|
||||
</div>
|
||||
</li>{/if}{if="$PASSWORD"}
|
||||
<li>
|
||||
<div id="password" class="navbar-form hidden">
|
||||
<input type="password" id="passwordinput" placeholder="{function="t('Password (recommended)')"}" class="form-control" size="19"/>
|
||||
</div>
|
||||
</li>{/if}{if="$FILEUPLOAD"}
|
||||
<li id="attach" class="hidden dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{function="t('Attach a file')"} <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li id="filewrap">
|
||||
<div>
|
||||
<input type="file" id="file" name="file" />
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<a id="fileremovebutton" href="#">
|
||||
{function="t('Remove attachment')"}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>{/if}
|
||||
<li class="dropdown">
|
||||
<select id="pasteFormatter" name="pasteFormatter" class="hidden">
|
||||
{loop="FORMATTER"}
|
||||
<option value="{$key}"{if="$key == $FORMATTERDEFAULT"} selected="selected"{/if}>{$value}</option>{/loop}
|
||||
</select>
|
||||
<a id="formatter" href="#" class="hidden dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{function="t('Format')"}: <span id="pasteFormatterDisplay">{$FORMATTER[$FORMATTERDEFAULT]}</span> <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
{loop="FORMATTER"}
|
||||
<li>
|
||||
<a href="#" onclick="$('#pasteFormatter').val('{$key}');$('#pasteFormatterDisplay').text('{$value}');return false;">
|
||||
{$value}
|
||||
</a>
|
||||
</li>{/loop}
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="nav navbar-nav pull-right">{if="strlen($LANGUAGESELECTION)"}
|
||||
<li id="language" class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><span class="glyphicon glyphicon-flag" aria-hidden="true"></span> {$LANGUAGES[$LANGUAGESELECTION][0]} <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
{loop="LANGUAGES"}
|
||||
<li>
|
||||
<a href="#" class="reloadlink" onclick="document.cookie='lang={$key}';">
|
||||
{$value[0]} ({$value[1]})
|
||||
</a>
|
||||
</li>{/loop}
|
||||
</ul>
|
||||
</li>{/if}
|
||||
<li>
|
||||
<button id="newbutton" type="button" class="reloadlink hidden btn btn-default navbar-btn">
|
||||
<span class="glyphicon glyphicon-file" aria-hidden="true"></span> {function="t('New')"}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
<header class="container">{if="strlen($NOTICE)"}
|
||||
<div role="alert" class="alert alert-info">
|
||||
<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span> {$NOTICE|htmlspecialchars}
|
||||
</div>{/if}
|
||||
<div id="remainingtime" role="alert" class="hidden alert alert-info">
|
||||
<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>
|
||||
</div>{if="$FILEUPLOAD"}
|
||||
<div id="attachment" role="alert" class="hidden alert alert-info">
|
||||
<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span> <a>{function="t('Download attachment')"}</a> <span id="clonedfile" class="hidden">{function="t('Cloned file attached.')"}</span>
|
||||
</div>{/if}{if="strlen($STATUS)"}
|
||||
<div id="status" role="alert" class="alert alert-success">
|
||||
<span class="glyphicon glyphicon-ok" aria-hidden="true"></span> {$STATUS|htmlspecialchars}
|
||||
</div>{/if}
|
||||
<div id="errormessage" role="alert" class="{if="!strlen($ERROR)"}hidden {/if}alert alert-danger"><span class="glyphicon glyphicon-alert" aria-hidden="true"></span> {$ERROR|htmlspecialchars}</div>
|
||||
<div id="noscript" role="alert" class="nonworking alert alert-warning"><span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span> {function="t('Javascript is required for ZeroBin to work.<br />Sorry for the inconvenience.')"}</div>
|
||||
<div id="oldienotice" role="alert" class="hidden nonworking alert alert-danger"><span class="glyphicon glyphicon-alert" aria-hidden="true"></span> {function="t('ZeroBin requires a modern browser to work.')"}</div>
|
||||
<div id="ienotice" role="alert" class="hidden alert alert-warning"><span class="glyphicon glyphicon-question-sign" aria-hidden="true"></span> {function="t('Still using Internet Explorer? Do yourself a favor, switch to a modern browser:')"}
|
||||
<a href="http://www.mozilla.org/firefox/">Firefox</a>,
|
||||
<a href="http://www.opera.com/">Opera</a>,
|
||||
<a href="http://www.google.com/chrome">Chrome</a>,
|
||||
<a href="http://www.apple.com/safari">Safari</a>...
|
||||
</div>
|
||||
<div id="pasteresult" role="alert" class="hidden alert alert-success">
|
||||
<span class="glyphicon glyphicon-ok" aria-hidden="true"></span>
|
||||
<div id="deletelink"></div>
|
||||
<div id="pastelink"></div>
|
||||
</div>
|
||||
</header>
|
||||
<section class="container">
|
||||
<article class="row">
|
||||
<div id="image" class="col-md-12 text-center hidden"></div>
|
||||
<div id="prettymessage" class="col-md-12 hidden">
|
||||
<pre id="prettyprint" class="col-md-12 prettyprint linenums:1"></pre>
|
||||
</div>
|
||||
<div id="cleartext" class="col-md-12 hidden"></div>
|
||||
<p class="col-md-12"><textarea id="message" name="message" cols="80" rows="25" class="form-control hidden"></textarea></p>
|
||||
</article>
|
||||
</section>
|
||||
<section class="container">
|
||||
<div id="discussion" class="hidden">
|
||||
<h4>{function="t('Discussion')"}</h4>
|
||||
<div id="comments"></div>
|
||||
</div>
|
||||
</section>
|
||||
<footer class="container">
|
||||
<div class="row">
|
||||
<h4 class="col-md-5 col-xs-8">{function="t('ZeroBin')"} <small>- {function="t('Because ignorance is bliss')"}</small></h4>
|
||||
<p class="col-md-1 col-xs-4 text-center">{$VERSION}</p>
|
||||
<p id="aboutbox" class="col-md-6 col-xs-12">
|
||||
{function="t('ZeroBin is a minimalist, opensource online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted <i>in the browser</i> using 256 bits AES. More information on the <a href="https://github.com/elrido/ZeroBin/wiki">project page</a>.')"}
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
<div id="cipherdata" class="hidden">{$CIPHERDATA}</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -23,6 +23,11 @@
|
||||
<!--[if lt IE 10]>
|
||||
<style type="text/css">#ienotice {display:block !important;} #oldienotice {display:block !important;}</style>
|
||||
<![endif]-->
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="img/favicons/apple-touch-icon.png?{$VERSION|rawurlencode}" />
|
||||
<link rel="icon" type="image/png" href="img/favicons/android-chrome-192x192.png?{$VERSION|rawurlencode}" sizes="192x192" />
|
||||
<link rel="icon" type="image/png" href="img/favicons/favicon-32x32.png?{$VERSION|rawurlencode}" sizes="32x32" />
|
||||
<link rel="icon" type="image/png" href="img/favicons/favicon-96x96.png?{$VERSION|rawurlencode}" sizes="96x96" />
|
||||
<link rel="icon" type="image/png" href="img/favicons/favicon-16x16.png?{$VERSION|rawurlencode}" sizes="16x16" />
|
||||
</head>
|
||||
<body role="document">
|
||||
<nav class="navbar navbar-default navbar-static-top">
|
||||
@@ -38,12 +43,12 @@
|
||||
<div id="navbar" class="navbar-collapse collapse">
|
||||
<ul class="nav navbar-nav">
|
||||
<li>
|
||||
<button id="sendbutton" type="button" class="hidden btn btn-default navbar-btn">
|
||||
<span class="glyphicon glyphicon-upload" aria-hidden="true"></span> {function="t('Send')"}
|
||||
</button>
|
||||
<button id="newbutton" type="button" class="reloadlink hidden btn btn-default navbar-btn">
|
||||
<span class="glyphicon glyphicon-file" aria-hidden="true"></span> {function="t('New')"}
|
||||
</button>{if="$EXPIRECLONE"}
|
||||
<button id="clonebutton" type="button" class="hidden btn btn-default navbar-btn">
|
||||
<span class="glyphicon glyphicon-duplicate" aria-hidden="true"></span> {function="t('Clone')"}
|
||||
</button>
|
||||
</button>{/if}
|
||||
<button id="rawtextbutton" type="button" class="hidden btn btn-default navbar-btn">
|
||||
<span class="glyphicon glyphicon-text-background" aria-hidden="true"></span> {function="t('Raw text')"}
|
||||
</button>
|
||||
@@ -128,8 +133,8 @@
|
||||
</ul>
|
||||
</li>{/if}
|
||||
<li>
|
||||
<button id="newbutton" type="button" class="reloadlink hidden btn btn-default navbar-btn">
|
||||
<span class="glyphicon glyphicon-file" aria-hidden="true"></span> {function="t('New')"}
|
||||
<button id="sendbutton" type="button" class="hidden btn btn-primary navbar-btn">
|
||||
<span class="glyphicon glyphicon-upload" aria-hidden="true"></span> {function="t('Send')"}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -18,6 +18,11 @@
|
||||
<!--[if lt IE 10]>
|
||||
<style type="text/css">body {padding-left:60px;padding-right:60px;} #ienotice {display:block;} #oldienotice {display:block;}</style>
|
||||
<![endif]-->
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="img/favicons/apple-touch-icon.png?{$VERSION|rawurlencode}" />
|
||||
<link rel="icon" type="image/png" href="img/favicons/android-chrome-192x192.png?{$VERSION|rawurlencode}" sizes="192x192" />
|
||||
<link rel="icon" type="image/png" href="img/favicons/favicon-32x32.png?{$VERSION|rawurlencode}" sizes="32x32" />
|
||||
<link rel="icon" type="image/png" href="img/favicons/favicon-96x96.png?{$VERSION|rawurlencode}" sizes="96x96" />
|
||||
<link rel="icon" type="image/png" href="img/favicons/favicon-16x16.png?{$VERSION|rawurlencode}" sizes="16x16" />
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
@@ -43,8 +48,8 @@
|
||||
<div id="errormessage" class="hidden">{$ERROR|htmlspecialchars}</div>
|
||||
<div id="toolbar">
|
||||
<button id="newbutton" class="reloadlink hidden"><img src="img/icon_new.png" width="11" height="15" alt="" />{function="t('New')"}</button>
|
||||
<button id="sendbutton" class="hidden"><img src="img/icon_send.png" width="18" height="15" alt="" />{function="t('Send')"}</button>
|
||||
<button id="clonebutton" class="hidden"><img src="img/icon_clone.png" width="15" height="17" alt="" />{function="t('Clone')"}</button>
|
||||
<button id="sendbutton" class="hidden"><img src="img/icon_send.png" width="18" height="15" alt="" />{function="t('Send')"}</button>{if="$EXPIRECLONE"}
|
||||
<button id="clonebutton" class="hidden"><img src="img/icon_clone.png" width="15" height="17" alt="" />{function="t('Clone')"}</button>{/if}
|
||||
<button id="rawtextbutton" class="hidden"><img src="img/icon_raw.png" width="15" height="15" alt="" />{function="t('Raw text')"}</button>
|
||||
<div id="expiration" class="hidden button">{function="t('Expires')"}:
|
||||
<select id="pasteExpiration" name="pasteExpiration">
|
||||
|
||||
2
tst/.gitignore
vendored
2
tst/.gitignore
vendored
@@ -1 +1 @@
|
||||
/configuration.php
|
||||
/configurationCombinations.php
|
||||
|
||||
@@ -43,6 +43,7 @@ class RainTPLTest extends PHPUnit_Framework_TestCase
|
||||
$page->assign('LANGUAGES', i18n::getLanguageLabels(i18n::getAvailableLanguages()));
|
||||
$page->assign('EXPIRE', self::$expire);
|
||||
$page->assign('EXPIREDEFAULT', self::$expire_default);
|
||||
$page->assign('EXPIRECLONE', true);
|
||||
ob_start();
|
||||
$page->draw('page');
|
||||
$this->_content = ob_get_contents();
|
||||
|
||||
@@ -3,6 +3,7 @@ error_reporting( E_ALL | E_STRICT );
|
||||
|
||||
// change this, if your php files and data is outside of your webservers document root
|
||||
if (!defined('PATH')) define('PATH', '..' . DIRECTORY_SEPARATOR);
|
||||
if (!defined('CONF')) define('CONF', PATH . 'cfg' . DIRECTORY_SEPARATOR . 'conf.ini');
|
||||
if (!defined('PUBLIC_PATH')) define('PUBLIC_PATH', '..');
|
||||
|
||||
require PATH . 'lib/auto.php';
|
||||
@@ -23,12 +24,12 @@ class helper
|
||||
*/
|
||||
private static $paste = array(
|
||||
'data' => '{"iv":"EN39/wd5Nk8HAiSG2K5AsQ","v":1,"iter":1000,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"QKN1DBXe5PI","ct":"8hA83xDdXjD7K2qfmw5NdA"}',
|
||||
'attachment' => '{"iv":"Pd4pOKWkmDTT9uPwVwd5Ag","v":1,"iter":1000,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"ZIUhFTliVz4","ct":"6nOCU3peNDclDDpFtJEBKA"}',
|
||||
'attachmentname' => '{"iv":"76MkAtOGC4oFogX/aSMxRA","v":1,"iter":1000,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"ZIUhFTliVz4","ct":"b6Ae/U1xJdsX/+lATud4sQ"}',
|
||||
'meta' => array(
|
||||
'formatter' => 'plaintext',
|
||||
'postdate' => 1344803344,
|
||||
'opendiscussion' => true,
|
||||
'formatter' => 'plaintext',
|
||||
'attachment' => '{"iv":"Pd4pOKWkmDTT9uPwVwd5Ag","v":1,"iter":1000,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"ZIUhFTliVz4","ct":"6nOCU3peNDclDDpFtJEBKA"}',
|
||||
'attachmentname' => '{"iv":"76MkAtOGC4oFogX/aSMxRA","v":1,"iter":1000,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"ZIUhFTliVz4","ct":"b6Ae/U1xJdsX/+lATud4sQ"}',
|
||||
),
|
||||
);
|
||||
|
||||
@@ -63,19 +64,46 @@ class helper
|
||||
return self::$pasteid;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* get example paste
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getPaste($meta = array())
|
||||
{
|
||||
$example = self::getPasteWithAttachment($meta);
|
||||
unset($example['attachment'], $example['attachmentname']);
|
||||
return $example;
|
||||
}
|
||||
|
||||
/**
|
||||
* get example paste
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getPasteWithAttachment($meta = array())
|
||||
{
|
||||
$example = self::$paste;
|
||||
$example['meta'] = array_merge($example['meta'], $meta);
|
||||
return $example;
|
||||
}
|
||||
|
||||
/**
|
||||
* get example paste
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getPasteAsJson($meta = array())
|
||||
{
|
||||
$example = self::getPaste();
|
||||
if (count($meta))
|
||||
$example['meta'] = $meta;
|
||||
$example['comments'] = array();
|
||||
$example['comment_count'] = 0;
|
||||
$example['comment_offset'] = 0;
|
||||
$example['@context'] = 'js/paste.jsonld';
|
||||
return json_encode($example);
|
||||
}
|
||||
|
||||
/**
|
||||
* get example paste ID
|
||||
@@ -87,7 +115,6 @@ class helper
|
||||
return self::$commentid;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* get example comment
|
||||
*
|
||||
@@ -100,6 +127,19 @@ class helper
|
||||
return $example;
|
||||
}
|
||||
|
||||
/**
|
||||
* get example comment
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getCommentPost($meta = array())
|
||||
{
|
||||
$example = self::getComment($meta);
|
||||
$example['nickname'] = $example['meta']['nickname'];
|
||||
unset($example['meta']['nickname']);
|
||||
return $example;
|
||||
}
|
||||
|
||||
/**
|
||||
* delete directory and all its contents recursively
|
||||
*
|
||||
@@ -127,6 +167,28 @@ class helper
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* create a backup of the config file
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function confBackup()
|
||||
{
|
||||
if (!is_file(CONF . '.bak') && is_file(CONF))
|
||||
rename(CONF, CONF . '.bak');
|
||||
}
|
||||
|
||||
/**
|
||||
* restor backup of the config file
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function confRestore()
|
||||
{
|
||||
if (is_file(CONF . '.bak'))
|
||||
rename(CONF . '.bak', CONF);
|
||||
}
|
||||
|
||||
/**
|
||||
* create ini file
|
||||
*
|
||||
@@ -145,6 +207,18 @@ class helper
|
||||
continue;
|
||||
} elseif (is_string($setting)) {
|
||||
$setting = '"' . $setting . '"';
|
||||
} elseif (is_array($setting)) {
|
||||
foreach ($setting as $key => $value) {
|
||||
if (is_null($value)) {
|
||||
$value = 'null';
|
||||
} elseif (is_string($value)) {
|
||||
$value = '"' . $value . '"';
|
||||
} else {
|
||||
$value = var_export($value, true);
|
||||
}
|
||||
fwrite($ini, $option . "[$key] = $value" . PHP_EOL);
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
$setting = var_export($setting, true);
|
||||
}
|
||||
|
||||
@@ -108,134 +108,6 @@ new configurationTestGenerator(array(
|
||||
'affects' => $vrd
|
||||
),
|
||||
),
|
||||
'main/defaultformatter' => array(
|
||||
array(
|
||||
'setting' => 'syntaxhighlighting',
|
||||
'tests' => array(
|
||||
array(
|
||||
'type' => 'Tag',
|
||||
'args' => array(
|
||||
array(
|
||||
'tag' => 'link',
|
||||
'attributes' => array(
|
||||
'type' => 'text/css',
|
||||
'rel' => 'stylesheet',
|
||||
'href' => 'regexp:#css/prettify/prettify\.css#',
|
||||
),
|
||||
),
|
||||
'$content',
|
||||
'outputs prettify stylesheet correctly',
|
||||
),
|
||||
), array(
|
||||
'type' => 'Tag',
|
||||
'args' => array(
|
||||
array(
|
||||
'tag' => 'script',
|
||||
'attributes' => array(
|
||||
'type' => 'text/javascript',
|
||||
'src' => 'regexp:#js/prettify\.js#'
|
||||
),
|
||||
),
|
||||
'$content',
|
||||
'outputs prettify javascript correctly',
|
||||
),
|
||||
),
|
||||
),
|
||||
'affects' => $vrd,
|
||||
), array(
|
||||
'setting' => 'plaintext',
|
||||
'tests' => array(
|
||||
array(
|
||||
'type' => 'NotTag',
|
||||
'args' => array(
|
||||
array(
|
||||
'tag' => 'link',
|
||||
'attributes' => array(
|
||||
'type' => 'text/css',
|
||||
'rel' => 'stylesheet',
|
||||
'href' => 'regexp:#css/prettify/prettify\.css#',
|
||||
),
|
||||
),
|
||||
'$content',
|
||||
'removes prettify stylesheet correctly',
|
||||
),
|
||||
), array(
|
||||
'type' => 'NotTag',
|
||||
'args' => array(
|
||||
array(
|
||||
'tag' => 'script',
|
||||
'attributes' => array(
|
||||
'type' => 'text/javascript',
|
||||
'src' => 'regexp:#js/prettify\.js#',
|
||||
),
|
||||
),
|
||||
'$content',
|
||||
'removes prettify javascript correctly',
|
||||
),
|
||||
),
|
||||
),
|
||||
'affects' => $vrd,
|
||||
),
|
||||
),
|
||||
'main/syntaxhighlightingtheme' => array(
|
||||
array(
|
||||
'setting' => 'sons-of-obsidian',
|
||||
'tests' => array(
|
||||
array(
|
||||
'conditions' => array('main/syntaxhighlighting' => true),
|
||||
'type' => 'Tag',
|
||||
'args' => array(
|
||||
array(
|
||||
'tag' => 'link',
|
||||
'attributes' => array(
|
||||
'type' => 'text/css',
|
||||
'rel' => 'stylesheet',
|
||||
'href' => 'regexp:#css/prettify/sons-of-obsidian\.css#',
|
||||
),
|
||||
),
|
||||
'$content',
|
||||
'outputs prettify theme stylesheet correctly',
|
||||
),
|
||||
), array(
|
||||
'conditions' => array('main/syntaxhighlighting' => false),
|
||||
'type' => 'NotTag',
|
||||
'args' => array(
|
||||
array(
|
||||
'tag' => 'link',
|
||||
'attributes' => array(
|
||||
'type' => 'text/css',
|
||||
'rel' => 'stylesheet',
|
||||
'href' => 'regexp:#css/prettify/sons-of-obsidian\.css#',
|
||||
),
|
||||
),
|
||||
'$content',
|
||||
'removes prettify theme stylesheet correctly',
|
||||
),
|
||||
),
|
||||
),
|
||||
'affects' => $vrd,
|
||||
), array(
|
||||
'setting' => null, // option not set
|
||||
'tests' => array(
|
||||
array(
|
||||
'type' => 'NotTag',
|
||||
'args' => array(
|
||||
array(
|
||||
'tag' => 'link',
|
||||
'attributes' => array(
|
||||
'type' => 'text/css',
|
||||
'rel' => 'stylesheet',
|
||||
'href' => 'regexp:#css/prettify/sons-of-obsidian\.css#',
|
||||
),
|
||||
),
|
||||
'$content',
|
||||
'removes prettify theme stylesheet correctly',
|
||||
),
|
||||
),
|
||||
),
|
||||
'affects' => $vrd,
|
||||
),
|
||||
),
|
||||
'main/burnafterreadingselected' => array(
|
||||
array(
|
||||
'setting' => true,
|
||||
@@ -528,7 +400,7 @@ class configurationTestGenerator
|
||||
*/
|
||||
private function _writeConfigurationTest()
|
||||
{
|
||||
$defaultOptions = parse_ini_file(PATH . 'cfg' . DIRECTORY_SEPARATOR . 'conf.ini', true);
|
||||
$defaultOptions = parse_ini_file(CONF, true);
|
||||
$code = $this->_getHeader();
|
||||
foreach ($this->_configurations as $key => $conf) {
|
||||
$fullOptions = array_replace_recursive($defaultOptions, $conf['options']);
|
||||
@@ -576,7 +448,7 @@ class configurationTestGenerator
|
||||
}
|
||||
}
|
||||
$code .= '}' . PHP_EOL;
|
||||
file_put_contents('configuration.php', $code);
|
||||
file_put_contents('configurationCombinations.php', $code);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -589,9 +461,9 @@ class configurationTestGenerator
|
||||
return <<<'EOT'
|
||||
<?php
|
||||
/**
|
||||
* DO NOT EDIT: This file is automatically generated by configGenerator.php
|
||||
* DO NOT EDIT: This file is generated automatically using configGenerator.php
|
||||
*/
|
||||
class configurationTest extends PHPUnit_Framework_TestCase
|
||||
class configurationCombinationsTest extends PHPUnit_Framework_TestCase
|
||||
{
|
||||
private $_model;
|
||||
|
||||
@@ -600,9 +472,7 @@ class configurationTest extends PHPUnit_Framework_TestCase
|
||||
public function setUp()
|
||||
{
|
||||
/* Setup Routine */
|
||||
$this->_conf = PATH . 'cfg' . DIRECTORY_SEPARATOR . 'conf.ini';
|
||||
if (!is_file($this->_conf . '.bak') && is_file($this->_conf))
|
||||
rename($this->_conf, $this->_conf . '.bak');
|
||||
helper::confBackup();
|
||||
|
||||
$this->_model = zerobin_data::getInstance(array('dir' => PATH . 'data'));
|
||||
serversalt::setPath(PATH . 'data');
|
||||
@@ -612,7 +482,7 @@ class configurationTest extends PHPUnit_Framework_TestCase
|
||||
public function tearDown()
|
||||
{
|
||||
/* Tear Down Routine */
|
||||
rename($this->_conf . '.bak', $this->_conf);
|
||||
helper::confRestore();
|
||||
}
|
||||
|
||||
public function reset($configuration = array())
|
||||
@@ -622,7 +492,7 @@ class configurationTest extends PHPUnit_Framework_TestCase
|
||||
$_SERVER = array();
|
||||
if ($this->_model->exists(helper::getPasteId()))
|
||||
$this->_model->delete(helper::getPasteId());
|
||||
helper::createIniFile($this->_conf, $configuration);
|
||||
helper::createIniFile(CONF, $configuration);
|
||||
}
|
||||
|
||||
|
||||
@@ -669,6 +539,8 @@ EOT;
|
||||
case 'Create':
|
||||
$code .= PHP_EOL . <<<'EOT'
|
||||
$_POST = helper::getPaste();
|
||||
$_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
|
||||
$_SERVER['REQUEST_METHOD'] = 'POST';
|
||||
$_SERVER['REMOTE_ADDR'] = '::1';
|
||||
EOT;
|
||||
break;
|
||||
@@ -710,7 +582,7 @@ EOT;
|
||||
$this->assertTag(
|
||||
array(
|
||||
'id' => 'cipherdata',
|
||||
'content' => htmlspecialchars(json_encode(helper::getPaste()), ENT_NOQUOTES)
|
||||
'content' => htmlspecialchars(helper::getPasteAsJson(), ENT_NOQUOTES)
|
||||
),
|
||||
$content,
|
||||
'outputs data correctly'
|
||||
|
||||
161
tst/configuration.php
Normal file
161
tst/configuration.php
Normal file
@@ -0,0 +1,161 @@
|
||||
<?php
|
||||
class configurationTest extends PHPUnit_Framework_TestCase
|
||||
{
|
||||
private $_options = array(
|
||||
'main' => array(
|
||||
'discussion' => true,
|
||||
'opendiscussion' => false,
|
||||
'password' => true,
|
||||
'fileupload' => false,
|
||||
'burnafterreadingselected' => false,
|
||||
'defaultformatter' => 'plaintext',
|
||||
'syntaxhighlightingtheme' => null,
|
||||
'sizelimit' => 2097152,
|
||||
'template' => 'bootstrap',
|
||||
'notice' => '',
|
||||
'base64version' => '2.1.9',
|
||||
'languageselection' => false,
|
||||
'languagedefault' => '',
|
||||
),
|
||||
'expire' => array(
|
||||
'default' => '1week',
|
||||
'clone' => true,
|
||||
),
|
||||
'expire_options' => array(
|
||||
'5min' => 300,
|
||||
'10min' => 600,
|
||||
'1hour' => 3600,
|
||||
'1day' => 86400,
|
||||
'1week' => 604800,
|
||||
'1month' => 2592000,
|
||||
'1year' => 31536000,
|
||||
'never' => 0,
|
||||
),
|
||||
'formatter_options' => array(
|
||||
'plaintext' => 'Plain Text',
|
||||
'syntaxhighlighting' => 'Source Code',
|
||||
'markdown' => 'Markdown',
|
||||
),
|
||||
'traffic' => array(
|
||||
'limit' => 10,
|
||||
'header' => null,
|
||||
'dir' => '../data',
|
||||
),
|
||||
'model' => array(
|
||||
'class' => 'zerobin_data',
|
||||
),
|
||||
'model_options' => array(
|
||||
'dir' => '../data',
|
||||
),
|
||||
);
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
/* Setup Routine */
|
||||
helper::confBackup();
|
||||
}
|
||||
|
||||
public function tearDown()
|
||||
{
|
||||
/* Tear Down Routine */
|
||||
helper::confRestore();
|
||||
}
|
||||
|
||||
public function testDefaultConfigFile()
|
||||
{
|
||||
$this->assertTrue(copy(CONF . '.bak', CONF), 'copy default configuration file');
|
||||
$conf = new configuration;
|
||||
$this->assertEquals($this->_options, $conf->get(), 'default configuration is correct');
|
||||
}
|
||||
|
||||
public function testHandleFreshConfigFile()
|
||||
{
|
||||
helper::createIniFile(CONF, $this->_options);
|
||||
$conf = new configuration;
|
||||
$this->assertEquals($this->_options, $conf->get(), 'newly generated configuration is correct');
|
||||
}
|
||||
|
||||
public function testHandleMissingConfigFile()
|
||||
{
|
||||
@unlink(CONF);
|
||||
$conf = new configuration;
|
||||
$this->assertEquals($this->_options, $conf->get(), 'returns correct defaults on missing file');
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException Exception
|
||||
* @expectedExceptionCode 2
|
||||
*/
|
||||
public function testHandleBlankConfigFile()
|
||||
{
|
||||
file_put_contents(CONF, '');
|
||||
$conf = new configuration;
|
||||
}
|
||||
|
||||
public function testHandleMinimalConfigFile()
|
||||
{
|
||||
file_put_contents(CONF, '[main]' . PHP_EOL . '[model]');
|
||||
$conf = new configuration;
|
||||
$this->assertEquals($this->_options, $conf->get(), 'returns correct defaults on empty file');
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException Exception
|
||||
* @expectedExceptionCode 3
|
||||
*/
|
||||
public function testHandleInvalidSection()
|
||||
{
|
||||
file_put_contents(CONF, '[main]' . PHP_EOL . '[model]');
|
||||
$conf = new configuration;
|
||||
$conf->getKey('foo', 'bar');
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException Exception
|
||||
* @expectedExceptionCode 4
|
||||
*/
|
||||
public function testHandleInvalidKey()
|
||||
{
|
||||
file_put_contents(CONF, '[main]' . PHP_EOL . '[model]');
|
||||
$conf = new configuration;
|
||||
$conf->getKey('foo');
|
||||
}
|
||||
|
||||
public function testHandleGetKey()
|
||||
{
|
||||
file_put_contents(CONF, '[main]' . PHP_EOL . '[model]');
|
||||
$conf = new configuration;
|
||||
$this->assertEquals($this->_options['main']['sizelimit'], $conf->getKey('sizelimit'), 'get default size');
|
||||
}
|
||||
|
||||
public function testHandleWrongTypes()
|
||||
{
|
||||
$original_options = $this->_options;
|
||||
$original_options['main']['syntaxhighlightingtheme'] = 'foo';
|
||||
$options = $original_options;
|
||||
$options['main']['discussion'] = 'true';
|
||||
$options['main']['opendiscussion'] = 0;
|
||||
$options['main']['password'] = -1; // evaluates to TRUE
|
||||
$options['main']['fileupload'] = 'false';
|
||||
$options['expire_options']['foo'] = 'bar';
|
||||
$options['formatter_options'][] = 'foo';
|
||||
helper::createIniFile(CONF, $options);
|
||||
$conf = new configuration;
|
||||
$original_options['expire_options']['foo'] = intval('bar');
|
||||
$original_options['formatter_options'][0] = 'foo';
|
||||
$this->assertEquals($original_options, $conf->get(), 'incorrect types are corrected');
|
||||
}
|
||||
|
||||
public function testHandleMissingSubKeys()
|
||||
{
|
||||
$options = $this->_options;
|
||||
unset($options['expire_options']['1week']);
|
||||
unset($options['expire_options']['1year']);
|
||||
unset($options['expire_options']['never']);
|
||||
helper::createIniFile(CONF, $options);
|
||||
$conf = new configuration;
|
||||
$options['expire']['default'] = '5min';
|
||||
$this->assertEquals($options, $conf->get(), 'not overriding "missing" subkeys');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -62,13 +62,6 @@ class filterTest extends PHPUnit_Framework_TestCase
|
||||
$this->assertEquals('1.21 YiB', filter::size_humanreadable(1234 * $exponent));
|
||||
}
|
||||
|
||||
public function testPasteIdValidation()
|
||||
{
|
||||
$this->assertTrue(filter::is_valid_paste_id('a242ab7bdfb2581a'), 'valid paste id');
|
||||
$this->assertFalse(filter::is_valid_paste_id('foo'), 'invalid hex values');
|
||||
$this->assertFalse(filter::is_valid_paste_id('../bar/baz'), 'path attack');
|
||||
}
|
||||
|
||||
public function testSlowEquals()
|
||||
{
|
||||
$this->assertTrue(filter::slow_equals('foo', 'foo'), 'same string');
|
||||
|
||||
257
tst/jsonApi.php
Normal file
257
tst/jsonApi.php
Normal file
@@ -0,0 +1,257 @@
|
||||
<?php
|
||||
class jsonApiTest extends PHPUnit_Framework_TestCase
|
||||
{
|
||||
protected $_model;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
/* Setup Routine */
|
||||
$this->_model = zerobin_data::getInstance(array('dir' => PATH . 'data'));
|
||||
serversalt::setPath(PATH . 'data');
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
public function tearDown()
|
||||
{
|
||||
/* Tear Down Routine */
|
||||
helper::confRestore();
|
||||
}
|
||||
|
||||
public function reset()
|
||||
{
|
||||
$_POST = array();
|
||||
$_GET = array();
|
||||
$_SERVER = array();
|
||||
if ($this->_model->exists(helper::getPasteId()))
|
||||
$this->_model->delete(helper::getPasteId());
|
||||
helper::confRestore();
|
||||
}
|
||||
|
||||
/**
|
||||
* @runInSeparateProcess
|
||||
*/
|
||||
public function testCreate()
|
||||
{
|
||||
$this->reset();
|
||||
$options = parse_ini_file(CONF, true);
|
||||
$options['traffic']['limit'] = 0;
|
||||
helper::confBackup();
|
||||
helper::createIniFile(CONF, $options);
|
||||
$_POST = helper::getPaste();
|
||||
$_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
|
||||
$_SERVER['REQUEST_METHOD'] = 'POST';
|
||||
$_SERVER['REMOTE_ADDR'] = '::1';
|
||||
ob_start();
|
||||
new zerobin;
|
||||
$content = ob_get_contents();
|
||||
$response = json_decode($content, true);
|
||||
$this->assertEquals(0, $response['status'], 'outputs status');
|
||||
$this->assertEquals(
|
||||
hash_hmac('sha1', $response['id'], serversalt::get()),
|
||||
$response['deletetoken'],
|
||||
'outputs valid delete token'
|
||||
);
|
||||
$this->assertStringEndsWith('?' . $response['id'], $response['url'], 'returned URL points to new paste');
|
||||
$this->assertTrue($this->_model->exists($response['id']), 'paste exists after posting data');
|
||||
}
|
||||
|
||||
/**
|
||||
* @runInSeparateProcess
|
||||
*/
|
||||
public function testPut()
|
||||
{
|
||||
$this->reset();
|
||||
$options = parse_ini_file(CONF, true);
|
||||
$options['traffic']['limit'] = 0;
|
||||
helper::confBackup();
|
||||
helper::createIniFile(CONF, $options);
|
||||
$paste = helper::getPaste();
|
||||
unset($paste['meta']);
|
||||
$file = tempnam(sys_get_temp_dir(), 'FOO');
|
||||
file_put_contents($file, http_build_query($paste));
|
||||
request::setInputStream($file);
|
||||
$_SERVER['QUERY_STRING'] = helper::getPasteId();
|
||||
$_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
|
||||
$_SERVER['REQUEST_METHOD'] = 'PUT';
|
||||
$_SERVER['REMOTE_ADDR'] = '::1';
|
||||
ob_start();
|
||||
new zerobin;
|
||||
$content = ob_get_contents();
|
||||
$response = json_decode($content, true);
|
||||
$this->assertEquals(0, $response['status'], 'outputs status');
|
||||
$this->assertEquals(helper::getPasteId(), $response['id'], 'outputted paste ID matches input');
|
||||
$this->assertEquals(
|
||||
hash_hmac('sha1', $response['id'], serversalt::get()),
|
||||
$response['deletetoken'],
|
||||
'outputs valid delete token'
|
||||
);
|
||||
$this->assertStringEndsWith('?' . $response['id'], $response['url'], 'returned URL points to new paste');
|
||||
$this->assertTrue($this->_model->exists($response['id']), 'paste exists after posting data');
|
||||
}
|
||||
|
||||
/**
|
||||
* @runInSeparateProcess
|
||||
*/
|
||||
public function testDelete()
|
||||
{
|
||||
$this->reset();
|
||||
$this->_model->create(helper::getPasteId(), helper::getPaste());
|
||||
$this->assertTrue($this->_model->exists(helper::getPasteId()), 'paste exists before deleting data');
|
||||
$file = tempnam(sys_get_temp_dir(), 'FOO');
|
||||
file_put_contents($file, http_build_query(array(
|
||||
'deletetoken' => hash_hmac('sha1', helper::getPasteId(), serversalt::get()),
|
||||
)));
|
||||
request::setInputStream($file);
|
||||
$_SERVER['QUERY_STRING'] = helper::getPasteId();
|
||||
$_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
|
||||
$_SERVER['REQUEST_METHOD'] = 'DELETE';
|
||||
ob_start();
|
||||
new zerobin;
|
||||
$content = ob_get_contents();
|
||||
$response = json_decode($content, true);
|
||||
$this->assertEquals(0, $response['status'], 'outputs status');
|
||||
$this->assertFalse($this->_model->exists(helper::getPasteId()), 'paste successfully deleted');
|
||||
}
|
||||
|
||||
/**
|
||||
* @runInSeparateProcess
|
||||
*/
|
||||
public function testDeleteWithPost()
|
||||
{
|
||||
$this->reset();
|
||||
$this->_model->create(helper::getPasteId(), helper::getPaste());
|
||||
$this->assertTrue($this->_model->exists(helper::getPasteId()), 'paste exists before deleting data');
|
||||
$_POST = array(
|
||||
'action' => 'delete',
|
||||
'deletetoken' => hash_hmac('sha1', helper::getPasteId(), serversalt::get()),
|
||||
);
|
||||
$_SERVER['QUERY_STRING'] = helper::getPasteId();
|
||||
$_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
|
||||
$_SERVER['REQUEST_METHOD'] = 'POST';
|
||||
ob_start();
|
||||
new zerobin;
|
||||
$content = ob_get_contents();
|
||||
$response = json_decode($content, true);
|
||||
$this->assertEquals(0, $response['status'], 'outputs status');
|
||||
$this->assertFalse($this->_model->exists(helper::getPasteId()), 'paste successfully deleted');
|
||||
}
|
||||
|
||||
/**
|
||||
* @runInSeparateProcess
|
||||
*/
|
||||
public function testRead()
|
||||
{
|
||||
$this->reset();
|
||||
$paste = helper::getPasteWithAttachment();
|
||||
$this->_model->create(helper::getPasteId(), $paste);
|
||||
$_SERVER['QUERY_STRING'] = helper::getPasteId();
|
||||
$_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
|
||||
ob_start();
|
||||
new zerobin;
|
||||
$content = ob_get_contents();
|
||||
$response = json_decode($content, true);
|
||||
$this->assertEquals(0, $response['status'], 'outputs success status');
|
||||
$this->assertEquals(helper::getPasteId(), $response['id'], 'outputs data correctly');
|
||||
$this->assertStringEndsWith('?' . $response['id'], $response['url'], 'returned URL points to new paste');
|
||||
$this->assertEquals($paste['data'], $response['data'], 'outputs data correctly');
|
||||
$this->assertEquals($paste['attachment'], $response['attachment'], 'outputs attachment correctly');
|
||||
$this->assertEquals($paste['attachmentname'], $response['attachmentname'], 'outputs attachmentname correctly');
|
||||
$this->assertEquals($paste['meta']['formatter'], $response['meta']['formatter'], 'outputs format correctly');
|
||||
$this->assertEquals($paste['meta']['postdate'], $response['meta']['postdate'], 'outputs postdate correctly');
|
||||
$this->assertEquals($paste['meta']['opendiscussion'], $response['meta']['opendiscussion'], 'outputs opendiscussion correctly');
|
||||
$this->assertEquals(0, $response['comment_count'], 'outputs comment_count correctly');
|
||||
$this->assertEquals(0, $response['comment_offset'], 'outputs comment_offset correctly');
|
||||
}
|
||||
|
||||
/**
|
||||
* @runInSeparateProcess
|
||||
*/
|
||||
public function testJsonLdPaste()
|
||||
{
|
||||
$this->reset();
|
||||
$paste = helper::getPasteWithAttachment();
|
||||
$this->_model->create(helper::getPasteId(), $paste);
|
||||
$_GET['jsonld'] = 'paste';
|
||||
ob_start();
|
||||
new zerobin;
|
||||
$content = ob_get_contents();
|
||||
$this->assertEquals(str_replace(
|
||||
'?jsonld=',
|
||||
'/?jsonld=',
|
||||
file_get_contents(PUBLIC_PATH . '/js/paste.jsonld')
|
||||
), $content, 'outputs data correctly');
|
||||
}
|
||||
|
||||
/**
|
||||
* @runInSeparateProcess
|
||||
*/
|
||||
public function testJsonLdComment()
|
||||
{
|
||||
$this->reset();
|
||||
$paste = helper::getPasteWithAttachment();
|
||||
$this->_model->create(helper::getPasteId(), $paste);
|
||||
$_GET['jsonld'] = 'comment';
|
||||
ob_start();
|
||||
new zerobin;
|
||||
$content = ob_get_contents();
|
||||
$this->assertEquals(str_replace(
|
||||
'?jsonld=',
|
||||
'/?jsonld=',
|
||||
file_get_contents(PUBLIC_PATH . '/js/comment.jsonld')
|
||||
), $content, 'outputs data correctly');
|
||||
}
|
||||
|
||||
/**
|
||||
* @runInSeparateProcess
|
||||
*/
|
||||
public function testJsonLdPasteMeta()
|
||||
{
|
||||
$this->reset();
|
||||
$paste = helper::getPasteWithAttachment();
|
||||
$this->_model->create(helper::getPasteId(), $paste);
|
||||
$_GET['jsonld'] = 'pastemeta';
|
||||
ob_start();
|
||||
new zerobin;
|
||||
$content = ob_get_contents();
|
||||
$this->assertEquals(str_replace(
|
||||
'?jsonld=',
|
||||
'/?jsonld=',
|
||||
file_get_contents(PUBLIC_PATH . '/js/pastemeta.jsonld')
|
||||
), $content, 'outputs data correctly');
|
||||
}
|
||||
|
||||
/**
|
||||
* @runInSeparateProcess
|
||||
*/
|
||||
public function testJsonLdCommentMeta()
|
||||
{
|
||||
$this->reset();
|
||||
$paste = helper::getPasteWithAttachment();
|
||||
$this->_model->create(helper::getPasteId(), $paste);
|
||||
$_GET['jsonld'] = 'commentmeta';
|
||||
ob_start();
|
||||
new zerobin;
|
||||
$content = ob_get_contents();
|
||||
$this->assertEquals(str_replace(
|
||||
'?jsonld=',
|
||||
'/?jsonld=',
|
||||
file_get_contents(PUBLIC_PATH . '/js/commentmeta.jsonld')
|
||||
), $content, 'outputs data correctly');
|
||||
}
|
||||
|
||||
/**
|
||||
* @runInSeparateProcess
|
||||
*/
|
||||
public function testJsonLdInvalid()
|
||||
{
|
||||
$this->reset();
|
||||
$paste = helper::getPasteWithAttachment();
|
||||
$this->_model->create(helper::getPasteId(), $paste);
|
||||
$_GET['jsonld'] = '../cfg/conf.ini';
|
||||
ob_start();
|
||||
new zerobin;
|
||||
$content = ob_get_contents();
|
||||
$this->assertEquals('{}', $content, 'does not output nasty data');
|
||||
}
|
||||
|
||||
}
|
||||
211
tst/model.php
Normal file
211
tst/model.php
Normal file
@@ -0,0 +1,211 @@
|
||||
<?php
|
||||
class modelTest extends PHPUnit_Framework_TestCase
|
||||
{
|
||||
private $_conf;
|
||||
|
||||
private $_model;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
/* Setup Routine */
|
||||
$options = parse_ini_file(CONF, true);
|
||||
$options['model'] = array(
|
||||
'class' => 'zerobin_db',
|
||||
);
|
||||
$options['model_options'] = array(
|
||||
'dsn' => 'sqlite::memory:',
|
||||
'usr' => null,
|
||||
'pwd' => null,
|
||||
'opt' => array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION),
|
||||
);
|
||||
helper::confBackup();
|
||||
helper::createIniFile(CONF, $options);
|
||||
$this->_conf = new configuration;
|
||||
$this->_model = new model($this->_conf);
|
||||
$_SERVER['REMOTE_ADDR'] = '::1';
|
||||
}
|
||||
|
||||
public function tearDown()
|
||||
{
|
||||
/* Tear Down Routine */
|
||||
}
|
||||
|
||||
public function testBasicWorkflow()
|
||||
{
|
||||
// storing pastes
|
||||
$pasteData = helper::getPaste();
|
||||
$this->_model->getPaste(helper::getPasteId())->delete();
|
||||
$paste = $this->_model->getPaste(helper::getPasteId());
|
||||
$this->assertFalse($paste->exists(), 'paste does not yet exist');
|
||||
|
||||
$paste = $this->_model->getPaste();
|
||||
$paste->setData($pasteData['data']);
|
||||
$paste->setOpendiscussion();
|
||||
$paste->setFormatter($pasteData['meta']['formatter']);
|
||||
$paste->store();
|
||||
|
||||
$paste = $this->_model->getPaste(helper::getPasteId());
|
||||
$this->assertTrue($paste->exists(), 'paste exists after storing it');
|
||||
$paste = $paste->get();
|
||||
$this->assertEquals($pasteData['data'], $paste->data);
|
||||
foreach (array('opendiscussion', 'formatter') as $key) {
|
||||
$this->assertEquals($pasteData['meta'][$key], $paste->meta->$key);
|
||||
}
|
||||
|
||||
// storing comments
|
||||
$commentData = helper::getComment();
|
||||
$paste = $this->_model->getPaste(helper::getPasteId());
|
||||
$comment = $paste->getComment(helper::getPasteId(), helper::getCommentId());
|
||||
$this->assertFalse($comment->exists(), 'comment does not yet exist');
|
||||
|
||||
$comment = $paste->getComment(helper::getPasteId());
|
||||
$comment->setData($commentData['data']);
|
||||
$comment->setNickname($commentData['meta']['nickname']);
|
||||
$comment->store();
|
||||
|
||||
$comment = $paste->getComment(helper::getPasteId(), helper::getCommentId());
|
||||
$this->assertTrue($comment->exists(), 'comment exists after storing it');
|
||||
$comment = $comment->get();
|
||||
$this->assertEquals($commentData['data'], $comment->data);
|
||||
$this->assertEquals($commentData['meta']['nickname'], $comment->meta->nickname);
|
||||
|
||||
// deleting pastes
|
||||
$this->_model->getPaste(helper::getPasteId())->delete();
|
||||
$paste = $this->_model->getPaste(helper::getPasteId());
|
||||
$this->assertFalse($paste->exists(), 'paste successfully deleted');
|
||||
$this->assertEquals(array(), $paste->getComments(), 'comment was deleted with paste');
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException Exception
|
||||
* @expectedExceptionCode 75
|
||||
*/
|
||||
public function testPasteDuplicate()
|
||||
{
|
||||
$pasteData = helper::getPaste();
|
||||
|
||||
$this->_model->getPaste(helper::getPasteId())->delete();
|
||||
$paste = $this->_model->getPaste();
|
||||
$paste->setData($pasteData['data']);
|
||||
$paste->setOpendiscussion();
|
||||
$paste->setFormatter($pasteData['meta']['formatter']);
|
||||
$paste->store();
|
||||
|
||||
$paste = $this->_model->getPaste();
|
||||
$paste->setData($pasteData['data']);
|
||||
$paste->setOpendiscussion();
|
||||
$paste->setFormatter($pasteData['meta']['formatter']);
|
||||
$paste->store();
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException Exception
|
||||
* @expectedExceptionCode 69
|
||||
*/
|
||||
public function testCommentDuplicate()
|
||||
{
|
||||
$pasteData = helper::getPaste();
|
||||
$commentData = helper::getComment();
|
||||
$this->_model->getPaste(helper::getPasteId())->delete();
|
||||
|
||||
$paste = $this->_model->getPaste();
|
||||
$paste->setData($pasteData['data']);
|
||||
$paste->setOpendiscussion();
|
||||
$paste->setFormatter($pasteData['meta']['formatter']);
|
||||
$paste->store();
|
||||
|
||||
$comment = $paste->getComment(helper::getPasteId());
|
||||
$comment->setData($commentData['data']);
|
||||
$comment->setNickname($commentData['meta']['nickname']);
|
||||
$comment->store();
|
||||
|
||||
$comment = $paste->getComment(helper::getPasteId());
|
||||
$comment->setData($commentData['data']);
|
||||
$comment->setNickname($commentData['meta']['nickname']);
|
||||
$comment->store();
|
||||
}
|
||||
|
||||
public function testImplicitDefaults()
|
||||
{
|
||||
$pasteData = helper::getPaste();
|
||||
$commentData = helper::getComment();
|
||||
$this->_model->getPaste(helper::getPasteId())->delete();
|
||||
|
||||
$paste = $this->_model->getPaste();
|
||||
$paste->setData($pasteData['data']);
|
||||
$paste->setBurnafterreading();
|
||||
$paste->setOpendiscussion();
|
||||
// not setting a formatter, should use default one
|
||||
$paste->store();
|
||||
|
||||
$paste = $this->_model->getPaste(helper::getPasteId())->get(); // ID was set based on data
|
||||
$this->assertEquals(true, property_exists($paste->meta, 'burnafterreading') && $paste->meta->burnafterreading, 'burn after reading takes precendence');
|
||||
$this->assertEquals(false, property_exists($paste->meta, 'opendiscussion') && $paste->meta->opendiscussion, 'opendiscussion is disabled');
|
||||
$this->assertEquals($this->_conf->getKey('defaultformatter'), $paste->meta->formatter, 'default formatter is set');
|
||||
|
||||
$this->_model->getPaste(helper::getPasteId())->delete();
|
||||
$paste = $this->_model->getPaste();
|
||||
$paste->setData($pasteData['data']);
|
||||
$paste->setBurnafterreading('0');
|
||||
$paste->setOpendiscussion();
|
||||
$paste->store();
|
||||
|
||||
$vz = new vizhash16x16();
|
||||
$pngdata = 'data:image/png;base64,' . base64_encode($vz->generate($_SERVER['REMOTE_ADDR']));
|
||||
$comment = $paste->getComment(helper::getPasteId());
|
||||
$comment->setData($commentData['data']);
|
||||
$comment->setNickname($commentData['meta']['nickname']);
|
||||
$comment->store();
|
||||
|
||||
$comment = $paste->getComment(helper::getPasteId(), helper::getCommentId())->get();
|
||||
$this->assertEquals($pngdata, $comment->meta->vizhash, 'nickname triggers vizhash to be set');
|
||||
}
|
||||
|
||||
public function testPasteIdValidation()
|
||||
{
|
||||
$this->assertTrue(model_paste::isValidId('a242ab7bdfb2581a'), 'valid paste id');
|
||||
$this->assertFalse(model_paste::isValidId('foo'), 'invalid hex values');
|
||||
$this->assertFalse(model_paste::isValidId('../bar/baz'), 'path attack');
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException Exception
|
||||
* @expectedExceptionCode 62
|
||||
*/
|
||||
public function testInvalidComment()
|
||||
{
|
||||
$paste = $this->_model->getPaste();
|
||||
$comment = $paste->getComment(helper::getPasteId());
|
||||
}
|
||||
|
||||
public function testExpiration()
|
||||
{
|
||||
$pasteData = helper::getPaste();
|
||||
$this->_model->getPaste(helper::getPasteId())->delete();
|
||||
$paste = $this->_model->getPaste(helper::getPasteId());
|
||||
$this->assertFalse($paste->exists(), 'paste does not yet exist');
|
||||
|
||||
$paste = $this->_model->getPaste();
|
||||
$paste->setData($pasteData['data']);
|
||||
$paste->setExpiration('5min'); // = 300 seconds
|
||||
$paste->store();
|
||||
|
||||
$paste = $paste->get();
|
||||
$this->assertEquals(300, $paste->meta->remaining_time, 'remaining time is set correctly');
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException Exception
|
||||
* @expectedExceptionCode 64
|
||||
*/
|
||||
public function testCommentDeletion()
|
||||
{
|
||||
$pasteData = helper::getPaste();
|
||||
$this->_model->getPaste(helper::getPasteId())->delete();
|
||||
|
||||
$paste = $this->_model->getPaste();
|
||||
$paste->setData($pasteData['data']);
|
||||
$paste->store();
|
||||
$paste->getComment(helper::getPasteId())->delete();
|
||||
}
|
||||
}
|
||||
107
tst/request.php
Normal file
107
tst/request.php
Normal file
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
class requestTest extends PHPUnit_Framework_TestCase
|
||||
{
|
||||
private $_request;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
/* Setup Routine */
|
||||
}
|
||||
|
||||
public function tearDown()
|
||||
{
|
||||
/* Tear Down Routine */
|
||||
}
|
||||
|
||||
public function reset()
|
||||
{
|
||||
$_SERVER = array();
|
||||
$_GET = array();
|
||||
$_POST = array();
|
||||
}
|
||||
|
||||
public function testView()
|
||||
{
|
||||
$this->reset();
|
||||
$_SERVER['REQUEST_METHOD'] = 'GET';
|
||||
$request = new request;
|
||||
$this->assertFalse($request->isJsonApiCall(), 'is HTML call');
|
||||
$this->assertEquals('view', $request->getOperation());
|
||||
}
|
||||
|
||||
public function testRead()
|
||||
{
|
||||
$this->reset();
|
||||
$_SERVER['REQUEST_METHOD'] = 'GET';
|
||||
$_SERVER['QUERY_STRING'] = 'foo';
|
||||
$request = new request;
|
||||
$this->assertFalse($request->isJsonApiCall(), 'is HTML call');
|
||||
$this->assertEquals('foo', $request->getParam('pasteid'));
|
||||
$this->assertEquals('read', $request->getOperation());
|
||||
}
|
||||
|
||||
public function testDelete()
|
||||
{
|
||||
$this->reset();
|
||||
$_SERVER['REQUEST_METHOD'] = 'GET';
|
||||
$_GET['pasteid'] = 'foo';
|
||||
$_GET['deletetoken'] = 'bar';
|
||||
$request = new request;
|
||||
$this->assertFalse($request->isJsonApiCall(), 'is HTML call');
|
||||
$this->assertEquals('delete', $request->getOperation());
|
||||
$this->assertEquals('foo', $request->getParam('pasteid'));
|
||||
$this->assertEquals('bar', $request->getParam('deletetoken'));
|
||||
}
|
||||
|
||||
public function testApiCreate()
|
||||
{
|
||||
$this->reset();
|
||||
$_SERVER['REQUEST_METHOD'] = 'PUT';
|
||||
$_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
|
||||
$file = tempnam(sys_get_temp_dir(), 'FOO');
|
||||
file_put_contents($file, 'data=foo');
|
||||
request::setInputStream($file);
|
||||
$request = new request;
|
||||
$this->assertTrue($request->isJsonApiCall(), 'is JSON Api call');
|
||||
$this->assertEquals('create', $request->getOperation());
|
||||
$this->assertEquals('foo', $request->getParam('data'));
|
||||
}
|
||||
|
||||
public function testApiCreateAlternative()
|
||||
{
|
||||
$this->reset();
|
||||
$_SERVER['REQUEST_METHOD'] = 'POST';
|
||||
$_SERVER['HTTP_ACCEPT'] = 'application/json, text/javascript, */*; q=0.01';
|
||||
$_POST['attachment'] = 'foo';
|
||||
$request = new request;
|
||||
$this->assertTrue($request->isJsonApiCall(), 'is JSON Api call');
|
||||
$this->assertEquals('create', $request->getOperation());
|
||||
$this->assertEquals('foo', $request->getParam('attachment'));
|
||||
}
|
||||
|
||||
public function testApiRead()
|
||||
{
|
||||
$this->reset();
|
||||
$_SERVER['REQUEST_METHOD'] = 'GET';
|
||||
$_SERVER['HTTP_ACCEPT'] = 'application/json, text/javascript, */*; q=0.01';
|
||||
$_SERVER['QUERY_STRING'] = 'foo';
|
||||
$request = new request;
|
||||
$this->assertTrue($request->isJsonApiCall(), 'is JSON Api call');
|
||||
$this->assertEquals('foo', $request->getParam('pasteid'));
|
||||
$this->assertEquals('read', $request->getOperation());
|
||||
}
|
||||
|
||||
public function testApiDelete()
|
||||
{
|
||||
$this->reset();
|
||||
$_SERVER['REQUEST_METHOD'] = 'POST';
|
||||
$_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
|
||||
$_SERVER['QUERY_STRING'] = 'foo';
|
||||
$_POST['deletetoken'] = 'bar';
|
||||
$request = new request;
|
||||
$this->assertTrue($request->isJsonApiCall(), 'is JSON Api call');
|
||||
$this->assertEquals('delete', $request->getOperation());
|
||||
$this->assertEquals('foo', $request->getParam('pasteid'));
|
||||
$this->assertEquals('bar', $request->getParam('deletetoken'));
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,10 @@ class sjclTest extends PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testSjclValidatorValidatesCorrectly()
|
||||
{
|
||||
$paste = helper::getPaste();
|
||||
$paste = helper::getPasteWithAttachment();
|
||||
$this->assertTrue(sjcl::isValid($paste['data']), 'valid sjcl');
|
||||
$this->assertTrue(sjcl::isValid($paste['meta']['attachment']), 'valid sjcl');
|
||||
$this->assertTrue(sjcl::isValid($paste['meta']['attachmentname']), 'valid sjcl');
|
||||
$this->assertTrue(sjcl::isValid($paste['attachment']), 'valid sjcl');
|
||||
$this->assertTrue(sjcl::isValid($paste['attachmentname']), 'valid sjcl');
|
||||
$this->assertTrue(sjcl::isValid(helper::getComment()['data']), 'valid sjcl');
|
||||
|
||||
$this->assertTrue(sjcl::isValid('{"iv":"83Ax/OdUav3SanDW9dcQPg","v":1,"iter":1000,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"Gx1vA2/gQ3U","ct":"j7ImByuE5xCqD2YXm6aSyA"}'), 'valid sjcl');
|
||||
|
||||
@@ -22,12 +22,15 @@ class trafficlimiterTest extends PHPUnit_Framework_TestCase
|
||||
$file = 'baz';
|
||||
$this->assertEquals($this->_path . DIRECTORY_SEPARATOR . $file, trafficlimiter::getPath($file));
|
||||
trafficlimiter::setLimit(4);
|
||||
$this->assertTrue(trafficlimiter::canPass('127.0.0.1'), 'first request may pass');
|
||||
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
|
||||
$this->assertTrue(trafficlimiter::canPass(), 'first request may pass');
|
||||
sleep(2);
|
||||
$this->assertFalse(trafficlimiter::canPass('127.0.0.1'), 'second request is to fast, may not pass');
|
||||
$this->assertFalse(trafficlimiter::canPass(), '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');
|
||||
$this->assertTrue(trafficlimiter::canPass(), 'third request waited long enough and may pass');
|
||||
$_SERVER['REMOTE_ADDR'] = '2001:1620:2057:dead:beef::cafe:babe';
|
||||
$this->assertTrue(trafficlimiter::canPass(), 'fourth request has different ip and may pass');
|
||||
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
|
||||
$this->assertFalse(trafficlimiter::canPass(), 'fifth request is to fast, may not pass');
|
||||
}
|
||||
}
|
||||
|
||||
274
tst/zerobin.php
274
tst/zerobin.php
@@ -1,22 +1,20 @@
|
||||
<?php
|
||||
class zerobinTest extends PHPUnit_Framework_TestCase
|
||||
{
|
||||
private $_conf;
|
||||
|
||||
private $_model;
|
||||
protected $_model;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
/* Setup Routine */
|
||||
$this->_model = zerobin_data::getInstance(array('dir' => PATH . 'data'));
|
||||
serversalt::setPath(PATH . 'data');
|
||||
$this->_conf = PATH . 'cfg' . DIRECTORY_SEPARATOR . 'conf.ini';
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
public function tearDown()
|
||||
{
|
||||
/* Tear Down Routine */
|
||||
helper::confRestore();
|
||||
}
|
||||
|
||||
public function reset()
|
||||
@@ -26,8 +24,7 @@ class zerobinTest extends PHPUnit_Framework_TestCase
|
||||
$_SERVER = array();
|
||||
if ($this->_model->exists(helper::getPasteId()))
|
||||
$this->_model->delete(helper::getPasteId());
|
||||
if (is_file($this->_conf . '.bak'))
|
||||
rename($this->_conf . '.bak', $this->_conf);
|
||||
helper::confRestore();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -55,11 +52,10 @@ class zerobinTest extends PHPUnit_Framework_TestCase
|
||||
public function testViewLanguageSelection()
|
||||
{
|
||||
$this->reset();
|
||||
$options = parse_ini_file($this->_conf, true);
|
||||
$options = parse_ini_file(CONF, true);
|
||||
$options['main']['languageselection'] = true;
|
||||
if (!is_file($this->_conf . '.bak') && is_file($this->_conf))
|
||||
rename($this->_conf, $this->_conf . '.bak');
|
||||
helper::createIniFile($this->_conf, $options);
|
||||
helper::confBackup();
|
||||
helper::createIniFile(CONF, $options);
|
||||
$_COOKIE['lang'] = 'de';
|
||||
ob_start();
|
||||
new zerobin;
|
||||
@@ -104,9 +100,8 @@ class zerobinTest extends PHPUnit_Framework_TestCase
|
||||
public function testConf()
|
||||
{
|
||||
$this->reset();
|
||||
if (!is_file($this->_conf . '.bak') && is_file($this->_conf))
|
||||
rename($this->_conf, $this->_conf . '.bak');
|
||||
file_put_contents($this->_conf, '');
|
||||
helper::confBackup();
|
||||
file_put_contents(CONF, '');
|
||||
ob_start();
|
||||
new zerobin;
|
||||
$content = ob_get_contents();
|
||||
@@ -118,7 +113,13 @@ class zerobinTest extends PHPUnit_Framework_TestCase
|
||||
public function testCreate()
|
||||
{
|
||||
$this->reset();
|
||||
$options = parse_ini_file(CONF, true);
|
||||
$options['traffic']['limit'] = 0;
|
||||
helper::confBackup();
|
||||
helper::createIniFile(CONF, $options);
|
||||
$_POST = helper::getPaste();
|
||||
$_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
|
||||
$_SERVER['REQUEST_METHOD'] = 'POST';
|
||||
$_SERVER['REMOTE_ADDR'] = '::1';
|
||||
ob_start();
|
||||
new zerobin;
|
||||
@@ -140,7 +141,10 @@ class zerobinTest extends PHPUnit_Framework_TestCase
|
||||
{
|
||||
$this->reset();
|
||||
$_POST = helper::getPaste();
|
||||
$_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
|
||||
$_SERVER['REQUEST_METHOD'] = 'POST';
|
||||
$_SERVER['REMOTE_ADDR'] = '::1';
|
||||
trafficlimiter::canPass();
|
||||
ob_start();
|
||||
new zerobin;
|
||||
$content = ob_get_contents();
|
||||
@@ -155,13 +159,14 @@ class zerobinTest extends PHPUnit_Framework_TestCase
|
||||
public function testCreateInvalidSize()
|
||||
{
|
||||
$this->reset();
|
||||
$options = parse_ini_file($this->_conf, true);
|
||||
$options = parse_ini_file(CONF, true);
|
||||
$options['main']['sizelimit'] = 10;
|
||||
$options['traffic']['limit'] = 0;
|
||||
if (!is_file($this->_conf . '.bak') && is_file($this->_conf))
|
||||
rename($this->_conf, $this->_conf . '.bak');
|
||||
helper::createIniFile($this->_conf, $options);
|
||||
helper::confBackup();
|
||||
helper::createIniFile(CONF, $options);
|
||||
$_POST = helper::getPaste();
|
||||
$_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
|
||||
$_SERVER['REQUEST_METHOD'] = 'POST';
|
||||
$_SERVER['REMOTE_ADDR'] = '::1';
|
||||
ob_start();
|
||||
new zerobin;
|
||||
@@ -177,13 +182,15 @@ class zerobinTest extends PHPUnit_Framework_TestCase
|
||||
public function testCreateProxyHeader()
|
||||
{
|
||||
$this->reset();
|
||||
$options = parse_ini_file($this->_conf, true);
|
||||
$options = parse_ini_file(CONF, true);
|
||||
$options['traffic']['header'] = 'X_FORWARDED_FOR';
|
||||
if (!is_file($this->_conf . '.bak') && is_file($this->_conf))
|
||||
rename($this->_conf, $this->_conf . '.bak');
|
||||
helper::createIniFile($this->_conf, $options);
|
||||
$options['traffic']['limit'] = 100;
|
||||
helper::confBackup();
|
||||
helper::createIniFile(CONF, $options);
|
||||
$_POST = helper::getPaste();
|
||||
$_SERVER['HTTP_X_FORWARDED_FOR'] = '::1';
|
||||
$_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
|
||||
$_SERVER['REQUEST_METHOD'] = 'POST';
|
||||
ob_start();
|
||||
new zerobin;
|
||||
$content = ob_get_contents();
|
||||
@@ -198,13 +205,14 @@ class zerobinTest extends PHPUnit_Framework_TestCase
|
||||
public function testCreateDuplicateId()
|
||||
{
|
||||
$this->reset();
|
||||
$options = parse_ini_file($this->_conf, true);
|
||||
$options = parse_ini_file(CONF, true);
|
||||
$options['traffic']['limit'] = 0;
|
||||
if (!is_file($this->_conf . '.bak') && is_file($this->_conf))
|
||||
rename($this->_conf, $this->_conf . '.bak');
|
||||
helper::createIniFile($this->_conf, $options);
|
||||
helper::confBackup();
|
||||
helper::createIniFile(CONF, $options);
|
||||
$this->_model->create(helper::getPasteId(), helper::getPaste());
|
||||
$_POST = helper::getPaste();
|
||||
$_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
|
||||
$_SERVER['REQUEST_METHOD'] = 'POST';
|
||||
$_SERVER['REMOTE_ADDR'] = '::1';
|
||||
ob_start();
|
||||
new zerobin;
|
||||
@@ -220,14 +228,15 @@ class zerobinTest extends PHPUnit_Framework_TestCase
|
||||
public function testCreateValidExpire()
|
||||
{
|
||||
$this->reset();
|
||||
$options = parse_ini_file($this->_conf, true);
|
||||
$options = parse_ini_file(CONF, true);
|
||||
$options['traffic']['limit'] = 0;
|
||||
if (!is_file($this->_conf . '.bak') && is_file($this->_conf))
|
||||
rename($this->_conf, $this->_conf . '.bak');
|
||||
helper::createIniFile($this->_conf, $options);
|
||||
helper::confBackup();
|
||||
helper::createIniFile(CONF, $options);
|
||||
$_POST = helper::getPaste();
|
||||
$_POST['expire'] = '5min';
|
||||
$_POST['formatter'] = 'foo';
|
||||
$_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
|
||||
$_SERVER['REQUEST_METHOD'] = 'POST';
|
||||
$_SERVER['REMOTE_ADDR'] = '::1';
|
||||
ob_start();
|
||||
new zerobin;
|
||||
@@ -240,6 +249,40 @@ class zerobinTest extends PHPUnit_Framework_TestCase
|
||||
'outputs valid delete token'
|
||||
);
|
||||
$this->assertTrue($this->_model->exists($response['id']), 'paste exists after posting data');
|
||||
$paste = $this->_model->read($response['id']);
|
||||
$this->assertEquals(time() + 300, $paste->meta->expire_date, 'time is set correctly');
|
||||
}
|
||||
|
||||
/**
|
||||
* @runInSeparateProcess
|
||||
*/
|
||||
public function testCreateValidExpireWithDiscussion()
|
||||
{
|
||||
$this->reset();
|
||||
$options = parse_ini_file(CONF, true);
|
||||
$options['traffic']['limit'] = 0;
|
||||
helper::confBackup();
|
||||
helper::createIniFile(CONF, $options);
|
||||
$_POST = helper::getPaste();
|
||||
$_POST['expire'] = '5min';
|
||||
$_POST['opendiscussion'] = '1';
|
||||
$_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
|
||||
$_SERVER['REQUEST_METHOD'] = 'POST';
|
||||
$_SERVER['REMOTE_ADDR'] = '::1';
|
||||
ob_start();
|
||||
new zerobin;
|
||||
$content = ob_get_contents();
|
||||
$response = json_decode($content, true);
|
||||
$this->assertEquals(0, $response['status'], 'outputs status');
|
||||
$this->assertEquals(
|
||||
hash_hmac('sha1', $response['id'], serversalt::get()),
|
||||
$response['deletetoken'],
|
||||
'outputs valid delete token'
|
||||
);
|
||||
$this->assertTrue($this->_model->exists($response['id']), 'paste exists after posting data');
|
||||
$paste = $this->_model->read($response['id']);
|
||||
$this->assertEquals(time() + 300, $paste->meta->expire_date, 'time is set correctly');
|
||||
$this->assertEquals(1, $paste->meta->opendiscussion, 'time is set correctly');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -248,13 +291,14 @@ class zerobinTest extends PHPUnit_Framework_TestCase
|
||||
public function testCreateInvalidExpire()
|
||||
{
|
||||
$this->reset();
|
||||
$options = parse_ini_file($this->_conf, true);
|
||||
$options = parse_ini_file(CONF, true);
|
||||
$options['traffic']['limit'] = 0;
|
||||
if (!is_file($this->_conf . '.bak') && is_file($this->_conf))
|
||||
rename($this->_conf, $this->_conf . '.bak');
|
||||
helper::createIniFile($this->_conf, $options);
|
||||
helper::confBackup();
|
||||
helper::createIniFile(CONF, $options);
|
||||
$_POST = helper::getPaste();
|
||||
$_POST['expire'] = 'foo';
|
||||
$_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
|
||||
$_SERVER['REQUEST_METHOD'] = 'POST';
|
||||
$_SERVER['REMOTE_ADDR'] = '::1';
|
||||
ob_start();
|
||||
new zerobin;
|
||||
@@ -275,13 +319,14 @@ class zerobinTest extends PHPUnit_Framework_TestCase
|
||||
public function testCreateInvalidBurn()
|
||||
{
|
||||
$this->reset();
|
||||
$options = parse_ini_file($this->_conf, true);
|
||||
$options = parse_ini_file(CONF, true);
|
||||
$options['traffic']['limit'] = 0;
|
||||
if (!is_file($this->_conf . '.bak') && is_file($this->_conf))
|
||||
rename($this->_conf, $this->_conf . '.bak');
|
||||
helper::createIniFile($this->_conf, $options);
|
||||
helper::confBackup();
|
||||
helper::createIniFile(CONF, $options);
|
||||
$_POST = helper::getPaste();
|
||||
$_POST['burnafterreading'] = 'neither 1 nor 0';
|
||||
$_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
|
||||
$_SERVER['REQUEST_METHOD'] = 'POST';
|
||||
$_SERVER['REMOTE_ADDR'] = '::1';
|
||||
ob_start();
|
||||
new zerobin;
|
||||
@@ -297,13 +342,14 @@ class zerobinTest extends PHPUnit_Framework_TestCase
|
||||
public function testCreateInvalidOpenDiscussion()
|
||||
{
|
||||
$this->reset();
|
||||
$options = parse_ini_file($this->_conf, true);
|
||||
$options = parse_ini_file(CONF, true);
|
||||
$options['traffic']['limit'] = 0;
|
||||
if (!is_file($this->_conf . '.bak') && is_file($this->_conf))
|
||||
rename($this->_conf, $this->_conf . '.bak');
|
||||
helper::createIniFile($this->_conf, $options);
|
||||
helper::confBackup();
|
||||
helper::createIniFile(CONF, $options);
|
||||
$_POST = helper::getPaste();
|
||||
$_POST['opendiscussion'] = 'neither 1 nor 0';
|
||||
$_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
|
||||
$_SERVER['REQUEST_METHOD'] = 'POST';
|
||||
$_SERVER['REMOTE_ADDR'] = '::1';
|
||||
ob_start();
|
||||
new zerobin;
|
||||
@@ -319,14 +365,16 @@ class zerobinTest extends PHPUnit_Framework_TestCase
|
||||
public function testCreateAttachment()
|
||||
{
|
||||
$this->reset();
|
||||
$options = parse_ini_file($this->_conf, true);
|
||||
$options = parse_ini_file(CONF, true);
|
||||
$options['traffic']['limit'] = 0;
|
||||
$options['main']['fileupload'] = true;
|
||||
if (!is_file($this->_conf . '.bak') && is_file($this->_conf))
|
||||
rename($this->_conf, $this->_conf . '.bak');
|
||||
helper::createIniFile($this->_conf, $options);
|
||||
$_POST = helper::getPaste();
|
||||
helper::confBackup();
|
||||
helper::createIniFile(CONF, $options);
|
||||
$_POST = helper::getPasteWithAttachment();
|
||||
$_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
|
||||
$_SERVER['REQUEST_METHOD'] = 'POST';
|
||||
$_SERVER['REMOTE_ADDR'] = '::1';
|
||||
$this->assertFalse($this->_model->exists(helper::getPasteId()), 'paste does not exists before posting data');
|
||||
ob_start();
|
||||
new zerobin;
|
||||
$content = ob_get_contents();
|
||||
@@ -338,6 +386,11 @@ class zerobinTest extends PHPUnit_Framework_TestCase
|
||||
'outputs valid delete token'
|
||||
);
|
||||
$this->assertTrue($this->_model->exists($response['id']), 'paste exists after posting data');
|
||||
$original = json_decode(json_encode($_POST));
|
||||
$stored = $this->_model->read($response['id']);
|
||||
foreach (array('data', 'attachment', 'attachmentname') as $key) {
|
||||
$this->assertEquals($original->$key, $stored->$key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -346,13 +399,14 @@ class zerobinTest extends PHPUnit_Framework_TestCase
|
||||
public function testCreateValidNick()
|
||||
{
|
||||
$this->reset();
|
||||
$options = parse_ini_file($this->_conf, true);
|
||||
$options = parse_ini_file(CONF, true);
|
||||
$options['traffic']['limit'] = 0;
|
||||
if (!is_file($this->_conf . '.bak') && is_file($this->_conf))
|
||||
rename($this->_conf, $this->_conf . '.bak');
|
||||
helper::createIniFile($this->_conf, $options);
|
||||
helper::confBackup();
|
||||
helper::createIniFile(CONF, $options);
|
||||
$_POST = helper::getPaste();
|
||||
$_POST['nickname'] = helper::getComment()['meta']['nickname'];
|
||||
$_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
|
||||
$_SERVER['REQUEST_METHOD'] = 'POST';
|
||||
$_SERVER['REMOTE_ADDR'] = '::1';
|
||||
ob_start();
|
||||
new zerobin;
|
||||
@@ -373,20 +427,24 @@ class zerobinTest extends PHPUnit_Framework_TestCase
|
||||
public function testCreateInvalidNick()
|
||||
{
|
||||
$this->reset();
|
||||
$options = parse_ini_file($this->_conf, true);
|
||||
$options = parse_ini_file(CONF, true);
|
||||
$options['traffic']['limit'] = 0;
|
||||
if (!is_file($this->_conf . '.bak') && is_file($this->_conf))
|
||||
rename($this->_conf, $this->_conf . '.bak');
|
||||
helper::createIniFile($this->_conf, $options);
|
||||
$_POST = helper::getPaste();
|
||||
helper::confBackup();
|
||||
helper::createIniFile(CONF, $options);
|
||||
$_POST = helper::getCommentPost();
|
||||
$_POST['pasteid'] = helper::getPasteId();
|
||||
$_POST['parentid'] = helper::getPasteId();
|
||||
$_POST['nickname'] = 'foo';
|
||||
$_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
|
||||
$_SERVER['REQUEST_METHOD'] = 'POST';
|
||||
$_SERVER['REMOTE_ADDR'] = '::1';
|
||||
$this->_model->create(helper::getPasteId(), helper::getPaste());
|
||||
ob_start();
|
||||
new zerobin;
|
||||
$content = ob_get_contents();
|
||||
$response = json_decode($content, true);
|
||||
$this->assertEquals(1, $response['status'], 'outputs error status');
|
||||
$this->assertFalse($this->_model->exists(helper::getPasteId()), 'paste exists after posting data');
|
||||
$this->assertTrue($this->_model->exists(helper::getPasteId()), 'paste exists after posting data');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -395,14 +453,15 @@ class zerobinTest extends PHPUnit_Framework_TestCase
|
||||
public function testCreateComment()
|
||||
{
|
||||
$this->reset();
|
||||
$options = parse_ini_file($this->_conf, true);
|
||||
$options = parse_ini_file(CONF, true);
|
||||
$options['traffic']['limit'] = 0;
|
||||
if (!is_file($this->_conf . '.bak') && is_file($this->_conf))
|
||||
rename($this->_conf, $this->_conf . '.bak');
|
||||
helper::createIniFile($this->_conf, $options);
|
||||
$_POST = helper::getComment();
|
||||
helper::confBackup();
|
||||
helper::createIniFile(CONF, $options);
|
||||
$_POST = helper::getCommentPost();
|
||||
$_POST['pasteid'] = helper::getPasteId();
|
||||
$_POST['parentid'] = helper::getPasteId();
|
||||
$_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
|
||||
$_SERVER['REQUEST_METHOD'] = 'POST';
|
||||
$_SERVER['REMOTE_ADDR'] = '::1';
|
||||
$this->_model->create(helper::getPasteId(), helper::getPaste());
|
||||
ob_start();
|
||||
@@ -419,14 +478,15 @@ class zerobinTest extends PHPUnit_Framework_TestCase
|
||||
public function testCreateInvalidComment()
|
||||
{
|
||||
$this->reset();
|
||||
$options = parse_ini_file($this->_conf, true);
|
||||
$options = parse_ini_file(CONF, true);
|
||||
$options['traffic']['limit'] = 0;
|
||||
if (!is_file($this->_conf . '.bak') && is_file($this->_conf))
|
||||
rename($this->_conf, $this->_conf . '.bak');
|
||||
helper::createIniFile($this->_conf, $options);
|
||||
$_POST = helper::getComment();
|
||||
helper::confBackup();
|
||||
helper::createIniFile(CONF, $options);
|
||||
$_POST = helper::getCommentPost();
|
||||
$_POST['pasteid'] = helper::getPasteId();
|
||||
$_POST['parentid'] = 'foo';
|
||||
$_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
|
||||
$_SERVER['REQUEST_METHOD'] = 'POST';
|
||||
$_SERVER['REMOTE_ADDR'] = '::1';
|
||||
$this->_model->create(helper::getPasteId(), helper::getPaste());
|
||||
ob_start();
|
||||
@@ -443,14 +503,15 @@ class zerobinTest extends PHPUnit_Framework_TestCase
|
||||
public function testCreateCommentDiscussionDisabled()
|
||||
{
|
||||
$this->reset();
|
||||
$options = parse_ini_file($this->_conf, true);
|
||||
$options = parse_ini_file(CONF, true);
|
||||
$options['traffic']['limit'] = 0;
|
||||
if (!is_file($this->_conf . '.bak') && is_file($this->_conf))
|
||||
rename($this->_conf, $this->_conf . '.bak');
|
||||
helper::createIniFile($this->_conf, $options);
|
||||
$_POST = helper::getComment();
|
||||
helper::confBackup();
|
||||
helper::createIniFile(CONF, $options);
|
||||
$_POST = helper::getCommentPost();
|
||||
$_POST['pasteid'] = helper::getPasteId();
|
||||
$_POST['parentid'] = helper::getPasteId();
|
||||
$_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
|
||||
$_SERVER['REQUEST_METHOD'] = 'POST';
|
||||
$_SERVER['REMOTE_ADDR'] = '::1';
|
||||
$paste = helper::getPaste(array('opendiscussion' => false));
|
||||
$this->_model->create(helper::getPasteId(), $paste);
|
||||
@@ -468,14 +529,15 @@ class zerobinTest extends PHPUnit_Framework_TestCase
|
||||
public function testCreateCommentInvalidPaste()
|
||||
{
|
||||
$this->reset();
|
||||
$options = parse_ini_file($this->_conf, true);
|
||||
$options = parse_ini_file(CONF, true);
|
||||
$options['traffic']['limit'] = 0;
|
||||
if (!is_file($this->_conf . '.bak') && is_file($this->_conf))
|
||||
rename($this->_conf, $this->_conf . '.bak');
|
||||
helper::createIniFile($this->_conf, $options);
|
||||
$_POST = helper::getComment();
|
||||
helper::confBackup();
|
||||
helper::createIniFile(CONF, $options);
|
||||
$_POST = helper::getCommentPost();
|
||||
$_POST['pasteid'] = helper::getPasteId();
|
||||
$_POST['parentid'] = helper::getPasteId();
|
||||
$_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
|
||||
$_SERVER['REQUEST_METHOD'] = 'POST';
|
||||
$_SERVER['REMOTE_ADDR'] = '::1';
|
||||
ob_start();
|
||||
new zerobin;
|
||||
@@ -491,17 +553,18 @@ class zerobinTest extends PHPUnit_Framework_TestCase
|
||||
public function testCreateDuplicateComment()
|
||||
{
|
||||
$this->reset();
|
||||
$options = parse_ini_file($this->_conf, true);
|
||||
$options = parse_ini_file(CONF, true);
|
||||
$options['traffic']['limit'] = 0;
|
||||
if (!is_file($this->_conf . '.bak') && is_file($this->_conf))
|
||||
rename($this->_conf, $this->_conf . '.bak');
|
||||
helper::createIniFile($this->_conf, $options);
|
||||
helper::confBackup();
|
||||
helper::createIniFile(CONF, $options);
|
||||
$this->_model->create(helper::getPasteId(), helper::getPaste());
|
||||
$this->_model->createComment(helper::getPasteId(), helper::getPasteId(), helper::getCommentId(), helper::getComment());
|
||||
$this->assertTrue($this->_model->existsComment(helper::getPasteId(), helper::getPasteId(), helper::getCommentId()), 'comment exists before posting data');
|
||||
$_POST = helper::getComment();
|
||||
$_POST = helper::getCommentPost();
|
||||
$_POST['pasteid'] = helper::getPasteId();
|
||||
$_POST['parentid'] = helper::getPasteId();
|
||||
$_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
|
||||
$_SERVER['REQUEST_METHOD'] = 'POST';
|
||||
$_SERVER['REMOTE_ADDR'] = '::1';
|
||||
ob_start();
|
||||
new zerobin;
|
||||
@@ -525,7 +588,7 @@ class zerobinTest extends PHPUnit_Framework_TestCase
|
||||
$this->assertTag(
|
||||
array(
|
||||
'id' => 'cipherdata',
|
||||
'content' => htmlspecialchars(json_encode(helper::getPaste()), ENT_NOQUOTES)
|
||||
'content' => htmlspecialchars(helper::getPasteAsJson(), ENT_NOQUOTES)
|
||||
),
|
||||
$content,
|
||||
'outputs data correctly'
|
||||
@@ -609,7 +672,7 @@ class zerobinTest extends PHPUnit_Framework_TestCase
|
||||
$this->assertTag(
|
||||
array(
|
||||
'id' => 'cipherdata',
|
||||
'content' => htmlspecialchars(json_encode($burnPaste), ENT_NOQUOTES)
|
||||
'content' => htmlspecialchars(helper::getPasteAsJson($burnPaste['meta']), ENT_NOQUOTES)
|
||||
),
|
||||
$content,
|
||||
'outputs data correctly'
|
||||
@@ -622,14 +685,23 @@ class zerobinTest extends PHPUnit_Framework_TestCase
|
||||
public function testReadJson()
|
||||
{
|
||||
$this->reset();
|
||||
$this->_model->create(helper::getPasteId(), helper::getPaste());
|
||||
$_SERVER['QUERY_STRING'] = helper::getPasteId() . '&json';
|
||||
$paste = helper::getPaste();
|
||||
$this->_model->create(helper::getPasteId(), $paste);
|
||||
$_SERVER['QUERY_STRING'] = helper::getPasteId();
|
||||
$_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
|
||||
ob_start();
|
||||
new zerobin;
|
||||
$content = ob_get_contents();
|
||||
$response = json_decode($content, true);
|
||||
$this->assertEquals(0, $response['status'], 'outputs success status');
|
||||
$this->assertEquals(array(helper::getPaste()), $response['messages'], 'outputs data correctly');
|
||||
$this->assertEquals(helper::getPasteId(), $response['id'], 'outputs data correctly');
|
||||
$this->assertStringEndsWith('?' . $response['id'], $response['url'], 'returned URL points to new paste');
|
||||
$this->assertEquals($paste['data'], $response['data'], 'outputs data correctly');
|
||||
$this->assertEquals($paste['meta']['formatter'], $response['meta']['formatter'], 'outputs format correctly');
|
||||
$this->assertEquals($paste['meta']['postdate'], $response['meta']['postdate'], 'outputs postdate correctly');
|
||||
$this->assertEquals($paste['meta']['opendiscussion'], $response['meta']['opendiscussion'], 'outputs opendiscussion correctly');
|
||||
$this->assertEquals(0, $response['comment_count'], 'outputs comment_count correctly');
|
||||
$this->assertEquals(0, $response['comment_offset'], 'outputs comment_offset correctly');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -638,7 +710,8 @@ class zerobinTest extends PHPUnit_Framework_TestCase
|
||||
public function testReadInvalidJson()
|
||||
{
|
||||
$this->reset();
|
||||
$_SERVER['QUERY_STRING'] = helper::getPasteId() . '&json';
|
||||
$_SERVER['QUERY_STRING'] = helper::getPasteId();
|
||||
$_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
|
||||
ob_start();
|
||||
new zerobin;
|
||||
$content = ob_get_contents();
|
||||
@@ -652,18 +725,23 @@ class zerobinTest extends PHPUnit_Framework_TestCase
|
||||
public function testReadOldSyntax()
|
||||
{
|
||||
$this->reset();
|
||||
$oldPaste = helper::getPaste(array('syntaxcoloring' => true));
|
||||
unset($oldPaste['meta']['formatter']);
|
||||
$oldPaste = helper::getPaste();
|
||||
$meta = array(
|
||||
'syntaxcoloring' => true,
|
||||
'postdate' => $oldPaste['meta']['postdate'],
|
||||
'opendiscussion' => $oldPaste['meta']['opendiscussion'],
|
||||
);
|
||||
$oldPaste['meta'] = $meta;
|
||||
$this->_model->create(helper::getPasteId(), $oldPaste);
|
||||
$_SERVER['QUERY_STRING'] = helper::getPasteId();
|
||||
ob_start();
|
||||
new zerobin;
|
||||
$content = ob_get_contents();
|
||||
$oldPaste['meta']['formatter'] = 'syntaxhighlighting';
|
||||
$meta['formatter'] = 'syntaxhighlighting';
|
||||
$this->assertTag(
|
||||
array(
|
||||
'id' => 'cipherdata',
|
||||
'content' => htmlspecialchars(json_encode($oldPaste), ENT_NOQUOTES)
|
||||
'content' => htmlspecialchars(helper::getPasteAsJson($meta), ENT_NOQUOTES)
|
||||
),
|
||||
$content,
|
||||
'outputs data correctly'
|
||||
@@ -687,7 +765,7 @@ class zerobinTest extends PHPUnit_Framework_TestCase
|
||||
$this->assertTag(
|
||||
array(
|
||||
'id' => 'cipherdata',
|
||||
'content' => htmlspecialchars(json_encode($oldPaste), ENT_NOQUOTES)
|
||||
'content' => htmlspecialchars(helper::getPasteAsJson($oldPaste['meta']), ENT_NOQUOTES)
|
||||
),
|
||||
$content,
|
||||
'outputs data correctly'
|
||||
@@ -794,8 +872,10 @@ class zerobinTest extends PHPUnit_Framework_TestCase
|
||||
$burnPaste = helper::getPaste(array('burnafterreading' => true));
|
||||
$this->_model->create(helper::getPasteId(), $burnPaste);
|
||||
$this->assertTrue($this->_model->exists(helper::getPasteId()), 'paste exists before deleting data');
|
||||
$_GET['pasteid'] = helper::getPasteId();
|
||||
$_GET['deletetoken'] = 'burnafterreading';
|
||||
$_POST['deletetoken'] = 'burnafterreading';
|
||||
$_SERVER['QUERY_STRING'] = helper::getPasteId();
|
||||
$_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
|
||||
$_SERVER['REQUEST_METHOD'] = 'POST';
|
||||
ob_start();
|
||||
new zerobin;
|
||||
$content = ob_get_contents();
|
||||
@@ -812,8 +892,10 @@ class zerobinTest extends PHPUnit_Framework_TestCase
|
||||
$this->reset();
|
||||
$this->_model->create(helper::getPasteId(), helper::getPaste());
|
||||
$this->assertTrue($this->_model->exists(helper::getPasteId()), 'paste exists before deleting data');
|
||||
$_GET['pasteid'] = helper::getPasteId();
|
||||
$_GET['deletetoken'] = 'burnafterreading';
|
||||
$_POST['deletetoken'] = 'burnafterreading';
|
||||
$_SERVER['QUERY_STRING'] = helper::getPasteId();
|
||||
$_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
|
||||
$_SERVER['REQUEST_METHOD'] = 'POST';
|
||||
ob_start();
|
||||
new zerobin;
|
||||
$content = ob_get_contents();
|
||||
|
||||
@@ -20,6 +20,8 @@ class zerobin_dataTest extends PHPUnit_Framework_TestCase
|
||||
|
||||
public function testFileBasedDataStoreWorks()
|
||||
{
|
||||
$this->_model->delete(helper::getPasteId());
|
||||
|
||||
// storing pastes
|
||||
$paste = helper::getPaste(array('expire_date' => 1344803344));
|
||||
$this->assertFalse($this->_model->exists(helper::getPasteId()), 'paste does not yet exist');
|
||||
@@ -33,8 +35,8 @@ class zerobin_dataTest extends PHPUnit_Framework_TestCase
|
||||
$this->assertTrue($this->_model->createComment(helper::getPasteId(), helper::getPasteId(), helper::getCommentId(), helper::getComment()) !== false, 'store comment');
|
||||
$this->assertTrue($this->_model->existsComment(helper::getPasteId(), helper::getPasteId(), helper::getCommentId()), 'comment exists after storing it');
|
||||
$comment = json_decode(json_encode(helper::getComment()));
|
||||
$comment->meta->commentid = helper::getCommentId();
|
||||
$comment->meta->parentid = helper::getPasteId();
|
||||
$comment->id = helper::getCommentId();
|
||||
$comment->parentid = helper::getPasteId();
|
||||
$this->assertEquals(
|
||||
array($comment->meta->postdate => $comment),
|
||||
$this->_model->readComments(helper::getPasteId())
|
||||
@@ -46,4 +48,19 @@ class zerobin_dataTest extends PHPUnit_Framework_TestCase
|
||||
$this->assertFalse($this->_model->existsComment(helper::getPasteId(), helper::getPasteId(), helper::getCommentId()), 'comment was deleted with paste');
|
||||
$this->assertFalse($this->_model->read(helper::getPasteId()), 'paste can no longer be found');
|
||||
}
|
||||
|
||||
public function testFileBasedAttachmentStoreWorks()
|
||||
{
|
||||
$this->_model->delete(helper::getPasteId());
|
||||
$original = $paste = helper::getPasteWithAttachment(array('expire_date' => 1344803344));
|
||||
$paste['meta']['attachment'] = $paste['attachment'];
|
||||
$paste['meta']['attachmentname'] = $paste['attachmentname'];
|
||||
unset($paste['attachment'], $paste['attachmentname']);
|
||||
$this->assertFalse($this->_model->exists(helper::getPasteId()), 'paste does not yet exist');
|
||||
$this->assertTrue($this->_model->create(helper::getPasteId(), $paste), 'store new paste');
|
||||
$this->assertTrue($this->_model->exists(helper::getPasteId()), 'paste exists after storing it');
|
||||
$this->assertFalse($this->_model->create(helper::getPasteId(), $paste), 'unable to store the same paste twice');
|
||||
$this->assertEquals(json_decode(json_encode($original)), $this->_model->read(helper::getPasteId()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,21 +3,24 @@ class zerobin_dbTest extends PHPUnit_Framework_TestCase
|
||||
{
|
||||
private $_model;
|
||||
|
||||
private $_options = array(
|
||||
'dsn' => 'sqlite::memory:',
|
||||
'usr' => null,
|
||||
'pwd' => null,
|
||||
'opt' => array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION),
|
||||
);
|
||||
|
||||
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),
|
||||
)
|
||||
);
|
||||
$this->_model = zerobin_db::getInstance($this->_options);
|
||||
}
|
||||
|
||||
public function testDatabaseBasedDataStoreWorks()
|
||||
{
|
||||
$this->_model->delete(helper::getPasteId());
|
||||
|
||||
// storing pastes
|
||||
$paste = helper::getPaste(array('expire_date' => 1344803344));
|
||||
$this->assertFalse($this->_model->exists(helper::getPasteId()), 'paste does not yet exist');
|
||||
$this->assertTrue($this->_model->create(helper::getPasteId(), $paste), 'store new paste');
|
||||
@@ -30,8 +33,8 @@ class zerobin_dbTest extends PHPUnit_Framework_TestCase
|
||||
$this->assertTrue($this->_model->createComment(helper::getPasteId(), helper::getPasteId(), helper::getCommentId(), helper::getComment()) !== false, 'store comment');
|
||||
$this->assertTrue($this->_model->existsComment(helper::getPasteId(), helper::getPasteId(), helper::getCommentId()), 'comment exists after storing it');
|
||||
$comment = json_decode(json_encode(helper::getComment()));
|
||||
$comment->meta->commentid = helper::getCommentId();
|
||||
$comment->meta->parentid = helper::getPasteId();
|
||||
$comment->id = helper::getCommentId();
|
||||
$comment->parentid = helper::getPasteId();
|
||||
$this->assertEquals(
|
||||
array($comment->meta->postdate => $comment),
|
||||
$this->_model->readComments(helper::getPasteId())
|
||||
@@ -44,6 +47,21 @@ class zerobin_dbTest extends PHPUnit_Framework_TestCase
|
||||
$this->assertFalse($this->_model->read(helper::getPasteId()), 'paste can no longer be found');
|
||||
}
|
||||
|
||||
public function testDatabaseBasedAttachmentStoreWorks()
|
||||
{
|
||||
$this->_model->delete(helper::getPasteId());
|
||||
$original = $paste = helper::getPasteWithAttachment(array('expire_date' => 1344803344));
|
||||
$paste['meta']['burnafterreading'] = $original['meta']['burnafterreading'] = true;
|
||||
$paste['meta']['attachment'] = $paste['attachment'];
|
||||
$paste['meta']['attachmentname'] = $paste['attachmentname'];
|
||||
unset($paste['attachment'], $paste['attachmentname']);
|
||||
$this->assertFalse($this->_model->exists(helper::getPasteId()), 'paste does not yet exist');
|
||||
$this->assertTrue($this->_model->create(helper::getPasteId(), $paste), 'store new paste');
|
||||
$this->assertTrue($this->_model->exists(helper::getPasteId()), 'paste exists after storing it');
|
||||
$this->assertFalse($this->_model->create(helper::getPasteId(), $paste), 'unable to store the same paste twice');
|
||||
$this->assertEquals(json_decode(json_encode($original)), $this->_model->read(helper::getPasteId()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException PDOException
|
||||
*/
|
||||
@@ -120,4 +138,29 @@ class zerobin_dbTest extends PHPUnit_Framework_TestCase
|
||||
'dsn' => 'foo:', 'usr' => null, 'pwd' => null, 'opt' => null
|
||||
));
|
||||
}
|
||||
|
||||
public function testTableUpgrade()
|
||||
{
|
||||
$path = PATH . 'data/db-test.sq3';
|
||||
@unlink($path);
|
||||
$this->_options['dsn'] = 'sqlite:' . $path;
|
||||
$this->_options['tbl'] = 'foo_';
|
||||
$db = new PDO(
|
||||
$this->_options['dsn'],
|
||||
$this->_options['usr'],
|
||||
$this->_options['pwd'],
|
||||
$this->_options['opt']
|
||||
);
|
||||
$db->exec(
|
||||
'CREATE TABLE foo_paste ( ' .
|
||||
'dataid CHAR(16), ' .
|
||||
'data TEXT, ' .
|
||||
'postdate INT, ' .
|
||||
'expiredate INT, ' .
|
||||
'opendiscussion INT, ' .
|
||||
'burnafterreading INT );'
|
||||
);
|
||||
zerobin_db::getInstance($this->_options);
|
||||
@unlink($path);
|
||||
}
|
||||
}
|
||||
|
||||
43
tst/zerobinWithDb.php
Normal file
43
tst/zerobinWithDb.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
require_once 'zerobin.php';
|
||||
|
||||
class zerobinWithDbTest extends zerobinTest
|
||||
{
|
||||
private $_options = array(
|
||||
'dsn' => 'sqlite:../data/tst.sq3',
|
||||
'usr' => null,
|
||||
'pwd' => null,
|
||||
'opt' => array(
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_PERSISTENT => true
|
||||
),
|
||||
);
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
/* Setup Routine */
|
||||
$this->_model = zerobin_db::getInstance($this->_options);
|
||||
serversalt::setPath(PATH . 'data');
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
public function tearDown()
|
||||
{
|
||||
/* Tear Down Routine */
|
||||
parent::tearDown();
|
||||
@unlink('../data/tst.sq3');
|
||||
}
|
||||
|
||||
public function reset()
|
||||
{
|
||||
parent::reset();
|
||||
// but then inject a db config
|
||||
$options = parse_ini_file(CONF, true);
|
||||
$options['model'] = array(
|
||||
'class' => 'zerobin_db',
|
||||
);
|
||||
$options['model_options'] = $this->_options;
|
||||
helper::confBackup();
|
||||
helper::createIniFile(CONF, $options);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user