CaptainHypertext's avatar

Can I turn a base64 string into an UploadedFile object?

Basically what I'm wanting to do here is accept a base64 encoded image from the client, convert that to an UploadedFile (like is made when you upload a file like normal), and pass that to the function that I normally use to process file uploads. This will later be uploaded to Amazon S3, preferably without writing the image to my server at any point. Can this be done?

0 likes
4 replies
CaptainHypertext's avatar
Level 1

So I actually just ended up modifying my functions to accept a binary string and then just duplicating some logic. Oh well.

Stasgar's avatar

You broke the rule - 'dont repeat yourself'. Other solutions here? I need it too :)

1 like
Drfraker's avatar

Could you do something like this:

use Illuminate\Http\File;

// Automatically generate a unique ID for file name...
Storage::disk('s3')->putFile('photos', base64_decode(request('file'));

I didn't try running this code, but it seems like it should be able to be done like that or something similar.

1 like
kmuenkel's avatar

I just finished working out the exact same scenario.

  1. Put the following in your AppServiceProvider::boot() method:

     File::macro('streamMimeType', function ($content) {
         $mimeType = finfo_buffer(finfo_open(), $content, FILEINFO_MIME_TYPE);
         
         return $mimeType;
     });
     
     File::macro('streamSize', function ($content) {
         $size = strlen($content);
         
         return $size;
     });
     
     File::macro('mimeExtension', function ($mimeType) {
         $extension = ExtensionGuesser::getInstance()->guess($mimeType);
         
         return $extension;
     });
     
     File::macro('streamExtension', function ($content) {
         $mimeType = $this->streamMimeType($content);
         $extension = $this->streamMimeExtension($mimeType);
         
         return $extension;
     });
    
  2. Create a custom Request object that extends Illuminate\Http\Request, and make sure the protected $rules array includes a ['your_file_param' => 'file'] element. https://laravel.com/docs/5.6/validation#creating-form-requests https://laravel.com/docs/5.6/validation#rule-file

  3. Leverage the optional prepareForValidation() method in your new Request object, and place the following inside it:

     $rules = $this->rules();
     foreach ($rules as $field => $ruleSet) {
         $ruleSet = is_string($ruleSet) ? explode('|', $ruleSet) : $ruleSet;
         if (in_array('file', $ruleSet) && $this->has($field)) {
             if (!$this->file($field) && ($content = $this->input($field)) && is_string($content)) {
                 //"data:image/png;base64,iVBORw0K...";
                 $content = array_last(explode(',', $content));
                 $content = base64_decode($content);
                 $mimeType = File::streamMimeType($content);
                 $extension = File::mimeExtension($mimeType);
                 $size = File::streamSize($content);
                 $name = uniqid('decoded_').'.'.$extension;
                 $path = tempnam(sys_get_temp_dir(), uniqid('symfony', true));
                 File::put($path, $content);
                 
                 $file = app(UploadedFile::class, [
                     'path' => $path,
                     'originalName' => $name,
                     'mimeType' => $mimeType,
                     'size' => $size,
                     'error' => null,
                     'test' => true
                 ]);
     
                 $this->files->add([$field => $file]);
                 $this->offsetUnset($field);
             }
         }
     }
    

    You can name it whatever you like of course, not just uniqid('decoded_'). Also, that $path was derived the same exact way Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory::getTemporaryPath() does it when constructing the default Request files in the first place. Sadly, getTemporaryPath() is protected, so there's no accessing that behavior without making a ServiceProvider bind an override class in place of HttpFoundationFactory, and that's more trouble than it's worth.

  4. Type-hint your new custom Request object into your controller method, and use that instead of the request() helper function. It should then have access to your UploadFile incarnation of the base64 input.

1 like

Please or to participate in this conversation.