Subversion Repositories Kolibri OS

Rev

Go to most recent revision | Blame | Last modification | View Log | Download | RSS feed

  1. /*
  2.  * This file is part of LibCSS.
  3.  * Licensed under the MIT License,
  4.  *                http://www.opensource.org/licenses/mit-license.php
  5.  * Copyright 2008 John-Mark Bell <jmb@netsurf-browser.org>
  6.  */
  7.  
  8. #include <stdbool.h>
  9. #include <string.h>
  10.  
  11. #include <parserutils/charset/mibenum.h>
  12.  
  13. #include "charset/detect.h"
  14. #include "utils/utils.h"
  15.  
  16. static parserutils_error css_charset_read_bom_or_charset(const uint8_t *data,
  17.                 size_t len, uint16_t *mibenum);
  18. static parserutils_error try_utf32_charset(const uint8_t *data,
  19.                 size_t len, uint16_t *result);
  20. static parserutils_error try_utf16_charset(const uint8_t *data,
  21.                 size_t len, uint16_t *result);
  22. static parserutils_error try_ascii_compatible_charset(const uint8_t *data,
  23.                 size_t len, uint16_t *result);
  24.  
  25. /**
  26.  * Extract a charset from a chunk of data
  27.  *
  28.  * \param data     Pointer to buffer containing data
  29.  * \param len      Buffer length
  30.  * \param mibenum  Pointer to location containing current MIB enum
  31.  * \param source   Pointer to location containing current charset source
  32.  * \return PARSERUTILS_OK on success, appropriate error otherwise
  33.  *
  34.  * ::mibenum and ::source will be updated on exit
  35.  *
  36.  * CSS 2.1 $4.4
  37.  */
  38. parserutils_error css__charset_extract(const uint8_t *data, size_t len,
  39.                 uint16_t *mibenum, uint32_t *source)
  40. {
  41.         parserutils_error error;
  42.         uint16_t charset = 0;
  43.  
  44.         if (data == NULL || mibenum == NULL || source == NULL)
  45.                 return PARSERUTILS_BADPARM;
  46.  
  47.         /* If the charset was dictated by the client, we've nothing to detect */
  48.         if (*source == CSS_CHARSET_DICTATED)
  49.                 return PARSERUTILS_OK;
  50.  
  51.         /* Look for a BOM and/or @charset */
  52.         error = css_charset_read_bom_or_charset(data, len, &charset);
  53.         if (error != PARSERUTILS_OK)
  54.                 return error;
  55.  
  56.         if (charset != 0) {
  57.                 *mibenum = charset;
  58.                 *source = CSS_CHARSET_DOCUMENT;
  59.  
  60.                 return PARSERUTILS_OK;
  61.         }
  62.  
  63.         /* If we've already got a charset from the linking mechanism or
  64.          * referring document, then we've nothing further to do */
  65.         if (*source != CSS_CHARSET_DEFAULT)
  66.                 return PARSERUTILS_OK;
  67.  
  68.         /* We've not yet found a charset, so use the default fallback */
  69.         charset = parserutils_charset_mibenum_from_name("UTF-8", SLEN("UTF-8"));
  70.  
  71.         *mibenum = charset;
  72.         *source = CSS_CHARSET_DEFAULT;
  73.  
  74.         return PARSERUTILS_OK;
  75. }
  76.  
  77.  
  78. /**
  79.  * Inspect the beginning of a buffer of data for the presence of a
  80.  * UTF Byte Order Mark and/or an @charset rule
  81.  *
  82.  * \param data     Pointer to buffer containing data
  83.  * \param len      Buffer length
  84.  * \param mibenum  Pointer to location to receive MIB enum
  85.  * \return PARSERUTILS_OK on success, appropriate error otherwise
  86.  */
  87. parserutils_error css_charset_read_bom_or_charset(const uint8_t *data,
  88.                 size_t len, uint16_t *mibenum)
  89. {
  90.         parserutils_error error;
  91.         uint16_t charset = 0;
  92.  
  93.         if (data == NULL)
  94.                 return PARSERUTILS_BADPARM;
  95.  
  96.         /* We require at least 4 bytes of data */
  97.         if (len < 4)
  98.                 return PARSERUTILS_NEEDDATA;
  99.  
  100.  
  101.         /* Look for BOM */
  102.         if (data[0] == 0x00 && data[1] == 0x00 &&
  103.                         data[2] == 0xFE && data[3] == 0xFF) {
  104.                 charset = parserutils_charset_mibenum_from_name("UTF-32BE",
  105.                                 SLEN("UTF-32BE"));
  106.         } else if (data[0] == 0xFF && data[1] == 0xFE &&
  107.                         data[2] == 0x00 && data[3] == 0x00) {
  108.                 charset = parserutils_charset_mibenum_from_name("UTF-32LE",
  109.                                 SLEN("UTF-32LE"));
  110.         } else if (data[0] == 0xFE && data[1] == 0xFF) {
  111.                 charset = parserutils_charset_mibenum_from_name("UTF-16BE",
  112.                                 SLEN("UTF-16BE"));
  113.         } else if (data[0] == 0xFF && data[1] == 0xFE) {
  114.                 charset = parserutils_charset_mibenum_from_name("UTF-16LE",
  115.                                 SLEN("UTF-16LE"));
  116.         } else if (data[0] == 0xEF && data[1] == 0xBB && data[2] == 0xBF) {
  117.                 charset = parserutils_charset_mibenum_from_name("UTF-8",
  118.                                 SLEN("UTF-8"));
  119.         }
  120.  
  121.         /* BOM beats @charset.
  122.          * UAs differ here, but none appear to match the spec.
  123.          * The spec indicates that any @charset present in conjunction with a
  124.          * BOM, should match the BOM. In reality, it appears UAs just take the
  125.          * BOM as gospel and ignore any @charset rule. The w3c CSS validator
  126.          * appears to do the same (at the least, it doesn't complain about a
  127.          * mismatch).
  128.          */
  129.         if (charset != 0) {
  130.                 *mibenum = charset;
  131.                 return PARSERUTILS_OK;
  132.         }
  133.  
  134.         error = try_utf32_charset(data, len, &charset);
  135.         if (error == PARSERUTILS_OK && charset != 0) {
  136.                 *mibenum = charset;
  137.                 return PARSERUTILS_OK;
  138.         }
  139.  
  140.         error = try_utf16_charset(data, len, &charset);
  141.         if (error == PARSERUTILS_OK && charset != 0) {
  142.                 *mibenum = charset;
  143.                 return PARSERUTILS_OK;
  144.         }
  145.  
  146.         error = try_ascii_compatible_charset(data, len, &charset);
  147.         if (error == PARSERUTILS_OK)
  148.                 *mibenum = charset;
  149.  
  150.         return PARSERUTILS_OK;
  151. }
  152.  
  153. static parserutils_error try_utf32_charset(const uint8_t *data,
  154.                 size_t len, uint16_t *result)
  155. {
  156.         uint16_t charset = 0;
  157.  
  158. #define CHARSET_BE "\0\0\0@\0\0\0c\0\0\0h\0\0\0a\0\0\0r\0\0\0s\0\0\0e\0\0\0t\0\0\0 \0\0\0\""
  159. #define CHARSET_LE "@\0\0\0c\0\0\0h\0\0\0a\0\0\0r\0\0\0s\0\0\0e\0\0\0t\0\0\0 \0\0\0\"\0\0\0"
  160.  
  161.         if (len <= SLEN(CHARSET_LE))
  162.                 return PARSERUTILS_NEEDDATA;
  163.  
  164.         /* Look for @charset, assuming UTF-32 source data */
  165.         if (memcmp(data, CHARSET_LE, SLEN(CHARSET_LE)) == 0) {
  166.                 const uint8_t *start = data + SLEN(CHARSET_LE);
  167.                 const uint8_t *end;
  168.                 char buf[8];
  169.                 char *ptr = buf;
  170.  
  171.                 /* Look for "; at end of charset declaration */
  172.                 for (end = start; end < data + len - 4; end += 4) {
  173.                         uint32_t c = end[0] | (end[1] << 8) |
  174.                                      (end[2] << 16) | (end[3] << 24);
  175.  
  176.                         /* Bail if non-ASCII */
  177.                         if (c > 0x007f)
  178.                                 break;
  179.  
  180.                         /* Reached the end? */
  181.                         if (c == '"' && end < data + len - 8) {
  182.                                 uint32_t d = end[4] | (end[5] << 8) |
  183.                                     (end[6] << 16) | (end[7] << 24);
  184.  
  185.                                 if (d == ';')
  186.                                         break;
  187.                         }
  188.  
  189.                         /* Append to buf, if there's space */
  190.                         if ((size_t) (ptr - buf) < sizeof(buf)) {
  191.                                 /* Uppercase */
  192.                                 if ('a' <= c && c <= 'z')
  193.                                         *ptr++ = c & ~0x20;
  194.                                 else
  195.                                         *ptr++ = c;
  196.                         }
  197.                 }
  198.  
  199.                 if (end == data + len - 4) {
  200.                         /* Ran out of input */
  201.                         return PARSERUTILS_NEEDDATA;
  202.                 }
  203.  
  204.                 /* Ensure we have something that looks like UTF-32(LE)? */
  205.                 if ((ptr - buf == SLEN("UTF-32LE") &&
  206.                                 memcmp(buf, "UTF-32LE", ptr - buf) == 0) ||
  207.                                 (ptr - buf == SLEN("UTF-32") &&
  208.                                 memcmp(buf, "UTF-32", ptr - buf) == 0)) {
  209.                         /* Convert to MIB enum */
  210.                         charset = parserutils_charset_mibenum_from_name(
  211.                                         "UTF-32LE", SLEN("UTF-32LE"));
  212.                 }
  213.         } else if (memcmp(data, CHARSET_BE, SLEN(CHARSET_BE)) == 0) {
  214.                 const uint8_t *start = data + SLEN(CHARSET_BE);
  215.                 const uint8_t *end;
  216.                 char buf[8];
  217.                 char *ptr = buf;
  218.  
  219.                 /* Look for "; at end of charset declaration */
  220.                 for (end = start; end < data + len - 4; end += 4) {
  221.                         uint32_t c = end[3] | (end[2] << 8) |
  222.                                      (end[1] << 16) | (end[0] << 24);
  223.  
  224.                         /* Bail if non-ASCII */
  225.                         if (c > 0x007f)
  226.                                 break;
  227.  
  228.                         /* Reached the end? */
  229.                         if (c == '"' && end < data + len - 8) {
  230.                                 uint32_t d = end[7] | (end[6] << 8) |
  231.                                     (end[5] << 16) | (end[4] << 24);
  232.  
  233.                                 if (d == ';')
  234.                                         break;
  235.                         }
  236.  
  237.                         /* Append to buf, if there's space */
  238.                         if ((size_t) (ptr - buf) < sizeof(buf)) {
  239.                                 /* Uppercase */
  240.                                 if ('a' <= c && c <= 'z')
  241.                                         *ptr++ = c & ~0x20;
  242.                                 else
  243.                                         *ptr++ = c;
  244.                         }
  245.                 }
  246.  
  247.                 if (end == data + len - 4) {
  248.                         /* Ran out of input */
  249.                         return PARSERUTILS_NEEDDATA;
  250.                 }
  251.  
  252.                 /* Ensure we have something that looks like UTF-32(BE)? */
  253.                 if ((ptr - buf == SLEN("UTF-32BE") &&
  254.                                 memcmp(buf, "UTF-32BE", ptr - buf) == 0) ||
  255.                                 (ptr - buf == SLEN("UTF-32") &&
  256.                                 memcmp(buf, "UTF-32", ptr - buf) == 0)) {
  257.                         /* Convert to MIB enum */
  258.                         charset = parserutils_charset_mibenum_from_name(
  259.                                         "UTF-32BE", SLEN("UTF-32BE"));
  260.                 }
  261.         }
  262.  
  263. #undef CHARSET_LE
  264. #undef CHARSET_BE
  265.  
  266.         *result = charset;
  267.  
  268.         return PARSERUTILS_OK;
  269. }
  270.  
  271. static parserutils_error try_utf16_charset(const uint8_t *data,
  272.                 size_t len, uint16_t *result)
  273. {
  274.         uint16_t charset = 0;
  275.  
  276. #define CHARSET_BE "\0@\0c\0h\0a\0r\0s\0e\0t\0 \0\""
  277. #define CHARSET_LE "@\0c\0h\0a\0r\0s\0e\0t\0 \0\"\0"
  278.  
  279.         if (len <= SLEN(CHARSET_LE))
  280.                 return PARSERUTILS_NEEDDATA;
  281.  
  282.         /* Look for @charset, assuming UTF-16 source data */
  283.         if (memcmp(data, CHARSET_LE, SLEN(CHARSET_LE)) == 0) {
  284.                 const uint8_t *start = data + SLEN(CHARSET_LE);
  285.                 const uint8_t *end;
  286.                 char buf[8];
  287.                 char *ptr = buf;
  288.  
  289.                 /* Look for "; at end of charset declaration */
  290.                 for (end = start; end < data + len - 2; end += 2) {
  291.                         uint32_t c = end[0] | (end[1] << 8);
  292.  
  293.                         /* Bail if non-ASCII */
  294.                         if (c > 0x007f)
  295.                                 break;
  296.  
  297.                         /* Reached the end? */
  298.                         if (c == '"' && end < data + len - 4) {
  299.                                 uint32_t d = end[2] | (end[3] << 8);
  300.  
  301.                                 if (d == ';')
  302.                                         break;
  303.                         }
  304.  
  305.                         /* Append to buf, if there's space */
  306.                         if ((size_t) (ptr - buf) < sizeof(buf)) {
  307.                                 /* Uppercase */
  308.                                 if ('a' <= c && c <= 'z')
  309.                                         *ptr++ = c & ~0x20;
  310.                                 else
  311.                                         *ptr++ = c;
  312.                         }
  313.                 }
  314.  
  315.                 if (end == data + len - 2) {
  316.                         /* Ran out of input */
  317.                         return PARSERUTILS_NEEDDATA;
  318.                 }
  319.  
  320.                 /* Ensure we have something that looks like UTF-16(LE)? */
  321.                 if ((ptr - buf == SLEN("UTF-16LE") &&
  322.                                 memcmp(buf, "UTF-16LE", ptr - buf) == 0) ||
  323.                                 (ptr - buf == SLEN("UTF-16") &&
  324.                                 memcmp(buf, "UTF-16", ptr - buf) == 0)) {
  325.                         /* Convert to MIB enum */
  326.                         charset = parserutils_charset_mibenum_from_name(
  327.                                         "UTF-16LE", SLEN("UTF-16LE"));
  328.                 }
  329.         } else if (memcmp(data, CHARSET_BE, SLEN(CHARSET_BE)) == 0) {
  330.                 const uint8_t *start = data + SLEN(CHARSET_BE);
  331.                 const uint8_t *end;
  332.                 char buf[8];
  333.                 char *ptr = buf;
  334.  
  335.                 /* Look for "; at end of charset declaration */
  336.                 for (end = start; end < data + len - 2; end += 2) {
  337.                         uint32_t c = end[1] | (end[0] << 8);
  338.  
  339.                         /* Bail if non-ASCII */
  340.                         if (c > 0x007f)
  341.                                 break;
  342.  
  343.                         /* Reached the end? */
  344.                         if (c == '"' && end < data + len - 4) {
  345.                                 uint32_t d = end[3] | (end[2] << 8);
  346.  
  347.                                 if (d == ';')
  348.                                         break;
  349.                         }
  350.  
  351.                         /* Append to buf, if there's space */
  352.                         if ((size_t) (ptr - buf) < sizeof(buf)) {
  353.                                 /* Uppercase */
  354.                                 if ('a' <= c && c <= 'z')
  355.                                         *ptr++ = c & ~0x20;
  356.                                 else
  357.                                         *ptr++ = c;
  358.                         }
  359.                 }
  360.  
  361.                 if (end == data + len - 2) {
  362.                         /* Ran out of input */
  363.                         return PARSERUTILS_NEEDDATA;
  364.                 }
  365.  
  366.                 /* Ensure we have something that looks like UTF-16(BE)? */
  367.                 if ((ptr - buf == SLEN("UTF-16BE") &&
  368.                                 memcmp(buf, "UTF-16BE", ptr - buf) == 0) ||
  369.                                 (ptr - buf == SLEN("UTF-16") &&
  370.                                 memcmp(buf, "UTF-16", ptr - buf) == 0)) {
  371.                         /* Convert to MIB enum */
  372.                         charset = parserutils_charset_mibenum_from_name(
  373.                                         "UTF-16BE", SLEN("UTF-16BE"));
  374.                 }
  375.         }
  376.  
  377. #undef CHARSET_LE
  378. #undef CHARSET_BE
  379.  
  380.         *result = charset;
  381.  
  382.         return PARSERUTILS_OK;
  383. }
  384.  
  385. parserutils_error try_ascii_compatible_charset(const uint8_t *data, size_t len,
  386.                 uint16_t *result)
  387. {
  388.         uint16_t charset = 0;
  389.  
  390. #define CHARSET "@charset \""
  391.  
  392.         if (len <= SLEN(CHARSET))
  393.                 return PARSERUTILS_NEEDDATA;
  394.  
  395.         /* Look for @charset, assuming ASCII-compatible source data */
  396.         if (memcmp(data, CHARSET, SLEN(CHARSET)) == 0) {
  397.                 const uint8_t *start = data + SLEN(CHARSET);
  398.                 const uint8_t *end;
  399.  
  400.                 /* Look for "; at end of charset declaration */
  401.                 for (end = start; end < data + len; end++) {
  402.                         if (*end == '"' && end < data + len - 1 &&
  403.                                         *(end + 1) == ';')
  404.                                 break;
  405.                 }
  406.  
  407.                 if (end == data + len) {
  408.                         /* Ran out of input */
  409.                         return PARSERUTILS_NEEDDATA;
  410.                 }
  411.  
  412.                 /* Convert to MIB enum */
  413.                 charset = parserutils_charset_mibenum_from_name(
  414.                                 (const char *) start,  end - start);
  415.  
  416.                 /* Any non-ASCII compatible charset must be ignored, as
  417.                  * we've just used an ASCII parser to read it. */
  418.                 if (charset == parserutils_charset_mibenum_from_name(
  419.                                         "UTF-32", SLEN("UTF-32")) ||
  420.                         charset == parserutils_charset_mibenum_from_name(
  421.                                         "UTF-32LE", SLEN("UTF-32LE")) ||
  422.                         charset == parserutils_charset_mibenum_from_name(
  423.                                         "UTF-32BE", SLEN("UTF-32BE")) ||
  424.                         charset == parserutils_charset_mibenum_from_name(
  425.                                         "UTF-16", SLEN("UTF-16")) ||
  426.                         charset == parserutils_charset_mibenum_from_name(
  427.                                         "UTF-16LE", SLEN("UTF-16LE")) ||
  428.                         charset == parserutils_charset_mibenum_from_name(
  429.                                         "UTF-16BE", SLEN("UTF-16BE"))) {
  430.  
  431.                         charset = 0;
  432.                 }
  433.         }
  434.  
  435. #undef CHARSET
  436.  
  437.         *result = charset;
  438.  
  439.         return PARSERUTILS_OK;
  440. }
  441.