This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
1 | <?php |
||||
2 | |||||
3 | declare(strict_types=1); |
||||
4 | |||||
5 | namespace BEAR\QueryRepository; |
||||
6 | |||||
7 | use BEAR\RepositoryModule\Annotation\EtagPool; |
||||
8 | use BEAR\RepositoryModule\Annotation\ResourceObjectPool; |
||||
9 | use BEAR\Resource\AbstractUri; |
||||
10 | use BEAR\Resource\RequestInterface; |
||||
11 | use BEAR\Resource\ResourceObject; |
||||
12 | use Override; |
||||
13 | use Ray\Di\Di\Set; |
||||
14 | use Ray\Di\ProviderInterface; |
||||
15 | use Symfony\Component\Cache\Adapter\TagAwareAdapterInterface; |
||||
16 | |||||
17 | use function array_merge; |
||||
18 | use function array_unique; |
||||
19 | use function assert; |
||||
20 | use function explode; |
||||
21 | use function implode; |
||||
22 | use function is_array; |
||||
23 | use function is_string; |
||||
24 | use function sprintf; |
||||
25 | use function strtoupper; |
||||
26 | |||||
27 | /** |
||||
28 | * @psalm-type Props = array{ |
||||
29 | * logger: RepositoryLoggerInterface, |
||||
30 | * purger:PurgerInterface, |
||||
31 | * uriTag: UriTag, |
||||
32 | * saver: ResourceStorageSaver, |
||||
33 | * roProvider:ProviderInterface<TagAwareAdapterInterface>, |
||||
34 | * etagProvider: ProviderInterface<TagAwareAdapterInterface> |
||||
35 | * } |
||||
36 | */ |
||||
37 | final class ResourceStorage implements ResourceStorageInterface |
||||
38 | { |
||||
39 | /** |
||||
40 | * Resource object cache prefix |
||||
41 | */ |
||||
42 | private const KEY_RO = 'ro-'; |
||||
43 | |||||
44 | /** |
||||
45 | * Resource static cache prifix |
||||
46 | */ |
||||
47 | private const KEY_DONUT = 'donut-'; |
||||
48 | |||||
49 | /** @var ProviderInterface<TagAwareAdapterInterface> */ |
||||
50 | private ProviderInterface $roPoolProvider; |
||||
51 | |||||
52 | /** @var ProviderInterface<TagAwareAdapterInterface> */ |
||||
53 | private ProviderInterface $etagPoolProvider; |
||||
54 | private TagAwareAdapterInterface $roPool; |
||||
55 | private TagAwareAdapterInterface $etagPool; |
||||
56 | |||||
57 | /** |
||||
58 | * @param ProviderInterface<TagAwareAdapterInterface> $roPoolProvider |
||||
59 | * @param ProviderInterface<TagAwareAdapterInterface> $etagPoolProvider |
||||
60 | */ |
||||
61 | public function __construct( |
||||
62 | private RepositoryLoggerInterface $logger, |
||||
63 | private PurgerInterface $purger, |
||||
64 | private UriTagInterface $uriTag, |
||||
65 | private ResourceStorageSaver $saver, |
||||
66 | #[Set(TagAwareAdapterInterface::class, ResourceObjectPool::class)] |
||||
67 | ProviderInterface $roPoolProvider, |
||||
68 | #[Set(TagAwareAdapterInterface::class, EtagPool::class)] |
||||
69 | ProviderInterface $etagPoolProvider, |
||||
70 | ) { |
||||
71 | $this->initializePools($roPoolProvider, $etagPoolProvider); |
||||
72 | } |
||||
73 | |||||
74 | /** |
||||
75 | * @param ProviderInterface<TagAwareAdapterInterface> $roPoolProvider |
||||
76 | * @param ProviderInterface<TagAwareAdapterInterface> $etagPoolProvider |
||||
77 | */ |
||||
78 | private function initializePools(ProviderInterface $roPoolProvider, ProviderInterface $etagPoolProvider): void |
||||
79 | { |
||||
80 | $this->roPoolProvider = $roPoolProvider; |
||||
81 | $this->etagPoolProvider = $etagPoolProvider; |
||||
82 | $this->roPool = $roPoolProvider->get(); |
||||
83 | $etagPool = $this->etagPoolProvider->get(); |
||||
84 | $this->etagPool = $etagPool instanceof TagAwareAdapterInterface ? $etagPool : $this->roPool; // @phpstan-ignore-line |
||||
85 | } |
||||
86 | |||||
87 | /** |
||||
88 | * {@inheritDoc} |
||||
89 | */ |
||||
90 | #[Override] |
||||
91 | public function get(AbstractUri $uri): ResourceState|null |
||||
92 | { |
||||
93 | $item = $this->roPool->getItem($this->getUriKey($uri, self::KEY_RO)); |
||||
94 | $state = $item->get(); |
||||
95 | assert($state instanceof ResourceState || $state === null); |
||||
96 | |||||
97 | return $state; |
||||
98 | } |
||||
99 | |||||
100 | #[Override] |
||||
101 | public function getDonut(AbstractUri $uri): ResourceDonut|null |
||||
102 | { |
||||
103 | $key = $this->getUriKey($uri, self::KEY_DONUT); |
||||
104 | $item = $this->roPool->getItem($key); |
||||
105 | $donut = $item->get(); |
||||
106 | assert($donut instanceof ResourceDonut || $donut === null); |
||||
107 | |||||
108 | return $donut; |
||||
109 | } |
||||
110 | |||||
111 | /** |
||||
112 | * {@inheritDoc} |
||||
113 | */ |
||||
114 | #[Override] |
||||
115 | public function hasEtag(string $etag): bool |
||||
116 | { |
||||
117 | return $this->etagPool->hasItem($etag); |
||||
118 | } |
||||
119 | |||||
120 | /** |
||||
121 | * {@inheritDoc} |
||||
122 | */ |
||||
123 | #[Override] |
||||
124 | public function deleteEtag(AbstractUri $uri) |
||||
125 | { |
||||
126 | $uriTag = ($this->uriTag)($uri); |
||||
127 | |||||
128 | return $this->invalidateTags([$uriTag]); |
||||
129 | } |
||||
130 | |||||
131 | /** |
||||
132 | * {@inheritDoc} |
||||
133 | */ |
||||
134 | #[Override] |
||||
135 | public function invalidateTags(array $tags): bool |
||||
136 | { |
||||
137 | $tag = $tags !== [] ? implode(' ', $tags) : ''; |
||||
138 | $this->logger->log('invalidate-etag tags:%s', $tag); |
||||
139 | $valid1 = $this->roPool->invalidateTags($tags); |
||||
140 | $valid2 = $this->etagPool->invalidateTags($tags); |
||||
141 | ($this->purger)(implode(' ', $tags)); |
||||
142 | |||||
143 | return $valid1 && $valid2; |
||||
144 | } |
||||
145 | |||||
146 | /** |
||||
147 | * {@inheritDoc} |
||||
148 | * |
||||
149 | * @return bool |
||||
150 | */ |
||||
151 | #[Override] |
||||
152 | public function saveValue(ResourceObject $ro, int $ttl) |
||||
153 | { |
||||
154 | /** @psalm-suppress MixedAssignment $body */ |
||||
155 | $body = $this->evaluateBody($ro->body); |
||||
156 | $value = ResourceState::create($ro, $body, null); |
||||
157 | $key = $this->getUriKey($ro->uri, self::KEY_RO); |
||||
158 | $tags = $this->getTags($ro); |
||||
159 | $this->logger->log('save-value uri:%s tags:%s ttl:%s', $ro->uri, $tags, $ttl); |
||||
160 | |||||
161 | return $this->saver->__invoke($key, $value, $this->roPool, $tags, $ttl); |
||||
162 | } |
||||
163 | |||||
164 | /** |
||||
165 | * {@inheritDoc} |
||||
166 | * |
||||
167 | * @return bool |
||||
168 | */ |
||||
169 | #[Override] |
||||
170 | public function saveView(ResourceObject $ro, int $ttl) |
||||
171 | { |
||||
172 | $this->logger->log('save-view uri:%s ttl:%s', $ro->uri, $ttl); |
||||
173 | /** @psalm-suppress MixedAssignment $body */ |
||||
174 | $body = $this->evaluateBody($ro->body); |
||||
175 | $value = ResourceState::create($ro, $body, $ro->view); |
||||
176 | $key = $this->getUriKey($ro->uri, self::KEY_RO); |
||||
177 | $tags = $this->getTags($ro); |
||||
178 | |||||
179 | return $this->saver->__invoke($key, $value, $this->roPool, $tags, $ttl); |
||||
180 | } |
||||
181 | |||||
182 | /** |
||||
183 | * {@inheritDoc} |
||||
184 | */ |
||||
185 | #[Override] |
||||
186 | public function saveDonut(AbstractUri $uri, ResourceDonut $donut, int|null $sMaxAge, array $headerKeys): void |
||||
187 | { |
||||
188 | $key = $this->getUriKey($uri, self::KEY_DONUT); |
||||
189 | $this->logger->log('save-donut uri:%s s-maxage:%s', $uri, $sMaxAge); |
||||
190 | $result = $this->saver->__invoke($key, $donut, $this->roPool, $headerKeys, $sMaxAge); |
||||
191 | assert($result, 'Donut save failed.'); |
||||
192 | } |
||||
193 | |||||
194 | #[Override] |
||||
195 | public function saveDonutView(ResourceObject $ro, int|null $ttl): bool |
||||
196 | { |
||||
197 | $resourceState = ResourceState::create($ro, [], $ro->view); |
||||
198 | $key = $this->getUriKey($ro->uri, self::KEY_RO); |
||||
199 | $tags = $this->getTags($ro); |
||||
200 | $this->logger->log('save-donut-view uri:%s surrogate-keys:%s s-maxage:%s', $ro->uri, $tags, $ttl); |
||||
201 | |||||
202 | return $this->saver->__invoke($key, $resourceState, $this->roPool, $tags, $ttl); |
||||
203 | } |
||||
204 | |||||
205 | /** @return list<string> */ |
||||
0 ignored issues
–
show
|
|||||
206 | private function getTags(ResourceObject $ro): array |
||||
207 | { |
||||
208 | $etag = $ro->headers['ETag']; |
||||
209 | $tags = [$etag, ($this->uriTag)($ro->uri)]; |
||||
210 | if (isset($ro->headers[Header::SURROGATE_KEY])) { |
||||
211 | $tags = array_merge($tags, explode(' ', $ro->headers[Header::SURROGATE_KEY])); |
||||
212 | } |
||||
213 | |||||
214 | /** @var list<string> $uniqueTags */ |
||||
215 | $uniqueTags = array_unique($tags); |
||||
216 | |||||
217 | return $uniqueTags; |
||||
0 ignored issues
–
show
|
|||||
218 | } |
||||
219 | |||||
220 | private function evaluateBody(mixed $body): mixed |
||||
221 | { |
||||
222 | if (! is_array($body)) { |
||||
223 | return $body; |
||||
224 | } |
||||
225 | |||||
226 | /** @psalm-suppress MixedAssignment $item */ |
||||
227 | foreach ($body as &$item) { |
||||
228 | if ($item instanceof RequestInterface) { |
||||
229 | $item = ($item)(); |
||||
230 | } |
||||
231 | |||||
232 | if ($item instanceof ResourceObject) { |
||||
233 | $item->body = $this->evaluateBody($item->body); |
||||
234 | } |
||||
235 | } |
||||
236 | |||||
237 | return $body; |
||||
238 | } |
||||
239 | |||||
240 | private function getUriKey(AbstractUri $uri, string $type): string |
||||
241 | { |
||||
242 | return $type . ($this->uriTag)($uri) . (isset($_SERVER['X_VARY']) ? $this->getVary() : ''); |
||||
243 | } |
||||
244 | |||||
245 | private function getVary(): string |
||||
246 | { |
||||
247 | $xvary = $_SERVER['X_VARY']; |
||||
248 | $varys = explode(',', $xvary); // @phpstan-ignore-line |
||||
249 | $varyString = ''; |
||||
250 | foreach ($varys as $vary) { |
||||
251 | $phpVaryKey = sprintf('X_%s', strtoupper($vary)); |
||||
252 | if (isset($_SERVER[$phpVaryKey]) && is_string($_SERVER[$phpVaryKey])) { |
||||
253 | $varyString .= $_SERVER[$phpVaryKey]; |
||||
254 | } |
||||
255 | } |
||||
256 | |||||
257 | return $varyString; |
||||
258 | } |
||||
259 | |||||
260 | #[Override] |
||||
261 | public function saveEtag(AbstractUri $uri, string $etag, string $surrogateKeys, int|null $ttl): void |
||||
262 | { |
||||
263 | $tags = $surrogateKeys !== '' ? explode(' ', $surrogateKeys) : []; |
||||
264 | $tags[] = (new UriTag())($uri); |
||||
265 | /** @var list<string> $uniqueTags */ |
||||
266 | $uniqueTags = array_unique($tags); |
||||
267 | $this->logger->log('save-etag uri:%s etag:%s surrogate-keys:%s', $uri, $etag, $uniqueTags); |
||||
268 | // Sanitize etag to remove reserved characters |
||||
269 | $this->saver->__invoke($etag, 'etag', $this->etagPool, $uniqueTags, $ttl); |
||||
0 ignored issues
–
show
$uniqueTags of type BEAR\QueryRepository\list is incompatible with the type array expected by parameter $tags of BEAR\QueryRepository\Res...torageSaver::__invoke() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
270 | } |
||||
271 | |||||
272 | public function __serialize(): array |
||||
273 | { |
||||
274 | return [ |
||||
275 | 'logger' => $this->logger, |
||||
276 | 'purger' => $this->purger, |
||||
277 | 'uriTag' => $this->uriTag, |
||||
278 | 'saver' => $this->saver, |
||||
279 | 'roProvider' => $this->roPoolProvider, |
||||
280 | 'etagProvider' => $this->etagPoolProvider, |
||||
281 | ]; |
||||
282 | } |
||||
283 | |||||
284 | /** |
||||
285 | * @param Props $data |
||||
0 ignored issues
–
show
The type
BEAR\QueryRepository\Props 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. filter:
dependency_paths: ["lib/*"]
For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths ![]() |
|||||
286 | * |
||||
287 | * @return void |
||||
288 | */ |
||||
289 | public function __unserialize(array $data): void |
||||
290 | { |
||||
291 | $this->logger = $data['logger']; |
||||
292 | $this->purger = $data['purger']; |
||||
293 | $this->uriTag = $data['uriTag']; |
||||
294 | $this->saver = $data['saver']; |
||||
295 | $this->initializePools($data['roProvider'], $data['etagProvider']); |
||||
296 | } |
||||
297 | } |
||||
298 |
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:For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths