Web Programming >> PHP Programming
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
[목차] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
제29장 IMAP을 이용한 웹메일 프로그래밍
3. 메일 본문 출력하기
메일 리스트의 출력은 IMAP함수 몇가지로 간단히 구현이 가능했지만 메일본문은 아쉽게도 그렇지 못하다. 원래 인터넷이메일은 RFC821, RFC822에 기술된 단순 텍스트메시지(Simple Text Message)형태로 출발했지만 보안상의 문제점과 바이너리 파일을 처리할 수 없다는 단점 때문에 MIME(Multipurpose Internet Mail Extensions)이란 개념이 RFC1521, 1522에 기술됐다. 실제 MIME의 개념을 모르고는 메일본문의 출력 구현이 어렵기 때문에 먼저 MIME에 대한 개념을 이해하고 실제 프로그래밍 방법을 살펴보도록 한다.
1. MIME의 이해 1 - MIME의 구조 MIME은 아스키코드형식의 데이터만 주고 받을 수 있었던 전자우편 프로토콜을 확장하여 이미지, 오디오, 비디오 등의 바이너리 파일을 주고 받을 수 있도록 확장된 프로토콜이다. 흔히 사용되는 MTA인 아웃룩도 MIME형식으로 메일을 발송하므로 다음의 실제 메일 원문을 통햐 비교해 보도록 한다. 다음은 Simple Text Message원문이다.
보기에도 두 개의 메일은 상당한 차이가 있다. 하지만 분명 같은 내용의 메일이다. 위 MIME 원문의 경우 아웃룩을 이용하여 발송한 경우이며 아웃룩의 경우는 텍스트 메시지를 Alternative의 MIME으로 작성해 발송한다. Alternative는 동일한 내용의 메일을 위와 같이 하나는 text/plain형식으로 포장하고, 다른 하나는 text/html형식으로 포장한다. 이렇게 하는 이유는 다른 수신 MTA에서 메일을 해석할 때 내용이 보이지 않는 문제를 해결하기 위함이고 Alternative타입은 아웃룩 고유의 방법이 아닌 RFC 에 기술된 MIME의 일종이다. 또다른 차이점은 메일 본문 중간중간에 바운더리라고 불리는 메일 파트 구분자가 있는 것이다. ------=_NextPart_000_0008_01C1A203.DD77C4A0
바운더리는 위와 같이 메일의 내용을 구분해 주는 것으로 단순 텍스트 메일과는 달리 텍스트, 이미지, 비디오, 오디오 등의 바이너리 데이터를 포함할 경우 각 내용들을 구분해 주기 위한 것이다. 위의 경우 같은 내용이지만 동일한 내용이 반복되는 다른 파트로 존재하기 때문에 중간에 바운더리가 들어가 있다. 메일은 바운더리 마지막 부분에 --가 추가됨으로써 더 이상 내용이 없음을 알려준다. 바운더리 안에는 다른 바운더리로 구분되는 메일파트가 계속해서 존재할 수 있으며 앞으로 설명될 내용은 메일의 바운터리, 즉 메일 파트별로 본문 내용을 읽어와 MIME형식에 따라 브라우저로 출력하도록 처리하는 부분이 되므로 MIME의 이해는 매우 중요하다.
2. MIME의 이해 2 - Content-Type 앞부분에서는 MIME의 구성에 대해 알아 보았고 이번에는 MIME의 또 다른 요소인 Content-Type에 대해 알아보도록 한다. Content-Type은 현재 운영체계가 마이크로소프트 윈도우 계열이라면 이미 우리가 알고 사용하고 있는 중이다. 윈도우 유틸리티중의 하나인 윈도우 익스플러러(파일탐색기)를 실행하여 드라이브의 디렉토리를 선택하면 각 파일의 타입에 따라 각기 다른 아이콘들이 보여진다. 이것들은 윈도 익스플러러 메뉴중 도구->폴더옵션->파일타입을 클릭해 보면 알 수 있다. 즉 이런 확장자를 가진 파일은 어떤 아이콘으로 표시하라는 정보가 이미 등록돼 있는 것이다. Content-Type도 마찬가지로 텍스트 형식 메일의 경우 text/plain이란 타입을 가지고, HTML형식 메일의 경우 text/html이란 타입을 가진다. MIME으로 작성된 인터넷 메일은 이런 Content-Type을 확인하여 각각의 메일내용에 맞는 처리를 해주어야 하며 주로 사용되는 Content-Type은 아래 표와 같다.
http://www.isi.edu/in-notes/iana/assignments/media-types/을 방문하면 Content-Type의 분류에 대한 목록이 있으며 약 450가지의 분류가 존재한다. 인터넷 메일에서 450가지 전부 사용되는 것은 아니지만 많은 웹메일에서 Content-Type을 제대로 인식하지 못하여 메일이 깨지는 경우가 많이 있으며 웹메일 작성하는데 가장 핵심이 되는 부분임은 강조해도 지나치지 않다.
3. MIME의 이해 3 - Content-Transfer-Encoding Content-Type과 함께 메일을 정확히 출력하기 위해 필요한 또하나의 핵심 부분이 content-Transfer-Encoding이다. 메일작성 시 바이너리 첨부 파일을 전송하여야 할 경우 단순히 파일 코드를 메일 안에 적어주는 방법으로는 안전한 메일을 발송할 수 없다. 인터넷 메일의 RFC규격에 따르면 메일 작성 시 1라인에 기술할 수 있는 코드의 길이가 제한되어있으며, 이런 규약을 지키기 위해서는 파일코드를 인코딩하여 메일 안에 첨부시켜 줘야한다. Content-Transfer-Encoding은 7bit, 8bit, Binary, Quoted-printable, base64, user define등이 있다. 단순 텍스트 메시지의 경우 Quoted-printable로 주로 인코딩하며, 바이너리 파일의 경우 BASE64로 인코딩하여 메일 본문에 첨부된다. 다음의 예와 테이블을 참고하여 가장 많이 사용되는 BASE64인코딩 방법에 대해 알아보도록 한다.
4. BASE64인코딩 방법 BASE64인코딩 방법은 RFC2045에 기술돼 있으며, 이것은 7비트나 Quoted-printable인코딩이 적합하지 않은파일을 보낼 경우 사용된다. BASE64는 3개의 octet(24bit)을 가지고 그것을4개의 64비트 블록으로 매핑한 다음 각 6비트 블록을 64문자 알파벳중 한 문자로 표현을 한다. 이런 방법으로 인해 BASE64로 이코딩된 내용은 기존의 내용보다 1/3정도 커지는 단점이 있다. 다음은 GIF 이미지 파일의 일부분을 BASE64인코딩하는 과정이다.
0100011101001001010001100011100000111001
010001 110100 100101 000110 001110 000011 100100
17 52 37 6 14 3 36 R 0 1 G O D K
R01GODK== <표> BASE64 매핑 테이블
위의 표는 BASE64 매핑 테이블이다.
지금까지 메일 본문을 출력하기 위해 MIME에 대해 개략적이나마 알아 보았다. MIME은 메일의 핵심이므로 위의 개념만으로 제대로 이해하기 어렵고 보다 자세한 내용은 RFC를 참조하기 바란다. 메일 본문을 출력해보자. 메일리스트로부터 전달될 정보는 현재의 메일박스 이름과 메일의 메시지 번호 2가지이다.
5. IMAP 스트림 생성 메일리스트 출력코드와 동일하다.
6. 메일 헤더 정보 읽어오기 <? $object= imap_headerinfo($stream,$msgno); ?> imap_headerinfo는 메일리스트 출력에서 사용되었던 함수이며, 메일본문 출력시 메일 제목, 보낸사람, 받는 사람 등의 정보를 출력하기 위하여 사용됐다.
7. 메일 본문 정보 읽어오기 <? $object= imap_fetchstructure($stream,$msgno); ?> imap_fetchstructure는 지정한 메일 메시지 번호의 메일 정보를 읽어 오브젝트 형태로 돌려주며 여러 오브젝트 중 다음의 정보를 주로 사용한다. $object->type : 메일의 Primary Content-Type $object->subtype : 메일의 Sub Content-Type $object->enconding : 메일의 Content-Transfer-Encoding $object->disposition : 메일의 속성을 나타낸다. $object->parameters : 메일의 Charset이나 첨부파일 명을 나타낸다. $object->dparameters : 메일의 Charset이나 첨부파일 명을 나타낸다. $object->parts : 메일안의 각 메시지의 갯수를 나타낸다.
<표> Primary Content-Type 변환표
<표> CONTENT-TRANSFER-ENCONDING 변환표
8. 메일본문 읽어오기 <? $object= imap_fetchbody($stream,$msgno,$partno); ?>
imap_fetchbody는 메시지 번호의 메일에서 파트번호에 해당하는 메일본문 내용만을 뽑아 string형태로 돌려주는 기능을 하며 문자열을 출력하는 것으로 메일 내용을 출력하는 것과 동일하다.
9. IMAP스트림을 닫자 메일리스트 출력코드와 동일하다.
10. 메일본문 출력방법 메일본문의 출력은 먼저 imap_fetchstructure함수를 이용하여 메일 메시지의 구조체를 읽어들인다. 얻어온 정보를 바탕으로 텍스트 메시지라면 화면에 출력하고, 첨부파일이라면 첨부파일 처리 루틴을 작성하여 다운 받도록 처리해준다. 텍스트 메시지의 출력은 imap_fetchstructure에서 얻어온 parts를 바탕으로 각각에 맞는 디코딩을 처리해 준다. 다음의 메일 원문 코드를 바탕으로 실제 처리가되는 과정을 살펴보자. 원문코드는 일반 텍스트메시지에 JPG형식의 이미지를 첨부한 예제이다.
파일명 : mail_read.html <? $stream = imap_open("{www.leelab.co.kr/pop3:110}Inbox","jklee","xxxxx"); $object_info = imap_mailboxmsginfo($stream); $mbox_record =$object_info->Nmsgs;
for($msgno=$mbox_record; $msgno > 0; $msgno--) { $object= imap_fetchstructure($stream,$msgno);
echo("Type : $object->type <br>\n"); echo("Subtype : $object->subtype <br>\n"); echo("Encoding : $object->encoding <br>\n"); echo("Dispostion : $object->disposition <br>\n"); echo("Parameters : $object->parameters <br>\n"); echo("Dparameters : $object->dparameters <br>\n"); echo("Parts : $object->parts <br>\n"); } imap_close($stream); ?>
Content-Type과 Content-Transfer-Encoding, Parts정보를 출력해보자.
Subtype : MIXED Encoding : 0 Dispostion : Parameters : Array Dparameters : Parts : Array
위의 정보로 메일의 타입과 인코딩 정보를 읽어낼 수 있었다. multipart/mixed타입은 텍스트와 바이너리 등 여러 가지 타입이 섞여있는 메일을 말한다. 또한 Parts가 Array형태이므로 각 Part별로 imap_fetchbody를 이용해 메일을 읽어 출력해야한다는 사실도 알 수 있다. 최초의 Parts번호는 0번으로 지정된다. Parts의 갯수는 PHP함수중 count()를 이용하여 얻어올 수 있으며 위 메일 원문의 경우 Parts의 갯수는 두 개이다. Parts가 Array형태이므로 Parts를 분석하여 구조체정보를 읽어온다.
테스트를 위해 다시 위의 파일들을 수정하였다. 파일명: mail_list.html <html> <head> <title>웹메일</title> </head> <body bgcolor="white" text="black" link="blue" vlink="purple" alink="red"> <? $stream = imap_open("{www.leelab.co.kr/pop3:110}Inbox","jklee","xxxxx"); $object_info = imap_mailboxmsginfo($stream); $mbox_record =$object_info->Nmsgs; ?> <FORM method=post> <TABLE cellSpacing=0 cellPadding=0 width="500" border=0> <TR> <TD width="500"> <TABLE cellSpacing="0" width="500" border="1" bordercolordark="white" bordercolorlight="black"> <TR height=23> <TD align=middle width=40><font size="2">선택</font></TD> <TD align=middle width=180><font size="2">보낸이 </font></TD> <TD align=middle><font size="2">제 목 </font></TD> <TD align=middle width=150><font size="2">날짜 </font></TD> <? for($msgno=$mbox_record; $msgno > 0; $msgno--) { $object = imap_header($stream, $msgno); ?> <TR> <TD align=middle><INPUT type=checkbox value=27 name=check[]></TD> <TD align=left> <?echo $object->fromaddress;?></TD> <TD align=left> <? $imap_subject = imap_mime_header_decode($object->subject); $mbox_subject = $imap_subject[0]->text; echo ("<a href='mail_read.html?j=$msgno'>$mbox_subject</a>"); ?></TD> <TD align=middle><?echo $object->date;?></TD> </TR> <? } ?>
</TABLE></TD></TR></TABLE></form> <p> </p> </body> <? imap_close($stream); ?> </html>
파일명 : mail_read.html <HTML> <HEAD> <TITLE> 메일본문출력 </TITLE> </HEAD>
<BODY BGCOLOR="#FFFFFF"> <? $stream = imap_open("{www.leelab.co.kr/pop3:110}Inbox","jklee","xxxxx"); $object= imap_fetchstructure($stream,$j);
switch($object->type) { case 0: $mail_type = "text"; break; case 1: $mail_type = "multipart"; break; case 2: $mail_type = "message"; break; case 3: $mail_type = "application"; break; case 4: $mail_type = "audio"; break; case 5: $mail_type = "image"; break; case 6: $mail_type = "video"; break; case 7: $mail_type = "other"; break; default: $mail_type = "text"; break; }
$mail_subtype = strtolower($object->subtype);
$mail_mime = $mail_type . "/" . $mail_subtype;
switch($object->encoding) { case 0: $mail_encode = "7bit"; break; case 1: $mail_encode = "8bit"; break; case 2: $mail_encode = "binary"; break; case 3: $mail_encode = "base64"; break; case 4: $mail_encode = "quoted-printable"; break; case 5: $mail_encode = "other"; break; default: $mail_encode = "base64"; break; }
$mail_lines = $object->lines; $mail_bytes = $object->bytes; $mail_parts = $object->parts; $mail_ifdisposition = $object->ifdisposition; $mail_disposition = strtolower($object->disposition); $mail_ifdparameters = $object->ifdparameters; $mail_dparameters = $object->dparameters; $mail_ifparameters = $object->ifparameters; $mail_parameters = $object->parameters;
echo("Type : $mail_type <br>\n"); echo("Subtype : $mail_subtype <br>\n"); echo("MIME : $mail_mime <br>\n"); echo("Encoding : $mail_encode <br>\n"); echo("Dispostion : $mail_disposition <br>\n"); echo("Parameters : $mail_parameters <br>\n"); echo("Dparameters : $mail_dparameters <br>\n"); echo("Parts : $mail_parts <br>\n");
imap_close($stream); ?>
</BODY> </HTML>
(가) Parts가 null일 때
Subtype : plain Encoding : quoted-printable Parts : null
이제 실제 메일에 대한 정보가 나타나기 시작한다. 우선 타입이 text/plain이면 단순 텍스트 메시비이므로 화면에 읽어서 출력해야한다. 인코딩 타입이 quoted-printable이면, imap_qprint함수로 디코딩해서 출력한다. 마지막으로 Parts가 null이면 1번으로 지정한다.
Parts 1번의 메일본문을 읽어들여 디코딩해서 출력해보자.. 메일이 단순 텍스트메시지일 때는 imap_fetchbody함수를 이용해서 메일 본문을 읽어들인다. $string = imap_fetchbody($stream,$j,1); 읽어들인 $string의 값은 인코딩 타입이 quoted-printable일 때 imap_qprint함수를 이용하여 디코딩해서 출력한다. printf("%s", imap_qprint($string));
(나) Parts가 Array일 때 Parts가 Array이고 두 개의 Parts로 구성이 돼있다. 1개의 Parts는 2,3번에서 처리를 했으므로 2번째 Part를 분석하여 구조체 정보를 읽어온다.Type : image 이번의 경우는 타입이 image/pjpeg이다. 이것은 JPG이미지를 말하며 이미지의 경우 보통은 첨부파일의 형태로 처리된다. 또한 인코딩 타입이 base64이므로 imap_base64함수를 이용해 디코딩해야 한다는 것도 알 수 있다. Parts 번호는 2번으로 지정한다. 첨부파일의 경우는 구조체 정보에서 몇가지 더 확인해야 할 것이다.
Parameters : new.gif Dparameters : new.gif Dispostion의 결과처럼 메일 속성이 첨부로 되어있고 첨부파일명이 new.gif라는 정보를 확인할 수 있다. Part 2번의 메일본문을 읽여들여 출력한다. 메일이 첨부파일이므로 imap_fetchbody함수를 이용해 메일 본문을 읽어들여 화면에 출력하는 과정 대신에 다운로드 받을 수 있도록 처리해 준다. Parameters정보와 Parts번호를 이용하여 첨부파일에 대한 정보만을 출력한다. Parts가 두 개인 간단한 첨부파일이 있는 예를 살펴봤다. 실제 메일 본문을 처리할 경우 이보다 더욱 복잡하고 난해한 Content-Type을 처리해야 할 경우가 많이 생긴다. 대부분 Content-type을 RFC참조하여 처리하면 가능하지만 그중 주의할 것은 nested(중첩)된 Parts를 처리할 경우다. 즉, 1개의 parts안에 새로운 Parts들이 존재하는 경우이며 아웃룩을 이용하여 메일을 발송할 경우 multipart/alternative같은 Content-Type이 그것이다. 이 경우의 처리는 Parts 번호가 1번인 메일에 .1, .2와 같이 번호를 증가시키며 읽여들이면 된다. 즉, imap_fetchbody함수의 partno부분에 parts번호를 1.1, 1.2 ...와 같이 입력하면 된다. 위의 mail_read.html파일을 수정하였습니다. 파일명 : mail_read.html <HTML> <HEAD> <TITLE> 메일본문출력 </TITLE> </HEAD>
<BODY BGCOLOR="#FFFFFF"> <? $stream = imap_open("{www.leelab.co.kr/pop3:110}Inbox","jklee","xxxxx"); $object= imap_fetchstructure($stream,$j); $object_header = imap_header($stream, $j);
$mbox_subject = imap_mime_header_decode($object_header->subject); $mail_subject = $mbox_subject[0]->text; $mail_date = $object_header->date; $mail_from = $object_header->fromaddress;
switch($object->type) { case 0: $mail_type = "text"; break; case 1: $mail_type = "multipart"; break; case 2: $mail_type = "message"; break; case 3: $mail_type = "application"; break; case 4: $mail_type = "audio"; break; case 5: $mail_type = "image"; break; case 6: $mail_type = "video"; break; case 7: $mail_type = "other"; break; default: $mail_type = "text"; break; }
$mail_subtype = strtolower($object->subtype);
$mail_mime = $mail_type . "/" . $mail_subtype;
switch($object->encoding) { case 0: $mail_encode = "7bit"; break; case 1: $mail_encode = "8bit"; break; case 2: $mail_encode = "binary"; break; case 3: $mail_encode = "base64"; break; case 4: $mail_encode = "quoted-printable"; break; case 5: $mail_encode = "other"; break; default: $mail_encode = "base64"; break; }
$mail_lines = $object->lines; $mail_bytes = $object->bytes;
$mail_parts = $object->parts; $mail_ifdisposition = $object->ifdisposition; $mail_disposition = strtolower($object->disposition); $mail_ifdparameters = $object->ifdparameters; $mail_dparameters = $object->dparameters; $mail_ifparameters = $object->ifparameters; $mail_parameters = $object->parameters;
$mail_body = imap_fetchbody($stream,$j,1);
?> <TABLE cellSpacing=0 cellPadding=0 width="500" bgColor=#6688cc border=0> <TR> <TD> <TABLE cellSpacing=1 cellPadding=3 width="500" align=center border=0> <TR height=23> <TD align=middle width=100 bgColor=#eeeeee><FONT color=#31317b size="2">보낸 날짜</FONT> </TD> <TD bgColor=#ffffff><font size="2"> <?echo $mail_date;?></font></TD></TR> <TR> <TD align=middle bgColor=#eeeeee><FONT color=#31317b size="2">보낸 사람</FONT> </TD> <TD bgColor=#ffffff><font size="2"> <?echo $mail_from;?></font></A> </TD></TR> <TR> <TD align=middle bgColor=#eeeeee><FONT color=#31317b size="2">메일 제목</FONT> </TD> <TD bgColor=#ffffff><font size="2"> <?echo $mail_subject;?></font></TD></TR> <TR> <TD align=middle bgColor=#ccccff><FONT color=#31317b size="2">첨부 파일</FONT> </TD> <TD bgColor=#ffffff><font size="2"> </font></TD></TR></TABLE></TD></TR></TABLE><BR> <TABLE width="500"> <TR><TD> <font size="2"> <?echo $mail_body;?> </font> </TD></TR></TABLE> <? imap_close($stream); ?>
</BODY> </HTML>
위의 프로그램은 첨부파일은 처리하지 못한다. 위의 내용을 수정하여 첨부파일 및 기타 MIME처리가 가능하도록 수정하자.
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
[목차] |