foldershare-8.x-1.2/src/Controller/FileDownload.php

src/Controller/FileDownload.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
<?php
 
namespace Drupal\foldershare\Controller;
 
use Drupal\Component\Utility\Unicode;
use Drupal\Component\Utility\UrlHelper;
 
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\StreamWrapper\StreamWrapperManager;
 
use Drupal\file\Entity\File;
 
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
 
use Drupal\foldershare\Settings;
use Drupal\foldershare\Constants;
use Drupal\foldershare\Entity\FolderShare;
 
/**
 * Defines a class to handle downloading a file in a folder.
 *
 * This download controller is used by the FolderShareStream wrapper for
 * external URLs to access individual files managed by FolderShare.
 * The controller is used for both private and public file systems in
 * order to do access control checks before download.
 *
 * @ingroup foldershare
 */
class FileDownload extends ControllerBase {
 
  /*--------------------------------------------------------------------
   *
   * Fields.
   *
   *--------------------------------------------------------------------*/
 
  /**
   * The stream wrapper manager.
   *
   * @var \Drupal\Core\StreamWrapper\StreamWrapperManager
   */
  private $streamWrapperManager;
 
  /*--------------------------------------------------------------------
   *
   * Construction.
   *
   *--------------------------------------------------------------------*/
 
  /**
   * Constructs a new form.
   *
   * @param \Drupal\Core\StreamWrapper\StreamWrapperManager $streamWrapperManager
   *   The MIME type guesser.
   */
  public function __construct(StreamWrapperManager $streamWrapperManager) {
    $this->streamWrapperManager = $streamWrapperManager;
  }
 
  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('stream_wrapper_manager')
    );
  }
 
  /*--------------------------------------------------------------------
   *
   * Download.
   *
   *--------------------------------------------------------------------*/
 
  /**
   * Downloads the file after access control checks.
   *
   * The file is sent with a custom HTTP header that includes the full
   * human-readable name of the file and its MIME type.
   *
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The request object that contains the entity ID of the
   *   file being requested. The entity ID is included in the URL
   *   for links to the file.
   * @param \Drupal\file\Entity\File $file
   *   (optional, default = NULL) The file object to download, parsed from
   *   the URL by using an embedded File entity ID. If the entity ID is not
   *   valid, the function receives a NULL argument.  NOTE:  Because this
   *   function is the target of a route with a file argument, the name of
   *   the function argument here *must be* named after the argument
   *   name: 'file'.
   *
   * @return \Symfony\Component\HttpFoundation\BinaryFileResponse
   *   A binary transfer response is returned to send the file to the
   *   user's browser.
   *
   * @throws \Symfony\Component\HttpKernel\Exxception\AccessDeniedHttpException
   *   Thrown when the user does not have access to the file.
   *
   * @throws \Symfony\Component\HttpKernel\Exxception\NotFoundHttpException
   *   Thrown when the entity ID is invalid, for a file not managed
   *   by this module, or any other access problem occurs.
   */
  public function download(Request $request, File $file = NULL) {
    //
    // Validate arguments
    // ------------------
    // Make sure the file argument loaded.
    if ($file === NULL) {
      // Fail. The URL did not include a File entity ID, or the entity ID
      // was not valid.
      throw new NotFoundHttpException($this->t(
        "The file '@name' (ID '@id') could not be found to download.",
        [
          '@name' => $file->getFilename(),
          '@id'   => $file->id(),
        ]));
    }
 
    // Get the file's URI.
    $uri = $file->getFileUri();
 
    //
    // Validate permission
    // -------------------
    // Make sure the File entity is wrapped by a FolderShare entity, and
    // that the FolderShare entity allows 'view' operations for this user.
    //
    // There are two branches for this code:
    //
    // - When using a public file system, this download controller must do
    //   access control checks itself.
    //
    // - When using a private file system, this download controller is
    //   expected to invoke hook_file_download(). This module's hook has
    //   to handle URIs from this controller, and any other code (for instance
    //   the download controller for the Image module calls the hook).
    //   Access control checks are therefore in this module's hook, not here.
    $fileScheme = Settings::getFileScheme();
    $stream = $this->streamWrapperManager->getViaScheme($fileScheme);
    $streamDirectory = $stream->getDirectoryPath();
 
    $isPrivate = ($fileScheme === 'private');
    $headers = [];
    if ($isPrivate === TRUE) {
      // Private file system is in use.
      //
      // Drupal core supports hook_file_download(), which is implemented by
      // this module. The hook is responsible for checking if the URI is
      // for a File entity (we already know it is), and that that File entity
      // is wrapped by a FolderShare entity. The hook then checks if the
      // FolderShare entity grants the current user 'view' permission.
      //
      // If the URI, File entity, FolderShare entity, and 'view' permission
      // are all OK, the hook returns HTTP headers for the file.
      $headers = $this->moduleHandler()->invokeAll('file_download', [$uri]);
 
      // If the returned $headers array is NULL, then ALL of the hooks
      // responded that the URI is not recognized. Report a not found error.
      if ($headers === NULL) {
        // Fail. This module's hook, and all other hooks, did not recognize
        // the file.
        throw new NotFoundHttpException($this->t(
          "The file '@name' (ID '@id') could not be found to download.",
          [
            '@name' => $file->getFilename(),
            '@id'   => $file->id(),
          ]));
      }
 
      // If ALL of the hooks return (-1), then Access Denied. If only some
      // did, then remove the (-1) entries and keep the good responses.
      // However, this seems unlikely - can one hook say this is my file and
      // access denied, while another hook says this is my file and sure, go
      // ahead and get the file?
      $cleanedHeaders = [];
      foreach ($headers as $index => $response) {
        // Valid headers do not have a numeric index and do not have
        // a (-1) as the value.
        if (!(is_int($index) === TRUE &&
            is_int($response) === TRUE &&
            (int) $response === (-1))) {
          $cleanedHeaders[$index] = $response;
        }
      }
 
      if (count($cleanedHeaders) === 0) {
        // Fail. This module's hook, or some other hook, has denied access to
        // the file.
        throw new AccessDeniedHttpException($this->t(
          "You do not have permission to download the item."));
      }
 
      $headers = $cleanedHeaders;
    }
    else {
      // Public file system (or some other stream wrapper) is in use.
      //
      // The Drupal core hook_file_download() is not supposed to be used.
      // We therefore do access control checking here.
      //
      // Look for the FolderShare entity that wraps this File entity.
      $wrapperId = FolderShare::findFileWrapperId($file);
      if ($wrapperId === FALSE) {
        // Fail. There is none. While the URI is for a File entity, the File
        // entity is not one referenced by a FolderShare entity and it is,
        // therefore, not a file under management by this module.
        throw new NotFoundHttpException($this->t(
          "The file '@name' (ID '@id') could not be found to download.",
          [
            '@name' => $file->getFilename(),
            '@id'   => $file->id(),
          ]));
      }
 
      // Make sure the folder is loadable.
      $wrapper = FolderShare::load($wrapperId);
      if ($wrapper === NULL) {
        // Fail. Something has become corrupted! The above query found the ID
        // of a FolderShare entity that wraps the file, but now when the
        // entity is loaded, the load fails. This can only happen if the
        // entity has been deleted between the previous call and this one.
        throw new NotFoundHttpException($this->t(
          "The file '@name' (ID '@id') could not be found to download.",
          [
            '@name' => $file->getFilename(),
            '@id'   => $file->id(),
          ]));
      }
 
      if ($wrapper->isSystemHidden() === TRUE) {
        // Hidden items do not exist.
        throw new NotFoundHttpException(
          FolderShare::getStandardHiddenMessage($wrapper->getName()));
      }
 
      if ($wrapper->isSystemDisabled() === TRUE) {
        // Disabled items cannot be edited.
        throw new ConflictHttpException(
          FolderShare::getStandardDisabledMessage('downloaded', $wrapper->getName()));
      }
 
      // Check for view access to the FolderShare entity that manages this
      // file.
      if ($wrapper->access('view') === FALSE) {
        // Fail. The user does not have access.
        throw new AccessDeniedHttpException($this->t(
          "You do not have permission to download the item."));
      }
    }
 
    //
    // Forward if not delivering file
    // ------------------------------
    // If there is a $prefix query parameter, then use this to build a new
    // path and hand processing back to Drupal to work with that path.
    //
    // Watch for the special case where the prefix is just the scheme's
    // directory, in which case it is redundant and we can do without
    // this forwarding.
    if ($request->query->has(Constants::ROUTE_DOWNLOADFILE_PREFIX) === TRUE) {
      // Get the prefix query parameter and remove it from the request.
      // We handle the prefix specially.
      $prefix = $request->query->get(Constants::ROUTE_DOWNLOADFILE_PREFIX);
      $prefix = trim($prefix, '/');
      if ($prefix !== $streamDirectory) {
        $request->query->remove(Constants::ROUTE_DOWNLOADFILE_PREFIX);
 
        // Build a URL query string with any remaining query parameters.
        if ($request->query->count() !== 0) {
          $query = '?' . UrlHelper::buildQuery($request->query->all());
        }
        else {
          $query = '';
        }
 
        // Get the file URI's path.
        $path = trim(file_uri_target($uri), '/');
 
        // Build the original URL path that our hook_file_url_alter() caught
        // earlier and morphed into a URL for this download controller. We
        // now need to reverse that and regain the original URL by prepending
        // the original URL prefix onto the file's path.
        $newPath = UrlHelper::encodePath('/' . $prefix . '/' . $path);
 
        $redirectUrl = $newPath . $query;
 
        // Forward back to Drupal.
        //
        // We'd rather send this URL back to Drupal immediately, but it isn't
        // clear how to do this. We therefore issue a redirect response.
        //
        // Arguments to the constructor are:
        // - The URL string for the redirect.
        // - The status code.
        // - Optional HTTP heades.
        //
        // The Drupal default status code is 302, which works for HTTP 1.0
        // but has an illdefined meaning for browsers. HTTP 1.1 clarified
        // the meaning by introducing new status codes 307 and 308:
        //
        // - 307 = Temporary redirect. Future requests for the same item should
        //   issue a request to the original URL.
        //
        // - 308 = Permanent redirect. Future requests for the same item should
        //   issue a request using the new URL (i.e. they should cache the
        //   revised URL and use it only).
        //
        // The old 302 status code could be interpreted with either meaning.
        //
        // For this code, it is *essential* that the redirect be treated as
        // temporary (status code 307) so that future requests for the same item
        // will go through this file download controller again and check access
        // again. Access may have changed if, for instance, the user's access
        // has been changed by the owner of the item.
        return new RedirectResponse($redirectUrl, 307);
      }
    }
 
    //
    // Update headers
    // --------------
    // Insure that the user-visible file name is used by the browser.
    // This will override any content disposition header value that might
    // have been provided by private file system download hooks.
    //
    // Including the user-visible file name in the header is essential.
    // The file name in the URI is an internal numeric name (see
    // ManageFileSystem::getFileUri()). If the user tries to save the delivered
    // file, they'll get that numeric name instead of the user-visible name
    // if we didn't include the correct name in the HTTP header.
    $filename = $file->getFilename();
    $disposition = 'filename="' . $filename . '"';
 
    $headers['Content-Disposition'] = $disposition;
 
    // Insure that other parts of the header are set, if hooks have not
    // set them already.
    if (isset($headers['Content-Type']) === FALSE) {
      $headers['Content-Type'] =
        Unicode::mimeHeaderEncode($file->getMimeType());
    }
 
    if (isset($headers['Content-Length']) === FALSE) {
      $headers['Content-Length'] = $file->getSize();
    }
 
    // Don't cache the file because permissions and content may change.
    // Override any header values that may have been set by hooks.
    $headers['Pragma']        = 'no-cache';
    $headers['Cache-Control'] = 'must-revalidate, post-check=0, pre-check=0';
    $headers['Expires']       = '0';
    $headers['Accept-Ranges'] = 'bytes';
 
    //
    // Respond
    // -------
    // Arguments to the response indicate:
    // - The URI.
    // - A status code (200 = OK).
    // - The HTTP headers.
    // - Whether the file is public. Public files can be delivered directly
    //   by the web server, rather than Drupal.
    // - Whether to set the content disposition header value. No.
    // - Whether to set the ETag header value. No.
    // - Whether to set the Last-modified header value. No.
    return new BinaryFileResponse(
      $uri,
      200,
      $headers,
      $isPrivate === FALSE,
      NULL,
      FALSE,
      FALSE);
  }
 
}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc