张墨轩的技术宅

不忘初心,方得始终

[置顶] 我的微信公众号

Mirrorlink类型车联网项目阶段性成果(二)

前段时间弄的是CE平台的车机。但是因为android发展迅猛,现在android车机已经相当普遍,所以最近一直在弄android系统的车机平台,这次主要的目标是实现android车机平台与iPhone手机之间的连接。对于苹果在车联网领域,已经有了它自己的方案CarPlay,但是正如苹果一贯的封闭,对我来说完全没有帮助。我只得采用airplay协议进行屏幕的投射。至于说反控,就是通过车机屏幕去控制iPhone手机的屏幕,这个因为iPhone手机严格的权限控制,是不可能实现的。不过人总是聪明的,这里有一种很取巧的办法,虽然我们没有权限控制整个iPhone手机,但是我们却可以实现应用内的控制,简单来说我们可以开发一个自己的苹果APP,然后可以通过车机端控制我们自己APP在运行时所展现出来的那个屏幕内容。这也不失是一种折中的好办法。 整的来说难度还是挺大的,而且对于airplay协议来说也是封闭的,没有太多公开的资料,很多时候需要自己抓包去分析,另外也不知道什么时候苹果一升级,协议又变了。

程序员的数学

数学对程序员来讲还是很重要的,特别是对我这种数学不好的人来讲更加重要。随着时代的发展,AR/VR,人工智能,自然人机交互,机器视觉,机器人技术等等将成为主流和重点。音频视频处理,3D技术,机器学习等等无不将成为必须要掌握的技术,而这些技术背后都蕴含着各种高深的数学知识。 所以适当的补充数学知识还是必须的。下面推荐一些看过的适合程序员看的好书:

Mirrorlink类型车联网项目阶段性成果

最近做的一个类似mirrorlink的车联网项目取得阶段性的成果,车机和手机之间已经连通并且已经可以实现远程控制,目前实现的车机平台是CE系统,手机是android类手机。其实这样的项目从原理上来讲并不复杂,主要是包括2大功能:


第一:实现一个类似手机助手功能,可以简单把车机理解成一台PC主机,当有android手机通过USB插入到车机上的时候,车机发现手机,并且推送类似VNC的远控程序到手机端,并且通过ADB的端口转发功能在USB链路上实现socket连通,这样车机控制端程序就可以和手机上的被控端程序进行socket网络通讯了。难点主要是需要在CE平台上实现一个ADB驱动程序, 并且将ADB移植到CE平台上来。

ADB与普通APP权限简单对比

最近在做一个车联网的项目,其中一个基本需求是将一台android手机通过USB线缆接入到车机上后,手机屏幕远程映射到车机的屏幕上,我们可以通过操作车机的屏幕来远程控制手机。这个技术本质上就是通过类似VNC的方式在车机上远程控制手机。业内有名的车联网方案如apple的carplay还有google的androidauto本质上其实也都是远程映射,另外一个著名的规范MirrorLink就直接定义了利用VNC技术进行映射。
android平台上有很多VNC相关的程序和库,如开源的libvncserver,如有名的droidVncServer项目,它内部其实就是使用了libvncserver库。还有如Remoteroid等等。这些程序作为远程控制中的《被控端》运行到android上都必须要求root权限,原因也很简单就是需要权限。作为VNC技术中的《被控端》有两个基本工作要做:
第一就是读取本机的屏幕buffer传送给《控制端》,这样《控制端》就可以看到《被控端》的屏幕内容了。
第二就是《被控端》要接受来自《控制端》的操作请求,比如点击了屏幕上某个位置,进而控制android做出相应的反应。
这里获取屏幕buffer暂且不聊,主要说说输入控制相关的部分,android是基于linux的,所以输入控制的部分主要是操作/dev/input下面的设备文件。我们来看看android系统/dev/input下面的设备文件的权限。如图:
可见root用户有读写权限,input组下面的用户也有读写权限。而作为android下面的普通的APP是不具备这样的权限的。
下面我们来看看一个普通的APP的权限,如图:
可见这个普通的APP所属用户为u0_a57,所在组也不包括input组。所以对/dev/input下面的设备文件无权操作,那么输入控制功能将无法实现。所以必须要求root权限才行。
但是别家的车联网项目确实不要求android手机已经root,在没有root的android手机上也能实现类似VNC的远程操作。那么这又是为什么呢。答案就在adb上。adb在设备端所对应的的守护进程叫做adbd,在android启动过程中它会被init进程所加载,程序刚运行的时候是root权限,然后它会给自己降权,最终以shell用户权限运行。而shell属于input组,是可以读写/dev/input下面的设备文件的。如图:
所以当用户把android手机通过USB接入到车机上时(为了方便理解这里可以把车机就想象成一台PC电脑),车机通过adb命令通知android手机上的adbd运行起《被控端》程序,这个时候《被控端》运行在shell用户权限,可以正常读写/dev/input。
因为adb本身就是作为android调试桥的作用存在的。所以它实际上也是具备一定权限的,虽然没有root权限那么大,但是比一般普通的APP的权限还是要大一些。像/system/bin/下面提供的一些工具,比如getevent/sendevent,input,screencap,screenrecord等都只有用adb去调用才能正常工作,普通APP去调用是无法正常工作的,因为缺乏权限。 而像screenshot这样的工具通过adb去调用也无法正常工作,因为这个工具内部是直接操作/dev/graphics/fb0设备的,而这个设备连shell权限也无权操作。如图:
 
只有system用户和graphics组才有权操作。那为什么adb调用screencap又可以呢,通过读取源代码可以知道,它的源代码在frameworks/base/cmds/screencap/ ,它内部实际上是调用SurfaceFlinger提供的接口ScreenshotClient,本质上是通过binder机制与SurfaceFlinger进程间通讯罢了。android自带的调试工具DDMS有一个截图功能,其实也是通过adbd调用screencap完成截图的。


VR,AR之我见

近两年VR,AR等技术越来越火,像微软的Microsoft HoloLens眼镜这样的产品,依靠手势识别,语音识别等交互技术,加上真三维的视觉冲击,可以说把我们和未来一下子拉近了很多。那站在我们程序员的角度,如何看待这样的变革呢。我想估计以后至少会有两种类型的应用。
第一就是传统的二维应用:我们现在开发的PC程序,手机APP等,不管是阅读为主的新闻APP还是大型3D游戏程序,本质上还是看成一个二维程序,为什么这样说呢,因为它们最终需要一个二维的屏幕给显示出来,它们以后在类似Microsoft HoloLens这样的环境中如何表现出来呢?那就是在真三维环境中虚拟出一块屏幕,然后把APP的内容显示到这个虚拟的屏幕上,就像在真实的屏幕中显示一样。如下图:
这种模式下利用手势识别模拟点击和滑动等操作,这样可以将现在传统的APP,全部照搬到真三维环境中。对我们程序员来说可能不需要做太多工作。
第二种就是真三维应用,下面两张图一个是电影《普罗米修斯》,一个是游戏《星际争霸2》中的截图,表现的就是这种类型的应用,做这种应用估计得懂几何学,图形学,机器视觉,DirectX,OpenGL,向量,矩阵变换,顶点,纹理,多边形。哈哈哈,努力学习,天天向上。


微信公众号及其账号授权模式的一些发散思考

用户在使用app前碰到最大的二大问题:
1. APP需要从市场上下载到手机,需要占用流量。
2. 安装完毕后,一般需要注册,才能使用完整的功能。

下载安装这个就不需要多讲了,那为什么要注册呢?注册的目的主要是有2点:

第一:最基础的一点就是用于唯一的标识一个客户,厂家可以知道是有不同的人在进行操作,虽然不知道具体是谁,不知道他的具体情况,但是确实是有这么一个人。
第二:可以在第一点的基础上,记录这个人的各种详细信息,以便于对他进行服务,比如电商系统,可以记录某个人手机是多少,叫什么,住哪里,这样可以为他送货,也可以记录他的购买历史等等。

现在微信公众号的推出,作为一种轻量级app,其实就是解决了一开始说的2个大问题,下载和注册。用户只需要进行关注,这样比下载成本低很多,也不需要刻意去做登录这一步,因为微信本身就已经是登录状态了,只需要把这个登录的账号做网页授权就可以了,获得的openid对这个公众号是唯一的,可以作为一个用户的唯一凭证。当然仅仅只知道一个openid是不够的,它只解决了用户识别问题,至于其他的用户详细信息部分就在软件的使用过程中逐步引导客户去填写,比如我是电商系统我可以在用户下单的时候,要求用户填入手机和姓名和地址等,因为这是理所当然的,不然怎么送货呢。

在需要登录的时候,一般可以如下方式:

1.微信授权登录 获取openid和一些其他信息 算是登录了。
2.自定义登录,又可以分出以下几种方式:
一:邮箱或者手机号码 发验证码登录 不需要固定密码
二:用邮箱或者手机绑定固定密码,以后登录直接用固定密码登录。

当获得手机或者邮箱或者一个openid的时候 就都算登录了。 因为这三者每一个都能唯一表示一个用户,用户的其他的信息,可以在随后的使用中逐步引导客户去输入。

比如京东PC站点有一种不需要登录一次性购买的行为,就是你可以不用登录,看完商品 然后在最后下单的时候 京东会要求你输入手机和送货地址 并且验证你的手机,然后就下单了。这样看上去好像是不需要登录直接购买,实际上是变相的方式引导客户进行了注册而已。所以方式很重要。

账号体系是如此之重要,所以BAT等都在争抢。所以我们也可以看到各种授权登录模式,比如微信授权登录,QQ授权登录,支付宝授权登录等等。

微信开放平台的核心思想就是账号授权给其他app免去注册这个坎。


小蚁直递

小蚁直递是我2015年做的一个社区电商领域的项目,虽然一直没什么起色,不过还是记录下来,自勉之。


Xdebug调试PHP原理浅析

用很多工具写过PHP,最后还是发现PhpStorm最好用,同时用PhpStorm搭配Xdebug调试PHP也很舒服。至于如何配置Xdebug网上已经有了太多文章可以参考,这里不再说明,本文只是简单的记录一下Xdebug调试PHP的原理和过程。

要进行调试有三个必要组件:

组件一:实现了DBGP调试协议的调试插件,这个PhpStorm默认就已经包含了,设置里面也可以看到相关设置,如图:
通过点击导航栏上电话一样的按钮(如下图),PhpStorm会开启9000端口进行监听。
通过进程信息可以清楚的看到PhpStorm已经在监听9000端口。如下图:
DBGP协议介绍地址:

组件二:PHP的Xdebug扩展。下载地址为:
Xdebug作为PHP扩展模块将会被PHP所加载,加载成功后可以通过 php -m 命令 或者 phpinfo() 函数查看。如下图:
php.ini文件中的配置示例:

[Xdebug]
zend_extension=E:\php-5.4.8-Win32-VC9-x86\ext\php_xdebug-2.4.0rc3-5.4-vc9.dll
xdebug.profiler_enable=on
xdebug.trace_output_dir="../xdebug"
xdebug.profiler_output_dir="../xdebug"
xdebug.idekey=PhpStorm
xdebug.remote_handler=dbgp
xdebug.remote_enable=on

组件三:Chrome浏览器的xdebug helper扩展。安装地址(记得翻墙):

当把上面三大必要组件都配置好后(具体配置网上很多文章都有详细介绍),就可以开始调试了。具体的调试原理如下:
1. 运行WEB服务器(如Apache),Apache会加载PHP,PHP会加载Xdebug扩展模块。
2. WEB浏览器(如Chrome)会通过80端口访问 WEB服务器中的某个PHP文件
3. WEB服务器会调用PHP解释器解析PHP文件,如果开启调试的话,PHP会通过Xdebug(根据php.ini中的Xdebug配置)主动连接调试客户端,默认是localhost:9000,如图:
也就是说 PHP会通过Xdebug扩展模块主动连接上PhpStorm正在监听的9000端口,从而PHP会与PhpStorm建立起调试会话通讯,这样程序员就可以通过PhpStorm对PHP程序进行各种调试交互动作了,比如查看变量值,比如下断点等等。
4. 当PHP的代码执行完毕以后,Xdebug会断开与PhpStorm的连接,从而结束本次调试会话过程。

交互流程如下:

browser <==> Port 80 <==> Apache+PHP+Xdebug <==> Port 9000 <==> PhpStorm(DBGP Plugin)

文章写到这里,可能细心的朋友就发现了。Chrome浏览器的xdebug helper扩展 这个部分一直没有提起。不是说它是三大必要组件之一吗!确实,从操作体验上来看用Chrome浏览器配合xdebug helper扩展确实会体验好很多,但是从纯技术角度来讲,它确实不是必须的,因为你想啊,PHP是一门服务端语言,调试也是服务端调试,怎么需要某个特定的浏览器呢?难道只能用Chrome浏览器,其他的浏览器不行吗,这个从技术角度来讲说不过去啊。事实上确实与特定浏览器没有关系,Chrome浏览器的xdebug helper扩展实际作用其实只是设置了一个标志在cookie中,当浏览器访问服务端时,这个cookie值会被传到服务端,这样Xdebug读取到这个值后就知道本次请求用户是想要开启调试功能,它才会连接调试客户端建立调试会话。
上图就是浏览器传给服务端的cookie值,意味着要开启调试。

所以简单来讲,不管用的是什么浏览器,其实只要设置对了相应的cookie值,就可以开启调试。在我这个示例当中还可以直接利用下面的代码来开启调试:
<div id="xdebug_bookmarklets" class="hide" style=" displayblock" >
    <h3>Bookmark these links for future use </h3>
    <h4>Debug: </h4>
    <ul class= "starlist" >
        <li>< a id="xdebugStartSession" href="javascript:(/** @version 0.5.2 */function() {document .cookie='XDEBUG_SESSION='+'PHPSTORM'+';path=/;';})()"> Start debugger</a></li>
        <li>< a id="xdebugStopSession" href="javascript:(/** @version 0.5.2 */function() {document .cookie='XDEBUG_SESSION='+''+';expires=Mon, 05 Jul 2000 00:00:00 GMT;path=/;';})()"> Stop debugger</a></ li>
        <li>< a id="xdebugThisPage" href="javascript:(/** @version 0.5.2 */function() {document .cookie='XDEBUG_SESSION='+'PHPSTORM'+';path=/;';document. location.reload(); document.cookie='XDEBUG_SESSION='+''+';expires=Mon, 05 Jul 2000 00:00:00 GMT;path=/;';})()" >Debug this page </a></li>
    </ul>
    <h4>Profile: </h4>
    <ul class= "starlist" >
        <li>< a id="xdebugStartProfiler" href="javascript:(/** @version 0.5.2 */function() {document .cookie='XDEBUG_PROFILE='+'1'+';path=/;';})()"> Start profiler</a></li>
        <li>< a id="xdebugStopProfiler" href="javascript:(/** @version 0.5.2 */function() {document .cookie='XDEBUG_PROFILE='+''+';expires=Mon, 05 Jul 2000 00:00:00 GMT;path=/;';})()"> Stop profiler</a></ li>
    </ul>
    <h4>Function Trace: </h4>
    <ul class= "starlist" >
        <li>< a id="xdebugStartTracer" href="javascript:(/** @version 0.5.2 */function() {document .cookie='XDEBUG_TRACE='+'1'+';path=/;';})()"> Start tracer</a></li>
        <li>< a id="xdebugStopTracer" href="javascript:(/** @version 0.5.2 */function() {document .cookie='XDEBUG_TRACE='+''+';expires=Mon, 05 Jul 2000 00:00:00 GMT;path=/;';})()"> Stop tracer</a></ li>
    </ul>
</div>
本质就是设置一个cookie值而已。
当然其实还有个更简单的办法 就是直接在请求的url后面连上一个&XDEBUG_SESSION_START=1,这样也开启了调试。

其实不单单是PhpStorm,其他编辑器如 vim,notepad++等编辑器装上DBGP调试插件后也都能进行PHP调试,原理流程也和上文介绍的一样,只是调试客户端不同而已。
下面是notepad++的DBGP调试插件的下载地址:
下面是vim插件下载地址:


APK【Volume Power】逆向全过程

之前对音量键点亮屏幕比较感兴趣就分析了一个这样功能的APP,现在又分析了另一个名叫Volume Power的APP,感觉比较坑爹,权且当学习吧。我们开始,先看看包结构,如图:
文件很少,非常简单,唯一一个可疑的是empty.mp3文件,这个文件稍后分析中会提到。运行后主界面如下:
就是一个设置界面,有三个设置选项:
一:启用还是禁用音量键点亮屏幕
二:是否启动运行
三:通过通知栏的按钮关闭屏幕
接下来正式开始分析,这里只会讲重点部分,其他可以自己看代码。按惯例先看AndroidManifest.xml文件内容:
这里注册了几个广播接收器,还有一个服务。其中:
android.media.VOLUME_CHANGED_ACTION 会在调节音量的时候被触发。
android.intent.action.SCREEN_OFF 在关闭屏幕的时候触发
android.intent.action.SCREEN_ON   在点亮屏幕的时候触发
android.app.action.DEVICE_ADMIN_ENABLED 将程序设置成Android设备管理器或者取消的时候被触发
android.intent.action.BOOT_COMPLETED 系统启动后自动运行
其他都先不关注,按程序执行流程先看com.example.stayawake.BootReceiver,它主要是创建一个线程并且执行,如图:
那么线程具体做了什么,接着看 BootReceiver$1类,这个是个匿名类,如图:
这里注解的很清楚,这是一个内部类,没有名字,它存在于BootReceiver.onReceive中,线程主要就是运行起了SoundService服务:
然后我们来看看这个服务,这个服务最主要的作用就是做了一件坑爹的事,如图:
就是不停的循环播放empty.mp3文件,而empty.mp3实际上是一个空的没有声音的文件,所以用户并不知道在播放声音,但是后台确实一直在播放,那就意味着运行了这个程序后,电量将飞快的被消耗掉,真是坑爹啊。那为什么要这样做呢,主要目的就是保持这个APP一直存在于内存中不被kill掉,如果被kill了功能就丧失了,因为在Android系统内存低时系统会根据一些策略kill掉一些进程来腾出内存,所以作者这里用了这样一种坑爹的方式来保证APP能一直运行。
接下来当用户点击音量键的时候,android.media.VOLUME_CHANGED_ACTION所对应的广播接收器VolumeReceiver.onReceive会被执行,然后事情就简单了:
直接调用系统的电源管理器点亮屏幕。另外程序为了能有关闭屏幕的权限会在设置界面将自己提升为Android设备管理器:
好了,基本就是这样,这个APP非常简单也没什么太多要说的,具体的可以自行查看源代码。
全部代码已经上传到github上,源代码的github地址:
https://github.com/phonegapX/com.teliapp.powervolume


特殊字符转义的一点体会

在编程的过程中经常会碰到一些特殊字符需要进行字符转义才能使程序正常运行,那为什么要进行字符转义才能使程序正常运行呢,我们举几个例子:

例子一:
比如在C语言中 我们想定义一个字符串如 hello,"moses" 那么如果写成 
char *str = "hello,"moses",are you ok"; 
那么程序铁定出错,编译不过,因为双引号"在C语言中是作为定义字符串开始和结束的一个特殊符号来用的,C编译器遇到上面这样的代码直接晕倒,那么必须像下面这样写:
char *str = " hello,\"moses\",are you ok";  
加上转义符号\才能让C编译器正确进行认知,简单来说就是,对于C编译器来讲,在字符串中【\"】才等于一个双引号【“】。
例子二:
比如在JS语言中我们定义一个JSON 如
{"content" : "hello,"moses",are you ok"}
同样JS解析引擎也会出错,因为双引号"在JS中也是作为定义字符串开始和结束的一个特殊符号来用的,那么必须像下面这样写:
{"content" : "hello,\"moses\",are you ok"}
加上转义符号\才能让JS解析引擎正确进行认知,简单来说就是,对于JS解析引擎来讲,在字符串中【\"】才等于一个双引号【“】。
例子三:
比如在SQL中,这里假设是MYSQL,我们定义一个SQL语句:
select * from mytable where content like 'hello,'moses',are you ok';
SQL解析引擎会出错,因为单引号'在SQL中是做为字符串开始和结束的一个特殊符号来用的,必须像下面这样写:
select * from mytable where content like 'hello,\'moses\',are you ok';
加上转义符号\才能让SQL解析引擎正确进行认知,简单来说就是,对于SQL解析引擎来讲,在字符串中【\'】才等于一个单引号【’】。
例子四:
比如在HTML中,我们定义如下一个标签:
<input type="text" value="hello,"moses",are you ok">
大家可以在浏览器中试一试,输出会被截断,并不能完整的输出 hello,"moses" 这样的字符串,因为双引号在这里是用来定义标签属性的,所以必须像下面这样写:
<input type="text" value="hello,&quot;moses&quot;,are you ok">
这里需要注意的是HTML里面的转义符号是&,而不是符号\。在这个例子上为了在标签属性中表示一个双引号【"】必须要用【&quot;】,当然也可以用【&#34;】来表示,前面那种叫实体名称,后面这种叫实体编号,具体请自行google或者baidu。这里需要强调的是在HTML中是否转义需要根据上下文来,比如:
<html>
<body>
<input type="text" value="hello,&quot;moses&quot;,are you ok">
hello,"moses",are you ok
</body>
</html>
这段代码在浏览器中是能正确显示的,因为第二处字符串并没有和任何特殊字符有冲突。当然如果你一定要转义表示那也没任何问题。如下:
<html>
<body>
<input type="text" value="hello,&quot;moses&quot;,are you ok">
hello,&quot;moses&quot;,are you ok
</body>
</html>
你会发现它在浏览器中也能正确显示,并且显示和前面的一模一样。
这里举了四个例子,总结起来,其实都是一次人机交互的过程,这里的“机器”分别指的是【C编译器】【JS解析引擎】【SQL解析引擎】【HTML解析引擎】,当我们“人”想告诉“机器”,比如告诉【JS解析引擎】我要输入的是一个双引号时,因为双引号对【JS解析引擎】有特殊含义,所以我们必须输入【\"】这样的字符序列给到【JS解析引擎】,这样【JS解析引擎】才会明白我们“人”其实只是想输入一个双引号【"】。
人=>输入序列\"=>JS解析引擎=>理解成=>双引号"
这里需要重点强调的就是关于转义字符问题对于不同的开发语言有着不同的规则,虽然也有很多相同的地方,但是不能预先做假设,需要查阅相关文档。
另外在实际开发过程中经常是各种语言混合开发的,比如做一个网站最常见的形式是 服务端用PHP+MySQL开发,客户端是HTML+JS。服务端PHP代码里面可能会包含SQL语句,而客户端的HTML代码里面可能也会混编入JS代码。那么这个时候我们就必须要有非常清晰的认知,对于一段包含有特殊字符的字符序列来说什么时候由【PHP解析引擎】处理,什么时候由【SQL解析引擎】处理,什么时候由【HTML解析引擎】处理,什么时候由【JS解析引擎】处理,必须搞的明明白白,否则就可能会发生莫名其妙的错误。我们来看一次简单的网页输出过程,很可能是:
【PHP解析引擎】=>sql语句序列=>【SQL解析引擎】=>返回各种结果字符序列=>【PHP解析引擎】=>网络输出给浏览器=>【HTML解析引擎】=>js代码字符序列=>【JS解析引擎】=>执行结果字符序列=>【HTML解析引擎】=>电脑屏幕
简单来说可以理解成一种引擎的输出很可能是作为另一种引擎的输入。其实这也符合计算机系统的基本概念,计算机系统本质上就是输入+处理+输出。
这里我们举几个例子:
我们先来看看例子一,这是一段HTML+JS混编代码,代码如下:
<html>
<body>
<input type="button" value="button" onclick="alert("hello,"moses",are you ok");">
</body>
</html>
这段代码包含了HTML代码也包含了JS代码,这段代码的功能是当点击按钮会弹出一个对话框,对话框的内容是:hello,"moses",are you ok
但是当点击按钮的时候,铁定出错,肯定不会弹出对话框,这么多双引号,估计都晕了,哈哈。既然点击按钮执行的是JS代码,那么首先肯定是想当然的利用JS语言的转义规则进行处理,代码如下:
<html>
<body>
<input type="button" value="button" onclick="alert(\"hello,\"moses\",are you ok\");">
</body>
</html>
结果还是不行,而且错得一塌糊涂,看样子想当然是不行的了,还的仔细捋一捋把来龙去脉搞清楚才能解决。首先我们必须得先明白,上面的代码是首先被【HTML解析引擎】解析后才会交给【JS解析引擎】处理,那么看看上面的代码被【HTML解析引擎】解析后是个什么样子,因为符号\并不是【HTML解析引擎】的转义符,所以最后交给JS执行的代码是alert(\,不出错才怪。既然是先被【HTML解析引擎】解析,那么我们肯定得先用HTML的转义规则来处理,修改后的代码如下:
<html>
<body>
<input type="button" value="button" onclick="alert(&quot;hello,&quot;moses&quot;,are you ok&quot;);">
</body>
</html>
可惜的是这样修改后还是不能正常工作,那是为什么呢,仔细分析一下就可以得知,这段代码被【HTML解析引擎】解析出来的JS代码实际如下:
alert(“hello,”moses“,are you ok”);
这行代码交给【JS解析引擎】后肯定会出错,原因就是中间那两个双引号,必须要按JS语言的规则转义,应该修改成下面这样子:
alert(“hello,\”moses\“,are you ok”);
这样才能正常工作,那么现在我们把HTML和JS两种规则结合起来处理后,得到了最终如下的整合代码:
<html>
<body>
<input type="button" value="button" onclick="alert(&quot;hello,\&quot;moses\&quot;,are you ok&quot;);">
</body>
</html>
至此,程序终于能按照期望运行了。当然实际上我们可以通过双引号和单引号配合使用来解决问题,但是这里我们为了很好的说明问题的来龙去脉,所以不会考虑其他途径。
接下来看看例子二,这是一段PHP+SQL混编代码,为了更好的说明原理和问题,这里全部使用单引号,代码如下:
<?php
$con = mysql_connect('localhost','root','123456');
mysql_select_db('db_test', $con);
mysql_query('INSERT INTO T_Test (F_Content) VALUES ('hello,'moses',are you ok')');
mysql_close($con);
?>
代码的目的是将字符序列 hello,'moses',are you ok 插入数据库中,但是上面这代码肯定是错的,因为对于【PHP解析引擎】来说单引号是作为定义字符串开始和结束的一个特殊字符,所以这里肯定必须进行转义处理,而PHP也是用符号\来作为转义符的,所以我们将代码改成如下样子:
<?php
$con = mysql_connect('localhost','root','123456');
mysql_select_db('db_test', $con);
mysql_query('INSERT INTO T_Test (F_Content) VALUES (\'hello,\'moses\',are you ok\')');
mysql_close($con);
?>
不幸的是,这段代码还是无法正常工作,那是为什么呢,我们来分析一下原因。首先要说的是这段代码是先交给【PHP解析引擎】解析后再把其中的SQL语句交给MySQL的【SQL解析引擎】来处理,有了这个前提后就比较好分析了,我们先来看看通过【PHP解析引擎】解析后出来的代码是什么样子:
解析之前:
INSERT INTO T_Test (F_Content) VALUES (\'hello,\'moses\',are you ok\')
解析之后:
INSERT INTO T_Test (F_Content) VALUES ('hello,'moses',are you ok')
然后解析之后的SQL语句会交给【SQL解析引擎】来处理,答案很明显了,肯定出错,因为在【SQL解析引擎】中单引号也是定义字符串开始和结束的特殊符号。那么这里要重点强调的是:
【PHP解析引擎】解析是没问题的,但是在【SQL解析引擎】解析的时候出错了。
然后接下来我们继续修改代码:
<?php
$con = mysql_connect('localhost','root','123456');
mysql_select_db('db_test', $con);
mysql_query('INSERT INTO T_Test (F_Content) VALUES (\'hello,\\'moses\\',are you ok\')');
mysql_close($con);
?>
一运行,代码还是出错,这又是为什么呢,那是因为中间\\'这样的语法出现了问题,前面那个\将后面的\给转义了从而失去了转义的特性导致单引号没被转义,所以直接在【PHP解析引擎】解析的时候就出错了,那么这里要重点强调的是:
这次是【PHP解析引擎】解析的时候就已经出问题了,压根和【SQL解析引擎】没啥关系。
只能继续修改代码了,修改后的代码如下:
<?php
$con = mysql_connect('localhost','root','123456');
mysql_select_db('db_test', $con);
mysql_query('INSERT INTO T_Test (F_Content) VALUES (\'hello,\\\'moses\\\',are you ok\')');
mysql_close($con);
?>
这次终于成功了,顺利执行完毕,数据也正常写入成功。我们来分析一下这次为什么可以:
【PHP解析引擎】解析前的代码:
INSERT INTO T_Test (F_Content) VALUES (\'hello,\\\'moses\\\',are you ok\')
【PHP解析引擎】解析后的代码:
INSERT INTO T_Test (F_Content) VALUES ('hello,\'moses\',are you ok')
然后再把这句SQL交给【SQL解析引擎】解析,根据转义规则,【SQL解析引擎】也非常清楚的明白要写入的数据里面包含有2个单引号,而不会发生冲突,最终数据终于成功的写入了。
如图:
最后要说的是在实际开发中其实有很多办法可以解决类似的问题,比如单引号和双引号配合使用,比如magic_quotes_gpc=on,addslashes和stripslashes,mysql_real_escape_string等等函数都能比较好的解决问题。
好了,具体的技术细节就说到这里了,那么接下来我们从宏观架构的角度来简单讨论一下这个话题。在项目的实际开发中要考虑的转义字符不单单只有引号,还有诸如<,>,&等等很多很多,这里只讨论几个最常见的。这些转义字符如果不做处理,可能会导致很大的安全隐患,比如古老的SQL注入技术,比如XSS攻击,这里举个最简单的例子,比如在一个文章发布系统里面不做任何特殊处理,那么有人在提交的文章里面加入一个<script src='x.js'>这样的标签那就麻烦大了,所以要做处理。那么到底什么时候做处理呢,我们先看一幅图:
这是现在最为常见的系统架构,一个软件可能会有不同平台的客户端,大家都统一通过RESTfulAPI进行通讯,一个典型的例子就是微信,它有PC端,Android端,IOS端,WEB端等等。
我们在设计这样的系统的时候 RESTfulAPI可能会用PHP,Java,NodeJS等语言开发,Android端开发有Java,IOS上有Objective-C,PC上用C/C++,.Net,Delphi等等,WEB端有HTML+JS。
你会发现这么多语言各有各的规则,所以最好的办法就是在保证安全的前提下输入的数据不做转换直接进入到数据库,输出的数据也不做转换直接给到客户端,然后各种客户端都根据自己的实际情况自行处理,这里打个简单的比方: 
比如用户输入一段字符序列 【hello<hr>"<br>】,那么最终进入数据库的也要是同样的序列,而不应该是【hello&lt;hr&gt;&quot;&lt;br&gt;】,因为这样的转换HTML认得,但是其他客户端比如C/C++就不认得,就会造成各种困扰,所以最终的处理过程应该留给各个客户端自行处理。


«123456»

Powered By Z-Blog 2.2 Prism Build 140101

Copyright phonegap.me Rights Reserved.