Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #if defined(_WIN32) && defined(GetMessage) | |
| 6 // Method in p224_spake conflicts with macro on Windows. | |
| 7 #undef GetMessage | |
| 8 #endif | |
| 9 | |
| 5 #include "chrome/browser/local_discovery/privetv3_session.h" | 10 #include "chrome/browser/local_discovery/privetv3_session.h" |
| 6 | 11 |
| 12 #include "base/base64.h" | |
| 7 #include "base/json/json_writer.h" | 13 #include "base/json/json_writer.h" |
| 8 #include "base/logging.h" | 14 #include "base/logging.h" |
| 9 #include "base/message_loop/message_loop.h" | 15 #include "base/message_loop/message_loop.h" |
| 10 #include "chrome/browser/local_discovery/privet_constants.h" | 16 #include "chrome/browser/local_discovery/privet_constants.h" |
| 11 #include "chrome/browser/local_discovery/privet_http.h" | 17 #include "chrome/browser/local_discovery/privet_http.h" |
| 12 #include "chrome/browser/local_discovery/privet_url_fetcher.h" | 18 #include "chrome/browser/local_discovery/privet_url_fetcher.h" |
| 13 #include "chrome/common/cloud_print/cloud_print_constants.h" | 19 #include "chrome/common/cloud_print/cloud_print_constants.h" |
| 20 #include "crypto/hmac.h" | |
| 21 #include "crypto/p224_spake.h" | |
| 14 #include "url/gurl.h" | 22 #include "url/gurl.h" |
| 15 | 23 |
| 16 namespace local_discovery { | 24 namespace local_discovery { |
| 17 | 25 |
| 18 namespace { | 26 namespace { |
| 19 | 27 |
| 20 const char kUrlPlaceHolder[] = "http://host/"; | 28 const char kUrlPlaceHolder[] = "http://host/"; |
| 21 | 29 |
| 22 const char kStubPrivetCode[] = "1234"; | 30 const char kStubPrivetCode[] = "1234"; |
| 23 | 31 |
| 24 char kPrivetV3AuthAnonymous[] = "Privet anonymous"; | 32 char kPrivetV3AuthAnonymous[] = "Privet anonymous"; |
| 33 char kPrivetV3CryptoP224Spake2[] = "p224_spake2"; | |
| 34 | |
| 35 char kPrivetV3InfoKeyAuth[] = "authentication"; | |
| 36 char kPrivetV3InfoKeyPairing[] = "authentication"; | |
| 37 char kPrivetV3InfoKeyHttpsPort[] = "endpoints.httpsPort"; | |
| 38 | |
| 39 char kPrivetV3KeyPairing[] = "pairing"; | |
| 40 char kPrivetV3KeyCrypto[] = "crypto"; | |
| 41 char kPrivetV3KeySessionId[] = "sessionId"; | |
| 42 char kPrivetV3KeyDeviceCommitment[] = "deviceCommitment"; | |
| 43 char kPrivetV3KeyClientCommitment[] = "clientCommitment"; | |
| 44 char kPrivetV3KeyCertFingerprint[] = "certFingerprint"; | |
| 45 char kPrivetV3KeyCertSignature[] = "certSignature"; | |
| 46 | |
| 47 char kPrivetV3PairingStartPath[] = "/privet/v3/pairing/start"; | |
| 48 char kPrivetV3PairingConfirmPath[] = "/privet/v3/pairing/confirm"; | |
|
Aleksey Shlyapnikov
2015/01/26 21:47:45
Make them all const char.
Vitaly Buka (NO REVIEWS)
2015/01/27 00:16:33
Done.
| |
| 49 | |
| 25 GURL CreatePrivetURL(const std::string& path) { | 50 GURL CreatePrivetURL(const std::string& path) { |
| 26 GURL url(kUrlPlaceHolder); | 51 GURL url(kUrlPlaceHolder); |
| 27 GURL::Replacements replacements; | 52 GURL::Replacements replacements; |
| 28 replacements.SetPathStr(path); | 53 replacements.SetPathStr(path); |
| 29 return url.ReplaceComponents(replacements); | 54 return url.ReplaceComponents(replacements); |
| 30 } | 55 } |
| 31 | 56 |
| 57 template <typename T> | |
| 58 class EnumToStringMap { | |
| 59 public: | |
| 60 static std::string FindNameById(T id) { | |
| 61 for (const Map& m : kMap) { | |
| 62 if (m.id == id) { | |
| 63 DCHECK(m.name); | |
| 64 return m.name; | |
| 65 } | |
| 66 } | |
| 67 NOTREACHED(); | |
| 68 return std::string(); | |
| 69 } | |
| 70 | |
| 71 static bool FindIdByName(const std::string& name, T* id) { | |
| 72 for (const Map& m : kMap) { | |
| 73 if (m.name && m.name == name) { | |
| 74 *id = m.id; | |
| 75 return true; | |
| 76 } | |
| 77 } | |
| 78 return false; | |
| 79 } | |
| 80 | |
| 81 private: | |
| 82 struct Map { | |
|
Aleksey Shlyapnikov
2015/01/26 21:47:45
Element
Vitaly Buka (NO REVIEWS)
2015/01/27 00:16:33
Done.
| |
| 83 const T id; | |
| 84 const char* const name; | |
| 85 }; | |
| 86 static const Map kMap[]; | |
| 87 }; | |
| 88 | |
| 89 using PairingType = PrivetV3Session::PairingType; | |
| 90 | |
| 91 template <> | |
| 92 const EnumToStringMap<PrivetV3Session::PairingType>::Map | |
| 93 EnumToStringMap<PrivetV3Session::PairingType>::kMap[] = { | |
| 94 {PairingType::PAIRING_TYPE_PINCODE, "pinCode"}, | |
| 95 {PairingType::PAIRING_TYPE_EMBEDDEDCODE, "embeddedCode"}, | |
| 96 {PairingType::PAIRING_TYPE_ULTRASOUNDDSSSBROADCASTER, | |
| 97 "ultrasoundDsssBroadcaster"}, | |
| 98 {PairingType::PAIRING_TYPE_AUDIBLEDTMFBROADCASTER, | |
| 99 "audibleDtmfBroadcaster"}, | |
| 100 }; | |
| 101 | |
| 102 template <typename T> | |
| 103 std::string EnumToString(T id) { | |
| 104 return EnumToStringMap<T>::FindNameById(id); | |
| 105 } | |
| 106 | |
| 107 template <typename T> | |
| 108 bool StringToEnum(const std::string& name, T* id) { | |
| 109 return EnumToStringMap<T>::FindIdByName(name, id); | |
| 110 } | |
| 111 | |
| 112 bool GetDecodedString(const base::DictionaryValue& response, | |
| 113 const std::string& key, | |
| 114 std::string* value) { | |
| 115 std::string base64; | |
| 116 return response.GetString(key, &base64) && base::Base64Decode(base64, value); | |
| 117 } | |
| 118 | |
| 32 } // namespace | 119 } // namespace |
| 33 | 120 |
| 34 class PrivetV3Session::FetcherDelegate : public PrivetURLFetcher::Delegate { | 121 class PrivetV3Session::FetcherDelegate : public PrivetURLFetcher::Delegate { |
| 35 public: | 122 public: |
| 36 FetcherDelegate(const base::WeakPtr<PrivetV3Session>& session, | 123 FetcherDelegate(const base::WeakPtr<PrivetV3Session>& session, |
| 37 const std::string& auth_token, | 124 const std::string& auth_token, |
| 38 const PrivetV3Session::MessageCallback& callback); | 125 const PrivetV3Session::MessageCallback& callback); |
| 39 ~FetcherDelegate() override; | 126 ~FetcherDelegate() override; |
| 40 | 127 |
| 41 // PrivetURLFetcher::Delegate methods. | 128 // PrivetURLFetcher::Delegate methods. |
| 42 std::string GetAuthToken() override; | 129 std::string GetAuthToken() override; |
| 43 void OnNeedPrivetToken( | 130 void OnNeedPrivetToken( |
| 44 PrivetURLFetcher* fetcher, | 131 PrivetURLFetcher* fetcher, |
| 45 const PrivetURLFetcher::TokenCallback& callback) override; | 132 const PrivetURLFetcher::TokenCallback& callback) override; |
| 46 void OnError(PrivetURLFetcher* fetcher, | 133 void OnError(PrivetURLFetcher* fetcher, |
| 47 PrivetURLFetcher::ErrorType error) override; | 134 PrivetURLFetcher::ErrorType error) override; |
| 48 void OnParsedJson(PrivetURLFetcher* fetcher, | 135 void OnParsedJson(PrivetURLFetcher* fetcher, |
| 49 const base::DictionaryValue& value, | 136 const base::DictionaryValue& value, |
| 50 bool has_error) override; | 137 bool has_error) override; |
| 51 | 138 |
| 139 PrivetURLFetcher* CreateURLFetcher(const GURL& url, | |
| 140 net::URLFetcher::RequestType request_type); | |
| 141 | |
| 52 private: | 142 private: |
| 53 friend class PrivetV3Session; | |
| 54 void DeleteThis(); | 143 void DeleteThis(); |
| 55 | 144 |
| 56 scoped_ptr<PrivetURLFetcher> url_fetcher_; | 145 scoped_ptr<PrivetURLFetcher> url_fetcher_; |
| 146 | |
| 57 base::WeakPtr<PrivetV3Session> session_; | 147 base::WeakPtr<PrivetV3Session> session_; |
| 58 std::string auth_token_; | 148 std::string auth_token_; |
| 59 MessageCallback callback_; | 149 MessageCallback callback_; |
| 60 }; | 150 }; |
| 61 | 151 |
| 62 PrivetV3Session::FetcherDelegate::FetcherDelegate( | 152 PrivetV3Session::FetcherDelegate::FetcherDelegate( |
| 63 const base::WeakPtr<PrivetV3Session>& session, | 153 const base::WeakPtr<PrivetV3Session>& session, |
| 64 const std::string& auth_token, | 154 const std::string& auth_token, |
| 65 const PrivetV3Session::MessageCallback& callback) | 155 const PrivetV3Session::MessageCallback& callback) |
| 66 : session_(session), auth_token_(auth_token), callback_(callback) { | 156 : session_(session), auth_token_(auth_token), callback_(callback) { |
| (...skipping 26 matching lines...) Expand all Loading... | |
| 93 PrivetURLFetcher* fetcher, | 183 PrivetURLFetcher* fetcher, |
| 94 const base::DictionaryValue& value, | 184 const base::DictionaryValue& value, |
| 95 bool has_error) { | 185 bool has_error) { |
| 96 if (session_) { | 186 if (session_) { |
| 97 DeleteThis(); | 187 DeleteThis(); |
| 98 callback_.Run( | 188 callback_.Run( |
| 99 has_error ? Result::STATUS_DEVICEERROR : Result::STATUS_SUCCESS, value); | 189 has_error ? Result::STATUS_DEVICEERROR : Result::STATUS_SUCCESS, value); |
| 100 } | 190 } |
| 101 } | 191 } |
| 102 | 192 |
| 193 PrivetURLFetcher* PrivetV3Session::FetcherDelegate::CreateURLFetcher( | |
| 194 const GURL& url, | |
| 195 net::URLFetcher::RequestType request_type) { | |
| 196 DCHECK(!url_fetcher_); | |
| 197 url_fetcher_ = | |
| 198 session_->client_->CreateURLFetcher(url, request_type, this).Pass(); | |
| 199 url_fetcher_->V3Mode(); | |
| 200 return url_fetcher_.get(); | |
| 201 } | |
| 202 | |
| 103 void PrivetV3Session::FetcherDelegate::DeleteThis() { | 203 void PrivetV3Session::FetcherDelegate::DeleteThis() { |
| 104 base::MessageLoop::current()->PostTask( | 204 base::MessageLoop::current()->PostTask( |
| 105 FROM_HERE, base::Bind(&PrivetV3Session::DeleteFetcher, session_, | 205 FROM_HERE, base::Bind(&PrivetV3Session::DeleteFetcher, session_, |
| 106 base::Unretained(this))); | 206 base::Unretained(this))); |
| 107 } | 207 } |
| 108 | 208 |
| 109 PrivetV3Session::PrivetV3Session(scoped_ptr<PrivetHTTPClient> client) | 209 PrivetV3Session::PrivetV3Session(scoped_ptr<PrivetHTTPClient> client) |
| 110 : client_(client.Pass()), code_confirmed_(false), weak_ptr_factory_(this) { | 210 : client_(client.Pass()), weak_ptr_factory_(this) { |
| 111 } | 211 } |
| 112 | 212 |
| 113 PrivetV3Session::~PrivetV3Session() { | 213 PrivetV3Session::~PrivetV3Session() { |
| 114 } | 214 } |
| 115 | 215 |
| 116 void PrivetV3Session::Init(const InitCallback& callback) { | 216 void PrivetV3Session::Init(const InitCallback& callback) { |
| 117 privet_auth_token_ = kPrivetV3AuthAnonymous; | 217 privet_auth_token_ = kPrivetV3AuthAnonymous; |
| 118 | 218 |
| 119 // TODO: call /info. | 219 DCHECK(fetchers_.empty()); |
| 120 base::MessageLoop::current()->PostDelayedTask( | 220 |
| 121 FROM_HERE, | 221 privet_auth_token_ = kPrivetV3AuthAnonymous; |
| 122 base::Bind(&PrivetV3Session::RunCallback, weak_ptr_factory_.GetWeakPtr(), | 222 |
| 123 base::Bind(callback, Result::STATUS_SUCCESS, | 223 StartGetRequest(kPrivetInfoPath, |
| 124 std::vector<PairingType>( | 224 base::Bind(&PrivetV3Session::OnInfoDone, |
| 125 1, PairingType::PAIRING_TYPE_EMBEDDEDCODE))), | 225 weak_ptr_factory_.GetWeakPtr(), callback)); |
| 126 base::TimeDelta::FromSeconds(1)); | 226 } |
| 227 | |
| 228 void PrivetV3Session::OnInfoDone(const InitCallback& callback, | |
| 229 Result result, | |
| 230 const base::DictionaryValue& response) { | |
| 231 std::vector<PairingType> pairing_types; | |
| 232 if (result != Result::STATUS_SUCCESS) | |
| 233 return callback.Run(result, pairing_types); | |
| 234 | |
| 235 const base::DictionaryValue* authentication = nullptr; | |
| 236 const base::ListValue* pairing = nullptr; | |
| 237 const base::ListValue* crypto = nullptr; | |
| 238 if (!response.GetDictionary(kPrivetV3InfoKeyAuth, &authentication) || | |
| 239 !authentication->GetList(kPrivetV3KeyPairing, &pairing) || | |
| 240 !authentication->GetList(kPrivetV3KeyCrypto, &crypto)) { | |
| 241 return callback.Run(Result::STATUS_SESSIONERROR, pairing_types); | |
| 242 } | |
| 243 | |
| 244 bool has_compartible_crypto = false; | |
|
Aleksey Shlyapnikov
2015/01/26 21:47:45
has_compatible_crypto
Vitaly Buka (NO REVIEWS)
2015/01/27 00:16:33
Done.
| |
| 245 for (const base::Value* value : *crypto) { | |
| 246 std::string crypto_string; | |
| 247 // The only supported crypto. | |
| 248 if (value->GetAsString(&crypto_string) && | |
| 249 crypto_string == kPrivetV3CryptoP224Spake2) { | |
| 250 has_compartible_crypto = true; | |
| 251 break; | |
| 252 } | |
| 253 } | |
| 254 | |
| 255 if (!has_compartible_crypto) | |
| 256 return callback.Run(Result::STATUS_SESSIONERROR, pairing_types); | |
| 257 | |
| 258 for (const base::Value* value : *pairing) { | |
| 259 std::string pairing_string; | |
| 260 PairingType pairing_type; | |
| 261 if (!value->GetAsString(&pairing_string) || | |
| 262 !StringToEnum(pairing_string, &pairing_type)) { | |
| 263 continue; // Skip unknown pairing. | |
| 264 } | |
| 265 pairing_types.push_back(pairing_type); | |
| 266 } | |
| 267 | |
| 268 callback.Run(Result::STATUS_SUCCESS, pairing_types); | |
| 127 } | 269 } |
| 128 | 270 |
| 129 void PrivetV3Session::StartPairing(PairingType pairing_type, | 271 void PrivetV3Session::StartPairing(PairingType pairing_type, |
| 130 const ResultCallback& callback) { | 272 const ResultCallback& callback) { |
| 131 // TODO: call /privet/v3/pairing/start. | 273 base::DictionaryValue input; |
| 132 base::MessageLoop::current()->PostDelayedTask( | 274 input.SetString(kPrivetV3KeyPairing, EnumToString(pairing_type)); |
| 133 FROM_HERE, | 275 input.SetString(kPrivetV3KeyCrypto, kPrivetV3CryptoP224Spake2); |
| 134 base::Bind(&PrivetV3Session::RunCallback, weak_ptr_factory_.GetWeakPtr(), | 276 |
| 135 base::Bind(callback, Result::STATUS_SUCCESS)), | 277 StartPostRequest(kPrivetV3PairingStartPath, input, |
| 136 base::TimeDelta::FromSeconds(1)); | 278 base::Bind(&PrivetV3Session::OnPairingStartDone, |
| 279 weak_ptr_factory_.GetWeakPtr(), callback)); | |
| 280 } | |
| 281 | |
| 282 void PrivetV3Session::OnPairingStartDone( | |
| 283 const ResultCallback& callback, | |
| 284 Result result, | |
| 285 const base::DictionaryValue& response) { | |
| 286 if (result != Result::STATUS_SUCCESS) | |
| 287 return callback.Run(result); | |
| 288 | |
| 289 if (!response.GetString(kPrivetV3KeySessionId, &session_id_) || | |
| 290 !GetDecodedString(response, kPrivetV3KeyDeviceCommitment, &commitment_)) { | |
| 291 return callback.Run(Result::STATUS_SESSIONERROR); | |
| 292 } | |
| 293 | |
| 294 return callback.Run(Result::STATUS_SUCCESS); | |
| 137 } | 295 } |
| 138 | 296 |
| 139 void PrivetV3Session::ConfirmCode(const std::string& code, | 297 void PrivetV3Session::ConfirmCode(const std::string& code, |
| 140 const ResultCallback& callback) { | 298 const ResultCallback& callback) { |
| 141 // TODO: call /privet/v3/pairing/confirm. | 299 spake_.reset(new crypto::P224EncryptedKeyExchange( |
| 142 if (code == kStubPrivetCode) { | 300 crypto::P224EncryptedKeyExchange::kPeerTypeClient, code)); |
| 143 code_confirmed_ = true; | 301 |
| 144 callback.Run(Result::STATUS_SUCCESS); | 302 base::DictionaryValue input; |
| 145 } else { | 303 input.SetString(kPrivetV3KeySessionId, session_id_); |
| 146 callback.Run(Result::STATUS_BADPAIRINGCODEERROR); | 304 |
| 305 std::string client_commitment; | |
| 306 base::Base64Encode(spake_->GetMessage(), &client_commitment); | |
| 307 input.SetString(kPrivetV3KeyClientCommitment, client_commitment); | |
| 308 | |
| 309 // Call ProcessMessage after GetMessage(). | |
| 310 crypto::P224EncryptedKeyExchange::Result result = | |
| 311 spake_->ProcessMessage(commitment_); | |
| 312 DCHECK_EQ(result, crypto::P224EncryptedKeyExchange::kResultPending); | |
| 313 | |
| 314 StartPostRequest(kPrivetV3PairingConfirmPath, input, | |
| 315 base::Bind(&PrivetV3Session::OnPairingConfirmDone, | |
| 316 weak_ptr_factory_.GetWeakPtr(), callback)); | |
| 317 } | |
| 318 | |
| 319 void PrivetV3Session::OnPairingConfirmDone( | |
| 320 const ResultCallback& callback, | |
| 321 Result result, | |
| 322 const base::DictionaryValue& response) { | |
| 323 if (result != Result::STATUS_SUCCESS) | |
| 324 return callback.Run(result); | |
| 325 | |
| 326 std::string fingerprint; | |
| 327 std::string signature; | |
| 328 if (!GetDecodedString(response, kPrivetV3KeyCertFingerprint, &fingerprint) || | |
| 329 !GetDecodedString(response, kPrivetV3KeyCertSignature, &signature)) { | |
| 330 return callback.Run(Result::STATUS_SESSIONERROR); | |
| 147 } | 331 } |
| 332 | |
| 333 crypto::HMAC hmac(crypto::HMAC::SHA256); | |
| 334 // Key will be verified below, using HMAC. | |
| 335 const std::string& key = spake_->GetUnverifiedKey(); | |
| 336 if (!hmac.Init(reinterpret_cast<const unsigned char*>(key.c_str()), | |
| 337 key.size()) || | |
| 338 !hmac.Verify(fingerprint, signature)) { | |
| 339 return callback.Run(Result::STATUS_SESSIONERROR); | |
| 340 } | |
| 341 | |
| 342 return callback.Run(Result::STATUS_SUCCESS); | |
| 148 } | 343 } |
| 149 | 344 |
| 150 void PrivetV3Session::SendMessage(const std::string& api, | 345 void PrivetV3Session::SendMessage(const std::string& api, |
| 151 const base::DictionaryValue& input, | 346 const base::DictionaryValue& input, |
| 152 const MessageCallback& callback) { | 347 const MessageCallback& callback) { |
| 153 if (!code_confirmed_) | 348 // TODO(vitalybuka): Implement validating HTTPS certificate using |
| 349 // fingerprint_. | |
| 350 if (fingerprint_.empty()) | |
| 154 return callback.Run(Result::STATUS_SESSIONERROR, base::DictionaryValue()); | 351 return callback.Run(Result::STATUS_SESSIONERROR, base::DictionaryValue()); |
| 155 | 352 |
| 156 FetcherDelegate* fetcher_delegate(new FetcherDelegate( | 353 StartPostRequest(api, input, callback); |
| 157 weak_ptr_factory_.GetWeakPtr(), privet_auth_token_, callback)); | |
| 158 fetchers_.push_back(fetcher_delegate); | |
| 159 | |
| 160 scoped_ptr<PrivetURLFetcher> url_fetcher(client_->CreateURLFetcher( | |
| 161 CreatePrivetURL(api), net::URLFetcher::POST, fetcher_delegate)); | |
| 162 | |
| 163 std::string json; | |
| 164 base::JSONWriter::WriteWithOptions( | |
| 165 &input, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json); | |
| 166 url_fetcher->SetUploadData(cloud_print::kContentTypeJSON, json); | |
| 167 | |
| 168 fetcher_delegate->url_fetcher_ = url_fetcher.Pass(); | |
| 169 fetcher_delegate->url_fetcher_->V3Mode(); | |
| 170 fetcher_delegate->url_fetcher_->Start(); | |
| 171 } | 354 } |
| 172 | 355 |
| 173 void PrivetV3Session::RunCallback(const base::Closure& callback) { | 356 void PrivetV3Session::RunCallback(const base::Closure& callback) { |
| 174 callback.Run(); | 357 callback.Run(); |
| 175 } | 358 } |
| 176 | 359 |
| 360 void PrivetV3Session::StartGetRequest(const std::string& api, | |
| 361 const MessageCallback& callback) { | |
| 362 CreateFetcher(api, net::URLFetcher::RequestType::GET, callback)->Start(); | |
| 363 } | |
| 364 | |
| 365 void PrivetV3Session::StartPostRequest(const std::string& api, | |
| 366 const base::DictionaryValue& input, | |
| 367 const MessageCallback& callback) { | |
| 368 PrivetURLFetcher* fetcher = | |
| 369 CreateFetcher(api, net::URLFetcher::RequestType::POST, callback); | |
| 370 | |
| 371 std::string json; | |
| 372 base::JSONWriter::WriteWithOptions( | |
| 373 &input, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json); | |
| 374 fetcher->SetUploadData(cloud_print::kContentTypeJSON, json); | |
| 375 fetcher->Start(); | |
| 376 } | |
| 377 | |
| 378 PrivetURLFetcher* PrivetV3Session::CreateFetcher( | |
| 379 const std::string& api, | |
| 380 net::URLFetcher::RequestType request_type, | |
| 381 const MessageCallback& callback) { | |
| 382 fetchers_.push_back(new FetcherDelegate(weak_ptr_factory_.GetWeakPtr(), | |
| 383 privet_auth_token_, callback)); | |
| 384 return fetchers_.back()->CreateURLFetcher(CreatePrivetURL(api), request_type); | |
| 385 } | |
| 386 | |
| 177 void PrivetV3Session::DeleteFetcher(const FetcherDelegate* fetcher) { | 387 void PrivetV3Session::DeleteFetcher(const FetcherDelegate* fetcher) { |
| 178 fetchers_.erase(std::find(fetchers_.begin(), fetchers_.end(), fetcher)); | 388 fetchers_.erase(std::find(fetchers_.begin(), fetchers_.end(), fetcher)); |
| 179 } | 389 } |
| 180 | 390 |
| 181 } // namespace local_discovery | 391 } // namespace local_discovery |
| OLD | NEW |