[javascript] ‘창’에서 ‘btoa’실행 실패 : 인코딩 할 문자열에 Latin1 범위 밖의 문자가 포함되어 있습니다.

내 테스트에 따르면 제목의 오류는 Google 크롬에서만 발생합니다. 큰 XML 파일을 다운로드 할 수 있도록 base64로 인코딩하고 있습니다.

this.loader.src = "data:application/x-forcedownload;base64,"+
                  btoa("<?xml version=\"1.0\" encoding=\"utf-8\"?>"
                  +"<"+this.gamesave.tagName+">"
                  +this.xml.firstChild.innerHTML
                  +"</"+this.gamesave.tagName+">");

this.loader 숨겨진 iframe입니다.

일반적으로 Google 크롬은 btoa호출 시 충돌이 발생하기 때문에이 오류는 실제로 큰 변화 입니다. Mozilla Firefox에는 여기에 문제가 없으므로 브라우저와 관련된 문제입니다. 파일에 이상한 문자가 있는지 모릅니다. 실제로 비 ASCII 문자가 없다고 생각합니다.

Q :
문제가있는 문자를 찾아 Chrome이 불평하지 않도록 바꾸려면 어떻게해야합니까?

Downloadify를 사용하여 다운로드를 시작하려고했지만 작동하지 않습니다. 신뢰할 수 없으며 디버그를 허용하기 위해 오류가 발생하지 않습니다.



답변

UTF8이있는 경우 다음과 같이 사용합니다 (실제로 SVG 소스에서 작동).

btoa(unescape(encodeURIComponent(str)))

예:

 var imgsrc = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(markup)));
 var img = new Image(1, 1); // width, height values are optional params 
 img.src = imgsrc;

해당 base64를 디코딩해야하는 경우 다음을 사용하십시오.

var str2 = decodeURIComponent(escape(window.atob(b64)));
console.log(str2);

예:

var str = "äöüÄÖÜçéèñ";
var b64 = window.btoa(unescape(encodeURIComponent(str)))
console.log(b64);

var str2 = decodeURIComponent(escape(window.atob(b64)));
console.log(str2);

참고 : mobile-safari에서이 작업을 수행해야하는 경우 base64 데이터에서 모든 공백을 제거해야 할 수 있습니다.

function b64_to_utf8( str ) {
    str = str.replace(/\s/g, '');    
    return decodeURIComponent(escape(window.atob( str )));
}

2017 업데이트

이 문제는 다시 나를 괴롭 혔습니다.
간단한 진실은 atob이 실제로 UTF8 문자열을 처리하지 않는다는 것입니다. ASCII 전용입니다.
또한 js-base64와 같은 블로 트웨어를 사용하지 않을 것입니다.
그러나 webtoolkit는 작은, 좋은 아주 유지 관리 구현을 가지고있다 :

/**
*
*  Base64 encode / decode
*  http://www.webtoolkit.info
*
**/
var Base64 = {

    // private property
    _keyStr: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="

    // public method for encoding
    , encode: function (input)
    {
        var output = "";
        var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
        var i = 0;

        input = Base64._utf8_encode(input);

        while (i < input.length)
        {
            chr1 = input.charCodeAt(i++);
            chr2 = input.charCodeAt(i++);
            chr3 = input.charCodeAt(i++);

            enc1 = chr1 >> 2;
            enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
            enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
            enc4 = chr3 & 63;

            if (isNaN(chr2))
            {
                enc3 = enc4 = 64;
            }
            else if (isNaN(chr3))
            {
                enc4 = 64;
            }

            output = output +
                this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
                this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);
        } // Whend 

        return output;
    } // End Function encode 


    // public method for decoding
    ,decode: function (input)
    {
        var output = "";
        var chr1, chr2, chr3;
        var enc1, enc2, enc3, enc4;
        var i = 0;

        input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
        while (i < input.length)
        {
            enc1 = this._keyStr.indexOf(input.charAt(i++));
            enc2 = this._keyStr.indexOf(input.charAt(i++));
            enc3 = this._keyStr.indexOf(input.charAt(i++));
            enc4 = this._keyStr.indexOf(input.charAt(i++));

            chr1 = (enc1 << 2) | (enc2 >> 4);
            chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
            chr3 = ((enc3 & 3) << 6) | enc4;

            output = output + String.fromCharCode(chr1);

            if (enc3 != 64)
            {
                output = output + String.fromCharCode(chr2);
            }

            if (enc4 != 64)
            {
                output = output + String.fromCharCode(chr3);
            }

        } // Whend 

        output = Base64._utf8_decode(output);

        return output;
    } // End Function decode 


    // private method for UTF-8 encoding
    ,_utf8_encode: function (string)
    {
        var utftext = "";
        string = string.replace(/\r\n/g, "\n");

        for (var n = 0; n < string.length; n++)
        {
            var c = string.charCodeAt(n);

            if (c < 128)
            {
                utftext += String.fromCharCode(c);
            }
            else if ((c > 127) && (c < 2048))
            {
                utftext += String.fromCharCode((c >> 6) | 192);
                utftext += String.fromCharCode((c & 63) | 128);
            }
            else
            {
                utftext += String.fromCharCode((c >> 12) | 224);
                utftext += String.fromCharCode(((c >> 6) & 63) | 128);
                utftext += String.fromCharCode((c & 63) | 128);
            }

        } // Next n 

        return utftext;
    } // End Function _utf8_encode 

    // private method for UTF-8 decoding
    ,_utf8_decode: function (utftext)
    {
        var string = "";
        var i = 0;
        var c, c1, c2, c3;
        c = c1 = c2 = 0;

        while (i < utftext.length)
        {
            c = utftext.charCodeAt(i);

            if (c < 128)
            {
                string += String.fromCharCode(c);
                i++;
            }
            else if ((c > 191) && (c < 224))
            {
                c2 = utftext.charCodeAt(i + 1);
                string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
                i += 2;
            }
            else
            {
                c2 = utftext.charCodeAt(i + 1);
                c3 = utftext.charCodeAt(i + 2);
                string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
                i += 3;
            }

        } // Whend 

        return string;
    } // End Function _utf8_decode 

}

https://www.fileformat.info/info/unicode/utf8.htm

  • 127 (16 진수 0x7F) 이하의 문자에 대해 UTF-8 표현은 1 바이트입니다. 전체 유니 코드 값 중 가장 낮은 7 비트입니다. 이것은 또한 ASCII 값과 동일합니다.

  • 2047 (16 진수 0x07FF) 이하의 문자의 경우 UTF-8 표현이 2 바이트에 걸쳐 분산됩니다. 첫 번째 바이트에는 두 개의 상위 비트가 설정되고 세 번째 비트는 삭제됩니다 (예 : 0xC2에서 0xDF). 두 번째 바이트는 최상위 비트가 설정되고 두 번째 비트는 지워집니다 (예 : 0x80에서 0xBF).

  • 2048 이상이지만 65535 (0xFFFF) 미만인 모든 문자의 경우 UTF-8 표현이 3 바이트로 분산됩니다.


답변

사용하기 btoaunescape하고 encodeURIComponent나를 위해 작동하지 않았다. 모든 특수 문자를 XML / HTML 엔터티로 바꾼 다음 base64 표현으로 변환하는 것이이 문제를 해결하는 유일한 방법이었습니다. 일부 코드 :

base64 = btoa(str.replace(/[\u00A0-\u2666]/g, function(c) {
    return '&#' + c.charCodeAt(0) + ';';
}));


답변

대신 라이브러리 사용

우리는 바퀴를 재발 명 할 필요가 없습니다. 라이브러리를 사용하여 시간과 두통을 절약하십시오.

js-base64

https://github.com/dankogai/js-base64 가 좋고 유니 코드를 잘 지원한다는 것을 확인했습니다.

Base64.encode('dankogai');  // ZGFua29nYWk=
Base64.encode('小飼弾');    // 5bCP6aO85by+
Base64.encodeURI('小飼弾'); // 5bCP6aO85by-

Base64.decode('ZGFua29nYWk=');  // dankogai
Base64.decode('5bCP6aO85by+');  // 小飼弾
// note .decodeURI() is unnecessary since it accepts both flavors
Base64.decode('5bCP6aO85by-');  // 小飼弾


답변

문제를 실제로 해결 한 방법과 이것이 올바른 해결책 이라고 생각하는 이유를 공유해야한다고 생각 했습니다 (이전 브라우저에 최적화하지 않은 경우).

데이터를 dataURL ( data: ...)로 변환

var blob = new Blob(
              // I'm using page innerHTML as data
              // note that you can use the array
              // to concatenate many long strings EFFICIENTLY
              [document.body.innerHTML],
              // Mime type is important for data url
              {type : 'text/html'}
); 
// This FileReader works asynchronously, so it doesn't lag
// the web application
var a = new FileReader();
a.onload = function(e) {
     // Capture result here
     console.log(e.target.result);
};
a.readAsDataURL(blob);

사용자가 데이터를 저장하도록 허용

명백한 해결책 외에도 URL로 dataURL을 사용하여 새 창을 열면 두 가지 다른 작업을 수행 할 수 있습니다.

1. fileSaver.js 사용

파일 세이버는 미리 정의 된 파일 이름으로 실제 파일 저장 대화 상자를 만들 수 있습니다. 일반 dataURL 접근 방식으로 대체 할 수도 있습니다.

2. 사용 (실험용) URL.createObjectURL

이것은 base64로 인코딩 된 데이터를 재사용하는 데 좋습니다. dataURL에 대한 짧은 URL을 생성합니다.

console.log(URL.createObjectURL(blob));
//Prints: blob:http://stackoverflow.com/7c18953f-f5f8-41d2-abf5-e9cbced9bc42

선행 blob접두사를 포함하는 URL을 사용하는 것을 잊지 마십시오 . 나는 document.body다시 사용 했다 :

이미지 설명

이 짧은 URL을 AJAX 대상, <script>소스 또는 <a>href 위치 로 사용할 수 있습니다 . 하지만 URL을 파기 할 책임은 다음과 같습니다.

URL.revokeObjectURL('blob:http://stackoverflow.com/7c18953f-f5f8-41d2-abf5-e9cbced9bc42')


답변

Stefan Steiger 답변에 대한 보완 : (댓글로보기 좋지 않기 때문에)

문자열 프로토 타입 확장 :

String.prototype.b64encode = function() { 
    return btoa(unescape(encodeURIComponent(this))); 
};
String.prototype.b64decode = function() { 
    return decodeURIComponent(escape(atob(this))); 
};

용법:

var str = "äöüÄÖÜçéèñ";
var encoded = str.b64encode();
console.log( encoded.b64decode() );

노트:

주석에서 언급했듯이 unescape향후 제거 될 수 있으므로 사용 하지 않는 것이 좋습니다.

경고 : … : 언 이스케이프 ()가 엄격하게되지 않는 것은 아니지만 ( “웹 표준에서 제거”에서와 같이), 그것은 누구의 소개 내용의 ECMA-262 표준의 부속서 B에 정의되어 이에 지정된 언어 기능 및 행동의 모든 부록에는 하나 이상의 바람직하지 않은 특성이 있으며 레거시 사용이없는 경우이 사양에서 제거됩니다.

참고 : URI를 디코딩하는 데 이스케이프 해제를 사용하지 말고 대신 decodeURI 또는 decodeURIComponent를 사용하세요.


답변

btoa ()는 String.fromCodePoint (0)에서 String.fromCodePoint (255)까지의 문자 만 지원합니다. 코드 포인트가 256 이상인 Base64 문자의 경우 앞뒤로 인코딩 / 디코딩해야합니다.

그리고이 시점에서 그것은 까다로워집니다 …

가능한 모든 기호는 유니 코드 테이블에 정렬됩니다. 유니 코드-표는 다른 영역 (언어, 수학 기호 등)으로 나뉩니다. 비행기의 모든 표지판에는 고유 한 코드 포인트 번호가 있습니다. 이론적으로 숫자는 임의로 커질 수 있습니다.

컴퓨터는 데이터를 바이트 단위로 저장합니다 (8 비트, 16 진수 0x00-0xff, 이진 00000000-11111111, 10 진수 0-255). 이 범위는 일반적으로 기본 문자를 저장하는 데 사용됩니다 (Latin1 범위).

코드 포인트가 더 높은 문자의 경우 255는 다른 인코딩이 존재합니다. JavaScript는 DOMString이라는 문자열 인 부호 당 16 비트 (UTF-16)를 사용합니다. 유니 코드는 최대 0x10fffff의 코드 포인트를 처리 할 수 ​​있습니다. 즉, 여러 셀에 걸쳐 여러 비트를 저장하는 방법이 있어야합니다.

String.fromCodePoint(0x10000).length == 2

UTF-16은 서로 게이트 쌍을 사용하여 두 개의 16 비트 셀에 20 비트를 저장합니다. 제 높은 대리로 시작 110110xxxxxxxxxx 와 하부 번째 110111xxxxxxxxxx . 유니 코드는이를 위해 자신의 비행기를 예약했습니다 : https://unicode-table.com/de/#high-surrogates

문자를 바이트 (Latin1 범위)로 저장하려면 표준화 된 프로시 저는 UTF-8을 사용하십시오 .

죄송하지만이 기능을 self 구현하는 다른 방법은 없다고 생각합니다.

function stringToUTF8(str)
{
    let bytes = [];

    for(let character of str)
    {
        let code = character.codePointAt(0);

        if(code <= 127)
        {
            let byte1 = code;

            bytes.push(byte1);
        }
        else if(code <= 2047)
        {
            let byte1 = 0xC0 | (code >> 6);
            let byte2 = 0x80 | (code & 0x3F);

            bytes.push(byte1, byte2);
        }
        else if(code <= 65535)
        {
            let byte1 = 0xE0 | (code >> 12);
            let byte2 = 0x80 | ((code >> 6) & 0x3F);
            let byte3 = 0x80 | (code & 0x3F);

            bytes.push(byte1, byte2, byte3);
        }
        else if(code <= 2097151)
        {
            let byte1 = 0xF0 | (code >> 18);
            let byte2 = 0x80 | ((code >> 12) & 0x3F);
            let byte3 = 0x80 | ((code >> 6) & 0x3F);
            let byte4 = 0x80 | (code & 0x3F);

            bytes.push(byte1, byte2, byte3, byte4);
        }
    }

    return bytes;
}

function utf8ToString(bytes, fallback)
{
    let valid = undefined;
    let codePoint = undefined;
    let codeBlocks = [0, 0, 0, 0];

    let result = "";

    for(let offset = 0; offset < bytes.length; offset++)
    {
        let byte = bytes[offset];

        if((byte & 0x80) == 0x00)
        {
            codeBlocks[0] = byte & 0x7F;

            codePoint = codeBlocks[0];
        }
        else if((byte & 0xE0) == 0xC0)
        {
            codeBlocks[0] = byte & 0x1F;

            byte = bytes[++offset];
            if(offset >= bytes.length || (byte & 0xC0) != 0x80) { valid = false; break; }

            codeBlocks[1] = byte & 0x3F;

            codePoint = (codeBlocks[0] << 6) + codeBlocks[1];
        }
        else if((byte & 0xF0) == 0xE0)
        {
            codeBlocks[0] = byte & 0xF;

            for(let blockIndex = 1; blockIndex <= 2; blockIndex++)
            {
                byte = bytes[++offset];
                if(offset >= bytes.length || (byte & 0xC0) != 0x80) { valid = false; break; }

                codeBlocks[blockIndex] = byte & 0x3F;
            }
            if(valid === false) { break; }

            codePoint = (codeBlocks[0] << 12) + (codeBlocks[1] << 6) + codeBlocks[2];
        }
        else if((byte & 0xF8) == 0xF0)
        {
            codeBlocks[0] = byte & 0x7;

            for(let blockIndex = 1; blockIndex <= 3; blockIndex++)
            {
                byte = bytes[++offset];
                if(offset >= bytes.length || (byte & 0xC0) != 0x80) { valid = false; break; }

                codeBlocks[blockIndex] = byte & 0x3F;
            }
            if(valid === false) { break; }

            codePoint = (codeBlocks[0] << 18) + (codeBlocks[1] << 12) + (codeBlocks[2] << 6) + (codeBlocks[3]);
        }
        else
        {
            valid = false; break;
        }

        result += String.fromCodePoint(codePoint);
    }

    if(valid === false)
    {
        if(!fallback)
        {
            throw new TypeError("Malformed utf-8 encoding.");
        }

        result = "";

        for(let offset = 0; offset != bytes.length; offset++)
        {
            result += String.fromCharCode(bytes[offset] & 0xFF);
        }
    }

    return result;
}

function decodeBase64(text, binary)
{
    if(/[^0-9a-zA-Z\+\/\=]/.test(text)) { throw new TypeError("The string to be decoded contains characters outside of the valid base64 range."); }

    let codePointA = 'A'.codePointAt(0);
    let codePointZ = 'Z'.codePointAt(0);
    let codePointa = 'a'.codePointAt(0);
    let codePointz = 'z'.codePointAt(0);
    let codePointZero = '0'.codePointAt(0);
    let codePointNine = '9'.codePointAt(0);
    let codePointPlus = '+'.codePointAt(0);
    let codePointSlash = '/'.codePointAt(0);

    function getCodeFromKey(key)
    {
        let keyCode = key.codePointAt(0);

        if(keyCode >= codePointA && keyCode <= codePointZ)
        {
            return keyCode - codePointA;
        }
        else if(keyCode >= codePointa && keyCode <= codePointz)
        {
            return keyCode + 26 - codePointa;
        }
        else if(keyCode >= codePointZero && keyCode <= codePointNine)
        {
            return keyCode + 52 - codePointZero;
        }
        else if(keyCode == codePointPlus)
        {
            return 62;
        }
        else if(keyCode == codePointSlash)
        {
            return 63;
        }

        return undefined;
    }

    let codes = Array.from(text).map(character => getCodeFromKey(character));

    let bytesLength = Math.ceil(codes.length / 4) * 3;

    if(codes[codes.length - 2] == undefined) { bytesLength = bytesLength - 2; } else if(codes[codes.length - 1] == undefined) { bytesLength--; }

    let bytes = new Uint8Array(bytesLength);

    for(let offset = 0, index = 0; offset < bytes.length;)
    {
        let code1 = codes[index++];
        let code2 = codes[index++];
        let code3 = codes[index++];
        let code4 = codes[index++];

        let byte1 = (code1 << 2) | (code2 >> 4);
        let byte2 = ((code2 & 0xf) << 4) | (code3 >> 2);
        let byte3 = ((code3 & 0x3) << 6) | code4;

        bytes[offset++] = byte1;
        bytes[offset++] = byte2;
        bytes[offset++] = byte3;
    }

    if(binary) { return bytes; }

    return utf8ToString(bytes, true);
}

function encodeBase64(bytes) {
    if (bytes === undefined || bytes === null) {
        return '';
    }
    if (bytes instanceof Array) {
        bytes = bytes.filter(item => {
            return Number.isFinite(item) && item >= 0 && item <= 255;
        });
    }

    if (
        !(
            bytes instanceof Uint8Array ||
            bytes instanceof Uint8ClampedArray ||
            bytes instanceof Array
        )
    ) {
        if (typeof bytes === 'string') {
            const str = bytes;
            bytes = Array.from(unescape(encodeURIComponent(str))).map(ch =>
                ch.codePointAt(0)
            );
        } else {
            throw new TypeError('bytes must be of type Uint8Array or String.');
        }
    }

    const keys = [
        'A',
        'B',
        'C',
        'D',
        'E',
        'F',
        'G',
        'H',
        'I',
        'J',
        'K',
        'L',
        'M',
        'N',
        'O',
        'P',
        'Q',
        'R',
        'S',
        'T',
        'U',
        'V',
        'W',
        'X',
        'Y',
        'Z',
        'a',
        'b',
        'c',
        'd',
        'e',
        'f',
        'g',
        'h',
        'i',
        'j',
        'k',
        'l',
        'm',
        'n',
        'o',
        'p',
        'q',
        'r',
        's',
        't',
        'u',
        'v',
        'w',
        'x',
        'y',
        'z',
        '0',
        '1',
        '2',
        '3',
        '4',
        '5',
        '6',
        '7',
        '8',
        '9',
        '+',
        '/'
    ];
    const fillKey = '=';

    let byte1;
    let byte2;
    let byte3;
    let sign1 = ' ';
    let sign2 = ' ';
    let sign3 = ' ';
    let sign4 = ' ';

    let result = '';

    for (let index = 0; index < bytes.length; ) {
        let fillUpAt = 0;

        // tslint:disable:no-increment-decrement
        byte1 = bytes[index++];
        byte2 = bytes[index++];
        byte3 = bytes[index++];

        if (byte2 === undefined) {
            byte2 = 0;
            fillUpAt = 2;
        }

        if (byte3 === undefined) {
            byte3 = 0;
            if (!fillUpAt) {
                fillUpAt = 3;
            }
        }

        // tslint:disable:no-bitwise
        sign1 = keys[byte1 >> 2];
        sign2 = keys[((byte1 & 0x3) << 4) + (byte2 >> 4)];
        sign3 = keys[((byte2 & 0xf) << 2) + (byte3 >> 6)];
        sign4 = keys[byte3 & 0x3f];

        if (fillUpAt > 0) {
            if (fillUpAt <= 2) {
                sign3 = fillKey;
            }
            if (fillUpAt <= 3) {
                sign4 = fillKey;
            }
        }

        result += sign1 + sign2 + sign3 + sign4;

        if (fillUpAt) {
            break;
        }
    }

    return result;
}

let base64 = encodeBase64("\u{1F604}"); // unicode code point escapes for smiley
let str = decodeBase64(base64);

console.log("base64", base64);
console.log("str", str);

document.body.innerText = str;

사용 방법: decodeBase64(encodeBase64("\u{1F604}"))

데모 : https://jsfiddle.net/qrLadeb8/


답변

이 문제를 직접 만났습니다.

먼저 코드를 약간 수정하십시오.

var download = "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
                  +"<"+this.gamesave.tagName+">"
                  +this.xml.firstChild.innerHTML
                  +"</"+this.gamesave.tagName+">";

this.loader.src = "data:application/x-forcedownload;base64,"+
                  btoa(download);

그런 다음 좋아하는 웹 검사기를 사용하고 this.loader.src를 할당하는 코드 줄에 중단 점을 지정한 다음 다음 코드를 실행합니다.

for (var i = 0; i < download.length; i++) {
  if (download[i].charCodeAt(0) > 255) {
    console.warn('found character ' + download[i].charCodeAt(0) + ' "' + download[i] + '" at position ' + i);
  }
}

응용 프로그램에 따라 데이터를 수정할 것이므로 범위를 벗어난 문자를 바꾸거나 작동하지 않을 수 있습니다. btoa 메서드를 사용하는 유니 코드 문자에 대한 MDN 참고 사항을 참조하십시오.

https://developer.mozilla.org/en-US/docs/Web/API/window.btoa