2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

base64でエンコードした画像をデコードした後にlaravel上で正しいMIMEを取得する方法

Posted at

遭遇した事象

クライアントからサーバーに画像ファイルをアップロードするAPIで、クライアントで画像ファイルをbase64にエンコードしてAPIのリクエストとして送信する処理がある

const reader = new FileReader();

reader.onload = (e) => {
    let base64 = e.target.result;
    const requestBody = {
        file: base64,
    }

    axios.post("/api/upload", requestBody, {
        headers: {
            "Content-Type": "application/json",
        },
    })
    .then((res) => {
        // 事後処理
    }
}

これを受けてバックエンド(laravelで構築)では下記のデコード後にMIMEを調べて画像じゃなかったらエラーを返すという処理をしている

$file = base64_decode($request->file);
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_buffer($finfo,$file);

if(strpos($mime,'image/') !== false){
    // ファイルを保存する
}
else{
    // エラー処理
}

ところが実際に動かして画像ファイルをアップロードすると、画像と判定されないエラーが出てアップロードができなかった。

調査と推測

実際にMIMEがバックエンドでどうなっているのかを調べてみたところ、「application/octet-stream」になっていることがわかった。
また、バックエンドの単体テストでは正しく判定できていため、クライアントサイドに問題があるのではと考えた・

そこで、クライアント側でbase64エンコードした内容をチェックすると、

data:image/png;base64,iVBORw0KG・・・

となっていたが、バックエンドの単体テストで使ったbase64の文字列を確認すると、

iVBORw0KG・・・

と、「data:image/png;base64,」の部分が無かった。

対処

上記の事から「data:image/png;base64,」を消して送ればいいだろう、ということでクライアント側のコードを下記のように修正した。

const reader = new FileReader();

reader.onload = (e) => {
    let base64 = e.target.result;

    // base64,の次の文字から取得して、それ以前を削除した
    const base64Index = base64.indexOf("base64,");
    if (base64Index !== -1) {
        // 「base64,」までで7文字なのでそれ以降を取得する
        base64 = base64.substring(base64Index + 7);
    }
    
    const requestBody = {
        file: base64,
    }

    axios.post("/api/upload", requestBody, {
        headers: {
            "Content-Type": "application/json",
        },
    })
    .then((res) => {
        // 事後処理
    }
}

これによって無事にMIMEを判定してアップロードをすることができた。

補足

base64エンコードにおいて「data:image/png;base64,」の部分はエンコードされたバイナリデータそのものではなく、エンコードされたバイナリデータのメタ情報を表すものなのでそれを含めてデコードを行い、MIMEを取得すると正しく取得できないことがある、というのが原因のようだった。

今回はクライアンド側でメタ情報を消して送信しているが、サーバサイド側で処理後にエンコードするということもできる。

<?php

// 例として、$request->fileにBase64エンコードされたデータが含まれているとします
$encodedFile = $request->file;

// プレフィックスを除去する
// preg_replaceを使用して、data:image/png;base64, などのプレフィックス部分を空文字に置き換えます
$base64Data = preg_replace('#^data:image/\w+;base64,#i', '', $encodedFile);

// Base64デコードを行う
$decodedFile = base64_decode($base64Data);

// MIMEタイプを調べる
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_buffer($finfo, $decodedFile);
finfo_close($finfo);

// MIMEタイプが画像かどうかをチェック
if (strpos($mime, 'image/') === 0) {
    // ファイルを保存する
} else {
    // エラー処理
}

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?