Problem/Motivation
Drupal 8.5 supports reading, creating, modifying and deleting any content entity, because #2824572: Write EntityResourceTestBase subclasses for every other entity type. was completed. Hurray!
However, there is one exception: File entities can not be created. Because it is not yet possible to upload the file associated with a File entity.
File entities are a special case because unlike other entity types, they correspond to stored files, which can range in size from a few bytes to many gigabytes.
Proposed resolution
A new @RestResource plugin that only supports the creation of file entities. This endpoint will only accept binary data (application/octet-stream), not any file entity representation, although a file entity will be created from this data. Large amounts of data can then be streamed in a request instead. These file entities will have their status field's value set to false, i.e. they are not in a permanent state. The uploaded file must be validated within the context of a file field, as we are currently restricted to this approach in Drupal — as files are not stand alone first-class entities. So all appropriate file entity validations configured for the file field context being used (such as size and extension) will be applied.
A second request can then be made, to reference the created file entity from a referencing entity. When the referencing entity is updated or created, and the file uploaded in the first request becomes referenced, that will in turn make the file permanent (status=true).
Design decisions explained:
Content-Type: application/octet-stream+php://input- Naïve implementations encode (binary) file data as strings in JSON/XML/YAML/… request bodies. But this means much slower file uploads, and more importantly, the need for a very expensive decoding on the receiving (server) side. In PHP in particular, this would mean the file size for uploaded files would be limited by the PHP memory limit (and actually, significantly below that, due to the encoding into a string).
See @damiankloip in #281.
- Only allows file uploads in the context of an entity type/bundle's file field
- Validation of uploaded files in Drupal today always is configured at the field level. Uploading arbitrary files is dangerous (think uploading HTML/SVG with embedded JS, or malware executables), therefore validation is necessary, validation must come from somewhere. (@Berdir in #291)
This means that we should perform@FieldType=file/@FieldType=image(the latter subclasses the former) validators (as configured in their field definition) even though we're not saving such a field (@damiankloip in #315). That's the only way we can (pragmatically) achieve file uploads without requiring lots of additional configuration to validate and hence guarantee safety. Explained clearly for the first time by @Berdir in #326. It also determines where the file is stored (stream wrapper + subdirectory, see @Berdir in #334)
The resulting must havestatus=false, because the uploaded file is not yet being used anywhere (becauseFileentities do not have canonical URLs of their own — this is an existing limitation in HEAD).
(Note this contrasts with @damiankloip original vision. Which was:Tying file uploads via REST to some entity type/bundle + field seems strange from a REST perspective (@damiankloip in #299 + #306).
, plus his comment in #320. It was also the opposite of @Wim Leers original vision (see #332). It's the discussion with the community and @Berdir in particular that convinced him to go this way, and so that is the consensus that was reached. Also: yes, this is very confusing due to historical reasons.)
- One request to upload the file (using "real" PHP upload semantics, response= created
Fileentity), second request to use the file - First framed conceptually by @garphy in #305 and then explicitly by @ibustos in #321. @dagmar in #304 stated that this is also what Contenful does. As @garphy explained in #305: this is also essentially how Drupal is already does it when handling AJAX file uploads. Confirmed by @damiankloip in #315. The first request creates a
Fileentity withstatus=false, the second request makes the referencing entity "use" theFileentity, and will result in thatFilegettingstatus=TRUE. - How to name the uploaded file?
- Considered: query string, path parameter, request header (@damiankloip in #315). @dabito in #319 was the first to point out the
Content-Dispositionrequest header, @Wim Leers confirmed that this was the actual recommended best practice by referencing the Mozilla docs at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Dispos... in #328.
- How is the uploaded file actually validated?
-
It was necessary to copy some logic from
FileItem::getUploadValidators()+file_save_upload()(#344)
We're validating theContent-Dispositionrequest header (see @damiankloip's comment at #397 and #447)
Also see below:Are edge cases tested?
— lots of things that are sort-of-validation that are explicitly tested!
- Are edge cases tested?
- Yes!
- Custom upload directories: #387 (including ones with tokens!)
Content-Dispositionrequest header values (see @damiankloip's interdiff at #397)- Zero-byte file upload (@damiankloip's interdiff at #409)
- Disallowed file type file upload (@damiankloip's interdiff at #409)
- Disallowed file size file upload (@damiankloip's interdiff at #410)
- File upload with size greater than PHP memory limit: manually tested by @blainelang at #414, impossible to test automatically per @damiankloip's comment in #438
- File with duplicate name (@damiankloip's interdiff at #432 + @tedbow's review in #494 + @damiankloip's interdiff at #507)
- File with non-ASCII characters in its name (@damiankloip's interdiff at #444)
- Malicious uploads (@daminakloip's interdiff at #457)
- Uploads that use a URL that don't actually reference an existing entity type + bundle + field combination are disallowed (@damiankloip's interdiff at #482)
- 403 in case you don't have entity/field access to the file field for which you're uploading a file (@damiankloip's interdiff at #482)
- Access checking
- Per the above, we must check entity
createaccess for the referencing entity type and fieldeditaccess for the file/image field, which happens inFileUploadResource::validateAndLoadFieldDefinition(). As originally pointed out in #326 by @Berdir and fixed (including explicit test coverage) by @Wim Leers in #487.4
- If you're wondering about what "temporary" means …
- … @Berdir clearly explains in #329 that there's two kinds:
- as in
public://vstemporary:// - as in
status=TRUEvsstatus=FALSE
This patch saves the uploaded file to
public://and setsstatus=FALSE. See #329.
- as in
- Removal of
\Drupal\hal\Normalizer\FileEntityNormalizer::denormalize() - The current code is A) insecure (see @damiankloip in #507), B) nobody could have been using it successfully anyway because HAL's denormalizers fail for file and image fields (see @Wim Leers in #498).
(If there is one comment you're going to read, it should be @Berdir's #334.)
Remaining tasks
None.
User interface changes
None.
API changes
- API addition:
file_upload@RestResource(path:/file/upload/{entity_type_id}/{bundle}/{field_name}that binary file data can bePOSTed/streamed to. - API addition: if a
@RestResourceplugin already defines a_formator_content_type_formatroute requirement,ResourceRouteswon't overwrite it with the configured formats. This is necessary to allow binary file uploads: the request body doesn't use any format, it sends binary data. (This was discovered by @damiankloip in #355 — related fixes landed in #2901704: Allow REST routes to use different request formats and content type formats, but this additional capability is landing as part of this issue.) - API addition: added
RequestHandler::handleRaw(), which@RestResourceplugins can optionally opt in to, to allow their allowed requestContent-Typeheaders to deviate from the configured formats, for example for binary request bodies.
| Comment | File | Size | Author |
|---|---|---|---|
| #581 | 1927648-581.patch | 77.57 KB | nicrodgers |
| #579 | 1927648-579.patch | 77.69 KB | ijsbrandy |
| #579 | interdiff_567-579.txt | 7.44 KB | ijsbrandy |
| #566 | 1927648-567.patch | 85.81 KB | alexpott |
| #566 | 558-567-interrdiff.txt | 857 bytes | alexpott |
Comments
Comment #1
Anonymous (not verified) commentedWe had discussed creating this as a temp file stream and then passing that off to the field. When you create a temp file, is there any way it is accessible via HTTP? If so, I believe it could open up a vulnerability where a malicious script could be base64 encoded and then run. I don't know enough about the file system to be sure.
Comment #2
moshe weitzman commentedIt would be poor practice but I do think some sites put their temp dir within the docroot. So, your worry is well founded. I debugged our current file upload validation and the key function we have to mimic is file_save_upload. We have to mimic it because it is hard coded to work with uploads (i.e. the $_FILES superglobal).
The key bits from that function are
Unfortunately, this function seems to do a bit of validation of its own (file_validate_extensions, file_munge_filename(), file_validate_name_length) that are not contained within file_validate() call. As a first pass, I would just ignore this extra validation and rely on the file_validate() call.
Also, you will want a stream wrapper which is inline data, so you don't have to save the file before calling file_validate. See the data:// wrapper at http://php.net/manual/en/wrappers.data.php
Comment #3
moshe weitzman commentedAdd tag
Comment #4
Anonymous (not verified) commentedWe discussed this on the REST call today.
I'm pretty sure that image fields are basically a special kind of entity reference, which reference a file entity. Thus, the URI should be a property of the file entity, which means that we can handle the relationship between the file entity and the referencing entity could be handled in the same way.
Comment #5
Anonymous (not verified) commentedWe discussed this with quicksketch on one of the calls. He agreed that the API interaction should be to create the file entity and then use the entity id from that to make another call to create the node that points to it.
To do this, we need Entity API support for files, #1818568: Convert files to the new Entity Field API.
Comment #6
Anonymous (not verified) commentedBecause supporting this in serialization is a separate issue, independent of REST, I created #1988426: Support deserialization of file entities in HAL.
For now, we should be able to put in a stub Normalizer that handles the current File class (which is not NG). This approach can be re-evaluated once File in NG.
Comment #7
moshe weitzman commentedStill a gaping hole in our REST support. Come on Internets ...
Comment #8
rcaracaus commentedI managed to get this working on GET, sort of.. but it was a hack. What are the next steps to properly get this into core now that the blockers above are resolved?
Comment #9
rteijeiro commentedHi @rcaracaus,
How did you manage to get this working? I'm getting this output:
I'm not sure if I have to make a new request for image entity or something else in order to get the image url, for example.
Comment #10
webchickSeems like this should be elevated, given the impact.
Comment #11
rteijeiro commentedI'm pretty interested in make this work. If someone can point me how to start working on this (related issues or something else) I will be pleased to contribute :)
Comment #12
clemens.tolboom@rteijeiro we do have our admin/help/rest which goes to https://www.drupal.org/documentation/modules/rest and in particular https://www.drupal.org/node/2098511 (POST content)
As pointed out in #0 and #2 checkout devel_generate in particular DevelGenerateFieldImage.
I use UIs on Chrome: Rest console and DHC - REST HTTP API Client but prefer command-line scripts
[Stock response from Dreditor templates and macros.]
Please update the issue summary by applying the template from http://drupal.org/node/1155816.
Comment #13
rteijeiro commentedComment #14
rteijeiro commentedComment #15
webchickWent to look into this tonight, but discovered that atm Devel Generate's image generation is broken: #2294283: Generate images is broken So can't really use that as a model.
Comment #16
juampynr commentedHere is a first stab at this.
Given the following:
And the following Guzzle 3 request:
With the attached patch image fields get populated. Any feedback is welcome. I will start adding testing coverage.
Comment #18
clemens.tolboomI just created a patch for GET in #2310307: File needs CRUD permissions to make REST work on entity/file/{id} so revisit this issue.
The title for this issue is weird as it suggest all 'REST operations'. This seems only the be about POST a parent content type _containing_ an image. Please update the summary :)
Reading the patch and issue comments what's the plan for ie a PDF file aka non-images?
The patch misses handling for image.alt and image.title
In #2 it seems we are coding around the validators. Is that still the biggest problem?
A side note: in #1925618: Ensure Drupal's web services are self-documenting: Swagger support OR rest_api_doc to Drupal core as an experimental module? I try to build the API docs by digging into the exposed entity/bundle/field features. How can I get to (lookup) this new normalizer in context of json and hal+json?
Comment #19
berdirYes, should work for all files. image/file references are both entity references, it would make a lot more sense to treat this like any other reference I think, so we would add this base64 magic on the file resource, so you'd upload your file first, and then reference it using the UUID in the image/file field? You could also have entity_reference fields pointing to files, for example.
Comment #20
juampynr commentedThanks for the feedback! I will give it another go and check out what's going on on the issues that @clemens.tolboom mentioned.
Comment #21
clemens.tolboomWhy rand() call?
What about filename transliteration?
This should not always be public://
Comment #22
juampynr commentedAddressed @clemens.tolboom's suggestions:
* Removed
rand()statement.* Filename is now transliterated.
* Now reading field insance's settings in order to determine where to save the file.
Comment #23
juampynr commentedOopsie. Here are the files.
Comment #24
juampynr commentedNext, I will address suggestions in #18 and #19.
Comment #26
juampynr commentedUpgrading scope so it covers file fields and not just image fields.
Comment #27
clemens.tolboomComment #28
arla commentedBased on the latest patch, I have just implemented something like this for the File entity module. I will make it a patch for core shortly and post it here.
Comment #29
juampynr commentedHere it is :D
Next step, I will create another one that inherit from this normalizer for Images.
Comment #30
juampynr commentedHere is the sample script that I have been using. The readline() statement is a sneaky trick to be able to trigger XDebug for the Drupal request and not this script:
Comment #32
joshk commentedIs there a separate issue for handling the GET side of things? I'm working on a D8 + Angular demo and getting rest responses back (read only even) with image references would make it approximately 1100% hotter.
Comment #33
berdirThe patch that @Arla is working on makes file/image behave like other entity references (with a UUID reference) and moves the support for passing along the file content as base64 to GET/POST of files, so that should include GET yes.
Comment #34
clemens.tolboom@berdir where is that issue patch done by @arla as that sound like a way better solution then this :)
Comment #35
arla commentedSo here's the patch.
I'm not sure how image fields are affected by these changes.
I got lots of help from @Berdir.
Comment #36
clemens.tolboom@Arla please add some hints on how to test this manually. I assume you have some code lying around :)
Comment #37
arla commentedI have just been running the hal kernel tests EntityTest, FileNormalizeTest and FileFieldNormalizeTest. I suppose REST requests will work as expected assuming the normalization is correct.
Comment #38
berdirIt works with the same principle as the other patch, but you submit the base64 data first by creating a file entity, then you get a UUID (or you submit it already) and then create a node where you reference the file through that UUID. Just like you would create a node with author, or a node with terms.
Comment #39
clemens.tolboom@Arla @Berdir I expected a non base64 solution but misread. So File entity supports temporary files now seemed a problem mentioned in #2 and #18.
#35 is a big improvement :-)
Comment #41
arla commentedActually, it might make more sense to pull the responsibility of normalizing the field properties (description, alt, etc.) up to EntityReferenceItemNormalizer, like in this patch.
Comment #43
arla commentedBy relying on
parent::denormalize()in FileEntityNormalizer, we practically bypass the filename and filemime assignment inFile::preCreate()(because ContentEntityNormalizer passes an empty$valuestocreate()). Thus I removed a bit from FileDenormalizeTest which was testing just this.Another option could be be to do
ContentEntityNormalizer::denormalize()in a different way, but I haven't looked into that... yet.Edit: I realized that the point of handling 'patch' differently in denormalize() is precisely to avoid setting default values. So it should make sense to remove that particular test case as I did.
Comment #45
juampynr commented@arla, how can we test this? I tried the following without success:
1. Gave access to anonymous users to file and node entities.
2. Added a file field to the page content type.
3. Created a node with a file.
4. Opened http://d8.local/node/2 >> I can see the file as an embedded entity.
5. Tried to access the file without luck. I guess this is related with #2310307: File needs CRUD permissions to make REST work on entity/file/{id} .
6. What should be the structure to be sent in order to create a file? I can't figure it out by reading the above patch.
Comment #46
berdirYeah, not being able to access the file is an access problem, we've been testing this in combination with https://github.com/md-systems/file_entity, which adds an improved access controller.
The structure to create a file is the same as you get with proper access, a normal content entity serialized to hal+json and additionally 'value' with the base64 encoded file content.
Comment #47
clemens.tolboomFile CRUD is addressed in #2310307: File needs CRUD permissions to make REST work on entity/file/{id}
Comment #48
arla commentedWithout using file_entity module, you can test it like this:
To correct #46, the base64 encoded file content goes in 'data', not 'value', per the patch. The name of this JSON property is subject to review, though.
Comment #49
arla commentedTrying to address the failing tests. In essence:
Comment #50
clemens.tolboomIn #18 one of my questions was about #2
A rephrase:
We currently want to upload files by creating a entity with file/image fields containing BASE64 encoded field values.
Why do we do it this way?
Why not similar to 'normal' Drupal UI or like the workflow on https://developers.facebook.com/docs/php/howto/uploadphoto/4.0.0 is to post a file and use the resulting ID for further processing. They use http://php.net/manual/en/class.curlfile.php
What happens if I want to create a node containing lots of images when the REST POST size it to big to handle?
Comment #51
berdirI don't understand the question.
We *are* doing separate requests for files, so if you want to upload 10 files, you do 10 POST requests for the files first, then pass all the UUID's when you POST the node.
The only difference is that we pass it along as base64 encoded instead of a multipart POST, but I have no idea if our rest/serializer API supports that.
Comment #52
clemens.tolboom@Berdir then please update the summary and title according.
I only scanned the patch and comments so misconcluded.
The steps in @Arla in #48 suggest it's fieldness too.
Comment #53
arla commentedYes, since #33 we have been moving the serializing of file contents from the file field to the referenced file entity. Editing summary accordingly.
Comment #54
clemens.tolboomComment #55
clemens.tolboomComment #56
arla commentedReroll.
Comment #59
larowlanrerolling
Comment #60
dave reidHow would this work with stream wrappers that don't need to be base-64 encoded, like referring to files on Amazon S3? Is there a way to bypass this?
Comment #61
larowlanYeah I think we need to check for the presence of 'data' and fallback to default, which the current patch doesn't yet do
this bit
Comment #62
larowlanre-roll, EntityTest fails - revision id is being added to entity_id field for comments
doesn't address #60, #61
Comment #63
larowlanComment #65
larowlanI think #2209981: ConfigurableEntityReferenceItem should not mess with the field schema will fix those fails
Comment #66
larowlan#2356609: Remove support for "reference a specific revision" is in
Comment #67
cloudbull commentedMay i ask for a sample of json file that use to post for file upload ? I cannot figure out whether it is a client / server side problem.
Thanks
Keith
Comment #68
cloudbull commentedThis is the json file i use to post file, tried to put both embedded and field_image no luck
I used angularjs project of #32, added a angularjs images to base64 library.
Any help is appreciated.
Comment #69
larowlanCloudbull please open a new support issue for your particular problem - this issue is about changing the way files are serialized - not providing support for the current format.
Comment #70
cloudbull commentedlarowlan,
I mean i applied the patch, but things doesn't work. The node is created but file is not uploaded. So, i put my testing json file here for reference.
Keith
Comment #71
larowlan@cloudbull - oh sorry - misunderstood
Comment #72
cloudbull commentedSo i think the status will be need work ?
Comment #73
berdirNo.
The structure for POST'ing is always identical to the structure you get back. So manually create a file on the server, then do a GET on file/ID and change it as you want.
Your example structure is impossible to read without being formatted, but one thing that is wrong for example is that the base64 encoded file contents are in 'data', not 'file'.
Comment #74
cloudbull commentedBerdir,
thanks your help.
This is the sample i get from D8 beta2, from the embeded image part. But, I think it need to change in order to fit the base64 content, right ?
if #16 / #30 's PHP client is correct, the json should be something like
But i cannot see any json return from the server is similar to this format. Any idea will be appreciated.
Thanks
Keith
Comment #75
berdirThat's because they messed up the href to the file, it should be something like file/1, aka a resource that you can fetch. That would contain the data you are looking for :(
Have a look at https://github.com/md-systems/file_entity, that should change that and give you a resource you can work with. Note that the module is in early development phase and might/will break.
Comment #76
cloudbull commentedThanks Berdir, So u mean i need to install this module as a contrib module in order to make post file works ?
Comment #77
berdirFor now, yes. This issue is one step towards making it work in core, but I'm no sure how far we can or should go here. The referenced permission issue is another step (that should also not be a problem if you use file_entity), the URI/href stuff is yet another problem.
Files have always been a second-class thing in Core, and that's changing only slowly.
The current patch is all about serialization of the file, so it only solves the first part of the issue title. Maybe it should be moved to a separate issue, so we can get it in as a first step without being blocked on having an complete working rest integration.
Comment #78
cloudbull commentedOK, thanks Berdir, I will install this module and try GET file/1 first. As for now, without this module, I even cannot do file/1 but ok with node/1 or user/1 in D8 Beta2
Comment #79
skyredwang@Berdir, make sense. I don't know how much work has been done in your D8 file_entity on github. But, since D8 support both file and rest, therefore, we need to patch either core or upload your branch to file_entity. I would guess rest integration for file entity needs to go in core.
Comment #80
cloudbull commentedBerdir,
I tried to enable the file_entity module and got error below
[Tue Oct 28 20:55:14.114583 2014] [:error] [pid 4811] [client 127.0.0.1:49954] PHP Fatal error: Call to a member function getConfigDependencyName() on a non-object in /var/www/html/src/drucloud-distro/core/lib/Drupal/Core/Entity/EntityDisplayBase.php on line 165, referer: http://localhost/src/drucloud-distro/admin/modules
Is that any update require to work with D8 Beta2 ?
And then calling file/1 will have error below
[Tue Oct 28 20:58:42.583792 2014] [:error] [pid 1531] [client 127.0.0.1:49974] Uncaught PHP Exception Drupal\\Core\\Database\\DatabaseExceptionWrapper: "SQLSTATE[42S22]: Column not found: 1054 Unknown column 'base.type' in 'field list': SELECT base.fid AS fid, base.type AS type, base.uuid AS uuid, base.langcode AS langcode, base.uid AS uid, base.filename AS filename, base.uri AS uri, base.filemime AS filemime, base.filesize AS filesize, base.status AS status, base.created AS created, base.changed AS changed\nFROM \n{file_managed} base\nWHERE (base.fid IN (:db_condition_placeholder_0)) ; Array\n(\n [:db_condition_placeholder_0] => 1\n)\n" at /var/www/html/src/drucloud-distro/core/lib/Drupal/Core/Database/Connection.php line 569
Thanks
Keith
Comment #81
sam152 commented+1 RTBC. Installed and exporting/importing entities with file/image fields works as expected. This fixed another issue where some were missing on the field causing SQL errors when importing files.
Comment #84
sam152 commentedHmm, after digging into this a little more, is "included_fields" being overloaded here? Should the base64 be toggled with a different key, say "include_data"? I think there are some places which expect everything in that array to be a valid field name, which data is not (I believe?).
InvalidArgumentException: Field data is unknown. in Drupal\Core\Entity\ContentEntityBase->getTranslatedField() (line 349 of core/lib/Drupal/Core/Entity/ContentEntityBase.php).Comment #85
sam152 commentedReroll.
Comment #86
sam152 commentedContinuing on from #84, if users can skip the base64 encoding, should the default behaviour be to still download the files over HTTP? Would seem useful for larger files.
Comment #88
dave reidI also have a suspicion that base 64 encoding is not going to be useful for the majority of sites when using GET operations.
Comment #89
sam152 commentedSo if we were to consolidate the requirements for getting this over the line, they might be:
* Optional base64 encoded assets (based on some context, not sure what specifically just yet).
* Default behavior is to base64 encode?
* Fall back downloads the assets over HTTP.
Would be good to get a decisive direction before doing more development.
Comment #90
queenvictoria commentedI'm not sure if I'm doing the right thing here so please have patience. In 88 and 89 it seems to indicate that including base64 in the HAL response is probably not the right way to do things (possibly just the URI maybe?). However I am trying to get support for base64 in PUT and the method I'm following is to use the format provided by GET.
Firstly I've applied the patch in 85 to the current 8.0.x branch. There is a chuck that doesn't apply which doesn't seem to be related anyway so I've ignored it.
Secondly in order to return the data field I've tried to add 'data' to the included fields. This fails as 'data' is not in the field definition for 'files'.
Finally I've unset the included_fields completely for files as that has the same effect (ie: includes ALL fields). This works well for returning the encoded file in the HAL response.
Again: I don't think this is the right way to proceed. Please use with caution.
Comment #91
larowlango bot go
Comment #93
jhedstromFeature request => 8.1.x.
Comment #94
marthinal commentedHi guys!
I need to upload/create images from an angular app.
If I apply #85 (Reroll) I can upload images but we have a couple of problems :-)
1) The first one is "permissions". I'm checking #2310307: File needs CRUD permissions to make REST work on entity/file/{id} and to be honest I'm not sure if file_entity will be the solution or we could fix it in core. I can create images commenting the access() method on post. Enabling file_entity with the same hal+json i have this error:
error: "Unprocessable Entity: validation failed. type.0: The referenced entity (<em class="placeholder">file_type</em>: <em class="placeholder">file</em>) does not exist. "I need to verify what's going on..
Should we have these permissions into the core?? At least Add/upload and view... Not sure if this is the way and how complicated it is...
2) Once the file is created we should receive the id needed when creating a node for example.
Comment #95
clemens.tolboom@marthinal best thing to do is upload your reroll of #85 so we know what you're talking about :-)
BTW what is wrong with #90? Should we hide it?
Comment #96
marthinal commented@queenvictoria about #90, I found the problems described in #94 and had no time to check your changes.
Comment #97
marthinal commentedAbout #90, Why not avoid to add "data" property on FileEntityNormalizer::normalize() ?
Rerolled #85.
Need help with #94
@queenvictoria Not sure about your changes, if we want to avoid the "data" property for GET method(because the uri is probably enough) then remove this line :
AFAIK we don't need this property, maybe I'm missing something...
I've detected that uid is not added when creating a new file.
Image attached(file_managed table).
The second one with uid was added from UI.
Comment #99
marthinal commentedReroll again. I'm commenting the access check at EntityResource::post() and at FileEntityNormalizer::normalize() this line
in this case to avoid the data property on get requests.
With the current permissions it is not possible to avoid the access method without commenting and not possible to continue working-testing the file creation.
Anyways I'm adding the patch and then you can try to upload files and verify that it works. As commented in #97 missing uid when creating a new file entity.
Comment #102
marthinal commentedI had some problems with the version working on another patch and maybe this is the problem...
Comment #105
marthinal commentedThat was the problem. Cannot apply the patch for 8.1.x
Comment #106
marthinal commentedFound the problem. Interdiff attached.
Does it make sense to have this static method only for Node?
If I need the same method for File... not sure about the best way to obtain the uid then. So, maybe a getter for the uid from the Drupal class? Maybe this method is useful in other cases too...
So at this point we can create files and get the file with the uri where the file is located.
Comment #108
marthinal commentedAs suggested by @berdir (IRC), we can simply allow to upload files. So, if you have access to the file entity resource then you can upload a file.
Code
But, what about validators? Actually we can upload files without controlling the extension or size for example...
From the UI when creating a node, the first step is to upload the image(in the current context :) ). From rest you need to upload the image before too. But we don't know the content type for example or you don't need this to create an image. So you can create an image and attach it to the content type you want. There's no control.
I think maybe we can do something like this:
1) Create the file from rest. When creating the file, by default it will be temporary. So we need to attach it to a node for example to mark as permanent. If you want to validate as the UI does, then add validators to the json. See FileEntityNormalizer::denormalize().
2) You can create a file for a content type and attach it to another one (if you know the uuid), with a extension not allowed for example. So let's add validators, detecting if the field type == 'file' . I was testing manually and if you upload a file from rest, then attach this uuid to the node and try to create the node, the validation works as expected. To be honest for the moment only tested for the extension validator.
We are validating 2 times. The first one is optional and the second when creating a node.
I think we need flood control here too... otherwise you can upload unlimited number of files. Temporary files are deleted by a cron task.
Not sure if I'm missing something but maybe this is a good first step.
Comment #109
marthinal commentedIf you want to test validators when creating the file then use something like this:
Comment #111
marthinal commentedProbably the first validation could be done from your app...
Comment #112
marthinal commentedAbout PATCH. You can add an empty entity reference, the file will be removed from the node and then the status will be set to 0. The file will be removed from cron.
Cannot DELETE the file from rest and not sure why.
Comment #113
marthinal commentedVerify that File module is installed and check if the current entity has files/images, then validate the file.
Comment #115
marthinal commentedComment #116
andypostcommented out?
please remove, no reason anymore
just add new method like Node does
Comment #117
dave reidComment #122
gaurav.goyal commentedAttached is the rerolled patch, Need to fix #116
Comment #127
marthinal commentedComment #128
marthinal commentedFixed Error 500. Verify if canonical path exists.
Comment #130
marthinal commentedAs discussed with @klausi we avoid to add the data when normalizing because we can access to the file using the uri.
Comment #131
vivekvpandya commentedThanks Jose,
The latest patch worked on 8.0.x branch. I have uploaded image and text file, it works fine for both
But here is a small problem file is uploaded but when deserilasing it , file is not getting a proper name which is passed along the request.
for following JSON :
The uploaded file is not getting proper name i.e in this case it should be Batman.jpg but it gets file4Inkr8 . It seems like file + 6 random chars .
I think that patch will also work for other types of files.
One more point I would like to make here is this method is fine but base64 encoding for file is 33% larger than original file so for mobile apps this matters and also for videos and audios. So Is the Drupal community planning some other option to upload file via REST without base64 encoding ?
Comment #132
vivekvpandya commented@marthinal , one more question here , How can we specify particular directory name into which particular file should go ?
Comment #133
marthinal commentedAs discussed with @berdir, the user can create a file and set it as persistent(status=1). This patch fix this problem. By default the user can upload a file and if this file is not referenced by an entity(node,user...) then should be removed by a cron task.
Now we are validating using a constraint. So, we only need use this constraint to validate the field(image,file,video...).
@vivekvpandya
1) Let me check why we are adding a random name.
2) Not sure about large files at this moment. I need to comment with @klausi or @berdir.
3) You need to add "uri" to your request
"uri":[{"value": "public://testfolder"}],Comment #134
vivekvpandya commented@marthinal,
I tried to upload a image file without mime type info and it worked successfully, So what is the use of specifying mime-type ? Is it there for Drupal's file-type restriction ?
Please add all available fields that can be sent via request JSON in documentation. Also let me know if you require any help for that.
Comment #137
marthinal commentedDeny only edit operations for status field.
Comment #138
vivekvpandya commentedAfter successful creation of file resource on site, with 201 it should also report URI for the file so that it can be used in further requests. I hope https://www.drupal.org/node/2546216 will cover this.
Comment #139
catchComment #140
vivekvpandya commentedRe-rolling the patch
Comment #143
marthinal commented@vivekvpandya #137 applies and works as expected.
Comment #145
vivekvpandya commented@marthinal it works on RC1 for me but not on 8.0.x branch , and on RC1 it returns 200 instead of 201.
Comment #146
marthinal commentedRelated issue.
@vivekvpandya This is weird... Attached image using #137 patch on 8.0.x branch.
Comment #147
kylebrowning commentedRe-roll
Comment #148
yesct commentedit's been a while, so unassigning.
Comment #149
drnikki commentedThis works for me on RC2.
Comment #150
kylebrowning commentedSO RTBC then?
Comment #153
drnikki commentedIt applied cleanly to rc2 but not to 8.0.x. Here's a reroll of kyle's reroll.
Comment #157
neclimdulunselecting all the old patches because RTBC acts weird...
We're using this and it seems to work but I don't understand how these changes are making it work so I'm not personally comfortable re-RTBCing this.
Comment #158
dawehnerHere are a couple of nitpicks.
PS: I cannot see any tests for the FileValidation constraint
Seems kinda out of scope of this issue to be honest. For me I could totally see value in exposing the target_id as well. Don't see any particular problem with that. In case there are some, maybe we should document this here.
Maybe the unsetting is related with that piece of code? Can we document why we need this here, why can't we just copy all of the properties?
Nitpick: Needs FQCN
Nitpick: Isn't entity_create() kinda deprecated already?
Comment #159
marthinal commentedWe are using the Constraint on #2594565: File extension + max_filesize should be validated using a Constraint on file fields
Comment #160
neclimdulwhich was committed. reroll without the old constraint patch.
Comment #161
klausiyou mean "edit" instead of "add"? Did you mean "No user can edit the status of a file. Prevents saving a new file as persistent before even validating it."
unrelated change not needed for this patch.
why do we avoid treating data as a field? Suggested comment: "File content can be passed base64 encoded in a special "data" property. That property is not a field, so we remove it before denormalizing the rest of the file entity."
the file_system service should be injected instead of using \Drupal.
We save the file contents to the file system, but we don't keep track of it. Ideally we would later delete unused files after 6 hours, same as we do with managed files. I don't see how we could track that except for saving a managed file, which a denormalizer should not do. So I don't see a solution, just worrying a bit.
never use random data in tests, see also #2571183: Deprecate random() usage in tests to avoid random test failures.
same here, please no random test data.
two times file_put_contents()? Why? Why do we need file_get_contents(), the data is just "hello world"?
I don't understand that comment. The serializer is supposed to create a new file object? why do we need "patch" as context here?
let's use File::class here
please, no random data in tests.
Comment #162
eiriksmFixed all points except 9. Not sure what the logic is here, so I am probably not the right person to answer the question or change the patch.
Comment #164
eiriksmOops, sorry. Undefined index. A bit quick there.
Also updated a test class comment that was not accurate anymore.
Comment #165
lightguardjp commentedDoes anyone have the hal format or required fields for creating a file resource via REST? I've found this part to be severely lacking in the docs department. Maybe it's because I'm new to drupal, but it's been very frustrating try to guess the magic incantation to make things work via REST calls.
Comment #166
vivekvpandya commented@lightguardjp
Here is an example hal+json
Here you can skip
urifiled to put the file in public directory.Here one issue know is that file gets uploaded with some random name. But perhaps @eiriksm 's patch may have solved it.
Comment #167
lightguardjp commented@vivekvpandya Still having issues, maybe we can talk via email (I sent one earlier). During deserialization it blows up as curl doesn't have the private, public, tempory, etc schemes enabled. If you leave out uri then it blows because it uses it for deserializing.
Comment #168
gcardinal commentedTested serialize_file_content-1927648-164.patch on 8.0.0 with following request:
Image was converted using base64 command line line tool
Using basic authorization
Content-Type: application/hal+json
POST send to http://drupal.url/entity/file/
File has been successfully uploaded to public folder.
Comment #169
fjen commentedI'm getting:
Recoverable fatal error: Argument 1 passed to Drupal\hal\Normalizer\FileEntityNormalizer::__construct() must implement interface Drupal\rest\LinkManager\LinkManagerInterface, instance of Drupal\Core\Entity\EntityManager given, called in /var/www/html/core/lib/Drupal/Component/DependencyInjection/Container.php on line 277 and defined in Drupal\hal\Normalizer\FileEntityNormalizer->__construct() (line 39 of /var/www/html/core/modules/hal/src/Normalizer/FileEntityNormalizer.php).for testing against patched D 8.0.0 with serialize_file_content-1927648-164.patch. Here is my request:
Comment #170
gcardinal commentedFor this to work, you will also need to activate / install "Responsive Image" module under Core
Comment #171
gcardinal commentedPatch #164 used only temp filenames, changed denormalize() function to pass real filename to file_unmanaged_save_data() function, on line #280
Now files are created with provided filename in request.
Comment #173
wim leersI'm missing integration tests that effectively prove that GETting/POSTing/PATCHing a base64-encoded file works, like the title indicates.
Comment #174
berdirIt doesn't work because that doesn't just depend on this issue. This is just the serialization part. There has been a lot of back and forth but I still think that we should split this up.
Actually using it for REST requests also requires working file permissions. See #2310307: File needs CRUD permissions to make REST work on entity/file/{id} . Note that file_entity adds permissions and it works with that (it also contains an override for this, so it works anyway with file_entity, also without this patch).
Comment #175
gcardinal commentedSetting this one to 'critical' - this one needs urgent attention. Working REST is critical part of a modern CMS and one can't go to 8.1.x and 8.2.x without giving this issue time it needs.
Comment #176
larowlanPlease read the priority values post (https://www.drupal.org/node/45111)
Comment #177
gcardinal commentedFrom priority values post (https://www.drupal.org/node/45111)
- Significant regressions in user experience, developer experience, or documentation (at the core maintainers' discretion)
Not being able to get, post or edit image using REST is "Significant regressions in developer experience".
Without this basic functionality it is impossible to integrate Drupal 8 or replace existing Drupal 7 installations.
This should be either adresed or support for REST removed from Drupal 8 - this way so RESTful can be turned back to what it was for Drupal 7. Ignoring this issue for months is not a solution.
Comment #178
klausiSince the REST module is new in Drupal 8 core this cannot be a regression, because it did not exist in Drupal 7 core.
Comment #179
gcardinal commentedIn practical sense this is a regression. Because REST is in core now, work on contribution module makes no sense. Based on how popular restful for D7 was, it is not unlikely that many D8 installation also depend on REST for integration.
And as long as this in Major it can stay ignored for month. This is just part of unfinished legacy of Drupal 8 release. Work was simply halted and never completed for Drupal 8 release. Then it got this major priority and unfinished bugged code gets dragget from release to release without people starting on Drupal 8 site even being aware of that basic facts that REST in Drupal 8 is not working for anything else then text.
Comment #180
tstoecklerRe @gcardinal: While I agree this is issue is important (not making any suggestion on the issue status, though) it's not correct that it's impossible to work with Rest and Files. You just need the contributed File Entity module at the moment.
Comment #181
gcardinal commented@tstoeckler This patch combined with project/file_entity which is in beta and nothing happened over there since January is not exactly a cocktail anyone would want to build any integration up on.
As for Drupal as a project this is just shameful "thing" that just gets swiped under the carpet. After all, this is not a bug - this is supposed to be part of Drupal 8 that never got finished. Its not like its there and dont work - its simply not there.
Comment #183
marthinal commentedI think it should be green now...
Comment #186
marthinal commentedOk. Should apply for 8.2.x
Comment #187
timmillwoodHere's how we do this in the Replication module http://cgit.drupalcode.org/replication/tree/src/Normalizer/FileItemNorma...
(used by RELAXed web services and Workspace)
Comment #189
marthinal commented@timmillwood Interesting, thanks for the info.
Comment #191
marthinal commented1.
EntityNormalizeTestwhen denormalizing $data, it contains the fid. So removing this assertion.
2. Moving
FileFieldNormalizeTestto the Kernel tests.Comment #192
marthinal commentedAdding integration test for POST method
Comment #193
marthinal commentedIntegration test for GET method.
Comment #195
marthinal commentedThis change makes possible enable multiple services per test.
Comment #197
marthinal commentedOk. The next time I'll try to run only a couple of times the testbot :)
Adding PATCH + DELETE.
I think we can improve the tests. For example we should verify that only the owner can remove a file.
Comment #198
marthinal commented1. Adding integration test to verify that only the owner/admin can remove a file. I'm using the patch #2310307: File needs CRUD permissions to make REST work on entity/file/{id}
2. DELETE: I've detected that the file entity is deleted from DB but seems like the file is renamed. So for example foo.txt will be removed from DB but a new foo_0.txt file appears in sites/default/files/... To be honest I have no idea for the moment about how to fix it. Running cron the file still appears.
3. PATCH: You can change the uri and the filename will be the same. So... if I change the uri and then remove the file entity, the file still appears in sites/default/files and ... AFAIK we need to know the file status(from DB) to remove the file using cron. Probably we cannot remove this file. But I'm not sure if I'm missing...
Comment #199
neclimdulwhat about data in the form of data:image/jpeg;base64,? I'm not sure if that's a consistent browser thing or a quirk of the application I'm interacting with but the encoded data is prefixed with that metadata which with the current path is encoded into the contents corrupting the file.
Comment #200
tedbowBecause of the weird behavior @marthinal described in #198 I thought it would be good to add a check to make sure the file is actually deleted from the database and file system.
This patches adds those checks. It also checks file is actually in the file system before delete.
Comment #201
anthony thomasI'm using JSON format, but when I post file this error is return :
My JSON :
Comment #202
wim leersThis is clearly getting there, but we need some polish/clean-up to have this be in a maintainable and understandable state. And we definitely need extra test coverage: it's not clear to me at the moment whether you can POST/PATCH multiple files at once, for example (for entities that have multiple file fields).
Ughhhhh this is awful. But there is a precedent:
\Drupal\node\Entity\Node::getCurrentUserId().So, fine for now.
This is talking about deleting, but this block of code is executed for both updating and deleting…
$file_uid->getTargetIdentifier()Missing cacheability metadata Per that if-test, these both vary by:
permissionscache contextaddCacheableDependency($entity)This means anybody can create file entities. Which is exactly what
\Drupal\Core\Entity\EntityAccessControlHandler::checkCreateAccess()does for us.So you can remove this.
Let's use strict equality.
Let's instead call the parent method. Which means this then is a pure decorator of the base class for this method.
Why change the position of every injected service that we keep? We remove one service, and add another, but every single one is in a different position. That's not necessary.
Why this change?
This looks… very weird. Could use a comment.
Needs FQCN.
Nit: Strict equality.
Why not
isset()? Can the value of this key be NULL?Wrong change.
This makes no sense at all.
Looks like we can remove this altogether now, since this merely calls the parent and returns the result?
So this is a property *under* the file reference field, right? Otherwise, you couldn't send multiple files at once.
Let's put this 'data' key in a class constant.
Why do we not need to decode and save when updating?
This is using lots of deprecated functions. Look at the
FileSystemservice.Nit: Let's make this a single line.
This reads far too generically. But the constant I asked for above would already make this much, much clearer.
That shouldn't be the reason we do this; the reason should be that we test both POST and PATCH.
Why did we remove this?
"de/serialization" -> "(de)serialization"
Pointless comments.
Nit: too many spaces.
Good call! :)
This is testing using HAL+JSON. But this is the REST module, not the HAL module.
This test should be moved to the HAL module, and a similar one should be created for the REST module. In this new test, we should test both JSON and XML.
Then the test moved to HAL can hopefully (but not certainly) reuse parts of this new test.
Why don't we need
hal_jsonfor PATCH?(For DELETE, it makes sense, for PATCH, not really.)
This feels like we have either a bug or missing test coverage.
They're accessible, they're just not modifiable?
Is this change here to allow appending?
Unnecessary change.
Comment #203
wim leersAlso, per #174, should we first do #2310307: File needs CRUD permissions to make REST work on entity/file/{id} ?
Comment #204
tedbowOk here is patch that address some of @Wim Leers recommendations in #202
Tried to fix the quick stuff and will investigate the other issues next. I will need to catch up on the issue better
1. just a note, no change
2. fixed comment
3,4 skipping for now
5. removed
6. fixed
7. fixed
8. reset position of service arguments
9. skipping
10.
$field_uri => array($embedded + $properties[$field_name][0]),Split the array merging into a line above with comment.
11. add FQCN
12. added Strict equality.
13. fixed wording
14. Fix constructor to match services arguments changed back in 8
15. remove normalize function
16. updated comment
17. added constant
18. skipping, having looked into it yet
19. only saw 1 function that was deprecated. fixed
20. changed to single line
21. used new constant from 17
22, 23. skipping, having looked into it yet
24. fixed wording
25. removed comments
26. fixed spacing
27. just a note - no fix needed
28, 29. skipping, having looked into it yet
30. fixed comment
31. skipping, having looked into it yet
32. It look like this import is need to me. left it
Comment #205
berdirRe #203: I've been trying to argue for a while that this issue is only about correctly *serializing and deserializing* a file entity. And not about actually making it work for rest.module. Then we could do this issue and the other one separately.
Question is which issue is going to add actual rest.module integration tests. Could be the one that happens to land second or a new issue for which this and the other one are child issues. Or we make permission depend on this one first and then add tests. I *think* this is closer as the permission issue still has some rather big unanswered questions (from me) AFAIK.
Comment #207
tedbowOk, in #204 I didn't see that FileEntityNormalizer was instantiated in the tests. I fixed the arguments to the constructor.
Comment #208
tedbowI added this back because the parent::checkCreateAccess depends on getAdminPermission which I don't will work until #2310307: File needs CRUD permissions to make REST work on entity/file/{id} is finished.
Using the new class constant
Comment #209
wim leers#205/@Berdir: I think it doesn't make sense for this issue to land without integration tests, so IMO it'd be more sensible for #2310307: File needs CRUD permissions to make REST work on entity/file/{id} to land first then. Because it sounds like the
FileAccessControlHandlerchanges here are only being done to make this issue actually work, and cannot actually be committed: those changes must happen in #2310307: File needs CRUD permissions to make REST work on entity/file/{id} ?Comment #210
berdirWell, integration for what exactly? We have integration test for serialization. This does nothing rest specific, so why do we need tests for that, here?
This issue alone already enables use cases, like default_content. rest.module isn't the only thing that uses serialization.
Comment #211
dawehnerYeah this issue is only about serialization
It actually turns out that
@see ::is not supported by API providers, nor IDE vendors, see https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Render%21...Let's not introduce one even yes, we have used it in quite some places now.
Don't we have to encode the owner somehow in the access result object cache contexts?
I should not have looked up
file_unmanaged_save_data()Comment #212
amateescu commentedI could only find a few small coding standards issues:
Comment needs to be wrapped to 80 cols.
Capital F :)
Missing empty line.
We usually put these in a single line.
Not needed. Note that this is in the
serializationmodule, to not be confused with the class that has the same name from thehalmodule.Comment #213
wim leersWoot, great to have @amateescu reviewing this patch!
Comment #214
tedbowAddress review in #212 and #211 except item 3.
@dawehner wasn't sure what you meant
file_unmanaged_save_data is not deprecated.
Comment #215
dawehnerIMHO we need to add the permission as cache context here, so use
cachePerPermissionsThis was just a humorist comment ...
Comment #216
tedbow@dawehner I didn't know aobut cachePerPermissions.
Updated to use cachePerPermissions
Comment #217
wim leersI still have nits, but I have only 3 critical, commit-blocking questions:
Is it okay for these changes to happen here, or do we want #2310307: File needs CRUD permissions to make REST work on entity/file/{id} to happen first? This does the same, but not exactly the same.
Let's document very clearly in the IS what this issue does and why, and what #2310307: File needs CRUD permissions to make REST work on entity/file/{id} then does, and why it's okay for this to go in first. Especially considering this does not add test coverage.
This sounds like it's impossible to POST an entity that has multiple files attached.
Let's add a test that proves me wrong.
Comment #218
berdir1. No. This obviously has limitations, base64 also needs 30% or so more memory than the binary data.
2. I still believe that permissions do *not* belong in here. Who knows what kind of side effects this has, there could be code there that's relying on create permissions and might expose things to anyone that weren't exposed before (rest still needs a separate permission, but there could be other services or so).
3. This is the file entity. You can only post one file, just like you can only post one node at a time. You can however post 3 files with 3 separate requests and then a node that references all of them with the 4th.
Comment #219
damiankloip commentedI agree with berdir here, I do not understand why we're bringing REST module into this. REST != serialization component necessarily. E.g. I maintain serialization, NOT REST :)
Comment #220
damiankloip commentedI think the current/old method is kind of valid too... would be nice if there was a way to incorporate both.
Why should denormalization care about the request method like this? Seems rather hackish. Should this not work based on whether there is encoded binary data present or not?
I am also not clear on why this doesn't just move to serialization module itself? This bakes everything into hal module, so it's not usable with other regular type of decoding, like JSON... If it was the other way, hal could easily use this normalizer anyway.
Comment #221
wim leersMoving to NW for #220, especially:
+100
Also, #2310307: File needs CRUD permissions to make REST work on entity/file/{id} landed, this needs to be rerolled.
Comment #222
tedbowWorking on re-roll
Comment #223
tedbowJust a re-roll
Comment #224
berdirRelated: #1979260: Automatically populate the author default value with the current user
The question is what we're going to do with this now. Just removing the @todo won't be enough :)
Per discussion in related issues, we're apparently considering again to upload files as part of field values of e.g. the node, which would change 90% of this patch. It would be simpler, but it would also be more limited.
I guess we could say that if you need more. you can use file_entity. Or some other module, there's at least another one started to contain various serializers.
Comment #225
wim leersComment #227
tedbowSo what are options here? It seems like we could make file entity type core permissions
But these would be only be used by REST module. So I think this would be confusing for site administrators.
While #2 is true for other entity types with other entity types at least the entity type permission have some meaning with just Drupal Core outside of REST.
return AccessResult::neutral();I did confirm locally with this returning allowed() all the tests pass so I don't think the re-roll introduced any new/unknown problems.
Comment #228
tedbowComment #229
wim leersBerdir has the most experience and insight on this, so I'd say: let's let Berdir decide.
Comment #230
dawehnerI'm just trying to be curious here.
Would that cause issues with something like a maximum payload you could manage?
Comment #231
wim leers#230:
Comment #232
berdirYes, but the point is that uploading as part of the node then means that max applies to the sum of all files. Imagine creating a gallery node with 20 images for example. That's just not going to work. While creating them separately will take a while but there's a chance that it will work ;)
I don't feel like I can decide here. All I can offer are some suggestions/ideas.
#227 has the same questions as I asked in #2310307: File needs CRUD permissions to make REST work on entity/file/{id} and there we basically avoided that problem, resulting in not actually solving it for this issue, which was my understanding of the split between the two.
As mentioned before, this is all working quite fine with file_entity, because it has those permissions and also a version of this serializer.
I know that it would derail this even more, but one idea would be to actually bring some of the features of file_entity into core, which is something that @slashrsm and I talked about. Like being able to upload files "into the media library" as a standalone thing. Then we'd need a permission for that and it wouldn't be weird anymore if rest would allow the same. It would also force us to think once more about the whole file usage and auto-delete situation, which is still not really resolved (upload a file, then use it (file usage is now 1), then stop using (file usage is now 0 and it is removed).
Another idea is the the same permission concept that we have for private files. By default, you can see an private file if you can see at least one of the entities that are using it. So we could say you can upload a file if you have access to at least one entity with a file/image field. obviously, that could be a pretty slow access check, if we'd need to do create access checks for 10 different node types until we find one that you're allowed to use. But it would kind of reflect the situtation in the UI. And we already enforce that files are temporary until you actually use them.
But even then, one of the problems that we haven't solved so far is file validation. In core, that's all tied to file/image fields. They control what file types you may upload, how big they can be and so on..
Comment #233
tedbowre #224"
If anybody has links to these issues can you post them here so we can see what discussion is going on? Thanks
Would this be an either or situation? Could we support both? I could see clients wanting to do both in different situations.
As @Berdir says if you are uploading a gallery node you are likely to run into upload limit if you have serializing all files into 1 node POST. But then again if you are just posting an article with an image having to post the image first and then post the article which includes a reference to the just posted image seems is 2 posts which could just be 1.
re @Berdir #232
It seems like this will probably happen eventually, especially since @dries mentioned Media as an initiative in Drupalcon NOLA.
One option would be to postpone this issue until core had the ability to upload files separate from file reference fields. Presumably at that point there would be permission for uploading files themselves and then rest could just check that permission.
In the meantime we could open a separate issue for letting file be serialized as part of file reference field.
Then after both issues were finished there would 2 ways to upload files and clients could use which was easier for a situation.
I think the problem with doing this 1 now and adding new CRUD permissions for file entities is that we run into a situation where we will want to change the way permissions work for file entities and then have to introduce a backward compatibility layer. For instance once something like File Entity is in core then we would probably not want file entity level CRUD permissions but rather file bundle level permissions.
If this issue did get postponed but the serializing on file fields got committed would it still be possible to attach 20 images to a single node(or other entity) via PATCH requests? Obviously it wouldn't be ideal versus individual file entity posts and 1 post to the referencing entity.
Comment #234
skyredwangI am an Android developer. I agree with @Berdir issue. Not being able to manage many files doesn't sound right. Also, I'd like to point out that for mobile dev, design for offline is a priority, in other words, uploading files based on connectivity conditions is must. Therefore, I would never want to combine 1 article and 1 image into 1 HTTP post to Drupal. It's always ideal to separate them into two requests.
Comment #235
bc commentedre-rolled patch for 8.2.x c9e8e141435fc916c6ead204dc48df132e7c5df5
Comment #236
tedbow@bc thanks for re-roll
Setting to "needs review" to trigger tests
Comment #238
bc commenteddarn it all, i uploaded a patch made against drupal-core; here's a more testbot-friendly one:
Comment #241
tedbow@bc was #238 just a re-roll of #223 or where there other changes. I am wondering about the extra failing tests.
Thanks
Comment #242
bc commentedJust a re-roll -- it's pretty strange. I'm double checking my work now.
Comment #243
bc commentedMissed a line in my reroll. Ugh. Let's see how the tests go this time ;)
Comment #245
tedbow@skyredwang re:
and
I don't disagree with you here but if there is a way to manage files separate from any entity they are attached to then we would need some sort of file permissions.
So then we would have to pick a way to implement those permissions.
Option 1: Create file CRUD permissions and have REST respect those permissions
This has several problems
Option 2: Create REST specific verb permissions for file methods
The also introduces problems
Option 3: Treat similar to private allow uploading temporary files based on entity access
From @Berdir comment in #232 again
Again this option has problems
One option, that doesn't seem very clean, to get around the slow access checks would be to have the file upload specify the entity or entity type that will be created that user will be attaching the file to and base the access check on that entity or create operation. But I don't know how you would cleanly specify that in REST post(a custom header?)
I don't think it is ideal to only be able to upload files as part of a field on an entity but we do have to solve the permissions issue we are going to manage them directly.
Once something like file/media library is then this would much easier because it would add file permissions.
But I don't think it is good idea delay file uploads via REST
Thoughts on those options or others?
Of course the other option is upload file as part of file field on another entity. While that doesn't introduce permissions problems it does introduce other problems mention in this issue.
Comment #246
skyredwanghttps://github.com/fago/entity/pull/9 will land soon, I am wondering if it will help?
Comment #247
tedbow@skyredwang no that is the Entity API contrib module and won't affect Drupal core
Also building the permissions is not the hard part. The hard part is choosing one of the options I described in #245 or another that we haven't thought of. And then dealing with the downsides of whatever choice we make(they all have downsides)
Comment #248
garphyConcerns about the maximum size of the actual "upload" were raised multiple times in this issue but were never actually really addressed.
Maybe the vast majority of current contributors to this issue are not used to manage big files but on the majority of projects I work on, we regularly upload files which size vary from several 100s MB to several GBs.
It's obvious that the approach of trying to base64_decode the payload in RAM then write it to disk won't work, not only because of PHP memory limitation but also because of underlying system itself memory limitations.
And it's worth mentioning that REST client will also have the same issue when trying to build the request. It's probably doable (but tedious) to "stream" the base64 encoded payload from the client to the server, but I can't actually think of a "streamed" approach on the server-side. We need to have the complete JSON (or whatever) request payload before trying to find the base64 payload.
The natural alternative approach would be to address this concern using "plain old" multi-part POST request in which we could have the file payload (obviously) and the required additional metadata needed to create a file_entity. This would (maybe) "break" REST semantics but I would be usable, even for very large files.
I would be more than happy to read followups on that :)
Comment #249
tedbow@garphy
Thanks for the input. I agree the serialization method would not work well for files of this size.
I would think though that serialization method would work for the majority of sites. Also just because we add this method does not preclude us adding another method later. Of course work could be done in contrib to prove a method of using REST resources to handle files of the the size you are talking about.
My opinion is that we shouldn't change this issue just because it doesn't work for every use case. Especially if as you say "vast majority of current contributors to this issue" haven't run into this use case. If true that probably points to it being a not very common use case.
Comment #250
garphyOk, I'll then go to implement a custom solution to accept multipart/form-data request that would upload file data and create the corresponding file entity.
What would be great is to be able to "hook" this solution with core REST module by making it accept multipart/form-data requests. Any thought on this ?
Comment #251
garphyRe-rolled the latest patch
Comment #252
bc commentedHere's how I added a POST resource for multipart/form-data uploads: https://gist.github.com/bnchdrff/a388b763bc97f7a1c6107698652cc58d
Comment #253
berdir@bc: your approach still loads the file contents into memory s o it actually has pretty much the same limitations, minus the base64 overhead.
And sure, something like that is possible. But doing that through the REST/serialization framework is harder.
Comment #254
bc commentedI'm curious how many people are uploading files as serialized data at this point vs how many people are either giving up on d8 or writing hacky custom endpoints like me :)
In the case of a mobile client uploading an image to a d8 server, it's really klutzy and inefficient to serialize the photo on the client -- many phones will lock up / OOM.
Comment #256
garphyHere's my approach so far to upload & create a file entity using multipart/form-data and avoid loading the file in memory :
https://www.drupal.org/sandbox/garphy/2770603
It tries to reuse existing pieces like file_save_upload(), so the key for the file in POST data has to be "files[file]".
Comment #257
bc commentedGarphy the link doesn't work! I'm curious how you've solved it -- also if anyone else on this issue has stopgap solutions maybe they can be used to inform how the d8 core api could function.
Comment #258
garphySorry for the wrong link...
Sandbox project page : https://www.drupal.org/sandbox/garphy/2770603
Repo : http://cgit.drupalcode.org/sandbox-garphy-2770603/tree/
The only important piece of code is the controller which basically does
Comment #260
Pedro J. Fernandez commentedHi everyone!
I got this working but the uploaded file is always uploaded in the /sites/default/files folder. I tryed to specify the destination folder adding the "uri" attribute to the hal request, or writting the uri in the filename attribute instead only the name. i.e. public://some_existing_path/filename. For any reason, the file is always created in the /sites/default/files and the destination folder is ignored. Please, I don't know where is the problem. Please, help.
This is the POST body:
The file is created and this is the response: 201 Created
And the file is created in /sites/default/files instead of /sites/default/files/photos.
Comment #261
eugene.ilyin commented@Pedro
I've specified uri:
"uri" : [{"value":"public://project/sad_photo.jpg"}],and my file has beed saved according to path.
P.S. As I see according to code in patch, now I cannot upload multiple files in one request? Only one file per request. Am I correct?
I need to save bunch of files into the Node and would be nice to avoid 20 requests.
Comment #262
Pedro J. Fernandez commented@eugene.ilyin
Thanks for your fast answer! But I have tryed this and I get the same result.
And I get this as result:
The patch I have applied is the 1927648_216.patch, not the last one, over a drupal 8.1.8. May be this is the problem.
To mitigate any kind of doubt, I have applied the last patch available (serialize_file_content-1927648-251.patch) in a brand new drupal 8.1.8. I have updated my drupal solution with it and the I tried again.
I have to say that every time I made a change in code, I creared the cache before testing again.
First of all, 403 Forbidden message appeared. I remaind that I have to change something to get it work last time, concretely this in the file core/modules/file/src/FileAccessControlHandler.php:
Now, 201 Created received, but same behaviour. Files are created in the public:// root folder but destination folder is ignored. Nothing has changed compared with the previous patch.
There are more patches I have to apply apart from this?
I'm a bit frustrated with this issue :-(
Comment #263
eugene.ilyin commented@Pedro, I've used patch from #251
Comment #264
benjy commentedI re-rolled this against 8.1.x as that's what i'm currently developing against. I got this working but note that since #2310307: File needs CRUD permissions to make REST work on entity/file/{id} you need to handle enabling the "create" permission for file entities. I presume installing the File Entity module will solve that, for my use case I created a simple module that allowed access to create file entities to anyone with the
restful post entity:filepermission. Module is simple for anyone that is interested:Might also be worth pointing out that you have to use application/hal+json as the Content-Type, I was previously using JSON and that doesn't work because the FileNormalizer is in the hal module and hal_json is the only supported format for the normalizer. Finally an example JS payload might help others:
Comment #265
benjy commentedHere's an 8.2.x version
Comment #267
tinom commentedI have applied patch 1927648-264.patch to 8.1.x successfully, added File Entity module but now the error is:
rest/type/file/file does not correspond to an entity on this site.Any idea what I am missing here?
Also, I want to upload the image not as a standalone entity but to a specific object. How can that be done?
Comment #268
larowlanComment #269
tedbow@Pedro J. Fernandez, @eugene.ilyin, @benjy, @tinom please don't use this issue for support requests for a patch in progress, or post backported patches to previous core versions that this issue does not relate to.
This issue is over 260 comments and it still needs work. It makes it very difficult for people who want to come work on the current patch to understand the issue because they have to read through so many comments. This is even worse if they have to weed through comments that are not about developing the current patch.
Thanks
Comment #270
benjy commentedWell, this is the canonical place for the problem of uploading files via REST and 8.1 is still the stable version of Drupal, so if we don't post patches here I'm not sure where else people would find such patches.
Comment #271
kylebrowning commentedIm actually going to make a case for switching this to use multipart form encode or maybe BSON, https://en.wikipedia.org/wiki/BSON
- base64 encoding makes file sizes roughly 33% larger than their original binary representations
- base64 encoded data may possibly take longer to process than binary data
This results in loss of data/compression in 2 areas, and strings will be huge. For Example a 1.6kb image is a string 1700 characters long, making a payload to upload a 5 mg image just insane.
What this means is that you will need more memory (in your browser or mobile device) than before just to upload an image/video.
With Mobile devices getting bigger and bigger cameras, this will cause OOM errors in many places. And its not just with mobile, browsers will have this issue too.
With multipart, we can send a fully represented binary object, and save the heavy memory footprint because its not a string. Of course the dev will still resize cause they can't upload a 200 mg video, or 20 mg picture.
#252 addresses this in a very nice way. Im going to provide a patch based on that.
Comment #272
garphy#271: +1
I would add :
- the memory consumption is also a server-side issue, you cannot require the server to allocate the file size of each upload in memory, it would obviously not be scalable ;
- there's no way (of I'm aware of) to provide a file upload progress status to the user when submitting a JSON blob.
Comment #273
Evgeny_Yudkin commentedI tried attached patches:
1) There are issues with jsonapi (type errors into the serializer constructor);
2) file_entity module worked for me (no need any patches);
By the way, all patches affects "hal" module, but what is somebody wants to create file via json/xml/any other format?
Comment #276
kim.pepperRe-roll of #265 for 8.2.x
*
core/modules/hal/tests/src/Kernel/EntityNormalizeTest.phpwas removed in #2824576: Delete old REST test coverage: (Read|Create|Update|Delete)Test, deprecate RESTTestBaseComment #277
wim leersThanks! But #271 still needs to be addressed, so back to NW…
Comment #278
wim leersThis is not a bug. It's something very important capability that is missing. Technically, this is a , but because it's such a glaring omission, I think is more appropriate.
Comment #279
kylebrowning commentedShould we update the title too Wim?
"Allow REST File uploads via binary."
Comment #280
bradjones1The re-roll in #276 no longer applies, I tried some manual re-working but the array syntax patch for core paired with some changes in the test classes in the interim made it a bit unwieldy.
I would agree with assessing the issues re: this only applying to HAL at the moment, along with the concerns over Base64 vis-a-vis upload size. In the interim, rather than waiting on core support for this I believe the FileEntityNormalizer that appeared in these patches could live in custom/contrib land for the time being, albeit without the tight integration with core.
Comment #281
damiankloip commentedOK, here is a new approach. I think it was discussed previously in part. However, it's a new approach from the standpoint that there is new code here. For now, I am disregarding the previous patches. We can revisit/re-integrate stuff needed to this. #280 is correct too, there are a lot of changes to reconcile anyways...
I have spoken to a few people about this. The only way I see to solve this properly is to expose a custom route that only deals with POSTing binary file data. We can then stream the data properly (I think) without always loading the whole file contents into memory. Using this approach locally, I was easily able to save files much much larger than my PHP memory limit. To do this, we obviously need a stand-alone endpoint only for binary data. So creation of the file entity would need to be done in a separate request, so you could obtain an ID. I think that is acceptable though
This patch is still very rough, but for now it just provides a new route and controller to handle POSTing binary data only. So E.g. if you were using curl to do this:
curl -v --header "Content-Type:application/octet-stream" --data-binary @/path/to/your/file http://d8-dev/file/upload/{FID}Comment #282
wim leersYes.
Especially if this can be combined with https://www.drupal.org/project/subrequests.
Comment #283
damiankloip commentedHere is a bit more work on this. Thinking about it, I think we need to do this (which this patch starts to do):
Problems we need to work out here still:
This obviously all still needs some additional test coverage too!
Comment #287
damiankloip commentedCouple of other improvements to the upload controller.
Comment #288
dawehnerIs there no file.create entity access?
Personally I prefer to have the constructor first, given that its more important and the create() method is just an implementation detail.
Isn't that a classical 415 error?
Should we use
FileInterfacehere?Nitpick: Missing space after the while
I'm wondering whether its the right thing to replace existing files.
Why do some of those exceptions cause a logging, and some don't?
Comment #289
damiankloip commentedHey Daniel!
so...
1. This is only for updating file data, not the file entities, you would have to already have a file entity at this point. So it's only ever an update kind of?
2. fair
3. Yes! This is the kind of thing that needs to be tidied up. Unsupported type is correct.
4. Yep, meant to use that!
5. fair
6. If you don't replace them, you would get lots of orphaned files? and you would need to update the actual file entity fields too? the size, type, name, everything could change. So then you are back in to create territory?
7. file_unmanaged_move generates its own logging upon failure.
Comment #290
damiankloip commentedLet's just make most of those changes now to keep on top of things.
Comment #291
berdirFile upload in the UI in core always happens *in the context of something* that control upload validation. File validation is configured on the field. Allowed extensions, size, ...
Somehow, we need to be able to respect that also in REST, otherwise you can upload whatever you want. file_entity does have a global file upload, but it also has permissions for that, list of allowed file extensions and so on. All of that is completely non-existent in core.
So IMHO, we need to limit file create/upload to the context of something, we could hardcode a path that contains entity_type/bundle/field/.. or we could try something more generic, but not sure how to do that.
Another idea would be to be able to upload raw binary data into a very temporary state that is *not* in any way accessible, and then you can reference that from the serializer and we create and validate the file entity at that point.
Comment #292
damiankloip commentedGood points @berdir, this is certainly something we need to think about, not sure how best to solve that at this point. I guess the trouble is that we need the file content before we can do anything with it, and allowing changing the extension etc.. could pose some security weaknesses?
This is maybe slightly different to file_entity in that case... You could currently upload anything you want for the file data BUT you cannot modify the file name or anything else about the file (entity). So maybe if that was the case, we don't need to validate anything on the upload path? Maybe just if the file entity field values are modified via REST? We could for example, validate the content-length is not above the max upload size or something? As it currently stands, you could upload any binary data you want, but you could not change file path or extension.
Or do you mean we need to think about this regular REST operations on the file entity? E.g. just to be allowed to create a file entity with x extension type.
Comment #293
damiankloip commentedWIP on some test coverage for the file data upload portion.
Comment #294
damiankloip commentedTrying to change the test to use BrowserTestBase like most REST tests do. The actual controller code seems to work ok, I am not sure what's going wrong with authenticating a user though. For now the access it just 'TRUE'.
Comment #295
jibranShouldn't this be some random name?
Comment #296
damiankloip commentedtempnam()creating a file with a unique name isn't enough?Also, intentionally didn't mark it as needs review because we don't need the bot to run at this point...
Comment #297
jibranYeah, that's fine. I forgot about that.
Sorry about that.
Comment #298
damiankloip commentedok, and ok. I guess :)
Comment #299
damiankloip commentedIt seems like the best way forward (IMO)may be to not provide this in the context of a node or parent entity (we could do this, but seems like the wrong direction - to try and bring in field level validation amongst other things, seems strange from a REST perspective). File entity adding it’s own endpoint for creating files seems like a good plan for core too. It could then just rely on a new file permission like ‘create file entities’ or something? We could then have a way to create the actual file entity. Then use the endpoint in this patch to post larger amounts of file data if needed? Should we allow smaller amounts of file data to be POSTed inline still (I vote no)?
To achieve this, (I think) we would need:
- The new file permission
- Adjustments to the FileAccessControlHandler to take this into account
- Provide our own REST resource implementation for File entities?
- A way (as berdir mentioned above) to configure a list of allowed file extensions
This would make files kind of a special case I guess, but that seems like a necessary evil if we want some functionality.
Comment #304
dagmarJust to add some extra thoughs on this. The binary upload approach is something that is already implemented by other API services like contentful: https://www.contentful.com/developers/docs/references/content-management...
This is what contentful does too.
From contentful docs:
Comment #305
garphyTL;DR: My point is that we should have one single API endpoint which handle the file upload (using "real" PHP upload semantics) AND create a temp File entity in one single atomic request.
Good finding @dagmar ! The approach taken by Contentful is IMHO the right one and it's basically how Drupal is already handling file upload through HTML forms when using ajax uploads : the file is uploaded through ajax, stored temporarily and if the form itself is not successfully submitted within a certain time, theses temporary files are/can be deleted. But it's at a higher API level though : it's about creating a temporary File entity upon file upload (in one single request), then associate the File entity to node (or not).
As this issue is currently going, we end up with two options :
- create a temporary File entity which references no actual file, then upload a file by passing the fid in the request ;
- or, upload the file and get a "temporary upload id", then creating a File entity with this upload id.
But maybe these two requests approaches (one for creating a file entity and one for uploading) are equally flawed(ish) no matter the ordering.
Do we actually discussed the fact of having one single API endpoint which handle both the file upload (in a way we get streaming, progress, ...) and file entity creation (like the approach it was suggested in #252 and #258) ? It's not the route we're currently on, but I don't think we properly discussed and dismissed (if needed) this approach.
@Wim Leers, in #282, I don't get if you agree that the current approach needs two requests or if you consider it acceptable. IMHO, you couldn't combine those two requests, even using subrequests, in a way which allow for a streamed upload . I think that you would end up on the same issue again : base64 (or other) encoding, memory consumption, no upload progress, ...
I'd love everyone's feedback there :)
Comment #306
damiankloip commentedWell, the main thing the current approach is trying to implement is the binary upload of data part. The rest is up for discussion as far as I'm concerned. Once we have the binary upload working (it does now) the main concern is how we handle permissions around the file creation - this is the same problem to solve regardless of the direction we go in. We also still have the validation issue - but hopefully file validation can happen when the entity is validated and the file validators run at the field level for the file used. I.e. you could upload any file you like, then reference it on a node or something, but if it doesn't match the validation criteria for that field configuration the parent entity would fail validation.
If we go with the temporary file approach, how would you then switch the status of the file entity? You would still need another request I think? So then you could need 3 requests? Or would saving the entity after denormalization then update the file status as it has now been referenced? The other problem with that approach is how we deal with the file name? You would still need another request to rename it, even if we did give it a temporary name before the file is used? OR our endpoint could be /file/create/{filename} or something?
I don't think we should work with temporary file IDs, then create a file entity, I think we should always create the file entity, just with a status of FALSE if we're going down that route.
Comment #307
damiankloip commentedRestoring the hal file entity normalizer so we can have to full file URL back instead.
Comment #308
ibustosI can confirm #307 is working great. It would be neat if the response included the file being created/updated instead of just an "Ok" though. Submitting binary data is so much better than base 64. Quick question though, shouldn't this functionality be part of the Rest module instead of the File module?
Comment #309
ibustosI was thinking something along the lines of this.
Comment #310
ibustosForgot to include interdiff.
Comment #312
damiankloip commentedThanks for testing! Yes, returning the serialized file data makes sense too. I was thinking similar, just waiting to see if we want to create the file entity too, as per comment #306.
I was also going to move this to REST module I think, it started out in file module whilst I was scratching around. I think we need it in REST module, with the endpoint/route being added when file module is enabled (pretty much always).
Comment #313
damiankloip commentedInstead of this, we should just call
serialize()Comment #314
damiankloip commentedThe only thing that was bugging me a bit (and why I just returned the string before) is its kind of weird to send a binary request and receive a json response? I guess that's an ok default.
Oh and..
YES!
Comment #315
damiankloip commentedThinking about this some more today, I think this might be a good path to try and go down:
- /file/upload endpoint also creates files, but they are temporary only : To solve - how do we name the file (path parameter, query string, header)?
- Make sure FileItem validation can happen on save (of the parent entity, through constraints), this will allow us to arbitrarily use/reference our uploaded files, but then they could still be validated in the context of where they are being used as things like max file size, and extensions are field specific
- When the parent is saved (FileItem?) if it's referencing a file that is temporary, it needs to set the status for the file entity to make it a permanent file. Could also move file if needed at this point too?
- Create new permissions in file module specifically for uploading temporary files
So the flow would then be:
- Create new temporary file at /file/upload and store the file ID you get as a result
- Use this in a file reference field on a parent entity
Also, me and dawehner were talking, and agreed that it does makes sense to keep the FileUploadController and route definition in the file module, as it is not really implementing anything from REST module.
Comment #316
dabito commented@damiankloip @ibustos
How do we deal with security here? Where would we get the constraints from for this arbitrary upload?
One solution is to add the entity/bundle/field information to the request, but I'm worried about tightly coupling to the field module.
Another solution is to pass constraints in the request itself, but this could create a sensible vulnerability.
Maybe add a config which defaults to a list of accepted mime types for all uploaded temp files?
Comment #317
berdirYes, I brought that up in #291 as well, some responses in #292 and #299.
I'm not sure yet. I think having good validation for this is absolutely critical and we need to be extra careful here. I get the point about not tying too much to entity/field, but REST is all about entity/field (validation) and field validation falls back to upload validators too.
Either we need to validate it there and upload in context of some field, or we need to ensure the validation happens while it is added to an entity *before* the file is made publicly available.
Comment #318
damiankloip commentedYes, we need to be super careful here. The latter is the only sane way to go I think (I don't know a lot of these systems as well as you do). I am hoping we can only allow uploads of temporary files, they can then only be made into 'real' files once attached to a parent and referenced. If we could have file validation running when entities are saved (or just validated) via some constraints, I think you could E.g. attach a temp file via the ID to your article, then have it validate based on (E.g.) the image field configuration on that. Is that possible?
Currently a lot/all of the validation is just at the form level. If the file is only temporary, and validation happens like it does now. We should be able to utilise current mechanisms for validation of the actual file? Also, this is not going to be a publicly accessible endpoint. If we can, it would be good to keep any specific field knowledge out of these requests.
As berdir suggested a while ago, I think, we could do something similar to file entity module, the endpoint to allow uploads has a whitelist of extensions too. So it could be limited there, and further validated at the field level. It was discussed previously, and I think it's a good option.
Comment #319
dabito commented@Berdir @damiankloip @ibustos
So we basically have the contextualized upload and the arbitrary upload. While the arbitrary upload is decoupled, it is way more insecure. Adding context to this upload process lets us ensure only validated files ever make it into the server.
With the contextualized upload we would require an owner, an entity/bundle and a field.
Entity/bundle could be part of the URL, so the endpoint URL could be /file/entity/bundle/field/upload
I would agree with Berdir's #291 since the above URL looks too long. So maybe just /entity/bundle/field and use PUT request?
Original file name could be passed using the content-disposition header (Do we need to change to multipart/form-data?).
Content-Disposition: file; filename="file1.txt";Comment #320
damiankloip commentedEither way, we still don't have a proper way to validate the files being uploaded right now, as it's all done at the form layer. If we have a whitelist of allowed extensions, I don't think that would be way less secure? Seems like it could be ok.
How do you see the flow working (request wise) if we go down the field context route? Upload file data for /entity/bundle/field get your file ID, then make another request to use that file ID on your parent field reference field? You could still POST data for field X, then attach it to field Y once you have the file ID if you wanted to anyway (unless we move file validation as I mentioned in #318)?
I think Content-Disposition is a response header really?
working on some changes to the patch currently. Won't change too much until we settle on some of these other points.
Comment #321
ibustosSo the way it would work IMHO, would be as follows:
1. Have users POST to say /file/upload/{entity_type}/{bundle}/{field_name} while optionally specifying a file name (I liked the idea of using Content Dispoition).
2. All validation for that field should be done at this point for security purposes (we don't want users uploading .exe files and the like). Once we have the file we run all validations. If we need to save the file for this purpose, the file must be set to TEMPORARY.
3. Once the file has passed validation, create the File entity (and move it to PERMANENT?), if it doesn't pass, attempt to remove the file.
4. Since users using this functionality are must likely using rest or Jsonapi, in order to append the new file to a field, all they would have to do is just do another POST or PATCH request to that entity endpoint to assign a value for that field.
What are your thoughts on this?
Comment #322
ibustosThis patch adds an endpoint to create files. The filename is being retrieved from the header using @dabito's suggestion for the time being. Also there is no validation whatsoever yet.
Comment #323
dagmarComment #325
damiankloip commentedBut POSTING to an endpoint like /file/upload/{entity_type}/{bundle}/{field_name} will be fine, but then what's stopping you then using that file for another entity/field instead? Not much, I don't think. This is why I prefer the idea of having a whitelist of file extensions on the endpoint, then actual validation on the referenced file.
Comment #326
berdirFile/image field entity validation should cover everything that upload covers as well. See \Drupal\file\Plugin\Validation\Constraint\FileValidationConstraintValidator
The problem is to figure out a way to actually validate a file while it's still in temporary:// and *after* that move it to public. That is however actually quite complicated, as we have no such logic right now, we'd need to add something, like a preSave() in FileItem, but based on what should it act?
The advantage of uploading for a specific field is that we don't have to worry about temporary:// IMHO. Because we can just create it as a normal public:// (or whatever is configured on that field) temporary (the status, not the location) file, just like when you upload in the UI. It would also fix the permission problem, because we can easily check edit access to that specific field for that node type. That means you can really only upload files if there's at least one file/image field on an entity type/bundle that you are allowed to edit.
Comment #327
damiankloip commentedNice, thanks berdir. Missed that validator! That is good news.
Yes, it's the moving of the file that could be really problematic indeed. So we should still have an endpoint but the access can check field access for the user instead. I guess you could still just use the file somewhere else, but that doesn't seem to bad, as long as it's valid (which seems will be taken care of already).
@berdir, do you have any preferences about the how we allocate a file name during the upload to this endpoint? As mentioned in #320, it seems the content-disposition header is not really a good choice, as that's really meant to be a response header only.
Comment #328
wim leersI've caught up on this issue starting at #291, i.e. from around the time I last reviewed this issue. This issue was re-kick-started around the end of DrupalCon Baltimore (which was April 24–28, 2017, comment #294 was on April 25, 2017), and after that I was on vacation.
I commented on the comments where I think I can add something meaningful, express my agreement, or disagreement.
#304: thanks for pointing at the Contentful example. That's another indicator we should take this approach. Twitter does something similar: https://dev.twitter.com/rest/reference/post/media/upload — and it explicitly returns the number of seconds until it expires. Although it also supports multi-step uploads:://dev.twitter.com/rest/reference/post/media/upload-init.html + https://dev.twitter.com/rest/reference/post/media/upload-append + https://dev.twitter.com/rest/reference/post/media/upload-finalize — for a concrete example of how to use that, see https://blog.twitter.com/2015/rest-api-now-supports-native-video-upload.
#305: I think two requests is acceptable. You're right that my suggestion in #282 was misguided, because when using https://www.drupal.org/project/subrequests, you wouldn't be able to use streamed uploads. So, +1 for two separate requests: one for uploading a file, another for associating it. Or, if uploading many files, N+1 requests: N file upload requests, 1 request to associate them all with the same entity.
#306:
+1
I think the act of referencing the temporary file from an entity's file field would cause that file to be marked permanent.
Yes.
I think the "upload binary file" request should indeed specify the filename. How exactly, I don't know. Possibilities are:
AFAICT that'd be okay. But then we'd still need to specify the file name in that request.
The advantage of a separate endpoint for just "raw uploads" would be that we could have more strict (and better tested) clean-up procedures for unused temporary files. But if we feel that this already is sufficiently reliable, well-tested, and so on, then I agree that ideally we'd just reuse the "temporary file while uploaded and made permanent when referenced" strategy that core has already been using for file uploads for many years.
#312:
I don't understand this. If it's specifically tied to
Fileentities, then why should it not be provided by thefilemodule?EDIT: hah, you already came to this conclusion after talking to @dawehner in #315 :)
#315++
#316 + #317 + #318: RE: security. I think this is the answer:
… which is to say, this is once again just the "normal" field validation constraints being applied: when saving e.g. a
Nodethat has a file field, then this is what happens:temporary://stream wrapperPOST(orPATCH) aNodethat lets the file field reference the file ID from point 1POSTing (orPATCHing), the entity is validated, which causes each modified field to be validated, which includes the file field.The file fields' validation constraints run, which are identical to the validation that is ran during file uploads for the "regular form file upload widget".
We should indeed. If we can't, then as part of this issue (or as a blocker of this issue), we should refactor the existing form-tied logic into proper Field Validation Constraints. I'm quite hopeful though, because
\Drupal\file\Plugin\Field\FieldType\FileItemhas this:and
\Drupal\file\Plugin\Validation\Constraint\FileValidationConstraintValidatorhas this:#319: I honestly don't yet see why we'd want/need to tie this to an entity/bundle and a field. What matters, is that a certain file field is allowed to use a particular file. Therefore the validation of the entity that contains the file field is what/when/where the validation should happen.
#326:
Yay, glad to see what I wrote confirmed :)
Why is this a problem? Surely we already need to do this for our form-based file uploads too?
Apparently we don't?! :O :O :O
… but … that can easily cause you to end up with lots and lots of unused "permanent" files? And it's also semantically wrong/strange/confusing?
Also, the downside is that you have to somehow figure out the particular magical URL for file uploads for every single field + entity type + bundle combination… which is a bad DX.
WRT the
Content-Dispositionheader: it seems it's actually allowed for POST requests when uploading files, see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Dispos.... For a concrete example of how that can be implemented for a REST API, see https://github.com/SparkPay/rest-api/blob/master/uploading_files.md.Note that another consequence of using multipart request bodies is that we can support uploading multiple files in a single request. But I don't know if it's compatible with
application/octet-stream?Comment #329
berdirThere are two different kinds of "temporary" concepts. One is the *status* of the file entity. A freshly uploaded file with status temporary is still in public:// and publicly available. Otherwise we wouldn't be able to show that file or e.g. a thumbnail of it.
The other is a file that is in the internal, non-public/accessible temporary:// stream-wrapper/directory. We have no support to automatically move those files. We would just make it a permanent file (file that is not being deleted after 6h) but it's still in the temporary folder and not publicly accessible.
That's why my suggestion is to enforce the upload in context of a field, because then it is the same as upload in the UI. We can make it a public, temporary file that will get deleted after 6h (we don't even need to do anything custom for that), and actually referencing it will make it permanent.
Comment #330
damiankloip commentedOK, spoke to Wim and Berdir in IRC for a while and here are some more changes, we all agreed in the end that keeping in bundle and field specific is the way to go. So we can then just create the file in place and don't need to worry about moving it etc... This is still a WIP, just ran out of time today.
This also converts things back to one route/endpoint that can create files, no update only endpoint, as well as converting to a rest resource instead. We should then get serialization handled for us.
There are still some todos, we need the validation in place based on the field definition being used, amongst other things.
Comment #331
damiankloip commentedAnd a couple of other fixes.
Comment #332
wim leersI did not agree with that.
The uploading should be agnostic of entity type/bundle/field. Then when you reference the uploaded file from a file field, the necessary validation should run.
Comment #333
damiankloip commentedok, I thought that we agreed it made sense for now to go with entity/bundle/field as we can place the file in it's actual location - like file uploads happen in the UI. If you want to try and also solve how we would move files when they are referenced, let me know :) Otherwise, all uploads would have to just sit in public:// or something. That said, I would still be happy to just upload temp (status = 0) files to public that would be either referenced and made permanent, or deleted on cron runs.
Ideally that would be the case, but I'm not sure we really have much of anything in place to validate and move temporary files when a parent is saved.
Comment #334
berdirNo, uploading is not agnostic of that. There's not just validation, the field also controls where the file is stored (private:// or public://, but it could also be on amazon s3 through flysystem module or who knows where) as well as the folder (media/YYYY/MM for example).
It is important that we respect that configuration. if this happens to be your CV that you upload to a job application form the you don't want that to end up somewhere public :)
And doing all those things *only* when attaching to an entity is complicated, it is possible to re-use files in multiple places even if those settings are not the same (they only matter for the initial upload), so we would need to figure out when to move and when not to move files and so on.
This is a first step and it reflects how file uploading works in drupal core. After that, we can think about improving security, also for the UI (which is a lot harder than you might think and there's a reason it wasn't done yet), there are ways to integrate better with media enties so you don't need 3 requests there, and contrib modules like file_entity could offer a standalone upload resource. And a module like flysystem_s3 could even offer a REST resource to create a file that was directly uploaded to S3, it already has ajax integration for that.
Comment #335
dabito commentedGreat job with the rest resource, I really like that strategy. I've two comments:
1) If we're going the Content-Disposition way for filename, we should be using multipart as that's the only way the header should be used in requests. Also there must be a better way to obtain the attribute other than the regex, as the todo mentions.
2) Regarding the agnostic upload, what do you guys think of the quarantined file strategy mentioned earlier in the thread? The full approach could look like this:
- single upload endpoint is requested *
- if it has context then follows the existing path *
- if there is no context, though, it could create a quarantined file entity which is hidden (random or hashed filename located in tmp)
- this file entity can be the upgraded to a permanent file managed entity upon parent entity create / update
- a cron job will delete all quarantined files older than 1 hr
* what we have so far
Comment #336
damiankloip commentedThanks :) I think the rest resource could be a good way to go. It's currently a bit strange with how REST handles allowed content types though... so we might need to look at that a bit more.
1. I don't think we should go with that header, I just left it in for now, along with the todo. I think we should just go with something else that only consists of a filename for now. I think we should stick to the single file per request for now, we need the simplest implementation in IMO, so parking for now is multipart is sensible.
2. I think that might be easier said than done. I'm not sure we can move files like that when a parent is updated at this stage. It get's tricky, and nothing like that currently exists in core. If it was a random or hashed filename, renaming it on parent create is also something that in no way exists. So we would need a way to rename it again, or you need to add another field to file entities.
Comment #337
kylebrowning commentedI agree, do single requests for now, this is the last feature IMO for a fully backend capable Drupal. Keep it simple we can always add more later.
Just my opinion though.
Comment #338
garphyBasically, Drupal core currently offers no way (through UI) to manage file entities directly. Everything is done through File/Image fields.
So we stuck with either :
Providing contextual info about the field seems fragile, as already noted : nothing would prevent from referencing the created file from another entity/field. Still, I do not see clearly how we would manage the temporary/permanent status when POSTing the entity referencing the file.
Allowing the creation of a file entity is another can of worms but it's probably the best way ultimately. It's basically what the media initiative is working on but I did not manage to find an existing issue on the specific topic of adding a new file to Drupal (adding a "new file" button to admin/content/file).
In the end, we have to face that files are entity as nodes are, we cannot keep on handling it like a piece of content which only belong to one entity like text fields. We have to decouple the lifecycle of a file entity from the lifecycle of its referencing entity (and figure out what happens to validation).
(BTW, the issue title seems pretty outdated now :)
Comment #339
garphyWell, per #2825215: Media initiative: Roadmap (if it lands), it seems that we are gonna deprecate File/Image field in favor of using the Media entity type with plain Entityreference fields.
So maybe it's better to focus our effort on "how to upload a file which will create a Media entity referencing a File entity" ?
Comment #340
damiankloip commentedI think still being able to upload a file is useful too. Media can then build on that (discussed briefly with Berdir yesterday).
The trouble with the standalone file upload endpoint (which I would prefer too) is that we have to decide where the file will live, so we could default to public:// for example, but then a field could be using a completely different storage location (or even different backend, like s3), and moving the file when the parent is saved seems like a no-go for now. Otherwise, as mentioned up somewhere, we just have a whitelist of file extensions (or maybe just validators that run) for file uploads. Then they can be used, and the parent entity would fail validation if the file did not meet the validation criteria.
Ideally files would be more standalone. but Drupal just doesn't seem to be there just yet. So it seems sensible to go with Berdir's suggestion for now, and implement something more akin to how file uploads work in the UI.
Comment #341
wim leers#333: I agree that it's better to have entity type/bundle/field-bound file uploads is better than not having at all. The problem with that though is that we have to support it forever, if we commit it and ship it in 8.4.0. I do definitely think that it's a great first step for this patch, I'm only saying we should very carefully considering the implications of shipping a Drupal release that supports it. But let's worry about that later.
#334: Ugh, I totally forgot about where it may be stored (which can be anywhere when using flysystem). Which suggests that we'll need to ship with it working in the way as described in #330 anyway :(
#335: in #330, the context is provided via the URL path. "Contextless file uploads" are therefore impossible in that approach. And per #334, it seems like it's highly undesirable/unsupportable/will require massive rearchitecting to support contextless file uploads.
#337: This is the guiding principle for me in my comments. And I was under the impression that contextless file uploads would be simpler, but #334 explains convincingly why that's not.
#338: I very much agree that files with context are fragile, because if a file is uploaded via an image field to a particular file directory and then later referenced from a file field that by default uses a different file directory… then it's still inconsistent in the end. But that's a problem we already have, and would require massive rearchitecting to fix/make clearer. We can't fix that here.
All in all, #340 captures it well:
We all would like to have contextless/standalone files… but we can't, because that's not how it's been designed to work, and we can't/shouldn't block REST-based file uploads on resolving that architectural problem, because it will likely take years.
So, let's move forward with #331! Any reason why that's not marked as "needs review"? I'm curious what testbot has to say!
Comment #342
damiankloip commentedI didn't trigger the bot just yet as the FileUploadTest I wrote for the previous behaviour has not yet been converted to account for this now being a rest resource. I'll take a look at that. I'm not sure it makes sense to extend ResourceTestBase, it provides a lot of stuff we don't really want to use here I think?
Comment #343
wim leers#2869387: Subclasses of ResourceTestBase for non-entity resources are required to add pointless code cleans up
ResourceTestBasequite a bit. Once that's in, why wouldn't you want to base test coverage off of that test? The current test coverage is not yet testing 4xx responses (except for one 415, but with insufficient rigor). It's also not yet testing in multiple formats.Very high-level, very rough review below:
Should we move this into a separate issue? This looks like a simple enough change that can go in independently?
Does it even make sense to have a
canonicallink relation for this? This REST resource plugin only provides apost()method after all…This should not be necessary, because this plugin is provided by this module, hence this would be calculated automatically?
I think all this would benefit from being moved into a helper method. All of this is related to access checking.
Nit: The name suggests this is validating far more than just the
Content-Typerequest header.Also: can be
static.Nit: mismatch.
Also: can be
static.Comment #344
damiankloip commentedHere is a bit more work on the actual resource validation. I'll wait on the test conversion to see which is the best class to extend from. I have copied the logic from FileItem::getUploadValidators, and maybe a couple of things from file_save_upload. I think maybe we should extract some of that logic into separate functions that are used internally by file_save_upload.
Comment #345
damiankloip commentedI didn't know about that issue, if it makes less assumptions about all the things a resource should test, then I have no issues using that. However, it looks like it doesn't really change too much? I'm not sure what benefit we will get from testing things like different formats, they should all fail? unless we use the accept header to determine the format of the response? Would be nice if it could test 403 responses etc.. for us. Seems like a lot of test duplication though. If it's a rest resource don't we already know that it will be protected by a permission?
Comment #346
garphyThe issue may be that exposing an upload feature as a REST resource of file entities feels like it provides contextless upload.
We could also postpone this until some bits from the media initiative lands in code (specifically the media entity) and provide upload a this level, as the media entity will definitively expose standalone media (including local files) management.
It's just a thought but it may be more relevant than shipping an upload feature bound to file/image field context when we can anticipate that those may be deprecated in favor of "plain" entity reference field targeting media entities.
Or we keep going with the current approach and we'll add a contextless upload alongside media entity later, but we'll have to support both methods for a while then.
Comment #347
wim leersWe should not wait for Media.
Comment #348
berdirNo, it actually doesn't.
An image media bundle is 100% identical to a node type with an image field. It's an entity with an image field. The field controls where the file is saved, validation settings and so on.
So with this approach, what you will need to do to create an article with a media element is:
1. Upload the file in context of media bundle image field.
2. Create the media entity referencing the file.
3. Create the node that references the media entity.
Yes, complicated, but it will also just work. As I already suggested earlier, what we could do is abstract part of that complexity and allow to merge step 1 and 2 together, so that you can directly create a media entity with binary data (you still need to provide the bundle, as you can have multiple image media bundles with different settings). But that's optional and there are quite a few things that still need to be answered there:
* Media entities can have all kinds of metadata, some of that could even be required, so how would you submit that?
* media entities could support multiple images in the same bundle (could be a gallery, or could be different versions of the same file or so)
So it would not replace this, it would just build upon it and it would probably not work for all sites/use cases. This however should.
Comment #349
wim leersBecause in case of file uploads, we just have binary data in the request body, not
hal_jsonorjson? Does that mean this is the first request body format-agnostic REST resource in core?Right, we do normalize the created
Fileentity in the response, which means it's not actually format-agnostic. The request body is, the response body is not.The request body format is determined by the
Content-Typerequest header, the response body format is determined by the?_formatURL query argument. So I'm not sure I understand what you're getting at?No, only REST resource plugins that do not override the default
\Drupal\rest\Plugin\ResourceBase::permissions()implementation are guaranteed to have a permission. REST resource plugins that override this can use whichever access control mechanism they like.For example,
\Drupal\rest\Plugin\rest\resource\EntityResource::permissions()overrides it so that it can choose to rely on Entity Access instead. The REST resource being introduced here should do so too.Comment #350
damiankloip commentedOK, here is a new patch with mostly changes from comments in #343:
So, regarding the request and response content type; I guess it makes sense to allow a ?_format to be used, it's just kind of weird as the Content-Type would still have to be application/octet-stream but this also makes sense. Ideally we would just have an Accept header but ... :) So guess it's a strange one, for the actual request we don't really care about any serialization formats, but response we do.
We still need to look at the tests, to convert my initial test coverage to some kind of REST based test. @WimLeers, can you give me some pointers here. I'm not sure if it's best to extend ResourceTestBase or not? We need to set up a file field on a test entity, users with perms to create a parent entity. The requests need to use the method I already have to do a proper request to send the data, but this is already pretty close to what's in ResourceTestBase, so I don't think this should be a problem.
Comment #351
wim leers#350:
Similarly, if this patch can do without the changes made in
FileAccessControlHandler, then that'd be splendid, yes. But I suspect we'll eventually need some changes.I don't see what's weird about sending a request with the request body in one format and the response body being in another format, a format that you can specify? Yes, it's uncommon, because 99% of the time, the format of the request body and response body match. But in this case, that wouldn't make sense, would it? Getting a
application/octet-streamresponse body wouldn't actually be able to convey information in a very useful way :DSo this is a place where it's natural for the formats of the request body and response body to differ.
Hopefully you're nodding along while reading this — if not, please elaborate!
On the subject of tests:
ResourceTestBase, because that'll make it easy to create tests injsonandhal_jsonformats and for different authentication providers.FileUploadResourceTestBase extends ResourceTestBase, then implementFileUploadJsonAnonTest,FileUploadJsonBasicAuthTest,FileUploadJsonCookieTestandFileUploadHalJsonAnonTest,FileUploadHalJsonBasicAuthTest,FileUploadHalJsonCookieTest. With all of those 6 extendingFileUploadResourceTestBase.RoleJsonAnonTest+RoleJsonBasicAuthTest+RoleJsonCookieTestas an example.ResourceTestBaseand working for all the formats + authentication provider combinations), probably look atEntityResourceTestBase::testPost()to see what kinds of edge cases you want to test. I'm happy to do that for you when we reach that point.Comment #352
slashrsm commentedI agree with @Berdir on that. As Media entity is adopted in core files will become even more hidden part of the system. When the switch is made to fully use media entity files won't appear outside of its scope any more. That said, I think that we need to treat them as such.
This would make a lot of sense IMO.
Comment #353
damiankloip commentedRE #351:
1. So that was needed before as the file entity was created more inline. Now we are controlling things as we have a resource that handles the creation of the file and its data only. So as the patch now does, we just pass the uid from the current user that is injected into the resource.
2. I can't remember where else I thought this might be a problem now, I'll just remove the canonical and we'll see how things go.
Yes, it's only weird really because we use _format as a replacement for content-type and accept depending on the case. Otherwise, it would totally not be weird. So this is just Drupal... otherwise, yes, I'm nodding along :)
Working on the tests now. I'll see how far I get.
Comment #354
dawehnerINHO we should add an explanation why we use
status: 0Can we add the file ID as location header, just like we do in
\Drupal\rest\Plugin\rest\resource\EntityResource::postDoes that mean some user can override the file of some other user? I guess this is not the case, because tempnam will generate a unique filename anyway? Do we need FILE_EXISTS_REPLACE then?
Mh, are we sure we don't want to throw a 404 exception? This exception is not implementing
\Symfony\Component\HttpKernel\Exception\HttpExceptionInterfaceWe should add a todo for #1916302: RFC 7807: "Problem Details for HTTP APIs" — serve REST error responses as application/problem+json, so we actually produce machine readable errors at some point.
Isn't that a BC break when we remove this functionality?
Comment #355
damiankloip commentedHere is a WIP start on the test coverage. Aside from needing to be tidied up and implemented properly (inc some more edge cases etc..) the first main blocker (after a little debugging) is that ContentTypeHeaderMatcher matches the '_content_type_format' requirement that is added to all REST routes, so our route is then blocked out I think. So we actually get a 415 back in that case. I am also still getting a 403 for the first case, so maybe I am doing something wrong with setting up the test user possibly..
Comment #356
damiankloip commentedDaniel!
1. We could just remove this, the default behaviour of a File is to start with status as FALSE.
2. Yeah! added. do we want to use url() like I have done for files, or should we use the same code (pretty much) that EntityResource has. I guess the question is do we like to the file entity or the file itself? I don't think file entities implement the templates needed for that to work.
3. Yeah, we wanted this before when it was only overwriting/updating but don't want it now!
4. Changed to AccessDeniedHttpException - it was meant to be that anyway - good catch
5. What should the @todo say? I'm not sure.
6. Meh. Well, that functionality doesn't really work anyway, it only saves files in temp. I would be very surprised if this was being used, and I think we should definitely not support it either. It has some of the same failings that is a big reason we cannot just have a generic upload endpoint.
Made some of those changes now quickly to keep things up to date with reviews.
Comment #357
wim leers#353:
:)
#354.2++ — see
\Drupal\rest\Plugin\rest\resource\EntityResource::post()for code to copy/paste :)#354.5: agreed, but seems kinda out of scope here; this is a pre-existing problem. It's okay to be explicit, but at the same time it's not guaranteed this will end up being the solution (it is likely though).
#355:
Oh that's interesting! I think it's fair to say that file uploads are the very very very rare exception to the rule. Indeed, file uploads don't want the
json,hal_jsonor whatever formats. You want theapplication/octet-streamMIME type… for which no format has been defined yet!\Symfony\Component\HttpFoundation\Request::initializeFormats()also doesn't define it.So, we'll want the File module (or perhaps core) to define a format for it (name TBD, but probably something like
'binary' => ['application/octet-stream']?). Then we have two options:RoutingEvents::ALTERsubscriber whose priority is lower than\Drupal\rest\Routing\ResourceRoutes, so that it runs later, and can set$route->setRequirement('_content_type_format', 'binary').\Drupal\rest\Routing\ResourceRoutes::getRoutesForResourceConfig()so that it doesn't set a_content_type_formatrequirement if and only if such a route requirement already exists. This then allows us to implementFileUploadResource::getBaseRouteRequirements(), so that we can specify'_content_type_format' => 'binary'there.I like option 2. Because it puts the control with the
@RestResourceplugin, and therefore allows self-contained implementations, rather than requiring additional event subscribers to be set up.Comment #358
damiankloip commentedYes, I totally prefer options 2 also :)
Comment #359
damiankloip commentedOK, a few more fixes and a few more problems :):
- \Drupal\rest\EventSubscriber\ResourceResponseSubscriber::getResponseFormat() uses the '_content_type_format' for acceptable requirements only, for responses. This doesn't quite fit our use case here. I have added our service provider and other changes to the rest route building to do what was suggested in #357. However, when we get to the response, we have issues. We can't the use the format (or default to JSON?) but the only acceptable type is 'bin', as that is specified in the '_content_type_format'. Not sure of the best fix yet, but the assumptions in this code on't seem 100% correct, shouldn't we be checking either the _format or the Accept header or something? Otherwise we can never accommodate the valid case we mentioned above for this; POSTing in binary but receiving the serialized file entity in JSON etc.. in the response. Thoughts on this?
- In order to not have the request content normalized for us, we need a new controller method to handle the request, so I made a new handleRaw method for now. This is just c/p from handle() but with the normalization removed. So if we like this idea, we can refactor/tidy it up a bit. Otherwise, we will need a way for the plugin to tell the controller method that it does not need to be serialized. Could be something on the annotation or something.
- My test is failing on the field 'create' access check, not sure why yet. Need to look into this. Probably insufficient perms granted for the test user...
On the plus side, we can remove the custom validation for the application/octet-stream content type as the '_content_type_format' access check can cover that for us now.
Comment #360
damiankloip commentedSorry, somehow I added this file to the commit for my previous patch and not in my latest interdiff! This also got added.
Comment #361
wim leersThat's because your POSTing to
/file/upload/entity_test/entity_test/field_rest_file_testand not to<code>/file/upload/entity_test/entity_test/field_rest_file_test?_format=json— i.e. you're not yet specifying the acceptable response format! :)Fixing that causes the response to not be HTML, but
json:What about checking if the format is
bin? So::)
Comment #362
damiankloip commentedRight, But I tried it adding format to the query too, and it seems the same problem happens (forget about access for now):
Serialization for the format bin is not supported ...Which I think is being enforced due to this in
\Drupal\rest\EventSubscriber\ResourceResponseSubscriber::getResponseFormat:So the acceptable formats will use the content type formats, as it is a POST method, but, REST resource plugins only add a _format requirement on GET and HEAD routes, so the acceptable request formats would not be populated for our route yet anyway.
I'm not sure about checking the 'bin' format explicitly in the ResourceHandler itself, then we kind of couple a format defined in the file module to the REST module?
I'm not sure what I'm doing wrong with the access check, I thought if the permission to create the entity was correct it should be ok, but maybe the access checking is not correct?
Comment #363
wim leersD'oh!
So the problem is this:
Because
$acceptable_request_formats === ['json']and$acceptable_content_type_formats === ['bin'], but due to the logic,$acceptable_formatsis calculated to be['bin'].So my answer is: is this a bug in
\Drupal\rest\EventSubscriber\ResourceResponseSubscriber::getResponseFormat()? Remember, that was written when we had no use cases yet for the requested response format not matching the request body format!I think this change would make sense:
Comment #364
damiankloip commentedHmm, I have made that change (looks sensible) but then we need to add the _format requirement for the resource, then I get 404's somewhere. So I guess _format is being checked/enforced somewhere else earlier in the routing system?
Comment #366
damiankloip commentedComment #367
damiankloip commentedComment #376
damiankloip commentedUpdated IS.
Comment #377
damiankloip commentedBetter title? Any other suggestions welcome.
**Looks at Wim
Comment #378
garphyI just attempted to try the latest patch (#366) to upload a file in the
imagefield of amediaentity of bundleimage.I try to call
/file/upload/media/image/image?_format=jsonwith the following headers :But I always get a 404 and the following response :
Is it supposed to work and I'm doing something wrong or is it just not ready for real testing yet ?
Comment #379
pnagornyak commentedHi garphy, The reason why you get 404 is here ResourceRoutes.php:
and here FileUploadResource.php:
i made some fixes to the last patch, now it should work, tested with Drupal 8.3.5
P.S. i removed application/octet-stream type, the serializer don`t know about this type (bin), maybe it will be added later
Comment #380
wim leersHere's the interdiff for #379.
@pnagornyak: for next time, please see https://www.drupal.org/documentation/git/interdiff :)
Also queued #379 for testing. If I run
FileUploadJsonBasicAuthTest::testPostFileUpload()locally (which failed for #366), then I see an interesting difference:Comment #381
pnagornyak commentedI didn`t change the tests, so it will fail as it uses Content-Type: application/octet-stream request header (maybe there not only one problem exists).
P.S. i have no experience in testing, that is why i didn`t fix it.
Comment #382
wim leersReviewing #379's changes:
This … seems wrong?
This seems very wrong.
This should not be necessary.
This is definitely wrong. A simple copy/paste mistake perhaps?
This is just reverting the changes made to
ResourceResponseSubscriber.I proposed that in #363. @damiankloip then wrote this in #364:
This now makes me suspect that this might be related to routing system bugs that I also encountered in #2858482: Simplify REST routing: disallow requesting POST/PATCH in any format, make consistent, and which are being fixed in #2883680: Force all route filters and route enhancers to be non-lazy.
So, back to the patch in #366. First: there's a small mistake in
FileUploadResourceTestBase::fileRequest()with the?_formatquery string. Next, turns out we indeed need some of the changes in #2858482: Simplify REST routing: disallow requesting POST/PATCH in any format, make consistent. Because this bit inResourceRoutesis causing the "file upload" route to not be generated:If we import the fix from #2858482, yet still keep #366's existing changes to
ResourceRoutes(to not overwrite_content_type_formatif it's already set, which this REST resource uniquely needs, to be able to specify a_content_type_formatthat differs from the other format, so it can accept binary data), then we get further in the tests than #366 and #379!The failure is then:
The actual response body is:
So that's the next step!
(My patch is relative to #366!)
Comment #383
pnagornyak commentedi added it as "file_unmanaged_move($temp_file_name, $destination_uri)" returns new file uri, if user1 send a file "avatar.jpg" and user2 send "avatar.jpg" than user2 will get an error but file will be uploaded and not saved into DB
This $field_definition->access('create') checks is user has an "administer fields" permission so i though that it wrong
Comment #384
wim leers@pnagornyak I wrote #382 in a hurry — it looks much more negative than I intended! :( Sorry about that.
You're absolutely right! I wrote , but I was completely wrong — it's very much necessary.
Bringing that hunk from @pnagornyak's patch in #379 :)
FileUploadJsonBasicAuthTestis now passing!Comment #386
wim leersAdded test coverage to assert the response of a successful upload.
Comment #387
wim leersThis is not quite realistic enough:
field_imageon thearticlenode_typethat's installed by default has thefile_directoryset to a token, so it's uploaded to that particular subdirectory.We should also be testing with a subdirectory here.
… and that's exactly how a whole bunch of other things in #379 will turn out to be useful after all! #379 was just ahead of its time without a clear explanation :)
So when I specify
'file_directory' => 'foobar',, the "is writable" check fails. The fix from #379 is only partially correct though: it's missing theFILE_CREATE_DIRECTORYdirective. And it should replace the existing check, not run before it.Comment #388
wim leersThese were added in #387 — and it makes the test pass, but it points out another bug. Let's fix that here.
(#379 fixed that too!)
Comment #391
wim leersAnd this then should most of the other tests pass. It reverts some of what I proposed in #363, as well as some of what I did in #382.
ResourceResponseSubscriberTeststill fails, but I don't have time to dig deeper into this now.At least the number of failures should now be manageable again.
(I also removed some methods that were previous required for
ResourceTestBaseclasses, but are not anymore.)Comment #392
wim leersThis removes one more failure.
Comment #393
wim leersNext steps: complete all todos, and add a lot more test coverage.
Comment #394
damiankloip commentedThe tests make the assumption that uncacheable POST/PUT routes will not have any acceptable response formats specified, so the logic fails when $acceptable_request_formats is empty in ResourceResponseSubsriber::getResponseFormat(). So I think this is the correct fix - to add the _format requirement to these routes too.
Comment #395
damiankloip commentedHere are some more test classes:
\Drupal\Tests\file\Functional\FileUploadJsonCookieTest\Drupal\Tests\hal\Functional\EntityResource\File\FileUploadHalJsonBasicAuthTest\Drupal\Tests\hal\Functional\EntityResource\File\FileUploadHalJsonCookieTest(and a
\Drupal\Tests\hal\Functional\EntityResource\File\FileUploadHalJsonTestBaseclass) with a couple of other fixes to help these along.Questions
FileUploadHalJsonTestBase- trying to use the applyHalFieldNormalization() method does not give the expected normalization result for file entities. Any ideas about that?FileUploadResourceTestBasemove into the rest module? This seems to be a pattern for others. E.g. CommentResourceTestBaseContent-Dispositionheader or have another one for our file name. We also need to add this as a hard requirement in the patch.Comment #396
wim leers#394: D'oh! OF COURSE! Perhaps we should move that logic into a
protected function generateRouteRequirements()helper method, because #394's interdiff is repeating the same logic in 3 places?In any case: this looks great :)
#395
- In other entity types, the
uidbase field is translatable, not forFile.- Are you sure that that is happening? I don't see the explanation for that either right now.
- There's lots of weirdness around this, because
File::url()callsfile_create_url(). The tricky thing is that theuribase field uses\Drupal\Core\Field\Plugin\Field\FieldType\UriItem, but that's for URIs in general. This is a very particular URI, with special treatment. We definitely need to investigate this further.Content-Dispositionfor now and make that a hard requirement, add explicit test coverage. Once we have test coverage for it, it'll be easier to change.Also: even though it's probably pretty rare for anon users to upload files, I do think we also want a
FileUpload(Hal)JsonAnonTesttest.Comment #397
damiankloip commentedThanks for the review, Wim.
Moved that test code into a
generateRouteRequirementsmethod.1. a. So with hal, the uid field is meant to be removed as it exists as a relation in _links and _embedded (sorry, the comment was not overly helpful). b. Yes, that's definitely what I have been seeing in the expected normalization data once passed through applyHalFieldNormalization. c. Not sure how we can better handle that, we ideally need to use file_create_url as the url will be specific to each test install. Otherwise we will basically just have to mimic this anyway? other ideas completely welcome though.
2. OK - moved it into the Drupal\Tests\rest\Functional namespace. I didn't go as far as EntityResource as it's technically not an entity resource like all other entity resources, and doesn't extend EntityResourceTestBase.
3 & 4. OK, sounds good to me - added additional enforcement for that. Also improved the regex logic to be a bit more restrictive. Before it accepted empty strings, as well as a a key-value pair in the header like 'not_a_filename=example.txt'.
Yes, I was going to look at implementing the anon tests too, I just didn't quite get on to that yet. I will have a look soon.
Comment #398
wim leers👍
Reply:
Fileentity'suidbase field is not translatable, unlike all others. But … that also doesn't make sense. We'll need to ddebug the HAL normalization process to figure out why this is happening — i.e. step through\Drupal\hal\Normalizer\ContentEntityNormalizer+\Drupal\hal\Normalizer\EntityReferenceItemNormalizerc) This is due to
\Drupal\hal\Normalizer\FileEntityNormalizer::normalize()AFAICT> Perhaps we need to change that?Review:
s/should/must/
Also: trailing period.
:P
Same description, but different header -> one of the descriptions is wrong?
👍
Comment #400
damiankloip commentedI got to the bottom of what was going on with the strange applyHalFieldNormalization behaviour I was seeing. FileUploadResource tests are a little different, in that the $entity property on the tests classes is an EntityTest entity (parent entity and field config for the file). So when applyHalFieldNormalization it was applying field data from the EntityTest entity to the normalization data of the file entity - doh! So I think we just don't use that help in this case. File uploads are a special case after all. I have updated the comment to reflect that.
I fixed other docs and string feedback from #398 too. I have left the trailing periods for now though. I usually don't add them, and haven't for any of the code here (I don't think :) ). I am happy to change though - do the standards say there should always be a trailing period?
I tried adding anon tests, but keep getting failures with the file upload resource route being a 404... So will need to debug that further.
Comment #401
kylebrowning commentedbumped to 8.5.x :(
Comment #402
wim leersOh, hah!
Actually, I think we do not need to set this, because that's determined by the
RestResourceConfigconfig entity?Ok.
#401: Yeah… but now we're finally reaching the point where this patch is actually RTBC'able! :) A review from you would be very helpful. And perhaps you can add more edge cases you can think of that we should add test coverage for (see below)?
I tried to review this from a high-level POV — I refrained from nitpicking this time, and focused on the bigger/architectural aspects.
At this point, all that remains is the
normalize()method that overrides\Drupal\hal\Normalizer\ContentEntityNormalizer::normalize(), to do this:I'm now wondering if we shouldn't change that?
Does it really make sense to only return
http://example.com/sites/default/files/blah.pnginstead of just returningpublic://blah.png?Wouldn't it be more logical to have
UriItem'svalueproperty still bepublic://blah.png, and then add a newpublic_urlproperty that computeshttp://example.com/sites/default/blah.pngfrompublic://blah.png?I'd rather not distract this issue with that, but if we change it later, it'll be a BC break.
Of course, not every
UriItemneeds this, only URIs that are actually stream wrapper URIs.So perhaps what we want is a new
StreamWrapperUriItem extends UriItem, and only add thispublic_urlproperty toStreamWrapper UriItem, not toUriItem?All test coverage in here should be generalized to
FileUploadResourceTestBase, so that it's not only tested for for HAL, but for all formats.We should be able to move this to a separate issue and land it there, that'd make the size of this patch smaller, and would make it easier to review. We would have to add a test case that would fail in HEAD though, to prove the need for this change.
This is 90% identical to the existing
::handle(). We're just leaving "the middle part" out.Would it be feasible to add two helper functions, one for the "pre" part and one for the "post" part, and then have
::handle()and::handleRaw()both call those two helpers, so that::handle()is only different in that it does this extra thing in the middle?Or does that not make sense?
Also note: #2720233: Use arguments resolver in RequestHandler to have consistent parameter ordering will change
::handle()quite a bit.I think the most important question is: are there more edge cases we should test here?
Things I can think of:
Content-Typeheader value than
application/octet-stream.MIME type, and so on,
which
\Drupal\Tests\hal\Functional\FileDenormalizeTestactually is testing already.Comment #403
blainelang commentedI've been following this issues great progress and want to applaud the efforts. Also wanted to report back on my testing with POSTMAN should someone else be trying to test.
Initial attempts at a POST to /file/upload failed because I didn't have the X-CSRF-TOKEN header. We need the User resource to get a X-CSRF-TOKEN back with a REST Login API call. A successful POST to /user/login will return the token that can be used in the POST to /file/upload API
Once I had the CSRF token, I was able to POST successfully: /file/upload/node/media_test/field_media_test_file
Request Header:
Note: I had to use 'content-type' => 'application/octet-stream' and not 'content-type' => 'bin' as I was getting an error that BIN was not allowed.
I successfully tested uploading image and files to both field types. The files were uploaded to public:// and appear under the admin/content/files with a Temporary status and not used.
Testing files with an extension that has not been allowed, results in a 422 with a clear message indicating the accepted formats. The file is still uploaded to public:// but is not listed under admin/content files.
Tested uploading larger files and was successful uploading up to a 75Meg movie file. Larger files crashed POSTMAN so this may just be a postman issue. My PHP Settings are 256M for MEMORY_LIMIT and 128M for MAX_POST_SIZE.
Comment #404
damiankloip commentedFirstly, thanks for testing this! People actually trying this out is very much appreciated.
The 'bin' format name is internal really, it's a machine name identifier for a format. This is something required by Symfony. E.g. JSON would be 'json' etc..
I'm not sure the CSRF token header makes sense unless cookie auth is being used? I'll look at how REST module handles this in other cases, I thought I mimiced the logic there, but maybe not. Or were you using cookie auth? If so, that's totally expected :)
Comment #405
damiankloip commentedAddressing Wim's feedback from #402 now (wow - over 400 comments already!).
Comment #406
blainelang commentedThanks Damian!
I was using basic auth with postman.
Comment #407
wim leers@blainelang: 👏❤️ Thank you so much for giving it a try! There's no conclusion in your comment, but I gather that you're satisfied with how it behaves? :)
Indeed.
The CSRF token is not necessary then, it's just ignored. But many tutorials/docs make it sound like you always need the CSRF token, so I totally understand why @blainelang did that.
That's one of my next big tasks: totally revamping the REST docs on Drupal.org!
Again, thank you so much, @blainelang! :)
Comment #408
damiankloip commented#402 (Great review BTW :)):
1. I thought that initially too, but was assuming it was overridden to provide the full URI as the value as a part of the hal JSON specification?
2.
FileDenormalizeTeststuff doesn't really fit in our newFileUploadResourceTestBaseas it's not really testing uploads at all, just that a file entity can be denormalized correctly. Which is still supported like before, just without the additional fetching and saving of random file data.3. Agreed. Created #2901704: Allow REST routes to use different request formats and content type formats. We can remove those changes from this patch when it will pass against 8.5.x without them there.
4. How about we add more helper methods to the class and use more of a compositional pattern within the methods? That seems easier to follow that trying to get both to work with two methods or something? They have slightly different params for creating the response. Or, we could maybe have one method that we pass additional parameters into - the regular handle method would then pass in the unserialized, and handleRaw would pass in nothing. We would need to pass around quite a few parameters though :/ as the deserializing needs some of the same stuff. So the helper methods seem like a good compromise potentially.
5. Will look at this later. I think they all make sense, and I think we are already testing some of those cases.
Comment #409
damiankloip commentedOK, RE the last point of #402:
1. I haven't done the larger than PHP memory limit test yet. This has a bit more complexity I think. We need to get the memory limit value, then parse it to bytes (could be a value like '512M'), then write a file that's as big as that. Although to pass this into our request for our test, we will need to make a string out of it anyway? Which then means it'll be larger than our memory limit? So this might not actually be possible for us to test? Unless we can use Guzzle stream to achieve this within the test framework. Instead/additionally we could also test setting a size limit on the field and uploading something greater than that? That should be easier to achieve.
2. Added a test for zero byte files. I also realised that File entities in general cannot have a filesize of 0 set automatically due to the code in
\Drupal\file\Entity\File::preSave. So I'll open a separate issue for this, it doesn't really affect us too much here (we set the filesize before anyway, so we can validate the file).3. We check the mime type when we assert the expected response data and all the other fields. Not sure it's worth us checking the file too. Do you? Related to that, the MIME type for a zero byte file should be the same as one with data, as we decided you MUST provide a file name.
4. Added a new test method that correctly fails when trying to upload a json file (The field is configured to only allow txt files). We already make a request with the test $format as the content type.
5. Same as #3. I don't think we need to assert the actual file fields too, as the serialized data will have the values from the file. Any other advantages to trying to test the entity values directly here?
Comment #410
damiankloip commentedAh, forgot we had the Bytes class - that would make any conversion to bytes much easier when working out the memory limit :)
Anyway, I also realised that we could have orphaned files that have been moved into the file storage location as the file was moved as part of the streaming method, we ideally need to validate the file entity first (based on the temp file size etc..) THEN move it. The additional testFileUploadLargerFileSize test covers this case. Before my change the last assertion, checking the file doesn't exist was failing, as the file was written/moved, then validation failed.
Comment #411
damiankloip commentedCreated #2901998: File size of 0 is not set when file entities are saved as a small related issue.
Comment #413
damiankloip commentedComment #414
blainelang commentedI've built an ember client to test out how best to handle file uploads and have it successfully uploading files and attaching to a content node. The app is uploading a batch of 5+ files at once and attaching them to a node is working. Moved on to testing large file uploads and hit the a few limits and wanted to share:
First tested with a 100MB file and got a HTTP 500 error which was due to an apache setting. Updated it to 768000000 and restarted apache:
mod_fcgid: HTTP request length 134225728 (so far) exceeds MaxRequestLen (134217728), referer: http://localhost:4200/uploadNext were two a PHP setting for post_max_size and upload_max_size that were restricting the uploads to 128M. After increasing them to 768M, I was able to upload files of 750Meg and I suspect larger if I kept kept increasing my local dev env settings - very nice!
Note that even when I was getting the PHP error about max 128M, it was actually uploaded and appeared in the admin/content/files area.
Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException: Unprocessable Entity: validation failed. field_media_test_file.0: The file is 410.93 MB exceeding the maximum file size of 128 MB. in Drupal\rest\Plugin\rest\resource\EntityResource->validate() (line 43 of /Users/blaine/Sites/devdesktop/d8site85/core/modules/rest/src/Plugin/rest/resource/EntityResourceValidationTrait.php)Comment #415
damiankloip commentedThanks for testing! Did you see this issue with the latest patch in #410 too? That makes sure the file is validated, then moved after. It behaved a bit differently in previous patches.
I wonder if you can stream the request body from your JS client? Never tried that before, personally. That would hopefully mean you don't need a larger request body and max upload limit - but maybe not.
Comment #416
blainelang commentedI was using the patch from #400 but restored /core and then re-applied #410 to test. Reset the PHP.ini settings back and re-tested with the Ember App to get a 422 return code and message:
"Unprocessable Entity: file validation failed.↵The file is <em class="placeholder">341.54 MB</em> exceeding the maximum file size of <em class="placeholder">256 MB</em>."So #410 is returning a 422 status code and handling the PHP Error as well, do not see the file under /admin/content/files now.
Also wanted to test if the patch was checking the field setting. If I change the field settings to 128 MB and try a file > 128MB but < 256 MB then it will upload ok.
Not sure what you mean. In my Ember App, I'm using the ember-file-upload addon which has a uploadBinary method.
Comment #417
blainelang commentedScanned through the related 8.4x and 8.5x issues and couldn't find an answer to this question - Is there a REST API endpoint to get a list of files?
The reason is that when an app attempts to upload a file with the same name as an existing file, the API returns a 422 with the URI of the file name conflict but not the file ID. The APP may still want to attach that file but how as we need the UUID and FID.
What if we returned the fid and uuid as part of the 422 response? Else I figure, we need a way to query for a matching file name.
Comment #418
blainelang commentedI used views to create a REST Export with an exposed filter on the URI field This should work as I was able to use postman to make a request passing in the URI.
Comment #419
blainelang commentedI think the current behaviour for when you upload a file that already exists should be changed to be the same as default Drupal. From the UI, I can attach the same image multiple times in the same node/add or node/edit operation and the file names and public URI values will be made unique automatically.
Shouldn't the REST API do the same?
Comment #420
garphyI just tested the patch in #410 without much luck :/
I always get a 422 Unprocessable Entity with the following body :
This is really strange because the $values array passed to File::create() contains a correct value for "uri" :
Comment #421
blainelang commented@garphy, I was getting this error as well if the JSONAPI module was enabled so I think there is an issue here that needs to be resolved.
Comment #422
garphy@blainelang : you're right. After uninstalling jsonapi, it worked.
But get rid of jsonapi is not an option right now :) I'll try to dig deeper into that.
Comment #423
garphyWith jsonapi module enabled, if I comment out the function
jsonapi_entity_base_field_info()entirely, I can upload a file.I think it's related to #2825487: Fix normalization of File entities: file entities should expose the file URL as a computed property on the 'uri' base field. This has been "resolved" in jsonapi by adding a "url" computed property to every File entities.
I don't get why it messes with the
urifield, though.Should we open a related issue in
jsonapiqueue ?Comment #424
garphyWell... it seems to be a bug in JSONAPI.
Basically, the "url" property it defines has a value of type UriItem which is actually the path of the file relative to the baseUrl. So it's not a valid URI, so it doesn't validate.
Side note : I should have been paying more attention to the error message which indeed related to url, not uri.
I'm going to open an issue there.
Comment #425
wim leersReviewed + rerolled #2901704: Allow REST routes to use different request formats and content type formats. Thanks for splitting that off: it makes sense, and keeps the scope here tighter. Now reviewing this issue!
Comment #426
wim leers#408:
This follow-up never happened, in part because there was no
@todo…Ironically, you're the one who added this in the first place, at #1988426-25: Support deserialization of file entities in HAL 😀
The goal AFAICT was always to add sensible properties to access the full URL…
Conclusion: we should do that here, otherwise we'll break BC later.
Interdiff review:
Yes.
\Drupal\rest\ResourceResponseInterfaceThese all look like sensible helper methods. It really is something that also just helps make
RequestHandler::handle()become more understandable.Therefore it's even something we can split off to its own issue, to again make the scope of this issue smaller :)
#409:
getExpectedNormalizedEntity()'s expected response data.Interdiff review:
Please use
\Drupal\Tests\rest\Functional\ResourceTestBase::assertResourceErrorResponse()instead, then we also get test coverage for the message that is returned, ensuring good DX.#410: NICE! 🙏
Interdiff review:
Nit: these comments look strange.
#414: @blainelang wow, you are rocking so much! First #403, then #2901574: Requests to log in (cookie auth) via /user/login?_format=json result in 403 without helpful message, now #414. Thank you so much! ❤️
This tells us we need more tests than I thought — we'll need them for:
post_max_size" (http://php.net/manual/en/ini.core.php#ini.post-max-size)post_max_sizebut smaller than the memory limitupload_max_filesize" (http://php.net/manual/en/ini.core.php#ini.upload-max-filesize)upload_max_filesizebut smaller than the memory limitThoughts, @damiankloip?
#417 + #418: No, you can't get a list of any entity type via REST until you create a View that lists them and add a "REST Export" display to the view. Via JSON API, you can get a list of all file entities (or entities of any other type) without needing to create a view. This is one of the big advantages of JSON API!
#420 + #421 + #422 + #423 + #424: yes, this is definitely a bug in the JSON API module. It's a temporary work-around:
Comment #427
blainelang commented@win-leers, very thorough review and followup. If you get a min, can you add your thoughts regarding #419 and how REST API should handle duplicate files as you can't do that presently via a PATCH method.
Comment #428
garphyHere's an attempt to deal with the duplicate filename situation by reusing file_unmanaged_prepare().
Comment #429
damiankloip commented@garphy (@blainelang), I think
file_unmanaged_movealready does what your patch in #428 adds.file_unmanaged_prepareis already called from there, and FILE_EXISTS_RENAME is the default.So this will handle duplicates by creating a new file with the new name. If we decide we want to return a 4xx response in this case then so be it. I think the current behaviour makes sense though. That matches how Drupal works normally when uploading files via a UI widget. Or does something fail before that in validation or something?
Comment #430
garphy@damiankloip, the problem is that file_unmanaged_move() is called after validate().
Maybe when could reverse it ?
Whatever solution we choose, it's definitely better to rename the filename on disk (and keep original filename in database) as it happens when uploading from UI. Otherwise it's a pretty bad DX in my opinion.
Comment #431
wim leersIt sounds like we definitely also want test coverage for auto-number-suffix-files-on-duplicate-filename.
Comment #432
damiankloip commented#430: Yes - I thought that might be the case... so we can slightly expand on your changes and FILE_EXISTS_ERROR when we actually move the file, and handle the creation of the unique file URI/location in the call you added. Seems like a good way to avoid duplication, and some oddities if a file was then renamed again after it was saved.
#431: Agreed, here is that coverage too.
I will look at the EPIC review from #426 a bit later :D
Comment #434
damiankloip commentedMissed a spot.
Comment #436
damiankloip commentedComment #437
wim leers:)
Review of #433+#434+#436:
Nit: s/duplicate file/duplicate file name/
Because it could very well be different files with the same name (for example
interdiff.txthere on d.o).s/actual/expected/
Comment #438
damiankloip commentedThis patch does all the other misc doc nits (I think), and the usage of
assertResourceErrorResponse.I would like to do this in theory, I think the trouble we will have in reality is trying to achieve this in the testing framework? I don't think we can stream data from a file in a request from within the test. I guess we could try a test for max_post_size, but this could also become very fragile depending on the memory limit in the php environment running the test... :/ I do however agree with one of your previous comments that we could land this without that, with manual testing that files larger than the php memory limit can be uploaded. We can also make sure the other cases listed above work with manual testing too.
RE the rest of #426;
Are you saying we should try and make sure we have the computed property first?
Exactly, this basically just tests file denormalization now. Which the FileEntityNormalizer doesn't even test anymore! :) So it doesn't do a great deal IMO. Depending on what we do with the file URL field, this test verging on useless. I guess it doesn't hurt to keep it though. OR we bring back the old functionality too. I think that is inherently insecure (?) though anyway, as it tries to make an HTTP request out to get a file and download it.
I think we're getting relatively close here :)
Comment #439
garphyI'm wondering if we're not risking a race condition with the latest patch approach of file name conflict handling.
If another concurrent request, with the exact same filename starts to be processed after the first one has performed the file_unmanaged_prepare() but before the first request has a chance to perform file_unmanaged_move(). This maybe very unlikely but not impossible.
Maybe we should do file_unmanaged_move() before $this->validate() (and catch any exception to delete the file) ?
Comment #440
dawehnerHere is just my little review ...
I was reading for a second about the content disposition header, see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Dispos...
What is interesting that "filename" and "filename*" are allowed header values:
Maybe we should support unicode filenames and have some form of test for it?
🔧 Is there a reason we need to typehint for both?
🔧 Should we document the exceptions which can be thrown?
... So I'm wondering whether we should have some kind of protection against uploading the same filename twice at the same time ... this would cause triggerable 500's, which might bring down reverse proxies ...
This is a tough question given that
\Drupal\rest\Plugin\rest\resource\EntityResource::postreturns the path to the file entity itself ...We don't take into account the possible return FALSE of fwrite ... we could check for it and in that case throw an exception or so?
🙃 Unneeded changes ...
Comment #441
damiankloip commentedNice review, as always!
So...
1. We talked about this briefly, it's a great find. I think we should keep our implementation as-is though, and just accept 'filename' in the header value. 'filename*' is kind of weird anyway. I'm not sure we need a distinction between accepting unicode and not. We are at the mercy of the file system, and what our file validation likes anyway. Just to keep this implementation slightly simpler. Also add test case for stripped file, and use basename() to make sure we only ever get a filename to use.
2. Nope. No reason. I blame phpstorm. Removed.
3. Yes. Added @throws for the HttpException
4. Yes, @garphy mentioned similar in #439, I was thinking this yesterday too. I think we should go with a lock on the filename here. This seems like a better approach than writing the file, moving it, then having to clean it up on all validation failures. Added a simple lock.
5. The location header... we still need a resolution on this :)
6. Yes, good idea. We should check the read and the write. Added checks for both, which throw exceptions, log and close the file handle - just to make sure.
7. Fine - reverted ;)
Comment #442
wim leers#438:
That interdiff looks like a good step forward :)
True :( I agree with your assessment that it's more realistic to do manual testing of this bit.
Yes.
Right… which means it's probably indeed safe to delete.
Agreed! 😀
#439: Wouldn't the same problem exist in the form-based file uploading today then? In fact, that would totally explain why 1-5% of the time, uploading
interdiff.txtto d.o fails (as in, AJAX spinner keeps going forever, and I have to reload the page).I'm not too concerned about this, the probability of this happening is low enough that I'm not worried about it for now, and fixing it won't result in API changes. Great critical thinking though :) I'd be fine with addressing this for both REST file uploads and form file uploads in a follow-up issue.
#440:
Fileentity here, we're just dealing with a file. So I think this makes sense?#441:
foo*instead offooimplies opting in to use the "extended annotation". In the extended annotation, you can specify any encoding.HOWEVER, it also says: , and https://en.wikipedia.org/wiki/ISO/IEC_8859-1 only allows 8-bit latin characters.
That would mean emoji and even em-dashes in filenames would be a problem. Perhaps we can get by with just
filename(and notfilename*) and just pass unicode characters, but it'd mean violating the HTTP spec. Which also means that if your request has to pass proxies or middlewares, that the filename you passed could be stripped away, because it's technically invalid HTTP.I think we definitely want a test with a filename that contains unicode. (e.g. Damian's favorite emoji)
Interdiff review:
This looks like a nice solution.
But I think this should be a 503, with a
Retry-Afterheader. Because retrying in a few seconds likely would succeed. This then clearly communicates that the request itself is not at fault, it'll work fine a few seconds later.Nice addition!
Comment #443
dawehnerI think one main difference is that by exposing a rest resource I think more people are willing to look into it ... I think making it easy to cause 500s is a problem we should avoid, by well, thinking about it beforehand. If there is just one less site down, this is totally worth doing!
I think I agree with @Wim Leers we should implement the HTTP spec instead of our own custom solution ... at least though we should accept
filename*.Great idea!
Comment #444
damiankloip commentedOK, here are some changes to allow for using
filename*. I haven't got into any business of trying to encode the string if there is the * present. Not sure we want to be going down that road... So this just adds support for the * but doesn't do anything behaviourally different based on that.Also, changed the response for the locked file to 503 with Retry-After header. I just used 1 second for the value - seems to be any value is fine in practice.
I will do some cleanup on tests in a bit, and add some of the test cases for the filename* header too.
Comment #445
dawehnerI could imagine that we could do the encoding bit maybe in a follow up?
Comment #447
damiankloip commentedRE the computed file URL property, we already have #2825487: Fix normalization of File entities: file entities should expose the file URL as a computed property on the 'uri' base field which is currently marked postponed. So I guess we need to progress there. Not sure if we should hold up this issue on that though. I guess we need to have the location of the newly uploaded file available :)
This patch:
Comment #448
tedbowSorry if I missed this in the previous comments but what if the file is referenced in field that is attached to an entity type other than nodes. It seems from previous comments that adding a permission for this is not likely and would be confusing.
Shouldn't we be checking if the user has an admin permission for any of the entity types that file is attached to?
Also it seems like if the user can delete all the entities that reference the file that the user should also be able to delete the file. I imagine there will be many situations that the file is only attached to 1 entity.
Also if the user can update 1 entity that references the file shouldn't that user be able to update the file.
This patch adds \Drupal\file\FileAccessControlHandler::getOperationAccessOnReference which tries to implement this logic.
Comment #450
wim leers#444: You've proven that uploading filenames with unicode filenames work, at least when there's no proxy/middleware in between. That's important. It is technically speaking a violation of the HTTP spec though… still not entirely sure how I feel about that. Why do you feel okay/safe doing this?
I don't see explicit test coverage for#447 added test coverage.filename*yet though.#447: Perhaps it's better to detect the
filename*case and throw an appropriate HTTP error response? Becausefilename*would have that special prefix to specify the used encoding, which this patch wouldn't handle. Better to fail explicitly then. Not sure which HTTP status is the right one. I first thought https://httpstatuses.com/501, but that's apparently for the HTTP method, not for some request header.RE: computed file URL property. Ugh yes… if we require that we have a computed file URL property before committing this, then this is blocked on #2825487: Fix normalization of File entities: file entities should expose the file URL as a computed property on the 'uri' base field, which is blocked on #2871591: Allow ComplexData in TypedData to specify computed properties that should be exposed in normalization and other contexts, which is in turn blocked on
But … if we don't block this on that issue, then we'll be shipping with REST file upload support of which the responses will change:
Committing this without that will mean a guaranteed BC break, will it not?
P.S.: It'd be really good to get a review from you of #2871591: Allow ComplexData in TypedData to specify computed properties that should be exposed in normalization and other contexts!
#448: Shouldn't that be handled in a separate issue? Otherwise scope becomes very big here. Should this issue be blocked on that? This is definitely related to #2310307: File needs CRUD permissions to make REST work on entity/file/{id} and #2843139: EntityResource: Provide comprehensive test coverage for File entity, and tighten access control handler. Perhaps it should be done in the latter?
Comment #451
damiankloip commentedYes, it seems like #448 should be done in a separate issue. This issue is big enough, complex enough, and long enough :) However, we do modify the access check here to check for 'administer nodes' here, which is not ideal. So maybe we need to break that out, have the smaller issue, and implement Ted's fixes there (they look sensible). I would say yes to Wim's question in #450 - we should probably block this on doing that AND the typed data normalization/export properties issue....
Comment #452
wim leersOkay so this patch is tantalizingly close to RTBC.
But it seems we're agreeing it's now blocked on two things:
Comment #453
wim leersWhat can still be done here, is the
filename*stuff in #450.Comment #454
aheimlichShouldn't this be doing to same file name munging that
file_save_uploaddoes?Comment #455
tedbow#452.1
I would love to see those issues get committed but does it need to postpone this 1. Is it BC break if we add extra fields to a REST response but don't change existing fields? We added extra fields here #2060677: Add target_type, target_uuid to serialized output of entity reference fields in non-HAL formats to existing responses and will likely add them here #2825812: ImageItem should have an "derivatives" computed property, to expose all image style URLs so it seems like we have do similar things before.
Comment #456
wim leers#2901998: File size of 0 is not set when file entities are saved landed, hurray! That was another blocker I should've listed in #452, but forgot. So we're staying at PP-2.
I discussed #455 with @tedbow in chat:
So now we have #2907402: HAL normalization of file fields don't provide file entity id or file entity REST URL … which is basically confirming what I wrote at the top of #426 as being a problem. Let's fix that in #2907402: HAL normalization of file fields don't provide file entity id or file entity REST URL.
Comment #457
damiankloip commented@aheimlich has a good point in #454 I think. Nice work! I've implemented this in a new patch. As well as throwing an exception when the filename* key is used. Leaving this here for now until we iron out the blocking dependencies.
Rebased too, looks like #2720233 conflicted with this patch, in the RequestHandler class. This should be working ok, we might want to see if we can further refactor the request handler a little though, it might be able to have another method to reuse the argument handling and warning that's triggered.
This interdiff is from #447. as I think we agreed @todbow's work was moving to the other issue?
Comment #458
tedbowI would say we remove 'administer nodes' permission this issue. Won't this be security violation if the file was attached to another entity type that use doesn't have permission to administer,delete or edit.
Yes I think this issue #2843139: EntityResource: Provide comprehensive test coverage for File entity, and tighten access control handler but still think we should remove the above permission for now and add a todo pointing to #2843139
This copied from
file_save_upload, correct?Should we somehow consolidate this code? at least the insecure file extensions?
Otherwise if we ever need to add another insecure file extension we have to remember to add it to both places.
Comment #459
wim leersFileAccessControlHandlerchange shouldn't happen here. I did some archeology: it was added in #1927648-197: Allow creation of file entities from binary data via REST requests by @marthinal. What happens if we just delete this? The test coverage has improved a lot since then (~1.5 year ago), so perhaps it's no longer necessary?Nit: s/AN/An/
This could benefit from an
@see file_save_upload()I think?(Basically echoing the second half of #458.)
Yay for explicit test coverage.
Comment #460
damiankloip commentedYes, I was planning to just remove most/all of that stuff but figured we might as well leave this as a passing patch until we can remove it and still have it passing.
#458: Yes, I think moving the insecure file extensions for another function or a constant is a good plan!
#459 We should see if we can just remove this now. I would have to look properly to be sure :)
Comment #461
wim leers#2901704: Allow REST routes to use different request formats and content type formats landed! But #2901704 was not reflected in the "PP-2" (see #452), so we remain at that level.
Comment #462
damiankloip commentedHere is a patch that rebases against 8.5.x (so #2901704: Allow REST routes to use different request formats and content type formats changes that we already got committed can be removed from this patch) and removes the changes in FileAccessControlHandler. Let's see how the tests get on!
Comment #463
wim leersThat passed! Which makes sense — we're
POSTing a File here, the changes toFileAccessControlHandlerwere only forPATCHandDELETE. Hurray, we were right in #458+#459 that the access handling improvement/fix doesn't need to block this!Which means we're down to 1 blocker: #2825487: Fix normalization of File entities: file entities should expose the file URL as a computed property on the 'uri' base field.
Comment #464
damiankloip commentedWhoooooop! yes, indeed :) Hopefully..
Comment #465
wim leersSo this is down to 1 blocker (#2825487: Fix normalization of File entities: file entities should expose the file URL as a computed property on the 'uri' base field), but that in turn is also blocked, on #2871591: Allow ComplexData in TypedData to specify computed properties that should be exposed in normalization and other contexts. So updating to PP-2.
Comment #466
wim leersBoth #2871591: Allow ComplexData in TypedData to specify computed properties that should be exposed in normalization and other contexts and its follow-up #2910211: Allow computed exposed properties in ComplexData to support cacheability. that was split off from #2871591 have been committed! That means the sole blocker is now #2825487: Fix normalization of File entities: file entities should expose the file URL as a computed property on the 'uri' base field.
Comment #467
garphy#462 does not apply cleanly on 8.5.x any more.
Comment #468
garphyRerolled.
Comment #469
webchickUpdating the issue summary to indicate at the top what this is postponed on.
Comment #470
benjy commentedApplying the patch in #448 with 8.4.x breaks one of my tests. Afterwards I have the XML route enabled even though my config looks like this:
I end up with the error
No route found for the specified format html.in a test when running the following code$url = Url::fromUri('internal:/api/forum/subscriptions/' . $this->question->id()This is because AcceptHeaderMatcher::filter can no longer detect the correct default format since there are multiple routes enabled.
This is the hunk of code that was removed that causes the issue. Brining that back fixes my issue.
Comment #471
wim leers#470: any chance you wanted to post this to #2858482: Simplify REST routing: disallow requesting POST/PATCH in any format, make consistent, i.e. that you mixed up two issues?
Comment #472
benjy commentedI wasn't actually aware of the other issue, looks like the patch there was merged into the patch in #448
Comment #473
wim leers@benjy: can you move your question over to #2858482: Simplify REST routing: disallow requesting POST/PATCH in any format, make consistent, if you can reproduce it with just that patch? That's where those changes are actively being worked on. Plus, this issue is approaching 500 comments, so keeping this on-topic as much as possible can only help :) Thanks!
Comment #474
wim leersOMG YAY #2825487: Fix normalization of File entities: file entities should expose the file URL as a computed property on the 'uri' base field is in, so this is now UNBLOCKED! 🎉
@damiankloip, the floor is yours :)
Comment #475
wim leersI just talked to @damiankloip, he will get to this soon. Maybe tomorrow, otherwise in the first days of the next year :)
In the mean time, here's a rebased patch already — several conflicts had to be resolved.
Also includes a tiny interdiff to add a placeholder
implementation, which is required since #2765959: Make 4xx REST responses cacheable by (Dynamic) Page Cache + comprehensive cacheability test coverage (which landed on November 30, quite a bit after this patch was last green). This allows the tests to at least run (but fail), and not cause a fatal PHP error.Comment #476
wim leers#2825487: Fix normalization of File entities: file entities should expose the file URL as a computed property on the 'uri' base field added a new
urlnon-internal computed property. This shows up in the normalization. Updating the expectations with a trivial one-line addition to account for this allows all non-HAL tests to pass.I'll leave updating HAL tests for @damiankloip. The current tests assume the BC layer behavior. Does it make sense to test the BC layer here if we already have
\Drupal\Tests\hal\Functional\EntityResource\File\FileHalJsonAnonTest::testGetBcUriField()for that? I'm not sure, but I personally lean towards .Now Damian can focus on the truly interesting bits: I handled the trivial stuff in this comment/patch and the previous one!
Comment #479
damiankloip commentedNice work Wim!
I agree, we shouldn't need to test the BC case here, as we already have explicit coverage for that. As long as we know this returns us a good response in the current normalization format, I think we're good.
Here is a patch to remove the uri override for hal tests. The default behaviour should now give us the correct normalization result I think. I also added a comment for getExpectedUnauthorizedAccessCacheability.
Comment #481
wim leersI wrote in #452 that this was tantalizingly close to RTBC, on September 6.
Time for another round of critical, detailed review!
Nit: Missing trailing period.
Nit: needs documentation lines.
Incorrect typehint.
Nit: extraneous empty line.
Let's use
setFilename(),setMimeType()etc.Nit: s/entity level/entity-level/, s/field level/field-level/
We need to address this
@todostill.Nit: s/windows/Windows/
Missing comments explaining *when* these ezxceptions are thrown.
Missing accompanyhing comment.
Another todo that needs to be resolved.
Nit: s/AN/An/
We "plain text ify" the violations (which can contain HTML), just like
\Drupal\rest\Plugin\rest\resource\EntityResourceValidationTrait::validate()already does.Nit: let's use
RequestHandler::class.Nit: s/THe/The/
Nit:
The HAL normalization adds entity reference fields to '_links' and '_embedded'.Nit: unnecessary change, can be reverted.
The changes here appear to revert #2901704: Allow REST routes to use different request formats and content type formats?
FYI: this is going to conflict with #2853460: Simplify RequestHandler: make it no longer ContainerAware, split up ::handle(), but it's definitely going to mean fewer changes are necessary here :)
We should test that a 403 response is sent when doing a request before authorization is granted!
Comment #482
damiankloip commentedok, this should fix the hal normalization issues, and address some other points me and Wim spoke though:
- Remove Location header setting. We can't do anything meaningful with this right now, and since we return the normalized file entity in the response now, we have all the info available to us currently. This might make sense to add at a later stage WHEN we have a file link template/resource we can reliably generate a link for.
- Implements getExpectedUnauthorizedAccessMessage
- Add comments for null implementations of assertNormalizationEdgeCases (and getExpectedUnauthorizedAccessCacheability in previous patch)
- Check file target type in FileUploadResource::validateAndLoadFieldDefinition
- Add 403 check in testPost and use getExpectedUnauthorizedAccessMessage
- Reverts changes reverted in error to ResourceResponseSubscriber (tests pass locally)
Let's see how this gets on with tests.
edit - x-post with Wim!
Comment #484
wim leers#482: reviewed this interdiff too:
No need for
sprintf():)FileUploadResourceinherits\Drupal\rest\Plugin\ResourceBase::permissions(). But just likeEntityResource::permissions()overrides it parent based on thebc_entity_resource_permissionsconfig flag, we want to override it. But without any BC layer, because file uploading was never supported, so no need for BC. So, basically, I think we should just add this toFileUploadResource:Let's add a
@todopointing to #2907402: HAL normalization of file fields don't provide file entity id or file entity REST URL.Comment #485
damiankloip commentedGreat, Let me address that soon! duh on on sprintf :) just forgot to remove it when I took the placeholders out of the original string I copied from.
Comment #486
damiankloip commentedok.. This adresses all of the feedback now (I think) except the last #481.20. I am just out of time today and need to head off. I have left a "// @todo FOR WIM." comment in there. Aside from that, I think this should pass now.
Comment #487
wim leersLet's add some
@sees here.s/Throws/Thrown/
Incorrect indentation.
Done :) (In hopes of this still shipping with 8.5.0.)
The reason this didn't work is due to one of those weird Field API design leftovers from before it even was in core: there is no
createoperation, only aeditoperation, which is for both the "create" and "update" cases!However, when I fix that, field access is still allowed… because we should also have been checking entity access.
Let's go back to where this approach was introduced. It's based on
file_entitymaintainer @Berdir's recommendations in #1927648-326: Allow creation of file entities from binary data via REST requests (from May 9, 2017, precisely 8 months ago today!), where he wrote this:Everybody agreed, but everybody overlooked the detail that the patch was only checking field access and not also entity access. Because there were other problems that had to be dealt with to get the patch to work at all. And so we collectively lost sight of this. Until now, because everything's working fine, and access checking was the last thing that didn't yet have test coverage!
(@damiankloip even mentioned this in #1927648-359: Allow creation of file entities from binary data via REST requests and #1927648-362: Allow creation of file entities from binary data via REST requests: + .)
Fixed now!
Comment #488
wim leersSo, time for a final review!
This looks weird maybe, but it's how it's done. Identical to the logic in
\Drupal\hal\HalServiceProvider::alter().This should probably document why we went with this approach. We should summarize @Berdir's comment in #326 for that.
(We'll also need that for the change record.)
Perhaps rename this to
REQUEST_HEADER_FILENAME_REGEX?Nit: extraneous
\n.Should use the setters where possible. (Already mentioned in #481.5.)
Nit: I introduced an extraneous
\nhere in my previous reroll, sorry.#2858482: Simplify REST routing: disallow requesting POST/PATCH in any format, make consistent has been RTBC for a while and will change this.
I think it's better to incorporate the bits from #2858482: Simplify REST routing: disallow requesting POST/PATCH in any format, make consistent that will land there, so that we know it actually will work.
In doing so, we can remove the allowing of all formats in
FileUploadResource::getBaseRouteRequirements()!Indentation is off here.
More whitespace clean-up necessary.
Unnecessary comment-only changes. Let's revert these.
That patch already is RTBC, we should clean it up here. Most importantly, we need to document the rationale behind this logic.
Also see the previous point: we should be compatible with what #2858482: Simplify REST routing: disallow requesting POST/PATCH in any format, make consistent does.
More whitespace clean-up necessary here.
Comment #489
wim leersThis fixes all whitespace/docs nitpicks (4+6+8+9+10+12).
Comment #490
wim leersAnd this fixes points 7+11 (relating to REST routing, and #2858482: Simplify REST routing: disallow requesting POST/PATCH in any format, make consistent).
Comment #491
wim leersSo that leaves just points 2 + 3 + 5 from #488 to be addressed. Leaving that for @damiankloip.
Really tempted to RTBC this already. Because 2+3+5 really are just nits. But I'm pretty sure committers will raise those too. And for committers to even look at this issue, we definitely need an issue summary at the very least, and most likely a CR to announce this new, long-sought after capability!
(This issue currently has 418 comments, which is the second most of all open issues for Drupal core — only #1356276: Allow profiles to define a base/parent profile has more: 424.)
(This issue also likely has one of the highest numbers of followers: 127!)
Keeping at , because this definitely could use reviews.
Comment #493
tedbowreviewing #489 b/c #490 failed but the interdiff is small.
couldn't figure out why #490 failed
Been a while since I dealt with reading file streams in PHP but I wondering... If we have no exceptions here at the end we call
fclose($file_data)to close the input stream but if encounter 1 of the 3 possible exceptions we don't. We only callfclose($temp_file).Should call
fclose($file_data)in the exception cases?To be extra careful should we:
$this->assertFalse(file_exists('public://foobar/example.php.txt'));Just to be safe why don't we again check if the file actually exists?
Check for file existing also?
Comment #494
berdirYes, this is certainly better than the previous create access check which doesn't exist on field-level.
The bundle usage is not quite correct yet, however. While a field is always added to a "bundle", in case of a non-bundleable entity type like user, the bundle is the same as the entity type.
In that case, it is however not correct to pass that as the bundle to the createAccess() call. I can't imagine it breaking anything, if there is no bundle, then nobody should rely on it being or not being there, but who knows.
So the currect thing would be to do something like also getting the entity_type and then doing $entity_type->hasKey('bundle') ? $bundle : NULL as the argument to createAccess()
I didn't even remember we had this stuff.
Isn't just removing it kind of a BC break even if there is now a better way of doing this? That means it should be deprecated now, not removed? I guess it's tied to the now deprecated opposite feature in normalize(), can we hide it between the same config check? Or alternatively, just look if we have an actual external URL on this property that we could download instead of unconditionally downloading it? then we could do that and trigger a deprecation message? Always doing that seems weird anyway.
Maybe I missed it, but I think we now don't have an actual test that tests the whole process, might make sense to have that?
Meaning:
1. Upload a file
2. Create an EntityTest or other entity that references that file, with a custom description or so, make sure everything is properly saved. ( Wondering if file description/alt/title currently works at all.. I currently have a custom normalizer in file_entity to support that)
If we have a follow-up for that, also fine. And it should then probably also be documented somewhere?
And the same could also be done for the media scenario:
1. Upload a file
2. Create a media entity that uses that file
3. Create an EntityTest/Something entity that uses the media. Make sure everyhting is created properly.
what would be the reason someone provides a path and not just a filename? Is there a reason we silently ignore that instead of returning an error?
Note: This will/would change if we do #2492171: Provide options to sanitize filenames (transliterate, lowercase, replace whitespace, etc). Nothing wrong with it, just pointing out that we'll need to update this test then in that issue.
Comment #495
berdirCrap, I didn't want to change the issue title/tags.
Comment #496
wim leers#493: Thanks for the review, @tedbow! Thanks for helping us to be even more cautious! :) Leaving those for @damiankloip to address.
#494: Thanks for the review, @Berdir!
FileEntityNormalizerand summarize the relevant bits here:FileEntityNormalizerwas always meant to be temporary … see #426.1EntityTestentity that actually uses this file.For the
Mediause case, I think that's in scope for #2895573: Decide on normalization of Media module entities. We should add a@todofor that.Comment #497
wim leersWorking on addressing #490 failing tests and addressing most of @Berdir's comment.
Comment #498
wim leersFixed #496.1 + #496.3 + #496.4. All points in #496 have been addressed. But #496.2 needs further discussion.
Also, for #496.3: test coverage for
hal_jsonis currently impossible because of a massive bug in the HAL module. Which further stresses the fact that nobody can really be using HAL'sFileEntityNormalizer::denormalize(), because it's literally impossible to use those files in a@FieldType=filefield! 😮 Created #2935738: Entity reference field subclasses (such as file and image fields) lose the non-default properties upon denormalization, in both hal_json and json for that.(This is relative to #489, still need to figure out why exactly #490 was failing.)
Comment #499
wim leersComment #500
wim leersRegarding #496.2: I don't think we should worry about breaking BC for
FileEntityNormalizer::denormalize()if A) it was always meant to be temporary, B) it has questionable security, C) any file uploaded through it will always betemporary://, D) if you try to reference files (FileItem) or images (ImageItem) uploaded via it, you can't: #2935738: Entity reference field subclasses (such as file and image fields) lose the non-default properties upon denormalization, in both hal_json and json.Is there actually anything that uses
FileEntityNormalizer::denormalize()? Because even https://www.drupal.org/project/default_content saysIn other words: it doesn't use/support the
FileEntityNormalizerin core's HAL module.Comment #501
wim leersSo regarding #490: let's do this properly. Let's rebase this patch on top of both #2858482: Simplify REST routing: disallow requesting POST/PATCH in any format, make consistent (which refactors
\Drupal\rest\Routing\ResourceRoutesinto sanity) and #2853460: Simplify RequestHandler: make it no longer ContainerAware, split up ::handle() (which refactors\Drupal\rest\RequestHandlerinto sanity).That'll make this patch A) simpler, B) more future-proof (because those patches are likely to land very soon, before this). Because, frankly, this patch is hard enough to review and commit without the extensive refactoring of
RequestHandlerand significant changes toResourceRoutes. Letting other issues handle those will make this issue's scope more manageable (and will also make the long-overdue issue summary update simpler).(Actually, #2853460: Simplify RequestHandler: make it no longer ContainerAware, split up ::handle() landed while I was working on this, hurray!)
This resulted in
1927648-501-do-not-test.patch— that's #498 rebased on top of both #2853460: Simplify RequestHandler: make it no longer ContainerAware, split up ::handle() (which already landed) and #2858482: Simplify REST routing: disallow requesting POST/PATCH in any format, make consistent (which is RTBC).To allow for testing this patch today, I have attached
1927648-501-combined.patch, which includes #2858482.Comment #502
wim leersAnd now re-applying #490's interdiff again actually works fine, because we're building this on top of #2858482: Simplify REST routing: disallow requesting POST/PATCH in any format, make consistent, which makes the code in HEAD much saner.
Comment #503
wim leersFixed coding standards violations.
Back to @damiankloip so he can continue in the morning: review my changes, and address #493 (the changes I made so far were all REST-related, #493 is all remarks related to file management, which @damiankloip is an expert in by now :P)
Comment #504
wim leersOne last CS fix.
Comment #505
wim leers… wow … d.o just discarded the files I attached. That is … interesting.
Comment #506
damiankloip commented@WimLeers, the patch in #505 doesn't apply for me. Can you rebase your local branch that this came from and upload a rerolled patch? Pretty please :D
Comment #507
damiankloip commentedThis should address the remaining feedback from Wim's review in #488 and Ted's review in #494 (some good points). Wim addressed all of Berdir's feedback, except #494.2. To reiterate what I mentioned previously on that; I am still in favour of removing this, as:
- The saving of the file to a temp location anyway is not really usable by the file entity properly
- No file validation at all. This means essentially any file of any type can be saved here. Security risk #1
- HTTP request is made to retrieve said file, this is Security risk #2 (and probably a good DDOS attack vector too)
Comment #508
wim leersThis alone is reason enough IMO to drop HAL's
FileEntityNormalizer::denormalize().I think all feedback has been addressed. I've updated the IS (which was last updated on June 6 in #376 by Damian!) and created a CR. To update the IS, I re-read the entire issue (well, starting at #281, which is when @damiankloip started working on it thanks to Acquia — my employer — sponsoring him, and started the long process of Doing This Right).
Not RTBC'ing, because this definitely needs to be reviewed by Berdir!
Comment #509
wim leers(Oops, forgot to remove .)
While working on the IS in #508, I noticed one bit of test coverage was missing: that uploading the file in the first request results in a
Fileentity withstatus=false(yes, it is!), and then using/referencing the file uploaded in a second request isstatus=true(sadly it isn't).IDK yet where the problem lies, I'd have expected
\Drupal\file\FileUsage\FileUsageBase::add()would have turnedstatus=falseintostatus=true, but it doesn't yet. Interdiff attached that will fail. Not attaching a patch that includes this interdiff, so that this stays at NR, so that it gets reviews!Comment #510
berdirYes it works fine, but the entity static cache is why the test doesn't work. Works fine if you reset the cache with "\Drupal::entityTypeManager()->getStorage('file')->resetCache();" before the second call to File::load().
Comment #511
wim leersD'OH OF COURSE 🤦♂️
Excellent, so that too is working as expected, just like we thought! :)
(The "combined" patch is now includes the latest of #2858482: #2858482-142: Simplify REST routing: disallow requesting POST/PATCH in any format, make consistent.)
Comment #512
wim leersWell, turns out that #2858482-142 is broken. Canceled #511's test.
Redid the combined patch.
Comment #514
wim leers#2858482: Simplify REST routing: disallow requesting POST/PATCH in any format, make consistent also landed. Therefore, no more need to rely on the "combined" patches.
Reuploading #511's "do not review" patch, this time without that suffix.
Comment #516
gabesulliceFirst of all... whoa! What a thread and what a patch! Tremendous props to everyone involved.
My nits feel so "nitty" after looking at the history of this.
I'll start with direct code review. I have a few thoughts at the end not directly related to any particular lines of code.
"from an endpoint"?
Can this be upcasted? If so, it could eliminate the dependency on the entity type manager.
I assume this is just the repeated entity type ID for non-bundled entity types? We should document that here since it's not an optional parameter.
Why not upcast this to a field definition as well? That would let you remove the entity type manager dependency and move field upcasting/validation logic into a reusable thing not tied to file uploads (this could benefit JSON API related/relationship routes).
Maybe it's because you need the entity type? I'm just "thinking out loud" here, I don't feel strongly about it.
What happens when the file already exists? Is this an error? Is it overwritten? If it's overwritten, should the HTTP method be PUT?
Ah, I see here how existing files are handled.
Love the appropriate error code!
Would be good to document this number, maybe even make it a class constant.
s/Disposition header"/Disposition" header/g
Given that the field name is in the request path, shouldn't this throw a 404?
Nice.
Why lock this away in a REST resource plugin?
Should this regex be a class constant?
Should this be a static method?
This seems generally useful enough to not be confined to the REST module. Why not a public static method of FileItem?
Can this be static as well?
Heh, I wish it could be this easy!
Let me just say, I love this pattern for testing different many different permutations.
In general, I think this is super impressive. I had a few thoughts while reviewing this that could help make this more useful to the rest of the API-first initiative (namely JSON API, but I imagine this would benefit GraphQL and others as well).
There was obviously a ton of effort put into creating and validating file uploads in a safe, secure and OO fashion. However, all of that effort is being locked away in protected methods inside the REST resource plugin. Most of this logic into the file module as a service like
file.upload_handler.This would make it much easier to leverage the work done here without forcing other modules to enable REST module "just" to allow file uploads.
The REST resource plugin would still exist, but only insofar as it provides the resource path (
/file/upload/{entity_type}/{bundle}/{field_name}) and loads the appropriate field definition accordingly. Such that the resource plugin simply become a wrapper around something like this:This would be important for JSON API, which has its own URL path structure as well as a concept of aliased field names.
On another note, I didn't read anything in the IS about
multipart/mixedas a Content-Type header. I'm just assuming that was already worked out and determined not to be the right approach here?Comment #517
damiankloip commentedNice! Thanks so much for the review, it's hard to get people to review code thoroughly!
1. Fixed
2. Hmm, we need the entity type manager to do the access control checks too. Otherwise, we need to care about creating the access control handler instance ourselves. We could still upcast the entity type though, could be a nice additional to 404 for entity types that don't exist. However, EntityResource, for example, doesn't do this either. So not sure about adding that pattern to this resource (follow up issue to change all maybe).
3. That's kind of a given for entity types that don't use bundles but makes sense to add it anyway I guess. Fixed.
4. Similar to 2, not sure we do this pattern in other places, we also use the entity type manager to get the access control handler. We also try to enforce that it's a file reference field.
5. We use
FILE_EXISTS_ERRORhere as we only allow posting new files for now.6. Oh, yeah ok :)
7. :)
8. Good call (not sure why I didn't do that originally). Added a
BYTES_TO_READconstant for this.9. Eagle eye! Fixed.
10. Fair point. changed to 404.
11. :)
12. Hmm, this could be moved into a central service/class. See later comments.
13. This was copied from file module. So Ideally we would have the const there. Added a
FILE_INSECURE_EXTENSION_REGEXto file.module. Any better names welcome.14. getUploadLocation uses the token service that's injected ($this->token), so we can't do that.
15. There is already a method on FileItem for this. Our logic is just a mixture of this and file_save_upload. The problem with trying to use it from the file item is that we're adding files independent of the parent file field (and therefore FileItem) so we don't actually have a FileItem to check. Unless we created an instance just to use that. Which feels...meh. Maybe this code could be shared in ANOTHER place? :)
16. Fixed. Made static.
17. Nice
18. Nice
So that just leaves:
1. Should we move some of this logic into a service in the file module that can be shared? Or should this be a follow up? Not sure. The reason I'm thinking follow up is that the file upload via the UI could also use some of this stuff too. E.g. #15
2. No upcasting. Thoughts on that? Wim?
Comment #519
damiankloip commentedWhoops - rebase fail.
Comment #520
wim leersDiscussed this with @damiankloip.
Created a follow-up issue: #2940383: [META] Unify file upload logic of REST and JSON:API
Regarding upcasting: I agree that it makes sense for this new@RestResourceplugin to follow the same patterns that the existing ones use, and to defer that upcasting nicety to a follow-up, where we can then change it also for existing REST resource plugins.(I almost created a follow-up issue, then I saw a flaw in Damian's argument)
@damiankloip wrote in #517.2 that
\Drupal\rest\Plugin\rest\resource\EntityResourcealso doesn't do this. But that plugin doesn't have a$entity_type_idparameter!However … there simply is no entity type paramconverter service in core. Look at
\Drupal\entity_test\Controller\EntityTestController::listEntitiesAlphabetically()for example. Same for field definitions.Introducing such paramconverters is definitely out of scope here. Very few things would be able to use them too.
Comment #521
gabesullice@damiankloip everything looks good to me! And @Wim Leers addressed the only remaining concerns I had.
I believe this still would benefit from a review by @Berdir, so not gonna RTBC quite yet.
Comment #522
wim leersAgreed, a @Berdir-review is what we really want/need here … so … assigning to him.
Comment #523
ayalon commentedI tested the patch together with default_content module. I wanted to export a taxonomy with a file entity attached.
As soon as I export a file I get this error:
I was using drush to export a taxonomy term with default content.
I figured out, that i had to enable the rest module to get it to work.
I can see a file entity exported but it has no binary content. Any hints?
Comment #524
berdir@ayalon: This is not meant to be compatible with default_content, it is about REST/HTTP use cases. For default_content, you need https://www.drupal.org/project/better_normalizers for now, see also https://www.drupal.org/project/default_content/issues/2933777. It is not the goal (anymore) to make this work with default_content. Instead, I think default content should add special support for files instead as suggested in the issue above.
Ha, that's nice of you to assign this to me ;) I don't think I have anything else to add, I believe my review from before has been addressed, the last interdiffs look fine to me as well. So all that's left here I think is probably creating/updating change records for example the removal of that file download thing (I agree with that I think, but we should probably still mention it.. with reasons why we remove it. Someone out there probably was relying on it..) and the other API additions (changes?) mentioned in the issue summary.
Comment #525
wim leers@ayalon: that was fixed in #2931765: Regression: \Drupal\hal\LinkManager\LinkManagerBase implicitly depends on REST module.
@Berdir: oh sorry, I thought I understood from IRC conversations from some week sago that you still wanted to get a chance to review the entire patch in-depth before this could become RTBC.
I can definitely work on the CRs.
The good thing of this landing so early in the Drupal 8.6 cycle is that there will be the longest test period possible, and therefore also the longest timeframe possible for a revert if need be.
Comment #526
wim leersCRs created:
FileEntityNormalizer::denormalize()and whySo …
It's time to get this in at last. 🎉 🍾
Comment #528
webchickWe talked about this on our committer call today, and there was quite a bit of trepidation to commit this to 8.5.x given it's a massive change with many twisty passages and also security implications. Something that would help build confidence is a review about security specifically (e.g. people embedding PHP in images and the like), so tagging for that.
However, there was broad agreement among those in attendance to committing it as early in the 8.6 cycle as possible, so yay!
Comment #529
wim leersI would not expect this to be committed to 8.5.x, I think that's too risky. File handling is always complex, but especially so in Drupal's case, with the many abstraction layers involved (entity references, validators, validation-dictated-by-bundle-settings, and so on).
I am very confident this patch is solid, and that it's been required to meet a much higher bar than the original REST module's code (which is also necessary of course, since D8 is out and stable now). Nevertheless, it's realistic/likely there's at least one edge case somewhere that we didn't think of. But given the amount of edge cases that we are testing, I think this is as far as we should take it before it can safely be committed to the next minor.
So, yes, please only commit this to 8.6.x! That gives it a full minor cycle to be tested and hardened, because 8.5.0 is not even released yet! I'm glad to see that that is also what the committers concluded :)
This uses the exact same validation logic that core already uses … so I disagree this needs security review. But would a security be nice? Yes, absolutely, it's a nice-to-have. Explicit security-related test coverage for "special" edge cases:
FileUploadResourceTestBase::testPostFileUpload()'s testing that a 404 response is sent in case of trying to upload a file for a non-existing file fieldFileUploadResourceTestBase::testFileUploadStrippedFilePath()— inability to upload into specified directories even when they are specifiedFileUploadResourceTestBase::testFileUploadMaliciousExtension()— self-explanatory#519 no longer applies — #2935783: RequestHandler's handle method interacts with the Route object but should be able to just declare arguments also modified
RequestHandler. Fortunately that was an easy conflict to resolve, and in fact it made this patch slightly simpler! :) 0.56 KB smaller :D (No more need for a::loadRestResourceConfigFromRouteMatch()helper method.)Comment #530
wim leersOne thing that we did forget was to scan the existing code base for references to this issue. Because this issue has been around for a long time.
This removes all those
@todos that this issue finally allows us to remove 🎉.Comment #531
wim leersIn looking at the test coverage code again (see next comment), I noticed some bits in the test coverage that could be simplified. (Making the patch slightly smaller again, 0.2 KB this time.)
Comment #532
wim leersAnd finally, even though we added explicit generic test coverage for uploading a file and then using/referencing it (in
\Drupal\Tests\rest\Functional\FileUploadResourceTestBase::testPostFileUpload()), there is one particular concrete use case where this is more essential than for all others:Media.Without file uploads, there literally is no way today (in 8.4 and 8.5) to create
Mediaentities entirely via HTTP APIs. This patch changes that. And in fact,\Drupal\Tests\rest\Functional\EntityResource\Media\MediaResourceTestBase::testPost()was marked as skipped for that very reason!So, this adds concrete test coverage for uploading a file and using that uploaded file in a
Mediaentity that we then create!(In doing so, I found a minor bug in
FileUploadResourcebecause the generic existing test coverage uses a bundle-less entity type.)I'd mark this as NW again, but this is merely repeating the same generic test coverage already in the patch for one particular concrete use case, further proving that this patch is solid, so keeping at RTBC.
Comment #534
wim leersThis fix caused failures in some tests, because the tests were assuming this bug!
This is the other problem in the test coveraget aht I added: this only works for the
jsonformat, we need to make it work for thehal_jsonandxmlformats too. The expected normalization should've lived in a separate method that the HAL test coverage could then decorate to add HAL-specific bits.Mediatest coverage to first upload a file did not first initialize authentication, which is necessary for cookie authentication, which is why all cookie test coverage for media also failed.Fixed all 3 small oversights. Back to RTBC because it's just copy/pasting code and applying the same pattern as all other REST test coverage, there's no original work here that needs extra scrutiny.
Comment #535
larowlanWe're basically doing the same thing three times here with a different log/message. Is there merit in a protected method?
nice
There's a minor information disclosure here, theoretically someone could use this to validate the presence of field names on the site. Not sure it matters though.
We return access denied here - should it be not found as well? Similar comment re information disclosure
Is there a reason why we don't use
$access_result->isAllowed()here, instead of calculating access again?We assume this is always present, but have a logic path where it might not be. Should we be doing
issetcheck here first before accessing the array members. And if so, indicates missing test coverage for when there is no file extension validationIs there a reason why we didn't inject this - we already inject a lot of services.
nit: missing a docblock
Would be nice to add another test here that tested absolute and relative paths, e.g.
../../../etc/shadowand/etc/passwdjust for piece of mind. Could use a data-provider for the various flavours of malicious filenameComment #536
larowlanIn addition the size of the constructor is a bit of a red-flag that perhaps this plugin is doing too many things.
I did some analysis of where each of the dependencies is used in the class as follows
file system
entity type manager
entity field manager
current user
mime type guesser
token
lock
So on that basis I think there is some scope for splitting and refactoring, but that can be a follow up - can we get an issue created for that?
Suggestions for splitting could be something like
Then we'd only inject the new binary file post validation service, the new binary file post service and token services.
Comment #537
berdir@larowlan: We have already one follow-up to extract a generic service, one argument for not doing it here is that we want to review how json_api and graphql would use it exactly to make sure we really have a service that makes sense and can be reused. See #2940383: [META] Unify file upload logic of REST and JSON:API. We also have several issues to refactor the file system functions and put them in services, which might help with cleaning this up as well.
Considering how big this issue already is (and how slow it makes to work here and do something as simple as posting a comment), I think it makes sense to do that in a follow-up. API design is hard and would probably delay this issue quite a bit more.
That said, relying on having the file uid set automatically through a default value sounds like a relatively simple and useful improvement. Could have a follow-up to remove explicitly setting it from existing places.
Comment #538
pwolanin commentedLooks like some minor fixes needed considering the review from larowlan and a couple nits below.
nit: local -> location
Seems problematic here to use the global function instead of the file system passed into the construction? I guess we still have to use a lot of global functions for file handling.
It seems like this should use a different filename from the code above to make sure the test is correct?
Comment #539
larowlan@Berdir from #536 (yikes!)
I agree, should be a follow up, and given we already have one created, nothing to do then
Comment #540
damiankloip commentedNice reviews.
#535:
1. I thought about doing this also, but the function would need at least 4 parameters. So seemed to not be much tidier in the end?
3 + 4. Technically it is, for authed users. Should we change both of these to just be not found? with less of a message (maybe log the message we actually send now)?
5. Not sure about this one, Wim did this change I think, so deferring to him :)
6. Very good spot. Changed this up a bit to catch this and added test coverage.
7. Injected
8. Added
9. Added some test coverage for this too.
#538:
1. Changed
2. Not sure why this would be problematic at the moment? This functionality doesn't exist on the file system service. So for now, global file functions it is.
3. Good idea. changed to use different file names.
Comment #541
wim leersNice!
I don't understand this test. How is this a masked exploit file?
#535.5: I did that in #487 apparently, because I'm an idiot who writes refactored code and then doesn't actually change the line he was trying to refactor :)
Comment #542
damiankloip commented#541: This was a rogue comment from a small hunk that I copied. So, the test is valid, the comment is misleading :) This addresses some missing test coverage from #535.6.
Needed a reroll too.
Comment #544
wim leers👍 So … back to RTBC!
Actually, #541 still applies cleanly to
8.6.x's HEAD. Perhaps that was not true when you rolled it, but it is now. Redid #542's interdiff on top of #541. This is equivalent to #544, but does apply to8.6.x.Comment #545
kylebrowning commentedYay!
Comment #546
kpv commentedNot sure if this belongs to the current issue..
For text fields you can set both
{ "field_text": {"value": "Dramallama"}, }and
{ "field_text": [{"value": "Dramallama"}], }when setting data for a request.
For file fields you should always pass it as an array, e.g.:
{ "field_file": [{"target_id": 42}], }Comment #547
wim leers#546: that's definitely out of scope here. Please repeat what you said at #2935738: Entity reference field subclasses (such as file and image fields) lose the non-default properties upon denormalization, in both hal_json and json — the root cause for what you describe lies in that same area of code.
Comment #549
Anonymous (not verified) commentedUnrelated fails
Comment #550
alexpottTestbot had a hiccup re-rtbcing
Comment #551
alexpottWhoops lol.
Comment #552
alexpottI did not credit:
I credited @moshe weitzmann, @linclark, @webchick, @slashrsm, @catch because I know they had long offline discussions about this issue and the fact these discussions took place is documented on issue.
Comment #553
alexpottMaybe we should thrown an error if
basename($filename) !== $filename? As atm we're silently rejecting user input atm.No need for the duplicate calls to check access - just can do
if (!$access_result->isAllowed())This should explain what
$validators['file_validate_extensions'][0]is... a list of extensions.$extensions does not exist. Should be
if (!empty($validators['file_validate_extensions'][0])) {- this fix implies we have missing test coverage.Comment #554
alexpottMarking as needs work for #553.1 discussion and for adding tests for #553.4.
Comment #555
alexpottthe test only patch reverts the fix for #553.4 but has the new test added by this patch. It will fail.
Patch attached addresses the missing tests from #553.4 and does a couple more coding standards cleanups.
As far as I can see all we need now is an opinion from someone else on #553.1.
Comment #557
berdir#553.1: I asked the same in #494 (https://www.drupal.org/project/drupal/issues/1927648?page=1#comment-1241...) although as a comment on the test coverage and got this response:
Looks like that was not commented sufficiently?
Comment #558
alexpottOkay let's add a comment to point to the RFC that Mozilla's docs are based on. Also fixed a stray @see to something not that helpful and also make it inline with the comment above which talks about a validate() method.
Comment #559
berdirThose changes look good to me and the additional test coverage covers the changes that were done.
Comment #560
alexpottCommitted fd8233c and pushed to 8.6.x. Thanks!
Discussed with @larowlan and he said that @catch agreed we should get this into 8.6.x early to hopefully catch any issues.
Thanks for the great issue summary. It made reviewing the code, commit credits and comments easier.
Comment #562
alexpottI did a security review as part of #553 - I especially compared the existing file_save_upload logic and the new flow. I found #553.4 which I addressed and added tests for.
This patch deserves release notes mention in 8.6.0.
Comment #563
gabesulliceThank you @alexpott!! This is will be a TREMENDOUS improvement for API-First Drupal :D
Comment #565
xjmUnfortunately the newly added tests are failing on PHP 5.5: https://www.drupal.org/pift-ci-job/929845
Sorry everyone -- have to revert.
Comment #566
alexpottHere's the fix. Straight to RTBC because this is just code removal.
Comment #568
alexpott#566 Unrelated fail on
Drupal\Tests\node\FunctionalJavascript\NodePreviewLinkTest- lol.Comment #569
alexpottGreat the PHP5.5 test proves that the PHP7 fail was random and it's not caused by this because that test doesn't try to do anything resty at all.
Committed a766172 and pushed to 8.6.x. Thanks!
Second time lucky.
Comment #571
Anonymous (not verified) commentedTitanic work from titanic people!
Comment #572
wim leers#552: wow, impressive thoughtfulness, @alexpott! ❤️
#553:
#555: thanks for the test coverage for the fix you added in #553.4 :)
#557 + #558: Thanks Berdir for pointing at that, thanks @alexpott for adding that comment!
#560:
❤️ That took me hours, but I'm glad it paid off. Otherwise it'd have taken you hours!
🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉
I do want to call out @damiankloip, whose time Acquia (my employer) sponsored to get this to the finish line. It was a pleasure working with Damian :) Thank you Acquia, for sponsoring this much-missed feature!
I just marked it as done in #2941316: REST: top priorities for Drupal 8.6.x. That's the "top priorities" issue for 8.6, it's been on the "top priorities" list for 2 years, since #2721489: REST: top priorities for Drupal 8.2.x!
Comment #573
wim leersNow created a sister issue for JSON API too: #2958554: Allow creation of file entities from binary data via JSON API requests.
Comment #574
chihada commentedI've applied the patch and followed the guide coming on https://www.drupal.org/node/2941420 for uploading files, and everything went good, but when I enable the https://www.drupal.org/project/file_entity module, I get the error:
Drupal\Component\Plugin\Exception\PluginNotFoundException: The "entity:file:undefined" plugin does not exist. in Drupal\Core\Plugin\DefaultPluginManager->doGetDefinition() (line 52 of /var/www/html/docroot/core/lib/Drupal/Component/Plugin/Discovery/DiscoveryTrait.php).Comment #575
wim leers#574: Excellent! :) Thank you very much for testing!
The problem you reported is related to either the
file_entitymodule in contrib, or therestmodule in core. It's probably thefile_entitymodule. Please create a separate issue for that, and provide steps to reproduce. Thanks!Comment #577
sharif.elshobkshy commentedHi.
I've applied the patch and now I'm able to upload files. All files are created as application/octet-stream
However, all files are created in the public folder without extension.
This causes:
PDF files not to open (unless the extension is added manually)
Images created and used in media fields are not entirely recognized as images (they appear broken and without preview thumbnails).
Am I missing something or is this how it's supposed to behave?
UPDATE:
I was missing the uri. Now the extension are properly added.
Thanks,
Sharif.
Comment #578
gabesullice@sharif.elshobkshy, I believe you were also probably missing the
filenameportion of theContent-Dispositionrequest header.F.E.
I really appreciate that you updated your comment with the solution. Next time, please open a new support request though :) This issue is already now 578 comments long!
Comment #579
ijsbrandy commentedPatch not working with drupal/core 8.5.5 due to the fact that the methods in FileResourceTestBase.php and MediaResourceTestBase.php are deleted.
Comment #580
wim leersComment #581
nicrodgersI couldn't get patch 579 to apply to 8.5.5, so I've re-rolled the one in 567, hopefully it'll help someone.
Comment #582
xjmWhat needs to be mentioned about this issue for the release notes? It makes sense for the 8.6.0 highlights, but the release notes should be reserved for important upgrade information. Is there something a site owner needs to know before upgrading to this release on account of this issue?
Comment #583
xjmAfter reading the CR which mostly describes how to use the functionality, I don't think there's any specific disruption to modules, remote API, or consumers that we need to document for this release. If there is something that site owners who might rely on something in 8.5.x. HEAD should change, please include a description of that and re-tag this issue.
Thanks!
Comment #584
wim leersWe overlooked at least one detail: #3032620: \Drupal\file\Plugin\rest\resource\FileUploadResource uses basename() when it needs to use the Drupal version.
Comment #585
alireza.tabatabaeian commentedI'm using drupal 8.7.1, I've enabled this modules :
I also have set
admin/config/services/jsonapitoThe Problem is I always get the Route Not Found for my requests.