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 #include "net/base/filename_util.h" | 5 #include "net/base/filename_util.h" |
6 | 6 |
7 #include "base/file_util.h" | 7 #include "base/file_util.h" |
8 #include "base/files/file_path.h" | 8 #include "base/files/file_path.h" |
9 #include "base/i18n/file_util_icu.h" | 9 #include "base/i18n/file_util_icu.h" |
10 #include "base/path_service.h" | 10 #include "base/path_service.h" |
(...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
70 url.ExtractFileName(), | 70 url.ExtractFileName(), |
71 UnescapeRule::SPACES | UnescapeRule::URL_SPECIAL_CHARS); | 71 UnescapeRule::SPACES | UnescapeRule::URL_SPECIAL_CHARS); |
72 | 72 |
73 // The URL's path should be escaped UTF-8, but may not be. | 73 // The URL's path should be escaped UTF-8, but may not be. |
74 std::string decoded_filename = unescaped_url_filename; | 74 std::string decoded_filename = unescaped_url_filename; |
75 if (!IsStringUTF8(decoded_filename)) { | 75 if (!IsStringUTF8(decoded_filename)) { |
76 // TODO(jshin): this is probably not robust enough. To be sure, we need | 76 // TODO(jshin): this is probably not robust enough. To be sure, we need |
77 // encoding detection. | 77 // encoding detection. |
78 base::string16 utf16_output; | 78 base::string16 utf16_output; |
79 if (!referrer_charset.empty() && | 79 if (!referrer_charset.empty() && |
80 net::ConvertToUTF16(unescaped_url_filename, | 80 net::ConvertToUTF16( |
81 referrer_charset.c_str(), | 81 unescaped_url_filename, referrer_charset.c_str(), &utf16_output)) { |
82 &utf16_output)) { | |
83 decoded_filename = base::UTF16ToUTF8(utf16_output); | 82 decoded_filename = base::UTF16ToUTF8(utf16_output); |
84 } else { | 83 } else { |
85 decoded_filename = base::WideToUTF8( | 84 decoded_filename = |
86 base::SysNativeMBToWide(unescaped_url_filename)); | 85 base::WideToUTF8(base::SysNativeMBToWide(unescaped_url_filename)); |
87 } | 86 } |
88 } | 87 } |
89 // If the URL contains a (possibly empty) query, assume it is a generator, and | 88 // If the URL contains a (possibly empty) query, assume it is a generator, and |
90 // allow the determined extension to be overwritten. | 89 // allow the determined extension to be overwritten. |
91 *should_overwrite_extension = !decoded_filename.empty() && url.has_query(); | 90 *should_overwrite_extension = !decoded_filename.empty() && url.has_query(); |
92 | 91 |
93 return decoded_filename; | 92 return decoded_filename; |
94 } | 93 } |
95 | 94 |
96 // Returns whether the specified extension is automatically integrated into the | 95 // Returns whether the specified extension is automatically integrated into the |
(...skipping 18 matching lines...) Expand all Loading... |
115 | 114 |
116 // Returns whether the specified file name is a reserved name on windows. | 115 // Returns whether the specified file name is a reserved name on windows. |
117 // This includes names like "com2.zip" (which correspond to devices) and | 116 // This includes names like "com2.zip" (which correspond to devices) and |
118 // desktop.ini and thumbs.db which have special meaning to the windows shell. | 117 // desktop.ini and thumbs.db which have special meaning to the windows shell. |
119 bool IsReservedName(const base::FilePath::StringType& filename) { | 118 bool IsReservedName(const base::FilePath::StringType& filename) { |
120 // This list is taken from the MSDN article "Naming a file" | 119 // This list is taken from the MSDN article "Naming a file" |
121 // http://msdn2.microsoft.com/en-us/library/aa365247(VS.85).aspx | 120 // http://msdn2.microsoft.com/en-us/library/aa365247(VS.85).aspx |
122 // I also added clock$ because GetSaveFileName seems to consider it as a | 121 // I also added clock$ because GetSaveFileName seems to consider it as a |
123 // reserved name too. | 122 // reserved name too. |
124 static const char* const known_devices[] = { | 123 static const char* const known_devices[] = { |
125 "con", "prn", "aux", "nul", "com1", "com2", "com3", "com4", "com5", | 124 "con", "prn", "aux", "nul", "com1", "com2", "com3", "com4", |
126 "com6", "com7", "com8", "com9", "lpt1", "lpt2", "lpt3", "lpt4", | 125 "com5", "com6", "com7", "com8", "com9", "lpt1", "lpt2", "lpt3", |
127 "lpt5", "lpt6", "lpt7", "lpt8", "lpt9", "clock$" | 126 "lpt4", "lpt5", "lpt6", "lpt7", "lpt8", "lpt9", "clock$"}; |
128 }; | |
129 #if defined(OS_WIN) | 127 #if defined(OS_WIN) |
130 std::string filename_lower = StringToLowerASCII(base::WideToUTF8(filename)); | 128 std::string filename_lower = StringToLowerASCII(base::WideToUTF8(filename)); |
131 #elif defined(OS_POSIX) | 129 #elif defined(OS_POSIX) |
132 std::string filename_lower = StringToLowerASCII(filename); | 130 std::string filename_lower = StringToLowerASCII(filename); |
133 #endif | 131 #endif |
134 | 132 |
135 for (size_t i = 0; i < arraysize(known_devices); ++i) { | 133 for (size_t i = 0; i < arraysize(known_devices); ++i) { |
136 // Exact match. | 134 // Exact match. |
137 if (filename_lower == known_devices[i]) | 135 if (filename_lower == known_devices[i]) |
138 return true; | 136 return true; |
139 // Starts with "DEVICE.". | 137 // Starts with "DEVICE.". |
140 if (filename_lower.find(std::string(known_devices[i]) + ".") == 0) | 138 if (filename_lower.find(std::string(known_devices[i]) + ".") == 0) |
141 return true; | 139 return true; |
142 } | 140 } |
143 | 141 |
144 static const char* const magic_names[] = { | 142 static const char* const magic_names[] = {// These file names are used by the |
145 // These file names are used by the "Customize folder" feature of the shell. | 143 // "Customize folder" feature of the |
146 "desktop.ini", | 144 // shell. |
147 "thumbs.db", | 145 "desktop.ini", "thumbs.db", |
148 }; | 146 }; |
149 | 147 |
150 for (size_t i = 0; i < arraysize(magic_names); ++i) { | 148 for (size_t i = 0; i < arraysize(magic_names); ++i) { |
151 if (filename_lower == magic_names[i]) | 149 if (filename_lower == magic_names[i]) |
152 return true; | 150 return true; |
153 } | 151 } |
154 | 152 |
155 return false; | 153 return false; |
156 } | 154 } |
157 | 155 |
158 | |
159 // Examines the current extension in |file_name| and modifies it if necessary in | 156 // Examines the current extension in |file_name| and modifies it if necessary in |
160 // order to ensure the filename is safe. If |file_name| doesn't contain an | 157 // order to ensure the filename is safe. If |file_name| doesn't contain an |
161 // extension or if |ignore_extension| is true, then a new extension will be | 158 // extension or if |ignore_extension| is true, then a new extension will be |
162 // constructed based on the |mime_type|. | 159 // constructed based on the |mime_type|. |
163 // | 160 // |
164 // We're addressing two things here: | 161 // We're addressing two things here: |
165 // | 162 // |
166 // 1) Usability. If there is no reliable file extension, we want to guess a | 163 // 1) Usability. If there is no reliable file extension, we want to guess a |
167 // reasonable file extension based on the content type. | 164 // reasonable file extension based on the content type. |
168 // | 165 // |
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
239 url_string.push_back(base::FilePath::kSeparators[0]); | 236 url_string.push_back(base::FilePath::kSeparators[0]); |
240 } | 237 } |
241 url_string.append(path.value()); | 238 url_string.append(path.value()); |
242 | 239 |
243 // Now do replacement of some characters. Since we assume the input is a | 240 // Now do replacement of some characters. Since we assume the input is a |
244 // literal filename, anything the URL parser might consider special should | 241 // literal filename, anything the URL parser might consider special should |
245 // be escaped here. | 242 // be escaped here. |
246 | 243 |
247 // must be the first substitution since others will introduce percents as the | 244 // must be the first substitution since others will introduce percents as the |
248 // escape character | 245 // escape character |
249 ReplaceSubstringsAfterOffset(&url_string, 0, | 246 ReplaceSubstringsAfterOffset( |
250 FILE_PATH_LITERAL("%"), FILE_PATH_LITERAL("%25")); | 247 &url_string, 0, FILE_PATH_LITERAL("%"), FILE_PATH_LITERAL("%25")); |
251 | 248 |
252 // semicolon is supposed to be some kind of separator according to RFC 2396 | 249 // semicolon is supposed to be some kind of separator according to RFC 2396 |
253 ReplaceSubstringsAfterOffset(&url_string, 0, | 250 ReplaceSubstringsAfterOffset( |
254 FILE_PATH_LITERAL(";"), FILE_PATH_LITERAL("%3B")); | 251 &url_string, 0, FILE_PATH_LITERAL(";"), FILE_PATH_LITERAL("%3B")); |
255 | 252 |
256 ReplaceSubstringsAfterOffset(&url_string, 0, | 253 ReplaceSubstringsAfterOffset( |
257 FILE_PATH_LITERAL("#"), FILE_PATH_LITERAL("%23")); | 254 &url_string, 0, FILE_PATH_LITERAL("#"), FILE_PATH_LITERAL("%23")); |
258 | 255 |
259 ReplaceSubstringsAfterOffset(&url_string, 0, | 256 ReplaceSubstringsAfterOffset( |
260 FILE_PATH_LITERAL("?"), FILE_PATH_LITERAL("%3F")); | 257 &url_string, 0, FILE_PATH_LITERAL("?"), FILE_PATH_LITERAL("%3F")); |
261 | 258 |
262 #if defined(OS_POSIX) | 259 #if defined(OS_POSIX) |
263 ReplaceSubstringsAfterOffset(&url_string, 0, | 260 ReplaceSubstringsAfterOffset( |
264 FILE_PATH_LITERAL("\\"), FILE_PATH_LITERAL("%5C")); | 261 &url_string, 0, FILE_PATH_LITERAL("\\"), FILE_PATH_LITERAL("%5C")); |
265 #endif | 262 #endif |
266 | 263 |
267 return GURL(url_string); | 264 return GURL(url_string); |
268 } | 265 } |
269 | 266 |
270 bool FileURLToFilePath(const GURL& url, base::FilePath* file_path) { | 267 bool FileURLToFilePath(const GURL& url, base::FilePath* file_path) { |
271 *file_path = base::FilePath(); | 268 *file_path = base::FilePath(); |
272 base::FilePath::StringType& file_path_str = | 269 base::FilePath::StringType& file_path_str = |
273 const_cast<base::FilePath::StringType&>(file_path->value()); | 270 const_cast<base::FilePath::StringType&>(file_path->value()); |
274 file_path_str.clear(); | 271 file_path_str.clear(); |
(...skipping 13 matching lines...) Expand all Loading... |
288 if (first_non_slash != std::string::npos && first_non_slash > 0) | 285 if (first_non_slash != std::string::npos && first_non_slash > 0) |
289 path.erase(0, first_non_slash); | 286 path.erase(0, first_non_slash); |
290 } else { | 287 } else { |
291 // URL contains a host: this means it's UNC. We keep the preceeding slash | 288 // URL contains a host: this means it's UNC. We keep the preceeding slash |
292 // on the path. | 289 // on the path. |
293 path = "\\\\"; | 290 path = "\\\\"; |
294 path.append(host); | 291 path.append(host); |
295 path.append(url.path()); | 292 path.append(url.path()); |
296 } | 293 } |
297 std::replace(path.begin(), path.end(), '/', '\\'); | 294 std::replace(path.begin(), path.end(), '/', '\\'); |
298 #else // defined(OS_WIN) | 295 #else // defined(OS_WIN) |
299 // Firefox seems to ignore the "host" of a file url if there is one. That is, | 296 // Firefox seems to ignore the "host" of a file url if there is one. That is, |
300 // file://foo/bar.txt maps to /bar.txt. | 297 // file://foo/bar.txt maps to /bar.txt. |
301 // TODO(dhg): This should probably take into account UNCs which could | 298 // TODO(dhg): This should probably take into account UNCs which could |
302 // include a hostname other than localhost or blank | 299 // include a hostname other than localhost or blank |
303 std::string path = url.path(); | 300 std::string path = url.path(); |
304 #endif // !defined(OS_WIN) | 301 #endif // !defined(OS_WIN) |
305 | 302 |
306 if (path.empty()) | 303 if (path.empty()) |
307 return false; | 304 return false; |
308 | 305 |
309 // GURL stores strings as percent-encoded 8-bit, this will undo if possible. | 306 // GURL stores strings as percent-encoded 8-bit, this will undo if possible. |
310 path = UnescapeURLComponent(path, | 307 path = UnescapeURLComponent( |
311 UnescapeRule::SPACES | UnescapeRule::URL_SPECIAL_CHARS); | 308 path, UnescapeRule::SPACES | UnescapeRule::URL_SPECIAL_CHARS); |
312 | 309 |
313 #if defined(OS_WIN) | 310 #if defined(OS_WIN) |
314 if (IsStringUTF8(path)) { | 311 if (IsStringUTF8(path)) { |
315 file_path_str.assign(base::UTF8ToWide(path)); | 312 file_path_str.assign(base::UTF8ToWide(path)); |
316 // We used to try too hard and see if |path| made up entirely of | 313 // We used to try too hard and see if |path| made up entirely of |
317 // the 1st 256 characters in the Unicode was a zero-extended UTF-16. | 314 // the 1st 256 characters in the Unicode was a zero-extended UTF-16. |
318 // If so, we converted it to 'Latin-1' and checked if the result was UTF-8. | 315 // If so, we converted it to 'Latin-1' and checked if the result was UTF-8. |
319 // If the check passed, we converted the result to UTF-8. | 316 // If the check passed, we converted the result to UTF-8. |
320 // Otherwise, we treated the result as the native OS encoding. | 317 // Otherwise, we treated the result as the native OS encoding. |
321 // However, that led to http://crbug.com/4619 and http://crbug.com/14153 | 318 // However, that led to http://crbug.com/4619 and http://crbug.com/14153 |
322 } else { | 319 } else { |
323 // Not UTF-8, assume encoding is native codepage and we're done. We know we | 320 // Not UTF-8, assume encoding is native codepage and we're done. We know we |
324 // are giving the conversion function a nonempty string, and it may fail if | 321 // are giving the conversion function a nonempty string, and it may fail if |
325 // the given string is not in the current encoding and give us an empty | 322 // the given string is not in the current encoding and give us an empty |
326 // string back. We detect this and report failure. | 323 // string back. We detect this and report failure. |
327 file_path_str = base::SysNativeMBToWide(path); | 324 file_path_str = base::SysNativeMBToWide(path); |
328 } | 325 } |
329 #else // defined(OS_WIN) | 326 #else // defined(OS_WIN) |
330 // Collapse multiple path slashes into a single path slash. | 327 // Collapse multiple path slashes into a single path slash. |
331 std::string new_path; | 328 std::string new_path; |
332 do { | 329 do { |
333 new_path = path; | 330 new_path = path; |
334 ReplaceSubstringsAfterOffset(&new_path, 0, "//", "/"); | 331 ReplaceSubstringsAfterOffset(&new_path, 0, "//", "/"); |
335 path.swap(new_path); | 332 path.swap(new_path); |
336 } while (new_path != path); | 333 } while (new_path != path); |
337 | 334 |
338 file_path_str.assign(path); | 335 file_path_str.assign(path); |
339 #endif // !defined(OS_WIN) | 336 #endif // !defined(OS_WIN) |
340 | 337 |
341 return !file_path_str.empty(); | 338 return !file_path_str.empty(); |
342 } | 339 } |
343 | 340 |
344 bool IsSafePortablePathComponent(const base::FilePath& component) { | 341 bool IsSafePortablePathComponent(const base::FilePath& component) { |
345 base::string16 component16; | 342 base::string16 component16; |
346 base::FilePath::StringType sanitized = component.value(); | 343 base::FilePath::StringType sanitized = component.value(); |
347 SanitizeGeneratedFileName(&sanitized, true); | 344 SanitizeGeneratedFileName(&sanitized, true); |
348 base::FilePath::StringType extension = component.Extension(); | 345 base::FilePath::StringType extension = component.Extension(); |
349 if (!extension.empty()) | 346 if (!extension.empty()) |
350 extension.erase(extension.begin()); // Erase preceding '.'. | 347 extension.erase(extension.begin()); // Erase preceding '.'. |
351 return !component.empty() && | 348 return !component.empty() && (component == component.BaseName()) && |
352 (component == component.BaseName()) && | |
353 (component == component.StripTrailingSeparators()) && | 349 (component == component.StripTrailingSeparators()) && |
354 FilePathToString16(component, &component16) && | 350 FilePathToString16(component, &component16) && |
355 file_util::IsFilenameLegal(component16) && | 351 file_util::IsFilenameLegal(component16) && |
356 !IsShellIntegratedExtension(extension) && | 352 !IsShellIntegratedExtension(extension) && |
357 (sanitized == component.value()) && | 353 (sanitized == component.value()) && !IsReservedName(component.value()); |
358 !IsReservedName(component.value()); | |
359 } | 354 } |
360 | 355 |
361 bool IsSafePortableRelativePath(const base::FilePath& path) { | 356 bool IsSafePortableRelativePath(const base::FilePath& path) { |
362 if (path.empty() || path.IsAbsolute() || path.EndsWithSeparator()) | 357 if (path.empty() || path.IsAbsolute() || path.EndsWithSeparator()) |
363 return false; | 358 return false; |
364 std::vector<base::FilePath::StringType> components; | 359 std::vector<base::FilePath::StringType> components; |
365 path.GetComponents(&components); | 360 path.GetComponents(&components); |
366 if (components.empty()) | 361 if (components.empty()) |
367 return false; | 362 return false; |
368 for (size_t i = 0; i < components.size() - 1; ++i) { | 363 for (size_t i = 0; i < components.size() - 1; ++i) { |
(...skipping 30 matching lines...) Expand all Loading... |
399 const std::string& referrer_charset, | 394 const std::string& referrer_charset, |
400 const std::string& suggested_name, | 395 const std::string& suggested_name, |
401 const std::string& mime_type, | 396 const std::string& mime_type, |
402 const std::string& default_name) { | 397 const std::string& default_name) { |
403 // TODO: this function to be updated to match the httpbis recommendations. | 398 // TODO: this function to be updated to match the httpbis recommendations. |
404 // Talk to abarth for the latest news. | 399 // Talk to abarth for the latest news. |
405 | 400 |
406 // We don't translate this fallback string, "download". If localization is | 401 // We don't translate this fallback string, "download". If localization is |
407 // needed, the caller should provide localized fallback in |default_name|. | 402 // needed, the caller should provide localized fallback in |default_name|. |
408 static const base::FilePath::CharType kFinalFallbackName[] = | 403 static const base::FilePath::CharType kFinalFallbackName[] = |
409 FILE_PATH_LITERAL("download"); | 404 FILE_PATH_LITERAL("download"); |
410 std::string filename; // In UTF-8 | 405 std::string filename; // In UTF-8 |
411 bool overwrite_extension = false; | 406 bool overwrite_extension = false; |
412 | 407 |
413 // Try to extract a filename from content-disposition first. | 408 // Try to extract a filename from content-disposition first. |
414 if (!content_disposition.empty()) { | 409 if (!content_disposition.empty()) { |
415 HttpContentDisposition header(content_disposition, referrer_charset); | 410 HttpContentDisposition header(content_disposition, referrer_charset); |
416 filename = header.filename(); | 411 filename = header.filename(); |
417 } | 412 } |
418 | 413 |
419 // Then try to use the suggested name. | 414 // Then try to use the suggested name. |
420 if (filename.empty() && !suggested_name.empty()) | 415 if (filename.empty() && !suggested_name.empty()) |
421 filename = suggested_name; | 416 filename = suggested_name; |
422 | 417 |
423 // Now try extracting the filename from the URL. GetFileNameFromURL() only | 418 // Now try extracting the filename from the URL. GetFileNameFromURL() only |
424 // looks at the last component of the URL and doesn't return the hostname as a | 419 // looks at the last component of the URL and doesn't return the hostname as a |
425 // failover. | 420 // failover. |
426 if (filename.empty()) | 421 if (filename.empty()) |
427 filename = GetFileNameFromURL(url, referrer_charset, &overwrite_extension); | 422 filename = GetFileNameFromURL(url, referrer_charset, &overwrite_extension); |
428 | 423 |
429 // Finally try the URL hostname, but only if there's no default specified in | 424 // Finally try the URL hostname, but only if there's no default specified in |
430 // |default_name|. Some schemes (e.g.: file:, about:, data:) do not have a | 425 // |default_name|. Some schemes (e.g.: file:, about:, data:) do not have a |
431 // host name. | 426 // host name. |
432 if (filename.empty() && | 427 if (filename.empty() && default_name.empty() && url.is_valid() && |
433 default_name.empty() && | |
434 url.is_valid() && | |
435 !url.host().empty()) { | 428 !url.host().empty()) { |
436 // TODO(jungshik) : Decode a 'punycoded' IDN hostname. (bug 1264451) | 429 // TODO(jungshik) : Decode a 'punycoded' IDN hostname. (bug 1264451) |
437 filename = url.host(); | 430 filename = url.host(); |
438 } | 431 } |
439 | 432 |
440 bool replace_trailing = false; | 433 bool replace_trailing = false; |
441 base::FilePath::StringType result_str, default_name_str; | 434 base::FilePath::StringType result_str, default_name_str; |
442 #if defined(OS_WIN) | 435 #if defined(OS_WIN) |
443 replace_trailing = true; | 436 replace_trailing = true; |
444 result_str = base::UTF8ToUTF16(filename); | 437 result_str = base::UTF8ToUTF16(filename); |
445 default_name_str = base::UTF8ToUTF16(default_name); | 438 default_name_str = base::UTF8ToUTF16(default_name); |
446 #else | 439 #else |
447 result_str = filename; | 440 result_str = filename; |
448 default_name_str = default_name; | 441 default_name_str = default_name; |
449 #endif | 442 #endif |
450 SanitizeGeneratedFileName(&result_str, replace_trailing); | 443 SanitizeGeneratedFileName(&result_str, replace_trailing); |
451 if (result_str.find_last_not_of(FILE_PATH_LITERAL("-_")) == | 444 if (result_str.find_last_not_of(FILE_PATH_LITERAL("-_")) == |
452 base::FilePath::StringType::npos) { | 445 base::FilePath::StringType::npos) { |
453 result_str = !default_name_str.empty() ? default_name_str : | 446 result_str = !default_name_str.empty() |
454 base::FilePath::StringType(kFinalFallbackName); | 447 ? default_name_str |
| 448 : base::FilePath::StringType(kFinalFallbackName); |
455 overwrite_extension = false; | 449 overwrite_extension = false; |
456 } | 450 } |
457 file_util::ReplaceIllegalCharactersInPath(&result_str, '-'); | 451 file_util::ReplaceIllegalCharactersInPath(&result_str, '-'); |
458 base::FilePath result(result_str); | 452 base::FilePath result(result_str); |
459 GenerateSafeFileName(mime_type, overwrite_extension, &result); | 453 GenerateSafeFileName(mime_type, overwrite_extension, &result); |
460 | 454 |
461 base::string16 result16; | 455 base::string16 result16; |
462 if (!FilePathToString16(result, &result16)) { | 456 if (!FilePathToString16(result, &result16)) { |
463 result = base::FilePath(default_name_str); | 457 result = base::FilePath(default_name_str); |
464 if (!FilePathToString16(result, &result16)) { | 458 if (!FilePathToString16(result, &result16)) { |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
496 // utf8 encoded characters in name. | 490 // utf8 encoded characters in name. |
497 file_util::NormalizeFileNameEncoding(&generated_name); | 491 file_util::NormalizeFileNameEncoding(&generated_name); |
498 #endif | 492 #endif |
499 | 493 |
500 DCHECK(!generated_name.empty()); | 494 DCHECK(!generated_name.empty()); |
501 | 495 |
502 return generated_name; | 496 return generated_name; |
503 } | 497 } |
504 | 498 |
505 } // namespace net | 499 } // namespace net |
OLD | NEW |