iOS WKWebView:拦截和篡改<input type="file">标签

0x00 为什么要这么做

需要把一个网站嵌入到APP里,那个网站的一个页面包含了一个上传文件的按钮,他们只能接受后缀为jpg的图片,但是iOS相册里png图片是常见的。
然而他们对客户端要求必须有这种限制。所以客户端需要想办法,把用户挑选出来的图片转为jpg格式,后缀改为jpg再上传。

初步考虑后大致想出了三个路子:

  1. 能不能在UIImageViewCongtroller里做method-swizzling.
  2. 能不能注入JS,通过JS拦截来转换图片格式。
  3. 在WebKit里做method-swizzling,毕竟总得通过一个回调把片传给WebView.

方法1无法走通,因为不知道找不到可行的方法,但是可以利用UIImagePickerControllerDelegate,这个由方法3描述。
方法2是可以的,针对性的对input标签增加坚挺,获取图片资源的base64,通过js转换成jpg.

然后就是方法3了。这是一个完全可行的方法。

0x01 寻找拦截的入口

在xcode里的文档并没有指出UIImagePickerController的delegate是谁。
这里可以从WebKit2文档里找到的,就是WKFileUploadPanel.
不过我用了一个自下而上的方法来寻找,swizzle了viewDidAppear方法。

- (void)swizzled_viewDidAppear {
    [self swizzled_viewDidAppear];
    if ([self isKindOfClass:[UIImagePickerController class]]) {
            UIImagePickerController *that = self;
            NSLog(@"%@",that.delegate);
        }

打印出来的便是<WKFileUploadPanel: 0x10bdc3710>,
google一下即可发现WKFileUploadPanel.mm源码,后面需要用到。

0x02 置换回调方法

根据文档,和对mediaInfo的打印结果确认,只有didFinishPickingMediaWithInfo包含了图片和后缀信息,是适用于重写图片信息的。

__TVOS_PROHIBITED @protocol UIImagePickerControllerDelegate<NSObject>
@optional
// The picker does not dismiss itself; the client dismisses it in these callbacks.
// The delegate will receive one or the other, but not both, depending whether the user
// confirms or cancels.
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingImage:(UIImage *)image editingInfo:(nullable NSDictionary<NSString *,id> *)editingInfo NS_DEPRECATED_IOS(2_0, 3_0);
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info;
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker;
//MediaInfo
[0] (null)  @"UIImagePickerControllerMediaType" : @"public.image"
[1] (null)  @"UIImagePickerControllerOriginalImage" : (no summary)
[2] (null)  @"UIImagePickerControllerReferenceURL" : @"assets-library://asset/asset.JPG?id=7C993AB5-2881-4261-BAB4-BB0559E8C65C&ext=JPG"
[3] (null)  @"UIImagePickerControllerImageURL" : @"file:///private/var/mobile/Containers/Data/Application/F024EE26-344E-4A83-AD0F-8E4BCEFA18AA/tmp/E1167ADD-779C-4496-B4E3-5AD45A0B2478.jpeg"

然后参照WKFileUploadPanel.mm源码进行swizzling.
先贴出swizzle后的方法实现

- (void)swizzled_imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info{
    NSMutableDictionary *dict = [[NSMutableDictionary alloc]initWithDictionary:info];
    //UIImagePickerControllerReferenceURL设为空是关键一步。
    [dict setValue:nil forKey:@"UIImagePickerControllerReferenceURL"];
    NSURL *oriPath = [dict valueForKey:@"UIImagePickerControllerImageURL"];
    NSString *imgfolder =[NSString stringWithFormat:@"file://%@",fast_PathInDocumentDirectory(@"tmpImgs")];
    NSString *imgsPath = fast_PathInDocumentDirectory(@"tmpImgs");
    if ([oriPath.absoluteString hasSuffix:@"png"]){
        UIImage *oriImg = [dict valueForKey:@"UIImagePickerControllerOriginalImage"];
        NSData* data = UIImageJPEGRepresentation(oriImg,0);
        NSString * shortUUID = [NSUUID shortUUIDString];
        NSString *finalPath = [imgsPath stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.jpg",shortUUID]];
        [data writeToFile:finalPath atomically:YES];
        UIImage *jpgImg = [UIImage imageWithData:data];
        NSURL *jpgPath = [@"file://"stringByAppendingString:finalPath].mj_url;
        [dict setObject:jpgImg forKey:@"UIImagePickerControllerOriginalImage"];
        [dict setObject:jpgPath forKey:@"UIImagePickerControllerImageURL"];
    }else if([oriPath.absoluteString hasSuffix:@"jpeg"]){
        UIImage *oriImg = [dict valueForKey:@"UIImagePickerControllerOriginalImage"];
        NSData* data = UIImageJPEGRepresentation(oriImg,0);
        NSString * shortUUID = [NSUUID shortUUIDString];
        NSString *finalPath = [imgsPath stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.jpg",shortUUID]];
        [data writeToFile:finalPath atomically:YES];
        UIImage *jpgImg = [UIImage imageWithData:data];
        NSURL *jpgPath = [@"file://"stringByAppendingString:finalPath].mj_url;
        [dict setObject:jpgImg forKey:@"UIImagePickerControllerOriginalImage"];
        [dict setObject:jpgPath forKey:@"UIImagePickerControllerImageURL"];
    }
    [self swizzled_imagePickerController:picker didFinishPickingMediaWithInfo:dict];
}

fast_PathInDocumentDirectory的实现

NSString *fast_PathInDocumentDirectory(NSString *fileName)
{
    NSArray *documentDirectories =
    NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
                                        NSUserDomainMask, YES);

    NSString *documentDirectory = [documentDirectories objectAtIndex:0];

    return [documentDirectory stringByAppendingPathComponent:fileName];
}

[NSUUID shortUUIDString] 是来源于UUIDShortener的扩展方法

对置换方法的解释

在结合mediaInfo从源码寻找的过程中发现了突破口,实际上在发现这个方法之前我都不确定是否可以实现对图片的置换。

因为这个方法之前,我单纯尝试性地修改了mediaInfo的里相关的后缀和图片格式。但这样都无法把图片上传给webview。

所以必须再深入一点去了解源码,确认是否可行。

下面是WKFileUploadPanel中上传图片的方法:

- (void)_uploadItemFromMediaInfo:(NSDictionary *)info successBlock:(void (^)(_WKFileUploadItem *))successBlock failureBlock:(void (^)(void))failureBlock
{
    NSString *mediaType = [info objectForKey:UIImagePickerControllerMediaType];

    // For videos from the existing library or camera, the media URL will give us a file path.
    if (UTTypeConformsTo((CFStringRef)mediaType, kUTTypeMovie)) {
        NSURL *mediaURL = [info objectForKey:UIImagePickerControllerMediaURL];
        if (![mediaURL isFileURL]) {
            LOG_ERROR("WKFileUploadPanel: Expected media URL to be a file path, it was not");
            ASSERT_NOT_REACHED();
            failureBlock();
            return;
        }

        successBlock(adoptNS([[_WKVideoFileUploadItem alloc] initWithFileURL:mediaURL]).get());
        return;
    }

    if (!UTTypeConformsTo((CFStringRef)mediaType, kUTTypeImage)) {
        LOG_ERROR("WKFileUploadPanel: Unexpected media type. Expected image or video, got: %@", mediaType);
        ASSERT_NOT_REACHED();
        failureBlock();
        return;
    }

    UIImage *originalImage = [info objectForKey:UIImagePickerControllerOriginalImage];
    if (!originalImage) {
        LOG_ERROR("WKFileUploadPanel: Expected image data but there was none");
        ASSERT_NOT_REACHED();
        failureBlock();
        return;
    }

    // If we have an asset URL, try to upload the native image.
    NSURL *referenceURL = [info objectForKey:UIImagePickerControllerReferenceURL];
    if (referenceURL) {
        [self _uploadItemForImage:originalImage withAssetURL:referenceURL successBlock:successBlock failureBlock:failureBlock];
        return;
    }

    // Photos taken with the camera will not have an asset URL. Fall back to a JPEG representation.
    [self _uploadItemForJPEGRepresentationOfImage:originalImage successBlock:successBlock failureBlock:failureBlock];
}

上面这些代码,主要关注一下几个部分

  1. [info objectForKey:UIImagePickerControllerMediaURL]是无需修改的
  2. [info objectForKey:UIImagePickerControllerOriginalImage]不能为空,而且需要修改为转换后的图片。
  3. 然后就是最后几行涉及到referenceURL的代码:
NSURL *referenceURL = [info objectForKey:UIImagePickerControllerReferenceURL];
if (referenceURL) {
        [self _uploadItemForImage:originalImage withAssetURL:referenceURL successBlock:successBlock failureBlock:failureBlock];
        return;
    }

    // Photos taken with the camera will not have an asset URL. Fall back to a JPEG representation.
    [self _uploadItemForJPEGRepresentationOfImage:originalImage successBlock:successBlock failureBlock:failureBlock];

这里有个比较巧妙的过程,按注释的意思是,相机是不会有asset URL,即referenceURL会为空,所以这里不需要传asset URL,直接传图片对象即可。

对于web页面来说,只需要我的图片就行了。

回到自己写的置换的方法中去:

[dict setValue:nil forKey:@"UIImagePickerControllerReferenceURL"];为什么是关键一步呢。

就是因为我希望WKFileUploadPanel以为从相册来的图片也是从相机来的。

最后一步执行了

[self swizzled_imagePickerController:picker didFinishPickingMediaWithInfo:dict];

这里就是调用原始的WKFileUploadPanel对UIImagePickerDelegate的实现了,被修改后的mediaInfo被传给webview,实现了置换。

其实还有个方法就是考虑下能不能拦截input标签 跳转到自定义的页面上。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容