NPAPI向Javascript传输中文的问题(一)
2013-08-15 Update:
这个代码在FireFox中处理插件内部的字符串输出是没有问题的,但是如果从Javascript中获取字符串然后再进行输出就会出现问题。以及代码本身也存在着一些bug及一些比较混乱的地方。故另开一篇文章对新出现的问题进行讨论,本文章暂不进行修改。
《NPAPI向Javascript传输中文的问题(二)》(http://www.cnblogs.com/wliu6V/p/3258962.html)
----------------------------------------------------------------------------------------------
本文是按照把我个人的开发思路记录下来,因此中间是有绕弯路的。想直接拷代码的请拖到第四节。
(一)
在开发NPAPI插件的时候,我们可以在NPAPI中使用代码弹出JS的Alert。比如可以将”javascript.alert(‘sth’);”这个字符串通过NPN_URL方法传给页面。比如这样:
char* command = "javascript:alert('Hello 6v');"; NPN_URL(m_npp, command, "_self"); // m_npp是CScriptPluginObject中的属性
但需要注意的是,该方法默认值支持英文,不能使用中文或\n等转义符。如果传\n,会被自动过滤掉。如果传中文,就可能整条alert都无法执行。
这个问题的原因并不是非常确定,但很有可能是因为NPAPI对传输的URL进行了某种处理,将里面的\n等转义符都以某种形式转化掉了,并且将它认为不合法的URL给去掉了。我们对NPN_URL这个方法一步步深入查看,最终可以在NPAPI的源码中找到一个nsPluginHost类(具体怎么找,基本是靠想象力),里面有个GetURLWithHeaders()方法,这个方法的一部分如下所示:
nsresult nsPluginHost::GetURLWithHeaders(nsNPAPIPluginInstance* pluginInst, const char* url, const char* target, nsIPluginStreamListener* streamListener, const char* altHost, const char* referrer, PRBool forceJSEnabled, PRUint32 getHeadersLength, const char* getHeaders) { nsAutoString string; string.AssignWithConversion(url); // we can only send a stream back to the plugin (as specified by a // null target) if we also have a nsIPluginStreamListener to talk to if (!target && !streamListener) return NS_ERROR_ILLEGAL_VALUE; nsresult rv = DoURLLoadSecurityCheck(pluginInst, url); if (NS_FAILED(rv)) return rv; //…….下面还有很多代码 }
我们注意到其中有这样一句
string.AssignWithConversion(url);
和这样一句
nsresult rv = DoURLLoadSecurityCheck(pluginInst, url);
再深处的实现就比较难以追踪,但我们根据方法名可以初步判断。前一句可能是将url进行了转义,因此\n在这里就被转换掉了,到了js代码那里就无法变成换行符了。基于这种考虑,我们可以为其添加一个反斜杠,变成\\n。这样,此处转义的时候就会变成\n,再传给JS。JS将其转义为换行符,就可以正常的显示换行了。(PS,在文章后面会介绍其他的方式,就不需要使用双反斜杠了。)
(二)
关于中文的问题,就更加纠结一点。\n那里虽然显示效果不同,但通过NPN_URL所传输的整条js语句至少还能执行。而传输中文的话,就压根不执行了。因此怀疑是DoURLLoadSecurityCheck()方法将其认作了非法的URL,故而整条语句都无法执行。
考虑到这一层,我们就不难想到通过某种转码方式将中文处理一下,然后到JS代码处再解码。参考了《详解Javascript中的Url编码/解码》这篇文章(原文地址http://www.cnblogs.com/angells/archive/2009/11/24/1609129.html),我们可以得到如下结论:
- 使用encodeURI(encodeURIComponent)与decodeURI(decodeURIComponent)可以将中文进行编码与解码。
- 目标编码包含了百分号’%’。
- 百分号本身用作对不安全字符进行编码时使用的特殊字符,因此本身需要编码,要改为%25。
基于上述结论,NPAPI向JS代码传输中文的解决方案就非常明了了。首先我们需要在C中将中文以encodeURI的方式进行编码,然后将编码后的字符串中所有的”%”替换为”%25”,最后传输的时候将字符串以”decodeURI(‘’)”包围即可。具体实现如下(C中的encodeURI方法需要自己编写,代码见后面):
CString s = encodeURI("中文&English混编测试"); s.Replace("%","%25"); CString command = "javascript:alert(decodeURI('" + s + "'));"; NPN_GetURL(m_npp, command, "_self");
这样就可以使中文与英文混编了。
附上C的encodeURI代码,针对Windows环境(来自百度知道用户@moxsone。网址:http://zhidao.baidu.com/question/113953665.html):
1 #include <windows.h> 2 #include <stdio.h> 3 #include <stdlib.h> 4 5 const char *encodeURI(const char *Str) 6 { 7 wchar_t *Bufw = NULL; 8 char *Bufc = NULL; 9 char RTV[5120]; 10 long needSize = MultiByteToWideChar(CP_ACP, 11 NULL, 12 Str, 13 -1, 14 NULL, 15 0); 16 if ( 0 == needSize ) goto ERROR_HANDLE; 17 18 Bufw = new wchar_t[needSize]; 19 if ( NULL == Bufw ) goto ERROR_HANDLE; 20 21 memset(Bufw,0x0,needSize*2); 22 MultiByteToWideChar(CP_ACP, 23 NULL, 24 Str, 25 -1, 26 Bufw, 27 needSize); 28 29 needSize = WideCharToMultiByte(CP_UTF8, 30 NULL, 31 Bufw, 32 -1, 33 NULL, 34 0, 35 NULL, 36 NULL); 37 if ( 0 == needSize ) goto ERROR_HANDLE; 38 39 Bufc = new char[needSize]; 40 if ( NULL == Bufc ) goto ERROR_HANDLE; 41 42 memset(Bufc,0x0,needSize); 43 WideCharToMultiByte(CP_UTF8, 44 NULL, 45 Bufw, 46 -1, 47 Bufc, 48 needSize, 49 NULL, 50 NULL); 51 52 unsigned char *pWork = (unsigned char *)Bufc; 53 memset(RTV,0x0,sizeof(RTV)); 54 if ( strlen(Bufc) > 5120 ) 55 { goto ERROR_HANDLE; } 56 while( *pWork != 0x0 ) 57 { 58 if ( *pWork != '!' && *pWork != '@' && *pWork != '#' && 59 *pWork != '$' && *pWork != '&' && *pWork != '*' && 60 *pWork != '(' && *pWork != ')' && *pWork != '=' && 61 *pWork != ':' && *pWork != '/' && *pWork != ';' && 62 *pWork != '?' && *pWork != '+' && *pWork != '\'' && 63 *pWork != '.' ) 64 { 65 sprintf(RTV+strlen(RTV),"%%%2X",*pWork); 66 } 67 else 68 { 69 sprintf(RTV+strlen(RTV),"%c",*pWork); 70 } 71 pWork++; 72 } 73 74 if ( NULL != Bufw ) 75 { 76 delete [] Bufw; 77 Bufw = NULL; 78 } 79 if ( NULL != Bufc ) 80 { 81 delete [] Bufc; 82 Bufc = NULL; 83 } 84 return RTV; 85 86 ERROR_HANDLE: 87 88 if ( NULL != Bufw ) 89 { 90 delete [] Bufw; 91 Bufw = NULL; 92 } 93 if ( NULL != Bufc ) 94 { 95 delete [] Bufc; 96 Bufc = NULL; 97 } 98 return NULL; 99 }
(三)
上面的方式实现了中英文混编。但是,使用上面那种方式的时候有两个bug。其一,\n通过encodeURI之后就无法被JS代码正常的deocde出来。其二,许多标点符号,如冒号等,也都无法正常的被编码。
首先我们解决第一个问题。经调试我们不难发现,当遇到\n时,本来应该被转为"%0A"的,结果实际上被转成了"% A",原本的0变成了空格。就导致了Javascript无法对其正常的进行识别。
知道了原因自然就很容易解决这个问题。在上面的代码中找到
sprintf(RTV+strlen(RTV),"%%%2X",*pWork);
这一句,将其中的"%%%2X"换成"%%%.2X"即可,也就是加一个小数点。这样令其强制保留两位数字,会在A前面补0,这样就达到了我们想要的效果。
之后是冒号等特殊符号的问题。上面的代码中明显有一个判断,会将这些符号跳过,不进行编码
if ( *pWork != '!' && *pWork != '@' && *pWork != '#' && *pWork != '$' && *pWork != '&' && *pWork != '*' && *pWork != '(' && *pWork != ')' && *pWork != '=' && *pWork != ':' && *pWork != '/' && *pWork != ';' && *pWork != '?' && *pWork != '+' && *pWork != '\'' && *pWork != '.' ) { sprintf(RTV+strlen(RTV),"%%%2X",*pWork); } else { sprintf(RTV+strlen(RTV),"%c",*pWork); }
既然我们需要传递冒号等字符,那可以将这个if粗暴的砍掉,将其改成
sprintf(RTV+strlen(RTV),"%%%.2X",*pWork);
即可。
实际上,这样修改了之后,我们的encodeURI就更像是JS中的encodeURIComponent了。因此在解码的时候也要使用对应的decodeURIComponent才可以。
以及,既然对所有的字符都进行编码了,\n实际上也不例外,因此我们也不需要通过双反斜杠对换行符进行传递了,只要\n,然后进行encode即可。
当然,encode结束之后还是不要忘记将"%"置换为"%25"。(四)
修改后的完整代码见下面。用户可以将绝大部分字符以及中文通过 popJavascriptAlert(const char* str) 这个方法传到页面上,并以Javascript Alert的形式弹出。如果需要换行就使用'\n',而不是'\\n'。
使用时需包含<stdio.h>与<malloc.h>这两个头文件。
1 /******************************************************************************* 2 * encodeURI 3 * 来自 http://zhidao.baidu.com/question/113953665.html 并有修改。 4 * 以C的方式实现了Javascript中的encodeURI方法。 5 * 其结果可以被Javascript通过DecodeURIComponent解码。 6 *******************************************************************************/ 7 char *encodeURI(const char *Str) { 8 9 // 先将ANSI转成UTF8 10 wchar_t *Bufw = NULL; 11 char *Bufc = NULL; 12 long needSize = MultiByteToWideChar(CP_ACP, 13 NULL, 14 Str, 15 -1, 16 NULL, 17 0); 18 if ( 0 == needSize ) goto ERROR_HANDLE; 19 20 Bufw = new wchar_t[needSize]; 21 if ( NULL == Bufw ) goto ERROR_HANDLE; 22 23 memset(Bufw,0x0,needSize*2); 24 MultiByteToWideChar(CP_ACP, 25 NULL, 26 Str, 27 -1, 28 Bufw, 29 needSize); 30 31 32 needSize = WideCharToMultiByte(CP_UTF8, 33 NULL, 34 Bufw, 35 -1, 36 NULL, 37 0, 38 NULL, 39 NULL); 40 if ( 0 == needSize ) goto ERROR_HANDLE; 41 42 Bufc = new char[needSize]; 43 if ( NULL == Bufc ) goto ERROR_HANDLE; 44 45 memset(Bufc,0x0,needSize); 46 WideCharToMultiByte(CP_UTF8, 47 NULL, 48 Bufw, 49 -1, 50 Bufc, 51 needSize, 52 NULL, 53 NULL); 54 55 // 进行转码 56 char* RTV = (char*)malloc(needSize*3 +1); // 一个字符转码变成三个字符 57 58 unsigned char *pWork = (unsigned char *)Bufc; 59 memset(RTV,0x0,sizeof(RTV)); 60 if ( strlen(Bufc) > 5120 ) { goto ERROR_HANDLE; } 61 while( *pWork != 0x0 ) { 62 sprintf(RTV+strlen(RTV),"%%%.2X",*pWork); 63 pWork++; 64 } 65 66 if ( NULL != Bufw ) { 67 delete [] Bufw; 68 Bufw = NULL; 69 } 70 if ( NULL != Bufc ) { 71 delete [] Bufc; 72 Bufc = NULL; 73 } 74 return RTV; 75 76 ERROR_HANDLE: 77 if ( NULL != Bufw ) { 78 delete [] Bufw; 79 Bufw = NULL; 80 } 81 if ( NULL != Bufc ) { 82 delete [] Bufc; 83 Bufc = NULL; 84 } 85 return NULL; 86 } 87 88 89 /******************************************************************************* 90 * replaceAll 91 * 替换char* 里面的某种字符。 92 * 把srcStr的oldStr换成newStr 93 *******************************************************************************/ 94 char *replaceAll(char *srcStr, char* oldStr, char* newStr) { 95 96 int srcLen = strlen(srcStr); 97 int oldLen = strlen(oldStr); 98 int newLen = strlen(newStr); 99 100 // 先计算一下目标字符串所需要的替换的次数,从而判断需分配的内存大小。 101 int replaceCount = 0; 102 for (int i = 0; i < srcLen; i++) { 103 if (srcStr[i] == oldStr[0]) 104 { 105 int k = i; 106 int j = 0; 107 while (j <= oldLen -1) { 108 if (srcStr[k] != oldStr[j]) { 109 --j; 110 break; 111 } 112 j++; 113 k++; 114 } 115 116 if (j >= oldLen - 1) { 117 replaceCount++; 118 } 119 } 120 } 121 122 // 创建目标字符串,并执行替换操作。 123 int destStrLen = srcLen + replaceCount * (newLen - oldLen) + 1; 124 char* result = (char*)malloc(destStrLen); 125 126 char* pSrc = srcStr; 127 char* pDest = result; 128 129 char *pTmp = NULL; 130 int nLen = 0; 131 132 do { 133 // 找到下一个替换点 134 pTmp = strstr(pSrc, oldStr); 135 136 if(pTmp != NULL) { 137 // 拷贝上一个替换点和下一个替换点中间的字符串 138 nLen = pTmp - pSrc; 139 memcpy(pDest, pSrc, nLen); 140 141 // 拷贝需要替换的字符串 142 memcpy(pDest + nLen, newStr, newLen); 143 } else { 144 strcpy(pDest, pSrc); 145 146 // 如果没有需要拷贝的字符串,说明循环应该结束 147 break; 148 } 149 150 pSrc = pTmp + oldLen; 151 pDest = pDest + nLen + newLen; 152 153 } while (pTmp != NULL); 154 155 156 return result; 157 } 158 159 160 /***************************************************************************************** 161 * popJavascriptAlert 162 * 将传入的CString以Alert的形式弹出在浏览器窗口。 163 * 会先对传入的字符串进行一系列处理 164 *****************************************************************************************/ 165 void CScriptPluginObject::popJavascriptAlert(const char* str){ 166 167 char* tmpStr = replaceAll(encodeURI(str), "%", "%25"); 168 char* prevStr = "javascript:alert(decodeURIComponent('"; 169 char* postStr = "'));"; 170 171 char* command = (char*)malloc(strlen(prevStr) + strlen(tmpStr) + strlen(postStr)); 172 memset(command,0,sizeof(command)); 173 sprintf(command, "%s%s%s", prevStr, tmpStr, postStr); 174 175 NPN_GetURL(m_npp, command, "_self"); 176 }
这里采用的字符集是Unicode字符集,而不是NPAPI推荐的多字节字符集。因为公司项目要求如此。
如代码有任何问题,望请不吝指教。个人感觉这段代码很有可能会有问题,特别是进行malloc的时候。以及个人对strlen的理解可能不太到位,也许会导致意外的Bug。