Completed
Pull Request — master (#32044)
by Thomas
19:39
created

Checksum   A

Complexity

Total Complexity 15

Size/Duplication

Total Lines 112
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 0
Metric Value
dl 0
loc 112
rs 10
c 0
b 0
f 0
wmc 15
lcom 1
cbo 5

7 Methods

Rating   Name   Duplication   Size   Complexity  
A getChecksumsInDbFormat() 0 15 3
A isPartialFile() 0 7 2
A file_get_contents() 0 6 1
A file_put_contents() 0 3 1
A writeFile() 0 14 2
A readFile() 0 12 2
A getMetaData() 0 18 4
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
		return $this->writeFile($path, stream_for($data));
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->writeFile(...sr7\stream_for($data)); (integer) is incompatible with the return type declared by the interface OCP\Files\Storage\IStorage::file_put_contents of type boolean.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
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