- add sources.
[platform/framework/web/crosswalk.git] / src / content / browser / geolocation / wifi_data_provider_win.cc
1 // Copyright (c) 2010 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 // Windows Vista uses the Native Wifi (WLAN) API for accessing WiFi cards. See
6 // http://msdn.microsoft.com/en-us/library/ms705945(VS.85).aspx. Windows XP
7 // Service Pack 3 (and Windows XP Service Pack 2, if upgraded with a hot fix)
8 // also support a limited version of the WLAN API. See
9 // http://msdn.microsoft.com/en-us/library/bb204766.aspx. The WLAN API uses
10 // wlanapi.h, which is not part of the SDK used by Gears, so is replicated
11 // locally using data from the MSDN.
12 //
13 // Windows XP from Service Pack 2 onwards supports the Wireless Zero
14 // Configuration (WZC) programming interface. See
15 // http://msdn.microsoft.com/en-us/library/ms706587(VS.85).aspx.
16 //
17 // The MSDN recommends that one use the WLAN API where available, and WZC
18 // otherwise.
19 //
20 // However, it seems that WZC fails for some wireless cards. Also, WLAN seems
21 // not to work on XP SP3. So we use WLAN on Vista, and use NDIS directly
22 // otherwise.
23
24 #include "content/browser/geolocation/wifi_data_provider_win.h"
25
26 #include <windows.h>
27 #include <winioctl.h>
28 #include <wlanapi.h>
29
30 #include "base/metrics/histogram.h"
31 #include "base/strings/utf_string_conversions.h"
32 #include "base/win/windows_version.h"
33 #include "content/browser/geolocation/wifi_data_provider_common.h"
34 #include "content/browser/geolocation/wifi_data_provider_common_win.h"
35
36 // Taken from ndis.h for WinCE.
37 #define NDIS_STATUS_INVALID_LENGTH   ((NDIS_STATUS)0xC0010014L)
38 #define NDIS_STATUS_BUFFER_TOO_SHORT ((NDIS_STATUS)0xC0010016L)
39
40 namespace content {
41 namespace {
42 // The limits on the size of the buffer used for the OID query.
43 const int kInitialBufferSize = 2 << 12;  // Good for about 50 APs.
44 const int kMaximumBufferSize = 2 << 20;  // 2MB
45
46 // Length for generic string buffers passed to Win32 APIs.
47 const int kStringLength = 512;
48
49 // The time periods, in milliseconds, between successive polls of the wifi data.
50 const int kDefaultPollingInterval = 10000;  // 10s
51 const int kNoChangePollingInterval = 120000;  // 2 mins
52 const int kTwoNoChangePollingInterval = 600000;  // 10 mins
53 const int kNoWifiPollingIntervalMilliseconds = 20 * 1000; // 20s
54
55 // WlanOpenHandle
56 typedef DWORD (WINAPI* WlanOpenHandleFunction)(DWORD dwClientVersion,
57                                                PVOID pReserved,
58                                                PDWORD pdwNegotiatedVersion,
59                                                PHANDLE phClientHandle);
60
61 // WlanEnumInterfaces
62 typedef DWORD (WINAPI* WlanEnumInterfacesFunction)(
63     HANDLE hClientHandle,
64     PVOID pReserved,
65     PWLAN_INTERFACE_INFO_LIST* ppInterfaceList);
66
67 // WlanGetNetworkBssList
68 typedef DWORD (WINAPI* WlanGetNetworkBssListFunction)(
69     HANDLE hClientHandle,
70     const GUID* pInterfaceGuid,
71     const  PDOT11_SSID pDot11Ssid,
72     DOT11_BSS_TYPE dot11BssType,
73     BOOL bSecurityEnabled,
74     PVOID pReserved,
75     PWLAN_BSS_LIST* ppWlanBssList
76 );
77
78 // WlanFreeMemory
79 typedef VOID (WINAPI* WlanFreeMemoryFunction)(PVOID pMemory);
80
81 // WlanCloseHandle
82 typedef DWORD (WINAPI* WlanCloseHandleFunction)(HANDLE hClientHandle,
83                                                 PVOID pReserved);
84
85
86 // Local classes and functions
87 class WindowsWlanApi : public WifiDataProviderCommon::WlanApiInterface {
88  public:
89   virtual ~WindowsWlanApi();
90   // Factory function. Will return NULL if this API is unavailable.
91   static WindowsWlanApi* Create();
92
93   // WlanApiInterface
94   virtual bool GetAccessPointData(WifiData::AccessPointDataSet* data);
95
96  private:
97   // Takes ownership of the library handle.
98   explicit WindowsWlanApi(HINSTANCE library);
99
100   // Loads the required functions from the DLL.
101   void GetWLANFunctions(HINSTANCE wlan_library);
102   int GetInterfaceDataWLAN(HANDLE wlan_handle,
103                            const GUID& interface_id,
104                            WifiData::AccessPointDataSet* data);
105
106   // Logs number of detected wlan interfaces.
107   static void LogWlanInterfaceCount(int count);
108
109   // Handle to the wlanapi.dll library.
110   HINSTANCE library_;
111
112   // Function pointers for WLAN
113   WlanOpenHandleFunction WlanOpenHandle_function_;
114   WlanEnumInterfacesFunction WlanEnumInterfaces_function_;
115   WlanGetNetworkBssListFunction WlanGetNetworkBssList_function_;
116   WlanFreeMemoryFunction WlanFreeMemory_function_;
117   WlanCloseHandleFunction WlanCloseHandle_function_;
118 };
119
120 class WindowsNdisApi : public WifiDataProviderCommon::WlanApiInterface {
121  public:
122   virtual ~WindowsNdisApi();
123   static WindowsNdisApi* Create();
124
125   // WlanApiInterface
126   virtual bool GetAccessPointData(WifiData::AccessPointDataSet* data);
127
128  private:
129   static bool GetInterfacesNDIS(
130       std::vector<string16>* interface_service_names_out);
131
132   // Swaps in content of the vector passed
133   explicit WindowsNdisApi(std::vector<string16>* interface_service_names);
134
135   bool GetInterfaceDataNDIS(HANDLE adapter_handle,
136                             WifiData::AccessPointDataSet* data);
137   // NDIS variables.
138   std::vector<string16> interface_service_names_;
139
140   // Remembers scan result buffer size across calls.
141   int oid_buffer_size_;
142 };
143
144 // Extracts data for an access point and converts to Gears format.
145 bool GetNetworkData(const WLAN_BSS_ENTRY& bss_entry,
146                     AccessPointData* access_point_data);
147 bool UndefineDosDevice(const string16& device_name);
148 bool DefineDosDeviceIfNotExists(const string16& device_name);
149 HANDLE GetFileHandle(const string16& device_name);
150 // Makes the OID query and returns a Win32 error code.
151 int PerformQuery(HANDLE adapter_handle,
152                  BYTE* buffer,
153                  DWORD buffer_size,
154                  DWORD* bytes_out);
155 bool ResizeBuffer(int requested_size, scoped_ptr_malloc<BYTE>* buffer);
156 // Gets the system directory and appends a trailing slash if not already
157 // present.
158 bool GetSystemDirectory(string16* path);
159 }  // namespace
160
161 WifiDataProviderImplBase* WifiDataProvider::DefaultFactoryFunction() {
162   return new Win32WifiDataProvider();
163 }
164
165 Win32WifiDataProvider::Win32WifiDataProvider() {
166 }
167
168 Win32WifiDataProvider::~Win32WifiDataProvider() {
169 }
170
171 WifiDataProviderCommon::WlanApiInterface* Win32WifiDataProvider::NewWlanApi() {
172   // Use the WLAN interface if we're on Vista and if it's available. Otherwise,
173   // use NDIS.
174   WlanApiInterface* api = WindowsWlanApi::Create();
175   if (api) {
176     return api;
177   }
178   return WindowsNdisApi::Create();
179 }
180
181 WifiPollingPolicy* Win32WifiDataProvider::NewPollingPolicy() {
182   return new GenericWifiPollingPolicy<kDefaultPollingInterval,
183                                       kNoChangePollingInterval,
184                                       kTwoNoChangePollingInterval,
185                                       kNoWifiPollingIntervalMilliseconds>;
186 }
187
188 // Local classes and functions
189 namespace {
190
191 // WindowsWlanApi
192 WindowsWlanApi::WindowsWlanApi(HINSTANCE library)
193     : library_(library) {
194   GetWLANFunctions(library_);
195 }
196
197 WindowsWlanApi::~WindowsWlanApi() {
198   FreeLibrary(library_);
199 }
200
201 WindowsWlanApi* WindowsWlanApi::Create() {
202   if (base::win::GetVersion() < base::win::VERSION_VISTA)
203     return NULL;
204   // We use an absolute path to load the DLL to avoid DLL preloading attacks.
205   string16 system_directory;
206   if (!GetSystemDirectory(&system_directory)) {
207     return NULL;
208   }
209   DCHECK(!system_directory.empty());
210   string16 dll_path = system_directory + L"wlanapi.dll";
211   HINSTANCE library = LoadLibraryEx(dll_path.c_str(),
212                                     NULL,
213                                     LOAD_WITH_ALTERED_SEARCH_PATH);
214   if (!library) {
215     return NULL;
216   }
217   return new WindowsWlanApi(library);
218 }
219
220 void WindowsWlanApi::GetWLANFunctions(HINSTANCE wlan_library) {
221   DCHECK(wlan_library);
222   WlanOpenHandle_function_ = reinterpret_cast<WlanOpenHandleFunction>(
223       GetProcAddress(wlan_library, "WlanOpenHandle"));
224   WlanEnumInterfaces_function_ = reinterpret_cast<WlanEnumInterfacesFunction>(
225       GetProcAddress(wlan_library, "WlanEnumInterfaces"));
226   WlanGetNetworkBssList_function_ =
227       reinterpret_cast<WlanGetNetworkBssListFunction>(
228       GetProcAddress(wlan_library, "WlanGetNetworkBssList"));
229   WlanFreeMemory_function_ = reinterpret_cast<WlanFreeMemoryFunction>(
230       GetProcAddress(wlan_library, "WlanFreeMemory"));
231   WlanCloseHandle_function_ = reinterpret_cast<WlanCloseHandleFunction>(
232       GetProcAddress(wlan_library, "WlanCloseHandle"));
233   DCHECK(WlanOpenHandle_function_ &&
234          WlanEnumInterfaces_function_ &&
235          WlanGetNetworkBssList_function_ &&
236          WlanFreeMemory_function_ &&
237          WlanCloseHandle_function_);
238 }
239
240 void WindowsWlanApi::LogWlanInterfaceCount(int count) {
241   UMA_HISTOGRAM_CUSTOM_COUNTS(
242       "Net.Wifi.InterfaceCount",
243       count,
244       1,
245       5,
246       5);
247 }
248
249 bool WindowsWlanApi::GetAccessPointData(
250     WifiData::AccessPointDataSet* data) {
251   DCHECK(data);
252
253   // Get the handle to the WLAN API.
254   DWORD negotiated_version;
255   HANDLE wlan_handle = NULL;
256   // We could be executing on either Windows XP or Windows Vista, so use the
257   // lower version of the client WLAN API. It seems that the negotiated version
258   // is the Vista version irrespective of what we pass!
259   static const int kXpWlanClientVersion = 1;
260   if ((*WlanOpenHandle_function_)(kXpWlanClientVersion,
261                                   NULL,
262                                   &negotiated_version,
263                                   &wlan_handle) != ERROR_SUCCESS) {
264     LogWlanInterfaceCount(0);
265     return false;
266   }
267   DCHECK(wlan_handle);
268
269   // Get the list of interfaces. WlanEnumInterfaces allocates interface_list.
270   WLAN_INTERFACE_INFO_LIST* interface_list = NULL;
271   if ((*WlanEnumInterfaces_function_)(wlan_handle, NULL, &interface_list) !=
272       ERROR_SUCCESS) {
273     LogWlanInterfaceCount(0);
274     return false;
275   }
276   DCHECK(interface_list);
277
278   LogWlanInterfaceCount(interface_list->dwNumberOfItems);
279
280   // Go through the list of interfaces and get the data for each.
281   for (int i = 0; i < static_cast<int>(interface_list->dwNumberOfItems); ++i) {
282     // Skip any interface that is midway through association; the
283     // WlanGetNetworkBssList function call is known to hang indefinitely
284     // when it's in this state. http://crbug.com/39300
285     if (interface_list->InterfaceInfo[i].isState ==
286         wlan_interface_state_associating) {
287       LOG(WARNING) << "Skipping wifi scan on adapter " << i << " ("
288                    << interface_list->InterfaceInfo[i].strInterfaceDescription
289                    << ") in 'associating' state. Repeated occurrences "
290                       "indicates a non-responding adapter.";
291       continue;
292     }
293     GetInterfaceDataWLAN(wlan_handle,
294                          interface_list->InterfaceInfo[i].InterfaceGuid,
295                          data);
296   }
297
298   // Free interface_list.
299   (*WlanFreeMemory_function_)(interface_list);
300
301   // Close the handle.
302   if ((*WlanCloseHandle_function_)(wlan_handle, NULL) != ERROR_SUCCESS) {
303     return false;
304   }
305
306   return true;
307 }
308
309 // Appends the data for a single interface to the data vector. Returns the
310 // number of access points found, or -1 on error.
311 int WindowsWlanApi::GetInterfaceDataWLAN(
312     const HANDLE wlan_handle,
313     const GUID& interface_id,
314     WifiData::AccessPointDataSet* data) {
315   DCHECK(data);
316
317   const base::TimeTicks start_time = base::TimeTicks::Now();
318
319   // WlanGetNetworkBssList allocates bss_list.
320   WLAN_BSS_LIST* bss_list = NULL;
321   if ((*WlanGetNetworkBssList_function_)(wlan_handle,
322                                          &interface_id,
323                                          NULL,   // Use all SSIDs.
324                                          dot11_BSS_type_any,
325                                          false,  // bSecurityEnabled - unused
326                                          NULL,   // reserved
327                                          &bss_list) != ERROR_SUCCESS) {
328     return -1;
329   }
330   // According to http://www.attnetclient.com/kb/questions.php?questionid=75
331   // WlanGetNetworkBssList can sometimes return success, but leave the bss
332   // list as NULL.
333   if (!bss_list)
334     return -1;
335
336   const base::TimeDelta duration = base::TimeTicks::Now() - start_time;
337
338   UMA_HISTOGRAM_CUSTOM_TIMES(
339       "Net.Wifi.ScanLatency",
340       duration,
341       base::TimeDelta::FromMilliseconds(1),
342       base::TimeDelta::FromMinutes(1),
343       100);
344
345
346   int found = 0;
347   for (int i = 0; i < static_cast<int>(bss_list->dwNumberOfItems); ++i) {
348     AccessPointData access_point_data;
349     if (GetNetworkData(bss_list->wlanBssEntries[i], &access_point_data)) {
350       ++found;
351       data->insert(access_point_data);
352     }
353   }
354
355   (*WlanFreeMemory_function_)(bss_list);
356
357   return found;
358 }
359
360 // WindowsNdisApi
361 WindowsNdisApi::WindowsNdisApi(
362     std::vector<string16>* interface_service_names)
363     : oid_buffer_size_(kInitialBufferSize) {
364   DCHECK(!interface_service_names->empty());
365   interface_service_names_.swap(*interface_service_names);
366 }
367
368 WindowsNdisApi::~WindowsNdisApi() {
369 }
370
371 WindowsNdisApi* WindowsNdisApi::Create() {
372   std::vector<string16> interface_service_names;
373   if (GetInterfacesNDIS(&interface_service_names)) {
374     return new WindowsNdisApi(&interface_service_names);
375   }
376   return NULL;
377 }
378
379 bool WindowsNdisApi::GetAccessPointData(WifiData::AccessPointDataSet* data) {
380   DCHECK(data);
381   int interfaces_failed = 0;
382   int interfaces_succeeded = 0;
383
384   for (int i = 0; i < static_cast<int>(interface_service_names_.size()); ++i) {
385     // First, check that we have a DOS device for this adapter.
386     if (!DefineDosDeviceIfNotExists(interface_service_names_[i])) {
387       continue;
388     }
389
390     // Get the handle to the device. This will fail if the named device is not
391     // valid.
392     HANDLE adapter_handle = GetFileHandle(interface_service_names_[i]);
393     if (adapter_handle == INVALID_HANDLE_VALUE) {
394       continue;
395     }
396
397     // Get the data.
398     if (GetInterfaceDataNDIS(adapter_handle, data)) {
399       ++interfaces_succeeded;
400     } else {
401       ++interfaces_failed;
402     }
403
404     // Clean up.
405     CloseHandle(adapter_handle);
406     UndefineDosDevice(interface_service_names_[i]);
407   }
408
409   // Return true if at least one interface succeeded, or at the very least none
410   // failed.
411   return interfaces_succeeded > 0 || interfaces_failed == 0;
412 }
413
414 bool WindowsNdisApi::GetInterfacesNDIS(
415     std::vector<string16>* interface_service_names_out) {
416   HKEY network_cards_key = NULL;
417   if (RegOpenKeyEx(
418       HKEY_LOCAL_MACHINE,
419       L"Software\\Microsoft\\Windows NT\\CurrentVersion\\NetworkCards",
420       0,
421       KEY_READ,
422       &network_cards_key) != ERROR_SUCCESS) {
423     return false;
424   }
425   DCHECK(network_cards_key);
426
427   for (int i = 0; ; ++i) {
428     TCHAR name[kStringLength];
429     DWORD name_size = kStringLength;
430     FILETIME time;
431     if (RegEnumKeyEx(network_cards_key,
432                      i,
433                      name,
434                      &name_size,
435                      NULL,
436                      NULL,
437                      NULL,
438                      &time) != ERROR_SUCCESS) {
439       break;
440     }
441     HKEY hardware_key = NULL;
442     if (RegOpenKeyEx(network_cards_key, name, 0, KEY_READ, &hardware_key) !=
443         ERROR_SUCCESS) {
444       break;
445     }
446     DCHECK(hardware_key);
447
448     TCHAR service_name[kStringLength];
449     DWORD service_name_size = kStringLength;
450     DWORD type = 0;
451     if (RegQueryValueEx(hardware_key,
452                         L"ServiceName",
453                         NULL,
454                         &type,
455                         reinterpret_cast<LPBYTE>(service_name),
456                         &service_name_size) == ERROR_SUCCESS) {
457       interface_service_names_out->push_back(service_name);
458     }
459     RegCloseKey(hardware_key);
460   }
461
462   RegCloseKey(network_cards_key);
463   return true;
464 }
465
466
467 bool WindowsNdisApi::GetInterfaceDataNDIS(HANDLE adapter_handle,
468                                           WifiData::AccessPointDataSet* data) {
469   DCHECK(data);
470
471   scoped_ptr_malloc<BYTE> buffer(
472       reinterpret_cast<BYTE*>(malloc(oid_buffer_size_)));
473   if (buffer == NULL) {
474     return false;
475   }
476
477   DWORD bytes_out;
478   int result;
479
480   while (true) {
481     bytes_out = 0;
482     result = PerformQuery(adapter_handle, buffer.get(),
483                           oid_buffer_size_, &bytes_out);
484     if (result == ERROR_GEN_FAILURE ||  // Returned by some Intel cards.
485         result == ERROR_INSUFFICIENT_BUFFER ||
486         result == ERROR_MORE_DATA ||
487         result == NDIS_STATUS_INVALID_LENGTH ||
488         result == NDIS_STATUS_BUFFER_TOO_SHORT) {
489       // The buffer we supplied is too small, so increase it. bytes_out should
490       // provide the required buffer size, but this is not always the case.
491       if (bytes_out > static_cast<DWORD>(oid_buffer_size_)) {
492         oid_buffer_size_ = bytes_out;
493       } else {
494         oid_buffer_size_ *= 2;
495       }
496       if (!ResizeBuffer(oid_buffer_size_, &buffer)) {
497         oid_buffer_size_ = kInitialBufferSize;  // Reset for next time.
498         return false;
499       }
500     } else {
501       // The buffer is not too small.
502       break;
503     }
504   }
505   DCHECK(buffer.get());
506
507   if (result == ERROR_SUCCESS) {
508     NDIS_802_11_BSSID_LIST* bssid_list =
509         reinterpret_cast<NDIS_802_11_BSSID_LIST*>(buffer.get());
510     GetDataFromBssIdList(*bssid_list, oid_buffer_size_, data);
511   }
512
513   return true;
514 }
515
516 bool GetNetworkData(const WLAN_BSS_ENTRY& bss_entry,
517                     AccessPointData* access_point_data) {
518   // Currently we get only MAC address, signal strength and SSID.
519   DCHECK(access_point_data);
520   access_point_data->mac_address = MacAddressAsString16(bss_entry.dot11Bssid);
521   access_point_data->radio_signal_strength = bss_entry.lRssi;
522   // bss_entry.dot11Ssid.ucSSID is not null-terminated.
523   UTF8ToUTF16(reinterpret_cast<const char*>(bss_entry.dot11Ssid.ucSSID),
524               static_cast<ULONG>(bss_entry.dot11Ssid.uSSIDLength),
525               &access_point_data->ssid);
526   // TODO(steveblock): Is it possible to get the following?
527   // access_point_data->signal_to_noise
528   // access_point_data->age
529   // access_point_data->channel
530   return true;
531 }
532
533 bool UndefineDosDevice(const string16& device_name) {
534   // We remove only the mapping we use, that is \Device\<device_name>.
535   string16 target_path = L"\\Device\\" + device_name;
536   return DefineDosDevice(
537       DDD_RAW_TARGET_PATH | DDD_REMOVE_DEFINITION | DDD_EXACT_MATCH_ON_REMOVE,
538       device_name.c_str(),
539       target_path.c_str()) == TRUE;
540 }
541
542 bool DefineDosDeviceIfNotExists(const string16& device_name) {
543   // We create a DOS device name for the device at \Device\<device_name>.
544   string16 target_path = L"\\Device\\" + device_name;
545
546   TCHAR target[kStringLength];
547   if (QueryDosDevice(device_name.c_str(), target, kStringLength) > 0 &&
548       target_path.compare(target) == 0) {
549     // Device already exists.
550     return true;
551   }
552
553   if (GetLastError() != ERROR_FILE_NOT_FOUND) {
554     return false;
555   }
556
557   if (!DefineDosDevice(DDD_RAW_TARGET_PATH,
558                        device_name.c_str(),
559                        target_path.c_str())) {
560     return false;
561   }
562
563   // Check that the device is really there.
564   return QueryDosDevice(device_name.c_str(), target, kStringLength) > 0 &&
565       target_path.compare(target) == 0;
566 }
567
568 HANDLE GetFileHandle(const string16& device_name) {
569   // We access a device with DOS path \Device\<device_name> at
570   // \\.\<device_name>.
571   string16 formatted_device_name = L"\\\\.\\" + device_name;
572
573   return CreateFile(formatted_device_name.c_str(),
574                     GENERIC_READ,
575                     FILE_SHARE_READ | FILE_SHARE_WRITE,  // share mode
576                     0,  // security attributes
577                     OPEN_EXISTING,
578                     0,  // flags and attributes
579                     INVALID_HANDLE_VALUE);
580 }
581
582 int PerformQuery(HANDLE adapter_handle,
583                  BYTE* buffer,
584                  DWORD buffer_size,
585                  DWORD* bytes_out) {
586   DWORD oid = OID_802_11_BSSID_LIST;
587   if (!DeviceIoControl(adapter_handle,
588                        IOCTL_NDIS_QUERY_GLOBAL_STATS,
589                        &oid,
590                        sizeof(oid),
591                        buffer,
592                        buffer_size,
593                        bytes_out,
594                        NULL)) {
595     return GetLastError();
596   }
597   return ERROR_SUCCESS;
598 }
599
600 bool ResizeBuffer(int requested_size, scoped_ptr_malloc<BYTE>* buffer) {
601   DCHECK_GT(requested_size, 0);
602   DCHECK(buffer);
603   if (requested_size > kMaximumBufferSize) {
604     buffer->reset();
605     return false;
606   }
607
608   buffer->reset(reinterpret_cast<BYTE*>(
609       realloc(buffer->release(), requested_size)));
610   return buffer != NULL;
611 }
612
613 bool GetSystemDirectory(string16* path) {
614   DCHECK(path);
615   // Return value includes terminating NULL.
616   int buffer_size = ::GetSystemDirectory(NULL, 0);
617   if (buffer_size == 0) {
618     return false;
619   }
620   scoped_ptr<char16[]> buffer(new char16[buffer_size]);
621
622   // Return value excludes terminating NULL.
623   int characters_written = ::GetSystemDirectory(buffer.get(), buffer_size);
624   if (characters_written == 0) {
625     return false;
626   }
627   DCHECK_EQ(buffer_size - 1, characters_written);
628
629   path->assign(buffer.get(), characters_written);
630
631   if (*path->rbegin() != L'\\') {
632     path->append(L"\\");
633   }
634   DCHECK_EQ(L'\\', *path->rbegin());
635   return true;
636 }
637 }  // namespace
638
639 }  // namespace content