1use std::borrow::Cow;
4
5use rupg_common::{Error, Result, SqlState};
6use rupg_types::Value;
7
8use crate::generated::single_byte::TABLES;
9use crate::{Call, Kernel, bad_value, not_yet};
10
11const NAMEDATALEN: usize = 64;
13
14const NAMES: [&str; 42] = [
16 "SQL_ASCII",
17 "EUC_JP",
18 "EUC_CN",
19 "EUC_KR",
20 "EUC_TW",
21 "EUC_JIS_2004",
22 "UTF8",
23 "",
24 "LATIN1",
25 "LATIN2",
26 "LATIN3",
27 "LATIN4",
28 "LATIN5",
29 "LATIN6",
30 "LATIN7",
31 "LATIN8",
32 "LATIN9",
33 "LATIN10",
34 "WIN1256",
35 "WIN1258",
36 "WIN866",
37 "WIN874",
38 "KOI8R",
39 "WIN1251",
40 "WIN1252",
41 "ISO_8859_5",
42 "ISO_8859_6",
43 "ISO_8859_7",
44 "ISO_8859_8",
45 "WIN1250",
46 "WIN1253",
47 "WIN1254",
48 "WIN1255",
49 "WIN1257",
50 "KOI8U",
51 "SJIS",
52 "BIG5",
53 "GBK",
54 "UHC",
55 "GB18030",
56 "JOHAB",
57 "SHIFT_JIS_2004",
58];
59
60const ALIASES: &[(&str, &str)] = &[
62 ("abc", "WIN1258"),
63 ("alt", "WIN866"),
64 ("big5", "BIG5"),
65 ("euccn", "EUC_CN"),
66 ("eucjis2004", "EUC_JIS_2004"),
67 ("eucjp", "EUC_JP"),
68 ("euckr", "EUC_KR"),
69 ("euctw", "EUC_TW"),
70 ("gb18030", "GB18030"),
71 ("gbk", "GBK"),
72 ("iso88591", "LATIN1"),
73 ("iso885910", "LATIN6"),
74 ("iso885913", "LATIN7"),
75 ("iso885914", "LATIN8"),
76 ("iso885915", "LATIN9"),
77 ("iso885916", "LATIN10"),
78 ("iso88592", "LATIN2"),
79 ("iso88593", "LATIN3"),
80 ("iso88594", "LATIN4"),
81 ("iso88595", "ISO_8859_5"),
82 ("iso88596", "ISO_8859_6"),
83 ("iso88597", "ISO_8859_7"),
84 ("iso88598", "ISO_8859_8"),
85 ("iso88599", "LATIN5"),
86 ("johab", "JOHAB"),
87 ("koi8", "KOI8R"),
88 ("koi8r", "KOI8R"),
89 ("koi8u", "KOI8U"),
90 ("latin1", "LATIN1"),
91 ("latin10", "LATIN10"),
92 ("latin2", "LATIN2"),
93 ("latin3", "LATIN3"),
94 ("latin4", "LATIN4"),
95 ("latin5", "LATIN5"),
96 ("latin6", "LATIN6"),
97 ("latin7", "LATIN7"),
98 ("latin8", "LATIN8"),
99 ("latin9", "LATIN9"),
100 ("mskanji", "SJIS"),
101 ("shiftjis", "SJIS"),
102 ("shiftjis2004", "SHIFT_JIS_2004"),
103 ("sjis", "SJIS"),
104 ("sqlascii", "SQL_ASCII"),
105 ("tcvn", "WIN1258"),
106 ("tcvn5712", "WIN1258"),
107 ("uhc", "UHC"),
108 ("unicode", "UTF8"),
109 ("utf8", "UTF8"),
110 ("vscii", "WIN1258"),
111 ("win", "WIN1251"),
112 ("win1250", "WIN1250"),
113 ("win1251", "WIN1251"),
114 ("win1252", "WIN1252"),
115 ("win1253", "WIN1253"),
116 ("win1254", "WIN1254"),
117 ("win1255", "WIN1255"),
118 ("win1256", "WIN1256"),
119 ("win1257", "WIN1257"),
120 ("win1258", "WIN1258"),
121 ("win866", "WIN866"),
122 ("win874", "WIN874"),
123 ("win932", "SJIS"),
124 ("win936", "GBK"),
125 ("win949", "UHC"),
126 ("win950", "BIG5"),
127 ("windows1250", "WIN1250"),
128 ("windows1251", "WIN1251"),
129 ("windows1252", "WIN1252"),
130 ("windows1253", "WIN1253"),
131 ("windows1254", "WIN1254"),
132 ("windows1255", "WIN1255"),
133 ("windows1256", "WIN1256"),
134 ("windows1257", "WIN1257"),
135 ("windows1258", "WIN1258"),
136 ("windows866", "WIN866"),
137 ("windows874", "WIN874"),
138 ("windows932", "SJIS"),
139 ("windows936", "GBK"),
140 ("windows949", "UHC"),
141 ("windows950", "BIG5"),
142];
143
144#[must_use]
146pub fn encoding(name: &str) -> Option<&'static str> {
147 if name.len() >= NAMEDATALEN {
148 return None;
149 }
150 let key: String =
151 name.chars().filter(char::is_ascii_alphanumeric).map(|c| c.to_ascii_lowercase()).collect();
152 ALIASES.iter().find(|(alias, _)| *alias == key).map(|(_, name)| *name)
153}
154
155pub(crate) fn valid_server_encoding(name: &str) -> bool {
157 let last = NAMES.iter().position(|n| *n == "KOI8U").unwrap_or_default();
158 usize::try_from(char_to_encoding(name)).is_ok_and(|n| n <= last)
159}
160
161#[must_use]
163pub fn char_to_encoding(name: &str) -> i32 {
164 encoding(name)
165 .and_then(|name| NAMES.iter().position(|n| *n == name))
166 .and_then(|n| i32::try_from(n).ok())
167 .unwrap_or(-1)
168}
169
170#[must_use]
172pub fn encoding_to_char(number: i64) -> &'static str {
173 usize::try_from(number).ok().and_then(|n| NAMES.get(n)).copied().unwrap_or_default()
174}
175
176const DATABASE: &str = "UTF8";
178
179const DIRECT: [(&str, &str); 10] = [
181 ("KOI8R", "WIN1251"),
182 ("KOI8R", "WIN866"),
183 ("WIN866", "WIN1251"),
184 ("ISO_8859_5", "KOI8R"),
185 ("ISO_8859_5", "WIN1251"),
186 ("ISO_8859_5", "WIN866"),
187 ("EUC_JP", "SJIS"),
188 ("EUC_TW", "BIG5"),
189 ("LATIN2", "WIN1250"),
190 ("EUC_JIS_2004", "SHIFT_JIS_2004"),
191];
192
193fn table(encoding: &str) -> Option<&'static [u16; 128]> {
195 TABLES.iter().find(|(name, _)| *name == encoding).map(|(_, table)| table)
196}
197
198fn single_byte(encoding: &str) -> bool {
200 matches!(encoding, "SQL_ASCII" | "LATIN1") || table(encoding).is_some()
201}
202
203fn decode_byte(encoding: &str, b: u8) -> Option<char> {
205 if b < 0x80 || encoding == "LATIN1" {
206 return Some(char::from(b));
207 }
208 let c = table(encoding)?[usize::from(b - 0x80)];
209 if c == 0 { None } else { char::from_u32(u32::from(c)) }
210}
211
212fn encode_char(encoding: &str, c: char) -> Option<u8> {
214 if let Ok(b) = u8::try_from(c)
215 && (b < 0x80 || encoding == "LATIN1")
216 {
217 return Some(b);
218 }
219 let c = u16::try_from(u32::from(c)).ok().filter(|c| *c != 0)?;
220 let at = table(encoding)?.iter().position(|x| *x == c)?;
221 u8::try_from(at + 0x80).ok()
222}
223
224fn utf8_length(b: u8) -> usize {
226 match b {
227 0x00..=0x7f => 1,
228 0xc0..=0xdf => 2,
229 0xe0..=0xef => 3,
230 0xf0..=0xf7 => 4,
231 _ => 1,
232 }
233}
234
235fn byte_text(bytes: &[u8]) -> String {
237 bytes.iter().take(8).map(|b| format!("0x{b:02x}")).collect::<Vec<_>>().join(" ")
238}
239
240fn char_length(encoding: &str, rest: &[u8]) -> usize {
242 let length = if encoding == "UTF8" { rest.first().map_or(1, |b| utf8_length(*b)) } else { 1 };
243 length.min(rest.len())
244}
245
246pub(crate) fn invalid(encoding: &str, rest: &[u8]) -> Error {
248 let bytes = &rest[..char_length(encoding, rest)];
249 Error::new(
250 SqlState::CHARACTER_NOT_IN_REPERTOIRE,
251 format!("invalid byte sequence for encoding \"{encoding}\": {}", byte_text(bytes)),
252 )
253}
254
255fn untranslatable(from: &str, to: &str, rest: &[u8]) -> Error {
257 let bytes = &rest[..char_length(from, rest)];
258 Error::new(
259 SqlState::UNTRANSLATABLE_CHARACTER,
260 format!(
261 "character with byte sequence {} in encoding \"{from}\" has no equivalent in encoding \"{to}\"",
262 byte_text(bytes)
263 ),
264 )
265}
266
267fn utf8_valid_length(bytes: &[u8]) -> usize {
269 let end = bytes.iter().position(|b| *b == 0).unwrap_or(bytes.len());
270 match std::str::from_utf8(&bytes[..end]) {
271 Ok(_) => end,
272 Err(e) => e.valid_up_to(),
273 }
274}
275
276fn verify(encoding: &str, bytes: &[u8]) -> Result<usize> {
278 if encoding == "UTF8" {
279 let ok = utf8_valid_length(bytes);
280 if ok < bytes.len() {
281 return Err(invalid(encoding, &bytes[ok..]));
282 }
283 return Ok(bytes.iter().filter(|b| (**b & 0xc0) != 0x80).count());
284 }
285 if !single_byte(encoding) && !bytes.is_ascii() {
287 return Err(not_yet(format_args!("a string in the encoding {encoding}")));
288 }
289 match bytes.iter().position(|b| *b == 0) {
290 Some(at) => Err(invalid(encoding, &bytes[at..])),
291 None => Ok(bytes.len()),
292 }
293}
294
295fn conversion<'a>(bytes: &'a [u8], from: &str, to: &str) -> Result<Cow<'a, [u8]>> {
297 if bytes.is_empty() || from == to || to == "SQL_ASCII" {
298 return Ok(Cow::Borrowed(bytes));
299 }
300 if from == "SQL_ASCII" {
301 verify(to, bytes)?;
302 return Ok(Cow::Borrowed(bytes));
303 }
304 if from == "UTF8" && single_byte(to) {
305 let text = std::str::from_utf8(bytes).map_err(|_| invalid(from, bytes))?;
306 let mut out = Vec::with_capacity(bytes.len());
307 for (at, c) in text.char_indices() {
308 out.push(encode_char(to, c).ok_or_else(|| untranslatable(from, to, &bytes[at..]))?);
309 }
310 return Ok(Cow::Owned(out));
311 }
312 if to == "UTF8" && single_byte(from) {
313 let mut out = String::with_capacity(bytes.len());
314 for (at, b) in bytes.iter().enumerate() {
315 out.push(decode_byte(from, *b).ok_or_else(|| untranslatable(from, to, &bytes[at..]))?);
316 }
317 return Ok(Cow::Owned(out.into_bytes()));
318 }
319 let direct = DIRECT.iter().any(|&(a, b)| (a, b) == (from, to) || (b, a) == (from, to));
320 if direct || from == "UTF8" || to == "UTF8" {
321 if bytes.is_ascii() {
323 return Ok(Cow::Borrowed(bytes));
324 }
325 return Err(not_yet(format_args!("the conversion from {from} to {to}")));
326 }
327 Err(Error::new(
328 SqlState::UNDEFINED_FUNCTION,
329 format!("default conversion function for encoding \"{from}\" to \"{to}\" does not exist"),
330 ))
331}
332
333fn convert_bytes<'a>(bytes: &'a [u8], from: &str, to: &str) -> Result<Cow<'a, [u8]>> {
335 let source = encoding(from).ok_or_else(|| {
336 Error::new(
337 SqlState::INVALID_PARAMETER_VALUE,
338 format!("invalid source encoding name \"{from}\""),
339 )
340 })?;
341 let destination = encoding(to).ok_or_else(|| {
342 Error::new(
343 SqlState::INVALID_PARAMETER_VALUE,
344 format!("invalid destination encoding name \"{to}\""),
345 )
346 })?;
347 verify(source, bytes)?;
348 conversion(bytes, source, destination)
349}
350
351fn pg_convert(_: &Call<'_>, args: &[Value]) -> Result<Value> {
353 let [Value::Bytea(bytes), from, to] = args else { return Err(bad_value()) };
354 let (from, to) = (from.as_str().ok_or_else(bad_value)?, to.as_str().ok_or_else(bad_value)?);
355 Ok(Value::Bytea(convert_bytes(bytes, from, to)?.into_owned()))
356}
357
358fn pg_convert_from(_: &Call<'_>, args: &[Value]) -> Result<Value> {
360 let [Value::Bytea(bytes), from] = args else { return Err(bad_value()) };
361 let out = convert_bytes(bytes, from.as_str().ok_or_else(bad_value)?, DATABASE)?;
362 let text = String::from_utf8(out.into_owned()).map_err(|_| bad_value())?;
363 Ok(Value::Text(text))
364}
365
366fn pg_convert_to(_: &Call<'_>, args: &[Value]) -> Result<Value> {
368 let [Value::Text(text), to] = args else { return Err(bad_value()) };
369 let out = convert_bytes(text.as_bytes(), DATABASE, to.as_str().ok_or_else(bad_value)?)?;
370 Ok(Value::Bytea(out.into_owned()))
371}
372
373fn length_in_encoding(_: &Call<'_>, args: &[Value]) -> Result<Value> {
375 let [Value::Bytea(bytes), name] = args else { return Err(bad_value()) };
376 let name = name.as_str().ok_or_else(bad_value)?;
377 let encoding = encoding(name).ok_or_else(|| {
378 Error::new(SqlState::INVALID_PARAMETER_VALUE, format!("invalid encoding name \"{name}\""))
379 })?;
380 let count = verify(encoding, bytes)?;
381 Ok(Value::Int4(i32::try_from(count).map_err(|_| bad_value())?))
382}
383
384fn to_ascii(text: &str, encoding: &str) -> Result<Value> {
386 let (ascii, start): (&[u8], u8) = match encoding {
387 "LATIN1" => (b" cL Y \"Ca -R 'u ., ?AAAAAAACEEEEIIII NOOOOOxOUUUUYTBaaaaaaaceeeeiiii nooooo/ouuuuyty", 160),
388 "LATIN2" => (b" A L LS \"SSTZ-ZZ a,l'ls ,sstz\"zzRAAAALCCCEEEEIIDDNNOOOOxRUUUUYTBraaaalccceeeeiiddnnoooo/ruuuuyt.", 160),
389 "LATIN9" => (b" cL YS sCa -R Zu .z EeY?AAAAAAACEEEEIIII NOOOOOxOUUUUYTBaaaaaaaceeeeiiii nooooo/ouuuuyty", 160),
390 "WIN1250" => (b" ' \" %S<STZZ `'\"\".-- s>stzz L A \"CS -RZ ,l'u .,as L\"lzRAAAALCCCEEEEIIDDNNOOOOxRUUUUYTBraaaalccceeeeiiddnnoooo/ruuuuyt ", 128),
391 _ => {
392 return Err(Error::new(
393 SqlState::FEATURE_NOT_SUPPORTED,
394 format!("encoding conversion from {encoding} to ASCII not supported"),
395 ));
396 }
397 };
398 let out: String = text
399 .bytes()
400 .map(|b| match b {
401 0..0x80 => char::from(b),
402 b if b < start => ' ',
403 b => char::from(ascii[usize::from(b - start)]),
404 })
405 .collect();
406 Ok(Value::Text(out))
407}
408
409fn to_ascii_default(_: &Call<'_>, args: &[Value]) -> Result<Value> {
411 to_ascii(args.first().and_then(Value::as_str).ok_or_else(bad_value)?, DATABASE)
412}
413
414fn to_ascii_encname(_: &Call<'_>, args: &[Value]) -> Result<Value> {
416 let [text, name] = args else { return Err(bad_value()) };
417 let name = name.as_str().ok_or_else(bad_value)?;
418 let encoding = encoding(name).ok_or_else(|| {
419 Error::new(SqlState::UNDEFINED_OBJECT, format!("{name} is not a valid encoding name"))
420 })?;
421 to_ascii(text.as_str().ok_or_else(bad_value)?, encoding)
422}
423
424fn to_ascii_enc(_: &Call<'_>, args: &[Value]) -> Result<Value> {
426 let [text, number] = args else { return Err(bad_value()) };
427 let number = number.as_i64().ok_or_else(bad_value)?;
428 let encoding = encoding_to_char(number);
429 if encoding.is_empty() {
430 return Err(Error::new(
431 SqlState::UNDEFINED_OBJECT,
432 format!("{number} is not a valid encoding code"),
433 ));
434 }
435 to_ascii(text.as_str().ok_or_else(bad_value)?, encoding)
436}
437
438fn pg_char_to_encoding(_: &Call<'_>, args: &[Value]) -> Result<Value> {
440 let name = args.first().and_then(Value::as_str).ok_or_else(bad_value)?;
441 Ok(Value::Int4(char_to_encoding(name)))
442}
443
444fn pg_encoding_to_char(_: &Call<'_>, args: &[Value]) -> Result<Value> {
446 let number = args.first().and_then(Value::as_i64).ok_or_else(bad_value)?;
447 Ok(Value::text(encoding_to_char(number)))
448}
449
450pub(crate) fn by_src(src: &str) -> Option<Kernel> {
452 Some(match src {
453 "PG_char_to_encoding" => pg_char_to_encoding,
454 "PG_encoding_to_char" => pg_encoding_to_char,
455 "pg_convert" => pg_convert,
456 "pg_convert_from" => pg_convert_from,
457 "pg_convert_to" => pg_convert_to,
458 "length_in_encoding" => length_in_encoding,
459 "to_ascii_default" => to_ascii_default,
460 "to_ascii_encname" => to_ascii_encname,
461 "to_ascii_enc" => to_ascii_enc,
462 _ => return None,
463 })
464}
465
466#[cfg(test)]
467mod tests {
468 use super::*;
469
470 #[test]
471 fn names_and_numbers() {
472 assert_eq!(char_to_encoding("UTF8"), 6);
473 assert_eq!(char_to_encoding("latin-1"), 8);
474 assert_eq!(char_to_encoding("mule_internal"), -1);
475 assert_eq!(char_to_encoding(""), -1);
476 assert_eq!(encoding_to_char(6), "UTF8");
477 assert_eq!(encoding_to_char(7), "");
478 assert_eq!(encoding_to_char(41), "SHIFT_JIS_2004");
479 assert_eq!(encoding_to_char(42), "");
480 assert_eq!(encoding_to_char(-1), "");
481 for (number, name) in NAMES.iter().enumerate().filter(|(_, n)| !n.is_empty()) {
482 assert_eq!(usize::try_from(char_to_encoding(name)).ok(), Some(number));
483 }
484 }
485
486 #[test]
487 fn single_byte_tables() {
488 for (name, table) in &TABLES {
489 assert!(NAMES.contains(name), "{name}");
490 for (n, c) in table.iter().enumerate().filter(|(_, c)| **c != 0) {
491 let b = u8::try_from(n + 0x80).unwrap();
492 let c = char::from_u32(u32::from(*c)).unwrap();
493 assert_eq!(decode_byte(name, b), Some(c));
494 assert_eq!(encode_char(name, c), Some(b), "{name} {c}");
495 }
496 }
497 }
498
499 #[test]
500 fn conversions() {
501 let to = |s: &str, e: &str| convert_bytes(s.as_bytes(), "UTF8", e).map(Cow::into_owned);
502 assert_eq!(to("aé€", "LATIN9").unwrap(), b"a\xe9\xa4");
503 assert_eq!(to("é", "latin1").unwrap(), b"\xe9");
504 let message = to("€", "LATIN1").unwrap_err().to_string();
505 assert!(message.contains("0xe2 0x82 0xac in encoding \"UTF8\" has no"), "{message}");
506 let from = |b: &[u8], e: &str| convert_bytes(b, e, "UTF8").map(Cow::into_owned);
507 assert_eq!(from(b"\xa4", "LATIN9").unwrap(), "€".as_bytes());
508 assert!(from(b"\x81", "WIN1252").is_err());
509 assert!(from(b"a\x00", "LATIN1").is_err());
510 assert_eq!(utf8_valid_length(b"ab\xc3\x28"), 2);
511 assert_eq!(utf8_valid_length(b"a\x00b"), 1);
512 assert_eq!(verify("UTF8", "aé€".as_bytes()).unwrap(), 3);
513 }
514}