1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace CodingSocks\UploadHandler\Driver; |
4
|
|
|
|
5
|
|
|
use Closure; |
6
|
|
|
use CodingSocks\UploadHandler\Helper\ChunkHelpers; |
7
|
|
|
use CodingSocks\UploadHandler\Identifier\Identifier; |
8
|
|
|
use CodingSocks\UploadHandler\Range\ResumableJsRange; |
9
|
|
|
use CodingSocks\UploadHandler\Response\PercentageJsonResponse; |
10
|
|
|
use CodingSocks\UploadHandler\StorageConfig; |
11
|
|
|
use Illuminate\Http\JsonResponse; |
12
|
|
|
use Illuminate\Http\Request; |
13
|
|
|
use Illuminate\Http\UploadedFile; |
14
|
|
|
use InvalidArgumentException; |
15
|
|
|
use Symfony\Component\HttpFoundation\Response; |
16
|
|
|
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; |
17
|
|
|
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; |
18
|
|
|
|
19
|
|
|
class ResumableJsHandler extends BaseHandler |
20
|
|
|
{ |
21
|
1 |
|
use ChunkHelpers; |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* @var string |
25
|
|
|
*/ |
26
|
|
|
private $fileParam; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* @var \CodingSocks\UploadHandler\Identifier\Identifier |
30
|
|
|
*/ |
31
|
|
|
private $identifier; |
32
|
|
|
|
33
|
|
|
/** |
34
|
|
|
* @var string |
35
|
|
|
*/ |
36
|
|
|
private $uploadMethod; |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* @var string |
40
|
|
|
*/ |
41
|
|
|
private $testMethod; |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* @var string |
45
|
|
|
*/ |
46
|
|
|
private $parameterNamespace; |
47
|
|
|
|
48
|
|
|
/** |
49
|
|
|
* @var string[] |
50
|
|
|
*/ |
51
|
|
|
private $parameterNames; |
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* ResumableJsDriver constructor. |
55
|
|
|
* |
56
|
|
|
* @param array $config |
57
|
|
|
* @param \CodingSocks\UploadHandler\Identifier\Identifier $identifier |
58
|
|
|
*/ |
59
|
79 |
|
public function __construct($config, Identifier $identifier) |
60
|
|
|
{ |
61
|
79 |
|
$this->fileParam = $config['param']; |
62
|
79 |
|
$this->identifier = $identifier; |
63
|
|
|
|
64
|
79 |
|
$this->uploadMethod = $config['upload-method']; |
65
|
79 |
|
$this->testMethod = $config['test-method']; |
66
|
|
|
|
67
|
79 |
|
$this->parameterNamespace = $config['parameter-namespace']; |
68
|
79 |
|
$this->parameterNames = $config['parameter-names']; |
69
|
79 |
|
} |
70
|
|
|
|
71
|
|
|
/** |
72
|
|
|
* @inheritDoc |
73
|
|
|
*/ |
74
|
73 |
|
public function handle(Request $request, StorageConfig $config, Closure $fileUploaded = null): Response |
75
|
|
|
{ |
76
|
73 |
|
if ($this->isRequestMethodIn($request, [$this->testMethod])) { |
77
|
6 |
|
return $this->resume($request, $config); |
78
|
|
|
} |
79
|
|
|
|
80
|
67 |
|
if ($this->isRequestMethodIn($request, [$this->uploadMethod])) { |
81
|
43 |
|
return $this->save($request, $config, $fileUploaded); |
82
|
|
|
} |
83
|
|
|
|
84
|
24 |
|
throw new MethodNotAllowedHttpException([ |
85
|
24 |
|
$this->uploadMethod, |
86
|
24 |
|
$this->testMethod, |
87
|
|
|
]); |
88
|
|
|
} |
89
|
|
|
|
90
|
|
|
/** |
91
|
|
|
* @param \Illuminate\Http\Request $request |
92
|
|
|
* @param \CodingSocks\UploadHandler\StorageConfig $config |
93
|
|
|
* |
94
|
|
|
* @return \Symfony\Component\HttpFoundation\Response |
95
|
|
|
*/ |
96
|
6 |
|
public function resume(Request $request, StorageConfig $config): Response |
97
|
|
|
{ |
98
|
6 |
|
$this->validateChunkRequest($request); |
99
|
|
|
|
100
|
|
|
try { |
101
|
6 |
|
$range = new ResumableJsRange( |
102
|
6 |
|
$request->query, |
103
|
6 |
|
$this->buildParameterName('chunk-number'), |
104
|
6 |
|
$this->buildParameterName('total-chunks'), |
105
|
6 |
|
$this->buildParameterName('chunk-size'), |
106
|
6 |
|
$this->buildParameterName('total-size') |
107
|
|
|
); |
108
|
|
|
} catch (InvalidArgumentException $e) { |
109
|
|
|
throw new BadRequestHttpException($e->getMessage(), $e); |
110
|
|
|
} |
111
|
|
|
|
112
|
6 |
|
$uid = $this->identifier->generateIdentifier($request->query($this->buildParameterName('identifier'))); |
|
|
|
|
113
|
6 |
|
$chunkname = $this->buildChunkname($range); |
114
|
|
|
|
115
|
6 |
|
if (! $this->chunkExists($config, $uid, $chunkname)) { |
116
|
3 |
|
return new Response('', Response::HTTP_NO_CONTENT); |
117
|
|
|
} |
118
|
|
|
|
119
|
3 |
|
return new JsonResponse(['OK']); |
120
|
|
|
} |
121
|
|
|
|
122
|
|
|
/** |
123
|
|
|
* @param \Illuminate\Http\Request $request |
124
|
|
|
* @param \CodingSocks\UploadHandler\StorageConfig $config |
125
|
|
|
* @param \Closure|null $fileUploaded |
126
|
|
|
* |
127
|
|
|
* @return \Symfony\Component\HttpFoundation\Response |
128
|
|
|
*/ |
129
|
43 |
|
public function save(Request $request, StorageConfig $config, Closure $fileUploaded = null): Response |
130
|
|
|
{ |
131
|
43 |
|
$file = $request->file($this->fileParam); |
132
|
|
|
|
133
|
43 |
|
$this->validateUploadedFile($file); |
|
|
|
|
134
|
|
|
|
135
|
37 |
|
$this->validateChunkRequest($request); |
136
|
|
|
|
137
|
12 |
|
return $this->saveChunk($file, $request, $config, $fileUploaded); |
|
|
|
|
138
|
|
|
} |
139
|
|
|
|
140
|
|
|
/** |
141
|
|
|
* @param \Illuminate\Http\Request $request |
142
|
|
|
*/ |
143
|
43 |
|
private function validateChunkRequest(Request $request): void |
144
|
|
|
{ |
145
|
43 |
|
$validation = []; |
146
|
|
|
|
147
|
43 |
|
foreach ($this->parameterNames as $key => $_) { |
148
|
43 |
|
$validation[$this->buildParameterName($key)] = 'required'; |
149
|
|
|
} |
150
|
|
|
|
151
|
43 |
|
$request->validate($validation); |
|
|
|
|
152
|
18 |
|
} |
153
|
|
|
|
154
|
|
|
/** |
155
|
|
|
* @param \Illuminate\Http\UploadedFile $file |
156
|
|
|
* @param \Illuminate\Http\Request $request |
157
|
|
|
* @param \CodingSocks\UploadHandler\StorageConfig $config |
158
|
|
|
* @param \Closure|null $fileUploaded |
159
|
|
|
* |
160
|
|
|
* @return \Symfony\Component\HttpFoundation\Response |
161
|
|
|
*/ |
162
|
12 |
|
private function saveChunk(UploadedFile $file, Request $request, StorageConfig $config, Closure $fileUploaded = null): Response |
163
|
|
|
{ |
164
|
|
|
try { |
165
|
12 |
|
$range = new ResumableJsRange( |
166
|
12 |
|
$request, |
167
|
12 |
|
$this->buildParameterName('chunk-number'), |
168
|
12 |
|
$this->buildParameterName('total-chunks'), |
169
|
12 |
|
$this->buildParameterName('chunk-size'), |
170
|
12 |
|
$this->buildParameterName('total-size') |
171
|
|
|
); |
172
|
|
|
} catch (InvalidArgumentException $e) { |
173
|
|
|
throw new BadRequestHttpException($e->getMessage(), $e); |
174
|
|
|
} |
175
|
|
|
|
176
|
12 |
|
$weakId = $request->post($this->buildParameterName('identifier')); |
177
|
12 |
|
$uid = $this->identifier->generateIdentifier($weakId); |
|
|
|
|
178
|
|
|
|
179
|
12 |
|
$chunks = $this->storeChunk($config, $range, $file, $uid); |
180
|
|
|
|
181
|
12 |
|
if (!$range->isFinished($chunks)) { |
182
|
6 |
|
return new PercentageJsonResponse($range->getPercentage($chunks)); |
183
|
|
|
} |
184
|
|
|
|
185
|
6 |
|
$targetFilename = $file->hashName(); |
186
|
|
|
|
187
|
6 |
|
$path = $this->mergeChunks($config, $chunks, $targetFilename); |
188
|
|
|
|
189
|
6 |
|
if ($config->sweep()) { |
190
|
|
|
$this->deleteChunkDirectory($config, $uid); |
191
|
|
|
} |
192
|
|
|
|
193
|
6 |
|
$this->triggerFileUploadedEvent($config->getDisk(), $path, $fileUploaded); |
194
|
|
|
|
195
|
6 |
|
return new PercentageJsonResponse(100); |
196
|
|
|
} |
197
|
|
|
|
198
|
|
|
/** |
199
|
|
|
* @param $key string |
200
|
|
|
* |
201
|
|
|
* @return string |
202
|
|
|
*/ |
203
|
43 |
|
private function buildParameterName(string $key): string |
204
|
|
|
{ |
205
|
43 |
|
if (! array_key_exists($key, $this->parameterNames)) { |
206
|
|
|
throw new InvalidArgumentException(sprintf('`%s` is an invalid key for parameter name', $key)); |
207
|
|
|
} |
208
|
|
|
|
209
|
43 |
|
return $this->parameterNamespace . $this->parameterNames[$key]; |
210
|
|
|
} |
211
|
|
|
} |
212
|
|
|
|
This check looks at variables that are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.