iOS WKWebView:攔截和篡改<input type="file">標(biāo)簽

0x00 為什么要這么做

需要把一個(gè)網(wǎng)站嵌入到APP里木蹬,那個(gè)網(wǎng)站的一個(gè)頁面包含了一個(gè)上傳文件的按鈕礼预,他們只能接受后綴為jpg的圖片,但是iOS相冊(cè)里png圖片是常見的。
然而他們對(duì)客戶端要求必須有這種限制赊抖。所以客戶端需要想辦法,把用戶挑選出來的圖片轉(zhuǎn)為jpg格式寨典,后綴改為jpg再上傳氛雪。

初步考慮后大致想出了三個(gè)路子:

  1. 能不能在UIImageViewCongtroller里做method-swizzling.
  2. 能不能注入JS,通過JS攔截來轉(zhuǎn)換圖片格式耸成。
  3. 在WebKit里做method-swizzling,畢竟總得通過一個(gè)回調(diào)把片傳給WebView.

方法1無法走通报亩,因?yàn)椴恢勒也坏娇尚械姆椒ǎ强梢岳肬IImagePickerControllerDelegate井氢,這個(gè)由方法3描述弦追。
方法2是可以的,針對(duì)性的對(duì)input標(biāo)簽增加堅(jiān)挺花竞,獲取圖片資源的base64劲件,通過js轉(zhuǎn)換成jpg.

然后就是方法3了。這是一個(gè)完全可行的方法约急。

0x01 尋找攔截的入口

在xcode里的文檔并沒有指出UIImagePickerController的delegate是誰零远。
這里可以從WebKit2文檔里找到的,就是WKFileUploadPanel.
不過我用了一個(gè)自下而上的方法來尋找厌蔽,swizzle了viewDidAppear方法牵辣。

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

打印出來的便是<WKFileUploadPanel: 0x10bdc3710>,
google一下即可發(fā)現(xiàn)WKFileUploadPanel.mm源碼,后面需要用到奴饮。

0x02 置換回調(diào)方法

根據(jù)文檔纬向,和對(duì)mediaInfo的打印結(jié)果確認(rèn),只有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源碼進(jìn)行swizzling.
先貼出swizzle后的方法實(shí)現(xiàn)

- (void)swizzled_imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info{
    NSMutableDictionary *dict = [[NSMutableDictionary alloc]initWithDictionary:info];
    //UIImagePickerControllerReferenceURL設(shè)為空是關(guān)鍵一步。
    [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的實(shí)現(xiàn)

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

    NSString *documentDirectory = [documentDirectories objectAtIndex:0];

    return [documentDirectory stringByAppendingPathComponent:fileName];
}

[NSUUID shortUUIDString] 是來源于UUIDShortener的擴(kuò)展方法

對(duì)置換方法的解釋

在結(jié)合mediaInfo從源碼尋找的過程中發(fā)現(xiàn)了突破口叉瘩,實(shí)際上在發(fā)現(xiàn)這個(gè)方法之前我都不確定是否可以實(shí)現(xiàn)對(duì)圖片的置換。

因?yàn)檫@個(gè)方法之前粘捎,我單純嘗試性地修改了mediaInfo的里相關(guān)的后綴和圖片格式薇缅。但這樣都無法把圖片上傳給webview危彩。

所以必須再深入一點(diǎn)去了解源碼,確認(rèn)是否可行泳桦。

下面是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];
}

上面這些代碼汤徽,主要關(guān)注一下幾個(gè)部分

  1. [info objectForKey:UIImagePickerControllerMediaURL]是無需修改的
  2. [info objectForKey:UIImagePickerControllerOriginalImage]不能為空,而且需要修改為轉(zhuǎn)換后的圖片灸撰。
  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];

這里有個(gè)比較巧妙的過程谒府,按注釋的意思是,相機(jī)是不會(huì)有asset URL,即referenceURL會(huì)為空浮毯,所以這里不需要傳asset URL完疫,直接傳圖片對(duì)象即可。

對(duì)于web頁面來說债蓝,只需要我的圖片就行了壳鹤。

回到自己寫的置換的方法中去:

[dict setValue:nil forKey:@"UIImagePickerControllerReferenceURL"];為什么是關(guān)鍵一步呢。

就是因?yàn)槲蚁M鸚KFileUploadPanel以為從相冊(cè)來的圖片也是從相機(jī)來的饰迹。

最后一步執(zhí)行了

[self swizzled_imagePickerController:picker didFinishPickingMediaWithInfo:dict];

這里就是調(diào)用原始的WKFileUploadPanel對(duì)UIImagePickerDelegate的實(shí)現(xiàn)了芳誓,被修改后的mediaInfo被傳給webview,實(shí)現(xiàn)了置換啊鸭。

其實(shí)還有個(gè)方法就是考慮下能不能攔截input標(biāo)簽 跳轉(zhuǎn)到自定義的頁面上锹淌。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市赠制,隨后出現(xiàn)的幾起案子赂摆,更是在濱河造成了極大的恐慌,老刑警劉巖憎妙,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件库正,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡厘唾,警方通過查閱死者的電腦和手機(jī)褥符,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來抚垃,“玉大人喷楣,你說我怎么就攤上這事『资鳎” “怎么了铣焊?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)罕伯。 經(jīng)常有香客問我曲伊,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任坟募,我火速辦了婚禮岛蚤,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘懈糯。我一直安慰自己涤妒,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布赚哗。 她就那樣靜靜地躺著她紫,像睡著了一般。 火紅的嫁衣襯著肌膚如雪屿储。 梳的紋絲不亂的頭發(fā)上贿讹,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音扩所,去河邊找鬼围详。 笑死,一個(gè)胖子當(dāng)著我的面吹牛祖屏,可吹牛的內(nèi)容都是我干的助赞。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼袁勺,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼雹食!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起期丰,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤群叶,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后钝荡,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體街立,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年埠通,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了赎离。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡端辱,死狀恐怖梁剔,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情舞蔽,我是刑警寧澤荣病,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站渗柿,受9級(jí)特大地震影響个盆,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一砾省、第九天 我趴在偏房一處隱蔽的房頂上張望鸡岗。 院中可真熱鬧,春花似錦编兄、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至悯嗓,卻和暖如春件舵,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背脯厨。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國(guó)打工铅祸, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人合武。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓临梗,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親稼跳。 傳聞我的和親對(duì)象是個(gè)殘疾皇子盟庞,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容

  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時(shí)...
    歐辰_OSR閱讀 29,321評(píng)論 8 265
  • 1、通過CocoaPods安裝項(xiàng)目名稱項(xiàng)目信息 AFNetworking網(wǎng)絡(luò)請(qǐng)求組件 FMDB本地?cái)?shù)據(jù)庫組件 SD...
    陽明先生_x閱讀 15,968評(píng)論 3 119
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,527評(píng)論 25 707
  • 前天晚上沒提前告訴彤兒汤善,我不回家睡覺了什猖,打電話的時(shí)候她已經(jīng)睡著了,第二天很早醒來红淡,問媽媽呢不狮?奶奶回:媽媽昨晚沒有回...
    甜心教主閱讀 85評(píng)論 0 0
  • 為什么我那么害怕沖突? 為什么接受不了不和諧的狀態(tài)在旱? 為什么早上會(huì)引發(fā)那么大的家庭糾紛和矛盾呢摇零? 為什么矛盾的發(fā)生...
    愛兒一米距閱讀 191評(píng)論 0 0