Completed
Pull Request — master (#32044)
by Thomas
12:01
created

Checksum::readFile()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 2
dl 0
loc 12
rs 9.8666
c 0
b 0
f 0
1
<?php
2
/**
3
 * @author Ilja Neumann <[email protected]>
4
 *
5
 * @copyright Copyright (c) 2018, ownCloud GmbH
6
 * @license AGPL-3.0
7
 *
8
 * This code is free software: you can redistribute it and/or modify
9
 * it under the terms of the GNU Affero General Public License, version 3,
10
 * as published by the Free Software Foundation.
11
 *
12
 * This program is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
 * GNU Affero General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Affero General Public License, version 3,
18
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
19
 *
20
 */
21
namespace OC\Files\Storage\Wrapper;
22
use function GuzzleHttp\Psr7\stream_for;
23
use GuzzleHttp\Psr7\StreamWrapper;
24
use GuzzleHttp\Stream\GuzzleStreamWrapper;
25
use Psr\Http\Message\StreamInterface;
26
27
/**
28
 * Class Checksum
29
 *
30
 * Computes checksums (default: SHA1, MD5, ADLER32) on all files under the /files path.
31
 * The resulting checksum can be retrieved by call getMetadata($path)
32
 *
33
 * If a file is read and has no checksum oc_filecache gets updated accordingly.
34
 *
35
 *
36
 * @package OC\Files\Storage\Wrapper
37
 */
38
class Checksum extends Wrapper {
39
40
	/** Format of checksum field in filecache */
41
	const CHECKSUMS_DB_FORMAT = 'SHA1:%s MD5:%s ADLER32:%s';
42
43
	const NOT_REQUIRED = 0;
44
	/** Calculate checksum on write (to be stored in oc_filecache) */
45
	const PATH_NEW_OR_UPDATED = 1;
46
	/** File needs to be checksummed on first read because it is already in cache but has no checksum */
47
	const PATH_IN_CACHE_WITHOUT_CHECKSUM = 2;
48
49
	/** @var array */
50
	private $checksums;
51
52
	/**
53
	 * @param $path
54
	 * @return string Format like "SHA1:abc MD5:def ADLER32:ghi"
55
	 */
56
	private function getChecksumsInDbFormat($path) {
57
		if (empty($this->checksums[$path])) {
58
			return '';
59
		}
60
		if (!isset($this->checksums[$path]->md5)) {
61
			throw new \BadMethodCallException('Reading from stream not yet finished. Close the stream before calling this method.');
62
		}
63
64
		return \sprintf(
65
			self::CHECKSUMS_DB_FORMAT,
66
			$this->checksums[$path]->sha1,
67
			$this->checksums[$path]->md5,
68
			$this->checksums[$path]->adler32
69
		);
70
	}
71
72
	/**
73
	 * check if the file metadata should not be fetched
74
	 * NOTE: files with a '.part' extension are ignored as well!
75
	 *       prevents unfinished put requests to fetch metadata which does not exists
76
	 *
77
	 * @param string $file
78
	 * @return boolean
79
	 */
80
	public static function isPartialFile($file) {
81
		if (\pathinfo($file, PATHINFO_EXTENSION) === 'part') {
82
			return true;
83
		}
84
85
		return false;
86
	}
87
88
	public function file_get_contents($path) {
89
		$stream = $this->readFile($path);
90
		$data = $stream->getContents();
91
		$stream->close();
92
		return $data;
93
	}
94
95
	public function file_put_contents($path, $data) {
96
		$this->writeFile($path, stream_for($data));
97
	}
98
99
	public function writeFile(string $path, StreamInterface $stream) : int {
100
		$this->checksums[$path] = new \stdClass();
101
102
		if (!\in_array('oc.checksum', \stream_get_filters(), true)) {
103
			\stream_filter_register('oc.checksum', ChecksumFilter::class);
104
		}
105
		$resource = StreamWrapper::getResource($stream);
106
		\stream_filter_append($resource, 'oc.checksum', STREAM_FILTER_READ, $this->checksums[$path]);
107
108
		$checksumStream = stream_for($resource);
109
		$return = $this->getWrapperStorage()->writeFile($path, $checksumStream);
110
		$checksumStream->close();
111
		return $return;
112
	}
113
114
	public function readFile(string $path, array $options = []): StreamInterface {
115
		$this->checksums[$path] = new \stdClass();
116
117
		if (!\in_array('oc.checksum', \stream_get_filters(), true)) {
118
			\stream_filter_register('oc.checksum', ChecksumFilter::class);
119
		}
120
		$stream = $this->getWrapperStorage()->readFile($path, $options);
121
		$resource = StreamWrapper::getResource($stream);
122
		\stream_filter_append($resource, 'oc.checksum', STREAM_FILTER_READ, $this->checksums[$path]);
123
124
		return stream_for($resource);
125
	}
126
127
	/**
128
	 * @param string $path
129
	 * @return array
130
	 */
131
	public function getMetaData($path) {
132
		// Check if it is partial file. Partial file metadata are only checksums
133
		$parentMetaData = [];
134
		if (!self::isPartialFile($path)) {
135
			$parentMetaData = $this->getWrapperStorage()->getMetaData($path);
136
			// can be null if entry does not exist
137
			if ($parentMetaData === null) {
138
				return null;
139
			}
140
		}
141
		$parentMetaData['checksum'] = $this->getChecksumsInDbFormat($path);
142
143
		if (!isset($parentMetaData['mimetype'])) {
144
			$parentMetaData['mimetype'] = 'application/octet-stream';
145
		}
146
147
		return $parentMetaData;
148
	}
149
}
150