PHP: Ubah string apa saja menjadi UTF-8 tanpa mengetahui set karakter asli, atau setidaknya coba

146

Saya memiliki aplikasi yang berhubungan dengan klien dari seluruh dunia, dan, tentu saja, saya ingin semuanya masuk ke basis data saya untuk disandikan UTF-8.

Masalah utama bagi saya adalah bahwa saya tidak tahu apa pengkodean sumber dari string apa pun yang akan terjadi - bisa dari kotak teks (menggunakan <form accept-charset="utf-8">hanya berguna jika pengguna benar-benar mengirimkan formulir), atau bisa juga dari file teks yang diunggah, jadi saya benar-benar tidak memiliki kendali atas input.

Yang saya butuhkan adalah fungsi atau kelas yang memastikan barang-barang masuk ke database saya, sejauh mungkin, disandikan UTF-8. Saya sudah mencoba iconv(mb_detect_encoding($text), "UTF-8", $text); tetapi ada masalah (jika inputnya adalah 'tunangan', ia mengembalikan 'tunangan'). Saya sudah mencoba banyak hal = /

Untuk unggahan file, saya menyukai gagasan meminta pengguna akhir untuk menentukan pengkodean yang mereka gunakan, dan menunjukkan kepada mereka pratinjau seperti apa bentuk outputnya, tetapi ini tidak membantu melawan peretas jahat (pada kenyataannya, itu bisa membuat hidup mereka sedikit lebih mudah).

Saya sudah membaca pertanyaan SO lainnya tentang masalah ini, tetapi semuanya tampaknya memiliki perbedaan yang halus seperti "Saya perlu menguraikan RSS feed" atau "Saya mengorek data dari situs web" (atau, memang, "Anda tidak bisa").

Tetapi pasti ada sesuatu yang paling tidak patut dicoba !

Suram...
sumber
5
Pada dasarnya tidak mungkin dengan definisi untuk mendapatkan yang benar-benar benar, pada kenyataannya tingkat keberhasilan menebak suatu pengkodean yang tidak diketahui tidak hebat. Dimungkinkan untuk menggunakan heuristik, tetapi itu akan benar kurang dari 100% dari waktu, tergantung pada bahan yang jauh lebih sedikit dari 100%. Anda harus sadar akan hal itu. Mungkin seseorang di sini setidaknya dapat menyarankan perpustakaan dengan heuristik yang baik.
tipuan
Tentu, saya tahu tidak ada solusi yang sempurna - karenanya keinginan untuk sesuatu yang setidaknya akan berhasil.
Grim ...
ini mungkin membantu: stackoverflow.com/q/505562/642173
Melsi
Sudahkah Anda mencoba menggunakan UTF-8//IGNOREsebagai param 2 di iconv?
tembak
Ya, itulah yang akhirnya saya lakukan. Tidak sempurna, jelas, saat itu 'tunangan' menjadi 'tunangan', tetapi tentu saja lebih baik. Kenapa TRANSLIT tidak berfungsi?
Grim ...

Jawaban:

255

Apa yang Anda minta sangat sulit. Jika memungkinkan, meminta pengguna untuk menentukan pengkodean adalah yang terbaik. Mencegah serangan seharusnya tidak semudah atau sesulit itu.

Namun, Anda dapat mencoba melakukan ini:

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

Menetapkannya dengan ketat mungkin membantu Anda mendapatkan hasil yang lebih baik.

Jeff Day
sumber
5
Silakan, lihat mb_detect_encodingkode sumber di distro php Anda (di suatu tempat di sini: ext / mbstring / libmbfl / mbfl / mbfl_ident.c). Fungsi ini sama sekali tidak berfungsi dengan baik. Untuk beberapa pengkodean bahkan "kembali benar", lol. Lainnya ada di fungsi Ctrl + c Ctrl + v. Itu karena Anda tidak dapat mendeteksi penyandian tanpa semacam kamus atau pendekatan statistik (seperti milik saya).
Oroboros102
1
Cara saya memahaminya, mb_detect_encodingmenelusuri daftar pengkodean yang disediakan, dan menerima yang pertama yang tidak memiliki urutan byte yang tidak valid dalam string ... Untuk pengkodean yang tidak memiliki urutan byte yang tidak valid seperti ISO-8859-1, itu selalu benar . Tidak ada heuristik "pintar", dan hasil sangat bervariasi dengan daftar (dan urutan) pengkodean yang Anda berikan.
wutz
Ini sepertinya bekerja untuk saya. Pengguna saya mengirimkan teks pada halaman utf8 dengan tinymce, namun karena alasan yang tidak diketahui terkadang karakter non utf8 berakhir di database. Ini memperbaikinya, jadi terima kasih banyak.
giorgio79
@ Jeff Day - Terima kasih untuk ini. Maafkan ketidaktahuan saya, apa maksud Anda 'Menyetel ke Ketat'?
Ash501
[Jeff Day] mengirim mb_detect_order()walaupun itu adalah nilai default untuk param ini, karena ia ingin mengatur deteksi pengkodean yang ketat menjadi true (the 3rd param) :)
jave.web
28

Di Rusia, kami memiliki 4 penyandian populer, jadi pertanyaan Anda sangat diminati di sini.

Hanya dengan kode simbol char Anda tidak dapat mendeteksi pengkodean, karena halaman kode berpotongan. Beberapa codepage dalam berbagai bahasa bahkan memiliki persimpangan penuh. Jadi, kita perlu pendekatan lain .

Satu-satunya cara untuk bekerja dengan penyandian yang tidak diketahui adalah bekerja dengan probabilitas. Jadi, kami tidak ingin menjawab pertanyaan "apa penyandian teks ini?", Kami mencoba memahami " apa yang paling mungkin penyandian teks ini? ".

Seorang pria di sini di blog teknologi Rusia populer menciptakan pendekatan ini:

Buat rentang probabilitas kode char di setiap penyandian yang ingin Anda dukung. Anda dapat membangunnya menggunakan beberapa teks besar dalam bahasa Anda (misalnya beberapa fiksi, gunakan Shakespeare untuk bahasa Inggris dan Tolstoy untuk bahasa Rusia, lol). Anda akan mendapatkan sesuatu seperti ini:

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

Lanjut. Anda mengambil teks dalam pengkodean yang tidak dikenal dan untuk setiap pengkodean dalam "kamus probabilitas" Anda mencari frekuensi setiap simbol dalam teks yang dikodekan tidak dikenal. Jumlah probabilitas simbol. Pengkodean dengan peringkat yang lebih besar kemungkinan adalah pemenangnya. Hasil yang lebih baik untuk teks yang lebih besar.

Jika Anda tertarik , saya dengan senang hati dapat membantu Anda dengan tugas ini. Kami dapat sangat meningkatkan akurasi dengan membangun daftar probabilitas dua-kode.

Btw. mb_detect_encoding pasti tidak berfungsi. Ya, sama sekali. Silakan, lihat kode sumber mb_detect_encoding di "ext / mbstring / libmbfl / mbfl / mbfl_ident.c".

Oroboros102
sumber
11

Anda mungkin sudah mencobanya, tetapi mengapa tidak menggunakan fungsi mb_convert_encoding? Ini akan mencoba untuk mendeteksi secara otomatis kumpulan karakter dari teks yang disediakan atau Anda dapat memberikannya daftar.

Juga, saya mencoba menjalankan:

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

dan hasilnya sama untuk keduanya. Bagaimana Anda melihat bahwa teks Anda terpotong ke 'tunangan'? apakah itu di DB atau di browser?

Alexey Gerasimov
sumber
Dalam database, sepertinya - Saya baru saja mencoba dengan kode Anda dan saya setuju.
Suram ...
1
Periksa untuk memastikan collation yang telah Anda tetapkan pada tabel / kolom juga UTF-8.
Alexey Gerasimov
@AlexeyGerasimov Saya kira saya benar-benar perlu menyelidiki iconv. Saya mencoba melakukan cara mb_ * hampir murni. Apa yang kamu pikirkan?
Anthony Rutledge
5

Tidak ada cara untuk mengidentifikasi charset dari string yang sepenuhnya akurat. Ada beberapa cara untuk mencoba menebak charset. Salah satu cara ini, dan mungkin / saat ini yang terbaik di PHP, adalah mb_detect_encoding (). Ini akan memindai string Anda dan mencari kemunculan hal-hal unik untuk rangkaian karakter tertentu. Tergantung pada string Anda, mungkin tidak ada kejadian yang dapat dibedakan.

Ambil ISO-8859-1 vs ISO-8859-15 ( http://en.wikipedia.org/wiki/ISO/IEC_8859-15#Changes_from_ISO-8859-1 )

Hanya ada beberapa karakter yang berbeda, dan untuk membuatnya lebih buruk, mereka diwakili oleh byte yang sama. Tidak ada cara untuk mendeteksi, diberi string tanpa mengetahui pengkodeannya, apakah byte 0xA4 seharusnya menandakan ¤ atau € di string Anda, jadi tidak ada cara untuk mengetahui itu charset yang tepat.

(Catatan: Anda dapat menambahkan faktor manusia, atau teknik pemindaian yang bahkan lebih maju (misalnya apa yang disarankan Oroboros102), untuk mencoba mencari tahu berdasarkan konteks sekitarnya, jika karakter harus ¤ atau €, meskipun ini seperti jembatan terlalu jauh)

Ada perbedaan yang lebih dapat dibedakan antara misalnya UTF-8 dan ISO-8859-1, jadi masih ada baiknya mencoba mencari tahu ketika Anda tidak yakin, meskipun Anda bisa dan tidak boleh mengandalkan itu benar.

Menarik dibaca: http://kore-nordmann.de/blog/php_charset_encoding_FAQ.html#how-do-i-determine-the-charset-encoding-of-a-string

Ada beberapa cara lain untuk memastikan charset yang benar. Mengenai formulir, cobalah untuk menegakkan UTF-8 sebanyak mungkin (periksa manusia salju untuk memastikan bahwa pengiriman Anda akan menjadi UTF-8 di setiap browser: http://intertwingly.net/blog/2010/07/29/Rails-and -Snowmen ) Itu sedang dilakukan, setidaknya Anda dapat yakin bahwa setiap teks yang dikirim melalui formulir Anda adalah utf_8. Mengenai file yang diunggah, coba jalankan perintah unix 'file -i' di atasnya melalui mis exec () (jika mungkin di server Anda) untuk membantu pendeteksian (menggunakan BOM dokumen.) Mengenai data pengikisan, Anda dapat membaca header HTTP, yang biasanya menentukan charset. Saat mem-parsing file XML, lihat apakah meta-data XML berisi definisi charset.

Alih-alih mencoba menebak charset secara otomatis, Anda harus terlebih dahulu mencoba memastikan charset tertentu sendiri, atau mencoba mengambil definisi dari sumber yang Anda dapatkan (jika berlaku) sebelum beralih ke deteksi.

matthiasmullie
sumber
Formulir dan tautan pendaftaran email dengan data terenkripsi. Di situlah saya mencoba membuat input saya menjadi UTF-8 atau tidak sama sekali. Apa pendapat Anda tentang jawaban saya? Komentar yang bermanfaat sangat dihargai. Terima kasih.
Anthony Rutledge
3

Ada beberapa jawaban dan upaya yang sangat bagus untuk menjawab pertanyaan Anda di sini. Saya bukan master encoding, tapi saya mengerti keinginan Anda untuk memiliki setumpuk UTF-8 murni sampai ke database Anda. Saya telah menggunakan utf8mb4pengkodean MySQL untuk tabel, bidang, dan koneksi.

Situasi saya berubah menjadi "Saya hanya ingin pembersih saya, validator, logika bisnis, dan menyiapkan pernyataan untuk berurusan dengan UTF-8 ketika data berasal dari formulir HTML, atau tautan pendaftaran email." Jadi, dengan cara sederhana saya, saya mulai dengan ide ini:

  1. Mencoba mendeteksi pengodean: $encodings = ['UTF-8', 'ISO-8859-1', 'ASCII'];
  2. Jika penyandian tidak dapat dideteksi, throw new RuntimeException
  3. Jika input UTF-8, lanjutkan.
  4. Lain, jika itu ISO-8859-1atauASCII

    Sebuah. Coba konversi ke UTF-8 (tunggu, belum selesai)

    b. Mendeteksi penyandian nilai yang dikonversi

    c. Jika pengkodean yang dilaporkan dan nilai yang dikonversi keduanya UTF-8, lanjutkan.

    d. Lain,throw new RuntimeException

Dari kelas abstrak saya Sanitizer

Pembersih

    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;
    }

Orang bisa membuat argumen bahwa saya harus memisahkan masalah pengkodean dari Sanitizerkelas abstrak saya dan cukup menyuntikkan Encoderobjek ke turunan anak konkret Sanitizer. Namun, masalah utama dengan pendekatan saya adalah bahwa, tanpa lebih banyak pengetahuan, saya hanya menolak jenis penyandian yang tidak saya inginkan (dan saya mengandalkan fungsi PHP mb_ *). Tanpa studi lebih lanjut, saya tidak bisa tahu apakah itu menyakitkan sebagian populasi atau tidak (atau, jika saya kehilangan informasi penting). Jadi, saya perlu belajar lebih banyak. Saya menemukan artikel ini.

Apa yang mutlak perlu diketahui oleh setiap programmer, tentang penyandian dan rangkaian karakter untuk bekerja dengan teks

Selain itu, apa yang terjadi ketika data terenkripsi ditambahkan ke tautan pendaftaran email saya (menggunakan OpenSSLatau mcrypt)? Mungkinkah ini mengganggu decoding? Bagaimana dengan Windows-1252? Bagaimana dengan implikasi keamanan? Penggunaan utf8_decode()dan utf8_encode()dalam Sanitizer::isUTF8meragukan.

Orang-orang telah menunjukkan kekurangan dalam fungsi PHP mb_ *. Saya tidak pernah meluangkan waktu untuk menyelidiki iconv, tetapi jika berfungsi lebih baik daripada fungsi mb_ *, beri tahu saya.

Anthony Rutledge
sumber
Saya menemukan ini, stackoverflow.com/a/3521396/1429677 jawaban yang sangat baik untuk masalah ini, di sini adalah lib github.com/neitanod/forceutf8
Llewellyn
2

Masalah utama bagi saya adalah bahwa saya tidak tahu apa pengkodean sumber string apa pun yang akan terjadi - bisa dari kotak teks (menggunakan hanya berguna jika pengguna benar-benar mengirimkan formulir), atau bisa juga dari file teks yang diunggah, jadi saya benar-benar tidak memiliki kendali atas input.

Saya tidak berpikir itu masalah. Aplikasi mengetahui sumber input. Jika itu dari formulir, gunakan pengkodean UTF-8 dalam kasus Anda. Itu bekerja. Cukup verifikasi data yang diberikan telah disandikan dengan benar (validasi). Perlu diingat bahwa tidak semua database mendukung UTF-8 dalam jangkauan penuhnya.

Jika itu file, Anda tidak akan menyimpannya UTF-8 yang disandikan ke dalam basis data tetapi dalam bentuk biner. Saat Anda meng-output file lagi, gunakan output biner juga, maka ini benar-benar transparan.

Ide Anda bagus agar pengguna dapat mengetahui enkode, baik ia dapat mengetahui setelah mengunduh file, karena itu biner.

Jadi saya harus mengakui bahwa saya tidak melihat masalah spesifik yang Anda ajukan dengan pertanyaan Anda. Tapi mungkin Anda bisa menambahkan beberapa detail apa masalah Anda.

hakre
sumber
Apakah Anda melihat dan mengeluarkan jawaban saya? Komentar konstruktif dihargai. Terima kasih.
Anthony Rutledge
1

Anda dapat menyiapkan serangkaian metrik untuk mencoba menebak penyandian mana yang sedang digunakan. Sekali lagi, tidak sempurna, tetapi bisa menangkap beberapa kesalahan dari mb_detect_encoding ().

Parris Varney
sumber
Ya, ngomong-ngomong mb_detect_encoding()soal rindu, apakah menurut Anda jawaban saya memiliki peluang bola salju di musim panas di Sahara?
Anthony Rutledge
1

Jika Anda bersedia "membawa ini ke konsol", saya sarankan enca. Berbeda dengan yang agak sederhana mb_detect_encoding, ini menggunakan "campuran parsing, analisis statistik, menebak dan ilmu hitam untuk menentukan pengkodean mereka" (lol - lihat halaman manual ). Namun, Anda biasanya harus melewati bahasa file input jika Anda ingin mendeteksi pengkodean khusus negara tersebut. (Namun, mb_detect_encodingpada dasarnya memiliki persyaratan yang sama, karena pengkodean harus muncul "di tempat yang tepat" dalam daftar penyandian yang disahkan agar dapat dideteksi sama sekali.)

encajuga muncul di sini: Cara menemukan penyandian file di Unix melalui skrip

wutz
sumber
1

Tampaknya pertanyaan Anda cukup dijawab, tetapi saya memiliki pendekatan yang dapat menyederhanakan kasus Anda:

Saya memiliki masalah serupa yang mencoba mengembalikan data string dari mysql, bahkan mengkonfigurasi database dan php untuk mengembalikan string yang diformat ke utf-8. Satu-satunya cara saya mendapatkan kesalahan sebenarnya mengembalikan mereka dari database.

Akhirnya, berlayar melalui web saya menemukan cara yang sangat mudah untuk menghadapinya:

Memberikan bahwa Anda dapat menyimpan semua jenis data string di mysql Anda dalam berbagai format dan susunan, yang hanya perlu Anda lakukan adalah, tepat di file koneksi php Anda, atur susunan ke utf-8, seperti ini:

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

Wich berarti pertama-tama Anda menyimpan data dalam format atau pemeriksaan apa pun dan Anda mengonversinya hanya dengan kembali ke file php Anda.

Semoga bermanfaat!

Quel Pino
sumber
-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;
}

Opsi default CURL:

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

Saya mencoba sesuatu seperti ini. Itu membantu saya. Jika ditemukan di info meta charset, saya mengonversi, jika tidak melakukan apa-apa.

littlealien
sumber
errr, bisakah Anda memeriksa fungsi Anda dan memperbaiki variabel?
Martin
Apa itu $ url? Apa itu $ html?
Martin