PHP: Chuyển đổi bất kỳ chuỗi nào thành UTF-8 mà không cần biết bộ ký tự gốc hoặc ít nhất là thử


146

Tôi có một ứng dụng liên quan đến các khách hàng từ khắp nơi trên thế giới và, một cách tự nhiên, tôi muốn mọi thứ đi vào cơ sở dữ liệu của mình được mã hóa UTF-8.

Vấn đề chính đối với tôi là tôi không biết mã hóa nguồn của bất kỳ chuỗi nào sẽ xảy ra - nó có thể là từ một hộp văn bản (sử dụng <form accept-charset="utf-8">chỉ hữu ích nếu người dùng thực sự được gửi biểu mẫu) hoặc có thể là từ một tệp văn bản được tải lên, vì vậy tôi thực sự không kiểm soát được đầu vào.

Những gì tôi cần là một hàm hoặc lớp đảm bảo các thứ đi vào cơ sở dữ liệu của tôi, càng xa càng tốt, mã hóa UTF-8. Tôi đã thử iconv(mb_detect_encoding($text), "UTF-8", $text); nhưng điều đó có vấn đề (nếu đầu vào là 'hôn thê' thì nó trả về 'hôn phu'). Tôi đã thử rất nhiều thứ = /

Để tải lên tệp, tôi thích ý tưởng yêu cầu người dùng cuối chỉ định mã hóa họ sử dụng và hiển thị cho họ xem trước kết quả sẽ như thế nào, nhưng điều này không giúp chống lại các tin tặc khó chịu (thực tế, nó có thể giúp ích cho cuộc sống của họ dễ dàng hơn một chút).

Tôi đã đọc các câu hỏi SO khác về chủ đề này, nhưng dường như tất cả chúng đều có những khác biệt tinh tế như "Tôi cần phân tích các nguồn cấp RSS" hoặc "Tôi cạo dữ liệu từ các trang web" (hoặc, thực sự, "Bạn không thể").

Nhưng phải có một cái gì đó ít nhất là có một thử !


5
Về cơ bản, định nghĩa không thể hoàn toàn chính xác, trong thực tế, tỷ lệ thành công trong việc đoán mã hóa không xác định là không tuyệt vời. Có thể sử dụng phương pháp phỏng đoán, nhưng nó sẽ chính xác dưới 100% thời gian, tùy thuộc vào vật liệu ít hơn 100%. Bạn cần phải nhận thức được điều đó. Có lẽ ai đó ở đây ít nhất có thể đề xuất một thư viện với các heuristic tốt.
lừa dối

Chắc chắn, tôi biết không có giải pháp hoàn hảo - do đó mong muốn một cái gì đó ít nhất sẽ có một hướng đi tốt.
Grim ...

điều này có thể giúp: stackoverflow.com/q/505562/642173
Melsi

Bạn đã thử sử dụng UTF-8//IGNOREnhư là thông số thứ 2 trong iconv?
cháy

Vâng, đó là những gì tôi đã làm. Rõ ràng là không hoàn hảo, khi đó 'hôn thê' trở thành 'hôn phu', nhưng chắc chắn sẽ tốt hơn. Làm thế nào mà TRANSLIT không hoạt động?
Grim ...

Câu trả lời:


255

Những gì bạn đang yêu cầu là vô cùng khó khăn. Nếu có thể, bắt người dùng chỉ định mã hóa là tốt nhất. Ngăn chặn một cuộc tấn công không nên dễ dàng hơn hoặc khó hơn theo cách đó.

Tuy nhiên, bạn có thể thử làm điều này:

iconv(mb_detect_encoding($text, mb_detect_order(), true), "UTF-8", $text);

Đặt nó thành nghiêm ngặt có thể giúp bạn có được kết quả tốt hơn.


5
Xin vui lòng, hãy xem mb_detect_encodingmã nguồn trong bản phân phối php của bạn (ở đâu đó tại đây: ext / mbopes / libmbfl / mbfl / mbfl_ident.c). Chức năng này hoàn toàn không hoạt động. Đối với một số mã hóa, nó thậm chí còn "return true", lol. Những người khác đang ở trong các chức năng Ctrl + c Ctrl + v. Đó là bởi vì bạn không thể phát hiện mã hóa mà không có một số cách tiếp cận từ điển hoặc thống kê (như của tôi).
Oroboros102

1
Theo cách tôi hiểu, mb_detect_encodingđi qua danh sách các mã hóa được cung cấp và chấp nhận mã hóa đầu tiên không có chuỗi byte không hợp lệ trong chuỗi ... Đối với các mã hóa không có chuỗi byte không hợp lệ như ISO-8859-1, điều đó luôn đúng . Không có phương pháp phỏng đoán "thông minh" và kết quả khác nhau rất nhiều với danh sách (và thứ tự) các mã hóa bạn vượt qua.
wutz

Điều này dường như đang làm việc cho tôi. Người dùng của tôi đã gửi văn bản trên trang utf8 với tinymce, nhưng vì một số lý do không rõ, các ký tự không phải utf8 đôi khi kết thúc trong cơ sở dữ liệu. Điều này đã sửa nó, vì vậy cảm ơn bạn rất nhiều.
giorgio79

@Jeff Day - Cảm ơn vì điều này. Xin thứ lỗi cho sự thiếu hiểu biết của tôi, ý của bạn là 'Đặt nó thành Nghiêm'?
Ash501

[Jeff Day] đang gửi mb_detect_order()mặc dù đó là giá trị mặc định cho thông số này, vì anh ấy muốn đặt phát hiện mã hóa nghiêm ngặt thành đúng (thông số thứ 3) :)
jave.web

28

Ở quê hương Nga, chúng tôi có 4 bảng mã phổ biến, vì vậy câu hỏi của bạn là rất cần ở đây.

Chỉ bằng mã char của các ký hiệu, bạn không thể phát hiện mã hóa, vì các trang mã giao nhau. Một số mật mã trong các ngôn ngữ khác nhau thậm chí có giao lộ đầy đủ. Vì vậy, chúng ta cần một cách tiếp cận khác .

Cách duy nhất để làm việc với các bảng mã không xác định là làm việc với xác suất. Vì vậy, chúng tôi không muốn trả lời câu hỏi "mã hóa văn bản này là gì?", Chúng tôi đang cố gắng hiểu " mã hóa nào có khả năng nhất của văn bản này? ".

Một anh chàng ở đây trong blog công nghệ nổi tiếng của Nga đã phát minh ra cách tiếp cận này:

Xây dựng phạm vi xác suất của mã char trong mỗi mã hóa bạn muốn hỗ trợ. Bạn có thể xây dựng nó bằng một số văn bản lớn trong ngôn ngữ của bạn (ví dụ như một số tiểu thuyết, sử dụng Shakespeare cho tiếng Anh và Tolstoy cho tiếng Nga, lol). Bạn sẽ nhận được smth như thế này:

    encoding_1:
    190 => 0.095249209893009,
    222 => 0.095249209893009,
    ...
    encoding_2:
    239 => 0.095249209893009,
    207 => 0.095249209893009,
    ...
    encoding_N:
    charcode => probabilty

Kế tiếp. Bạn lấy văn bản ở dạng mã hóa không xác định và với mỗi mã hóa trong "từ điển xác suất", bạn tìm kiếm tần số của mọi ký hiệu trong văn bản được mã hóa không xác định. Tổng xác suất của các ký hiệu. Mã hóa với đánh giá lớn hơn có khả năng là người chiến thắng. Kết quả tốt hơn cho các văn bản lớn hơn.

Nếu bạn quan tâm , tôi có thể sẵn sàng giúp bạn với nhiệm vụ này. Chúng tôi có thể tăng đáng kể độ chính xác bằng cách xây dựng danh sách xác suất hai ký tự.

Btw. mb_detect_encoding chắc chắn không hoạt động. Vâng, tất cả. Xin vui lòng, hãy xem mã nguồn mb_detect_encoding trong "ext / mbopes / libmbfl / mbfl / mbfl_ident.c".


11

Có lẽ bạn đã thử điều này nhưng tại sao không sử dụng hàm mb_convert_encoding? Nó sẽ cố gắng tự động phát hiện tập char của văn bản được cung cấp hoặc bạn có thể chuyển cho nó một danh sách.

Ngoài ra, tôi đã cố chạy:

$text = "fiancée";
echo mb_convert_encoding($text, "UTF-8");
echo "<br/><br/>";
echo iconv(mb_detect_encoding($text), "UTF-8", $text);

và kết quả là như nhau cho cả hai. Làm thế nào để bạn thấy rằng văn bản của bạn bị cắt ngắn thành 'fianc'? Nó ở trong DB hay trong trình duyệt?


Trong cơ sở dữ liệu, có vẻ như - tôi vừa thử mã của bạn và tôi đồng ý.
Grim ...

1
Kiểm tra để đảm bảo đối chiếu bạn đã xác định trên bảng / cột là UTF-8.
Alexey Gerasimov

@AlexeyGerasimov Tôi đoán tôi thực sự cần phải điều tra iconv. Tôi đã thử làm một cách gần như thuần túy mb_ *. Bạn nghĩ gì?
Anthony Rutledge

5

Không có cách nào để xác định bộ ký tự của một chuỗi hoàn toàn chính xác. Có nhiều cách để cố gắng đoán bảng mã. Một trong những cách này, và có lẽ / hiện tại là tốt nhất trong PHP, là mb_detect_encoding (). Điều này sẽ quét chuỗi của bạn và tìm kiếm sự xuất hiện của công cụ duy nhất cho bộ ký tự nhất định. Tùy thuộc vào chuỗi của bạn, có thể không có sự xuất hiện có thể phân biệt như vậy.

Lấy bộ ký tự ISO-8859-1 so với ISO-8859-15 ( http://en.wikipedia.org/wiki/ISO/IEC_8859-15#Changes_from_ISO-8859-1 )

Chỉ có một số ít các ký tự khác nhau và để làm cho nó tệ hơn, chúng được biểu thị bằng cùng một byte. Không có cách nào để phát hiện, được cung cấp một chuỗi mà không biết mã hóa, cho dù byte 0xA4 có nghĩa là biểu thị ¤ hoặc € trong chuỗi của bạn, vì vậy không có cách nào để biết chính xác bộ ký tự của nó.

(Lưu ý: bạn có thể thêm yếu tố con người hoặc kỹ thuật quét thậm chí tiên tiến hơn (ví dụ như những gì Oroboros102 gợi ý), để cố gắng tìm ra dựa trên bối cảnh xung quanh, nếu nhân vật nên là ¤ hoặc €, mặc dù điều này có vẻ giống như một cây cầu quá xa)

Có nhiều sự khác biệt dễ phân biệt hơn giữa ví dụ UTF-8 và ISO-8859-1, do đó, vẫn đáng để thử tìm hiểu khi bạn không chắc chắn, mặc dù bạn có thể và không bao giờ nên dựa vào nó là chính xác.

Đọc thú vị: http://kore-nordmann.de/blog/php_charset_encoding_FAQ.html#how-do-i-determine-the-charset-encoding-of-a-opes

Có nhiều cách khác để đảm bảo bộ ký tự chính xác. Liên quan đến các biểu mẫu, cố gắng thực thi UTF-8 càng nhiều càng tốt (kiểm tra người tuyết để đảm bảo rằng bạn gửi sẽ là UTF-8 trong mọi trình duyệt: http://intertwingly.net/blog/2010/07/29/Rails-and -Snowmen ) Điều đó đã được thực hiện, ít nhất bạn có thể chắc chắn rằng mọi văn bản được gửi qua biểu mẫu của bạn là utf_8. Liên quan đến các tệp đã tải lên, hãy thử chạy lệnh unix 'file -i' trên nó thông qua ví dụ exec () (nếu có thể trên máy chủ của bạn) để hỗ trợ phát hiện (sử dụng BOM của tài liệu.) Liên quan đến dữ liệu cạo, bạn có thể đọc các tiêu đề HTTP, mà thường chỉ định bộ ký tự. Khi phân tích tệp XML, hãy xem liệu siêu dữ liệu XML có chứa định nghĩa bộ ký tự không.

Thay vì cố gắng tự động đoán bộ ký tự, trước tiên bạn nên cố gắng tự đảm bảo một bộ ký tự nhất định nếu có thể hoặc cố gắng lấy một định nghĩa từ nguồn bạn nhận được (nếu có thể) trước khi dùng đến phát hiện.


Biểu mẫu và liên kết đăng ký email với dữ liệu được mã hóa. Đó là nơi tôi đang cố gắng để làm cho đầu vào của mình là UTF-8 hoặc không có gì. Bạn nghĩ gì về câu trả lời của tôi? Ý kiến ​​hữu ích được đánh giá cao. Cảm ơn.
Anthony Rutledge

3

Có một số câu trả lời thực sự tốt và cố gắng trả lời câu hỏi của bạn ở đây. Tôi không phải là bậc thầy về mã hóa, nhưng tôi hiểu mong muốn của bạn là có một ngăn xếp UTF-8 thuần túy cho đến cơ sở dữ liệu của bạn. Tôi đã sử dụng utf8mb4mã hóa của MySQL cho các bảng, trường và kết nối.

Tình huống của tôi trở nên sôi nổi "Tôi chỉ muốn các nhà vệ sinh, trình xác nhận, logic kinh doanh và các tuyên bố đã chuẩn bị của mình để đối phó với UTF-8 khi dữ liệu đến từ các biểu mẫu HTML hoặc liên kết đăng ký email." Vì vậy, theo cách đơn giản của tôi, tôi bắt đầu với ý tưởng này:

  1. Cố gắng phát hiện mã hóa: $encodings = ['UTF-8', 'ISO-8859-1', 'ASCII'];
  2. Nếu mã hóa không thể được phát hiện, throw new RuntimeException
  3. Nếu đầu vào là UTF-8, tiếp tục.
  4. Khác, nếu nó là ISO-8859-1hoặcASCII

    a. Cố gắng chuyển đổi sang UTF-8 (chờ, chưa kết thúc)

    b. Phát hiện mã hóa của giá trị chuyển đổi

    c. Nếu mã hóa được báo cáo và giá trị chuyển đổi là cả hai UTF-8, hãy tiếp tục.

    d. Khác,throw new RuntimeException

Từ lớp trừu tượng của tôi Sanitizer

Vệ sinh

    private function isUTF8($encoding, $value)
    {
        return (($encoding === 'UTF-8') && (utf8_encode(utf8_decode($value)) === $value));
    }

    private function utf8tify(&$value)
    {
        $encodings = ['UTF-8', 'ISO-8859-1', 'ASCII'];

        mb_internal_encoding('UTF-8');
        mb_substitute_character(0xfffd); //REPLACEMENT CHARACTER
        mb_detect_order($encodings);

        $stringEncoding = mb_detect_encoding($value, $encodings, true);

        if (!$stringEncoding) {
            $value = null;
            throw new \RuntimeException("Unable to identify character encoding in sanitizer.");
        }

        if ($this->isUTF8($stringEncoding, $value)) {
            return;
        } else {
            $value = mb_convert_encoding($value, 'UTF-8', $stringEncoding);
            $stringEncoding = mb_detect_encoding($value, $encodings, true);

            if ($this->isUTF8($stringEncoding, $value)) {
                return;
            } else {
                $value = null;
                throw new \RuntimeException("Unable to convert character encoding from ISO-8859-1, or ASCII, to UTF-8 in Sanitizer.");
            }
        }

        return;
    }

Người ta có thể đưa ra một lập luận rằng tôi nên tách các mối quan tâm mã hóa khỏi Sanitizerlớp trừu tượng của mình và chỉ cần đưa một Encoderđối tượng vào một thể hiện con cụ thể của Sanitizer. Tuy nhiên, vấn đề chính với cách tiếp cận của tôi là, không có thêm kiến ​​thức, tôi chỉ đơn giản từ chối các loại mã hóa mà tôi không muốn (và tôi đang dựa vào các hàm mb_ ​​* của PHP). Nếu không nghiên cứu thêm, tôi không thể biết liệu điều đó có làm tổn thương một số quần thể hay không (hoặc, nếu tôi bị mất thông tin quan trọng). Vì vậy, tôi cần tìm hiểu thêm. Tôi tìm thấy bài viết này.

Điều mà mọi lập trình viên hoàn toàn, tích cực cần biết về mã hóa và bộ ký tự để làm việc với văn bản

Hơn nữa, điều gì xảy ra khi dữ liệu được mã hóa được thêm vào liên kết đăng ký email của tôi (sử dụng OpenSSLhoặc mcrypt)? Điều này có thể can thiệp vào giải mã? Còn Windows-1252 thì sao? Điều gì về ý nghĩa bảo mật? Việc sử dụng utf8_decode()utf8_encode()trong Sanitizer::isUTF8là đáng ngờ.

Mọi người đã chỉ ra các tính năng ngắn trong các hàm mb_ ​​* của PHP. Tôi không bao giờ mất thời gian để điều tra iconv, nhưng nếu nó hoạt động tốt hơn các hàm mb_ ​​*, hãy cho tôi biết.


Tôi đã tìm thấy điều này, stackoverflow.com/a35321394/1429677 câu trả lời tuyệt vời cho vấn đề này, đây là lib github.com/neitanod/forceutf8
Llewellyn

2

Vấn đề chính đối với tôi là tôi không biết mã hóa nguồn của bất kỳ chuỗi nào sẽ xảy ra - nó có thể là từ một hộp văn bản (sử dụng chỉ hữu ích nếu người dùng thực sự được gửi biểu mẫu) hoặc có thể là từ một tệp văn bản được tải lên, vì vậy tôi thực sự không kiểm soát được đầu vào.

Tôi không nghĩ đó là một vấn đề. Một ứng dụng biết nguồn của đầu vào. Nếu đó là từ một biểu mẫu, hãy sử dụng mã hóa UTF-8 trong trường hợp của bạn. Điều đó làm việc. Chỉ cần xác minh dữ liệu được cung cấp là được mã hóa chính xác (xác nhận). Hãy nhớ rằng không phải tất cả các cơ sở dữ liệu đều hỗ trợ UTF-8 trong phạm vi đầy đủ của nó.

Nếu đó là một tệp bạn sẽ không lưu nó được mã hóa UTF-8 vào cơ sở dữ liệu nhưng ở dạng nhị phân. Khi bạn xuất lại tệp, sử dụng đầu ra nhị phân, thì điều này hoàn toàn minh bạch.

Ý tưởng của bạn thật tuyệt khi người dùng có thể nói mã hóa, dù sao thì anh ấy / cô ấy cũng có thể nói sau khi tải xuống tệp, vì nó là nhị phân.

Vì vậy, tôi phải thừa nhận tôi không thấy một vấn đề cụ thể mà bạn nêu ra với câu hỏi của bạn. Nhưng có lẽ bạn có thể thêm một số chi tiết vấn đề của bạn là gì.


Bạn sẽ thấy và vấn đề với câu trả lời của tôi? Ý kiến ​​xây dựng được đánh giá cao. Cảm ơn.
Anthony Rutledge

1

Bạn có thể thiết lập một bộ số liệu để cố gắng đoán mã hóa nào đang được sử dụng. Một lần nữa, không hoàn hảo, nhưng có thể bắt được một số lỗi từ mb_detect_encoding ().


Vâng, nói tốt về những lần mb_detect_encoding()bỏ lỡ, bạn có nghĩ câu trả lời của tôi có cơ hội của một quả cầu tuyết vào mùa hè ở Sahara không?
Anthony Rutledge

1

Nếu bạn sẵn sàng "mang cái này đến bàn điều khiển", tôi khuyên bạn nên enca. Không giống như khá đơn giản mb_detect_encoding, nó sử dụng "hỗn hợp phân tích cú pháp, phân tích thống kê, đoán và ma thuật đen để xác định mã hóa của chúng" (lol - xem trang con người ). Tuy nhiên, bạn thường phải chuyển ngôn ngữ của tệp đầu vào nếu bạn muốn phát hiện các mã hóa theo quốc gia cụ thể đó. (Tuy nhiên, mb_detect_encodingvề cơ bản có cùng một yêu cầu, vì mã hóa sẽ phải xuất hiện "đúng chỗ" trong danh sách các mã hóa được thông qua để có thể phát hiện được tất cả.)

encacũng xuất hiện ở đây: Cách tìm mã hóa tệp trong Unix thông qua (các) tập lệnh


1

Có vẻ như câu hỏi của bạn đã được trả lời, nhưng tôi có một cách tiếp cận có thể đơn giản hóa trường hợp của bạn:

Tôi gặp vấn đề tương tự khi cố gắng trả về dữ liệu chuỗi từ mysql, thậm chí cấu hình cả cơ sở dữ liệu và php để trả về các chuỗi được định dạng thành utf-8. Cách duy nhất tôi gặp lỗi là thực sự trả chúng từ cơ sở dữ liệu.

Cuối cùng, lướt qua web tôi đã tìm thấy một cách thực sự dễ dàng để đối phó với nó:

Cho rằng bạn có thể lưu tất cả các loại dữ liệu chuỗi đó trong mysql của mình ở các định dạng và đối chiếu khác nhau, điều bạn chỉ cần làm là, ngay tại tệp kết nối php của mình, đặt đối chiếu thành utf-8, như sau:

$connection = new mysqli($server, $user, $pass, $db);
$connection->set_charset("utf8");

Không có nghĩa là trước tiên bạn lưu dữ liệu ở bất kỳ định dạng hoặc đối chiếu nào và bạn chỉ chuyển đổi nó khi trở về tệp php của mình.

Hy vọng nó hữu ích!



-2
public function convertToUtf8($text) {
    if(!$this->html)
        $this->html = cURL('http://'.$this->url, array('timeout' => 15));

    $html = $this->html;
    preg_match('/<meta.*?charset=(|\")(.*?)("|\")/i', $html, $matches);

    $charset = $matches[2];

    if($charset)
        return mb_convert_encoding($text, 'UTF-8', $charset);
    else
        return $text;
}

tùy chọn mặc định của cURL:

curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);

Tôi đã thử một cái gì đó như thế này. Nó đã giúp đỡ tôi. Nếu tìm thấy trên thông tin bảng mã meta, tôi đang chuyển đổi, nếu không thì không làm gì cả.


errr, bạn có thể vui lòng kiểm tra chức năng của bạn và sửa các biến?
Martin

$ Url là gì? $ Html là gì?
Martin
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.