Skip to content

Commit 7460423

Browse files
committed
feat(Base64 String Converter): handle text encoding other than utf8
Fix CorentinTh#1777
1 parent dfa836b commit 7460423

3 files changed

Lines changed: 131 additions & 18 deletions

File tree

locales/en.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -593,6 +593,7 @@ tools:
593593
placeholder-the-decoded-string-will-be-here: The decoded string will be here
594594
tag-copy-base64: Copy base64
595595
tag-copy-decoded-string: Copy decoded string
596+
text-encoding: 'Text Encoding:'
596597
toml-to-yaml:
597598
title: TOML to YAML
598599
description: Parse and convert TOML to YAML.

src/tools/base64-string-converter/base64-string-converter.vue

Lines changed: 109 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,36 +3,121 @@ import { useI18n } from 'vue-i18n';
33
import { useCopy } from '@/composable/copy';
44
import { base64ToText, isValidBase64, textToBase64 } from '@/utils/base64';
55
import { withDefaultOnError } from '@/utils/defaults';
6-
import { useITStorage, useQueryParam } from '@/composable/queryParams';
6+
import { useITStorage, useQueryParam, useQueryParamOrStorage } from '@/composable/queryParams';
77
88
const { t } = useI18n();
99
1010
const encodeUrlSafe = useITStorage('base64-string-converter:encode-url-safe', false);
1111
const decodeUrlSafe = useITStorage('base64-string-converter:decode-url-safe', false);
1212
13+
const encoding = useQueryParamOrStorage({
14+
storageName: 'base64-string-converter:encoding',
15+
name: 'encoding',
16+
defaultValue: 'utf8',
17+
});
18+
1319
const textInput = useQueryParam({ tool: 'base64-string-converter', name: 'text', defaultValue: '' });
14-
const base64Output = computed(() => textToBase64(textInput.value, { makeUrlSafe: encodeUrlSafe.value }));
15-
const { copy: copyTextBase64 } = useCopy({ source: base64Output, text: t('tools.base64-string-converter.texts.text-base64-string-copied-to-the-clipboard') });
20+
const base64Output = computed(() =>
21+
textToBase64(textInput.value, { makeUrlSafe: encodeUrlSafe.value, encoding: encoding.value }),
22+
);
23+
const { copy: copyTextBase64 } = useCopy({
24+
source: base64Output,
25+
text: t('tools.base64-string-converter.texts.text-base64-string-copied-to-the-clipboard'),
26+
});
1627
1728
const base64Input = useQueryParam({ tool: 'base64-string-converter', name: 'base64', defaultValue: '' });
1829
const textOutput = computed(() =>
19-
withDefaultOnError(() => base64ToText(base64Input.value.trim(), { makeUrlSafe: decodeUrlSafe.value }), ''),
30+
withDefaultOnError(
31+
() => base64ToText(base64Input.value.trim(), { makeUrlSafe: decodeUrlSafe.value, encoding: encoding.value }),
32+
'',
33+
),
2034
);
21-
const { copy: copyText } = useCopy({ source: textOutput, text: t('tools.base64-string-converter.texts.text-string-copied-to-the-clipboard') });
35+
const { copy: copyText } = useCopy({
36+
source: textOutput,
37+
text: t('tools.base64-string-converter.texts.text-string-copied-to-the-clipboard'),
38+
});
2239
const b64ValidationRules = [
2340
{
2441
message: t('tools.base64-string-converter.texts.message-invalid-base64-string'),
2542
validator: (value: string) => isValidBase64(value.trim(), { makeUrlSafe: decodeUrlSafe.value }),
2643
},
2744
];
2845
const b64ValidationWatch = [decodeUrlSafe];
46+
47+
const encodings = [
48+
{ label: 'UTF-8', value: 'utf8' },
49+
{ label: 'UTF-8 (Alias: UTF8)', value: 'utf-8' },
50+
51+
{ label: 'UTF-16LE', value: 'utf16le' },
52+
{ label: 'UTF-16BE', value: 'utf16be' },
53+
54+
{ label: 'UTF-7', value: 'utf7' },
55+
{ label: 'UTF-7 (IMAP)', value: 'utf7imap' },
56+
57+
{ label: 'UTF-32LE', value: 'utf32le' },
58+
{ label: 'UTF-32BE', value: 'utf32be' },
59+
60+
{ label: 'ISO-8859-1 (Latin-1)', value: 'latin1' },
61+
{ label: 'ISO-8859-2 (Central European)', value: 'iso-8859-2' },
62+
{ label: 'ISO-8859-3 (South European)', value: 'iso-8859-3' },
63+
{ label: 'ISO-8859-4 (North European)', value: 'iso-8859-4' },
64+
{ label: 'ISO-8859-5 (Cyrillic)', value: 'iso-8859-5' },
65+
{ label: 'ISO-8859-6 (Arabic)', value: 'iso-8859-6' },
66+
{ label: 'ISO-8859-7 (Greek)', value: 'iso-8859-7' },
67+
{ label: 'ISO-8859-8 (Hebrew)', value: 'iso-8859-8' },
68+
{ label: 'ISO-8859-9 (Turkish)', value: 'iso-8859-9' },
69+
{ label: 'ISO-8859-10 (Nordic)', value: 'iso-8859-10' },
70+
{ label: 'ISO-8859-13 (Baltic)', value: 'iso-8859-13' },
71+
{ label: 'ISO-8859-14 (Celtic)', value: 'iso-8859-14' },
72+
{ label: 'ISO-8859-15 (Latin-9)', value: 'iso-8859-15' },
73+
{ label: 'ISO-8859-16 (Romanian)', value: 'iso-8859-16' },
74+
75+
{ label: 'Windows-1250 (Central European)', value: 'win1250' },
76+
{ label: 'Windows-1251 (Cyrillic)', value: 'win1251' },
77+
{ label: 'Windows-1252 (Western European)', value: 'win1252' },
78+
{ label: 'Windows-1253 (Greek)', value: 'win1253' },
79+
{ label: 'Windows-1254 (Turkish)', value: 'win1254' },
80+
{ label: 'Windows-1255 (Hebrew)', value: 'win1255' },
81+
{ label: 'Windows-1256 (Arabic)', value: 'win1256' },
82+
{ label: 'Windows-1257 (Baltic)', value: 'win1257' },
83+
{ label: 'Windows-1258 (Vietnamese)', value: 'win1258' },
84+
85+
{ label: 'KOI8-R (Russian)', value: 'koi8-r' },
86+
{ label: 'KOI8-U (Ukrainian)', value: 'koi8-u' },
87+
88+
{ label: 'GBK (Simplified Chinese)', value: 'gbk' },
89+
{ label: 'GB2312 (Simplified Chinese)', value: 'gb2312' },
90+
{ label: 'GB18030 (Simplified Chinese)', value: 'gb18030' },
91+
92+
{ label: 'Big5 (Traditional Chinese)', value: 'big5' },
93+
94+
{ label: 'Shift-JIS (Japanese)', value: 'shift_jis' },
95+
{ label: 'EUC-JP (Japanese)', value: 'euc-jp' },
96+
97+
{ label: 'MacRoman', value: 'macroman' },
98+
{ label: 'MacGreek', value: 'macgreek' },
99+
{ label: 'MacTurkish', value: 'macturkish' },
100+
{ label: 'MacCyrillic', value: 'maccyrillic' },
101+
{ label: 'MacCentralEurope', value: 'maccentraleurope' },
102+
{ label: 'MacIceland', value: 'maciceland' },
103+
];
29104
</script>
30105

31106
<template>
32107
<c-card :title="t('tools.base64-string-converter.texts.title-string-to-base64')">
33-
<n-form-item :label="t('tools.base64-string-converter.texts.label-encode-url-safe')" label-placement="left">
34-
<n-switch v-model:value="encodeUrlSafe" />
35-
</n-form-item>
108+
<n-space>
109+
<n-form-item :label="t('tools.base64-string-converter.texts.label-encode-url-safe')" label-placement="left">
110+
<n-switch v-model:value="encodeUrlSafe" />
111+
</n-form-item>
112+
<c-select
113+
:label="t('tools.base64-string-converter.texts.text-encoding')"
114+
label-position="left"
115+
style="width: 400px"
116+
v-model:value="encoding"
117+
:options="encodings"
118+
searchable
119+
/>
120+
</n-space>
36121
<c-input-text
37122
v-model:value="textInput"
38123
multiline
@@ -48,7 +133,9 @@ const b64ValidationWatch = [decodeUrlSafe];
48133
:value="base64Output"
49134
multiline
50135
readonly
51-
:placeholder="t('tools.base64-string-converter.texts.placeholder-the-base64-encoding-of-your-string-will-be-here')"
136+
:placeholder="
137+
t('tools.base64-string-converter.texts.placeholder-the-base64-encoding-of-your-string-will-be-here')
138+
"
52139
rows="5"
53140
mb-5
54141
/>
@@ -61,9 +148,19 @@ const b64ValidationWatch = [decodeUrlSafe];
61148
</c-card>
62149

63150
<c-card :title="t('tools.base64-string-converter.texts.title-base64-to-string')">
64-
<n-form-item :label="t('tools.base64-string-converter.texts.label-decode-url-safe')" label-placement="left">
65-
<n-switch v-model:value="decodeUrlSafe" />
66-
</n-form-item>
151+
<n-space>
152+
<n-form-item :label="t('tools.base64-string-converter.texts.label-decode-url-safe')" label-placement="left">
153+
<n-switch v-model:value="decodeUrlSafe" />
154+
</n-form-item>
155+
<c-select
156+
:label="t('tools.base64-string-converter.texts.text-encoding')"
157+
label-position="left"
158+
style="width: 400px"
159+
v-model:value="encoding"
160+
:options="encodings"
161+
searchable
162+
/>
163+
</n-space>
67164
<c-input-text
68165
v-model:value="base64Input"
69166
multiline

src/utils/base64.ts

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,26 @@
1+
import iconv from 'iconv-lite';
12
import { Base64 } from 'js-base64';
23

34
export { textToBase64, base64ToText, isValidBase64, removePotentialDataAndMimePrefix };
45

5-
function textToBase64(str: string, { makeUrlSafe = false }: { makeUrlSafe?: boolean } = {}) {
6+
function textToBase64(
7+
str: string,
8+
{ makeUrlSafe = false, encoding = 'utf8' }: { makeUrlSafe?: boolean; encoding?: string } = {},
9+
) {
10+
if (encoding !== 'utf8') {
11+
// Handle non-UTF-8 encoding if needed
12+
const buffer = iconv.encode(str, encoding);
13+
const encoded = Base64.fromUint8Array(buffer);
14+
return makeUrlSafe ? makeUriSafe(encoded) : encoded;
15+
}
616
const encoded = Base64.encode(str);
717
return makeUrlSafe ? makeUriSafe(encoded) : encoded;
818
}
919

10-
function base64ToText(str: string, { makeUrlSafe = false }: { makeUrlSafe?: boolean } = {}) {
20+
function base64ToText(
21+
str: string,
22+
{ makeUrlSafe = false, encoding = 'utf8' }: { makeUrlSafe?: boolean; encoding?: string } = {},
23+
) {
1124
if (!isValidBase64(str, { makeUrlSafe })) {
1225
throw new Error('Incorrect base64 string');
1326
}
@@ -18,9 +31,12 @@ function base64ToText(str: string, { makeUrlSafe = false }: { makeUrlSafe?: bool
1831
}
1932

2033
try {
34+
if (encoding !== 'utf8') {
35+
// Handle non-UTF-8 encoding if needed
36+
return iconv.decode(Base64.toUint8Array(cleanStr), encoding);
37+
}
2138
return Base64.decode(cleanStr);
22-
}
23-
catch (_) {
39+
} catch (_) {
2440
throw new Error('Incorrect base64 string');
2541
}
2642
}
@@ -41,8 +57,7 @@ function isValidBase64(str: string, { makeUrlSafe = false }: { makeUrlSafe?: boo
4157
return removePotentialPadding(reEncodedBase64) === cleanStr;
4258
}
4359
return reEncodedBase64 === cleanStr.replace(/\s/g, '');
44-
}
45-
catch (err) {
60+
} catch (err) {
4661
return false;
4762
}
4863
}

0 commit comments

Comments
 (0)