1/////////////////////////////////////////////////////////////////////////////// 2// Name: src/osx/webrequest_urlsession.mm 3// Purpose: wxWebRequest implementation using URLSession 4// Author: Tobias Taschner 5// Created: 2018-10-25 6// Copyright: (c) 2018 wxWidgets development team 7// Licence: wxWindows licence 8/////////////////////////////////////////////////////////////////////////////// 9 10// For compilers that support precompilation, includes "wx.h". 11#include "wx/wxprec.h" 12 13#include "wx/webrequest.h" 14 15#if wxUSE_WEBREQUEST && wxUSE_WEBREQUEST_URLSESSION 16 17#import <Foundation/Foundation.h> 18 19#include "wx/osx/private/webrequest_urlsession.h" 20#include "wx/osx/private.h" 21 22#ifndef WX_PRECOMP 23 #include "wx/log.h" 24 #include "wx/utils.h" 25#endif 26 27@interface wxWebSessionDelegate : NSObject <NSURLSessionDataDelegate> 28{ 29 wxWebSessionURLSession* m_session; 30 NSMapTable* m_requests; 31} 32 33@end 34 35@implementation wxWebSessionDelegate 36 37- (id)initWithSession:(wxWebSessionURLSession*)session 38{ 39 m_session = session; 40 m_requests = [[NSMapTable weakToStrongObjectsMapTable] retain]; 41 return self; 42} 43 44- (void)dealloc 45{ 46 [m_requests release]; 47 [super dealloc]; 48} 49 50- (void)registerRequest:(wxWebRequestURLSession*)request task:(NSURLSessionTask*)task 51{ 52 [m_requests setObject:[NSValue valueWithPointer:request] forKey:task]; 53} 54 55- (wxWebRequestURLSession*)requestForTask:(NSURLSessionTask*)task 56{ 57 wxWebRequestURLSession* request = NULL; 58 NSValue* val = [m_requests objectForKey:task]; 59 if (val) 60 request = static_cast<wxWebRequestURLSession*>(val.pointerValue); 61 62 return request; 63} 64 65- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data 66{ 67 wxUnusedVar(session); 68 69 wxWebRequestURLSession* request = [self requestForTask:dataTask]; 70 71 wxLogTrace(wxTRACE_WEBREQUEST, "Request %p: didReceiveData", request); 72 73 if (request) 74 request->GetResponseImplPtr()->HandleData(data); 75} 76 77- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error 78{ 79 wxUnusedVar(session); 80 81 wxWebRequestURLSession* request = [self requestForTask:task]; 82 if (error) 83 { 84 wxLogTrace(wxTRACE_WEBREQUEST, "Request %p: didCompleteWithError, error=%s", 85 request, wxCFStringRefFromGet([error description]).AsString()); 86 87 if ( error.code == NSURLErrorCancelled ) 88 request->SetState(wxWebRequest::State_Cancelled); 89 else 90 request->SetState(wxWebRequest::State_Failed, wxCFStringRefFromGet(error.localizedDescription).AsString()); 91 } 92 else 93 { 94 wxLogTrace(wxTRACE_WEBREQUEST, "Request %p: completed successfully", request); 95 96 request->HandleCompletion(); 97 } 98 99 // After the task is completed it no longer needs to be mapped 100 [m_requests removeObjectForKey:task]; 101} 102 103- (void)URLSession:(NSURLSession *)session 104 task:(NSURLSessionTask *)task 105 didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge 106 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler 107{ 108 wxUnusedVar(session); 109 110 wxWebRequestURLSession* request = [self requestForTask:task]; 111 wxCHECK_RET( request, "received authentication challenge for an unknown task" ); 112 113 NSURLProtectionSpace* const space = [challenge protectionSpace]; 114 115 wxLogTrace(wxTRACE_WEBREQUEST, "Request %p: didReceiveChallenge for %s", 116 request, 117 wxCFStringRefFromGet([space description]).AsString()); 118 119 // We need to distinguish between session-wide and task-specific 120 // authentication challenges, we're only really interested in the latter 121 // ones here (but apparently there is no way to get just them, even though 122 // the documentation seems to imply that session-wide challenges shouldn't 123 // be sent to this task-specific delegate -- but they're, at least under 124 // 10.14). 125 const auto authMethod = space.authenticationMethod; 126 if ( authMethod == NSURLAuthenticationMethodHTTPBasic || 127 authMethod == NSURLAuthenticationMethodHTTPDigest ) 128 { 129 if ( auto* const authChallenge = request->GetAuthChallengeImplPtr() ) 130 { 131 // We're going to get called until we don't provide the correct 132 // credentials, so don't use them again (and again, and again...) 133 // if we had already used them unsuccessfully. 134 if ( !challenge.previousFailureCount ) 135 { 136 wxLogTrace(wxTRACE_WEBREQUEST, "Request %p: using credentials", request); 137 138 completionHandler(NSURLSessionAuthChallengeUseCredential, 139 authChallenge->GetURLCredential()); 140 141 return; 142 } 143 144 wxLogTrace(wxTRACE_WEBREQUEST, "Request %p: not using failing credentials again", request); 145 } 146 147 request->HandleChallenge(new wxWebAuthChallengeURLSession( 148 [space isProxy] ? wxWebAuthChallenge::Source_Proxy 149 : wxWebAuthChallenge::Source_Server, 150 *request 151 )); 152 } 153 else if ( authMethod == NSURLAuthenticationMethodServerTrust ) 154 { 155 if (request->IsPeerVerifyDisabled()) 156 completionHandler(NSURLSessionAuthChallengeUseCredential, 157 [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]); 158 } 159 160 completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil); 161} 162 163@end 164 165// 166// wxWebRequestURLSession 167// 168wxWebRequestURLSession::wxWebRequestURLSession(wxWebSession& session, 169 wxWebSessionURLSession& sessionImpl, 170 wxEvtHandler* handler, 171 const wxString& url, 172 int winid): 173 wxWebRequestImpl(session, sessionImpl, handler, winid), 174 m_sessionImpl(sessionImpl), 175 m_url(url) 176{ 177} 178 179wxWebRequestURLSession::~wxWebRequestURLSession() 180{ 181 [m_task release]; 182} 183 184void wxWebRequestURLSession::Start() 185{ 186 wxString method = m_method; 187 if ( method.empty() ) 188 method = m_dataSize ? wxASCII_STR("POST") : wxASCII_STR("GET"); 189 190 NSMutableURLRequest* req = [NSMutableURLRequest requestWithURL: 191 [NSURL URLWithString:wxCFStringRef(m_url).AsNSString()]]; 192 req.HTTPMethod = wxCFStringRef(method).AsNSString(); 193 194 // Set request headers 195 for (wxWebRequestHeaderMap::const_iterator it = m_headers.begin(); it != m_headers.end(); ++it) 196 { 197 [req setValue:wxCFStringRef(it->second).AsNSString() forHTTPHeaderField: 198 wxCFStringRef(it->first).AsNSString()]; 199 } 200 201 if (m_dataSize) 202 { 203 // Read all upload data to memory buffer 204 void* const buf = malloc(m_dataSize); 205 m_dataStream->Read(buf, m_dataSize); 206 207 // Create NSData from memory buffer, passing it ownership of the data. 208 NSData* data = [NSData dataWithBytesNoCopy:buf length:m_dataSize]; 209 m_task = [[m_sessionImpl.GetSession() uploadTaskWithRequest:req fromData:data] retain]; 210 } 211 else 212 { 213 // Create data task 214 m_task = [[m_sessionImpl.GetSession() dataTaskWithRequest:req] retain]; 215 } 216 217 wxLogTrace(wxTRACE_WEBREQUEST, "Request %p: start \"%s %s\"", 218 this, method, m_url); 219 220 // The session delegate needs to know which task is wrapped in which request 221 [m_sessionImpl.GetDelegate() registerRequest:this task:m_task]; 222 223 m_response.reset(new wxWebResponseURLSession(*this, m_task)); 224 225 SetState(wxWebRequest::State_Active); 226 [m_task resume]; 227} 228 229void wxWebRequestURLSession::DoCancel() 230{ 231 [m_task cancel]; 232} 233 234void wxWebRequestURLSession::HandleCompletion() 235{ 236 switch ( m_response->GetStatus() ) 237 { 238 case 401: 239 case 407: 240 SetState(wxWebRequest::State_Unauthorized, m_response->GetStatusText()); 241 break; 242 243 default: 244 SetFinalStateFromStatus(); 245 } 246} 247 248void wxWebRequestURLSession::HandleChallenge(wxWebAuthChallengeURLSession* challenge) 249{ 250 m_authChallenge.reset(challenge); 251} 252 253wxFileOffset wxWebRequestURLSession::GetBytesSent() const 254{ 255 return m_task.countOfBytesSent; 256} 257 258wxFileOffset wxWebRequestURLSession::GetBytesExpectedToSend() const 259{ 260 return m_task.countOfBytesExpectedToSend; 261} 262 263wxFileOffset wxWebRequestURLSession::GetBytesReceived() const 264{ 265 return m_task.countOfBytesReceived; 266} 267 268wxFileOffset wxWebRequestURLSession::GetBytesExpectedToReceive() const 269{ 270 return m_task.countOfBytesExpectedToReceive; 271} 272 273// 274// wxWebAuthChallengeURLSession 275// 276 277wxWebAuthChallengeURLSession::~wxWebAuthChallengeURLSession() 278{ 279 [m_cred release]; 280} 281 282void wxWebAuthChallengeURLSession::SetCredentials(const wxWebCredentials& cred) 283{ 284 wxLogTrace(wxTRACE_WEBREQUEST, "Request %p: setting credentials", &m_request); 285 286 [m_cred release]; 287 288 m_cred = [NSURLCredential 289 credentialWithUser:wxCFStringRef(cred.GetUser()).AsNSString() 290 password:wxCFStringRef(wxSecretString(cred.GetPassword())).AsNSString() 291 persistence:NSURLCredentialPersistenceNone 292 ]; 293 294 [m_cred retain]; 295 296 m_request.Start(); 297} 298 299 300// 301// wxWebResponseURLSession 302// 303 304wxWebResponseURLSession::wxWebResponseURLSession(wxWebRequestURLSession& request, 305 WX_NSURLSessionTask task): 306 wxWebResponseImpl(request) 307{ 308 m_task = [task retain]; 309 310 Init(); 311} 312 313wxWebResponseURLSession::~wxWebResponseURLSession() 314{ 315 [m_task release]; 316} 317 318void wxWebResponseURLSession::HandleData(WX_NSData data) 319{ 320 void* buf = GetDataBuffer(data.length); 321 [data getBytes:buf length:data.length]; 322 ReportDataReceived(data.length); 323} 324 325wxFileOffset wxWebResponseURLSession::GetContentLength() const 326{ 327 return m_task.response.expectedContentLength; 328} 329 330wxString wxWebResponseURLSession::GetURL() const 331{ 332 return wxCFStringRefFromGet(m_task.response.URL.absoluteString).AsString(); 333} 334 335wxString wxWebResponseURLSession::GetHeader(const wxString& name) const 336{ 337 NSHTTPURLResponse* httpResp = (NSHTTPURLResponse*) m_task.response; 338 NSString* value = [httpResp.allHeaderFields objectForKey:wxCFStringRef(name).AsNSString()]; 339 if (value) 340 return wxCFStringRefFromGet(value).AsString(); 341 else 342 return wxString(); 343} 344 345int wxWebResponseURLSession::GetStatus() const 346{ 347 NSHTTPURLResponse* httpResp = (NSHTTPURLResponse*) m_task.response; 348 return httpResp.statusCode; 349} 350 351wxString wxWebResponseURLSession::GetStatusText() const 352{ 353 return wxCFStringRefFromGet([NSHTTPURLResponse localizedStringForStatusCode:GetStatus()]).AsString(); 354} 355 356wxString wxWebResponseURLSession::GetSuggestedFileName() const 357{ 358 return wxCFStringRefFromGet(m_task.response.suggestedFilename).AsString(); 359} 360 361// 362// wxWebSessionURLSession 363// 364 365wxWebSessionURLSession::wxWebSessionURLSession() 366{ 367 m_delegate = [[wxWebSessionDelegate alloc] initWithSession:this]; 368 369 m_session = [[NSURLSession sessionWithConfiguration: 370 [NSURLSessionConfiguration defaultSessionConfiguration] 371 delegate:m_delegate delegateQueue:nil] retain]; 372} 373 374wxWebSessionURLSession::~wxWebSessionURLSession() 375{ 376 [m_session release]; 377 [m_delegate release]; 378} 379 380wxWebRequestImplPtr 381wxWebSessionURLSession::CreateRequest(wxWebSession& session, 382 wxEvtHandler* handler, 383 const wxString& url, 384 int winid) 385{ 386 return wxWebRequestImplPtr(new wxWebRequestURLSession(session, *this, handler, url, winid)); 387} 388 389wxVersionInfo wxWebSessionURLSession::GetLibraryVersionInfo() 390{ 391 int verMaj, verMin, verMicro; 392 wxGetOsVersion(&verMaj, &verMin, &verMicro); 393 return wxVersionInfo("URLSession", verMaj, verMin, verMicro); 394} 395 396#endif // wxUSE_WEBREQUEST_URLSESSION 397