Kódování hlaviček e-mailů
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, https://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"
);
?>
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) . '?=';
}
?>
20.11.2006 02:55:25
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)."?=";
15.12.2012 17:34:56
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 :)
8.4.2013 18:12:50
V-na:
Díky moc, bojoval jsem se zobrazením v Outlooku, vyřešeno!
14.5.2016 08:07:54
3rojka:
No vidíš a mě by to stačilo před měsícem, a taky jsem u toho pěkně ztrácel nervy.
20.11.2006 08:28:37
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.
20.11.2006 10:25:27
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.
20.11.2006 10:38:25
Torque:
Za to ale muzou lidi, co tyhle normy nedodrzujou. Agitaci proti W3C a XHTML to zacina a padem Ariane kvuli odflaknutymu programu konci...
20.11.2006 17:27:54
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.
20.11.2006 18:07:37
NoiseR:
Ja se s tim trapil prave ted - 2 minuty a pak mi RSS ctecka oznamila tenhle clanek - neveril jsem... DIKY!
20.11.2006 10:27:07
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í.
20.11.2006 11:49:25
Díky za tip, v rozšíření ICONV mě hledat nenapadlo, jinak bych samozřejmě místo vytváření vlastní funkce doporučil tuto.
20.11.2006 12:08:37
A jen pro doplnění - kdo místo iconv používá mb_string, tak tam zase existuje mb_encode_mimeheader.
1.10.2007 06:03:39
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)...
20.11.2006 12:24:11
Martin:
Jakube, nerekl bych ze to ta tvoje funkce udela spravne podle RFC 2047, neni tam zadny test na delku radku v hlavicce.
20.11.2006 13:00:07
Pavel:
Tento priklad me nefunguje, pri pouziti me prijde predmet mailu se skomolenyma znakama, misto cestiny, nevite cimto?
23.11.2006 15:37:19
Nejspíš používáš jiné kódování než UTF-8.
30.7.2008 05:19:24
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 !
25.11.2006 00:09:17
Mirek:
Super, díky za tohle.
22.4.2007 04:30:42
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 :-).
4.1.2007 00:43:54
HoKe:
a da se to nejak obeji?
24.4.2007 05:48:50
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?
19.2.2007 21:46:59
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 ;)
11.5.2007 14:12:18
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.'?=';
}
?>
3.8.2007 19:28:25
díky, tvoje funkce jako jediná tady funguje perfektně:)
5.6.2008 03:39:09
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.
14.10.2009 18:20:28
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
15.2.2008 20:21:04
imap_8bit mi bohužel nezpracuje znaky ' ' a '_', což by podle zmiňovaného RFC asi měl.
28.2.2008 01:20:10
kozotoč:
nepatří do povinných hlaviček také datum odeslání?
30.9.2008 18:17:56
Funkce mail() má čtvrtý parametr nepovinný, tudíž povinné hlavičky doplňuje sama. Já je mohu přepsat nebo nastavit ty nepovinné.
1.10.2008 03:34:31
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ší.
30.9.2008 18:31:26
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);
?>
2.2.2009 20:53:00
jay:
Nick: Díky moc za tenhle příspěvek, ušetřil mi spoustu času a dalšího úsilí.
31.8.2009 21:54:06
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
27.5.2012 15:38:58
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);
?>
2.5.2009 17:30:52
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.
16.8.2010 13:33:26
To bude nejspíš proto, že proměnná $text je právě v tomto kódování…
28.8.2010 01:24:31
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
23.10.2010 12:54:03
jarda:
Před tím "mail(" by asi nemělo být "return" ???
23.9.2010 01:25:42
Exquis:
Přesně tohle jsem hledal :) děkuju
18.10.2011 16:10:27
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('ě','ĕ',$text);
$text = str_replace('š','š',$text);
...
$text = str_replace('č','č',$text);
return $text;
}
?>
23.10.2010 13:02:17