| 1 | |
| 2 | |
| 3 | * unicode.c
|
| 4 | *
|
| 5 | * PURPOSE
|
| 6 | * Routines for converting between UTF-8 and OSTA Compressed Unicode.
|
| 7 | * Also handles filename mangling
|
| 8 | *
|
| 9 | * DESCRIPTION
|
| 10 | * OSTA Compressed Unicode is explained in the OSTA UDF specification.
|
| 11 | * http://www.osta.org/
|
| 12 | * UTF-8 is explained in the IETF RFC XXXX.
|
| 13 | * ftp://ftp.internic.net/rfc/rfcxxxx.txt
|
| 14 | *
|
| 15 |
|
| 16 |
|
| 17 | #include "udfdecl.h"
|
| 18 |
|
| 19 | #include <linux/hex.h>
|
| 20 | #include <linux/kernel.h>
|
| 21 | #include <linux/string.h>
|
| 22 | #include <linux/nls.h>
|
| 23 | #include <linux/crc-itu-t.h>
|
| 24 | #include <linux/slab.h>
|
| 25 |
|
| 26 | #include "udf_sb.h"
|
| 27 |
|
| 28 | #define PLANE_SIZE 0x10000
|
| 29 | #define UNICODE_MAX 0x10ffff
|
| 30 | #define SURROGATE_MASK 0xfffff800
|
| 31 | #define SURROGATE_PAIR 0x0000d800
|
| 32 | #define SURROGATE_LOW 0x00000400
|
| 33 | #define SURROGATE_CHAR_BITS 10
|
| 34 | #define SURROGATE_CHAR_MASK ((1 << SURROGATE_CHAR_BITS) - 1)
|
| 35 |
|
| 36 | #define ILLEGAL_CHAR_MARK '_'
|
| 37 | #define EXT_MARK '.'
|
| 38 | #define CRC_MARK '#'
|
| 39 | #define EXT_SIZE 5
|
| 40 |
|
| 41 | #define CRC_LEN 5
|
| 42 |
|
| 43 | static unicode_t get_utf16_char(const uint8_t *str_i, int str_i_max_len,
|
| 44 | int str_i_idx, int u_ch, unicode_t *ret)
|
| 45 | {
|
| 46 | unicode_t c;
|
| 47 | int start_idx = str_i_idx;
|
| 48 |
|
| 49 |
|
| 50 | c = str_i[str_i_idx++];
|
| 51 | if (u_ch > 1)
|
| 52 | c = (c << 8) | str_i[str_i_idx++];
|
| 53 | if ((c & SURROGATE_MASK) == SURROGATE_PAIR) {
|
| 54 | unicode_t next;
|
| 55 |
|
| 56 |
|
| 57 | if (str_i_idx >= str_i_max_len) {
|
| 58 | c = UNICODE_MAX + 1;
|
| 59 | goto out;
|
| 60 | }
|
| 61 |
|
| 62 |
|
| 63 | if (c & SURROGATE_LOW) {
|
| 64 | c = UNICODE_MAX + 1;
|
| 65 | goto out;
|
| 66 | }
|
| 67 |
|
| 68 | WARN_ON_ONCE(u_ch != 2);
|
| 69 | next = str_i[str_i_idx++] << 8;
|
| 70 | next |= str_i[str_i_idx++];
|
| 71 | if ((next & SURROGATE_MASK) != SURROGATE_PAIR ||
|
| 72 | !(next & SURROGATE_LOW)) {
|
| 73 | c = UNICODE_MAX + 1;
|
| 74 | goto out;
|
| 75 | }
|
| 76 |
|
| 77 | c = PLANE_SIZE +
|
| 78 | ((c & SURROGATE_CHAR_MASK) << SURROGATE_CHAR_BITS) +
|
| 79 | (next & SURROGATE_CHAR_MASK);
|
| 80 | }
|
| 81 | out:
|
| 82 | *ret = c;
|
| 83 | return str_i_idx - start_idx;
|
| 84 | }
|
| 85 |
|
| 86 |
|
| 87 | static int udf_name_conv_char(uint8_t *str_o, int str_o_max_len,
|
| 88 | int *str_o_idx,
|
| 89 | const uint8_t *str_i, int str_i_max_len,
|
| 90 | int *str_i_idx,
|
| 91 | int u_ch, int *needsCRC,
|
| 92 | int (*conv_f)(wchar_t, unsigned char *, int),
|
| 93 | int translate)
|
| 94 | {
|
| 95 | unicode_t c;
|
| 96 | int illChar = 0;
|
| 97 | int len, gotch = 0;
|
| 98 |
|
| 99 | while (!gotch && *str_i_idx < str_i_max_len) {
|
| 100 | if (*str_o_idx >= str_o_max_len) {
|
| 101 | *needsCRC = 1;
|
| 102 | return gotch;
|
| 103 | }
|
| 104 |
|
| 105 | len = get_utf16_char(str_i, str_i_max_len, *str_i_idx, u_ch,
|
| 106 | &c);
|
| 107 |
|
| 108 | if (c == 0 || c > UNICODE_MAX || (conv_f && c > MAX_WCHAR_T) ||
|
| 109 | (translate && c == '/')) {
|
| 110 | illChar = 1;
|
| 111 | if (!translate)
|
| 112 | gotch = 1;
|
| 113 | } else if (illChar)
|
| 114 | break;
|
| 115 | else
|
| 116 | gotch = 1;
|
| 117 | *str_i_idx += len;
|
| 118 | }
|
| 119 | if (illChar) {
|
| 120 | *needsCRC = 1;
|
| 121 | c = ILLEGAL_CHAR_MARK;
|
| 122 | gotch = 1;
|
| 123 | }
|
| 124 | if (gotch) {
|
| 125 | if (conv_f) {
|
| 126 | len = conv_f(c, &str_o[*str_o_idx],
|
| 127 | str_o_max_len - *str_o_idx);
|
| 128 | } else {
|
| 129 | len = utf32_to_utf8(c, &str_o[*str_o_idx],
|
| 130 | str_o_max_len - *str_o_idx);
|
| 131 | if (len < 0)
|
| 132 | len = -ENAMETOOLONG;
|
| 133 | }
|
| 134 |
|
| 135 | if (len >= 0)
|
| 136 | *str_o_idx += len;
|
| 137 | else if (len == -ENAMETOOLONG) {
|
| 138 | *needsCRC = 1;
|
| 139 | gotch = 0;
|
| 140 | } else {
|
| 141 | str_o[(*str_o_idx)++] = ILLEGAL_CHAR_MARK;
|
| 142 | *needsCRC = 1;
|
| 143 | }
|
| 144 | }
|
| 145 | return gotch;
|
| 146 | }
|
| 147 |
|
| 148 | static int udf_name_from_CS0(struct super_block *sb,
|
| 149 | uint8_t *str_o, int str_max_len,
|
| 150 | const uint8_t *ocu, int ocu_len,
|
| 151 | int translate)
|
| 152 | {
|
| 153 | uint32_t c;
|
| 154 | uint8_t cmp_id;
|
| 155 | int idx, len;
|
| 156 | int u_ch;
|
| 157 | int needsCRC = 0;
|
| 158 | int ext_i_len, ext_max_len;
|
| 159 | int str_o_len = 0;
|
| 160 | int ext_o_len = 0;
|
| 161 | int ext_crc_len = 0;
|
| 162 | int i_ext = -1;
|
| 163 | int o_crc = 0;
|
| 164 | unsigned short valueCRC;
|
| 165 | uint8_t ext[EXT_SIZE * NLS_MAX_CHARSET_SIZE + 1];
|
| 166 | uint8_t crc[CRC_LEN];
|
| 167 | int (*conv_f)(wchar_t, unsigned char *, int);
|
| 168 |
|
| 169 | if (str_max_len <= 0)
|
| 170 | return 0;
|
| 171 |
|
| 172 | if (ocu_len == 0) {
|
| 173 | memset(str_o, 0, str_max_len);
|
| 174 | return 0;
|
| 175 | }
|
| 176 |
|
| 177 | if (UDF_SB(sb)->s_nls_map)
|
| 178 | conv_f = UDF_SB(sb)->s_nls_map->uni2char;
|
| 179 | else
|
| 180 | conv_f = NULL;
|
| 181 |
|
| 182 | cmp_id = ocu[0];
|
| 183 | if (cmp_id != 8 && cmp_id != 16) {
|
| 184 | memset(str_o, 0, str_max_len);
|
| 185 | pr_err("unknown compression code (%u)\n", cmp_id);
|
| 186 | return -EINVAL;
|
| 187 | }
|
| 188 | u_ch = cmp_id >> 3;
|
| 189 |
|
| 190 | ocu++;
|
| 191 | ocu_len--;
|
| 192 |
|
| 193 | if (ocu_len % u_ch) {
|
| 194 | pr_err("incorrect filename length (%d)\n", ocu_len + 1);
|
| 195 | return -EINVAL;
|
| 196 | }
|
| 197 |
|
| 198 | if (translate) {
|
| 199 |
|
| 200 | for (idx = ocu_len - u_ch, ext_i_len = 0;
|
| 201 | (idx >= 0) && (ext_i_len < EXT_SIZE);
|
| 202 | idx -= u_ch, ext_i_len++) {
|
| 203 | c = ocu[idx];
|
| 204 | if (u_ch > 1)
|
| 205 | c = (c << 8) | ocu[idx + 1];
|
| 206 |
|
| 207 | if (c == EXT_MARK) {
|
| 208 | if (ext_i_len)
|
| 209 | i_ext = idx;
|
| 210 | break;
|
| 211 | }
|
| 212 | }
|
| 213 | if (i_ext >= 0) {
|
| 214 |
|
| 215 | ext_max_len = min_t(int, sizeof(ext), str_max_len);
|
| 216 | ext[ext_o_len++] = EXT_MARK;
|
| 217 | idx = i_ext + u_ch;
|
| 218 | while (udf_name_conv_char(ext, ext_max_len, &ext_o_len,
|
| 219 | ocu, ocu_len, &idx,
|
| 220 | u_ch, &needsCRC,
|
| 221 | conv_f, translate)) {
|
| 222 | if ((ext_o_len + CRC_LEN) < str_max_len)
|
| 223 | ext_crc_len = ext_o_len;
|
| 224 | }
|
| 225 | }
|
| 226 | }
|
| 227 |
|
| 228 | idx = 0;
|
| 229 | while (1) {
|
| 230 | if (translate && (idx == i_ext)) {
|
| 231 | if (str_o_len > (str_max_len - ext_o_len))
|
| 232 | needsCRC = 1;
|
| 233 | break;
|
| 234 | }
|
| 235 |
|
| 236 | if (!udf_name_conv_char(str_o, str_max_len, &str_o_len,
|
| 237 | ocu, ocu_len, &idx,
|
| 238 | u_ch, &needsCRC, conv_f, translate))
|
| 239 | break;
|
| 240 |
|
| 241 | if (translate &&
|
| 242 | (str_o_len <= (str_max_len - ext_o_len - CRC_LEN)))
|
| 243 | o_crc = str_o_len;
|
| 244 | }
|
| 245 |
|
| 246 | if (translate) {
|
| 247 | if (str_o_len > 0 && str_o_len <= 2 && str_o[0] == '.' &&
|
| 248 | (str_o_len == 1 || str_o[1] == '.'))
|
| 249 | needsCRC = 1;
|
| 250 | if (needsCRC) {
|
| 251 | str_o_len = o_crc;
|
| 252 | valueCRC = crc_itu_t(0, ocu, ocu_len);
|
| 253 | crc[0] = CRC_MARK;
|
| 254 | crc[1] = hex_asc_upper_hi(valueCRC >> 8);
|
| 255 | crc[2] = hex_asc_upper_lo(valueCRC >> 8);
|
| 256 | crc[3] = hex_asc_upper_hi(valueCRC);
|
| 257 | crc[4] = hex_asc_upper_lo(valueCRC);
|
| 258 | len = min_t(int, CRC_LEN, str_max_len - str_o_len);
|
| 259 | memcpy(&str_o[str_o_len], crc, len);
|
| 260 | str_o_len += len;
|
| 261 | ext_o_len = ext_crc_len;
|
| 262 | }
|
| 263 | if (ext_o_len > 0) {
|
| 264 | memcpy(&str_o[str_o_len], ext, ext_o_len);
|
| 265 | str_o_len += ext_o_len;
|
| 266 | }
|
| 267 | }
|
| 268 |
|
| 269 | return str_o_len;
|
| 270 | }
|
| 271 |
|
| 272 | static int udf_name_to_CS0(struct super_block *sb,
|
| 273 | uint8_t *ocu, int ocu_max_len,
|
| 274 | const uint8_t *str_i, int str_len)
|
| 275 | {
|
| 276 | int i, len;
|
| 277 | unsigned int max_val;
|
| 278 | int u_len, u_ch;
|
| 279 | unicode_t uni_char;
|
| 280 | int (*conv_f)(const unsigned char *, int, wchar_t *);
|
| 281 |
|
| 282 | if (ocu_max_len <= 0)
|
| 283 | return 0;
|
| 284 |
|
| 285 | if (UDF_SB(sb)->s_nls_map)
|
| 286 | conv_f = UDF_SB(sb)->s_nls_map->char2uni;
|
| 287 | else
|
| 288 | conv_f = NULL;
|
| 289 |
|
| 290 | memset(ocu, 0, ocu_max_len);
|
| 291 | ocu[0] = 8;
|
| 292 | max_val = 0xff;
|
| 293 | u_ch = 1;
|
| 294 |
|
| 295 | try_again:
|
| 296 | u_len = 1;
|
| 297 | for (i = 0; i < str_len; i += len) {
|
| 298 |
|
| 299 | if (u_len + u_ch > ocu_max_len)
|
| 300 | return 0;
|
| 301 | if (conv_f) {
|
| 302 | wchar_t wchar;
|
| 303 |
|
| 304 | len = conv_f(&str_i[i], str_len - i, &wchar);
|
| 305 | if (len > 0)
|
| 306 | uni_char = wchar;
|
| 307 | } else {
|
| 308 | len = utf8_to_utf32(&str_i[i], str_len - i,
|
| 309 | &uni_char);
|
| 310 | }
|
| 311 |
|
| 312 | if (len <= 0 || uni_char > UNICODE_MAX) {
|
| 313 | len = 1;
|
| 314 | uni_char = '?';
|
| 315 | }
|
| 316 |
|
| 317 | if (uni_char > max_val) {
|
| 318 | unicode_t c;
|
| 319 |
|
| 320 | if (max_val == 0xff) {
|
| 321 | max_val = 0xffff;
|
| 322 | ocu[0] = 0x10;
|
| 323 | u_ch = 2;
|
| 324 | goto try_again;
|
| 325 | }
|
| 326 | |
| 327 | * Use UTF-16 encoding for chars outside we
|
| 328 | * cannot encode directly.
|
| 329 |
|
| 330 | if (u_len + 2 * u_ch > ocu_max_len)
|
| 331 | return 0;
|
| 332 |
|
| 333 | uni_char -= PLANE_SIZE;
|
| 334 | c = SURROGATE_PAIR |
|
| 335 | ((uni_char >> SURROGATE_CHAR_BITS) &
|
| 336 | SURROGATE_CHAR_MASK);
|
| 337 | ocu[u_len++] = (uint8_t)(c >> 8);
|
| 338 | ocu[u_len++] = (uint8_t)(c & 0xff);
|
| 339 | uni_char = SURROGATE_PAIR | SURROGATE_LOW |
|
| 340 | (uni_char & SURROGATE_CHAR_MASK);
|
| 341 | }
|
| 342 |
|
| 343 | if (max_val == 0xffff)
|
| 344 | ocu[u_len++] = (uint8_t)(uni_char >> 8);
|
| 345 | ocu[u_len++] = (uint8_t)(uni_char & 0xff);
|
| 346 | }
|
| 347 |
|
| 348 | return u_len;
|
| 349 | }
|
| 350 |
|
| 351 | |
| 352 | * Convert CS0 dstring to output charset. Warning: This function may truncate
|
| 353 | * input string if it is too long as it is used for informational strings only
|
| 354 | * and it is better to truncate the string than to refuse mounting a media.
|
| 355 |
|
| 356 | int udf_dstrCS0toChar(struct super_block *sb, uint8_t *utf_o, int o_len,
|
| 357 | const uint8_t *ocu_i, int i_len)
|
| 358 | {
|
| 359 | int s_len = 0;
|
| 360 |
|
| 361 | if (i_len > 0) {
|
| 362 | s_len = ocu_i[i_len - 1];
|
| 363 | if (s_len >= i_len) {
|
| 364 | pr_warn("incorrect dstring lengths (%d/%d),"
|
| 365 | " truncating\n", s_len, i_len);
|
| 366 | s_len = i_len - 1;
|
| 367 |
|
| 368 | if (ocu_i[0] == 16)
|
| 369 | s_len -= (s_len - 1) & 2;
|
| 370 | }
|
| 371 | }
|
| 372 |
|
| 373 | return udf_name_from_CS0(sb, utf_o, o_len, ocu_i, s_len, 0);
|
| 374 | }
|
| 375 |
|
| 376 | int udf_get_filename(struct super_block *sb, const uint8_t *sname, int slen,
|
| 377 | uint8_t *dname, int dlen)
|
| 378 | {
|
| 379 | int ret;
|
| 380 |
|
| 381 | if (!slen)
|
| 382 | return -EIO;
|
| 383 |
|
| 384 | if (dlen <= 0)
|
| 385 | return 0;
|
| 386 |
|
| 387 | ret = udf_name_from_CS0(sb, dname, dlen, sname, slen, 1);
|
| 388 |
|
| 389 | if (ret == 0)
|
| 390 | ret = -EINVAL;
|
| 391 | return ret;
|
| 392 | }
|
| 393 |
|
| 394 | int udf_put_filename(struct super_block *sb, const uint8_t *sname, int slen,
|
| 395 | uint8_t *dname, int dlen)
|
| 396 | {
|
| 397 | return udf_name_to_CS0(sb, dname, dlen, sname, slen);
|
| 398 | }
|
| 399 |
|