반응형
C로 구현하는 MIME Parser (2)

MIME과 7bit 인코딩

전자메일을 통해 바이너리 파일을 전송하거나, 다양한 민족국가의 언어를 전송하기 위해서는 8bit 전송이 필수적이다. 이번 호에서는 MIME의 개괄적 이해와 7bit 인코딩에 대한 내용을 알아보도록 하자.

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

1. MIME

MIME이란 그 말뜻 그대로, 다양한 목적을 위해 전자메일 메시지 형식을 확장시킨 것을 말한다. 지난 호에서도 언급했지만 미국에서 군사적인 목적으로 연구 개발되어 사용되던 인터넷이 전세계로 확산되면서 인터넷에 대한 다양한 요구들이 늘어나기 시작했다. 이러한 요구들 가운데에는 물론 전자메일을 그 대상으로 하는 것도 많았다. 그 중 대표적인 것이 전자메일을 통해 바이너리 파일을 주고 받으려는 것이다.
또한 미국이 아닌 다른 여러 나라의 언어를 가지고도 전자메일을 주고 받으려면 MSB(Most Significant Bit - 가장 최상위 비트)가 1인 바이트들도 전자메일을 통해 깨지지 않고 전송이 되어야 한다는 말이다. 그러나 7bit 전송 프로토콜인 SMTP를 기반으로 하는 전자메일은 MSB가 1인 바이트를 0으로 바꾸어 전송하기도 한다. 이처럼 아직도 많은 메일 서버들이 8bit 전송을 깨뜨려버리는 SMTP를 사용하고 있기 때문에 8bit 전송을 위해서는 새로운 방법이 필요하게 되었다. 그 새로운 방법이 바로 MIME이다.

1.1 MIME의 역할
MIME의 역할은 의외로 간단하다. 어떤 정해진 규칙에 따라서 8bit 데이터를 7bit 데이터로 바꾸어 주는 기능과, 바뀌어진 7bit 데이터를 원래의 8bit 데이터로 그대로 복원하는 기능을 제공하는 일이다. 7bit와 8bit 사이의 변환을 정의해 놓은 표준규약 역시 MIME에 포함되어 있다.
MIME의 중요한 역할은 또 하나 있다. 그것은 바로 메일 메시지에 여러 개의 파일들을 첨부해서 보낼 수 있도록 메일 메시지 형식을 정의해 놓은 것이다. 요즘은 흔하게 파일이 첨부된 메일을 받아볼 수 있다. 이것이 모두 MIME의 개발 덕분이다.

1.2 Multi-part MIME message
파일이 첨부된 메일을 Multi-part MIME message라고 한다. 첨부된 각각의 파일은 하나의 부분(part)을 이루고 있고, 이러한 부분들이 모여 하나의 메일 메시지를 형성하기 때문에 Multi-part MIME message라고 한다.

1.3 RFC 822 헤더와 MIME 헤더
지난 호에서 우리는 RFC 822 헤더에 관해 자세히 살펴보았다. RFC 822 헤더는 메일에 관한 정보를 알려주는 핵심적인 역할을 한다. MIME 헤더 역시 RFC 822 헤더와 마찬가지로 메일 형식에 관한 정보를 알려준다. MIME 헤더는 MIME의 규약에 따라 변환된 메일 메시지를 원래의 메시지로 재변환 시키는데 있어서 필요한 정보들을 제공한다. 즉, 어떤 방식으로 7bit 변환이 되었는지와 Multi-part MIME message를 파싱하기 위한 Boundary 정보들을 담고 있는 것이 MIME 헤더이다.
MIME 헤더 역시 RFC 822 헤더와 같은 형식을 가지고 있다. 즉, 이름과 값의 쌍으로 이루어져 있으며, 이름과 값을 구분하는 구분자는 콜론(:)이다.
대부분의 MIME 헤더는 Content-로 시작한다. MIME 헤더에는 다음과 같은 것이 있다.

·MIME-Version
·Content-Type
·Content-Transfer-Encoding
·Content-ID
·Content-Description
·Content-Disposition

<박스>
MIME 헤더의 예(역상부분이 MIME 헤더)
From hanyoul@netsarang.com Fri Jul 7 15:37:22 2000
Received: from HANYOUL (hanyoul.conux.com)
by netsarang.com (8.9.3/8.9.3) with SMTP id PAA20996
for <hanyoul@conux.com>; Fri, 7 Jul 2000 15:37:19 +0900
From: "Cho Hanyoul" <hanyoul@netsarang.com>
To: "=?ks_c_5601-1987?B?wbYgx9G/rQ==?=" <hanyoul@netsarang.com>
Subject: Test
Date: Fri, 7 Aug 2000 15:39:00 +0900
Message-ID: <FPEEJIDNJIB.hanyoul@conux.com>
MIME-Version: 1.0
Content-Type: multipart/mixed;
boundary="----=_NextPart_000_0009_01BFE829.78EE4E90"

This is a multi-part message in MIME format.


2. MIME 헤더 설명

2.1 Content-Type
Content-Type 헤더는 메일 메시지가 담고 있는 데이터가 어떤 종류의 데이터인지를 알려준다. MUA가 MIME을 해석할 때, Content-Type을 보고 이 메시지가 어떤 종류의 데이터인지를 알아야 디스플레이 해줄 수 있을 것이다. 예를 들어 Content-Type이 그림이라면 MUA는 메일 메시지가 담고 있는 데이터가 그림이라는 것을 인식하고 메일 메시지가 담고 있는 데이터를 읽어 들여 그림을 보여줄 것이고, 만약 Content-Type이 소리라면 메일 메시지의 데이터를 읽어 들여 소리를 출력하게 될 것이다. Content-Type은 text/plain, text/html, image/jpeg 등이 있다.

text/plain, image/jpeg 등을 미디어 타입(media type)이라고 한다. Content-Type으로 지정될 수 있는 미디어 타입은 IANA라는 국제기구에 의해 미리 정의되어 있다. 미디어 타입은 주타입과 부타입으로 나누어진다. 주타입은 8개가 있으며 각각의 주타입마다 무수한 부타입이 있다. 미디어 타입은 주타입/부타입의 형식으로 이루어진다. 즉 image/jpeg 이라는 미디어 타입은 image라는 주타입과 jpeg이라는 부타입으로 이루어진 것이다.

주타입 8가지는 다음과 같다.
·text
·image
·audio
·video
·application
·multipart
·message

<박스>
대표적인 Media Type
Text/plain, text/html, text/xml, text/enriched
image/gif, image/jpeg, image/tiff
audio/basic, audio/32kadpcm
video/mpeg, video/quicktime
model/vrml, model/mesh
application/octet-stream, application/zip, application/vnd.ms-excel
multipart/mixed, multipart/alternative
message/rfc822, message/news

Content-Type 헤더는 몇가지 부가 필드를 가질 수도 있다. 이러한 부가 필드들은 세미콜론(;)에 의해 구분된다.
만약 Content-Type이 text 미디어 타입이라면 charset(Character set - 문자세트)이라는 부가 필드를 가질 수 있다. 아래와 같은 Content-Type을 살펴보자.

Content-Type: text/plain; charset=us-ascii

이것은 메일 메시지가 Plain text(평범한 텍스트 데이터)라는 것을 나타내고 있으며 부가적으로 텍스트의 문자세트는 US ASCII, 즉 영문이라는 것을 나타내고 있다.
또한, Content-Type이 multipart 미디어 타입이라면 boundary라는 부가 필드를 가질 수 있다. 이 boundary 필드는 대단히 중요하다. 미디어 타입이 multipart라는 것은 메일 메시지의 데이터가 하나의 단일한 데이터가 아니라 여러 개의 데이터가 모여 하나의 메시지를 구성한 것을 뜻한다. 앞서 언급했던 첨부파일을 포함한 메일이 여기에 속한다. MUA가 multipart로 구성된 메일 메시지를 파싱하여 각각의 데이터로 만들기 위해서는 어디서부터 어디까지가 각각의 데이터인지를 알아야 한다. 이것을 알려주는 것이 바로 boundary라는 부가 필드이다. boundary가 가리키는 문자열이 바로 데이터들을 구분해주는 경계선이 되기 때문에 boundary 부가 필드는 Multipart MIME message를 파싱하는데 없어서는 안 될 중요한 요소이다(multipart message에 대한 자세한 설명은 다음 호에 연재 할 예정이다).

Content-Type: multipart/mixed;
boundary="----=_NextPart_000_0009_01BFE829.78EE4E90"

Content-Transfer-Encoding

Content-Transfer-Encoding 헤더는 굉장히 중요한 MIME 헤더이다. 바로 8bit 데이터를 어떤 방식을 통해 7bit 데이터로 변환시켰는지를 알려주는 헤더이기 때문이다. 그러므로 Content-Transfer-Encoding 헤더의 정보가 잘못되었을 경우에는, 원래의 데이터를 잃게 되고 만다.
Content-Transfer-Encoding 헤더의 값으로는 다음과 같은 것들이 올 수 있다.

·7bit
·8bit
·binary
·quoted-printable
·base64

위의 값 중에서 7bit, 8bit, binary는 그 어떤 변환도 하지 않음을 말해준다. 그냥 메일을 보낸 시점의 데이터가 변환되지 않고 그대로 메일 메시지에 실려왔음을 말해주는 것이다. 7bit는 메일 메시지가 7bit임을 뜻하고, 8bit는 메일 메시지가 8bit 데이터임을 말해준다. binary는 메일 메시지가 text가 아닌 binary 데이터임을 뜻한다.
우리가 눈여겨 봐야 할 것이 바로 quoted-printable과 base64라는 변환 방식이다. 이는 조금 후에 다시 자세히 설명한다. 이번 호에서 설명하고자 하는 핵심적인 내용이 바로 quoted-printable과 base64라는 변환 방식이다.

* Content-Disposition
: Content-Disposition 헤더는 현재의 데이터를 인라인(inline)으로 할 것인지, 아니면 첨부파일(attachment)로 할 것인지에 대한 것을 결정하는 헤더이다. 아직 실험적인 헤더이다.

* Content-Description
: Content-Description 헤더는 메일 메시지가 담고 있는 Content에 대한 설명을 해 놓는 헤더이다. 만약 MIME 이 해석이 되지 않을 때, 위의 필드를 보고 아래의 데이터가 무슨 데이터인지를 알 수 있도록 설명을 달아 놓으면 좋다.

* Content-ID
: Content-ID 헤더는 메일 메시지 외에 다른 Content를 가리킬 때 사용하지만, 잘 사용하지 않는다.

2.2 MIME Encoding
MIME의 역할 중 큰 역할이 바로 8bit 데이터를 7bit로 만드는 것이라고 앞서 언급했었다. 이러한 역할을 MIME Encoding이라고 한다. 앞서도 살펴봤듯이 MIME Encoding에는 두가지 방식이 있다. 바로 Quoted-Printable 방식과 Base64 방식이 그것이다.

2.3 Quoted-Printable Encoding & Decoding
Quoted-Printable Encoding 방식은 인코딩 된 메시지를 디코딩하지 않더라도 ASCII 문자들이 그대로 보일 수 있도록 하는 방식이다. 즉, 영문과 숫자등의 ASCII 7bit 문자들은 그대로 놔두고 8bit 문자만을 인코딩하는 방식이다. 이 때, 8bit 문자를 인코딩하는 방법은 대단히 간단하다. 8bit 문자는 등호(=) 뒤에 8bit 문자의 값을 16진수로 표현하여 써넣으면 된다. 이렇게 되면 모든 문자가 7bit 문자가 되어 메일 메시지 형태로 전송이 가능하다.
Quoted-Printable Encoding 방식은 대부분이 7bit ASCII 문자들이고, 가끔씩 8bit 문자들이 나오는 text 메시지를 인코딩하는데 유리한 방식이다.


2.4 Quoted-Printable Encoding 규칙
다음은 RFC2045에서 정의한 Quoted-Printable Encoding 규칙이다.

[규칙 1] 모든 옥텟(바이트)의 인코딩은 그 값을 16진수로 표현하여 '=' 뒤에 붙이면 된다. 옥텟의 값을 16진수로 표현하는데 사용되는 문자는 0123456789ABCDEF 이며, 대문자만을 사용한다. 예를 들어 십진수로 12인 옥텟(LF)을 Quoted-Printable로 인코딩하면 "=0C"가 되고, 십진수로 61인 옥텟(=)은 "=3D"가 되는 것이다. 뒤에 나오는 규칙 2-5에서 제시되고 있는 인코딩 방법을 사용하지 않는 모든 옥텟은 이 방식으로 인코딩 해야 한다.

[규칙 2] 십진수 33 - 60, 62 - 126의 문자(제어코드와 십진수 61인 '='를 제외한 아스키 값)는 규칙 1에 따라 인코딩 되지 않아도 된다. 즉, 이스케이프 문자인 '='를 제외한 영문자와 숫자등은 그대로 표현된다.

[규칙 3] 십진수 9(TAB)와 32(SPACE)는 절대로 인코딩 된 문자열의 끝에 나타나서는 않된다. 그러므로 문자열의 끝에 있는 Tab이나 Space는 규칙 1에 따라 인코딩 되어야 한다.

[규칙 4] 줄바꿈 문자(컴퓨터에 따라 CR, LF, CRLF로 각기 달리 나타나는 문자)는 CRLF의 형태로 표현되어야 한다.(RFC822 정의)

[규칙 5] Quoted-Printable로 인코딩 된 문자열의 길이는 76자 이상 이어서는 않된다. 이 때 문자열의 길이는 문자열 맨 뒤에 붙는 CRLF는 제외한 나머지 문자열의 길이를 말한다. 인코딩 되었을 때, 76자가 넘는 문자열에는 Soft Line Break를 사용한다. 즉 원래의 문자열에는 영향을 주지 않고 단지 인코딩 된 문자열의 줄바꿈만을 나타내는 문자를 덧붙이고 줄바꿈을 하여, 인코딩 된 문자열이 76자가 넘지 않도록 하는 것이다. Quoted-Printable에서는 '='을 Soft Line Break로 사용한다. Soft Line Break 뒤에는 Tab이나 Space가 나타날 수 있으며 규칙 3에 의한 인코딩 해서는 않된다. 그 이유는 몇몇 MTA들이 전송하는 원래의 문자열에 Tab이나 Space를 붙이기도 하고 빼기도 하는데, 이 때 덧붙여지는 Tab이나 Space는 원래의 데이터가 아니기 때문이다.

위의 규칙을 적용하여 다음과 같은 문자열을 Quoted-Printable 방식으로 인코딩 해보자.

Quoted-Printable Encoding 방식으로 인코딩하고 디코딩하는 함수를 작성해보자. 함수는 hQPencode(), hQPdecode()이다(리스트 1,2).

리스트 1 : hQPencode()
/**********************************************************************/
/* */
/* hQPencode() */
/* */
/* ------------------------------------------------------------------ */
/* desc : QP 방식으로 encode한다. */
/* usage : QPencode(string pointer, line size, ptr of encoded length) */
/* return: if Success, encoded string pointer */
/* else if fail, NULL */
/* */
/**********************************************************************/
char *hQPencode(char *str, int lineSize, int *len)
{
char *encodeStr = (char *)0x00;
char *encodeHex;
int i = 0, j = 0;
int qpSizeCnt = 0;

/* encoding된 문자열은 원래의 문자열에 대해 최대 3배가 된다. */
encodeStr = (char *)malloc(sizeof(char)*(lineSize*3 + 1));

for(i=0;i<lineSize;i++)
{
if(qpSizeCnt >= QP_SIZE - 3)
qpSizeCnt = 0, encodeStr[j++] = 0x0A;

if((str[i] >= 33 && str[i] <= 126) || (str[i] == 0x0A))
{
if(str[i] == 61)
{
encodeHex = hDec2Hex(str[i]);
encodeStr[j++] = 0x3D; /* '=' */
encodeStr[j++] = encodeHex[0];
encodeStr[j++] = encodeHex[1];
qpSizeCnt += 3;
}
else
qpSizeCnt++, encodeStr[j++] = str[i];
}
else if(str[i] == 9 || str[i] == 32)
{
if(str[i+1] == 0x0A || str[i+1] == 0x00) /* 문자열 끝의 Tab, Space */
{
encodeHex = hDec2Hex(str[i]);
encodeStr[j++] = 0x3D; /* '=' */
encodeStr[j++] = encodeHex[0];
encodeStr[j++] = encodeHex[1];
qpSizeCnt += 3;
}
else
qpSizeCnt++, encodeStr[j++] = str[i];
}
else
{
encodeHex = hDec2Hex(str[i]);
encodeStr[j++] = 0x3D; /* '=' */
encodeStr[j++] = encodeHex[0];
encodeStr[j++] = encodeHex[1];
qpSizeCnt += 3;
}
}

encodeStr[j] = 0x00;
return encodeStr;
}


리스트 2 : hQPdecode()
/**********************************************************************/
/* */
/* hQPdecode() */
/* */
/* ------------------------------------------------------------------ */
/* desc : QP 방식으로 encoding된 string을 decode한다. */
/* usage : QPdecode(encoded string pointer) */
/* return: if Success, decoded string pointer */
/* else if fail, NULL */
/* */
/**********************************************************************/
char *hQPdecode(char *encodeStr, int *len)
{
char *decodeStr;
char hex[3];
char ch;
int dec;
int spaceAdded = 0;
int i = 0, j = 0;

decodeStr = (char *)malloc(sizeof(char) * (strlen(encodeStr) + 1));

if(decodeStr == 0x00)
return 0x00;

while(encodeStr[i] != '\0')
{
if(encodeStr[i] == 0x3D) /* QP ESC seqeunce, '=' */
{
ch = encodeStr[++i]; /* white space를 체크하기 위해 미리 내다봄 */

while(ch == 0x09 || ch == 0x20) /* '=' 다음에 따라오는 character가 Tab, space이면 건너뛴다. */
spaceAdded = 1, ch = encodeStr[++i];

if(spaceAdded == 1)
{
spaceAdded = 0;
continue;
}

if(ch == 0x0A) /* '=' 다음에 LF가 있으면 soft line break임. encoded QP string은 한 라인에 76 characters만 허용 */
{
i++;
continue;
}

hex[0] = encodeStr[i++];
hex[1] = encodeStr[i++];
hex[2] = '\0';

dec = hHex2Dec(hex);

if(dec < 0) /* decoding error */
{
/* error 발생시 그대로 출력하기 위해 메시지 복원 */
decodeStr[j++] = 0x3D; /* '=' */
decodeStr[j++] = hex[0];
decodeStr[j++] = hex[1];
}
else
decodeStr[j++] = dec;
}
else if(encodeStr[i] > 0x7E) /* encoding error */
i++; /* ignore that character */
else
decodeStr[j++] = encodeStr[i++];
}

decodeStr[j] = '\0';

if(len != 0x00)
*len = j;

return decodeStr;
}

3. Base64 Encoding & Decoding

Base64 Encoding 방식은 바이너리 파일을 메일을 통해서 보내거나, Quoted-Printable 인코딩 방식이 부적합한 모든 메시지에 적용하는 방식이다. Base64 Encoding 방식은 이론적으로 무척 간단하다. 간단히 말해서 Base64 방식은 원래의 데이터 3바이트를 6bit씩 나누어 4바이트로 만드는 방식을 말한다. 3바이트, 즉 24bit는 6bit씩 나누면 4개가 나온다. 이렇게 해서 나온 4개의 6bit 값을 다음의 변환 테이블에서 각각 문자로 변환하는 것이 Base64 Encoding 방식의 핵심이다.
표 1은 Base64 Encoding 방식에서 사용되는 변환 테이블이다.

6bit 값
변환값
6bit값
변환값
6bit 값
변환값
6bit값
변환값
0
A
16
Q
32
g
48
w
1
B
17
R
33
h
49
x
2
C
18
S
34
i
50
y
3
D
19
T
35
j
51
z
4
E
20
U
36
k
52
0
5
F
21
V
37
l
53
1
6
G
22
W
38
m
54
2
7
H
23
X
39
n
55
3
8
I
24
Y
40
o
56
4
9
J
25
Z
41
p
57
5
10
K
26
a
42
q
58
6
11
L
27
b
43
r
59
7
12
M
28
c
44
s
60
8
13
N
29
d
45
t
61
9
14
O
30
e
46
u
62
+
15
P
31
f
47
v
63
/
표 1 : Base64 Encoding 방식에서 사용되는 변환 테이블

하지만 실제는 데이터가 3바이트씩 나누어 떨어지는 것이 아니기 때문에 인코딩 된 메시지에 등호(=)를 가지고 패딩을 한다.
실제 예를 통해 Base64 Encoding 방식을 이해해보도록 하자.

10바이트의 변환되기 전의 문자열이 있고, 그것을 이진수로 표현하면 다음과 같다.
0100101011100100100011010110001001011101
이것을 6bit씩으로 나누어보자.
010010 101110 010010 001101 011000 100101 1101
맨 뒤의 숫자는 6bit가 되지 않으므로 뒤에 0을 붙여 6bit로 만든다.
010010 101110 010010 001101 011000 100101 110100
위의 6bit 숫자들을 10진수로 표현하면 다음과 같다.
18 46 18 13 24 37 52
이들 10진수를 위의 변환 테이블을 이용해서 문자로 바꾸어 보자.
S u S N Y l 0
위의 문자열의 길이가 4로 나누어 떨어지도록 문자열의 뒤에 등호(=)를 붙인다(패딩).
S u S N Y l 0 =
이렇게 해서 인코딩된 마지막 결과는 SuSNYl0= 이 된다.

다음에는 Base64 방식으로 인코딩, 디코딩을 하는 함수인 hBASE64encode(), hBASE64decode() 함수를 작성해보도록 하자.

리스트 3 : hBASE64encode()
/**********************************************************************/
/* */
/* hBASE64encode() */
/* */
/* ------------------------------------------------------------------ */
/* desc : 문자열을 BASE64 방식으로 encoding 한다. */
/* usage : BASE64encode(string pointer) */
/* return: if Success, encoded string pointer */
/* else if fail, NULL */
/* */
/**********************************************************************/
char *hBASE64encode(char *str, int lineSize, int *len)
{
char *encodeStr = (char *)0x00;
int i = 0, j = 0;
int count = 0;
int ch;
int base64SizeCnt = 0;

if(str == 0x00)
return 0x00;

encodeStr = (char *)malloc(sizeof(char)*(lineSize*2));

while(TRUE)
{
switch(count++)
{
case 0:
if(i < lineSize)
ch = (str[i] & 0xFC) >> 2;
else
ch = -1;
break;

case 1:
if(i < lineSize)
if(i+1 < lineSize)
ch = ((str[i] & 0x03) << 4) | ((str[i+1] & 0xF0) >> 4);
else
ch = ((str[i] & 0x03) << 4);
else
ch = -1;
i++;
break;

case 2:
if(i < lineSize)
if(i+1 < lineSize)
ch = ((str[i] & 0x0F) << 2) | ((str[i+1] & 0xC0) >> 6);
else
ch = ((str[i] & 0x0F) << 2);
else
ch = -1;
i++;
break;

case 3:
if(i < lineSize)
ch = (str[i] & 0x3F);
else
ch = -1;
i++;
count = 0;
break;
}


if(ch >= 0 && ch <= 25) /* Upper Case Alphabet */
encodeStr[j++] = 'A' + ch;
else if(ch >= 26 && ch <= 51) /* Lower Case Alphabet */
encodeStr[j++] = 'a' + ch - 26;
else if(ch >= 52 && ch <= 61) /* Digit */
encodeStr[j++] = '0' + ch - 52;
else if(ch == 62)
encodeStr[j++] = '+';
else if(ch == 63)
encodeStr[j++] = '/';
else if(ch == -1)
encodeStr[j++] = '='; /* padding */

base64SizeCnt++;

if(j%4 == 0)
{
if(base64SizeCnt == BASE64_SIZE)
base64SizeCnt = 0, encodeStr[j++] = 0x0A; /* soft break */

if(i >= lineSize)
break;
}
}

encodeStr[j] = 0x00;

if(len != 0x00)
*len = j;

return encodeStr;
}

리스트 4 : hBASE64decode()
/**********************************************************************/
/* */
/* hBASE64decode() */
/* */
/* ------------------------------------------------------------------ */
/* desc : BASE64 방식으로 encoding된 string을 decode한다. */
/* usage : BASE64decode(encoded string pointer) */
/* return: if Success, decoded string pointer */
/* else if fail, NULL */
/* */
/**********************************************************************/
char *hBASE64decode(char *encodeStr, int *len)
{
char *decodeStr;
long btmp = 0; /* 4byte (decoding 용) */
int i = 0, j = 0;
int count = 0;
int padCount = 0;

/* decoded string을 위한 메모리를 할당한다. */
/* 실제로는 encodeStr length의 3/4 만큼만 잡으면 된다. */
decodeStr = (char *)malloc(sizeof(char) * strlen(encodeStr));

if(decodeStr == 0x00)
return 0x00;

while(encodeStr[i] != '\0')
{
if(isupper(encodeStr[i]))
btmp = (btmp << 6) | (encodeStr[i] - 'A' ); /* 대문자는 0 - 25까지 */
else if(islower(encodeStr[i]))
btmp = (btmp << 6) | (encodeStr[i] - 'a' + 0x1A); /* 소문자는 26(0x1A) - 51까지 */
else if(isdigit(encodeStr[i]))
btmp = (btmp << 6) | (encodeStr[i] - '0' + 0x34); /* 숫자는 52(0x34) - 61까지 */
else if(encodeStr[i] == '+')
btmp = (btmp << 6) | 0x3E; /* '+'는 62(0x3E) */
else if(encodeStr[i] == '/')
btmp = (btmp << 6) | 0x3F; /* '/'는 63(0x3F) */
else if(encodeStr[i] == '=')
padCount++, btmp = (btmp << 6); /* '='는 pad */
else
btmp = (btmp << 6); /* encoding error */

if(++count >= 4) /* 한 transaction이 끝났으면 */
{

decodeStr[j++] = (char)((btmp & 0x00FF0000) >> 16);
decodeStr[j++] = (char)((btmp & 0x0000FF00) >> 8);
decodeStr[j++] = (char)((btmp & 0x000000FF) );

count = 0;
btmp = 0;

if(encodeStr[i+1] == 0x0A) /* soft linebreak */
i++;
}

i++;
}

decodeStr[j - padCount] = '\0';

if(len != 0x00)
*len = j - padCount;

return decodeStr;
}

/**********************************************************************/
/* */
/* hHex2Dec() */
/* */
/* ------------------------------------------------------------------ */
/* desc : hex를 dec로 전환 */
/* usage : hHex2Dec(hex number e.g. "3D" or "3d") */
/* return: if Success, dec number */
/* else if fail, -1 */
/* */
/**********************************************************************/
int hHex2Dec(char *str)
{
int dec = 0;
int byte;
int i;

for(i=0;i<2;i++)
{
if(str[i] >= '0' && str[i] <= '9')
byte = str[i] - '0';
else if(str[i] >= 'A' && str[i] <= 'F')
byte = str[i] - 'A' + 10;
else if(str[i] >= 'a' && str[i] <= 'f')
byte = str[i] - 'a' + 10;
else
byte = -1;

if(byte < 0)
return -1;

dec += (i == 0) ? byte << 4 : byte;
}

return dec;
}

/**********************************************************************/
/* */
/* hDec2Hex() */
/* */
/* ------------------------------------------------------------------ */
/* desc : dec를 hex로 전환 */
/* usage : hDec2Hex(dec number) */
/* return: if Success, str represented hex number */
/* else if fail, NULL */
/* */
/**********************************************************************/
char *hDec2Hex(int dec)
{
static char hex[3];
int i;
int ch;

for(i=0;i<2;i++)
{
if(i == 0)
ch = (dec & 0xF0) >> 4;
else if(i == 1)
ch = (dec & 0x0F);

if(ch >= 10)
hex[i] = 'A' + ch - 10;
else
hex[i] = '0' + ch;
}

hex[i] = 0x00;

return &hex[0];
}

이번호에는 MIME의 전반부에 해당하는 MIME Encoding에 대하여 살펴보았다. 다음 호에는 MIME 메시지를 구성(Compose)하고 구성된 MIME 메시지를 해석(Parse)하는 방법에 대하여 알아보도록 하겠다.

+ Recent posts