1. 발단
모든 일에는 시작이 있는 법…
발단은 메모장2 mod 포스팅에 달린 댓글 하나였다.
애초에 이 기능을 제대로 써볼 생각도 없었던지라 생각도 못했는데, 소스를 읽다보니 뭔가 많이 이상하다.
이 기능은 기본적으로 유니코드 → UTF-8 → UrlEscape 순으로 변환하는 게 일반적이다.
하지만, Edit.c의 해당 부분 코드는 아래와 같다.
1 2 3 4 5 6 7 8 9 | ////////////////// // 인코딩 ////////////////// cchTextW = MultiByteToWideChar(cpEdit,0,pszText,iSelCount,pszTextW,( int )LocalSize(pszTextW)/ sizeof ( WCHAR ));//(중략) cchEscapedW = ( int )LocalSize(pszEscapedW) / sizeof ( WCHAR ); UrlEscape(pszTextW,pszEscapedW,&cchEscapedW,URL_ESCAPE_SEGMENT_ONLY); cchEscaped = WideCharToMultiByte(cpEdit,0,pszEscapedW,cchEscapedW,pszEscaped,( int )LocalSize(pszEscaped), NULL , NULL ); |
1 2 3 4 5 6 7 8 9 | ////////////////// // 디코딩 ////////////////// cchTextW = MultiByteToWideChar(cpEdit,0,pszText,iSelCount,pszTextW,( int )LocalSize(pszTextW)/ sizeof ( WCHAR ));//(중략) cchUnescapedW = ( int )LocalSize(pszUnescapedW) / sizeof ( WCHAR ); UrlUnescape(pszTextW,pszUnescapedW,&cchUnescapedW,0); cchUnescaped = WideCharToMultiByte(cpEdit,0,pszUnescapedW,cchUnescapedW,pszUnescaped,( int )LocalSize(pszUnescaped), NULL , NULL ); |
즉, UTF-8 변환을 아예 하지 않는다.
따라서 URL Decode UTF-8로 인코딩된 데이터를 그대로 유니코드 문자인 셈치고 읽는 것이다…
2. 첫번째 시도
이 기능을 정상적[각주:1]으로 동작하게 하려면 중간에 UTF-8 변환 부분을 추가해야 한다.
하지만, 생각해보니 이게 쉽지만은 않다.
UTF-8을 거쳐 UrlEscape된 데이터는 ASCII 텍스트인데, 메모장2에서 쓰려면 유니코드로 변환하는 과정이 추가로 필요하다.
이런 부분을 모두 고려한 코드를 일단 작성해봤다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | ////////////////// // 인코딩 추가 ////////////////// LPWSTR Unicode2UTF8W( LPWSTR pszUnicode, DWORD lenUnicode, DWORD *lenUTF8) { LPWSTR pszUTF8; register long lB = 0; register DWORD l; pszUTF8 = LocalAlloc(LPTR, (lenUnicode * 3 + 1) * sizeof ( WCHAR )); if (!pszUTF8) return NULL ; for (l = 0; l < lenUnicode; l++) { if ((pszUnicode[l] & 0xff80) == 0) pszUTF8[lB++] = pszUnicode[l]; else if ((pszUnicode[l] & 0xf800) == 0) { pszUTF8[lB++] = (pszUnicode[l] >> 6) & 0x1f | 0xc0; pszUTF8[lB++] = (pszUnicode[l]) & 0x3f | 0x80; } else { pszUTF8[lB++] = (pszUnicode[l] >> 12) & 0x0f | 0xe0; pszUTF8[lB++] = (pszUnicode[l] >> 6) & 0x3f | 0x80; pszUTF8[lB++] = (pszUnicode[l]) & 0x3f | 0x80; } if (!(pszUnicode[l])) break ; } *lenUTF8 = lB; return pszUTF8; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | ////////////////// // 디코딩 추가 ////////////////// BOOL IsUTF8W( LPWSTR pTest, int nLength) { static int byte_class_table[256] = { /* 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F */ /* 00 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 10 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 20 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 30 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 40 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 50 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 60 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 70 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 80 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 90 */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* A0 */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* B0 */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* C0 */ 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, /* D0 */ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, /* E0 */ 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 7, 7, /* F0 */ 9,10,10,10,11, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4 /* 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F */ }; /* state table */ typedef enum { kSTART = 0, kA, kB, kC, kD, kE, kF, kG, kERROR, kNumOfStates } utf8_state; static utf8_state state_table[] = { /* kSTART, kA, kB, kC, kD, kE, kF, kG, kERROR */ /* 0x00-0x7F: 0 */ kSTART, kERROR, kERROR, kERROR, kERROR, kERROR, kERROR, kERROR, kERROR, /* 0x80-0x8F: 1 */ kERROR, kSTART, kA, kERROR, kA, kB, kERROR, kB, kERROR, /* 0x90-0x9f: 2 */ kERROR, kSTART, kA, kERROR, kA, kB, kB, kERROR, kERROR, /* 0xa0-0xbf: 3 */ kERROR, kSTART, kA, kA, kERROR, kB, kB, kERROR, kERROR, /* 0xc0-0xc1, 0xf5-0xff: 4 */ kERROR, kERROR, kERROR, kERROR, kERROR, kERROR, kERROR, kERROR, kERROR, /* 0xc2-0xdf: 5 */ kA, kERROR, kERROR, kERROR, kERROR, kERROR, kERROR, kERROR, kERROR, /* 0xe0: 6 */ kC, kERROR, kERROR, kERROR, kERROR, kERROR, kERROR, kERROR, kERROR, /* 0xe1-0xec, 0xee-0xef: 7 */ kB, kERROR, kERROR, kERROR, kERROR, kERROR, kERROR, kERROR, kERROR, /* 0xed: 8 */ kD, kERROR, kERROR, kERROR, kERROR, kERROR, kERROR, kERROR, kERROR, /* 0xf0: 9 */ kF, kERROR, kERROR, kERROR, kERROR, kERROR, kERROR, kERROR, kERROR, /* 0xf1-0xf3: 10 */ kE, kERROR, kERROR, kERROR, kERROR, kERROR, kERROR, kERROR, kERROR, /* 0xf4: 11 */ kG, kERROR, kERROR, kERROR, kERROR, kERROR, kERROR, kERROR, kERROR };#define BYTE_CLASS(b) (byte_class_table[(unsigned char)b]) #define NEXT_STATE(b,cur) (state_table[(BYTE_CLASS(b) * kNumOfStates) + (cur)]) utf8_state current = kSTART; register int i; const WCHAR * pt = pTest; int len = nLength; for (i = 0; i < len; i++, pt++) { if ((*pt) & (~0xff)) return FALSE; current = NEXT_STATE(*pt, current ); if (kERROR == current ) break ; } return ( current == kSTART) ? TRUE : FALSE;} LPWSTR UTF82UnicodeW( LPWSTR pszUTF8, DWORD lenUTF8, DWORD *lenUnicode) { LPWSTR pszUnicode; register long l1 = 0, l2 = 0; pszUnicode = LocalAlloc(LPTR, (lenUTF8 + 1) * 3); while (pszUTF8[l1]) { if (!(pszUTF8[l1] & 0x80)) { pszUnicode[l2] = pszUTF8[l1]; l1++; } else if ((pszUTF8[l1] & 0xe0) == 0xc0) { pszUnicode[l2] = ((pszUTF8[l1] & 0x1f) << 6) | (pszUTF8[l1 + 1] & 0x3f); l1 += 2; } else if ((pszUTF8[l1] & 0xf0) == 0xe0) { pszUnicode[l2] = ((pszUTF8[l1] & 0x0f) << 12) | ((pszUTF8[l1 + 1] & 0x3f) << 6) | (pszUTF8[l1 + 2] & 0x3f); l1 += 3; } else { l1++; } l2++; } pszUnicode[l2] = L '\0' ; *lenUnicode = l2; return pszUnicode; } |
3. 원작자(Florian Balmer)와 토의
메모장2 mod 프로젝트는 XhmikosR가 관리하는 프로젝트지만, 이 친구는 뭘 얘기해도 답이 없는 친구라 패스하고…
원작자인 Florian Balmer에게 이 코드를 보냈다.
그리고, 기대했던 대로 친절한 답변을 받았다.
메모장2의 ToDo 목록에 추가하겠다는 점이 가장 눈에 띄었다.
또한, 유추할 수 있는 내용은 이 쪽에선 URL Encode/Decode 기능 자체를 이해를 하지 못한다는 것.
아마도 필요 자체가 거의 없는 환경[각주:2]이라 그런 것 같다.
더불어, UrlEscapeA, MultiByteToWideChar 등의 함수를 사용하는 것이 더 나을 것 같다[각주:3]는 조언도 해줬다.
4-1. 두번째 시도: 인코딩
Balmer 씨의 의견을 적극 반영하여 OS에서 제공하는 라이브러리를 적극 사용하는 버전을 만들었다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | BOOL UrlUTF8Escape( LPWSTR pszUnicode, DWORD lenUnicode, UINT codePage, LPWSTR pszEscapedW, DWORD *cchEscapedW, DWORD dwFlags) { unsigned char *pszTemp1, *pszTemp2; DWORD lenTemp1 = lenUnicode * 3 + 1, lenTemp2 = lenUnicode * 3 * 3 + 1; BOOL ret = FALSE; pszTemp1 = LocalAlloc(LPTR, lenTemp1); pszTemp2 = LocalAlloc(LPTR, lenTemp2); if (pszTemp1 && pszTemp2) { lenTemp1 = WideCharToMultiByte(CP_UTF8, 0, pszUnicode, lenUnicode, pszTemp1, lenTemp1, NULL , NULL ); if (lenTemp1) { // 실제로는 UrlEscapeA()가 기대한대로 동작하지 않음 // 0x80 이상의 문자는 죄다 '?'(0x3F)로 처리함 UrlEscapeA(pszTemp1, pszTemp2, &lenTemp2, dwFlags); (*cchEscapedW) = MultiByteToWideChar(codePage, 0, pszTemp2, lenTemp2, pszEscapedW, *cchEscapedW); ret = TRUE; } } if (pszTemp1) LocalFree(pszTemp1); if (pszTemp2) LocalFree(pszTemp2); return ret; } |
그런데, 막상 만들고 보니 정상적으로 동작하지 않는다.
면밀히 검토해보니, UrlEscapeA() 함수의 동작방식이 내가 기대한 것과 달랐다.
기본적으로 7비트 ASCII 문자에서만 동작하는 것이다.
즉, UTF-8로 변환한 문자열을 처리할 수 없다…
이러한 점을 고려해서 아래와 같이 수정했다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | BOOL UrlUTF8Escape( LPWSTR pszUnicode, DWORD lenUnicode, LPWSTR pszEscapedW, DWORD *cchEscapedW, DWORD dwFlags) { unsigned char *pszTemp1, *pszTemp2; WCHAR *pszTempW; int lenTemp1 = lenUnicode * 3 + 1, lenTemp2 = lenUnicode * 3 * 3 + 1; BOOL ret = FALSE; register int i; pszTemp1 = LocalAlloc(LPTR, lenTemp1); pszTempW = LocalAlloc(LPTR, lenTemp1 * sizeof ( WCHAR )); pszTemp2 = LocalAlloc(LPTR, lenTemp2); if (pszTemp1 && pszTempW && pszTemp2) { lenTemp1 = WideCharToMultiByte(CP_UTF8, 0, pszUnicode, lenUnicode, pszTemp1, lenTemp1, NULL , NULL ); if (lenTemp1) { for (i = 0; i < lenTemp1; i++) { pszTempW[i] = ( WCHAR )pszTemp1[i]; } pszTempW[lenTemp1] = L '\0' ; // UrlEscapeW()는 UrlEscapeA()와 달리 0x80 이상의 문자도 정상적으로 변환함 // UrlEscapeA()는 기대한대로 동작하지 않고, 0x80 이상의 문자를 '?'(0x3F)로 변환함 UrlEscape(pszTempW, pszEscapedW, cchEscapedW, dwFlags); ret = TRUE; } } if (pszTemp1) LocalFree(pszTemp1); if (pszTempW) LocalFree(pszTempW); if (pszTemp2) LocalFree(pszTemp2); return ret; } |
4-2. 두번째 시도: 디코딩
디코딩 쪽은 인코딩보다는 깔끔하다.
UrlUnescapeA는 UrlEsacpeA와 달리 0x80 이상의 문자에 대해 특별한 문제를 야기하지 않는다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | BOOL UrlUTF8Unescape( LPWSTR pszUTF8, DWORD lenUTF8, UINT codePage, LPWSTR pszUnescapedW, DWORD *cchUnescapedW) { unsigned char *pszTemp1, *pszTemp2; int lenTemp1 = lenUTF8 * 3 + 1, lenTemp2 = lenUTF8 * 3 + 1; BOOL ret = FALSE; pszTemp1 = LocalAlloc(LPTR, lenTemp1); pszTemp2 = LocalAlloc(LPTR, lenTemp2); if (pszTemp1 && pszTemp2) { lenTemp1 = WideCharToMultiByte(codePage, 0, pszUTF8, lenUTF8, pszTemp1, lenTemp1, NULL , NULL ); if (lenTemp1) { UrlUnescapeA(pszTemp1, pszTemp2, &lenTemp2, 0); if (lenTemp2 && IsUTF8(pszTemp2, lenTemp2)) { (*cchUnescapedW) = MultiByteToWideChar(CP_UTF8, 0, pszTemp2, lenTemp2, pszUnescapedW, *cchUnescapedW); ret = TRUE; } } } if (pszTemp1) LocalFree(pszTemp1); if (pszTemp2) LocalFree(pszTemp2); return ret; } |
5. 결론
이 내용이 모두 반영된 메모장2 mod는 별도 포스팅으로 공개했다.
드디어 해결한 Notepad2-mod의 드래그앤드롭 오류 (2) | 2016.11.08 |
---|---|
Notepad2-mod r986 (5393ab8) #4 한국어화 버전 공개 (1) | 2016.10.24 |
Notepad2-mod r986 (5393ab8) #3 한국어화 버전 공개 (3) | 2016.09.14 |
Notepad2-mod r986 (5393ab8) #1 한국어화 버전 공개 (2) | 2016.09.11 |
Notepad2-mod r981 (8711668) #2 한국어화 버전 공개 (23) | 2016.07.25 |
내 블로그 - 관리자 홈 전환 |
Q
Q
|
---|---|
새 글 쓰기 |
W
W
|
글 수정 (권한 있는 경우) |
E
E
|
---|---|
댓글 영역으로 이동 |
C
C
|
이 페이지의 URL 복사 |
S
S
|
---|---|
맨 위로 이동 |
T
T
|
티스토리 홈 이동 |
H
H
|
단축키 안내 |
Shift + /
⇧ + /
|
* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.