Issues (11)

lib/Model/Paste.php (1 issue)

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.7.1
11
 */
12
13
namespace PrivateBin\Model;
14
15
use Exception;
16
use PrivateBin\Controller;
17
use PrivateBin\Persistence\ServerSalt;
18
19
/**
20
 * Paste
21
 *
22
 * Model of a PrivateBin paste.
23
 */
24
class Paste extends AbstractModel
25
{
26
    /**
27
     * Get paste data.
28
     *
29
     * @access public
30
     * @throws Exception
31
     * @return array
32
     */
33 49
    public function get()
34
    {
35 49
        $data = $this->_store->read($this->getId());
36 49
        if ($data === false) {
37 1
            throw new Exception(Controller::GENERIC_ERROR, 64);
38
        }
39
40
        // check if paste has expired and delete it if neccessary.
41 48
        if (array_key_exists('expire_date', $data['meta'])) {
42 11
            if ($data['meta']['expire_date'] < time()) {
43 6
                $this->delete();
44 6
                throw new Exception(Controller::GENERIC_ERROR, 63);
45
            }
46
            // We kindly provide the remaining time before expiration (in seconds)
47 5
            $data['meta']['time_to_live'] = $data['meta']['expire_date'] - time();
48 5
            unset($data['meta']['expire_date']);
49
        }
50
51
        // check if non-expired burn after reading paste needs to be deleted
52
        if (
53 42
            (array_key_exists('adata', $data) && $data['adata'][3] === 1) ||
54 42
            (array_key_exists('burnafterreading', $data['meta']) && $data['meta']['burnafterreading'])
55
        ) {
56 6
            $this->delete();
57
        }
58
59
        // set formatter for the view in version 1 pastes.
60 42
        if (array_key_exists('data', $data) && !array_key_exists('formatter', $data['meta'])) {
61
            // support < 0.21 syntax highlighting
62 4
            if (array_key_exists('syntaxcoloring', $data['meta']) && $data['meta']['syntaxcoloring'] === true) {
63 3
                $data['meta']['formatter'] = 'syntaxhighlighting';
64
            } else {
65 1
                $data['meta']['formatter'] = $this->_conf->getKey('defaultformatter');
66
            }
67
        }
68
69
        // support old paste format with server wide salt
70 42
        if (!array_key_exists('salt', $data['meta'])) {
71 6
            $data['meta']['salt'] = ServerSalt::get();
72
        }
73 42
        $data['comments']       = array_values($this->getComments());
74 42
        $data['comment_count']  = count($data['comments']);
75 42
        $data['comment_offset'] = 0;
76 42
        $data['@context']       = '?jsonld=paste';
77 42
        $this->_data            = $data;
78
79 42
        return $this->_data;
80
    }
81
82
    /**
83
     * Store the paste's data.
84
     *
85
     * @access public
86
     * @throws Exception
87
     */
88 38
    public function store()
89
    {
90
        // Check for improbable collision.
91 38
        if ($this->exists()) {
92 4
            throw new Exception('You are unlucky. Try again.', 75);
93
        }
94
95 35
        $this->_data['meta']['created'] = time();
96 35
        $this->_data['meta']['salt']    = ServerSalt::generate();
97
98
        // store paste
99
        if (
100 35
            $this->_store->create(
101 35
                $this->getId(),
102 35
                $this->_data
103 35
            ) === false
104
        ) {
105 1
            throw new Exception('Error saving paste. Sorry.', 76);
106
        }
107
    }
108
109
    /**
110
     * Delete the paste.
111
     *
112
     * @access public
113
     * @throws Exception
114
     */
115 34
    public function delete()
116
    {
117 34
        $this->_store->delete($this->getId());
118
    }
119
120
    /**
121
     * Test if paste exists in store.
122
     *
123
     * @access public
124
     * @return bool
125
     */
126 96
    public function exists()
127
    {
128 96
        return $this->_store->exists($this->getId());
129
    }
130
131
    /**
132
     * Get a comment, optionally a specific instance.
133
     *
134
     * @access public
135
     * @param string $parentId
136
     * @param string $commentId
137
     * @throws Exception
138
     * @return Comment
139
     */
140 23
    public function getComment($parentId, $commentId = '')
141
    {
142 23
        if (!$this->exists()) {
143 1
            throw new Exception('Invalid data.', 62);
144
        }
145 22
        $comment = new Comment($this->_conf, $this->_store);
146 22
        $comment->setPaste($this);
147 22
        $comment->setParentId($parentId);
148 18
        if ($commentId !== '') {
149 2
            $comment->setId($commentId);
150
        }
151 18
        return $comment;
152
    }
153
154
    /**
155
     * Get all comments, if any.
156
     *
157
     * @access public
158
     * @return array
159
     */
160 42
    public function getComments()
161
    {
162 42
        return $this->_store->readComments($this->getId());
163
    }
164
165
    /**
166
     * Generate the "delete" token.
167
     *
168
     * The token is the hmac of the pastes ID signed with the server salt.
169
     * The paste can be deleted by calling:
170
     * https://example.com/privatebin/?pasteid=<pasteid>&deletetoken=<deletetoken>
171
     *
172
     * @access public
173
     * @return string
174
     */
175 38
    public function getDeleteToken()
176
    {
177 38
        if (!array_key_exists('salt', $this->_data['meta'])) {
178 1
            $this->get();
179
        }
180 38
        return hash_hmac(
181 38
            $this->_conf->getKey('zerobincompatibility') ? 'sha1' : 'sha256',
182 38
            $this->getId(),
183 38
            $this->_data['meta']['salt']
184 38
        );
185
    }
186
187
    /**
188
     * Check if paste has discussions enabled.
189
     *
190
     * @access public
191
     * @throws Exception
192
     * @return bool
193
     */
194 16
    public function isOpendiscussion()
195
    {
196 16
        if (!array_key_exists('adata', $this->_data) && !array_key_exists('data', $this->_data)) {
197 11
            $this->get();
198
        }
199 16
        return
200 16
            (array_key_exists('adata', $this->_data) && $this->_data['adata'][2] === 1) ||
201 16
            (array_key_exists('opendiscussion', $this->_data['meta']) && $this->_data['meta']['opendiscussion']);
202
    }
203
204
    /**
205
     * Sanitizes data to conform with current configuration.
206
     *
207
     * @access protected
208
     * @param  array $data
209
     * @return array
210
     */
211 45
    protected function _sanitize(array $data)
212
    {
213 45
        $expiration = $data['meta']['expire'];
214 45
        unset($data['meta']['expire']);
215 45
        $expire_options = $this->_conf->getSection('expire_options');
216 45
        if (array_key_exists($expiration, $expire_options)) {
217 39
            $expire = $expire_options[$expiration];
218
        } else {
219
            // using getKey() to ensure a default value is present
220 6
            $expire = $this->_conf->getKey($this->_conf->getKey('default', 'expire'), 'expire_options');
221
        }
222 45
        if ($expire > 0) {
223 45
            $data['meta']['expire_date'] = time() + $expire;
224
        }
225 45
        return $data;
226
    }
227
228
    /**
229
     * Validate data.
230
     *
231
     * @access protected
232
     * @param  array $data
233
     * @throws Exception
234
     */
235 45
    protected function _validate(array $data)
236
    {
237
        // reject invalid or disabled formatters
238 45
        if (!array_key_exists($data['adata'][1], $this->_conf->getSection('formatter_options'))) {
239 1
            throw new Exception('Invalid data.', 75);
240
        }
241
242
        // discussion requested, but disabled in config or burn after reading requested as well, or invalid integer
243
        if (
244 44
            ($data['adata'][2] === 1 && ( // open discussion flag
0 ignored issues
show
Consider adding parentheses for clarity. Current Interpretation: ($data['adata'][2] === 1...$data['adata'][2] !== 1, Probably Intended Meaning: $data['adata'][2] === 1 ...data['adata'][2] !== 1)
Loading history...
245 40
                !$this->_conf->getKey('discussion') ||
246 40
                $data['adata'][3] === 1  // burn after reading flag
247
            )) ||
248 44
            ($data['adata'][2] !== 0 && $data['adata'][2] !== 1)
249
        ) {
250 3
            throw new Exception('Invalid data.', 74);
251
        }
252
253
        // reject invalid burn after reading
254 41
        if ($data['adata'][3] !== 0 && $data['adata'][3] !== 1) {
255 3
            throw new Exception('Invalid data.', 73);
256
        }
257
    }
258
}
259