반응형
C로 구현하는 MIME Parser (마지막회)

MIME Parser의 실제 구현

이번 호는 연재의 마지막으로 실제 MIME Parser를 구현해볼 것이다. 비록 제한된 지면으로 인해 모든 것을 다 설명할 수는 없지만 MIME Parser의 핵심만큼은 이해할 수 있도록 설명할 것이다. 이번 호의 내용이 쉽게 이해된다면 자신만의 MIME Parser를 구현하는 것은 시간 문제일 것이다. 그럼, MIME Parser를 실제로 구현해보도록 하자.

(주)넷사랑컴퓨터 조한열
hanyoul@netsarang.com


1. MIME Parser의 자료 구조

프로그램을 만들 때, 가장 중요한 것 중의 하나가 자료 구조이다. 자료 구조만 보면 프로그램의 모든 것을 본 것과 같다라는 명언이 있을 정도로 프로그램을 설계할 때 가장 먼저 고려해야 할 것이 자료 구조 설계이다. 우리도 MIME Parser를 위한 자료 구조를 설계하여 보자.
MIME Parser는 stream을 입력 받아 입력 받은 stream을 의미 있는 Mail Message로 만드는(파싱하는) 것을 주된 목표로 삼는다. 그러므로 우리는 Mail Message를 저장할 수 있는 자료 구조를 설계해야 한다.
Mail Message(MIME을 포함하는 Mail Message)는 크게 두 가지 부분으로 나눌 수 있다. 첫 번째는 헤더이고 두 번째는 바디이다. 헤더는 RFC822 헤더를 가리킨다. 헤더에는 Mail의 모든 정보가 들어가 있다. 바디는 한 개 혹은 여러 개의 Content로 구성된다. 물론 아예 컨텐트가 없는 경우도 있을 수 있다. MIME Message는 여러 개의 컨텐트를 가질 수도 있고 아니면 아예 컨텐트가 없을 수도 있기 때문이다. 각각의 컨텐트는 컨텐트의 정보를 가리키는 부분(MIME Header에 해당하는 부분)과 실제 데이터로 구성된다. 이와 같은 특성을 갖고 있는 MIME Message를 저장할 수 있는 자료 구조를 다음과 같이 설계하였다.

[Mail Message]
struct H_MESSAGE
{
struct H_HEADER *m_header; /* header를 가리키는 포인터 */
struct H_BODY *m_body; /* body를 가리키는 포인터 */
};

Mail Message는 위의 구조체에 저장된다. Mail Message가 헤더와 바디로 구성되어 있기 때문에 헤더를 가리키는 포인터와 바디를 가리키는 포인터를 가지고 있다. 이때, H_HEADER, H_BODY는 바로 뒤에 정의하게 될 헤더와 바디를 저장할 수 있는 구조체이다.

[HEADER]
struct H_HEADER
{
char *h_field; /* header field name */
char *h_value; /* header field value */
struct H_HEADER *h_next; /* 다음 header entry를 가리키는 포인터 */
};

RFC822 헤더는 위의 구조체에 저장된다. RFC822 헤더는 콜론(:)으로 구분되는 name-value쌍으로 이루어져 있다. 위에서 h_field는 각 헤더의 이름을 가리키고(Subject, To, X-Mailer등등) h_value는 h_field에 대응되는 값을 가리키고 있다. 또한 위에서 역상으로 표시된 곳을 보면 H_HEADER라는 구조체가 연결 리스트(Linked List)라는 것을 알 수 있다. 즉 name-value 쌍의 연결 구조로서 헤더를 저장할 수 있다는 것이다.

[BODY]
struct H_BODY
{
char *b_c_type; /* body part의 Content-Type */
char *b_c_desc; /* body part의 Content-Description */
char *b_c_encode; /* body part의 Content-Transfer-Encoding */
char *b_c_id; /* body part의 Content-ID */
char *b_charset; /* body part의 charater set */
char *b_filename; /* body part의 실제 파일이름 */
char *b_savename; /* body part가 파일시스템에 저장된 이름 */
struct H_BODY *b_next; /* 다음 body part를 가리키는 포인터 */
struct H_BODY *b_body; /* 중첩된 구조의 multipart를 가리키는 포인터*/
};

MIME Message의 컨텐트들은 위의 구조체에 저장된다. 위의 구조체에서 b_c_type(Content-Type), b_c_desc(Content-Description), b_c_encode(Cotent-Transfer-Encoding), b_c_id(Content-ID), b_charset(Content-Type의 부가 정보인 Character Set), b_filename(Content-Dispositon의 부가 정보인 Filename)은 MIME 헤더에서 나오는 컨텐트에 관한 정보들이다. 그리고 실제 컨텐트의 데이터는 b_savename이 가리키는 파일로 저장될 것이다.
위의 구조체에서 눈여겨 봐야 할 것은 b_next와 b_body이다. b_next는 다음 컨텐트를 가리킨다. 일반적인 컨텐트들이 MIME 형식으로 인코딩 되어 있다면 그 Content들은 b_next를 통해 연결 리스트를 형성할 것이다. 그렇다면 b_body는 어디에 사용할 것인가. b_body는 바로 지난 호에서 살펴본 중첩된 구조의 MIME Message에서 사용할 것이다. 즉 multipart내에 또 다시 multipart가 있을 때, 우리는 새로운 body의 연결 리스트를 b_body에 저장할 것이다.

2. MIME Parser의 알고리즘

MIME Parser의 알고리즘은 의외로 간단하다. 일단 중요한 것은 Mail Message를 한 줄씩 읽어 들인다는 것이다. 모든 프로세스가 한 줄단위로 이루어진다.
MIME Parser는 한 줄씩 읽어 들이면서 헤더를 파싱하고 그 뒤 바디를 파싱한다. gpejdhk 바디는 빈 줄 하나로 구분되기 때문에 한 줄씩 읽어 들이다가 빈 줄을 만나며 헤더가 끝났다는 것을 알 수 있게 되고 그 뒤부터는 바디를 파싱한다.
바디를 파싱하는데 있어서 중요한 것은 Content-Type이다. Content-Type이 Multipart이면 boundary가 구분하는 경계를 찾아서 각각의 Content들을 하나씩 하나씩 파싱하면 된다. 이 때, boundary를 찾는 것 역시 한 줄씩 읽어 들이면서 체크를 하게 된다.

2.1 MIME Parser의 핵심 함수

MIME Mesasge를 파싱하기 위한 핵심 함수는 다음과 같다.

● hParseMessageRFC822()
아래에서 소개될 hMessageHeaderParsing(), hMessageBodyParsing() 함수를 호출하여 Mail Message stream으로부터 헤더와 바디를 파싱하고 파싱된 헤더와 바디를 H_MESSAGE 구조체에 저장한 뒤 저장된 구조체를 리턴하는 함수이다. 이 때, Mail Message stream은 파일로 저장되어 있다고 가정하며, Mail Message가 저장된 파일의 file descriptor를 인수로 넘겨받아 사용하게 된다.

● hMessageHeaderParsing()
hParseMessageRFC822() 함수가 넘겨준 Mail Message stream을 저장하고 있는 file descriptor로부터 헤더를 파싱해 내고 그 결과를 H_HEADER 구조체에 저장한 뒤 구조체를 리턴한다.

● hMessageBodyParsing()
hParseMessageRFC822() 함수가 넘겨준 Mail Message stream을 저장하고 있는 file descriptor(이 때, file descriptor의 파일포인터는 헤더를 파싱하고 난 다음라인을 가리키고 있다.)로부터 바디를 파싱해 내고 그 결과를 H_BODY 구조체에 저장한 뒤 구조체를 리턴한다. 이 때, 아래의 두 함수 hParseMultipartMixed(), hCollect()를 적절하게 사용한다.

● hParseMultipartMixed()
hMessageBodyParsing() 함수가 넘겨준 file descriptor와 boundary(Content-Type의 부가정보인 boundary)를 이용하여 Multipart Content를 파싱하고 파싱된 결과를 H_BODY 구조체에 저장한 뒤 구조체를 리턴한다. 이 때, 리턴된 H_BODY 구조체는 b_body에 저장된다.

● hCollect()
hMessageBodyParsing() 함수가 넘겨준 file descriptor와 boundary(Content-Type의 부가정보인 boundary)를 이용하여 Multipart가 아닌 하나의 Content만을 파싱하고 파싱된 결과를 temporary 파일을 생성하여 저장한다. 이 때, Content-Transfer-Encoding 정보를 이용하여 데이터를 디코딩 한다.

2.2 MIME Parser의 핵심 Source코드

다음은 위에서 설명한 함수들의 소스 코드이다. 소스 코드 중간 중간에 사용하는 함수들에 대해서 짤막하게 설명하고 넘어가도록 하겠다. 이 함수들을 구현하는 것은 독자들의 몫으로 남긴다.

● hReadLineCRLF2LF(int fd, char *buf, int size)
file descriptor의 파일 포인터로부터 한 라인을 읽어 들여 buf에 저장하는 함수이다. 이 때, CRLF는 LF로 변환한다.

● hStrHasSpace(char *str)
str에 space가 있는지 확인하는 함수 있다. space가 있다면 1을 리턴하고 그렇지 않다면 0을 리턴한다.

● hStrLtrim(char *str)
str의 왼쪽 부분(처음 부분)에 있는 white space(공백)을 제거하는 함수이다. 리턴 값은 공백이 없어진 새로운 str의 포인터이다.

● hStrRtrim(char *str)
str의 오른쪽 부분(마지막 부분)에 있는 white space(공백)을 제거하는 함수로서 나머지는 hStrLtrim()과 동일하다.

● hHeaderAddValue(struct H_HEADER **header, char *field, char *value)
H_HEADER 구조체에 새로운 엔트리를 첨가하는 함수이다. 이 때 첨가되는 엔트리의 값은 field, value 쌍이다.

● hHeaderNamedValueCat(struct H_HEADER *header, char *field, char *str)
field가 가리키는 엔트리를 header에서 찾아 str을 h_value에 덧붙인다.

● hHeaderContentTypeGet(struct H_HEADER *header)
header에서 Content-Type의 값을 찾아 리턴한다.

● hHeaderContentEncodingGet(struct H_HEADER *header)
header에서 Content-Transfer-Encoding의 값을 찾아 리턴한다.

● hHeaderCharsetGet(struct H_HEADER *header)
header에서 charset의 값을 찾아 리턴한다.

● hHeaderFilenameGet(struct H_HEADER *header)
header에서 filename의 값을 찾아 리턴한다.

● hStrToLower(char *str)
str을 모두 lower case로 만든다.

● hSavenameGet()
temporary 파일 이름을 만들어 리턴한다.

리스트 1 : struct H_MESSAGE *hParseMessageRFC822(int fd, const char *boundary)
{
struct H_MESSAGE *msg = (struct H_MESSAGE *)NULL;
struct H_HEADER *tempHeader = (struct H_HEADER *)NULL;
struct H_BODY *tempBody = (struct H_BODY *)NULL;

msg = hMessageCreate();

tempHeader = hMessageHeaderParsing(fd);

if(!tempHeader)
return NULL;

tempBody = hMessageBodyParsing(fd, tempHeader, boundary);

if(!tempBody)
return NULL;

msg->m_header = tempHeader;
msg->m_body = tempBody;

return msg;
}

리스트 1의 hMessageCreate()는 H_MESSAGE 구조체의 메모리를 동적으로 할당하는 함수이다. 위의 소스코드에서 msg는 H_MESSAGE 구조체의 포인터로서 리턴할 값이다. 위에서 살펴본대로 msg는 두개의 멤버를 가지고 있는데, 하나는 헤더를 가리키는 m_header이고, 다른 하나는 바디를 가리키는 m_body이다. hMessageHeaderParsing() 함수로 만들어진 H_HEADER 구조체(tempHeader)와 hMessageBodyParsing() 함수로 만들어진 H_BODY 구조체(tempBody)를 msg에 저장하고 msg를 리턴한다.


리스트 2 : struct H_HEADER *hMessageHeaderParsing(int fd)
{
char buf[BUF_SIZE];
char *field = (char *)NULL;
char *value = (char *)NULL;
int islong; /* header가 long임을 나타내는 flag */
struct H_HEADER *retHeader = (struct H_HEADER *)NULL;

/* EOF을 만났다는 것은 파일의 끝 */
if(hReadLineCRLF2LF(fd, buf, BUF_SIZE) == NULL)
return NULL;

while(TRUE)
{
/* LF로 시작되는 행은 header의 끝을 나타낸다. */
if(!strcmp(buf, "\n")) break;

/* 첫 character가 white space이면 long header(RFC822 정의) */
islong = hIsSpace(buf[0]) ? LONG : NOT_LONG;

switch(islong)
{
case NOT_LONG:
field = (char *)strtok(buf , ":");
value = (char *)strtok(NULL, "");

if(hStrHasSpace(field)) break; /* field에 space가 있으면 :쌍이 아니다. */

/* value 맨 앞 character는 space 이다. */
value = hStrLtrim(value);

if(field != NULL && value != NULL)
hHeaderAddValue(&retHeader, field, value);

break;

case LONG:
/* long character 이면 뒤에 삽입한다. */
hHeaderNamedValueCat(retHeader, NULL, buf);
break;
}

if(hReadLineCRLF2LF(fd, buf, BUF_SIZE) == NULL) /* 헤더가 중간에 비정상적으로 끝나버린 경우 */
return retHeader;
}

return retHeader;
}

리스트 2의 함수는 헤더를 파싱하여 H_HEADER 구조체에 저장하고 저장된 H_HEADER 구조체를 리턴하는 함수이다. 그런데 헤더 파싱에는 한가지 유의할 것이 있다. 바로 RFC822 에서 정의하고 있는 long 헤더 때문이다. 다음 예와 같이 long 헤더는 여러 줄에 걸쳐서 나올 수 있다.

[long 헤더의 예]

Subject: 이것은 긴 제목의 예입니다. 제목 역시 헤더에 들어가죠? 얼마나 긴 제목인지 한번 살펴보세요.

앞서 우리는 메일 파싱을 위해 메일을 한 줄씩 읽어 들인다고 했다. 그런데 위에서 살펴 본대로 long 헤더는 여러 줄에 걸쳐 나올 수 있으므로 name, value 쌍인 H_HEADER 구조체에 저장하기 위해서는 여러 줄에 걸쳐 나오는 헤더의 value를 하나로 합쳐서 저장해야 한다. 다행히도 RFC822 문서에는 어떤 줄의 첫 문자가 White Space(공백)이면 이 라인은 바로 위에 나온 헤더 name에 속한다라고 정의하고 있다. 그러므로 우리는 한 줄씩 읽어 들여가며 첫번째 문자가 공백인지를 체크하고 공백이라면 바로 위에 나온 헤더 name에 속한 value에 덧붙이면 된다. 이 때, 사용하는 것이 hHeaderNamedValueCat() 함수이다(리스트 3).

리스트 3 : hHeaderNamedValueCat() 함수
struct H_BODY *hMessageBodyParsing(int fd, const struct H_HEADER *hd, const char *boundary)
{
struct H_BODY *retBody = (struct H_BODY *)NULL;
struct H_BODY *tempBody = (struct H_BODY *)NULL;
struct H_MESSAGE *tempMsg = (struct H_MESSAGE *)NULL;
char buf[BUF_SIZE];
char *content_type = (char *)NULL;
char *content_encoding = (char *)NULL;
char *value = (char *)NULL;
char *decodeValue = (char *)NULL;
char *nested_boundary = (char *)NULL;
char *filename = (char *)NULL;
char *savename = (char *)NULL;
char *charset = (char *)NULL;
int state = 0;
int isend = 0;
int decodeLen = 0;

content_type = hHeaderContentTypeGet(hd);

if(content_type != NULL)
{
content_type = strtok(content_type, ";");
content_type = hStrRtrim(content_type);
}
else /* Content Type이 없으면 디폴트로 text/plain이다. */
content_type = "text/plain";

content_encoding = hHeaderContentEncodingGet(hd); /* 모든 body part에 적용되는 encoding type */

/* body의 Content-Type에 따라 프로세스 결정 */
if (!strcmp(hStrToLower(content_type), "message/rfc822" ))
state = MESSAGE_RFC822;
else if(!strcmp(hStrToLower(content_type), "multipart/mixed" ))
state = MULTIPART_MIXED;
else if(!strcmp(hStrToLower(content_type), "multipart/alternative"))
state = MULTIPART_ALTERNATIVE;
else if(!strcmp(hStrToLower(content_type), "multipart/report"))
state = MULTIPART_REPORT;
else if(!strcmp(hStrToLower(content_type), "multipart/related"))
state = MULTIPART_RELATED;
else
state = REGULAR;

switch(state)
{

case MULTIPART_ALTERNATIVE:
case MULTIPART_REPORT:
case MULTIPART_RELATED:
case MULTIPART_MIXED:

nested_boundary = hHeaderBoundaryGet(hd);

/* boundary가 NULL이면 parsing 할 수 없다. */
if(!nested_boundary)
return NULL;

tempBody = hParseMultipartMixed(fd, nested_boundary);

/* body linked list에 첨가 */
hBodyAddValue(&retBody, content_type, NULL, content_encoding, NULL, NULL, NULL);

retBody->b_body = tempBody;

while(TRUE)
{
if(hReadLineCRLF2LF(fd, buf, BUF_SIZE) == NULL)
break;
}

break;

case MESSAGE_RFC822:
case REGULAR:

savename = hSavenameGet();

hCollect(fd, savename, boundary, content_encoding, &isend);

/* filename과 charset을 header에서 알아낸다. */
filename = hHeaderFilenameGet(hd);
charset = hHeaderCharsetGet(hd);

/* body linked list에 첨가 */
hBodyAddValue(&retBody, content_type, NULL, content_encoding, charset, filename, savename);

break;
}

return retBody;

}

리스트 3의 함수와 아래에서 나올 hParseMultipartMixed() 함수가 이번 호의 핵심 중의 핵심이다. 리스트 3의 함수는 Mail Message의 헤더에서 Content-Type을 구하고 Content-Type에 따라 Content-Type이 multipart이면 MIME decoding을 위해 hParseMultipartMixed() 함수를 호출하고 multipart가 아니면 hCollect() 함수를 호출한다. hParseMultipartMixed(), hCollect() 함수호출에 따라 얻어진 H_BODY 구조체를 리턴하는 것이 이 함수의 역할이다.

리스트 4 : hParseMultipartMixed() 함수
struct H_BODY *hParseMultipartMixed(int fd, const char *boundary)
{
struct H_MESSAGE *tempMsg = (struct H_MESSAGE *)NULL;
struct H_HEADER *tempHeader = (struct H_HEADER *)NULL;
struct H_BODY *retBody = (struct H_BODY *)NULL;
struct H_BODY *tempBody = (struct H_BODY *)NULL;
char buf[BUF_SIZE];
char boundaryn[256];
char boundaryEOFn[256];
char *nested_boundary = (char *)NULL;
char *value = (char *)NULL;
char *decodeValue = (char *)NULL;
char *content_type = (char *)NULL;
char *content_encoding = (char *)NULL;
char *filename = (char *)NULL;
char *savename = (char *)NULL;
char *charset = (char *)NULL;
int isend = 0;
int state = 0;
int decodeLen = 0;

sprintf(boundaryn, "--%s\n", boundary);
sprintf(boundaryEOFn, "--%s--\n", boundary);

/* MIME prologue 제거 */
while(TRUE)
{
if(hReadLineCRLF2LF(fd, buf, BUF_SIZE) == NULL)
return NULL;

if(!strcmp(buf, boundaryn))
break;
}

while(TRUE)
{
tempHeader = hMessageHeaderParsing(fd);

content_type = hHeaderContentTypeGet(tempHeader);

if(content_type != NULL)
{
content_type = strtok(content_type, ";");
content_type = hStrRtrim(content_type);
}
else
content_type = "text/plain";

content_encoding = hHeaderContentEncodingGet(tempHeader); /* 모든 body part에 적용되는 encoding type */

/* body의 Content-Type에 따라 프로세스 결정 */
if (!strcmp(hStrToLower(content_type), "message/rfc822" ))
state = MESSAGE_RFC822;
else if(!strcmp(hStrToLower(content_type), "multipart/mixed" ))
state = MULTIPART_MIXED;
else if(!strcmp(hStrToLower(content_type), "multipart/alternative"))
state = MULTIPART_ALTERNATIVE;
else if(!strcmp(hStrToLower(content_type), "multipart/report"))
state = MULTIPART_REPORT;
else if(!strcmp(hStrToLower(content_type), "multipart/related"))
state = MULTIPART_RELATED;
else
state = REGULAR;

switch(state)
{
case MULTIPART_ALTERNATIVE:
case MULTIPART_REPORT:
case MULTIPART_RELATED:
case MULTIPART_MIXED:

nested_boundary = hHeaderBoundaryGet(tempHeader);

/* boundary가 NULL이면 parsing 할 수 없다. */
if(!boundary)
return NULL;

tempBody = hParseMultipartMixed(fd, nested_boundary);

/* body linked list에 첨가 */
hBodyAddValue(&retBody, content_type, NULL, content_encoding, NULL, NULL, NULL);

retBody->b_body = tempBody;

/* multipart내의 multipart이면 다음 라인은 boundary이거나 boundaryEOF이다. 즉, skip */
while(TRUE)
{
if(hReadLineCRLF2LF(fd, buf, BUF_SIZE) == NULL)
{
isend = 1;
break;
}

if(!strcmp(buf, boundaryn))
{
isend = 0;
break;
}
/* boundaryEOFn을 만나면 파싱이 끝난다. */
else if(!strcmp(buf, boundaryEOFn))
{
isend = 1;
break;
}
}

break;

case MESSAGE_RFC822:

case REGULAR:

savename = hSavenameGet();

hCollect(fd, savename, boundary, content_encoding, &isend);

/* filename과 charset을 header에서 알아낸다. */
filename = hHeaderFilenameGet(tempHeader);
charset = hHeaderCharsetGet(tempHeader);

/* body linked list에 첨가 */
hBodyAddValue(&retBody, content_type, NULL, content_encoding, charset, filename, savename);

break;
}

if(isend == 1) /* end of multipart */
break;

}

return retBody;
}

리스트 4의 함수를 몇 번이고 읽어보게 되면 자연스레 MIME Parsing의 원리를 이해할 수 있을 것이다. 이 함수의 기본 동작은 boundary를 찾는 것이다. Boundary를 찾아서 각각의 Content들을 파싱 해낸다. 파싱 된 각각의 Content들은 H_BODY 구조체에 저장된 채로 연결리스트를 이루게 된다. Content-Type이 Multipart가 아닌 각각의 Content들을 파싱하기 위해서는 hCollect() 함수의 도움을 받는다. 만약 Content가 또 다시 Multipart이면 recursive하게 hParseMultipartMixed() 함수를 호출한다. 이렇게 호출된 hParserMultipartMixed() 함수의 끝내기 조건은 --boundary-를 만났을 때이다. --boundary-가 MIME Message의 끝을 가리키고 있는 경계선이기 때문이다. 위의 함수에서 --boundary-를 만나면 isend 라는 변수를 1로 셋팅하게 되고, while() 루프에서 isend가 1이면 루프를 빠져 나와 H_BODY 구조체를 리턴한다.

리스트 5 : Content를 파싱하는 함수
int hCollect(int fd, const char *filename, const char *boundary, const char *content_encoding, int *isend)
{
int valuelen = 0;
int buflen = 0;
int templen = 0;
char *temp = (char *)NULL;
char boundaryn[256], boundaryEOFn[256];
char buf[BUF_SIZE];
int i;
FILE *fout;

fout = fopen(filename, "w+");

if(!boundary) /* boundary가 없는 경우 */
{
while(TRUE)
{
if(hReadLineCRLF2LF(fd, buf, BUF_SIZE) == NULL)
{
fclose(fout);
*isend = 1;

return 0;
}

temp = hStrDecode(buf, content_encoding, &templen);

for(i=0;i<templen;i++)
fputc((int)temp[i], fout);

if(temp != buf)
free(temp);
}
}
else /* boundary가 있는 경우 */
{
/* boundary set */
sprintf(boundaryn, "--%s\n", boundary);
sprintf(boundaryEOFn, "--%s--\n", boundary);

while(TRUE)
{
if(hReadLineCRLF2LF(fd, buf, BUF_SIZE) == NULL)
{
fclose(fout);

*isend = 1;
return 1;
}

if(!strcmp(buf, boundaryn))
{
fclose(fout);

*isend = 0;
return 0;
}
else if(!strcmp(buf, boundaryEOFn))
{
fclose(fout);

*isend = 1;
return 0;
}

temp = hStrDecode(buf, content_encoding, &templen);

for(i=0;i<templen;i++)
fputc((int)temp[i], fout);

if(temp != buf)
free(temp);
}
}

return 1;
}

리스트 5의 함수는 multipart가 아닌 Content를 파싱하는 함수이다. Boundary가 없을 때는 Content가 multipart 안에 들어 있는 것이 아니기 때문에 Mail Message의 끝까지가 Content 데이터이다. 그렇지 않고 boundary가 존재한다면 파싱하려는 Content는 multipart 안에 속해있는 Content이기 때문에 boundary를 체크 해야 한다. 만약 --boundary-를 만나면 MIME의 끝이기 때문에 isend 값을 1로 셋팅하여 이 함수를 호출한 함수에게 MIME이 끝났음을 알린다. 파싱된 데이터는 hSavenameGet() 함수에 의해 얻어진 임시 파일이름으로 저장된다.

리스트 6 : hSavenameGet() 함수
char *hSavenameGet(void)
{
struct timeval t;
static char savename[512];

gettimeofday(&t, NULL);
sprintf(savename, "%s/%ld.%ld%ld", TMP_DIR, getpid(), t.tv_sec, t.tv_usec);

return savename;
}

리스트 6의 함수는 temporary 파일이름을 리턴하는 함수이다. TMP_DIR는 파싱된 Content들의 데이터가 임시 파일로 저장될 디렉토리를 가리킨다.

마치며

지금까지 간략하게나마 MIME Parser의 핵심부분을 살펴보았다. 잘 이해가 되지 않는다면 지난 호의 내용과 이번 호의 소스 코드를 계속해서 읽어보길 바란다. 조만간 프로그램세계 홈페이지를 통하여 필자의 홈페이지를 공지하며 MIME Parser를 라이브러리 형태로 공개할 예정이니 참고하길 바란다. 지금까지 4회에 걸쳐 필자의 글을 읽어준 독자들에게 다시 한 번 감사의 말씀을 전하며 마칠까 한다.

+ Recent posts