Change User Agent for UIWebView in iOS at run time - ios

I have an app in which I have different web views for different flows (which are being managed by different teams).
My problem is that I want to set different user agents for different flows, ie. User-Agent = "XYZ" for some flow and User-Agent = "ABC" for some other flow.
I have tried using the following code, after reading from some StackOverflow links, before I init the controller containing UIWebView, but I read somewhere that I cannot modify User-Agent this way.
- (void)setUserAgentForWebView
{
UIWebView *dummyWebView = [[UIWebView alloc] initWithFrame:CGRectZero];
NSString *secretAgent = [dummyWebView stringByEvaluatingJavaScriptFromString:#"navigator.userAgent"];
NSString *newUserAgent = [NSString stringWithFormat:#"%#/IPHONE_V3",secretAgent];
NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:newUserAgent, #"UserAgent", nil];
[[NSUserDefaults standardUserDefaults] registerDefaults:dictionary];
[[NSUserDefaults standardUserDefaults] synchronize];
}
I use this code to set different User-Agent for different flows according to my need. But it seems that User-Agent is not modified because I can't get desired results.
Kindly guide me on this.

Related

iOS Objective-C - Problem during the restore of WKWebView status using cookies

We are using WKWebViews in our native iPhone application, on a website that allows login and stores the session information in cookies.
We tried to properly mange web cookies but we have some problems when we kill and reopen the app.
Preamble: the app manages a web login toward two WKWebViews (let's call them WKW_A and WKW_B) in two different sections.
Use case 1: we open the app, the app loads WKW_A, and the user fill in the credentials to login on the website. At this point we stores the related cookies. The user then opens a section of the app loading WKW_B . Here we load the above mentioned cookies. What we see is that the website bring us to the expected screen without asking user credentials i.e. in WKW_B cookies are properly sent to the website.
Use case 2: we open the app, the app loads WKW_A, and the user fill in the credentials to login on the website. At this point the app stores the related cookies. The user kills the app.
The user opens the app again, goes directly to the WKW_B section . Here the app loads the cookies mentioned above. What we see is that the website is asking the user to login again. i.e. it looks like there is something wrong with cookies loaded by WKW_B and sent to the website.
Briefly, cookies management looks like it is working only when we don’t kill the app. We tested the flow and the cookies are stored and reloaded correctly when we kill and reopen the app.
Below some reference code describing the way we manage the store/loading of the Cookies.
a) We’ve added this line in the applicationDidBecomeActive of AppDelegate
[[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookieAcceptPolicy:NSHTTPCookieAcceptPolicyAlways];
b) This is the WKWebView loading function in the ViewController managing the WKW_A.
- (void)loadWebView {
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
[config setWebsiteDataStore:[WKWebsiteDataStore defaultDataStore]];
self.webView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:config];
[self.webView setCustomUserAgent:#"Mozilla/5.0 (iPhone; CPU iPhone OS 13_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/79.0.3945.73 Mobile/15E148 Safari/604.1"];
[self.webView setNavigationDelegate:self];
[self.webView evaluateJavaScript:#"window.open = function(open) { return function (url, name, features) { window.location.href = url; return window; }; } (window.open);" completionHandler:nil];
[self.webViewContainer addSubview:self.webView];
[self.webView loadRequest:[NSURLRequest requestWithURL:self.loginURL]];
}
c) This is the WKWebView loading function in the ViewController managing the WKW_B.
- (void)loadWebView {
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
[config setWebsiteDataStore:[WKWebsiteDataStore defaultDataStore]];
self.webView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:config];
[self.webView setCustomUserAgent:#"Mozilla/5.0 (iPhone; CPU iPhone OS 13_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/79.0.3945.73 Mobile/15E148 Safari/604.1"];
[self.webView setNavigationDelegate:self];
[self.webView evaluateJavaScript:#"window.open = function(open) { return function (url, name, features) { window.location.href = url; return window; }; } (window.open);" completionHandler:nil];
[self.webViewContainer addSubview:self.webView];
NSMutableURLRequest *sRequest = [NSMutableURLRequest requestWithURL:self.loginURL];
[WKWebsiteDataStore.defaultDataStore.httpCookieStore getAllCookies:^(NSArray<NSHTTPCookie *> *aCookies) {
NSDictionary *sCookieHeaderFields = [NSHTTPCookie requestHeaderFieldsWithCookies:aCookies];
[sRequest setAllHTTPHeaderFields:sCookieHeaderFields];
NSData *data = [[NSUserDefaults standardUserDefaults] objectForKey:#"cookie_array"];
NSArray *myArrayCookies = [NSKeyedUnarchiver unarchiveObjectWithData:data];
for (int i=0;i< myArrayCookies.count;i++) {
[WKWebsiteDataStore.defaultDataStore.httpCookieStore setCookie:[myArrayCookies objectAtIndex:i] completionHandler:^{
}];
}
[sRequest setAllHTTPHeaderFields:[NSHTTPCookie requestHeaderFieldsWithCookies:myArrayCookies]];
[self.webView loadRequest:sRequest];
}];
}
d) This is the function to load the cookies inside the decidePolicyForNavigationResponse function in the ViewController managing the WKW_B
[WKWebsiteDataStore.defaultDataStore.httpCookieStore getAllCookies:^(NSArray<NSHTTPCookie *> *aCookies) {
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:aCookies];
[[NSUserDefaults standardUserDefaults] setObject:data forKey:#"cookie_array"];
[[NSUserDefaults standardUserDefaults] synchronize];
for (int i=0;i< aCookies.count;i++) {
[WKWebsiteDataStore.defaultDataStore.httpCookieStore setCookie:[aCookies objectAtIndex:i] completionHandler:^{
}];
}
}];
e) This is the function that we put in the applicationDidEnterBackground function of AppDelegate to store the cookies before kill the app
[WKWebsiteDataStore.defaultDataStore.httpCookieStore getAllCookies:^(NSArray<NSHTTPCookie *> *aCookies) {
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:aCookies];
[[NSUserDefaults standardUserDefaults] setObject:data forKey:#"cookie_array"];
[[NSUserDefaults standardUserDefaults] synchronize];
}];
We think that could be a problem of something behind the WKWebView configuration that can be lost when we kill the app.
We were thinking also to store directly the WKWebView in the UserDefaults instead of the list of cookies but it is impossible.
How could we do to store the WKWebView status for re-uploading after the reopen of the app?

IOS get installed Apps [duplicate]

Is it possible to programatically find out name of all apps installed on my iOS device ?
Is there any API available for same ?
Thanks for the help
No, on iOS applications has no access to information of/about other applications due to sandboxed environment.
Yes it is possible to get list of all installed app
-(void) allInstalledApp
{
NSDictionary *cacheDict;
NSDictionary *user;
static NSString *const cacheFileName = #"com.apple.mobile.installation.plist";
NSString *relativeCachePath = [[#"Library" stringByAppendingPathComponent: #"Caches"] stringByAppendingPathComponent: cacheFileName];
NSString *path = [[NSHomeDirectory() stringByAppendingPathComponent: #"../.."] stringByAppendingPathComponent: relativeCachePath];
cacheDict = [NSDictionary dictionaryWithContentsOfFile: path];
user = [cacheDict objectForKey: #"User"];
NSDictionary *systemApp=[cacheDict objectForKey:#"System"];
}
systemApp Dictionary contains the list of all system related app
and user Dictionary contains other app information.
Not from the device. However, from the desktop you could peek into the iTunes library.
There are ways to do this without a jailbroken device and not get your app rejected.
1. get a list of currently running processes see this SO answer. You will need to translate from process name to app name.
2. Check to see if any apps have registered a unique URL scheme with UIApplicationDelegate canOpenURL. There are a few sites cataloging known url schemes, this is the best one.
If an app is not currently running and does not register a custom url scheme then it will not be detected by these methods. I am interested in hearing a method that will be allowed in the app store that works better than this.
try this, it will work even with non-jailbroken devices:
#include <objc/runtime.h>
Class LSApplicationWorkspace_class = objc_getClass("LSApplicationWorkspace");
SEL selector=NSSelectorFromString(#"defaultWorkspace");
NSObject* workspace = [LSApplicationWorkspace_class performSelector:selector];
SEL selectorALL = NSSelectorFromString(#"allApplications");
NSLog(#"apps: %#", [workspace performSelector:selectorALL]);//will give you all **Bundle IDS** of user's all installed apps
You can do it by checking whether an application is installed or not by using canOpenURL method or by checking the background processes and matching them with the name of the app you are interested in.
You can use runtime objective c to get the list of all installed apps. It will give you an array of LSApplicationProxy objects.
Following is a code snippet that prints Name of all applications installed in your device.
Class LSApplicationWorkspace_class = objc_getClass("LSApplicationWorkspace");
NSObject* workspace = [LSApplicationWorkspace_class performSelector:NSSelectorFromString(#"defaultWorkspace")];
NSMutableArray *array = [workspace performSelector:NSSelectorFromString(#"allApplications")];
NSMutableArray *mutableArray = [[NSMutableArray alloc] init];
for (id lsApplicationProxy in array) {
if(nil != [lsApplicationProxy performSelector:NSSelectorFromString(#"itemName")]){
[mutableArray addObject:[lsApplicationProxy performSelector:NSSelectorFromString(#"itemName")]];
}
}
NSLog(#"********* Applications List ************* : \n %#",mutableArray);
Don't forget to include <objc/runtime.h> .

iOS - sending URL through mail showing different behavior in simulator and device

A strange behaviour is happening when I am trying to send mail from device.
I have used SMTP to send mail from background in my app and I have to send user's current location URL in app.
Now, when I send it from simulator it works almost fine and i got this url -
https://maps.google.com/maps?f=q&q=0.000000,0.000000
But, when I send it from device it sends the url like this -
https://maps.google.com/maps?f=q&q".719793,75.877068
The code I used to make url is
-(NSString*)LocationLinkTosentInMail
{
CLLocationCoordinate2D coordinate = [self getLocation];
NSString *latitude = [NSString stringWithFormat:#"%f", coordinate.latitude];
NSString *longitude = [NSString stringWithFormat:#"%f", coordinate.longitude];
NSLog(#"*dLatitude : %#", latitude);
NSLog(#"*dLongitude : %#",longitude);
NSString *currentLocationURL = [NSString stringWithFormat:#"https://maps.google.com/maps?f=q&q=%#,%#",latitude,longitude];
return currentLocationURL;
}
NSURL *locationURL = [NSURL URLWithString:[NSString stringWithFormat:#"%#",[self LocationLinkTosentInMail]]];
In SMTP mail function, I use this code to make dictionary
NSDictionary *plain_text_part = [NSDictionary dictionaryWithObjectsAndKeys:
#"text/plain\r\n\tcharset=UTF-8;\r\n\tformat=flowed", kSKPSMTPPartContentTypeKey,
[NSString stringWithFormat:#"My Location is: %#",locationURL], kSKPSMTPPartMessageKey,
#"quoted-printable", kSKPSMTPPartContentTransferEncodingKey,
nil];
Can anyone suggest me if any change required in code?
Why simulator and device are showing different behavior to send this url?
Use HTML Tag and in mail body enable HTML in iOS
[emailDialog setMessageBody:mailBody isHTML:YES];
Check this page for href tag!
Whenever we have to send URL thourgh smtp in objective c, we should always careful about the special character of url.
Finally i got the solution of my problem in the encoding process.
the Encoding in the NSDictionary object, I changed it from quoted-printable
to 8bit .
Now dictionary obj would be:
NSDictionary *plain_text_part = [NSDictionary dictionaryWithObjectsAndKeys:
#"text/plain", kSKPSMTPPartContentTypeKey,
strMessage, kSKPSMTPPartMessageKey,
#"8bit", kSKPSMTPPartContentTransferEncodingKey,
nil];

UIWebView displays blank page on form submit

I'm an iOS newb (.NET professional), so this may be a simple issue but I couldn't find anything through the SO search or Google (and maybe not looking for the right terms).
I'm writing an app that displays information from a DD-WRT router through it's web interface. I have no problem displaying the initial page and navigating through any of the other pages, but if I make any change on a form (and it redirects to apply.cgi or applyuser.cgi), the UIWebView is blank - it's supposed to display the same page, with the form submission changes. The site works fine in Mobile Safari, which I find intriguing, but I guess UIWebView isn't totally the same.
I think the iOS code is pretty standard for display a webpage, but I'll list it below. I can't give you access to my router because, well, that's not a good idea :) Hopefully someone with a DD-WRT router can help (or know what my issue is anyway).
- (void)viewDidLoad
{
[super viewDidLoad];
NSString *sURL = #"http://user:pass#XXX.XXX.X.X";
NSURL *url = [NSURL URLWithString:sURL];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[self.webView loadRequest:request];
self.webView.delegate = self ;
}
And I'm doing a few things with Javascript in the webViewDidFinishLoad method, but I know that's not the culprit because it still happens when I comment it out.
Well I figured out the problem on my own. I think part of it was putting the username & password in the URL (which was just a temporary measure) because I found that method provided the same results in mobile Safari and desktop Chrome.
So I added MKNetworkKit to my project that provided a simple way to add authentication to my request, and found I had to make a specific request to POST the data, then reloaded the page the to see the changes.
In the (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType method, I check if ([request.HTTPMethod isEqualToString:#"POST"]) and do this:
NSString *sPostData = [[NSString alloc] initWithData:request.HTTPBody encoding:NSUTF8StringEncoding];
NSArray *aPostData = [sPostData componentsSeparatedByString:#"&"];
NSMutableDictionary *dPostData = [[NSMutableDictionary alloc] init];
//i don't know if this is the best way to set a dictionary, but it works
for (id apd in aPostData)
{
NSString *key = [apd componentsSeparatedByString:#"="][0];
NSString *val = [apd componentsSeparatedByString:#"="][1];
[dPostData setValue:val forKey:key];
}
MKNetworkEngine *engine = [[MKNetworkEngine alloc] init];
MKNetworkOperation *op = [engine operationWithURLString:[request.URL description] params:dPostData httpMethod:#"POST"];
[op setUsername:#"myUserName" password:#"myPassword" basicAuth:YES];
self.postedRequest = TRUE; //a bool I set so, when it comes to webViewDidFinishLoad, I reload the current page
[op start]; //send POST operation

Change User Agent in UIWebView

I have a business need to be able to customize the UserAgent for an embedded UIWebView. (For instance, I'd like the server to respond differently if, say, a user is using one version of the app versus another.)
Is it possible to customize the UserAgent in the existing UIWebView control the way it is, say, for an embedded IE browser in a Windows app?
Modern Swift
Here's a suggestion for Swift 3+ projects from StackOverflow users PassKit and Kheldar:
UserDefaults.standard.register(defaults: ["UserAgent" : "Custom Agent"])
Source: https://stackoverflow.com/a/27330998/128579
Earlier Objective-C Answer
With iOS 5 changes, I recommend the following approach, originally from this StackOverflow question: UIWebView iOS5 changing user-agent as pointed out in an answer below. In comments on that page, it appears to work in 4.3 and earlier also.
Change the "UserAgent" default value by running this code once when
your app starts:
NSDictionary *dictionary = #{#"UserAgent": #"Your user agent"};
[[NSUserDefaults standardUserDefaults] registerDefaults:dictionary];
[[NSUserDefaults standardUserDefaults] synchronize];
See previous edits on this post if you need methods that work in versions of iOS before 4.3/5.0. Note that because of the extensive edits, the following comments / other answers on this page may not make sense. This is a four year old question, after all. ;-)
I had this problem too, and tried all methods. I found that only this method works (iOS 5.x):
UIWebView iOS5 changing user-agent
The principle is to set the user agent permanently in the user settings. This works; Webview sends the given header. Just two lines of code:
NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:#"Mozilla/Whatever version 913.6.beta", #"UserAgent", nil];
[[NSUserDefaults standardUserDefaults] registerDefaults:dictionary];
Setting User-Agent, or User_Agent in the mutable request, or overriding the setValue in the NSHttpRequest by swizzling, - I tried all that and controlled the results with wireshark, and none of that seems to work, because Webview still uses the user agent value from the user defaults, no matter what you try to set in the NSHttpRequest.
It should work with an NSMutableURLRequest as Kuso has written.
NSMutableURLRequest *urlRequest = [[NSMutableURLRequest alloc] initWithURL: [NSURL URLWithString: #"http://www.google.com/"]];
[urlRequest setValue: #"iPhone" forHTTPHeaderField: #"User-Agent"]; // Or any other User-Agent value.
You'll have to use NSURLConnection to get the responseData. Set the responseData to your UIWebView and the webView should render:
[webView loadData:(NSData *)data MIMEType:(NSString *)MIMEType textEncodingName:(NSString *)encodingName baseURL:(NSURL *)baseURL];
Very simple in Swift. Just place the following into your App Delegate.
UserDefaults.standard.register(defaults: ["UserAgent" : "Custom Agent"])
If you want to append to the existing agent string then:
let userAgent = UIWebView().stringByEvaluatingJavaScript(from: "navigator.userAgent")! + " Custom Agent"
UserDefaults.standard.register(defaults: ["UserAgent" : userAgent])
Note: You may will need to uninstall and reinstall the App to avoid appending to the existing agent string.
Actually adding any header field to the NSURLRequest argument in shouldStartLoadWithRequest seems to work, because the request responds to setValue:ForHTTPHeaderField - but it doesn't actually work - the request is sent out without the header.
So I used this workaround in shouldStartLoadWithRequest which just copies the given request to a new mutable request, and re-loads it. This does in fact modify the header which is sent out.
if ( [request valueForHTTPHeaderField:#"MyUserAgent"] == nil )
{
NSMutableURLRequest *modRequest = [request mutableCopyWithZone:NULL];
[modRequest setValue:#"myagent" forHTTPHeaderField:#"MyUserAgent"];
[webViewArgument loadRequest:modRequest];
return NO;
}
Unfortunately, this still doesn't allow overriding the user-agent http header, which is apparently overwritten by Apple. I guess for overriding it you would have to manage a NSURLConnection by yourself.
Using #"User_Agent" simply causes a custom header to appear in the GET request.
User_agent: Foobar/1.0\r\nUser-Agent: Mozilla/5.0 (iPhone; U; CPU
iPhone OS 3_1_2 like Mac OS X; en-us) AppleWebKit/528.18 (KHTML, like
Gecko) Mobile/7D11\r\n
The above is what appears in the dissected HTTP packet, essentially confirming what Sfjava was quoting from that forum. It's interesting to note that "User-Agent" gets turned into "User_agent."
Taking everything this is how it was solved for me:
- (void)viewDidLoad {
NSURL *url = [NSURL URLWithString: #"http://www.amazon.com"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setValue:#"Foobar/1.0" forHTTPHeaderField:#"User-Agent"];
[webView loadRequest:request];
}
Thanks Everyone.
By pooling the answer by Louis St-Amour and the NSUserDefaults+UnRegisterDefaults category from this question/answer, you can use the following methods to start and stop user-agent spoofing at any time while your app is running:
#define kUserAgentKey #"UserAgent"
- (void)startSpoofingUserAgent:(NSString *)userAgent {
[[NSUserDefaults standardUserDefaults] registerDefaults:#{ kUserAgentKey : userAgent }];
}
- (void)stopSpoofingUserAgent {
[[NSUserDefaults standardUserDefaults] unregisterDefaultForKey:kUserAgentKey];
}
This solution seems to have been seen as a pretty clever way to do it
changing-the-headers-for-uiwebkit-http-requests
It uses Method Swizzling and you can learn more about it on the CocoaDev page
Give it a look !
I faced the same question. I want to add some info to the user-agent, also need to keep the original user-agent of webview. I solved it by using the code below:
//get the original user-agent of webview
UIWebView *webView = [[UIWebView alloc] initWithFrame:CGRectZero];
NSString *oldAgent = [webView stringByEvaluatingJavaScriptFromString:#"navigator.userAgent"];
NSLog(#"old agent :%#", oldAgent);
//add my info to the new agent
NSString *newAgent = [oldAgent stringByAppendingString:#" Jiecao/2.4.7 ch_appstore"];
NSLog(#"new agent :%#", newAgent);
//regist the new agent
NSDictionary *dictionnary = [[NSDictionary alloc] initWithObjectsAndKeys:newAgent, #"UserAgent", nil];
[[NSUserDefaults standardUserDefaults] registerDefaults:dictionnary];
Use it before you instancing webview.
Try this in the AppDelegate.m
+ (void)initialize
{
// Set user agent (the only problem is that we can’t modify the User-Agent later in the program)
// iOS 5.1
NSDictionary *dictionnary = [[NSDictionary alloc] initWithObjectsAndKeys:#”Mozilla/5.0 (iPad; CPU OS 5_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9B176 Safari/7534.48.3”, #”UserAgent”, nil];
[[NSUserDefaults standardUserDefaults] registerDefaults:dictionnary];
}
The only problem I have found was change user agent only
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSDictionary *dictionary = [NSDictionary
dictionaryWithObjectsAndKeys:
#"Mozilla/5.0 (iPod; U; CPU iPhone OS 4_3_3 like Mac OS X; ja-jp) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8J2 Safari/6533.18.5",
#"UserAgent", nil];
[[NSUserDefaults standardUserDefaults] registerDefaults:dictionary];
}
Apple will soon stop accepting apps with UIWebView. Find below for how you could change the user agent in WKWebView.
let config = WKWebViewConfiguration()
config.applicationNameForUserAgent = "My iOS app"
webView = WKWebView(frame: <the frame you need>, configuration: config)
To just add a custom content to the current UserAgent value, do the following:
1 - Get the user agent value from a NEW WEBVIEW
2 - Append the custom content to it
3 - Save the new value in a dictionary with the key UserAgent
4 - Save the dictionary in standardUserDefaults.
See the exemple below:
NSString *userAgentP1 = [[[UIWebView alloc] init] stringByEvaluatingJavaScriptFromString:#"navigator.userAgent"];
NSString *userAgentP2 = #"My_custom_value";
NSString *userAgent = [NSString stringWithFormat:#"%# %#", userAgentP1, userAgentP2];
NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:userAgent, #"UserAgent", nil];
[[NSUserDefaults standardUserDefaults] registerDefaults:dictionary];

Resources