Completed
Push — master ( 2d707f...1a83f1 )
by Morris
12:00
created

PhotoCache::getFile()   C

Complexity

Conditions 7
Paths 28

Size

Total Lines 40
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 25
nc 28
nop 2
dl 0
loc 40
rs 6.7272
c 0
b 0
f 0
1
<?php
2
3
namespace OCA\DAV\CardDAV;
4
5
use OCP\Files\IAppData;
6
use OCP\Files\NotFoundException;
7
use OCP\Files\NotPermittedException;
8
use OCP\Files\SimpleFS\ISimpleFile;
9
use OCP\Files\SimpleFS\ISimpleFolder;
10
use Sabre\CardDAV\Card;
11
use Sabre\VObject\Property\Binary;
12
use Sabre\VObject\Reader;
13
14
class PhotoCache {
15
16
	/** @var IAppData $appData */
17
	protected $appData;
18
19
	/**
20
	 * PhotoCache constructor.
21
	 *
22
	 * @param IAppData $appData
23
	 */
24
	public function __construct(IAppData $appData) {
25
		$this->appData = $appData;
26
	}
27
28
	/**
29
	 * @param int $addressBookId
30
	 * @param string $cardUri
31
	 * @param int $size
32
	 * @param Card $card
33
	 *
34
	 * @return ISimpleFile
35
	 * @throws NotFoundException
36
	 */
37
	public function get($addressBookId, $cardUri, $size, Card $card) {
38
		$folder = $this->getFolder($addressBookId, $cardUri);
39
40
		if ($this->isEmpty($folder)) {
41
			$this->init($folder, $card);
42
		}
43
44
		if (!$this->hasPhoto($folder)) {
45
			throw new NotFoundException();
46
		}
47
48
		if ($size !== -1) {
49
			$size = 2 ** ceil(log($size) / log(2));
50
		}
51
52
		return $this->getFile($folder, $size);
53
	}
54
55
	/**
56
	 * @param ISimpleFolder $folder
57
	 * @return bool
58
	 */
59
	private function isEmpty(ISimpleFolder $folder) {
60
		return $folder->getDirectoryListing() === [];
61
	}
62
63
	/**
64
	 * @param ISimpleFolder $folder
65
	 * @param Card $card
66
	 */
67
	private function init(ISimpleFolder $folder, Card $card) {
68
		$data = $this->getPhoto($card);
69
70
		if ($data === false) {
71
			$folder->newFile('nophoto');
72
		} else {
73
			switch ($data['Content-Type']) {
74
				case 'image/png':
75
					$ext = 'png';
76
					break;
77
				case 'image/jpeg':
78
					$ext = 'jpg';
79
					break;
80
				case 'image/gif':
81
					$ext = 'gif';
82
					break;
83
			}
84
			$file = $folder->newFile('photo.' . $ext);
0 ignored issues
show
Bug introduced by
The variable $ext does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
85
			$file->putContent($data['body']);
86
		}
87
	}
88
89
	private function hasPhoto(ISimpleFolder $folder) {
90
		return !$folder->fileExists('nophoto');
91
	}
92
93
	private function getFile(ISimpleFolder $folder, $size) {
94
		$ext = $this->getExtension($folder);
95
96
		if ($size === -1) {
97
			$path = 'photo.' . $ext;
98
		} else {
99
			$path = 'photo.' . $size . '.' . $ext;
100
		}
101
102
		try {
103
			$file = $folder->getFile($path);
104
		} catch (NotFoundException $e) {
105
			if ($size <= 0) {
106
				throw new NotFoundException;
107
			}
108
109
			$photo = new \OC_Image();
110
			/** @var ISimpleFile $file */
111
			$file = $folder->getFile('photo.' . $ext);
112
			$photo->loadFromData($file->getContent());
113
114
			$ratio = $photo->width() / $photo->height();
115
			if ($ratio < 1) {
116
				$ratio = 1/$ratio;
117
			}
118
			$size = (int)($size * $ratio);
119
120
			if ($size !== -1) {
121
				$photo->resize($size);
122
			}
123
			try {
124
				$file = $folder->newFile($path);
125
				$file->putContent($photo->data());
126
			} catch (NotPermittedException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
127
128
			}
129
		}
130
131
		return $file;
132
	}
133
134
135
	/**
136
	 * @param int $addressBookId
137
	 * @param string $cardUri
138
	 * @return ISimpleFolder
139
	 */
140
	private function getFolder($addressBookId, $cardUri) {
141
		$hash = md5($addressBookId . ' ' . $cardUri);
142
		try {
143
			return $this->appData->getFolder($hash);
144
		} catch (NotFoundException $e) {
145
			return $this->appData->newFolder($hash);
146
		}
147
	}
148
149
	/**
150
	 * Get the extension of the avatar. If there is no avatar throw Exception
151
	 *
152
	 * @param ISimpleFolder $folder
153
	 * @return string
154
	 * @throws NotFoundException
155
	 */
156
	private function getExtension(ISimpleFolder $folder) {
157
		if ($folder->fileExists('photo.jpg')) {
158
			return 'jpg';
159
		} elseif ($folder->fileExists('photo.png')) {
160
			return 'png';
161
		} elseif ($folder->fileExists('photo.gif')) {
162
			return 'gif';
163
		}
164
		throw new NotFoundException;
165
	}
166
167
	private function getPhoto(Card $node) {
168
		try {
169
			$vObject = $this->readCard($node->get());
170
			if (!$vObject->PHOTO) {
171
				return false;
172
			}
173
174
			$photo = $vObject->PHOTO;
175
			$type = $this->getType($photo);
176
177
			$val = $photo->getValue();
178
			if ($photo->getValueType() === 'URI') {
179
				$parsed = \Sabre\URI\parse($val);
180
				//only allow data://
181
				if ($parsed['scheme'] !== 'data') {
182
					return false;
183
				}
184
				if (substr_count($parsed['path'], ';') === 1) {
185
					list($type,) = explode(';', $parsed['path']);
186
				}
187
				$val = file_get_contents($val);
188
			}
189
190
			$allowedContentTypes = [
191
				'image/png',
192
				'image/jpeg',
193
				'image/gif',
194
			];
195
196
			if(!in_array($type, $allowedContentTypes, true)) {
197
				$type = 'application/octet-stream';
198
			}
199
200
			return [
201
				'Content-Type' => $type,
202
				'body' => $val
203
			];
204
		} catch(\Exception $ex) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
205
206
		}
207
		return false;
208
	}
209
210
	/**
211
	 * @param string $cardData
212
	 * @return \Sabre\VObject\Document
213
	 */
214
	private function readCard($cardData) {
215
		return Reader::read($cardData);
216
	}
217
218
	/**
219
	 * @param Binary $photo
220
	 * @return string
221
	 */
222
	private function getType(Binary $photo) {
223
		$params = $photo->parameters();
224
		if (isset($params['TYPE']) || isset($params['MEDIATYPE'])) {
225
			/** @var Parameter $typeParam */
226
			$typeParam = isset($params['TYPE']) ? $params['TYPE'] : $params['MEDIATYPE'];
227
			$type = $typeParam->getValue();
228
229
			if (strpos($type, 'image/') === 0) {
230
				return $type;
231
			} else {
232
				return 'image/' . strtolower($type);
233
			}
234
		}
235
		return '';
236
	}
237
238
	/**
239
	 * @param int $addressBookId
240
	 * @param string $cardUri
241
	 */
242
	public function delete($addressBookId, $cardUri) {
243
		$folder = $this->getFolder($addressBookId, $cardUri);
244
		$folder->delete();
245
	}
246
}
247