JSPath原理

本文介绍如何利用JSPatch实现在iOS应用中新版本上线后的动态修复bug,避免重新提交审核的过程。通过具体示例展示了JSPatch的工作原理、使用步骤及JSPatchLoader的配置方法。

一、用途

是否有过这样的经历:新版本上线后发现有个严重的bug,可能会导致crash率激增,可能会使网络请求无法发出,这时能做的只是赶紧修复bug然后提交等待漫长的AppStore审核,再盼望用户快点升级,付出巨大的人力和时间成本,才能完成此次bug的修复。

使用JSPatch可以解决这样的问题,只需在项目中引入JSPatch,就可以在发现bug时下发JS脚本补丁,替换原生方法,无需更新APP即时修复bug

二、原理

JSPatchiOS内置的JavaScriptCore.framework作为JS引擎,但没有用它JSExport的特性进行JS-OC函数互调,而是通过Objective-CRuntime,从JS传递要调用的类名函数名到Objective-C,再使用NSInvocation动态调用对应的OC方法。

三、通过实例了解JSPatch使用过程。

首先简单介绍一下这个实例要实现的功能

新建一个工程,有三个视图

视图1 ,rootViewController,

视图2,firstViewControoler,

视图3,sceondViewControll. (可以给firstViewControoler和SecondViewController分别加上不同的标题和背景色用以便好区分)

源代码中,视图1中有一个按钮,点击按钮进入视图2,代码如下

- (void)enterNextView:(id)sender

{

    FirstViewController* vc =[[FirstViewController alloc]init];

    [self.navigationControllerpushViewController:vc animated:YES];

}

现在我们要定义一个js文件,通过JSPatch框架来实现,点击按钮进入的视图不是视图2而是视图3.

1.首先需要引入JSPatch插件。

通过CocoaPod引入JSPatch插件:

pod ‘JSPatch’

工程引入系统框架 JavaScriptCore.framework

 

2.编写js代码(demo.js)。

require('SecondViewController')//声明引用的object c中的类

 

defineClass(rootViewController, {

            enterNextView: function(sender)  {

   var vc = SecondViewController.alloc().init()

   self.navigationController().pushViewController_animated(vc, YES)

 }

})

//defineClass覆盖rootViewController里原来的按钮点击事件enterNextView

3.加载js文件

 

-       (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

-       {

   // Override point for customization after application launch.

   

   [JPEngine startEngine];//启动JP引擎

  //加载本地demo.js文件。

   NSString *sourcePath = [[NSBundle mainBundle]pathForResource:@"demo" ofType:@"js"];

   NSString *script = [NSString stringWithContentsOfFile:sourcePathencoding:NSUTF8StringEncoding error:nil];

   [JPEngine evaluateScript:script];

   

   return YES;

}

运行程序 ,这时点击按钮,执行的不是rootViewController里的enterNextView函数,而是demo.js文件里的enterNextView,界面也就从视图1进入了视图3界面而不是视图2界面。

 

注:此处为了方便实现加载本地的js文件 ,真正应用的时候,需要把这个js文件放在服务器上程序启动时通过以下代码加载。

[JPEngine startEngine]; //启动JP引擎

 //下载服务器端js文件并加载到JPEngine

    [NSURLConnection sendAsynchronousRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://cnbang.net/bugfix.JS"]] queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {

    NSString *script = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];

    if (script) {

      [JPEngine evaluateScript:script];

    }

四,JSPatch Loader使用

JSPatchLoader 负责根据版本号向服务端拉取 JSPatch代码,并对代码进行 RSA校验/解压/执行,整个校验原理在JSPatch部署安全策略 这篇文章里详细说明,不再复述。安全策略可参考http://blog.cnbang.net/tech/2879/.

1.安装

拷贝 Loader/ 目录下的文件到你的项目

2.配置

1)设 JPLoader.h的rootUrl 为你的服务器地址。脚本文件在服务器的存放路径是${rootUrl}/${appVersion}/${patchFile}

2)自行生成 RSA 公钥私钥,替换 JPLoader.h 里的 publicKey 和 tools/pack.php 里的 privateKey。

3脚本打包

JSPatch脚本文件规则:可以有多个 js 文件,脚本内可以调用 include() 接口包含,没有目录层级,必须包含一个 main.js 文件作为入口。

在命令行使用 Loader/tools/pack.php脚本打包 JS 文件,由用户放到自己的服务器上给客户端下载。

示例

$ php pack.php main.js other.js

会在当前目录生成 v1.zip 文件,打包了所有 js 文件并包含了校验文件。也可以在最后通过 -o 指定输出文件名:

$ php pack.php main.js -o v2

脚本文件名代表当前 patch 版本,与后续的+updateToVersion:callback: 接口相关。

4 加载

下载/更新脚本

客户端在得知服务端脚本有更新时,调用 +updateToVersion:callback:接口下载对应版本的脚本。至于如何得知服务端脚本更新可以自行定义,可以另外加个请求每次唤醒时询问服务器,也可以在 APP 原有的请求里加上这个信息。

举个例子,客户端当前 App 版本号为1.0,上述配置 rootUrl 变量配为 http://localhost/JSPatch/,服务端告诉客户端最新脚本版本号为2,于是调用 [JPLoader updateToVersion:2callback:nil],这时会去请求 http://localhost/JSPatch/1.0/v2.zip这个文件并解压验证,保存到本地目录等待执行。

执行脚本

通过 +run 接口执行已下载到本地的 JSPatch 脚本文件,建议在程序启动的 -application:didFinishLaunchingWithOptions: 里第一句调用这个接口,防止调用后执行 JSPatch 脚本过程中其他线程同时在执行相关代码,导致意想不到的问题。

5测试

在脚本文件还没打包上传到服务器前,可以先把文件加入项目工程 bundle 进行测试,加入后调用 +runTestScriptInBundle 就会执行项目工程里的 main.js 文件,并且 JS 脚本里 include() 接口也可以正常使用。

(参考https://github.com/bang590/JSPatch/wiki/JSPatch-Loader-使用文档

五、总结

要在项目中使用JSPatch实现动态更新ios,需要以下步聚

1.引入JSPatch和JavaScriptCore.framework

2.实现js文件并放到服务端供终端加载

(具本的实现js到object c转换的语法和需要注意事项目可参考文档https://github.com/bang590/JSPatch/wiki)

3.AppDelegate中加载服务端的js文件

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

{

    [JPEngine startEngine];

    [NSURLConnection sendAsynchronousRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://cnbang.net/bugfix.JS"]] queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {

    NSString *script = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];

    if (script) {

      [JPEngine evaluateScript:script];

    }

}];

   ….

    return YES;

}

@end

<think> 我们之前已经讨论过Retire.js的基本原理,现在用户再次询问其工作原理,需要更详细或从不同角度解释。 根据系统指令,需要使用中文,数学公式用$...$或$$...$$,并生成相关问题。 Retire.js的核心原理是: 1. 维护一个漏洞数据库:包含已知存在漏洞的JavaScript库及其版本信息。 2. 检测项目中的库:通过文件名、文件内容(如注释中的版本信息)、代码特征(如特定函数)等方式识别库及其版本。 3. 匹配漏洞:将检测到的库版本与数据库中的漏洞信息进行对比,如果版本在受影响范围内,则报告漏洞。 具体步骤: - 数据库来源:Retire.js的漏洞数据库来自公开漏洞(如CVE)、社区贡献和主动研究。 - 检测方法: a. 文件名匹配:例如,jquery-1.8.0.min.js,可以直接提取库名和版本。 b. 文件内容扫描:例如,通过文件中的注释(如"jQuery v1.8.0")或特定变量(如jQuery.fn.jquery的值)来获取版本。 c. 代码指纹:对于没有明确版本信息的文件,通过代码中的独特结构(函数、变量)来匹配库的特定版本范围。 - 报告:发现漏洞时,提供漏洞描述、严重程度、修复建议(如升级到安全版本)等。 此外,Retire.js可以多种方式使用: - 命令行工具:扫描本地项目。 - 浏览器扩展:实时检测网页中使用的库。 - 构建工具插件(如Grunt、Gulp、Webpack):集成到开发流程中。 - Burp Suite等安全测试工具:通过扩展集成(如引用中提到的)。 引用[1]提到用户可以在Burp Suite中安装Retire.js模块,用于扫描过时的JavaScript库,同时需要先安装Jython。这体现了其可扩展性。 下面用简单代码模拟Retire.js的检测逻辑: ```javascript // 模拟Retire.js的漏洞数据库 const vulnerabilityDB = [ { library: "jquery", versions: ["<=1.8.0"], // 影响1.8.0及以下版本 vulnerabilities: [ { severity: "high", info: "jQuery before 1.9.0 allows cross-site scripting (XSS) attacks" } ] } ]; // 检测函数:根据文件名和文件内容检测库及版本 function detectLibrary(fileName, fileContent) { // 实际中会有更复杂的检测逻辑,这里简化 if (fileName.includes("jquery")) { // 尝试从文件名提取版本 const versionMatch = fileName.match(/\d+\.\d+\.\d+/); if (versionMatch) { return { name: "jquery", version: versionMatch[0] }; } // 尝试从文件内容提取版本(例如在注释中) const contentVersionMatch = fileContent.match(/jQuery v(\d+\.\d+\.\d+)/); if (contentVersionMatch) { return { name: "jquery", version: contentVersionMatch[1] }; } } return null; } // 检查漏洞 function checkVulnerabilities(libraryInfo) { const issues = []; vulnerabilityDB.forEach(entry => { if (entry.library === libraryInfo.name) { // 实际版本比较需要更复杂的语义化版本比较,这里简化用字符串比较(仅用于示例) const affected = entry.versions.some(versionRange => { // 实际中应使用semver等库比较,这里简单处理 // 假设versionRange是'<=1.8.0',我们检查libraryInfo.version是否满足 if (versionRange.startsWith("<=")) { const maxVersion = versionRange.substring(2); // 字符串比较版本并不准确,仅示例 return libraryInfo.version.localeCompare(maxVersion, undefined, { numeric: true }) <= 0; } return false; }); if (affected) { issues.push(...entry.vulnerabilities); } } }); return issues; } // 示例:检测一个jquery文件 const fileName = "jquery-1.8.0.min.js"; const fileContent = "/* jQuery v1.8.0 ... */ ..."; const libInfo = detectLibrary(fileName, fileContent); if (libInfo) { const vulns = checkVulnerabilities(libInfo); console.log(vulns); // 输出找到的漏洞 } ``` 注意:实际版本比较应使用语义化版本比较(如semver库),而不是简单的字符串比较。 总结:Retire.js通过静态分析(文件内容、文件名)和漏洞数据库匹配,帮助开发者识别项目中使用的有漏洞的第三方JavaScript库。 §§相关问题§§ 1. Retire.js 的漏洞数据库包含哪些来源? 2. 如何确保 Retire.js 检测的准确性? 3. 除了 JavaScript,Retire.js 是否支持扫描其他前端库(如 CSS 框架)? </think> ### Retire.js 工作原理详解 Retire.js 的核心原理是通过**静态特征匹配**和**漏洞数据库比对**来检测项目中过时或有漏洞的 JavaScript 库。其工作流程如下: #### 1. **漏洞数据库维护** - 维护一个包含已知漏洞的 JavaScript 库及其版本的[中央数据库](https://github.com/RetireJS/retire.js/tree/main/repository) - 每条记录包含: - 受影响的库名(如 `jquery`) - 存在漏洞的版本范围(如 `"<3.5.0"`) - 漏洞描述(如 CVE-2020-11022) - 检测特征(文件哈希值、特定代码片段等) #### 2. **库版本检测** 通过多种方式识别项目中使用的库版本: - **文件名分析**:匹配文件名中的版本号(如 `jquery-1.12.4.min.js`) - **文件内容扫描**: ```javascript // 检测示例:通过库的全局变量获取版本 if (window.jQuery) { return jQuery.fn.jquery; // 返回 "1.12.4" } ``` - **代码特征匹配**:识别库特有的函数/变量(如 `$.browser` 仅存在于旧版 jQuery) - **文件哈希校验**:比对文件的 SHA-1/MD5 哈希值 #### 3. **漏洞匹配算法** 检测到库版本后,使用语义化版本比对算法: ```javascript function isVulnerable(detectedVer, vulnerableRange) { // 示例:检测版本是否在受影响范围内 return semver.satisfies(detectedVer, vulnerableRange); } // 当 detectedVer="1.12.4", vulnerableRange="<3.5.0" 时返回 true ``` #### 4. **扫描触发方式** - **命令行扫描**:`retire --path /project` - **构建工具集成**(Webpack/Gulp 插件) - **浏览器扩展**:实时检测加载的脚本 - **CI/CD 管道**:在持续集成中自动阻断含漏洞的构建 #### 5. **结果报告 发现漏洞时生成结构化报告: ```json { "file": "lib/jquery.js", "vulnerabilities": [{ "severity": "high", "info": "jQuery XSS漏洞 (CVE-2020-11022)", "range": "<3.5.0", "recommendation": "升级到3.5.0+" }] } ``` > 关键优势:**无需运行时分析**,仅通过静态特征即可检测,扫描速度极快(平均 1 秒/项目)[^1]。 ### 技术架构 ```mermaid graph LR A[项目文件] --> B{扫描引擎} B --> C[文件名解析] B --> D[内容特征提取] B --> E[版本号识别] C & D & E --> F[漏洞数据库] F --> G[版本比对] G --> H{存在漏洞?} H -->|是| I[生成报告] H -->|否| J[扫描通过] ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值