Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

form_with doesn't post remote forms with empty file input on Safari #32440

Closed
brenogazzola opened this issue Apr 3, 2018 · 16 comments

Comments

@brenogazzola
Copy link

commented Apr 3, 2018

When using form_with with a single file_field (using the form helper), the form will not submit on Safari for OSX if no file was chosen. Switching to "local: true", solves the problem.

Steps to reproduce

1 - Create a form using form_with;
2 - Add a single file input field using the file_field helper;
3 - Without selecting a file, click submit.

Expected behavior

Form is submitted in all browsers

Actual behavior

In Safari the form isn't submitted. In Chrome, Android and Safari iOS the form is submitted.

System configuration

Rails version:
5.2.0.rc2
Ruby version:
2.4.3 and 2.5.0
OSX version:
10.13.4
Safari version:
11.1 (13605.1.33.1.2)

Relevant Code

<%= form_with(scope: :photo, url: photos_path, method: :post) do |f| %>
  <%= f.file_field :file, accept: "image/gif, image/jpg, image/jpeg, image/png" %>
  <%= f.submit "Save" %>
<% end %>

Demo Repository

https://github.com/brenogazzola/turbolinks-bug-demo

@yhirano55

This comment has been minimized.

Copy link
Contributor

commented Apr 3, 2018

I've tried Demo Repository in Chrome and Safari.
But as far as watching these logs, it seems these are no problems:

Chrome: v65.0.3325.181

Started GET "/photos/new" for 127.0.0.1 at 2018-04-04 08:49:06 +0900
Processing by PhotosController#new as HTML
  Rendering photos/new.html.erb within layouts/application
  Rendered photos/new.html.erb within layouts/application (0.7ms)
Completed 200 OK in 19ms (Views: 16.7ms | ActiveRecord: 0.0ms)


Started POST "/photos" for 127.0.0.1 at 2018-04-04 08:49:16 +0900
Processing by PhotosController#create as JS
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"Wtwxv6qcUQXmQGijv/nVWrLkwvscaPF7TKf/MRrJMTSoSREM9+oMv1v4P6yEuCPK3Z13jwNgUd81CBSqMA91yQ==", "photo"=>{"file"=>#<ActionDispatch::Http::UploadedFile:0x00007ff60afe5198 @tempfile=#<Tempfile:/var/folders/p2/qbr5k5hn4gdb7r2vjm8jryfw0000gn/T/RackMultipart20180404-88902-jis5al.jpg>, @original_filename="beach-1867285_1920.jpg", @content_type="image/jpeg", @headers="Content-Disposition: form-data; name=\"photo[file]\"; filename=\"beach-1867285_1920.jpg\"\r\nContent-Type: image/jpeg\r\n">}, "commit"=>"Save"}
Redirected to http://localhost:3000/photos
Completed 200 OK in 1ms (ActiveRecord: 0.0ms)

Safari: v11.0.3 (13604.5.6)

Started GET "/photos/new" for 127.0.0.1 at 2018-04-04 08:50:28 +0900
Processing by PhotosController#new as HTML
  Rendering photos/new.html.erb within layouts/application
  Rendered photos/new.html.erb within layouts/application (0.8ms)
Completed 200 OK in 33ms (Views: 30.3ms | ActiveRecord: 0.0ms)


Started POST "/photos" for 127.0.0.1 at 2018-04-04 08:50:35 +0900
Processing by PhotosController#create as JS
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"h/DYMCya5qlzRrslyEQR8zvej3z7i+OZ75wYB1A4tPuqT4GUZ6xqVONg6UXGdeDGKBdfNIdj9zFIhkDLblbkAQ==", "photo"=>{"file"=>#<ActionDispatch::Http::UploadedFile:0x00007ff611255738 @tempfile=#<Tempfile:/var/folders/p2/qbr5k5hn4gdb7r2vjm8jryfw0000gn/T/RackMultipart20180404-88902-jtj57j.jpg>, @original_filename="beach-1867285_1920.jpg", @content_type="image/jpeg", @headers="Content-Disposition: form-data; name=\"photo[file]\"; filename=\"beach-1867285_1920.jpg\"\r\nContent-Type: image/jpeg\r\n">}, "commit"=>"Save"}
Redirected to http://localhost:3000/photos
Completed 200 OK in 1ms (ActiveRecord: 0.0ms)
@brenogazzola

This comment has been minimized.

Copy link
Author

commented Apr 4, 2018

From your logs it seems you selected a file in both cases (beach-xxx.jpg). Please try again, but leave the input empty. Just open the new photo page and click save. You will notice that the button is disabled but there's no network request (on safari)

@yhirano55

This comment has been minimized.

Copy link
Contributor

commented Apr 4, 2018

I got it. Thanks.

@yhirano55

This comment has been minimized.

Copy link
Contributor

commented Apr 4, 2018

I reproduced in Safari. It looks ActionController::InvalidAuthenticityToken error. It might be better to check these header's diff.

Safari

Started POST "/photos" for 127.0.0.1 at 2018-04-04 09:21:07 +0900
Processing by PhotosController#create as JS
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"ASG4lYhYSRD+JvGGudloV0oiViizDkZIzUpL8yFbYbDfGCqb3JYCixIQvPuiQz6GRmEwUwIHo56pJByEmNZiIA==", "commit"=>"Save"}
Can't verify CSRF token authenticity.
Completed 422 Unprocessable Entity in 1ms (ActiveRecord: 0.0ms)



ActionController::InvalidAuthenticityToken (ActionController::InvalidAuthenticityToken):

vendor/bundle/gems/actionpack-5.2.0.rc2/lib/action_controller/metal/request_forgery_protection.rb:211:in `handle_unverified_request'
vendor/bundle/gems/actionpack-5.2.0.rc2/lib/action_controller/metal/request_forgery_protection.rb:243:in `handle_unverified_request'
vendor/bundle/gems/actionpack-5.2.0.rc2/lib/action_controller/metal/request_forgery_protection.rb:238:in `verify_authenticity_token'
vendor/bundle/gems/activesupport-5.2.0.rc2/lib/active_support/callbacks.rb:426:in `block in make_lambda'
vendor/bundle/gems/activesupport-5.2.0.rc2/lib/active_support/callbacks.rb:198:in `block (2 levels) in halting'
vendor/bundle/gems/actionpack-5.2.0.rc2/lib/abstract_controller/callbacks.rb:34:in `block (2 levels) in <module:Callbacks>'
vendor/bundle/gems/activesupport-5.2.0.rc2/lib/active_support/callbacks.rb:199:in `block in halting'
vendor/bundle/gems/activesupport-5.2.0.rc2/lib/active_support/callbacks.rb:513:in `block in invoke_before'
vendor/bundle/gems/activesupport-5.2.0.rc2/lib/active_support/callbacks.rb:513:in `each'
vendor/bundle/gems/activesupport-5.2.0.rc2/lib/active_support/callbacks.rb:513:in `invoke_before'

Chrome

Started GET "/photos/new" for 127.0.0.1 at 2018-04-04 09:20:14 +0900
Processing by PhotosController#new as HTML
  Rendering photos/new.html.erb within layouts/application
  Rendered photos/new.html.erb within layouts/application (4.7ms)
Completed 200 OK in 31ms (Views: 25.6ms | ActiveRecord: 0.0ms)


Started POST "/photos" for 127.0.0.1 at 2018-04-04 09:20:15 +0900
Processing by PhotosController#create as JS
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"XbZ5Sydz+5f8XfuTpUPwH6oNH+iTkDDkm+JaTdkGv6tc79Th6GSzs2AJr27cnEli7FduH2iI7+IuAGuQEudR+Q==", "commit"=>"Save"}
Redirected to http://localhost:3000/photos
Completed 200 OK in 2ms (ActiveRecord: 0.0ms)
@brenogazzola

This comment has been minimized.

Copy link
Author

commented Apr 4, 2018

That's better than what I get. While I made a mistake when I said that there's no network activity (safari shows a post request, but it just hangs there), my server logs don't show anything after the GET request for /photos/new

Started GET "/photos" for 127.0.0.1 at 2018-04-03 22:13:36 -0300
Processing by PhotosController#index as HTML
  Rendering photos/index.html.erb within layouts/application
  Rendered photos/index.html.erb within layouts/application (0.8ms)
Completed 200 OK in 33ms (Views: 27.7ms | ActiveRecord: 0.0ms)

screen shot 2018-04-03 at 22 15 44

Edit: Just noticed you are using an earlier version of Safari then the one I'm using. That might be way we are getting different things.

@hkurokawa

This comment has been minimized.

Copy link

commented Apr 5, 2018

Hi,

I just came across with a similar problem and found this SO. Probably it's not a bug in Rails? Hope it would be of your help.
https://stackoverflow.com/questions/49614091/safari-11-1-ajax-xhr-form-submission-fails-when-inputtype-file-is-empty

jrae added a commit to jrae/turbolinks-bug-demo that referenced this issue Apr 5, 2018
By forcing data-remote to false we can show safari works as expected
Adding a comment to rails/rails#32440 to
explain the relevance of this commit.
@jrae

This comment has been minimized.

Copy link

commented Apr 5, 2018

I think this has come about with this change

#26976

More specifically the change to ensure

Default to remote: true

Which was merged into rails 5.1
https://github.com/rails/rails/blob/08e03cfde4e1fe4c435e9f8925bcf3fee7fa5a3a/guides/source/5_1_release_notes.md#unification-of-form_for-and-form_tag-into-form_with

This means that data-remote=true as added even though it's not specified in the form above

https://github.com/brenogazzola/turbolinks-bug-demo.

Here is a shot from the inspector devtools inspector for chrome.

screen shot 2018-04-04 at 22 30 37

This of course, is the same html for Safari but maybe Safari reads this data-remote as more explicit. For inputs of type file anyway. At a guess, it looks for the AJAX request in the in the javascript and doesn't default to a regular HTTP post request when there's no related javascript to be found. Not sure just a hypothesis.

What we can demonstrate though is that if we set data-remote to false, safari works as expected.. Here is the forked request of your demo @brenogazzola

jrae/turbolinks-bug-demo@bba828f

Seems this default behaviour was added for a reason so hesitant to add a pull request to undo this behaviour. New to open source contributions but keen to do more - please let me know of best practices/comments :).

(thanks @kpearson for the open source meetup session)

@freddm

This comment has been minimized.

Copy link

commented Apr 5, 2018

Hi! I also got some problems with empty file inputs on data-remote forms. I think the problem is related to the new security feature in Safari: https://support.apple.com/en-us/HT208695

@brenogazzola

This comment has been minimized.

Copy link
Author

commented Apr 5, 2018

@jrae not sure what's the difference between data: {remote: false} and local: true, but I was using the latter and it worked fine for me.

@jrae

This comment has been minimized.

Copy link

commented Apr 5, 2018

@brenogazzola Nice! Prevents rails-ujs adding the data-remote attribute altogether 👍 A better solution :)

Looks like they intended it to be used this way by the generated template

https://github.com/rails/rails/blob/b1f140ef2e5af605ea12d8ee1c932eaf728a398d/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb.tt

@mani47

This comment has been minimized.

Copy link

commented Apr 7, 2018

I'm experiencing the same issue and I have to use remote because I want to submit the form with Ajax. Have you guys found any workaround?

@brenogazzola

This comment has been minimized.

Copy link
Author

commented Apr 7, 2018

Unfortunately not. I thought of capturing a form submit event and removing the empty file input, but I couldn't get the submit it to trigger (maybe a Turbolinks thing?). Capturing clicks on the submit button wouldn't for me either since I sometimes trigger the submit programatically and didn't want to refactor.

Still, if you want to try the submit button method, the closest I got was the code below (I'm not using jQuery, that's why I'm adding the event delegation code I use below:

this.App || (this.App = {});
this.App.util = (function () {
    function delegate(event, selector, callback) {
        document.addEventListener(event, function (e) {
            if (e.target.matches(selector)) {
                e.stopPropagation();

                self = e.srcElement || e.target;
                callback(e);
            }
        });
    }

    return {
        delegate: delegate,
    };
})();

App.util.delegate("click", "input[type='submit']", function(){
    var file_input = document.querySelector("input[type=file]");
    if (file_input.files.length === 0) file_input.parentNode.removeChild(file_input);
});
@ypresto

This comment has been minimized.

Copy link
Contributor

commented Apr 13, 2018

I confirmed that this also reproduces in iOS 11.3 (real devise and simulator) but not in 11.2 (simulator).

@ypresto

This comment has been minimized.

Copy link
Contributor

commented Apr 13, 2018

Created (updated) workaround snippet:
https://gist.github.com/ypresto/b4715b06230d4014a90eaacc3445158f

// iOS 11.3 Safari / macOS Safari 11.1 empty <input type="file"> XHR bug workaround.
// Replace empty File object with equivalent Blob in FormData, keeping its order, before sending it to server.
// Should work with IE10 and all other modern browsers.
// Because useragent value can be customized by WebView or etc., applying workaround code for all browsers.
// https://stackoverflow.com/questions/49614091/safari-11-1-ajax-xhr-form-submission-fails-when-inputtype-file-is-empty
// https://github.com/rails/rails/issues/32440
document.addEventListener('ajax:beforeSend', function(e) {
  var formData = e.detail[1].data
  if (!(formData instanceof window.FormData)) return
  if (!formData.keys) return // unsupported browser
  var newFormData = new window.FormData()
  Array.from(formData.entries()).forEach(function(entry) {
    var value = entry[1]
    if (value instanceof window.File && value.name === '' && value.size === 0) {
      newFormData.append(entry[0], new window.Blob([]), '')
    } else {
      newFormData.append(entry[0], value)
    }
  })
  e.detail[1].data = newFormData
})

See here for detail: https://stackoverflow.com/a/49827426/1474113

@y-yagi

This comment has been minimized.

Copy link
Member

commented May 3, 2018

This seems Webkit's bug and already fixed by https://trac.webkit.org/changeset/230963/webkit.

@y-yagi y-yagi closed this May 3, 2018

chloerei added a commit to getcampo/campo that referenced this issue May 23, 2018
Disable account setting remote form
Webkit bug:

rails/rails#32440

When file input is empty, safari don't send xhr request.
@chloerei chloerei referenced this issue May 23, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
8 participants
You can’t perform that action at this time.