Issues (12)

lib/Data/GoogleCloudStorage.php (3 issues)

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
 */
11
12
namespace PrivateBin\Data;
13
14
use Exception;
15
use Google\Cloud\Core\Exception\NotFoundException;
0 ignored issues
show
The type Google\Cloud\Core\Exception\NotFoundException was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
16
use Google\Cloud\Storage\Bucket;
0 ignored issues
show
The type Google\Cloud\Storage\Bucket was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
17
use Google\Cloud\Storage\StorageClient;
0 ignored issues
show
The type Google\Cloud\Storage\StorageClient was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
18
use PrivateBin\Json;
19
20
class GoogleCloudStorage extends AbstractData
21
{
22
    /**
23
     * GCS client
24
     *
25
     * @access private
26
     * @var    StorageClient
27
     */
28
    private $_client = null;
29
30
    /**
31
     * GCS bucket
32
     *
33
     * @access private
34
     * @var    Bucket
35
     */
36
    private $_bucket = null;
37
38
    /**
39
     * object prefix
40
     *
41
     * @access private
42
     * @var    string
43
     */
44
    private $_prefix = 'pastes';
45
46
    /**
47
     * bucket acl type
48
     *
49
     * @access private
50
     * @var    bool
51
     */
52
    private $_uniformacl = false;
53
54
    /**
55
     * instantiantes a new Google Cloud Storage data backend.
56
     *
57
     * @access public
58
     * @param array $options
59
     */
60 43
    public function __construct(array $options)
61
    {
62 43
        if (getenv('PRIVATEBIN_GCS_BUCKET')) {
63
            $bucket = getenv('PRIVATEBIN_GCS_BUCKET');
64
        }
65 43
        if (is_array($options) && array_key_exists('bucket', $options)) {
66 43
            $bucket = $options['bucket'];
67
        }
68 43
        if (is_array($options) && array_key_exists('prefix', $options)) {
69 43
            $this->_prefix = $options['prefix'];
70
        }
71 43
        if (is_array($options) && array_key_exists('uniformacl', $options)) {
72 32
            $this->_uniformacl = $options['uniformacl'];
73
        }
74
75 43
        $this->_client = class_exists('StorageClientStub', false) ?
76 43
            new \StorageClientStub(array()) :
77
            new StorageClient(array('suppressKeyFileNotice' => true));
78 43
        if (isset($bucket)) {
79 43
            $this->_bucket = $this->_client->bucket($bucket);
80
        }
81
    }
82
83
    /**
84
     * returns the google storage object key for $pasteid in $this->_bucket.
85
     *
86
     * @access private
87
     * @param $pasteid string to get the key for
88
     * @return string
89
     */
90 41
    private function _getKey($pasteid)
91
    {
92 41
        if ($this->_prefix != '') {
93 41
            return $this->_prefix . '/' . $pasteid;
94
        }
95
        return $pasteid;
96
    }
97
98
    /**
99
     * Uploads the payload in the $this->_bucket under the specified key.
100
     * The entire payload is stored as a JSON document. The metadata is replicated
101
     * as the GCS object's metadata except for the fields attachment, attachmentname
102
     * and salt.
103
     *
104
     * @param $key string to store the payload under
105
     * @param $payload array to store
106
     * @return bool true if successful, otherwise false.
107
     */
108 27
    private function _upload($key, $payload)
109
    {
110 27
        $metadata = array_key_exists('meta', $payload) ? $payload['meta'] : array();
111 27
        unset($metadata['attachment'], $metadata['attachmentname'], $metadata['salt']);
112 27
        foreach ($metadata as $k => $v) {
113 27
            $metadata[$k] = strval($v);
114
        }
115
        try {
116 27
            $data = array(
117 27
                'name'          => $key,
118 27
                'chunkSize'     => 262144,
119 27
                'metadata'      => array(
120 27
                    'content-type' => 'application/json',
121 27
                    'metadata'     => $metadata,
122 27
                ),
123 27
            );
124 27
            if (!$this->_uniformacl) {
125 27
                $data['predefinedAcl'] = 'private';
126
            }
127 27
            $this->_bucket->upload(Json::encode($payload), $data);
128 2
        } catch (Exception $e) {
129 2
            error_log('failed to upload ' . $key . ' to ' . $this->_bucket->name() . ', ' .
130 2
                trim(preg_replace('/\s\s+/', ' ', $e->getMessage())));
131 2
            return false;
132
        }
133 26
        return true;
134
    }
135
136
    /**
137
     * @inheritDoc
138
     */
139 27
    public function create($pasteid, array $paste)
140
    {
141 27
        if ($this->exists($pasteid)) {
142 1
            return false;
143
        }
144
145 27
        return $this->_upload($this->_getKey($pasteid), $paste);
146
    }
147
148
    /**
149
     * @inheritDoc
150
     */
151 20
    public function read($pasteid)
152
    {
153
        try {
154 20
            $o    = $this->_bucket->object($this->_getKey($pasteid));
155 20
            $data = $o->downloadAsString();
156 20
            return Json::decode($data);
157 1
        } catch (NotFoundException $e) {
158 1
            return false;
159
        } catch (Exception $e) {
160
            error_log('failed to read ' . $pasteid . ' from ' . $this->_bucket->name() . ', ' .
161
                trim(preg_replace('/\s\s+/', ' ', $e->getMessage())));
162
            return false;
163
        }
164
    }
165
166
    /**
167
     * @inheritDoc
168
     */
169 11
    public function delete($pasteid)
170
    {
171 11
        $name = $this->_getKey($pasteid);
172
173
        try {
174 11
            foreach ($this->_bucket->objects(array('prefix' => $name . '/discussion/')) as $comment) {
175
                try {
176 1
                    $this->_bucket->object($comment->name())->delete();
177
                } catch (NotFoundException $e) {
178
                    // ignore if already deleted.
179
                }
180
            }
181
        } catch (NotFoundException $e) {
182
            // there are no discussions associated with the paste
183
        }
184
185
        try {
186 11
            $this->_bucket->object($name)->delete();
187 3
        } catch (NotFoundException $e) {
188
            // ignore if already deleted
189
        }
190
    }
191
192
    /**
193
     * @inheritDoc
194
     */
195 41
    public function exists($pasteid)
196
    {
197 41
        $o = $this->_bucket->object($this->_getKey($pasteid));
198 41
        return $o->exists();
199
    }
200
201
    /**
202
     * @inheritDoc
203
     */
204 4
    public function createComment($pasteid, $parentid, $commentid, array $comment)
205
    {
206 4
        if ($this->existsComment($pasteid, $parentid, $commentid)) {
207 1
            return false;
208
        }
209 4
        $key = $this->_getKey($pasteid) . '/discussion/' . $parentid . '/' . $commentid;
210 4
        return $this->_upload($key, $comment);
211
    }
212
213
    /**
214
     * @inheritDoc
215
     */
216 12
    public function readComments($pasteid)
217
    {
218 12
        $comments = array();
219 12
        $prefix   = $this->_getKey($pasteid) . '/discussion/';
220
        try {
221 12
            foreach ($this->_bucket->objects(array('prefix' => $prefix)) as $key) {
222 2
                $comment         = JSON::decode($this->_bucket->object($key->name())->downloadAsString());
223 2
                $comment['id']   = basename($key->name());
224 2
                $slot            = $this->getOpenSlot($comments, (int) $comment['meta']['created']);
225 2
                $comments[$slot] = $comment;
226
            }
227
        } catch (NotFoundException $e) {
228
            // no comments found
229
        }
230 12
        return $comments;
231
    }
232
233
    /**
234
     * @inheritDoc
235
     */
236 7
    public function existsComment($pasteid, $parentid, $commentid)
237
    {
238 7
        $name = $this->_getKey($pasteid) . '/discussion/' . $parentid . '/' . $commentid;
239 7
        $o    = $this->_bucket->object($name);
240 7
        return $o->exists();
241
    }
242
243
    /**
244
     * @inheritDoc
245
     */
246 6
    public function purgeValues($namespace, $time)
247
    {
248 6
        $path = 'config/' . $namespace;
249
        try {
250 6
            foreach ($this->_bucket->objects(array('prefix' => $path)) as $object) {
251 3
                $name = $object->name();
252 3
                if (strlen($name) > strlen($path) && substr($name, strlen($path), 1) !== '/') {
253
                    continue;
254
                }
255 3
                $info = $object->info();
256 3
                if (key_exists('metadata', $info) && key_exists('value', $info['metadata'])) {
257 3
                    $value = $info['metadata']['value'];
258 3
                    if (is_numeric($value) && intval($value) < $time) {
259
                        try {
260 2
                            $object->delete();
261
                        } catch (NotFoundException $e) {
262
                            // deleted by another instance.
263
                        }
264
                    }
265
                }
266
            }
267
        } catch (NotFoundException $e) {
268
            // no objects in the bucket yet
269
        }
270
    }
271
272
    /**
273
     * For GoogleCloudStorage, the value will also be stored in the metadata for the
274
     * namespaces traffic_limiter and purge_limiter.
275
     * @inheritDoc
276
     */
277 18
    public function setValue($value, $namespace, $key = '')
278
    {
279 18
        if ($key === '') {
280 17
            $key = 'config/' . $namespace;
281
        } else {
282 6
            $key = 'config/' . $namespace . '/' . $key;
283
        }
284
285 18
        $metadata = array('namespace' => $namespace);
286 18
        if ($namespace != 'salt') {
287 13
            $metadata['value'] = strval($value);
288
        }
289
        try {
290 18
            $data = array(
291 18
                'name'          => $key,
292 18
                'chunkSize'     => 262144,
293 18
                'metadata'      => array(
294 18
                    'content-type' => 'application/json',
295 18
                    'metadata'     => $metadata,
296 18
                ),
297 18
            );
298 18
            if (!$this->_uniformacl) {
299 18
                $data['predefinedAcl'] = 'private';
300
            }
301 18
            $this->_bucket->upload($value, $data);
302
        } catch (Exception $e) {
303
            error_log('failed to set key ' . $key . ' to ' . $this->_bucket->name() . ', ' .
304
                trim(preg_replace('/\s\s+/', ' ', $e->getMessage())));
305
            return false;
306
        }
307 18
        return true;
308
    }
309
310
    /**
311
     * @inheritDoc
312
     */
313 18
    public function getValue($namespace, $key = '')
314
    {
315 18
        if ($key === '') {
316 17
            $key = 'config/' . $namespace;
317
        } else {
318 6
            $key = 'config/' . $namespace . '/' . $key;
319
        }
320
        try {
321 18
            $o = $this->_bucket->object($key);
322 18
            return $o->downloadAsString();
323 18
        } catch (NotFoundException $e) {
324 18
            return '';
325
        }
326
    }
327
328
    /**
329
     * @inheritDoc
330
     */
331 11
    protected function _getExpiredPastes($batchsize)
332
    {
333 11
        $expired = array();
334
335 11
        $now    = time();
336 11
        $prefix = $this->_prefix;
337 11
        if ($prefix != '') {
338 11
            $prefix .= '/';
339
        }
340
        try {
341 11
            foreach ($this->_bucket->objects(array('prefix' => $prefix)) as $object) {
342 2
                $metadata = $object->info()['metadata'];
343 2
                if ($metadata != null && array_key_exists('expire_date', $metadata)) {
344 1
                    $expire_at = intval($metadata['expire_date']);
345 1
                    if ($expire_at != 0 && $expire_at < $now) {
346 1
                        array_push($expired, basename($object->name()));
347
                    }
348
                }
349
350 2
                if (count($expired) > $batchsize) {
351
                    break;
352
                }
353
            }
354
        } catch (NotFoundException $e) {
355
            // no objects in the bucket yet
356
        }
357 11
        return $expired;
358
    }
359
360
    /**
361
     * @inheritDoc
362
     */
363
    public function getAllPastes()
364
    {
365
        $pastes = array();
366
        $prefix = $this->_prefix;
367
        if ($prefix != '') {
368
            $prefix .= '/';
369
        }
370
371
        try {
372
            foreach ($this->_bucket->objects(array('prefix' => $prefix)) as $object) {
373
                $candidate = substr($object->name(), strlen($prefix));
374
                if (strpos($candidate, '/') === false) {
375
                    $pastes[] = $candidate;
376
                }
377
            }
378
        } catch (NotFoundException $e) {
379
            // no objects in the bucket yet
380
        }
381
        return $pastes;
382
    }
383
}
384