Problem
Follow-up from #2211227: Refactor image and imagetoolkit: isExisting, isSupported, supportedTypes, getMimeType.
The image system should be restricted to image processing, not image-as-a-file handling.
How does this show:
- ImageInterface and ImageToolkitInterface both have a method getMimeType(). This information has no use during image processing, nor when processing html img tags, only when serving an image file. (and when saving an image, but that is until #2168511: Allow to pass save options to ImageInterface::save gets in.
- ImageInterface has a method getFileSize(). This information has no use during image processing, nor when processing html img tags, only when serving an image file.
- ImageStyleDownloadController creates an image object when it serves an image derivative. It uses this image to get the source, mime type, and file size. The source was already passed to the image constructor, so there's no Image object needed to get that info. The mime type and file size can as well be served by already existing file functionality.
- image_file_download() kind of mimics file_file_download (it actually call this hook) but, in the end, uses an image object to get the Content-Type and Content-Length headers.
Proposed resolution
- Remove use of getMimeType() from ImageInterface outside the image subsystem.
- Remove getFileSize() from ImageInterface and Image.
- Let ImageStyleDownloadController use standard, already existing, file functionality.
- Let image_file_download() use standard, already existing, file functionality.
Note 1: the image system determines mime type based on content (thereby opening and reading the file), the file system determines the mime type based on extension. This might fail when we have image derivatives that were saved in another image format (we can't change extension of image derivatives). However, as soon as an image derivative is created on the public file system, the webserver will do the same as our file system. So this only differs for first time image derivative requests (when the derivative gets created by Drupal) and for requests on the private file system (that always pass through Drupal). IMO, no big deal thus.
Notes added after writing the patch:
- This existing file functionality does indeed exist but not in 1 place: for the file size, filesize() is a simple PHP core function that can be used.
- For mime type, Symphony adds mime type guessers based on content (finfo) and a binary guesser (file -b --mime command on linux). Drupal adds an extension based checker and a multi guesser. This patch defines a service around this functionality. Please check the logic in core.services.yml as I did not really know what I was doing: a bit of copy and paste from the existing guesser and some thought up naming.
- This patch solves a bug: file_validate_image_resolution() in file.module used $file->filesize, a property on Drupal\file\Entity\File whereas $file is a FileInterface and it thus cannot be expected that it will always be a Drupal\file\Entity\File. This patch changes this into setFileSize().
- This patch solves a documentation issue in ImageStyle.php.
- The current implementation of image_file_download() (implementation of hook_image_download) checks if the file is valid and of a supported image format. This check can fail in edge situations and reduces flexibility for contrib to add e.g. webp support. This patch removes the check.
- The current implementation of image_file_download() does not return NULL in other case, as the docs prescribe. This patch adds this omission.
- This patch does not remove getMimeType() from Image as there are currently some issues in the queue that explicitly depend on mime type being available in image. #2168511: Allow to pass save options to ImageInterface::save will revisit this part.
- Even if we cannot get to an agreement there are some changes in this patch that can still be committed. The errors it solves, the documentation error and the mime type service. It was @Jelle_S who notified me of the existence of mime type checkers and that this service can be used in #2340699: Let GDToolkit support WEBP image format as well.
Remaining tasks
- Agree on proposal.
- Review and accept patch.
User interface changes
None.
API changes
Will be documented in the main change record of the image system overhaul.
Comment | File | Size | Author |
---|---|---|---|
#9 | interdiff.txt | 560 bytes | fietserwin |
#9 | restrict_image_system-2257163-8.patch | 13.14 KB | fietserwin |
Comments
Comment #1
fietserwinFirst try. I updated the issue summary to follow status of the current patch.
Comment #2
Jelle_SJust a quick review (I didn't do any manual testing):
Copy-paste error: This is not an image factory.
I would add priorities to the services so we can control which one gets called first. I believe if we do it in the way it is currently implemented in the patch, the MIME type guesser that guesses the MIME type based on the extension will get called first (because it comes first in the YAML file), which is not reliable and will always result in skipping the other guessers because it defaults to "application/octet-stream" (doc link) and the multi guesser provided by Drupal returns the first MIME type it gets (doc link)
This part of the comment isn't entirely clear to me:
Why not? If it's supported, it's supported? I don't really see what the toolkit has to do with it?
Comment #3
fietserwinre #2: thanks for this quick review.
#re 2.1: Good catch. In fact, the goal of this issue is to remove usage of the image sub system when not doing image manipulation. So the image factory property/constructor parameter should have been deleted.
#re 2.2: The order is OK, I had debugged that. In the absence of the priority parameter, the order is determined by the order of calls to addGuesser() and that is determined by the 3 lines in this part:
This can indeed be made more clear, but I have no clue if I can pass a 2nd parameter by using this declarative syntax only.
Note: the Symphony provided finfo guesser always returns the value from finfo->file() though that can be FALSE according to the PHPdoc. So I think that guesser violates the interface documentation and our multi guesser strictly follows that interface (guess() returns the mime type or NULL and thus not FALSE and the check is on strictly NULL). I don't think that we should do something about that here.
#re 2.3: This comment was more describing the change then documenting the new code. After some further analysis, I concluded that the check on file_exist() always returns TRUE and thus can be removed. I added some documentation about it in the function doc.
FYI: it is the toolkit that defines which image formats it supports. Thus an ImageMagick based toolkit in contrib can define that it supports webp, bmp, etc. without the need to change the core provided GD toolkit. The former check could instantiate the GD toolkit to ask if the file is a supported image format: incorrect fail.
Comment #4
Jelle_S#3.1 Cool!
#3.2 To add the priority to the guessers just add them to the individual declaration of the services I highlighted in #2.2 as a tag (the same way you added the name tag). For a working example see http://cgit.drupalcode.org/drupal/tree/core/modules/image/image.services...
#3.3 Ok, thanks for clarifying.
I haven't reviewed your patch in #3 since I'm on my tablet now.
Comment #5
Jelle_SSo besides the priorities I spoke about in #4.2 I found three more nitpicks:
Nit: s/mime/MIME
Nit: s/mime/MIME
guesser in previous comment vs provider in this comment. I'd make in consistent and go with guesser.
Nit: s/MIME-type/MIME type
[...] the image's size and MIME type. (lose the three dots at the end)
Comment #6
fietserwinThanks, all solved. Added a line in image_file_download that was in the previous interdiff but not in the previous patch.
BTW: I was flabbergasted by the fact that a higher number has a higher priority. To me this is counter intuitive: priority 1 is top priority. I found this while manual testing the mime type guessing, so that ha been done :)
Comment #7
Jelle_SOne final little nitpick (sorry to keep doing this but I assume core committers will catch these things as well and won't commit it as long as it's in there)
s/when the use/when the user (forgot the r).
has not access => has no access or does not have access
The rest of the patch looks great. I'll try to do some manual testing later today or maybe tomorrow.
Comment #8
fietserwinCorrected, and no problem, I do the same when I review.
Comment #9
fietserwinForget #8, it's not correct English (I think).
Comment #10
heddnMe thinks should be postponed at this point. It is too late to make into 8.0.x
Comment #17
andypost