Skip to content


iCab Mobile/UIWebView and implementing support for file uploads

Many web pages provide a form where users can upload photos, videos and other files, like for example on the photo community flickr.com, on social networks like facebook.com or video platforms like DailyMotion.com, on email services where you upload files to use them as attachments for emails, etc. But when visiting these sites on the iOS in Safari or in other iOS browsers, you won’t be able to upload any files. The button to select the file is disabled and not working. The reason is that the web-engine of the iOS (and Apple does not allow to use another web engine than the one that is built-in in the iOS) does not support file uploads, and the API of the web engine (UIWebView) is also extremely limited and has nothing that can be used to add support for file uploads (it seems ;-) ).

I found this situation not very satisfying. So I tried to find a way to add support for file uploads in iCab Mobile, and had some success. So iCab Mobile seems to be the only iOS Browser for iPad and iPhone which supports file uploads at the moment.

Note: With the upcoming iOS 6 release later this year, the web engine of the iOS will also add support for file uploads, though only for photos or videos. But of course if you need to upload other files than photos or videos from the photo album, or if iOS 6 cannot be installed on your device (the most important device which won’t get iOS 6 will be the 1st iPad model), you still need iCab Mobile to be able to upload files.

This blog post will explain how iCab Mobile implements the file upload support, lists all the limitations resulting from this implementation and which are caused by the iOS itself. This post will also give web authors all the information they need to make sure that file upload forms on their web pages do work with iCab Mobile and what they can do to make the user experience even better.

HTML and file uploads

In HTML, file uploads are done using standard HTML forms. These forms must use a certain encoding method for the form data (“multipart/form-data” instead of the usual default “application/x-www-form-urlencoded“), and the INPUT element with the TYPE attribute of “file” must be used to add a button to let the user select the file for the upload.

A simple form with file upload capability would look like this:

<form action="url" method="post" enctype="multipart/form-data">
	<input type="file" name="file"> 
	<input type="submit" value="Upload">
</form>

The user can click on the “file” button of the form and the browser presents a file selector where the user can select the file for the upload. When submitting the form, the browser would include the data of the file into the HTTP request and post it to the server.

The iOS web engine

In the web engine of the iOS there are several things which do not work. First of all the “file” button will be inactive and greyed out. So there’s no way to select a file in the first place. The web engine also isn’t able to add the file data to the HTTP request. This means iCab Mobile has to find a way to enable the “file” button so the user can select files or photos for the upload, and it has to include the data of the selected file into the HTTP request of the form.

iCab Mobile implementation of file uploads

You can’t get the “file” button working in the web engine of the iOS, the OS is not prepared for this. So the only option is to replace the “file” buttons by normal push buttons. In HTML these are defined this way:

<input type="button">

which means the type is “button” instead of “file”. iCab is using the method stringByEvaluatingJavaScriptFromString: of UIWebView to execute JavaScript code, which searches for “file” buttons and converts these into push buttons by changing the “type” attribute, and enables these buttons. For these push buttons iCab Mobile will also define an “onclick” handler, so whenever the button is clicked, the “onclick” handler will assign a special custom URL to location.href. In the delegate method webView:shouldStartLoadWithRequest:navigationType: of UIWebView iCab checks for this special URL and can then present the window to select a file or photo for the upload. After a file is selected, iCab executes some JavaScript code which adds a new hidden “input” element into the form whose “name” and “value” attributes will be set to some meta information about the selected file. This additional input element is required to store the information about the selected file in the form. It is necessary to create additional “input” elements in the form, because this meta information of the file must be submitted with the rest of the form, and because  “push” buttons won’t be included in the form data  this meta data can not be added to these “push” (former “file”) buttons (you’ll find more about this below).

When the form is submitted, it is impossible to directly add the file data that needs to be uploaded to HTTP request that is sent to the server. The web engine does not support file uploads and therefore is not prepared to process any files and their data. So what iCab Mobile needs to do is to intercept the HTTP request which the web engine is building when submitting the form and to modify the request by adding the file data somehow. Intercepting the HTTP requests from UIWebView can be done by implementing your own HTTP protocol handler using the NSURLProtocol class. All HTTP requests which are created using the standard Cocoa network layer (NSURLConnection) will be then re-routed through this HTTP protocol handler.

What iCab is doing in the HTTP protocol handler is to first check if the method for the HTTP request is “POST” and the encoding type is “multipart/form-data”. Only when this is the case, this request could be a file upload and iCab Mobile needs to do something more. Now the additional hidden “input” elements which iCab has inserted in the form for the selected files before will be used to find out if the request should include any files for the upload. These input elements do have a unique identifier within the name/value attributes, so by searching for these identifiers iCab Mobile can find out if  and which files needs to be uploaded. The information of these special hidden “input” fields is then removed from the request and replaced by the data of the selected files. This modified request is then send to the server. The server will therefore receive a properly formatted HTTP request which includes the data of the selected file and which no longer includes the data of the additional “input” elements. The server gets the same request from iCab Mobile it would have received from a desktop browser which built-in file upload support.

Limitations (how web authors can make sure the upload works in iCab Mobile)

This implementation works fine for all plain-vanilla HTML forms with one or more “file” buttons.

But submitting plain-vanilla HTML forms and uploading larger files will block the user interaction with the web page  until the upload is finished. The page can only update itself when the upload is finished.

This is not very user-friendly, especially when uploading larger files, like videos, where the web page can be blocked for a long time (this is not an issue of iCab Mobile or the iOS platform, this is a general issue of pure HTML forms and affects all browsers on all platforms in the same way).

Therefore many web pages try to improve the user experience for file uploads. The upload process itself is still blocked until it is finished, but it is moved to something that is invisible on the page (like an iframe) or which can run independently in the background (AJAX), so as long as the user does not leave the page, the upload can be done without blocking the interaction with the main page.

Also many web pages try to check if the form is filled out properly before it submits the form data to the server. This helps to avoid resubmitting forms because certain fields were filled out out wrong.

In principle, this all can still work in iCab Mobile, and in many cases it also does work fine. But this can also fail, depending of how the web pages do the redirection of the form submission, or how the page is checking the form.

Here are the known limitations

Some web pages are using the “file reader” API to check meta data for the file (file sizes, file types etc), or read the file data. This won’t work on the iOS platform at the moment. Also the “file” input element has attributes like “file” or “files” to access meta data of the selected files, these attributes are also not accessible (their value is “null”). All this is related to the fact the the web engine does not support file uploads and therefore won’t need these file related features. So when a web page detects that it runs on the iOS platform, it should not rely on the “file reader” API or the “file”/”files” attributes. But is still possible to check the “value” of the (former “file”) push button to get the file name of the selected file, for example to check if the file has the expected file extension.

Another issue is that iCab has to convert the “file” buttons into push buttons to make them work. So web pages which look for “input” element of type “file” to check if a file is selected will fail. But if they look for the “name” or “id” of these former “file” input elements, the input elements can be found and everything can work just fine. This is because these input elements keep their name and id and also all other attributes, only their “type” attribute has changed.

If a web page accesses certain “input” elements by static index, it can fail. This is because iCab has to add additional “input” elements for selected files.

Also invalid HTML code can cause problems, especially when nesting form/input elements within table/tr/td elements where these elements are not allowed. This is because the browser has to correct this invalid nesting when it renders the code and creates a valid HTML/DOM tree. And for wrong nesting within tables, almost all browsers just move the wrongly placed element in front of the table. So for the invalid code like this…

<table>
  <form>
    <tr>
      <td>
        <input>	
      </td>
    </tr>
  </form>
</table>

the browser would create this valid HTML/DOM tree…

<form>
</form>
<table>
  <tr>
    <td>
      <input>	
    </td>
  </tr>
</table>

As you can see, the form element is now no longer a (grand-)parent of the input element, which means the relationship between input and form element is gone. Normally this is not a big deal, because while rendering the code, the browser keeps a reference to the last form element and therefore can link the input elements to its form element. But if iCab needs to add these special hidden input elements for the selected files,  the rendering is already finished, and so the web engine can’t link the new input element to the right form element anymore. This means when submitting the form, these special hidden input elements won’t be included. iCab tries to find the correct form element for these input elements manually, which works fine in many cases, especially if there’s only one form on the page, but it can fail if there are multiple forms. So as a web author please make sure that you write valid HTML code to avoid problems.

Also if the upload form is loaded in a frame or iframe that is coming from a different domain than the main document of the web page, iCab can not modify the form to enable the file buttons. This is because UIWebView only supports access to the web page via JavaScript through the main document, and the “same-origin” policy prevents access to documents coming from a different domain.

iCab Mobile will modify the upload forms to make them work when the web page has finished loading, after the “onload” handler has fired. But some web pages do create “file” input elements or whole forms only when needed and the user has clicked on a certain button. So the upload form is not yet available when iCab searches for these forms. This means the “file” buttons of the forms which are created later remain disabled. This problem could be solved by checking for newly created “file” input elements every second or so, but this could also be a big performance issue because searching the whole code of the web page can be expensive, especially on larger web pages. And most web pages do not even have file upload forms at all. So searching the page for “file” buttons all the time is not an option.

iCab will workaround this issue by letting the user hold down a finger for a longer time (about a second) on a disabled “file” button. This will force iCab to search and replace all “file” buttons again, so the user can then select files for the upload. This is not the best user experience, because the requirement to activate the “file” button first is nothing a user is used to do. But without the help of the web page, there’s no better way to solve this.

Web sites can give iCab hints about dynamically created INPUT elements

Update May 2013
This paragraph is still valid, but it is usually no longer necessary that web sites need to help iCab. iCab Mobile will now monitor the creation of new HTML elements and if new INPUT elements are created it will automatically check if these are file elements and therefore can convert these automatically. There might be still some rare cases where iCab fails to recognize new elements, in these cases the site can still help out like suggested in the following paragraph.

Since iCab Mobile 6.0.1 there is a way web pages can help iCab Mobile to automatically activate dynamically created “file” input elements, so the user does no longer to do this manually.

This works in the following way:

The web page needs to set a global JavaScript variable named iCabMobile_FileFieldActive and set it to 1 if (and only if) the web page can create and insert a “file” input element into a form via JavaScript after the “onload” handler has fired. If the “file” buttons are all created and inserted into the forms before the “onload” handler fires or created and added within the “onload” handler or created statically in the HTML code, this variable should not be defined at all.

  iCabMobile_FileFieldActive = 1;

If this variable is 1 when iCab first checks the web page (after the “onload” handler was executed), iCab will set this variable to 0 and will start observing the value of this variable until the user loads a new page. The web page should now set the variable to 1 each time it has created a “file” input element and added it to a form. iCab Mobile will search for all newly created “file” buttons as soon as the value of this variable was set to 1 again (and only if the value is 1). This way it is possible to activate all the “file” buttons automatically, without any performance issues. It is super-easy for web developers to add this variable and this should not have any side effects at all.

There’s even the first add-on available for the concrete 5 CMS which provides an file uploader and which supports this global variable to improve the user experience in iCab Mobile.

Posted in iCab, iPhone & iPod Touch, Programming, Web Technology.

Tagged with , , , , .


26 Responses

Stay in touch with the conversation, subscribe to the RSS feed for comments on this post.

  1. Johnny says

    Thank you for your great explanation. I have a question about “assign a special custom URL to location.href”. What do you mean for this?

  2. Alexander Alexander says

    @Johnny
    Because the web engine of the iOS can not deal with file uploads, it can not be used to select a file for the upload, so when the user clicks the “Select file” button, control must be handed over from the web page to the “Cocoa” part of the App. And the only way to do this is to assign a URL to location.href, which will end up in calling a delegate method of the UIWebView object (which is the Objective-C object which contains an instance of the web engine). Of course you don’t want to open a new page in the web engine, this is why you assign a special custom URL which you can recognize when the delegate method is called. In this delegate method is checked if a “normal” Url or this special URL is opened, when it’s the latter, opening the URL is canceled and iCab presents the user a window where the file or photo for the upload can be selected.

  3. Johnny says

    Thanks for your reply. I did some researches I found what you meant already. Again thank you for your sharing.

  4. Ankit says

    Hi Alexander, would you mind share how did you implement your own HTTP handler using NSURLProtocol class? I need to modify the submitting form before it reach to the server. Thank you.

  5. Ankit says

    I have tried like this

    //check to see if its a form
    if ([request.HTTPMethod isEqualToString:@"POST"] && [[methodType objectAtIndex:0] isEqualToString:@"multipart/form-data"]) {
    
            UIImage *postImg = [UIImage imageWithContentsOfFile:SelImg];
            NSString *boundary = [NSString stringWithString:@"----WebKitFormBoundaryvEvBRMI2Zj9rIUJi"];
            NSData *imageData = UIImageJPEGRepresentation(postImg, 1.0);
            if (!imageData) return NO;
            
            NSMutableURLRequest *newRequest = [[NSMutableURLRequest alloc] init];
            [newRequest setURL:[NSURL URLWithString:[[webView request]URL].absoluteString]];
            [newRequest setHTTPMethod:@"POST"];
            [newRequest setAllHTTPHeaderFields:request.allHTTPHeaderFields];
            
            NSMutableData *body = [NSMutableData data];
            [body appendData:[[NSString stringWithFormat:@"\r\n--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
            [body appendData:[[NSString stringWithString:@"Content-Disposition: form-data; name=\"userfile\"; filename=\"IMG_0310.JPG\"\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
            [body appendData:[[NSString stringWithString:@"Content-Type: application/octet-stream\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
            [body appendData:[NSData dataWithData:imageData]];
            [body appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
            [newRequest setHTTPBody:body];
            
            NSData *returnData = [NSURLConnection  sendSynchronousRequest:request returningResponse:nil error:nil];
            NSString *returnString = [[NSString alloc] initWithData:returnData encoding:NSUTF8StringEncoding];
            NSLog(@"Return %@",returnString);
    
  6. Alexander Alexander says

    @Ankit
    Basically when implementing your own protocol handler, you just need to implement the required methods of the NSURLProtocol class. The header file of this class tells you which one are required.

    Your own protocol handler will be asked for all requests that are done via NSURLConnection if it will process this request. If it answers NO, then the standard handler will take over. You can even use NSURLConnections within your own handler to process the requests, but in this case your handler will be asked if it can process your own internal request as well, and to prevent a recursion you need to return NO here. Your internal requests needs to be processed by the standard HTTP protocol handler, but this is fine, because within your protocol handler you are able to modify or change the original request before sending it to the internet via your new internal NSURLConnection.

    Using NSURLConnection within your protocol handler has the big advantage, that for almost all of the “callback” methods of the NSURLProtocol class there’s a corresponding delegate of the NSURLConnection class. So most of the delegate methods of NSURLConnection just look similar to this one (the parameters of the delegate method are passed to the protocol handler object):

    - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
    {
    	[[self client] URLProtocol:self didLoadData:data];
    }
    

    The only methods where you really need to do something more would be

    - (void)startLoading
    

    where you actually create a new connection for your (modified) request , and

    - (void)stopLoading 
    

    where you need to stop the connection, and

    + (BOOL)canInitWithRequest:(NSURLRequest *)request
    

    Where you have to decide if you want to take over this request (in case it’s your form you need to modify), or if the standard handler needs to process it (it’s another request or its one of your internal requests where you’ve already modified the original requests).

    According to your code: you should avoid using synchronous connections. Using the NSURLProtocol these are not needed.

  7. Ankit says

    Thank you for your explanation. But Im still confusing that what methods that I need to implement? And where/when I actually modify the request? Right now, in the delegate method webView:shouldStartLoadWithRequest:navigationType: I have the code above, but seem like it doesn’t work correctly. Please assist me with this matter. Really appreciated your help.

  8. Alexander Alexander says

    @Ankit
    Please read the header foe of the NSURLProtocol class, where you can see which methods you must implement.

    Basically the implementation of your own HTTP protocol handler is a global thing to your App, so it is completely independent of UIWebView. UIWebView will internally load all the data from the internet using the standard networking routines of Cocoa and all these are using NSURLProtocol to find and process the networking stuff. So you do not need to do anything within the UIWebView delegates or methods. Just install your own HTTP protocol handler and it will be automatically called.

    I’ll probably write a blog post about this topic soon. This is a little bit to big for the comments.

  9. Rahaman says

    Hi Alexander,
    I am particularly interested in this file upload feature & while playing with a uploading site e.g. http://www.sendspace.com, I am encountering two issues regarding multiple file download

    – For multiple file download, after I select 2 files, if I want to select 3rd file, it is not openning the “Downloads” folder to select my own file, rather it is only openning the default photo library
    – Even after 2 file selected, if I do submit, after submit, I can only get download link for the 1st file from the server, which means only 1st of the 2 files selected got uploaded successfully

    Is this a known behavior? Can you please throw some light on this?

  10. Alexander Alexander says

    @Rahaman
    I’ll check this. The problem when you can only select from the photo album is a known issue. This happens when the web site creates the “file select” button on-the-fly after iCab has replaced the original file buttons. So this new buttons is unknown to iCab and therefore will be processed by the iOS itself. This is the reason for the proposal with the variable “iCabMobile_FileFieldActive” from the blog post, which can give icab a hint that it needs to search for new file buttons again.

  11. Sascha says

    Is it possible, to provide a small example for fileuploads, that will work with icab mobile. I´ve a very small page, where I need a feature to upload a file to a special directory, but I´m not able to do this correctly, so that I can use the form from my ipad.

  12. Alexander Alexander says

    @Sascha
    “Real” web sites where you can upload files are Flickr and Facebook for example. When writing your own HTML code, a simple standard HTML form would work as well:

    <form action="url" method="post" enctype="multipart/form-data">
       <input name="name" type="file">
       <input value="Upload" type="submit">
    </form>
    

    iCab also supports many of the more advanced JS tricks to make upload forms more comfortable to use (like for example on the Facebook page). But there are a few things which do not work because of limitations of the iOS (like the “File reader” API), so this should be avoided in your own HTML code when the form should work on the iOS platform as well.

  13. Sascha says

    I don’t know, what I’m doing wring. If I use your code, I’m not able to find the file, I tried to upload. I also copied some examples, I found by google, but nothing seems to work. What is my fault?

    My target is to run a local instance of runalyze, withc can bei downloaded at www. Runalyze.de

  14. Alexander Alexander says

    @Sascha
    What exactly are you trying to do? The upload forms of the Runalyze size isn’t really compatible to the iOS. This is one of the sites which seems to use the file reader API which is not working on the iOS. And often the “upload” buttons would only shown on “mouse hover” events (move the ouse over a certain arteas without clicking), which are impossible to create on a touch screen (you have to click to trigger an action). There are also some other sthings this page is doing which is not really working well on a mobile device.

  15. Sascha says

    Because the default runalyze doesen´t work, I tried to create a workaround for me. So I createted a form (copied from http://www.selfphp.info with the folowing code:

    Datei:

    To move the file to an upload folder, I created the folowing script (upload.php):

    But after all, where is no file within the ipad/upload folder and I don´t know, what I´m doing wrong.

  16. Sascha says

    Hier noch einmal die PHP-Datei:
    “<?php
    f (isset($_FILES["datei"])) {

    $_FILES["datei"]["size"] “

  17. Alexander Alexander says

    @Sascha
    What do you mean by the “iPad Upload folder”? And how exactly does your form look like?

  18. Sascha says

    Is it possible, to send you both files (form and upload.php) for example via email or something else?

  19. Alexander Alexander says

    @Sascha
    Yes, just send it to alexander@icab.de

  20. Sascha says

    I´ve send the files via email

  21. gaurav says

    Hi Alex, I am trying to upload a file using icab mobile browser but not able to do so. Would u be able to please advise.

    I am able to select the file (.doc,.pdf etc. ) and press upload but it’s is taking too much of time and after that it automatically time out.

  22. Alexander Alexander says

    @gaurav

    The upload feature can not work on all web sites. Some web sites are doing things which can not work on the iOS platform. There are several restrictions on the IOS platform.

    I don’t know with which page you’re experiencing these issues, but I guess it relies on those things, which do not work on the iOS platform (like for example using the JavaScript File API).

  23. Prasad says

    I am not able to upload my resume either in dice or internship .. Will it support ?

  24. Alexander Alexander says

    @Prasad
    I don’t know internship (internship.com seems to be not an existing web site), but “dice” should work with iCab Mobile. But you need to switch to the “desktop” version of the site, because the mobile version of dice seems to be broken. Also you need to configure the option “Upload via” in the general settings of iCab Mobile to “iCab Mobile”

  25. Olayinka Adeleke says

    I have been trying to upload my resume to job sites to no avail . I installed the icabmobile but it’s still the same thing, It just brings out videos or phtotos pls help.

  26. Alexander Alexander says

    @Olayinka Adeleke
    Please read the FAQ of iCab Mobile. There’s a section about „uploading files“ which explains how to configure iCab to upload other files than just photos.



Some HTML is OK

or, reply to this post via trackback.