@@ -19,6 +19,9 @@ namespace web { namespace details
19
19
{
20
20
namespace
21
21
{
22
+ const ::utility::string_t dotSegment = _XPLATSTR(" ." );
23
+ const ::utility::string_t dotDotSegment = _XPLATSTR(" .." );
24
+
22
25
// / <summary>
23
26
// / Unreserved characters are those that are allowed in a URI but do not have a reserved purpose. They include:
24
27
// / - A-Z
@@ -423,7 +426,60 @@ namespace
423
426
return encoded;
424
427
}
425
428
426
- }
429
+ // 5.2.3. Merge Paths https://tools.ietf.org/html/rfc3986#section-5.2.3
430
+ utility::string_t mergePaths (const utility::string_t &base, const utility::string_t &relative)
431
+ {
432
+ const auto lastSlash = base.rfind (_XPLATSTR (' /' ));
433
+ if (lastSlash == utility::string_t ::npos)
434
+ {
435
+ return base + _XPLATSTR (' /' ) + relative;
436
+ }
437
+ else if (lastSlash == base.size () - 1 )
438
+ {
439
+ return base + relative;
440
+ }
441
+ // path contains and does not end with '/', we remove segment after last '/'
442
+ return base.substr (0 , lastSlash + 1 ) + relative;
443
+ }
444
+
445
+ // 5.2.4. Remove Dot Segments https://tools.ietf.org/html/rfc3986#section-5.2.4
446
+ void removeDotSegments (uri_builder &builder)
447
+ {
448
+ if (builder.path ().find (_XPLATSTR (' .' )) == utility::string_t ::npos)
449
+ return ;
450
+
451
+ const auto segments = uri::split_path (builder.path ());
452
+ std::vector<std::reference_wrapper<const utility::string_t >> result;
453
+ for (auto & segment : segments)
454
+ {
455
+ if (segment == dotSegment)
456
+ continue ;
457
+ else if (segment != dotDotSegment)
458
+ result.push_back (segment);
459
+ else if (!result.empty ())
460
+ result.pop_back ();
461
+ }
462
+ if (result.empty ())
463
+ {
464
+ builder.set_path (utility::string_t ());
465
+ return ;
466
+ }
467
+ utility::string_t path = result.front ().get ();
468
+ for (size_t i = 1 ; i != result.size (); ++i)
469
+ {
470
+ path += _XPLATSTR (' /' );
471
+ path += result[i].get ();
472
+ }
473
+ if (segments.back () == dotDotSegment
474
+ || segments.back () == dotSegment
475
+ || builder.path ().back () == _XPLATSTR (' /' ))
476
+ {
477
+ path += _XPLATSTR (' /' );
478
+ }
479
+
480
+ builder.set_path (std::move (path));
481
+ }
482
+ } // namespace
427
483
428
484
utility::string_t uri_components::join ()
429
485
{
@@ -448,7 +504,8 @@ utility::string_t uri_components::join()
448
504
449
505
if (!m_scheme.empty ())
450
506
{
451
- ret.append (m_scheme).append ({ _XPLATSTR (' :' ) });
507
+ ret.append (m_scheme);
508
+ ret.push_back (_XPLATSTR (' :' ));
452
509
}
453
510
454
511
if (!m_host.empty ())
@@ -473,25 +530,27 @@ utility::string_t uri_components::join()
473
530
// only add the leading slash when the host is present
474
531
if (!m_host.empty () && m_path.front () != _XPLATSTR (' /' ))
475
532
{
476
- ret.append ({ _XPLATSTR (' /' ) } );
533
+ ret.push_back ( _XPLATSTR (' /' ));
477
534
}
478
535
479
536
ret.append (m_path);
480
537
}
481
538
482
539
if (!m_query.empty ())
483
540
{
484
- ret.append ({ _XPLATSTR (' ?' ) }).append (m_query);
541
+ ret.push_back (_XPLATSTR (' ?' ));
542
+ ret.append (m_query);
485
543
}
486
544
487
545
if (!m_fragment.empty ())
488
546
{
489
- ret.append ({ _XPLATSTR (' #' ) }).append (m_fragment);
547
+ ret.push_back (_XPLATSTR (' #' ));
548
+ ret.append (m_fragment);
490
549
}
491
550
492
551
return ret;
493
552
}
494
- }
553
+ } // namespace details
495
554
496
555
uri::uri (const details::uri_components &components) : m_components(components)
497
556
{
@@ -715,7 +774,7 @@ std::map<utility::string_t, utility::string_t> uri::split_query(const utility::s
715
774
utility::string_t key (key_value_pair.begin (), key_value_pair.begin () + equals_index);
716
775
utility::string_t value (key_value_pair.begin () + equals_index + 1 , key_value_pair.end ());
717
776
results[key] = value;
718
- }
777
+ }
719
778
}
720
779
721
780
return results;
@@ -784,4 +843,54 @@ bool uri::operator == (const uri &other) const
784
843
return true ;
785
844
}
786
845
846
+ // resolving URI according to RFC3986, Section 5 https://tools.ietf.org/html/rfc3986#section-5
847
+ utility::string_t uri::resolve_uri (const utility::string_t &relativeUri) const
848
+ {
849
+ if (relativeUri.empty ())
850
+ {
851
+ return to_string ();
852
+ }
853
+
854
+ if (relativeUri[0 ] == _XPLATSTR (' /' )) // starts with '/'
855
+ {
856
+ if (relativeUri.size () >= 2 && relativeUri[1 ] == _XPLATSTR (' /' )) // starts with '//'
857
+ {
858
+ return this ->scheme () + _XPLATSTR (' :' ) + relativeUri;
859
+ }
860
+
861
+ // otherwise relative to root
862
+ auto builder = uri_builder (this ->authority ());
863
+ builder.append (relativeUri);
864
+ details::removeDotSegments (builder);
865
+ return builder.to_string ();
866
+ }
867
+
868
+ const auto url = uri (relativeUri);
869
+ if (!url.scheme ().empty ())
870
+ return relativeUri;
871
+
872
+ if (!url.authority ().is_empty ())
873
+ {
874
+ return uri_builder (url).set_scheme (this ->scheme ()).to_string ();
875
+ }
876
+
877
+ // relative url
878
+ auto builder = uri_builder (*this );
879
+ if (url.path () == _XPLATSTR (" /" ) || url.path ().empty ()) // web::uri considers empty path as '/'
880
+ {
881
+ if (!url.query ().empty ())
882
+ {
883
+ builder.set_query (url.query ());
884
+ }
885
+ }
886
+ else if (!this ->path ().empty ())
887
+ {
888
+ builder.set_path (details::mergePaths (this ->path (), url.path ()));
889
+ details::removeDotSegments (builder);
890
+ builder.set_query (url.query ());
891
+ }
892
+
893
+ return builder.set_fragment (url.fragment ()).to_string ();
787
894
}
895
+
896
+ } // namespace web
0 commit comments