NPAPI向JavaScript传输中文的问题(一)

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),我们可以得到如下结论:

  1. 使用encodeURI(encodeURIComponent)与decodeURI(decodeURIComponent)可以将中文进行编码与解码。
  2. 目标编码包含了百分号’%’。
  3. 百分号本身用作对不安全字符进行编码时使用的特殊字符,因此本身需要编码,要改为%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。

 

 

 

转载于:https://www.cnblogs.com/wliu6V/p/3245684.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值