0

I'm using AngularJS v1.6.1, Apache 2.4.10 on Debian with PHP 5.6.24 and I'm trying to upload a file to my server using $http POST service. On my php.ini, max file size is set to 8Mo, max post size too, upload file is on, and memory size limit is set to 128Mo.

Form:

<input type="file" accept="application/pdf" id="uploadOT" max-files="1" ng-model="uploadOT" name="uploadOT" valid-file required ng-class="{'md-input-invalid':uploadForm.uploadOT.$error.validFile}" />

Angular directive: (when input content change, get a FileReader object and send file)

myModule.directive('validFile', function() {
    return {
        require:    'ngModel',
        link:       function(scope, elt, attrs, ctrl) {
            ctrl.$setValidity('validFile', elt.val() !== '');
            elt.bind('change', function() {

                var file = document.getElementById('uploadOT').files;
                var reader = new FileReader();
                reader.onload = function(e) {
                    scope.sendFile(reader, scope.id);
                };
                scope.showUploadProgress = true;
                scope.filename = file[0].name;
                reader.readAsBinaryString(file[0]);

                ctrl.$setValidity('validFile', elt.val() !== '');
                scope.$apply(function() {
                    ctrl.$setViewValue(elt.val());
                    ctrl.$render();
                });
            });
        }
    };
});

Inside controller:

$scope.sendFile = function(reader, id) {
    var fd = new FormData();
    fd.append('id', id);
    fd.append('file', reader.result);
    fd.append('MAX_FILE_SIZE', 8 * 1024 * 1024);
    $http.post('api/upload.php', fd, {
        headers:            {'Content-Type' : undefined },
        transformRequest:   angular.identity
    }).then(function() {
        alert('upload success');
    }, function() {
        $scope.showUploadError = true;
        $scope.showUploadProgress = false;
        $scope.postError = 'Une erreur inconnue est survenue !';
    });
};

On server side (file api/upload.php), I print variables $_POST and $_FILES with print_r().

Why is $_FILES always empty, and my file data is in $_POST['file']?

I can create file from $_POST['file'] data with php function file_put_contents() but I cannot make verifications that I can make with $_FILES. Is it really important (security issues)?

If I change my POST Content-Type to multipart/form-data, the same thing happend.

Ayak973
  • 468
  • 1
  • 6
  • 23

3 Answers3

0

I presume it's because you forgot to specify the encoding type of your form element.

enctype="multipart/form-data"

So, by default - the browser will assume that the form encoding type is "application/x-www-form-urlencoded" which does not support files in this way. You can still securely send file binary data with the stock encoding method however, this might be where performance and functionality are determining factors to which you choose. I recommend running some tests to confirm which is the fastest. In some cases, the difference will be negligible and will likely be for sake of consistency.

Timothy Wood
  • 487
  • 3
  • 10
  • When I show my headers, the request is with Content-Type multipart/form-data, and the data of the request encoded properly... not application/x-www-form-urlencoded... – Ayak973 Jan 25 '17 at 15:09
  • Try adding it, and also add method="post" to the element as well. Then vardump $_FILES. Edit: Also, check your php ini to ensure that "file_uploads" is on. – Timothy Wood Jan 25 '17 at 15:12
  • What about "file_uploads" option in your ini? – Timothy Wood Jan 25 '17 at 15:21
  • As I said in my question, file_uploads is 'On' – Ayak973 Jan 25 '17 at 15:23
  • Ah yes, sorry. Can you show me the entire post form (or at least the inputs)? – Timothy Wood Jan 25 '17 at 15:28
  • The input in the question is the only one on the form, and I think that the form is irrelevant here, because I put my data manually in a FormData object, and dont submit the form itself – Ayak973 Jan 25 '17 at 15:34
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/134006/discussion-between-timothy-wood-and-ayak973). – Timothy Wood Jan 25 '17 at 15:39
0

Skip the FileReader API and use the file object directly:

<input type=file files-input ng-model="files" ng-change="upload()" />

The filesInput Directive

angular.module("myApp").directive("filesInput", function() {
  return {
    require: 'ngModel',
    link: function linkFn (scope, elem, attrs, ngModel) {
      elem.on("change", function (e) {
        ngModel.$setViewValue(elem[0].files, "change");
      });
    },
  };
});

The upload() function

 vm.upload = function() {

    //var formData = new $window.FormData();
    //formData.append("file-0", vm.files[0]);

    var config = { headers: { "Content-Type": undefined } };

    $http.post(url, vm.files[0], config)
    .then(function(response) {
      vm.result = "SUCCESS";
    }).catch(function(response) {
      vm.result = "ERROR "+response.status;
    });
  };

The XHR API send() method can post either a file object or a FormData object. It is more efficient to send the file object directly as the XHR API uses base64 encoding for the FormData object which has a 33% overhead.

The DEMO on PLNKR.

georgeawg
  • 48,608
  • 13
  • 72
  • 95
  • Can you tell me where your variable *vm* come from? And what if I have other values to send, I suppose that I have to use FormData? – Ayak973 Jan 27 '17 at 13:24
0

To make it works, I had to do these modifications:

Directive:

myModule.directive('validFile', function() {
    return {
        require:    'ngModel',
        link:       function(scope, elt, attrs, ctrl) {
            ctrl.$setValidity('validFile', elt.val() !== '');
            elt.bind('change', function() {

                var file = document.getElementById('uploadOT').files;
                var reader = new FileReader();
                reader.onload = function(e) {
                    scope.sendFile(file[0], scope.OT);  ////CHANGE HERE
                };
                scope.showUploadProgress = true;
                scope.filename = file[0].name;
                reader.readAsArrayBuffer(file[0]);      ////CHANGE HERE

                ctrl.$setValidity('validFile', elt.val() !== '');
                scope.$apply(function() {
                    ctrl.$setViewValue(elt.val());
                    ctrl.$render();
                });
            });
        }
    };
});

Inside Controller

$scope.sendFile = function(file, id) {
        var fd = new FormData();
        fd.append('id', id);
        fd.append('file', file);
        fd.append('MAX_FILE_SIZE', 8 * 1024 * 1024);
        $http({
            method: 'POST',
            url: 'upload.php',
            data: fd,
            headers: {'Content-Type': undefined, 'Process-Data': false},
            transformRequest: angular.identity
        }).then( function() {
            console.log('success');
        }, function() {
            console.log('failure');
        });
    };
Ayak973
  • 468
  • 1
  • 6
  • 23