it-swarm.com.de

Safari 11.1: Die Übermittlung von ajax/XHR-Formularen schlägt fehl, wenn die Eingabe [type = file] leer ist

UPDATE: Seit Webkit build r230963 wurde dieses Problem in Webkit behoben.

===========

Seit dem letzten Update von Safari 11.1 unter macOS und iOS sowie in Safari Technology Preview 11.2 schlagen die $.ajax-Aufrufe in meiner Webanwendung fehl, wenn für ein input[type=file]-Feld keine Datei ausgewählt wurde (dies ist in meinem Formular nicht erforderlich). Kein Fehler, wenn für das Feld () eine Datei ausgewählt wurde.

Der error-Callback von ajax wird ausgeführt und die Safari-Konsole enthält die folgende Meldung: Failed to load resource: The operation couldn’t be completed. Protocol error. Ich bin HTTPS und übermittle an einen Ort in derselben Domäne (und demselben Server) auch über HTTPS.

Vor dem 11.1-Update wurde der $.ajax-Aufruf gut übermittelt, wenn keine Datei ausgewählt wurde. Die neuesten Versionen von Chrome und Firefox haben keine Probleme.

Relevante Teile meines Codes:

Die Eingabe:

Browse... <input id="file-upload" type="file" name="image" accept=".jpg,.jpeg">

Die JS:

var formData = new FormData($(this)[0]);
$.ajax({
    type: 'POST',
    enctype: 'multipart/form-data',
    url: '../process.php',
    data: formData,
    contentType: false,
    processData: false,
    cache: false,
    success: function(response) { ... },
    error: function() { //my code reaches here }
});

Als temporäre (hoffentlich) Lösung erkenne ich ein leeres Dateifeld und entferne es vor dem Aufruf von formData aus ajax. Alles funktioniert wie erwartet/vorher:

$("input[type=file]").each(function() {
    if($(this).val() === "") {
        formData.delete($(this).attr("name"));
    }
});

Mache ich etwas falsch, gibt es ein Problem mit Safari oder gibt es eine Änderung in Safari, die jetzt bei Ajax-Anrufen berücksichtigt werden muss?

38
Matt.

Seit Webkit Build r230963 wurde dieses Problem in Webkit behoben. Ich habe den Build heruntergeladen und ausgeführt und festgestellt, dass das Problem behoben ist. Keine Vorstellung, wann eine öffentliche Version für Safari verfügbar sein wird, die dieses Update enthält.

5
Matt.

UPDATE: Alte Antwort funktioniert NICHT in Firefox.

Firefox gibt nur leere Zeichenfolge für FormData.get() in einem leeren Dateifeld zurück (anstelle des Dateiobjekts in anderen Browsern). Wenn Sie eine alte Problemumgehung verwenden, wird leerer <input type="file"> wie leerer <input type="text"> gesendet. Leider gibt es keine Möglichkeit, eine leere Datei nach dem Erstellen eines FormData-Objekts von einem leeren Text zu unterscheiden.

Verwenden Sie stattdessen diese Lösung:

var $form = $('form')
var $inputs = $('input[type="file"]:not([disabled])', $form)
$inputs.each(function(_, input) {
  if (input.files.length > 0) return
  $(input).prop('disabled', true)
})
var formData = new FormData($form[0])
$inputs.prop('disabled', false)

Live-Demo: https://jsfiddle.net/ypresto/05Lc45eL/

Für eine Umgebung ohne JQuery:

var form = document.querySelector('form')
var inputs = form.querySelectorAll('input[type="file"]:not([disabled])')
inputs.forEach(function(input) {
  if (input.files.length > 0) return
  input.setAttribute('disabled', '')
})
var formData = new FormData(form)
inputs.forEach(function(input) {
  input.removeAttribute('disabled')
})

Für Rails (Rails-ujs/jQuery-ujs): https://Gist.github.com/ypresto/cabce63b1f4ab57247e1f836668a00a5


Alte Antwort:

Das Filtern von FormData (in der Antwort von Ravichandra Adiga) ist besser, da es kein DOM manipuliert.

Aber die Reihenfolge der Teile in FormData entspricht garantiert der Eingabe von Elementen im Formular , entsprechend der <form>-Spezifikation. Es könnte ein anderer Fehler auftreten, wenn sich jemand auf diese Spezifikation stützt.

Unter dem Snippet bleibt die Reihenfolge der FormData und der leere Teil erhalten.

var formDataFilter = function(formData) {
    // Replace empty File with empty Blob.
  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)
    }
  })
  return newFormData
}

Ein Live-Beispiel ist hier: https://jsfiddle.net/ypresto/y6v333bq/

Für Rails siehe hier: https://github.com/Rails/rails/issues/32440#issuecomment-381185380

(HINWEIS: Safari von iOS 11.3 hat dieses Problem, 11.2 jedoch nicht.)

18
ypresto

Zur Problemumgehung lösche ich die Eingabetypdatei mit der jQuery-Methode remove () vollständig aus DOM.

$("input[type=file]").each(function() {
    if($(this).val() === "") {
        $(this).remove();
    }
});
5
mani_007

Ich habe an einem scheinbar gleichen Thema in einem Perl-Programm gearbeitet

Die Verarbeitung von mehrteiligen/Formulardaten in Perl ruft bei Apple-Geräten einen Apache-Fehler auf, wenn ein Formularteilelement leer ist

Umgehung besteht darin, die Formularelemente zu entfernen, bevor die Formulardaten zugewiesen werden: 

$('#myForm').find("input[type='file']").each(function(){
    if ($(this).get(0).files.length === 0) {$(this).remove();}
});
var fData = new FormData($('#myForm')[0]);
...
2
    var fileNames = formData.getAll("filename[]");
    formData.delete("filename[]");
    jQuery.each(fileNames, function (key, fileNameObject) {
        if (fileNameObject.name) {
            formData.append("filename[]", fileNameObject);
        }
    });

Versuche dies !!

1

Dies funktioniert für mich, um zu überprüfen, ob das Eingabefeld leer ist. Wenn leer, deaktivieren Sie das Eingabefeld, bevor Sie die FormData erstellen. Entfernen Sie nach dem Erstellen der FormData das Attribut "disabled". Der Unterschied zu anderen Antworten ist, dass ich nach "input [0] .files.length == 0" suche.

// get the input field with type="file"
var input = $('#myForm').find("input[type='file']")

// add the "disabled" attribute to the input
if (input[0].files.length == 0) {
  input.prop('disabled', true);
}

// create the formdata  
var formData = new FormData($(this)[0]);

// remove the "disabled" attribute
input.prop('disabled', false);
0
Martin