Paste.php 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. <?php
  2. /**
  3. * PrivateBin
  4. *
  5. * a zero-knowledge paste bin
  6. *
  7. * @link https://github.com/PrivateBin/PrivateBin
  8. * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
  9. * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
  10. * @version 1.1
  11. */
  12. namespace PrivateBin\Model;
  13. use Exception;
  14. use PrivateBin\Persistence\ServerSalt;
  15. use PrivateBin\PrivateBin;
  16. use PrivateBin\Sjcl;
  17. /**
  18. * Paste
  19. *
  20. * Model of a PrivateBin paste.
  21. */
  22. class Paste extends AbstractModel
  23. {
  24. /**
  25. * Get paste data.
  26. *
  27. * @access public
  28. * @throws Exception
  29. * @return stdClass
  30. */
  31. public function get()
  32. {
  33. $data = $this->_store->read($this->getId());
  34. if ($data === false) {
  35. throw new Exception(PrivateBin::GENERIC_ERROR, 64);
  36. }
  37. // check if paste has expired and delete it if neccessary.
  38. if (property_exists($data->meta, 'expire_date')) {
  39. if ($data->meta->expire_date < time()) {
  40. $this->delete();
  41. throw new Exception(PrivateBin::GENERIC_ERROR, 63);
  42. }
  43. // We kindly provide the remaining time before expiration (in seconds)
  44. $data->meta->remaining_time = $data->meta->expire_date - time();
  45. }
  46. // set formatter for for the view.
  47. if (!property_exists($data->meta, 'formatter')) {
  48. // support < 0.21 syntax highlighting
  49. if (property_exists($data->meta, 'syntaxcoloring') && $data->meta->syntaxcoloring === true) {
  50. $data->meta->formatter = 'syntaxhighlighting';
  51. } else {
  52. $data->meta->formatter = $this->_conf->getKey('defaultformatter');
  53. }
  54. }
  55. // support old paste format with server wide salt
  56. if (!property_exists($data->meta, 'salt')) {
  57. $data->meta->salt = ServerSalt::get();
  58. }
  59. $data->comments = array_values($this->getComments());
  60. $data->comment_count = count($data->comments);
  61. $data->comment_offset = 0;
  62. $data->{'@context'} = 'js/paste.jsonld';
  63. $this->_data = $data;
  64. return $this->_data;
  65. }
  66. /**
  67. * Store the paste's data.
  68. *
  69. * @access public
  70. * @throws Exception
  71. */
  72. public function store()
  73. {
  74. // Check for improbable collision.
  75. if ($this->exists()) {
  76. throw new Exception('You are unlucky. Try again.', 75);
  77. }
  78. $this->_data->meta->postdate = time();
  79. $this->_data->meta->salt = serversalt::generate();
  80. // store paste
  81. if (
  82. $this->_store->create(
  83. $this->getId(),
  84. json_decode(json_encode($this->_data), true)
  85. ) === false
  86. ) {
  87. throw new Exception('Error saving paste. Sorry.', 76);
  88. }
  89. }
  90. /**
  91. * Delete the paste.
  92. *
  93. * @access public
  94. * @throws Exception
  95. */
  96. public function delete()
  97. {
  98. $this->_store->delete($this->getId());
  99. }
  100. /**
  101. * Test if paste exists in store.
  102. *
  103. * @access public
  104. * @return bool
  105. */
  106. public function exists()
  107. {
  108. return $this->_store->exists($this->getId());
  109. }
  110. /**
  111. * Get a comment, optionally a specific instance.
  112. *
  113. * @access public
  114. * @param string $parentId
  115. * @param string $commentId
  116. * @throws Exception
  117. * @return Comment
  118. */
  119. public function getComment($parentId, $commentId = null)
  120. {
  121. if (!$this->exists()) {
  122. throw new Exception('Invalid data.', 62);
  123. }
  124. $comment = new Comment($this->_conf, $this->_store);
  125. $comment->setPaste($this);
  126. $comment->setParentId($parentId);
  127. if ($commentId !== null) {
  128. $comment->setId($commentId);
  129. }
  130. return $comment;
  131. }
  132. /**
  133. * Get all comments, if any.
  134. *
  135. * @access public
  136. * @return array
  137. */
  138. public function getComments()
  139. {
  140. return $this->_store->readComments($this->getId());
  141. }
  142. /**
  143. * Generate the "delete" token.
  144. *
  145. * The token is the hmac of the pastes ID signed with the server salt.
  146. * The paste can be deleted by calling:
  147. * http://example.com/privatebin/?pasteid=<pasteid>&deletetoken=<deletetoken>
  148. *
  149. * @access public
  150. * @return string
  151. */
  152. public function getDeleteToken()
  153. {
  154. if (!property_exists($this->_data->meta, 'salt')) {
  155. $this->get();
  156. }
  157. return hash_hmac(
  158. $this->_conf->getKey('zerobincompatibility') ? 'sha1' : 'sha256',
  159. $this->getId(),
  160. $this->_data->meta->salt
  161. );
  162. }
  163. /**
  164. * Set paste's attachment.
  165. *
  166. * @access public
  167. * @param string $attachment
  168. * @throws Exception
  169. */
  170. public function setAttachment($attachment)
  171. {
  172. if (!$this->_conf->getKey('fileupload') || !Sjcl::isValid($attachment)) {
  173. throw new Exception('Invalid attachment.', 71);
  174. }
  175. $this->_data->meta->attachment = $attachment;
  176. }
  177. /**
  178. * Set paste's attachment name.
  179. *
  180. * @access public
  181. * @param string $attachmentname
  182. * @throws Exception
  183. */
  184. public function setAttachmentName($attachmentname)
  185. {
  186. if (!$this->_conf->getKey('fileupload') || !Sjcl::isValid($attachmentname)) {
  187. throw new Exception('Invalid attachment.', 72);
  188. }
  189. $this->_data->meta->attachmentname = $attachmentname;
  190. }
  191. /**
  192. * Set paste expiration.
  193. *
  194. * @access public
  195. * @param string $expiration
  196. */
  197. public function setExpiration($expiration)
  198. {
  199. $expire_options = $this->_conf->getSection('expire_options');
  200. if (array_key_exists($expiration, $expire_options)) {
  201. $expire = $expire_options[$expiration];
  202. } else {
  203. // using getKey() to ensure a default value is present
  204. $expire = $this->_conf->getKey($this->_conf->getKey('default', 'expire'), 'expire_options');
  205. }
  206. if ($expire > 0) {
  207. $this->_data->meta->expire_date = time() + $expire;
  208. }
  209. }
  210. /**
  211. * Set paste's burn-after-reading type.
  212. *
  213. * @access public
  214. * @param string $burnafterreading
  215. * @throws Exception
  216. */
  217. public function setBurnafterreading($burnafterreading = '1')
  218. {
  219. if ($burnafterreading === '0') {
  220. $this->_data->meta->burnafterreading = false;
  221. } else {
  222. if ($burnafterreading !== '1') {
  223. throw new Exception('Invalid data.', 73);
  224. }
  225. $this->_data->meta->burnafterreading = true;
  226. $this->_data->meta->opendiscussion = false;
  227. }
  228. }
  229. /**
  230. * Set paste's discussion state.
  231. *
  232. * @access public
  233. * @param string $opendiscussion
  234. * @throws Exception
  235. */
  236. public function setOpendiscussion($opendiscussion = '1')
  237. {
  238. if (
  239. !$this->_conf->getKey('discussion') ||
  240. $this->isBurnafterreading() ||
  241. $opendiscussion === '0'
  242. ) {
  243. $this->_data->meta->opendiscussion = false;
  244. } else {
  245. if ($opendiscussion !== '1') {
  246. throw new Exception('Invalid data.', 74);
  247. }
  248. $this->_data->meta->opendiscussion = true;
  249. }
  250. }
  251. /**
  252. * Set paste's format.
  253. *
  254. * @access public
  255. * @param string $format
  256. * @throws Exception
  257. */
  258. public function setFormatter($format)
  259. {
  260. if (!array_key_exists($format, $this->_conf->getSection('formatter_options'))) {
  261. $format = $this->_conf->getKey('defaultformatter');
  262. }
  263. $this->_data->meta->formatter = $format;
  264. }
  265. /**
  266. * Check if paste is of burn-after-reading type.
  267. *
  268. * @access public
  269. * @throws Exception
  270. * @return bool
  271. */
  272. public function isBurnafterreading()
  273. {
  274. if (!property_exists($this->_data, 'data')) {
  275. $this->get();
  276. }
  277. return property_exists($this->_data->meta, 'burnafterreading') &&
  278. $this->_data->meta->burnafterreading === true;
  279. }
  280. /**
  281. * Check if paste has discussions enabled.
  282. *
  283. * @access public
  284. * @throws Exception
  285. * @return bool
  286. */
  287. public function isOpendiscussion()
  288. {
  289. if (!property_exists($this->_data, 'data')) {
  290. $this->get();
  291. }
  292. return property_exists($this->_data->meta, 'opendiscussion') &&
  293. $this->_data->meta->opendiscussion === true;
  294. }
  295. }