URL encoding iOS NSURL error - ios

URL which opens in Firefox,Chrome browsers on desktop, doesn't open in WebView on iPhone.
This URL is supposedly accessing a GET request.
When creating the NSURL without percentescaping the url doesn't get generated.
When using percentescape the url redirects to a Bad url content.
Is there a different encoding used on desktop browsers and not on the iPhone? or mobile Safari?
Are there different ways to encode the URL in iOS other than using
-stringByAddingPercentEscapesUsingEncoding
-CFURLCreateStringByAddingPercentEscapes
which generates bad request content pages from server.
Any help would be really great, Thanks.
EDIT:
The URL been generated is as below http://something.test.com/iostest/index.html?{"a":"b"}
Managed to figure that not encoding the curly brackets is causing the issue in iOS.
as in
NSString *tempUrlSting = (NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)tempURLA,CFSTR("{}"), CFSTR("\""), CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding)));
NSURL *tempUrl=[NSURL URLWithString:tempUrlSting];
If not encoding the braces in the URL but encoding the rest using [Rob's answer][1] as above. When creating the NSURL, the url is empty.
If encoding the braces the URL gets generated fine, but the server throws an exception.
This Question suggests to use CFNetworking.
EDIT
Used CFNetworking as below
-(void)getDataFromUrl{
CFStringRef tempURLA = CFSTR("http://my.test.server/iostest/index.html?{\"a\":\"b\"}");
CFStringRef tempUrlSting = CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)tempURLA,CFSTR("{}"), CFSTR("\""), CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding));
CFURLRef myURL = CFURLCreateWithString(kCFAllocatorDefault, tempUrlSting, NULL);
CFStringRef requestMethod = CFSTR("GET");
CFHTTPMessageRef myRequest = CFHTTPMessageCreateRequest(kCFAllocatorDefault, requestMethod, myURL,kCFHTTPVersion1_1);
CFStringRef headerFieldName = CFSTR("Accept");
CFStringRef headerFieldValue = CFSTR("text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
CFHTTPMessageSetHeaderFieldValue(myRequest, headerFieldName, headerFieldValue);
[self performHTTPRequest:myRequest];
}
-(void)performHTTPRequest:(CFHTTPMessageRef)request {
CFURLRef gotdatab = (__bridge CFURLRef)(CFBridgingRelease(CFHTTPMessageCopyRequestURL(request)));
// NSLog(#"(CFHTTPMessageRef request %#",gotdatab);
CFReadStreamRef requestStream = CFReadStreamCreateForHTTPRequest(NULL, request);
CFReadStreamOpen(requestStream);
NSMutableData *responseBytes = [NSMutableData data];
NSError *error;
while (TRUE) {
if (CFReadStreamHasBytesAvailable(requestStream)) {
UInt8 streambuffer[1024];
int readBytes = CFReadStreamRead (requestStream,streambuffer,sizeof(streambuffer));
NSLog(#"Read: %d",readBytes);
[responseBytes appendBytes:streambuffer length:readBytes];
}
if (CFReadStreamGetStatus(requestStream) == kCFStreamStatusError) {
error = (NSError*)CFBridgingRelease(CFReadStreamCopyError (requestStream));
if ([error code] == 61) {
// connection refused
NSLog(#"Error occured: %d",[error code]);
}
break;
}
if (CFReadStreamGetStatus(requestStream) == kCFStreamStatusAtEnd) {
NSLog(#"Stream reached end!");
error = nil;
break;
}
}//
CFHTTPMessageRef response = (CFHTTPMessageRef)CFReadStreamCopyProperty(requestStream, kCFStreamPropertyHTTPResponseHeader);
if (response==NULL) {
NSLog(#"response is null");
return;
}
}
The above was done using examples from here and here
Above method still has the same issue. That is: if {} are not encoded the URL doesn't get generated. If the {} are encoded the server doesn't return a proper value.
Any suggestions pls?

Sometimes URL encoded format already except for the é-character which should probably be encoded as %c3%a9. Desktop browser is quite liberal with invalid URLs, thats why it works in Safari etc.
So if you have a NSString and you want to convert it into a proper URL encoding then use the below method of NSString class.
NSURL* url = [NSURL URLWithString:[strURL stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];

You should edit your question showing us an example of your URL and your GET parameters. If you're percent escaping, for example, some reserved character in the domain name or the URL path, that suggests one solution (e.g. stringByAddingPercentEscapesUsingEncoding is fine). If you're percent escaping the broader array of reserved characters in the parameters of a GET request (notably = or +), then stringByAddingPercentEscapesUsingEncoding is simply not up to the job and you'd have to use CFURLCreateStringByAddingPercentEscapes (but only on the parameter keys and their values, not on the full URL string). I use a method like the following on the parameters as I append them to the URL:
- (NSString *)percentEscapeURLParameter:(NSString *)string
{
return CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,
(CFStringRef)string,
NULL,
(CFStringRef)#":/?#!$&'()*+,;=",
kCFStringEncodingUTF8));
}
If you're saying that CFURLCreateStringByAddingPercentEscapes is not working for you, you'd have to show us how you're using it. Make sure you are doing it just on the GET parameter values, that you're supplying the necessary "legal characters to escape" parameter, that you're not escaping something that shouldn't be, etc.

Related

Firebase Dynamic link: How to get the URI scheme from the dynamic link that gets generated?

My requirement is to get the URI scheme out of the dynamic link URL that has been generated. Even in the firebase sample app, its the deep link url that gets returned and not the URI scheme.
For eg:
Our dynamic link url is this -> https://my-app-code.app.goo.gl/value.
iOS custom app scheme (added from advanced options): myappscheme://some-tag/some-tag-id
Long dynamic link url is -> https://my-app-code.app.goo.gl/?link=my-web-url&apn=id&isi=android-google-play-id&ibi=ios-bundle-id&ius=ios-custom-app-scheme
When i click on the dynamic link url from email/notes (https://my-app-code.app.goo.gl/value), the callback will be the continueuseractivity function and i use the following block -
BOOL handled = [[FIRDynamicLinks dynamicLinks] handleUniversalLink:incomingUrl completion:^(FIRDynamicLink * _Nullable dynamicLink, NSError * _Nullable error)
to get the url (dynamicLink.url). In this block, i get the deep link url which is this
-> my-web-url (which is a part of the link parameter in the long dynamic link which i have mentioned above).
My actual requirement is to get the URI scheme myappscheme://some-tag/some-tag-id which associated with the URL. How do i get this?
I even tried the below -
FIRDynamicLink *dynamicLink = [[FIRDynamicLinks dynamicLinks] dynamicLinkFromCustomSchemeURL:url];
In either cases, I do not get the URI scheme.
Please help.
What you are trying to do is not currently possible on iOS with Dynamic Links. You are confusing URI scheme (myappscheme://) with URI path (myappscheme://some-tag/some-tag-id).
To my knowledge, Dynamic Links on iOS only support the scheme (via the ius param). This is confusing because Android does support a URI path (via the al param). For iOS, you'll need to do your routing based on the deep link URL.
Alternatively, you could investigate a more robust deep linking platform like Branch.io (full disclosure: I'm on the Branch team). Branch does support custom link parameters containing any data you wish, including a custom URI path.
What Alex said is correct. It is not possible, but there is an easy workaround to this.
I appended my desired custom url deep link as a query parameter to the https link url I provided.
So in your case it would look something like this
https://my-app-code.app.goo.gl/?link=https://my-web-url.com/?myCustomSchemeUri%3Dmy-custom-scheme://my-custom-scheme-url&apn=id&isi=android-google-play-id&ibi=ios-bundle-id&ius=ios-custom-app-scheme
When the continueuseractivity callback gets to your app you can retrieve myCustomSchemeUri parameter from the query string and use it as you please.
Something like this:
- (BOOL)application:(UIApplication *)application
continueUserActivity:(NSUserActivity *)userActivity
restorationHandler:(void (^)(NSArray *))restorationHandler {
BOOL handled = [[FIRDynamicLinks dynamicLinks]
handleUniversalLink:userActivity.webpageURL
completion:^(FIRDynamicLink * _Nullable dynamicLink,
NSError * _Nullable error) {
// ...
NSURL *url = dynamicLink.url;
NSString *myUri = [url queryParameterValueWithName:#"customUri"];
if (myUri.lenghth > 0){
NSURL *myURL = [NSURL URLWithString:myUri];
if (myURL != nil){
//source application is your bundle id in this case
[self application:application openURL:myURL sourceApplication:#"com.yourbundle.id" annotation:#{}];
}
}
}];
return handled;
}
And this is the method I wrote as a category of NSURL to retrieve the parameter
- (NSString *)queryParameterValueWithName:(NSString*)name{
NSURLComponents *urlComponents = [NSURLComponents componentsWithURL:self
resolvingAgainstBaseURL:NO];
NSArray *queryItems = urlComponents.queryItems;
NSString *param = [self valueForKey:name
fromQueryItems:queryItems];
return param;
}
- (NSString *)valueForKey:(NSString *)key
fromQueryItems:(NSArray *)queryItems
{
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"name=%#", key];
NSURLQueryItem *queryItem = [[queryItems
filteredArrayUsingPredicate:predicate]
firstObject];
return queryItem.value;
}
Maybe this answer is a bit late to you, but I ran into this problem just today.
Anyway I hope this helps somebody else out there.
Cheers,
Alessio

Azure DocumentDB Intermittent 401 error when querying REST API via Obj-c

I've been charged with implementing an objective-c based iOS query of the Azure DocumentDB system using the REST API scheme. Utilizing the code found on github, specifically https://github.com/Azure/azure-storage-ios I was able to generate a request that appropriately authenticates and returns the appropriate data.... sometimes.
The problem: I receive a 401 (authentication failure) error response from the server intermittently. Making the same request via Node.js does not encounter this behavior, so I believe this to be an issue with my objective-c implementation.
- (NSMutableURLRequest *) RequestWithQuery:(NSString*)query Parameters:(NSArray*)parameters {
NSError* error;
NSDictionary* dictionaryOfBodyContents = #{#"query":query,
#"parameters":parameters};
NSData* body = [NSJSONSerialization dataWithJSONObject:dictionaryOfBodyContents
options:NSJSONWritingPrettyPrinted
error:&error];
if(error != nil) {
NSLog(#"AzureRequestWithQueryParameters error generating the body: %#",error);
return nil;
}
char buffer[30];
struct tm * timeptr;
time_t time = (time_t) [[NSDate date] timeIntervalSince1970];
timeptr = gmtime(&time);
if (!strftime_l(buffer, 30, [#"%a, %d %b %Y %T GMT" UTF8String], timeptr, NULL))
{
NSException* myException = [NSException
exceptionWithName:#"Error in date/time format"
reason:#"Unknown"
userInfo:nil];
#throw myException;
}
NSString* date = [NSString stringWithUTF8String:buffer];
// generate auth token
NSString* authorizationToken = [self AuthorizationTokenForTableQueryWithDate:date];
// generate header contents
NSDictionary* dictionaryOfHeaderContents = #{#"authorization":authorizationToken,
#"connection":AZURE_CONNECTION_HEADER_CONNECTION,
#"content-type":AZURE_CONNECTION_HEADER_CONTENTTYPE,
#"content-length":[NSString stringWithFormat:#"%lu",(unsigned long)[body length]],
#"x-ms-version":AZURE_CONNECTION_APIVERSION,
#"x-ms-documentdb-isquery":#"true",
#"x-ms-date":date.lowercaseString,
#"cache-control":#"no-cache",
#"user-agent":AZURE_CONNECTION_HEADER_USERAGENT,
#"accept":#"application/json"};
// generate url contents
NSString* urlString = [NSString stringWithFormat:#"https://%#:%#/%#", AZURE_URL_HOST, AZURE_URL_PORT, AZURE_URL_DOCUMENTS];
NSURL* url = [NSURL URLWithString:urlString];
NSMutableURLRequest* request = [[NSMutableURLRequest alloc] initWithURL:url];
[request setHTTPMethod:AZURE_CONNECTION_METHOD];
[request setAllHTTPHeaderFields:dictionaryOfHeaderContents];
[request setHTTPBody:body];
return request;
}
- (NSString*) AuthorizationTokenForTableQueryWithDate:(NSString*)date {
//
// Based on https://msdn.microsoft.com/en-us/library/azure/dd179428.aspx under "Table Service (Shared Key Authentication)"
//
// generating a authentication token is a Hash-based Message Authentication Code (HMAC) constructed from the request
// and computed by using the SHA256 algorithm, and then encoded by using Base64 encoding.
//
// StringToSign = VERB + "\n" +
// Content-MD5 + "\n" +
// Content-Type + "\n" +
// Date + "\n" +
// CanonicalizedHeaders +
// CanonicalizedResource;
//
NSString* StringToSign = [NSString stringWithFormat:#"%#\n%#\n%#\n%#\n\n",
AZURE_CONNECTION_METHOD.lowercaseString?:#"",
AZURE_RESOURCE_TYPE.lowercaseString?:#"",
AZURE_URL_COLLECTIONS.lowercaseString?:#"",
date.lowercaseString?:#""];
// Generate Key/Message pair
NSData* keyData = [[NSData alloc] initWithBase64EncodedString:AZURE_AUTH_KEY options:NSDataBase64DecodingIgnoreUnknownCharacters];
NSData* messageData = [StringToSign dataUsingEncoding:NSUTF8StringEncoding];
// Encrypt your Key/Message using HMAC SHA256
NSMutableData* HMACData = [NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH];
CCHmac(kCCHmacAlgSHA256, keyData.bytes, keyData.length, messageData.bytes, messageData.length, HMACData.mutableBytes);
// Take your encrypted data, and generate a token that Azure likes.
NSString* signature = [HMACData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];
NSString* unencodedToken = [NSString stringWithFormat:#"type=master&ver=1.0&sig=%#",signature];
NSString* authorizationToken = [unencodedToken stringByReplacingOccurrencesOfString:#"&" withString:#"%26"];
authorizationToken = [authorizationToken stringByReplacingOccurrencesOfString:#"=" withString:#"%3D"];
return authorizationToken;
}
If anyone has encountered a similar intermittent 401 and was able to resolve any help would be appreciated. Or suggestions for debugging steps for the above code bearing in mind, I have attempted decrementing the timestamp by a few seconds, similar intermittent failures.
Although simply retrying a few times upon a failure while decrementing the seconds results in a 200 response in 1-2 retries, I don't feel it is an ideal solution by any means.
Thank you for your time.
Update: Please see Andrew Liu's explanation below for the reason for this failure. I have flagged his response as the answer, below is the updated snippet of code.
NSString* unencodedToken = [NSString stringWithFormat:#"type=master&ver=1.0&sig=%#",signature];
// NSString* authorizationToken = [unencodedToken stringByReplacingOccurrencesOfString:#"&" withString:#"%26"];
// authorizationToken = [authorizationToken stringByReplacingOccurrencesOfString:#"=" withString:#"%3D"];
NSString* authorizationToken = [unencodedToken stringByAddingPercentEncodingWithAllowedCharacters:[[NSCharacterSet characterSetWithCharactersInString:#"&+="] invertedSet]];
return authorizationToken;
401 (auth failure) usually indicates that something is wrong with the auth token.
It's important to note that the auth token is a Base64-encoded string - meaning it can contain the + character.
The db server expects + characters in the auth token to be url encoded (%2B)... some but not all HTTP clients will automatically encode HTTP headers for you.
I suspect url-encoding or converting + to %2B for the following variable will fix your intermittent 401 issue:
NSString* signature = [HMACData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];
I've seen this issue before and usually it has to do with the final 2 steps of the protocol, i.e. either the base64 encoding is whacky, or the URI encoding. One way to debug this would be to print the auth token that you had sent, in case of failure, and see if there are any strange characters which are possibly not being transmitted correctly. You can post the buggy token here and I can take a look.

Retrieving and Parsing Text From Specific Webpage Using Swift

I need to retrieve the text from a specific website. However, I only need a few parts of it. How can I accomplish this using swift.
I have found the following in objective-c, but am not sure it provides how to reference it from a specific site:
NSString *webString = [webView stringByEvaluatingJavaScriptFromString:#"document.documentElement.innerText"];
NSScanner *stringScanner = [NSScanner scannerWithString:webString];
NSString *content = [[NSString alloc] init];
while ([stringScanner isAtEnd] == NO) {
[stringScanner scanUpToString:#"Start of the text you want" intoString:null];
[stringScanner scanUpToString:#"End of the text you want" intoString:&content];
}`
I have put an example of what I mean below:
Again, I would like to accomplish this using Swift.
If your HTML was easily targetable with identifiers or class names, I would suggest using a library such as Kanna. But I've had a look at your page and the text you need is lost amidst an ocean of divs...
So I've quickly hacked a way to get your text with componentsSeparatedByString: I'm cutting the HTML in blocks until I get to the part we're interested in.
Note that it's far from being the most efficient way: instead of using componentsSeparatedByString you should come with a way of identifying the HTML block you want and search for it with NSScanner.
That being said, here's my example of a working hack, tested in a Playground:
enum CustomErrors : String, ErrorType {
case InvalidURL = "Invalid URL"
}
do {
let str = "http://www.golfwrx.com/328370/mizuno-to-offer-custom-grips-at-no-additional-charge/"
guard let url = NSURL(string: str) else { throw CustomErrors.InvalidURL }
let html = try String(contentsOfURL: url)
let separator1 = "<div class='mailmunch-forms-before-post' style='display: none !important;'></div><p>"
let temp = html.componentsSeparatedByString(separator1)
let separator2 = "</p>\n<p>"
let temp2 = temp[1].componentsSeparatedByString(separator2)
let separator3 = "</p><div class='mailmunch-forms-in-post-middle'"
let separated = temp2[1].componentsSeparatedByString(separator3)
let result = separated[0]
print(result)
} catch {
print(error)
}
Note: my example is in Swift 2 (Xcode 7).
Sorry about the specifics, I'm an Objective-C guy. but, here is an example of how to use NString to get the contents of a websites HTML
NSString *url = #"http://www.example.com"; // Your URL
NSURL *urlRequest = [NSURL URLWithString:url]; // Make a request with your URL
NSError *err = nil; // Error handler
NSString *html = [NSString stringWithContentsOfURL:urlRequest encoding:NSUTF8StringEncoding error:&err]; // Try to get the HTML in the string
if(err)
{
//Do something as it didn't work! Maybe a connection problem
}
else
{
// Use NScanner on html string
}
http://nshipster.com/nsscanner/ is a good place to learn about NScanner for swift
EDIT: Here is the above translated to swift
var err: NSError? // Error handler
let url: NSURL = NSURL(string: "http://www.example.com") // NSURL, put your website URL in here
let string = NSString(contentsOfURL: url, encoding: NSUTF8StringEncoding, error: &err) // String will now hold your HTML
// Now use NScanner (See Link) to parse the HTML output
My swift is rusty. but this might help you. This is roughly translated but outlines exactly what you need

I want to call phone number "#51234" in Xcode use telprompt [duplicate]

This question already has answers here:
Phone call number with hashtag on iOS
(3 answers)
Closed 4 years ago.
I want to call phone number "#51234" in Xcode use telprompt.
but telprompt is reject it.
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:[NSString stringWithFormat:#"telprompt://#5%#", nzoneNum]]];
nzomeNum is "1234"
At least as of iOS 11, one can dial numbers with a hashtag (#) or asterisk (*).
Make calls with these characters by first encoding the phone number, then adding the tel: prefix, and finally turning the resulting string into a URL.
Swift 4, iOS 11
// set up the dial sequence
let nzoneNum = "1234"
let prefix = "#5"
let dialSequence = "\(prefix)\(nzoneNum)"
// "percent encode" the dial sequence with the URL Host allowed character set
guard let encodedDialSequence =
dialSequence.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) else {
print("Unable to encode the dial sequence.")
return
}
// add the `tel:` url scheme to the front of the encoded string
let dialURLString = "tel:\(encodedDialSequence)"
// set up the URL with the scheme/encoded number string
guard let dialURL = URL(string: dialURLString) else {
print("Couldn't make the dial string into an URL.")
return
}
// dial the URL
UIApplication.shared.open(dialURL, options: [:]) { success in
if success { print("SUCCESSFULLY OPENED DIAL URL") }
else { print("COULDN'T OPEN DIAL URL") }
}
Objective-C, iOS 11
// set up the dial sequence
NSString *nzoneNum = #"1234";
NSString *prefix = #"#5";
NSString *dialSequence = [NSString stringWithFormat:#"%#%#", prefix, nzoneNum];
// set up the URL Host allowed character set, and "percent encode" the dial sequence
NSCharacterSet *urlHostAllowed = [NSCharacterSet URLHostAllowedCharacterSet];
NSString *encodedDialSequence = [dialSequence stringByAddingPercentEncodingWithAllowedCharacters:urlHostAllowed];
// add the `tel` url scheme to the front of the encoded string
NSString *dialURLString = [NSString stringWithFormat:#"tel:%#", encodedDialSequence];
// set up the URL with the scheme/encoded number string
NSURL *dialURL = [NSURL URLWithString:dialURLString];
// set up an empty dictionary for the options parameter
NSDictionary *optionsDict = [[NSDictionary alloc] init];
// dial the URL
[[UIApplication sharedApplication] openURL:dialURL
options:optionsDict
completionHandler:^(BOOL success) {
if (success) { NSLog(#"SUCCESSFULLY OPENED DIAL URL"); }
else { NSLog(#"COULDN'T OPEN DIAL URL"); }
}];
Unfortunately you can't make calls to any number including a hashtag. Apple clearly restricts those calls: iPhoneURLScheme_Reference
To prevent users from maliciously redirecting phone calls or changing the behavior of a phone or account, the Phone app supports most, but not all, of the special characters in the tel scheme. Specifically, if a URL contains the * or # characters, the Phone app does not attempt to dial the corresponding phone number.

Correcting user submitted URL in Xcode/Objective C

I'm trying to programme a mini browser in Xcode however at the moment the UIWebView will only load URLs that include the http ://www The user submits their URL using a UITextField and the contents become a string.
I wondered if there was a way to either search the submitted string and add the http or www or both where required or format the text input so it automatically checks to see if the correct address is used.
Thanks
Do something like this:
NSString *urlString = ... // the user entered URL string
if (![urlString hasPrefix:#"http://"]) {
urlString = [#"http://" stringByAppendingString:urlString];
}
Note that this is just a rough suggestion to get you started. This code doesn't handle cases such as the URL already having a prefix of "https://" or typos such as "htp://".
A better approach might be:
NSURL *url = [NSURL URLWithString:urlString];
NSString *scheme = [url scheme];
if (scheme.length == 0) {
// The string has no scheme - add "http://"
} else {
// check for valid schemes
}

Resources