Kódování hlaviček e-mailů

Školení, která pořádám

Pokud odesíláme e-mail, je slušností do pole příjemce i odesílatele uvést kromě e-mailové adresy i skutečné jméno. Doba, kdy třeba celá firma měla jen jednu adresu a příjemce se určoval právě podle uvedeného jména, je už naštěstí pryč, nicméně důvod pro použití jména stále existuje – kromě toho, že zprávy s uvedeným jménem lépe vypadají, tak jsou na chybějící jméno odesílatele háklivé také spam-filtry. V hlavičkách jsou povolené jen některé znaky, takže pokud jméno obsahuje např. diakritiku, je potřeba ji buď odstranit nebo náležitě zakódovat.

V rozšíření IMAP je k dispozici funkce imap_mime_header_decode, která text v hlavičce e-mailu rozkóduje, funkce pro zakódování nicméně chybí a nenašel jsem ji ani v knihovně Mail_Mime. Kódování je nicméně naštěstí poměrně jednoduché:

<?php
/** Zakódování e-mailové hlavičky podle RFC 2047
* @param string text k zakódování
* @param string kódování, výchozí je utf-8
* @return string řetězec pro použití v e-mailové hlavičce
* @copyright Jakub Vrána, http://php.vrana.cz/
*/
function mime_header_encode($text, $encoding = "utf-8") {
    return "=?$encoding?Q?" . imap_8bit($text) . "?=";
}
mail(
    mime_header_encode("Testovací uživatel") . " <test@example.com>",
    mime_header_encode("Testovací předmět"),
    "Zpráva",
    "MIME-Version: 1.0\nContent-Type: text/plain; charset=utf-8\nContent-Transfer-Encoding: 8bit"
);
?>
Jakub Vrána, Řešení problému, 20.11.2006, diskuse: 43 (nové: 0)

Diskuse

Kevujin:

Když jsi vzpomenu na hodiny, který jsem dal tomu, abych na tohle přišel, aj..... Taky jsi tenhle trik nemohl popsat tak o rok dřív? :D

Já sem nakonec vytvořil něco takovýhleho a s úspěchem to používám v kombinaci s phpMailerem při posílání utf-8 mailů:

<?php
function encode_header($string) {
  return  '=?UTF-8?B?' . base64_encode($string) . '?=';
}

?>

Jirka:

Jakubuvo řešení mi fungovalo pro Outlook, ale Gmail nebo Mac mail takto nakódovanou hlavičku nepřijal = mail takto vůbec nedorazil.

Naopak řešení s .base64_encode mi funguje v Mac mailu, Gmailu, Seznam mailu i Outlooku:
$text = "Nějaký text";
$enctext = "=?utf-8?B?".base64_encode($text)."?=";

Pozor níže uvedený zápis nefunguje v Mac mailu nebo Gmailu:
$text = "Nějaký text";
$text = "=?utf-8?B?".base64_encode($text)."?=";

Hanka:

Pánové převelice děkuji, strávila jsem odpoledne pročítáním příspěvků k RFC 2047, ale netoužím být odborníkem na kódování.

Funkce Kevujina encode_header($string) si s čínštinou a ruštinou poradila. Díky a hezký den :)

V-na:

Díky moc, bojoval jsem se zobrazením v Outlooku, vyřešeno!

3rojka:

No vidíš a mě by to stačilo před měsícem, a taky jsem u toho pěkně ztrácel nervy.

Martin:

ad [1,2]> Lidi, kteří nikdy nečetli RFC a trvá jim hodiny, aby objevili fakta v nich jasně napsaná, by se neměli vrhat do psaní webových aplikací a měli by zůstat u nějaké jednodušší činnosti.

salko:

No to si síce pekne napísal, ale keď som sa s tým ja trápil, cca 2 roky naspäť, tak i keď som dodržiaval RFC, tak niektorý mailový klienti jednoducho nedokázali prekusnúť UTF8. Dokonca ani Yahoo-Mail. Tak som musel ísť pekne krásne do ISO-8859-2. Ale o tom sa v žiadnom RFC nič nepísalo.

Torque:

Za to ale muzou lidi, co tyhle normy nedodrzujou. Agitaci proti W3C a XHTML to zacina a padem Ariane kvuli odflaknutymu programu konci...

salko:

Mas pravdu, ale skus vysvetlit zakaznikovi, ze jeho mailovy klient je nemoderny a ze tvoja aplikacia generuje validne maily. To mas ako s IE. Sice vsetci vieme, ze nie je dobry, ale aj tak musime pre neho optimalizovat a pisat rozne hacky.

NoiseR:

Ja se s tim trapil prave ted - 2 minuty a pak mi RSS ctecka oznamila tenhle clanek - neveril jsem... DIKY!

Robo.cz:

Jakube díky za zajímavou alternativu :-)
Před nějakou dobou jsem tohle také zkoumal, ale na mém systému vracela funkce imap_8bit docela podivné výsledky. Čistě pro vaši informaci: nakonec jsem s úspěchem použil funkci iconv_mime_encode, takže pokud by vám Jakubovo řešení nejelo, můžete to zkusit s ní.

ikona Jakub Vrána OpenID:

Díky za tip, v rozšíření ICONV mě hledat nenapadlo, jinak bych samozřejmě místo vytváření vlastní funkce doporučil tuto.

Andrew:

A jen pro doplnění - kdo místo iconv používá mb_string, tak tam zase existuje mb_encode_mimeheader.

Ondra:

Nestará se o to v Mail_mime náhodou metoda _encodeHeaders? Jediný (nezanedbatelný) problém je s českým znakem na konci slova (pokud je následováno mezerou a dalším slovem)...

Martin:

Jakube, nerekl bych ze to ta tvoje funkce udela spravne podle RFC 2047, neni tam zadny test na delku radku v hlavicce.

Mazlo:

Ja jsem se s tim taky lopotil a vyresil jsem to (lamersky! :-)) takto: http://www.mazlo.org/blog/clanek/86-Problemy-…-funkce-mail

Pavel:

Tento priklad me nefunguje, pri pouziti me prijde predmet mailu se skomolenyma znakama, misto cestiny, nevite cimto?

ikona Jakub Vrána OpenID:

Nejspíš používáš jiné kódování než UTF-8.

Kvakoš:

Jakube, díky !

Týden předtím, než vyšel ohlášený Jakubův článek jsem musel ze dne na den tento problém nějak vyřešit (čeština v e-mailech odesílaných ze stránky kódované v UTF-8). Šel jsem na to právě přes to RFC 2047, kde jsem se pokusil nějak vyjít z příkladů tam uvedených, ale méně šikovně než Jakub, chodí mě to také, ale nefungovalo to v předmětu zprávy, což ale bude chyba ve funkci mail.

Kód:

<?php
$recipient
= "nejaky@email.cz";
$subject = "Předmět zprávy";
$message = "Text zprávy e-mailu";
$from = "=?UTF-8?B?".base64_encode("Jméno a Příjmení")."?=";
$from .= "<". $emailodesilatele .">";
$headers .= "From: ".$from."\n"; //
$headers .= "Content-Type: text/html; charset=utf-8\n";
mail($recipient, $subject, $message, $headers);
?>

Když jsem ale přesně podle RFC nastavoval položku headru Subject, nefungovalo to, pak je asi lepší použít phpmailer, nebo jak Jakub použil zakódovat přímo vlastní text subjektu místo deklarace použitého kódování pro položku subject. Ten problém s předmětem jsem dost dlouho zkoušel a myslím si, že bude chyba v samotné PHP-funkci mail(). Jakubův přístup jsem zatím nezkoušel, ale už na to jdu.

Jakube díky, taky Jsi to mohl napsat o ten týden dříve, ale i tak je to fajn !

Mirek:

Super, díky za tohle.

kavos:

Ještě pozor na omezení délky kódovaného textu – podle RFC 2047 může text mít (včetně jména kódové stránky a oddělovačů) max. 75 znaků. Striktní klient pak může odmítnout takový text rozkódovat (např. pine – nejdřív jsem to považoval za bug, pak jsem si ale přečetl RFC a bylo jasno :-).

HoKe:

a da se to nejak obeji?

Lenka:

Krásně mi funguje posílání emailů do Outlooku, ale portál seznam mi zobrazí pouze odesilatele a subject a vnitřek emailu je prázdný. Neví někdo co s tím dělat?

eMZák:

U PHP po ránu
vzpomeňte si na Vránu.
Díky němu nemusím
číst složité RFC -
lamou budu na dobro,
ale tohle pomohlo ;)

Honza Odvárko:

Nemám rád lidi, který ostatním cpou svoje "vychytaný funkcičky". Takže přesně tohle teď udělám, protože si myslím že jsem jí věnoval dost času a sem se prostě hodí. Hlavní její výhoda je, že nedělá řádky delší než $linesize znaků, tak jak to definuje RFC 2045, zároveň zohledňuje multibyte znaky.

<?php
/*
* Encodes the string according to RFC 2045 quoted-printable encoding,
* taking into account multibyte characters.
*/
function quoted_printable_header($str, $charset='', $linesize=76) {
    $charset || $charset = mb_internal_encoding();
    $out = "=?$charset?Q?";
    if($linesize === 0) {
        for($i=0,$c=strlen($str); $i<$c; $i++) {
            $chr = $str[$i];
            $ord = ord($chr);
            if(($ord>=33 && $ord<=60) || ($ord>=62 && $ord<=126) && $chr!=='_')
                $out .= $chr;
            elseif($chr === ' ')
                $out .= '_';
            else
                $out .= sprintf('=%02X', $ord);
        }
    } else {
        $linepos = strlen($out);
        for($i=0,$c=mb_strlen($str,$charset); $i<$c; $i++) {
            $chr = mb_substr($str, $i, 1, $charset);
            if(strlen($chr) === 1) {
                $ord = ord($chr);
                if(($ord>=33 && $ord<=60) || ($ord>=62 && $ord<=126) && $chr!=='_')
                    $append = $chr;
                elseif($chr === ' ')
                    $append = '_';
                else
                    $append = sprintf('=%02X', $ord);
            } else for($i2=0,$append=''; $i2<strlen($chr); $i2++) $append .= sprintf('=%02X', ord($chr[$i2]));

            if($i > 0 && $linepos+strlen($append)+2 > $linesize) {
                $out .= "?=\r\n";
                $linepos = 0;
                $append = " =?$charset?Q?$append";
            }
            $out .= $append;
            $linepos += strlen($append);
        }
    }
    return $out.'?=';
}
?>

H3m3R:

díky, tvoje funkce jako jediná tady funguje perfektně:)

ikona Jakub Vrána OpenID:

Pokud to dobře chápu, tak RFC 2045 se vztahuje jen na tělo zprávy. Na hlavičky se vztahuje RFC 2822, která dovoluje 998 znaků na řádek.

Deny:

Ahoj, Jakubovo reseni je funkcni napr na serverech webzdarma.cz , ale ja bych to potreboval rozchodit na forpsi.cz , bohuzel tam mi to hazi XML parsing error, jedine co me napada, ze asi maji nejako jinak nastavene PHP. Zajimalo by me tedy, na jakem nastaveni PHP muze byt tato Jakubova fce zavisla. Diky za kazdou odpoved

bukaJ:

imap_8bit mi bohužel nezpracuje znaky ' ' a '_', což by podle zmiňovaného RFC asi měl.

kozotoč:

nepatří do povinných hlaviček také datum odeslání?

ikona Jakub Vrána OpenID:

Funkce mail() má čtvrtý parametr nepovinný, tudíž povinné hlavičky doplňuje sama. Já je mohu přepsat nebo nastavit ty nepovinné.

kozotoč:

A taky "From:", i když... jak tak zběžně pročítám http://cz.php.net/manual/en/function.mail.php , tak jsou tam podstatné změny v implementaci na Windows a (pokud jsem to správně pochopil) měl by tam být nepovinný(?!).

Skoro bych ten kód pozměnil na:
<?php
...
mail(... ,
$common_headers."MIME-Version: 1.0\nContent-Type: text/plain; charset=utf-8\nContent-Transfer-Encoding: 8bit")
..
?>

p.s. čím víc se do dokumentace té funkce začínám, tím je to složitější.

Petr:

Koukám ještě, že existuje funkce mb_encode_mimeheader().

http://php.net/mb_encode_mimeheader

Nick:

Pro ty co chcou něco jednoduchého ale zajímavého hlavně funkčního

<?php

$subject
= "=?utf-8?B?".base64_encode("Příliš Žluťoučký kůň")."?=";
        $headers = "MIME-Version: 1.0\n";
        $headers .= "Content-Type: text/plain; charset=utf-8\n";
        $headers .= "From: =?UTF-8?B?".base64_encode("Ještě Žlouťoučtější kůň")."?=<zluty@kun.cz>\n";
        $sending = "Právě jste dostali e-mail od Příliš Žluťoučkého koně.";
        mail ($_POST['email'], $subject, $sending, $headers);

?>

jay:

Nick: Díky moc za tenhle příspěvek, ušetřil mi spoustu času a dalšího úsilí.

ikona Jiro:

a helemese, jak se to hodí i po letech se stránkou v UTF-8, kdy to už, už někde chodilo a na seznamu třeba v mejlu se předmět zobrazoval špatně. Stačilo už jen přepsání několika znaků v jednom řádku dle řešení na které odpovídám a bylo to :-D Díky

ikona Allegro:

No, a mně to zase funguje takto. (Odesílání z Webzdarma na Seznam (Win XP, Firefox)).

<?php
 
function zakoduj($text) {
      return "=?ISO-8859-2?B?".base64_encode($text)."?=";
  }
  $text="Text zprávy.";
  $subject=zakoduj("Předmět zprávy.");
  $header="MIME-Version: 1.0\n";
  $header.="Content-Type: text/plain; charset=iso-8859-2\n";
  return mail(
    "nejaky_email@seznam.cz",
    $subject,
    $text,
    $header);
?>

Jirka:

A já zase velmi děkuji až za tohle, protože teprve tohle mi otevřelo oči. Konečnou funkci jsem si pak stejně musel upravit. Měl jsem vždy problém s kódováním předmětu zprávy v češtině. Nevím, jak je to možné, ale nefunguje mi ani

return "=?UTF-8?B?".base64_encode($text)."?=";

ani

return "=?ISO-8859-2?B?".base64_encode($text)."?=";

ale teprve až

return "=?Windows-1250?B?".base64_encode($text)."?=";

zakóduje všechny interpunkční znaménka správě. Do problematiky kódování nevidím nějak hluboce, ale Windows-1250 mě ještě nikdy nezklamalo.

ikona Jakub Vrána OpenID:

To bude nejspíš proto, že proměnná $text je právě v tomto kódování…

lamer:

Může to být i tím, že že soubor se skriptem je uložen v jiném kódování (ansi)... jinak převést znaky s interpunkcí na číselné entity řeší jakékoli problémy s kódováním

jarda:

Před tím "mail(" by asi nemělo být "return" ???

Exquis:

Přesně tohle jsem hledal :) děkuju

lamer:

<?php
// Převádí libovolné znaky na číselné entity
// pro použití s každým kódováním
function Czech2Entities($text)
{
    $text = str_replace('ě','&#277;',$text);
    $text = str_replace('š','&#353;',$text);
    ...
    $text = str_replace('č','&#269;',$text);

    return $text;
}
?>

Vložit příspěvek

Používejte diakritiku. Vstup se chápe jako čistý text, ale URL budou převedeny na odkazy a PHP kód uzavřený do <?php ?> bude zvýrazněn. Pokud máte dotaz, který nesouvisí s článkem, zkuste raději diskusi o PHP, zde se odpovědi pravděpodobně nedočkáte.

Jméno: URL:

avatar © 2005-2016 Jakub Vrána. Publikované texty můžete přetiskovat pouze se svolením autora. Ukázky kódu smíte používat s uvedením autora a URL tohoto webu bez dalších omezení Creative Commons. Můžeme si tykat. Skripty předpokládají nastavení: magic_quotes_gpc=Off, magic_quotes_runtime=Off, error_reporting=E_ALL & ~E_NOTICE a očekávají předchozí zavolání mysql_set_charset. Skripty by měly být funkční v PHP >= 4.3 a PHP >= 5.0.