1 #ifndef CORELIB___NCBI_URL__HPP
2 #define CORELIB___NCBI_URL__HPP
3
4 /* $Id: ncbi_url.hpp 613672 2020-08-11 16:34:37Z grichenk $
5 * ===========================================================================
6 *
7 * PUBLIC DOMAIN NOTICE
8 * National Center for Biotechnology Information
9 *
10 * This software/database is a "United States Government Work" under the
11 * terms of the United States Copyright Act. It was written as part of
12 * the author's official duties as a United States Government employee and
13 * thus cannot be copyrighted. This software/database is freely available
14 * to the public for use. The National Library of Medicine and the U.S.
15 * Government have not placed any restriction on its use or reproduction.
16 *
17 * Although all reasonable efforts have been taken to ensure the accuracy
18 * and reliability of the software and data, the NLM and the U.S.
19 * Government do not and cannot warrant the performance or results that
20 * may be obtained by using this software or data. The NLM and the U.S.
21 * Government disclaim all warranties, express or implied, including
22 * warranties of performance, merchantability or fitness for any particular
23 * purpose.
24 *
25 * Please cite the author in any work or product based on this material.
26 *
27 * ===========================================================================
28 *
29 * Authors: Alexey Grichenko, Vladimir Ivanov
30 *
31 * File Description: URL parsing classes
32 *
33 */
34
35 /// @file ncbi_url.hpp
36 ///
37 /// URL parsing classes.
38 ///
39
40 #include <corelib/ncbi_param.hpp>
41
42 /** @addtogroup UTIL
43 *
44 * @{
45 */
46
47 BEGIN_NCBI_SCOPE
48
49 /////////////////////////////////////////////////////////////////////////////
50 ///
51 /// IUrlEncoder::
52 ///
53 /// URL parts encoder/decoder interface. Used by CUrl.
54 ///
55
56 class IUrlEncoder
57 {
58 public:
~IUrlEncoder(void)59 virtual ~IUrlEncoder(void) {}
60
61 /// Encode user name
62 virtual string EncodeUser(const string& user) const = 0;
63 /// Decode user name
64 virtual string DecodeUser(const string& user) const = 0;
65 /// Encode password
66 virtual string EncodePassword(const string& password) const = 0;
67 /// Decode password
68 virtual string DecodePassword(const string& password) const = 0;
69 /// Encode path on server
70 virtual string EncodePath(const string& path) const = 0;
71 /// Decode path on server
72 virtual string DecodePath(const string& path) const = 0;
73 /// Encode URL argument name
74 virtual string EncodeArgName(const string& name) const = 0;
75 /// Decode URL argument name
76 virtual string DecodeArgName(const string& name) const = 0;
77 /// Encode URL argument value
78 virtual string EncodeArgValue(const string& value) const = 0;
79 /// Decode URL argument value
80 virtual string DecodeArgValue(const string& value) const = 0;
81 /// Encode fragment
82 virtual string EncodeFragment(const string& value) const = 0;
83 /// Decode fragment
84 virtual string DecodeFragment(const string& value) const = 0;
85 };
86
87
88 /// Primitive encoder - all methods return the argument value.
89 /// Used as base class for other encoders.
90 class NCBI_XNCBI_EXPORT CEmptyUrlEncoder : public IUrlEncoder
91 {
92 public:
EncodeUser(const string & user) const93 virtual string EncodeUser(const string& user) const
94 { return user; }
DecodeUser(const string & user) const95 virtual string DecodeUser(const string& user) const
96 { return user; }
EncodePassword(const string & password) const97 virtual string EncodePassword(const string& password) const
98 { return password; }
DecodePassword(const string & password) const99 virtual string DecodePassword(const string& password) const
100 { return password; }
EncodePath(const string & path) const101 virtual string EncodePath(const string& path) const
102 { return path; }
DecodePath(const string & path) const103 virtual string DecodePath(const string& path) const
104 { return path; }
EncodeArgName(const string & name) const105 virtual string EncodeArgName(const string& name) const
106 { return name; }
DecodeArgName(const string & name) const107 virtual string DecodeArgName(const string& name) const
108 { return name; }
EncodeArgValue(const string & value) const109 virtual string EncodeArgValue(const string& value) const
110 { return value; }
DecodeArgValue(const string & value) const111 virtual string DecodeArgValue(const string& value) const
112 { return value; }
EncodeFragment(const string & value) const113 virtual string EncodeFragment(const string& value) const
114 { return value; }
DecodeFragment(const string & value) const115 virtual string DecodeFragment(const string& value) const
116 { return value; }
117 };
118
119
120 /// Default encoder, uses the selected encoding for argument names/values
121 /// and eUrlEncode_Path for document path. Other parts of the URL are
122 /// not encoded.
123 class NCBI_XNCBI_EXPORT CDefaultUrlEncoder : public CEmptyUrlEncoder
124 {
125 public:
CDefaultUrlEncoder(NStr::EUrlEncode encode=NStr::eUrlEnc_SkipMarkChars)126 CDefaultUrlEncoder(NStr::EUrlEncode encode = NStr::eUrlEnc_SkipMarkChars)
127 : m_Encode(NStr::EUrlEncode(encode)) { return; }
EncodeUser(const string & user) const128 virtual string EncodeUser(const string& user) const
129 { return NStr::URLEncode(user, NStr::eUrlEnc_URIUserinfo); }
DecodeUser(const string & user) const130 virtual string DecodeUser(const string& user) const
131 { return NStr::URLDecode(user, NStr::eUrlDec_Percent); }
EncodePassword(const string & password) const132 virtual string EncodePassword(const string& password) const
133 { return NStr::URLEncode(password, NStr::eUrlEnc_URIUserinfo); }
DecodePassword(const string & password) const134 virtual string DecodePassword(const string& password) const
135 { return NStr::URLDecode(password, NStr::eUrlDec_Percent); }
EncodePath(const string & path) const136 virtual string EncodePath(const string& path) const
137 { return NStr::URLEncode(path, NStr::eUrlEnc_URIPath); }
DecodePath(const string & path) const138 virtual string DecodePath(const string& path) const
139 { return NStr::URLDecode(path); }
EncodeArgName(const string & name) const140 virtual string EncodeArgName(const string& name) const
141 { return NStr::URLEncode(name, m_Encode); }
DecodeArgName(const string & name) const142 virtual string DecodeArgName(const string& name) const
143 { return NStr::URLDecode(name,
144 m_Encode == NStr::eUrlEnc_PercentOnly ?
145 NStr::eUrlDec_Percent : NStr::eUrlDec_All); }
EncodeArgValue(const string & value) const146 virtual string EncodeArgValue(const string& value) const
147 { return NStr::URLEncode(value, m_Encode); }
DecodeArgValue(const string & value) const148 virtual string DecodeArgValue(const string& value) const
149 { return NStr::URLDecode(value,
150 m_Encode == NStr::eUrlEnc_PercentOnly ?
151 NStr::eUrlDec_Percent : NStr::eUrlDec_All); }
EncodeFragment(const string & value) const152 virtual string EncodeFragment(const string& value) const
153 { return NStr::URLEncode(value, NStr::eUrlEnc_URIFragment); }
DecodeFragment(const string & value) const154 virtual string DecodeFragment(const string& value) const
155 { return NStr::URLDecode(value, NStr::eUrlDec_Percent); }
156 private:
157 NStr::EUrlEncode m_Encode;
158 };
159
160
161
162 /////////////////////////////////////////////////////////////////////////////
163 ///
164 /// CUrlArgs_Parser::
165 ///
166 /// Base class for arguments parsers.
167 ///
168
169 class NCBI_XNCBI_EXPORT CUrlArgs_Parser
170 {
171 public:
CUrlArgs_Parser(void)172 CUrlArgs_Parser(void) : m_SemicolonIsNotArgDelimiter(false) {}
~CUrlArgs_Parser(void)173 virtual ~CUrlArgs_Parser(void) {}
174
175 /// Parse query string, call AddArgument() to store each value.
176 void SetQueryString(const string& query, NStr::EUrlEncode encode);
177 /// Parse query string, call AddArgument() to store each value.
178 void SetQueryString(const string& query,
179 const IUrlEncoder* encoder = 0);
180
181 /// Treat semicolon as query string argument separator
SetSemicolonIsNotArgDelimiter(bool enable=true)182 void SetSemicolonIsNotArgDelimiter(bool enable = true)
183 {
184 m_SemicolonIsNotArgDelimiter = enable;
185 }
186
187 protected:
188 /// Query type flag
189 enum EArgType {
190 eArg_Value, ///< Query contains name=value pairs
191 eArg_Index ///< Query contains a list of names: name1+name2+name3
192 };
193
194 /// Process next query argument. Must be overriden to process and store
195 /// the arguments.
196 /// @param position
197 /// 1-based index of the argument in the query.
198 /// @param name
199 /// Name of the argument.
200 /// @param value
201 /// Contains argument value if query type is eArg_Value or
202 /// empty string for eArg_Index.
203 /// @param arg_type
204 /// Query type flag.
205 virtual void AddArgument(unsigned int position,
206 const string& name,
207 const string& value,
208 EArgType arg_type = eArg_Index) = 0;
209 private:
210 void x_SetIndexString(const string& query,
211 const IUrlEncoder& encoder);
212
213 bool m_SemicolonIsNotArgDelimiter;
214 };
215
216
217 /////////////////////////////////////////////////////////////////////////////
218 ///
219 /// CUrlArgs::
220 ///
221 /// URL arguments list.
222 ///
223
224 class NCBI_XNCBI_EXPORT CUrlArgs : public CUrlArgs_Parser
225 {
226 public:
227 /// Create an empty arguments set.
228 CUrlArgs(void);
229 /// Parse the query string, store the arguments.
230 CUrlArgs(const string& query, NStr::EUrlEncode decode);
231 /// Parse the query string, store the arguments.
232 CUrlArgs(const string& query, const IUrlEncoder* encoder = 0);
233
234 /// Ampersand encoding for composed URLs
235 enum EAmpEncoding {
236 eAmp_Char, ///< Use & to separate arguments
237 eAmp_Entity ///< Encode '&' as "&"
238 };
239
240 /// Construct and return complete query string. Use selected amp
241 /// and name/value encodings.
242 string GetQueryString(EAmpEncoding amp_enc,
243 NStr::EUrlEncode encode) const;
244 /// Construct and return complete query string. Use selected amp
245 /// and name/value encodings.
246 string GetQueryString(EAmpEncoding amp_enc,
247 const IUrlEncoder* encoder = 0) const;
248
249 /// Name-value pair.
250 struct SUrlArg
251 {
SUrlArgCUrlArgs::SUrlArg252 SUrlArg(const string& aname, const string& avalue)
253 : name(aname), value(avalue) { }
254 string name;
255 string value;
256 };
257 typedef SUrlArg TArg;
258 typedef list<TArg> TArgs;
259 typedef TArgs::iterator iterator;
260 typedef TArgs::const_iterator const_iterator;
261
262 /// Check if an argument with the given name exists.
IsSetValue(const string & name) const263 bool IsSetValue(const string& name) const
264 { return FindFirst(name) != m_Args.end(); }
265
266 /// Get value for the given name. finds first of the arguments with the
267 /// given name. If the name does not exist, is_found is set to false.
268 /// If is_found is null, CUrlArgsException is thrown.
269 const string& GetValue(const string& name, bool* is_found = 0) const;
270
271 /// Set new value for the first argument with the given name or
272 /// add a new argument.
273 void SetValue(const string& name, const string& value);
274
275 /// Add new value even if an argument with the same name already exists.
276 void AddValue(const string& name, const string& value);
277
278 /// Set value, remove any other values for the name.
279 void SetUniqueValue(const string& name, const string& value);
280
281 /// Get the const list of arguments.
GetArgs(void) const282 const TArgs& GetArgs(void) const
283 { return m_Args; }
284
285 /// Get the list of arguments.
GetArgs(void)286 TArgs& GetArgs(void)
287 { return m_Args; }
288
289 /// Find the first argument with the given name. If not found, return
290 /// GetArgs().end().
291 iterator FindFirst(const string& name);
292
293 /// Take argument name from the iterator, find next argument with the same
294 /// name, return GetArgs().end() if not found.
295 iterator FindNext(const iterator& iter);
296
297 /// Find the first argument with the given name. If not found, return
298 /// GetArgs().end().
299 const_iterator FindFirst(const string& name) const;
300
301 /// Take argument name from the iterator, find next argument with the same
302 /// name, return GetArgs().end() if not found.
303 const_iterator FindNext(const const_iterator& iter) const;
304
305 /// Select case sensitivity of arguments' names.
SetCase(NStr::ECase name_case)306 void SetCase(NStr::ECase name_case)
307 { m_Case = name_case; }
308
309 protected:
310 virtual void AddArgument(unsigned int position,
311 const string& name,
312 const string& value,
313 EArgType arg_type);
314 private:
315 iterator x_Find(const string& name, const iterator& start);
316 const_iterator x_Find(const string& name,
317 const const_iterator& start) const;
318
319 NStr::ECase m_Case;
320 bool m_IsIndex;
321 TArgs m_Args;
322 };
323
324
325 /////////////////////////////////////////////////////////////////////////////
326 ///
327 /// CUrl::
328 ///
329 /// URL parser. Uses CUrlArgs to parse arguments.
330 ///
331
332 #define NCBI_SCHEME_SERVICE "ncbilb"
333
334 class NCBI_XNCBI_EXPORT CUrl
335 {
336 public:
337 /// Default constructor
338 CUrl(void);
339
340 /// Parse the URL.
341 ///
342 /// @param url
343 /// String to parse as URL:
344 /// Generic: [scheme://[[user]:[password]@]]host[:port][/path][?args]
345 /// Special: scheme:[path]
346 /// The leading '/', if any, is included in path value.
347 /// @param encoder
348 /// URL encoder object. If not set, the default encoder will be used.
349 /// @sa CDefaultUrlEncoder
350 CUrl(const string& url, const IUrlEncoder* encoder = 0);
351
352 /// Parse the URL.
353 ///
354 /// @param url
355 /// String to parse as URL
356 /// @param encoder
357 /// URL encoder object. If not set, the default encoder will be used.
358 /// @sa CDefaultUrlEncoder
359 void SetUrl(const string& url, const IUrlEncoder* encoder = 0);
360
361 /// Compose the URL.
362 ///
363 /// @param amp_enc
364 /// Method of encoding ampersand.
365 /// @sa CUrlArgs::EAmpEncoding
366 /// @param encoder
367 /// URL encoder object. If not set, the default encoder will be used.
368 /// @sa CDefaultUrlEncoder
369 string ComposeUrl(CUrlArgs::EAmpEncoding amp_enc,
370 const IUrlEncoder* encoder = 0) const;
371
372 // Access parts of the URL
373
GetScheme(void) const374 string GetScheme(void) const { return m_Scheme; }
375 void SetScheme(const string& value);
376
377 /// Generic schemes use '//' prefix (after optional scheme).
GetIsGeneric(void) const378 bool GetIsGeneric(void) const { return m_IsGeneric; }
SetIsGeneric(bool value)379 void SetIsGeneric(bool value) { m_IsGeneric = value; }
380
GetUser(void) const381 string GetUser(void) const { return m_User; }
SetUser(const string & value)382 void SetUser(const string& value) { m_User = value; }
383
GetPassword(void) const384 string GetPassword(void) const { return m_Password; }
SetPassword(const string & value)385 void SetPassword(const string& value) { m_Password = value; }
386
GetHost(void) const387 string GetHost(void) const { return m_Host; }
388 void SetHost(const string& value);
389
IsService(void) const390 bool IsService(void) const { return !m_Service.empty(); }
GetService(void) const391 string GetService(void) const { return m_Service; }
392 void SetService(const string& value);
393
GetPort(void) const394 string GetPort(void) const { return m_Port; }
SetPort(const string & value)395 void SetPort(const string& value) { m_Port = value; }
396
GetPath(void) const397 string GetPath(void) const { return m_Path; }
SetPath(const string & value)398 void SetPath(const string& value) { m_Path = value; }
399
GetFragment(void) const400 string GetFragment(void) const { return m_Fragment; }
SetFragment(const string & value)401 void SetFragment(const string& value) { m_Fragment = value; }
402
403 /// Get the original (unparsed and undecoded) query string
GetOriginalArgsString(void) const404 string GetOriginalArgsString(void) const
405 { return m_OrigArgs; }
406
407 /// Check if the URL contains any arguments
HaveArgs(void) const408 bool HaveArgs(void) const
409 { return m_ArgsList.get() != 0 && !m_ArgsList->GetArgs().empty(); }
410
411 /// Get const list of arguments
412 const CUrlArgs& GetArgs(void) const;
413
414 /// Get list of arguments
415 CUrlArgs& GetArgs(void);
416
417 CUrl(const CUrl& url);
418 CUrl& operator=(const CUrl& url);
419
420 /// Return default URL encoder.
421 ///
422 /// @sa CDefaultUrlEncoder
423 static IUrlEncoder* GetDefaultEncoder(void);
424
425 bool IsEmpty(void) const;
426
427 /// Flags controlling URL adjustment.
428 /// @sa CUrl::Adjust
429 enum EAdjustFlags {
430 fUser_Replace = 0x0001, ///< Replace user if set in 'other'
431 fUser_ReplaceIfEmpty = 0x0002, ///< Replace user only if not yet set
432 fPassword_Replace = 0x0004, ///< Replace password if set in 'other'
433 fPassword_ReplaceIfEmpty = 0x0008, ///< Replace password only if not yet set
434 fPath_Replace = 0x0010, ///< Replace path
435 fPath_Append = 0x0020, ///< Append new path to the existing one
436 fFragment_Replace = 0x0040, ///< Replace fragment if set in 'other'
437 fFragment_ReplaceIfEmpty = 0x0080, ///< Replace fragment only if not yet set
438 fArgs_Replace = 0x0100, ///< Discard all args, replace with args from 'other'
439 fArgs_Append = 0x0200, ///< Append args, allow duplicate names and values
440 fArgs_Merge = 0x0400, ///< Append new args; replace values of existing args,
441 ///< do not allow to set multiple values with the same name
442 fScheme_Replace = 0x0800 ///< Replace scheme if set in 'other'
443 };
444 typedef int TAdjustFlags;
445
446 /// Adjust this URL using information from 'other' URL.
447 /// Scheme, host and port are never changed. Other parts can be replaced or merged
448 /// depending on the flags.
449 /// Throw CUrlException if the flags are inconsistent (e.g. both fPath_Replace and fPath_Append are set).
450 void Adjust(const CUrl& other, TAdjustFlags flags);
451
452 private:
453 // Set values with verification
454 void x_SetScheme(const string& scheme, const IUrlEncoder& encoder);
455 void x_SetUser(const string& user, const IUrlEncoder& encoder);
456 void x_SetPassword(const string& password, const IUrlEncoder& encoder);
457 void x_SetHost(const string& host, const IUrlEncoder& encoder);
458 void x_SetService(const string& service);
459 void x_SetPort(const string& port, const IUrlEncoder& encoder);
460 void x_SetPath(const string& path, const IUrlEncoder& encoder);
461 void x_SetArgs(const string& args, const IUrlEncoder& encoder);
462 void x_SetFragment(const string& fragment, const IUrlEncoder& encoder);
463
464 bool x_IsHostPort(const string& scheme, string& unparsed, const IUrlEncoder& encoder);
465
466 string m_Scheme;
467 bool m_IsGeneric; // generic schemes include '//' delimiter
468 string m_User;
469 string m_Password;
470 string m_Host;
471 string m_Service;
472 string m_Port;
473 string m_Path;
474 string m_Fragment;
475 string m_OrigArgs;
476 unique_ptr<CUrlArgs> m_ArgsList;
477 };
478
479
480 /////////////////////////////////////////////////////////////////////////////
481 ///
482 /// CUrlException --
483 ///
484 /// Exceptions to be used by CUrl.
485 ///
486
487 class CUrlException : public CException
488 {
489 public:
490 enum EErrCode {
491 eName, //< Argument does not exist
492 eNoArgs, //< CUrl contains no arguments
493 eFlags //< Inconsistent flags passed to Adjust()
494 };
GetErrCodeString(void) const495 virtual const char* GetErrCodeString(void) const override
496 {
497 switch ( GetErrCode() ) {
498 case eName: return "Unknown argument name";
499 case eNoArgs: return "Arguments list is empty";
500 case eFlags: return "Inconsistent flags set";
501 default: return CException::GetErrCodeString();
502 }
503 }
504
505 NCBI_EXCEPTION_DEFAULT(CUrlException, CException);
506 };
507
508
509 /////////////////////////////////////////////////////////////////////////////
510 ///
511 /// CUrlParserException --
512 ///
513 /// Exceptions used by the URL parser
514
515 class CUrlParserException : public CParseTemplException<CUrlException>
516 {
517 public:
518 enum EErrCode {
519 eFormat //< Invalid URL format
520 };
521
GetErrCodeString(void) const522 virtual const char* GetErrCodeString(void) const override
523 {
524 switch (GetErrCode()) {
525 case eFormat: return "Url format error";
526 default: return CException::GetErrCodeString();
527 }
528 }
529
530 NCBI_EXCEPTION_DEFAULT2
531 (CUrlParserException, CParseTemplException<CUrlException>,
532 std::string::size_type);
533 };
534
535
536 //////////////////////////////////////////////////////////////////////////////
537 //
538 // Inline functions
539 //
540 //////////////////////////////////////////////////////////////////////////////
541
542
543 // CUrl
544
545 inline
SetHost(const string & host)546 void CUrl::SetHost(const string& host)
547 {
548 m_Service.clear();
549 m_Host = host;
550 }
551
552 inline
SetService(const string & service)553 void CUrl::SetService(const string& service)
554 {
555 m_Host.clear();
556 m_Service = service;
557 m_IsGeneric = true; // services are always generic
558 }
559
560 inline
x_SetScheme(const string & scheme,const IUrlEncoder &)561 void CUrl::x_SetScheme(const string& scheme,
562 const IUrlEncoder& /*encoder*/)
563 {
564 m_Scheme = scheme;
565 }
566
567 inline
x_SetUser(const string & user,const IUrlEncoder & encoder)568 void CUrl::x_SetUser(const string& user,
569 const IUrlEncoder& encoder)
570 {
571 m_User = encoder.DecodeUser(user);
572 }
573
574 inline
x_SetPassword(const string & password,const IUrlEncoder & encoder)575 void CUrl::x_SetPassword(const string& password,
576 const IUrlEncoder& encoder)
577 {
578 m_Password = encoder.DecodePassword(password);
579 }
580
581 inline
x_SetHost(const string & host,const IUrlEncoder &)582 void CUrl::x_SetHost(const string& host,
583 const IUrlEncoder& /*encoder*/)
584 {
585 m_Host = host;
586 m_Service.clear();
587 }
588
589 inline
x_SetService(const string & service)590 void CUrl::x_SetService(const string& service)
591 {
592 m_Service = NStr::URLDecode(service);
593 }
594
595 inline
x_SetPort(const string & port,const IUrlEncoder &)596 void CUrl::x_SetPort(const string& port,
597 const IUrlEncoder& /*encoder*/)
598 {
599 NStr::StringToInt(port);
600 m_Port = port;
601 }
602
603 inline
x_SetPath(const string & path,const IUrlEncoder & encoder)604 void CUrl::x_SetPath(const string& path,
605 const IUrlEncoder& encoder)
606 {
607 m_Path = encoder.DecodePath(path);
608 }
609
610 inline
x_SetFragment(const string & fragment,const IUrlEncoder & encoder)611 void CUrl::x_SetFragment(const string& fragment,
612 const IUrlEncoder& encoder)
613 {
614 m_Fragment = encoder.DecodeFragment(fragment);
615 }
616
617 inline
x_SetArgs(const string & args,const IUrlEncoder & encoder)618 void CUrl::x_SetArgs(const string& args,
619 const IUrlEncoder& encoder)
620 {
621 m_OrigArgs = args;
622 m_ArgsList.reset(new CUrlArgs(m_OrigArgs, &encoder));
623 }
624
625
626 inline
GetArgs(void)627 CUrlArgs& CUrl::GetArgs(void)
628 {
629 if ( !m_ArgsList.get() ) {
630 x_SetArgs(kEmptyStr, *GetDefaultEncoder());
631 }
632 return *m_ArgsList;
633 }
634
635
636 inline
FindFirst(const string & name) const637 CUrlArgs::const_iterator CUrlArgs::FindFirst(const string& name) const
638 {
639 return x_Find(name, m_Args.begin());
640 }
641
642
643 inline
FindFirst(const string & name)644 CUrlArgs::iterator CUrlArgs::FindFirst(const string& name)
645 {
646 return x_Find(name, m_Args.begin());
647 }
648
649
650 inline
FindNext(const const_iterator & iter) const651 CUrlArgs::const_iterator CUrlArgs::FindNext(const const_iterator& iter) const
652 {
653 const_iterator next = iter;
654 ++next;
655 return x_Find(iter->name, next);
656 }
657
658
659 inline
FindNext(const iterator & iter)660 CUrlArgs::iterator CUrlArgs::FindNext(const iterator& iter)
661 {
662 iterator next = iter;
663 ++next;
664 return x_Find(iter->name, next);
665 }
666
667 /* @} */
668
669 END_NCBI_SCOPE
670
671 #endif /* CORELIB___NCBI_URL__HPP */
672