OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #import "ios/chrome/common/physical_web/physical_web_request.h" |
| 6 |
| 7 #include "base/ios/weak_nsobject.h" |
| 8 #include "base/logging.h" |
| 9 #include "base/mac/foundation_util.h" |
| 10 #include "base/mac/scoped_block.h" |
| 11 #include "base/mac/scoped_nsobject.h" |
| 12 #include "base/metrics/histogram.h" |
| 13 #include "base/strings/sys_string_conversions.h" |
| 14 #include "components/version_info/version_info.h" |
| 15 #include "google_apis/google_api_keys.h" |
| 16 #import "ios/chrome/common/physical_web/physical_web_device.h" |
| 17 #import "ios/chrome/common/physical_web/physical_web_types.h" |
| 18 #include "ios/web/public/user_agent.h" |
| 19 |
| 20 typedef void (^SessionCompletionProceduralBlock)(NSData* data, |
| 21 NSURLResponse* response, |
| 22 NSError* error); |
| 23 |
| 24 namespace { |
| 25 |
| 26 NSString* const kUrlsKey = @"urls"; |
| 27 NSString* const kUrlKey = @"url"; |
| 28 NSString* const kResultsKey = @"results"; |
| 29 NSString* const kPageInfoKey = @"pageInfo"; |
| 30 NSString* const kIconKey = @"icon"; |
| 31 NSString* const kTitleKey = @"title"; |
| 32 NSString* const kDescriptionKey = @"description"; |
| 33 NSString* const kScannedUrlKey = @"scannedUrl"; |
| 34 NSString* const kResolvedUrlKey = @"resolvedUrl"; |
| 35 |
| 36 NSString* const kMetadataServiceUrl = |
| 37 @"https://physicalweb.googleapis.com/v1alpha1/urls:resolve"; |
| 38 NSString* const kKeyQueryItemName = @"key"; |
| 39 NSString* const kHTTPPOSTRequestMethod = @"POST"; |
| 40 |
| 41 std::string GetUserAgent() { |
| 42 static std::string user_agent; |
| 43 static dispatch_once_t once_token; |
| 44 dispatch_once(&once_token, ^{ |
| 45 std::string product("CriOS/"); |
| 46 product += version_info::GetVersionNumber(); |
| 47 user_agent = web::BuildUserAgentFromProduct(product); |
| 48 }); |
| 49 return user_agent; |
| 50 } |
| 51 |
| 52 } // namespace |
| 53 |
| 54 @implementation PhysicalWebRequest { |
| 55 base::mac::ScopedBlock<physical_web::RequestFinishedBlock> block_; |
| 56 base::scoped_nsobject<PhysicalWebDevice> device_; |
| 57 base::scoped_nsobject<NSMutableURLRequest> request_; |
| 58 base::scoped_nsobject<NSURLSessionDataTask> urlSessionTask_; |
| 59 base::scoped_nsobject<NSMutableData> data_; |
| 60 base::scoped_nsobject<NSDate> startDate_; |
| 61 } |
| 62 |
| 63 - (instancetype)initWithDevice:(PhysicalWebDevice*)device { |
| 64 self = [super init]; |
| 65 if (self) { |
| 66 device_.reset([device retain]); |
| 67 } |
| 68 return self; |
| 69 } |
| 70 |
| 71 - (instancetype)init { |
| 72 NOTREACHED(); |
| 73 return nil; |
| 74 } |
| 75 |
| 76 - (void)cancel { |
| 77 [urlSessionTask_ cancel]; |
| 78 block_.reset(); |
| 79 } |
| 80 |
| 81 - (void)start:(physical_web::RequestFinishedBlock)block { |
| 82 block_.reset([block copy]); |
| 83 data_.reset([[NSMutableData alloc] init]); |
| 84 |
| 85 // Creates the HTTP post request. |
| 86 NSURLComponents* components = |
| 87 [[NSURLComponents alloc] initWithString:kMetadataServiceUrl]; |
| 88 NSString* apiKey = |
| 89 [NSString stringWithUTF8String:google_apis::GetAPIKey().c_str()]; |
| 90 [components |
| 91 setQueryItems:@[ [NSURLQueryItem queryItemWithName:kKeyQueryItemName |
| 92 value:apiKey] ]]; |
| 93 NSURL* url = [components URL]; |
| 94 request_.reset([[NSMutableURLRequest requestWithURL:url] retain]); |
| 95 [request_ setHTTPMethod:kHTTPPOSTRequestMethod]; |
| 96 |
| 97 // body of the POST request. |
| 98 NSDictionary* jsonBody = |
| 99 @{ kUrlsKey : @[ @{kUrlKey : [[device_ url] absoluteString]} ] }; |
| 100 [request_ setHTTPBody:[NSJSONSerialization dataWithJSONObject:jsonBody |
| 101 options:0 |
| 102 error:NULL]]; |
| 103 [request_ setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; |
| 104 [request_ setValue:base::SysUTF8ToNSString(GetUserAgent()) |
| 105 forHTTPHeaderField:@"User-Agent"]; |
| 106 |
| 107 startDate_.reset([[NSDate date] retain]); |
| 108 // Starts the request. |
| 109 NSURLSession* session = |
| 110 [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration |
| 111 ephemeralSessionConfiguration] |
| 112 delegate:nil |
| 113 delegateQueue:[NSOperationQueue mainQueue]]; |
| 114 base::WeakNSObject<PhysicalWebRequest> weakSelf(self); |
| 115 SessionCompletionProceduralBlock completionHandler = |
| 116 ^(NSData* data, NSURLResponse* response, NSError* error) { |
| 117 base::scoped_nsobject<PhysicalWebRequest> strongSelf([weakSelf retain]); |
| 118 if (!strongSelf) { |
| 119 return; |
| 120 } |
| 121 if (error) { |
| 122 [strongSelf callBlockWithError:error jsonObject:nil]; |
| 123 } else { |
| 124 [strongSelf.get()->data_ appendData:data]; |
| 125 [strongSelf sessionDidFinishLoading]; |
| 126 } |
| 127 }; |
| 128 urlSessionTask_.reset([ |
| 129 [session dataTaskWithRequest:request_ completionHandler:completionHandler] |
| 130 retain]); |
| 131 [urlSessionTask_ resume]; |
| 132 } |
| 133 |
| 134 - (void)sessionDidFinishLoading { |
| 135 NSError* error = nil; |
| 136 NSDictionary* jsonObject = |
| 137 [NSJSONSerialization JSONObjectWithData:data_ options:0 error:&error]; |
| 138 if (error != nil) { |
| 139 [self callBlockWithError:error jsonObject:nil]; |
| 140 return; |
| 141 } |
| 142 if (![self isValidJSON:jsonObject]) { |
| 143 [self callBlockWithError: |
| 144 [NSError errorWithDomain:physical_web::kPhysicalWebErrorDomain |
| 145 code:physical_web::ERROR_INVALID_JSON |
| 146 userInfo:nil] |
| 147 jsonObject:jsonObject]; |
| 148 return; |
| 149 } |
| 150 [self callBlockWithError:error jsonObject:jsonObject]; |
| 151 } |
| 152 |
| 153 // Checks whether the JSON object has the correct format. |
| 154 // The format should be similar to the following: |
| 155 // { |
| 156 // "results":[ |
| 157 // { |
| 158 // "pageInfo":{ |
| 159 // "title":"Google" |
| 160 // "description":"Search the world's information" |
| 161 // "icon":"https://www.google.com/favicon.ico" |
| 162 // } |
| 163 // "scannedUrl":"https://www.google.com" |
| 164 // "resolvedUrl":"https://www.google.com" |
| 165 // } |
| 166 // ] |
| 167 // } |
| 168 - (BOOL)isValidJSON:(id)jsonObject { |
| 169 NSDictionary* dict = base::mac::ObjCCast<NSDictionary>(jsonObject); |
| 170 if (!dict) { |
| 171 return NO; |
| 172 } |
| 173 NSArray* list = base::mac::ObjCCast<NSArray>(dict[kResultsKey]); |
| 174 if (!list) { |
| 175 return NO; |
| 176 } |
| 177 if ([list count] != 1) { |
| 178 return NO; |
| 179 } |
| 180 NSDictionary* item = base::mac::ObjCCast<NSDictionary>(list[0]); |
| 181 if (!item) { |
| 182 return NO; |
| 183 } |
| 184 return YES; |
| 185 } |
| 186 |
| 187 // Calls the block passed as parameter of -start: when the request is finished. |
| 188 - (void)callBlockWithError:(NSError*)error |
| 189 jsonObject:(NSDictionary*)jsonObject { |
| 190 if (error) { |
| 191 if (block_.get()) { |
| 192 block_.get()(nil, error); |
| 193 } |
| 194 } else { |
| 195 NSTimeInterval roundTripTime = |
| 196 [[NSDate date] timeIntervalSinceDate:startDate_]; |
| 197 int roundTripTimeMS = 1000 * roundTripTime; |
| 198 UMA_HISTOGRAM_COUNTS("PhysicalWeb.RoundTripTimeMilliseconds", |
| 199 roundTripTimeMS); |
| 200 NSArray* list = jsonObject[kResultsKey]; |
| 201 NSDictionary* item = list[0]; |
| 202 |
| 203 NSDictionary* pageInfo = |
| 204 base::mac::ObjCCast<NSDictionary>(item[kPageInfoKey]); |
| 205 NSString* scannedUrlString = |
| 206 base::mac::ObjCCast<NSString>(item[kScannedUrlKey]); |
| 207 NSString* resolvedUrlString = |
| 208 base::mac::ObjCCast<NSString>(item[kResolvedUrlKey]); |
| 209 |
| 210 // Verify required fields pageInfo, scannedUrl, and resolvedUrl are present. |
| 211 if (pageInfo == nil || scannedUrlString == nil || |
| 212 resolvedUrlString == nil) { |
| 213 error = [NSError errorWithDomain:physical_web::kPhysicalWebErrorDomain |
| 214 code:physical_web::ERROR_INVALID_JSON |
| 215 userInfo:nil]; |
| 216 if (block_.get()) { |
| 217 block_.get()(nil, error); |
| 218 } |
| 219 return; |
| 220 } |
| 221 |
| 222 // Read optional fields. |
| 223 NSString* iconString = base::mac::ObjCCast<NSString>(pageInfo[kIconKey]); |
| 224 NSString* description = |
| 225 base::mac::ObjCCast<NSString>(pageInfo[kDescriptionKey]); |
| 226 NSString* title = base::mac::ObjCCast<NSString>(pageInfo[kTitleKey]); |
| 227 NSURL* scannedUrl = |
| 228 scannedUrlString ? [NSURL URLWithString:scannedUrlString] : nil; |
| 229 NSURL* resolvedUrl = |
| 230 resolvedUrlString ? [NSURL URLWithString:resolvedUrlString] : nil; |
| 231 NSURL* icon = iconString ? [NSURL URLWithString:iconString] : nil; |
| 232 base::scoped_nsobject<PhysicalWebDevice> device([[PhysicalWebDevice alloc] |
| 233 initWithURL:resolvedUrl |
| 234 requestURL:scannedUrl |
| 235 icon:icon |
| 236 title:title |
| 237 description:description |
| 238 transmitPower:[device_ transmitPower] |
| 239 rssi:[device_ rssi] |
| 240 rank:physical_web::kMaxRank]); |
| 241 if (block_.get() != nil) { |
| 242 block_.get()(device, nil); |
| 243 } |
| 244 } |
| 245 } |
| 246 |
| 247 @end |
OLD | NEW |