@@ -12,7 +12,7 @@ defmodule URI do
12
12
"""
13
13
14
14
defstruct scheme: nil ,
15
- path: nil ,
15
+ path: "" ,
16
16
query: nil ,
17
17
fragment: nil ,
18
18
authority: nil ,
@@ -21,16 +21,27 @@ defmodule URI do
21
21
port: nil
22
22
23
23
@ type t :: % __MODULE__ {
24
- scheme: nil | binary ,
25
- path: nil | binary ,
26
- query: nil | binary ,
24
+ authority: authority ,
27
25
fragment: nil | binary ,
28
- authority: nil | binary ,
29
- userinfo: nil | binary ,
30
26
host: nil | binary ,
31
- port: nil | :inet . port_number ( )
27
+ path: binary ,
28
+ port: nil | :inet . port_number ( ) ,
29
+ query: nil | binary ,
30
+ scheme: nil | binary ,
31
+ userinfo: nil | binary
32
32
}
33
33
34
+ @ typedoc deprecated: "The authority field is deprecated"
35
+ @ opaque authority :: nil | binary
36
+
37
+ defmodule Error do
38
+ defexception [ :action , :reason , :part ]
39
+
40
+ def message ( % Error { action: action , reason: reason , part: part } ) do
41
+ "cannot #{ action } due to reason #{ reason } : #{ inspect ( part ) } "
42
+ end
43
+ end
44
+
34
45
import Bitwise
35
46
36
47
@ reserved_characters ':/?#[]@!$&\' ()*+,;='
@@ -465,89 +476,202 @@ defmodule URI do
465
476
defp hex_to_dec ( _n ) , do: nil
466
477
467
478
@ doc """
468
- Parses a well-formed URI into its components.
479
+ Creates a new URI struct from a URI or a string.
480
+
481
+ If a `%URI{}` struct is given, it returns `{:ok, uri}`. If a string is
482
+ given, it will parse it and returns `{:ok, uri}`. If the string is
483
+ invalid, it returns `{:error, part}` instead, with the invalid part of the URI.
469
484
470
485
This function can parse both absolute and relative URLs. You can check
471
486
if a URI is absolute or relative by checking if the `scheme` field is
472
- nil or not. Furthermore, this function expects both absolute and
473
- relative URIs to be well-formed and does not perform any validation.
474
- See the "Examples" section below.
475
-
476
- When a URI is given without a port, the value returned by
477
- `URI.default_port/1` for the URI's scheme is used for the `:port` field.
487
+ `nil` or not. All fields may be `nil`, except for the `path`.
478
488
479
- When a URI hostname is an IPv6 literal, it has the `[]` unwrapped before
480
- being stored in the `:host` field. Note this doesn't match the formal
481
- grammar for hostnames, which preserves the `[]` around the IP. You can
482
- parse the IP address by calling `:inet.parse_address/1` (remember to
483
- call `String.to_charlist/1` to convert the host to a charlist before
484
- calling `:inet`).
485
-
486
- If a `%URI{}` struct is given to this function, this function returns it
487
- unmodified.
489
+ When a URI is given without a port, the value returned by `URI.default_port/1`
490
+ for the URI's scheme is used for the `:port` field. The scheme is also
491
+ normalized to lowercase.
488
492
489
493
## Examples
490
494
491
- iex> URI.parse("https://elixir-lang.org/")
492
- %URI{
493
- authority: "elixir-lang.org",
495
+ iex> URI.new("https://elixir-lang.org/")
496
+ {:ok, %URI{
494
497
fragment: nil,
495
498
host: "elixir-lang.org",
496
499
path: "/",
497
500
port: 443,
498
501
query: nil,
499
502
scheme: "https",
500
503
userinfo: nil
501
- }
504
+ }}
502
505
503
- iex> URI.parse("//elixir-lang.org/")
504
- %URI{
505
- authority: "elixir-lang.org",
506
+ iex> URI.new("//elixir-lang.org/")
507
+ {:ok, %URI{
506
508
fragment: nil,
507
509
host: "elixir-lang.org",
508
510
path: "/",
509
511
port: nil,
510
512
query: nil,
511
513
scheme: nil,
512
514
userinfo: nil
513
- }
515
+ }}
514
516
515
- iex> URI.parse("/foo/bar")
516
- %URI{
517
- authority: nil,
517
+ iex> URI.new("/foo/bar")
518
+ {:ok, %URI{
518
519
fragment: nil,
519
520
host: nil,
520
521
path: "/foo/bar",
521
522
port: nil,
522
523
query: nil,
523
524
scheme: nil,
524
525
userinfo: nil
525
- }
526
+ }}
526
527
527
- iex> URI.parse("foo/bar")
528
- %URI{
529
- authority: nil,
528
+ iex> URI.new("foo/bar")
529
+ {:ok, %URI{
530
530
fragment: nil,
531
531
host: nil,
532
532
path: "foo/bar",
533
533
port: nil,
534
534
query: nil,
535
535
scheme: nil,
536
536
userinfo: nil
537
- }
537
+ }}
538
538
539
- iex> URI.parse("//[fe80::]/")
540
- %URI{
541
- authority: "[fe80::]",
539
+ iex> URI.new("//[fe80::]/")
540
+ {:ok, %URI{
542
541
fragment: nil,
543
542
host: "fe80::",
544
543
path: "/",
545
544
port: nil,
546
545
query: nil,
547
546
scheme: nil,
548
547
userinfo: nil
548
+ }}
549
+
550
+ iex> URI.new("https:?query")
551
+ {:ok, %URI{
552
+ fragment: nil,
553
+ host: nil,
554
+ path: "",
555
+ port: 443,
556
+ query: "query",
557
+ scheme: "https",
558
+ userinfo: nil
559
+ }}
560
+
561
+ iex> URI.new("/invalid_greater_than_in_path/>")
562
+ {:error, ">"}
563
+
564
+ Giving an existing URI simply returns it wrapped in a tuple:
565
+
566
+ iex> {:ok, uri} = URI.new("https://elixir-lang.org/")
567
+ iex> URI.new(uri)
568
+ {:ok, %URI{
569
+ fragment: nil,
570
+ host: "elixir-lang.org",
571
+ path: "/",
572
+ port: 443,
573
+ query: nil,
574
+ scheme: "https",
575
+ userinfo: nil
576
+ }}
577
+ """
578
+ @ doc since: "1.13.0"
579
+ @ spec new ( t ( ) | String . t ( ) ) :: { :ok , t ( ) } | { :error , String . t ( ) }
580
+ def new ( % URI { } = uri ) , do: { :ok , uri }
581
+
582
+ def new ( binary ) when is_binary ( binary ) do
583
+ case :uri_string . parse ( binary ) do
584
+ % { } = map -> { :ok , uri_from_map ( map ) }
585
+ { :error , :invalid_uri , term } -> { :error , Kernel . to_string ( term ) }
586
+ end
587
+ end
588
+
589
+ @ doc """
590
+ Similar to `new/0` but raises `URI.Error` if an invalid string is given.
591
+
592
+ ## Examples
593
+
594
+ iex> URI.new!("https://elixir-lang.org/")
595
+ %URI{
596
+ fragment: nil,
597
+ host: "elixir-lang.org",
598
+ path: "/",
599
+ port: 443,
600
+ query: nil,
601
+ scheme: "https",
602
+ userinfo: nil
603
+ }
604
+
605
+ iex> URI.new!("/invalid_greater_than_in_path/>")
606
+ ** (URI.Error) cannot parse due to reason invalid_uri: ">"
607
+
608
+ Giving an existing URI simply returns it:
609
+
610
+ iex> uri = URI.new!("https://elixir-lang.org/")
611
+ iex> URI.new!(uri)
612
+ %URI{
613
+ fragment: nil,
614
+ host: "elixir-lang.org",
615
+ path: "/",
616
+ port: 443,
617
+ query: nil,
618
+ scheme: "https",
619
+ userinfo: nil
549
620
}
550
621
"""
622
+ @ doc since: "1.13.0"
623
+ @ spec new! ( t ( ) | String . t ( ) ) :: t ( )
624
+ def new! ( % URI { } = uri ) , do: uri
625
+
626
+ def new! ( binary ) when is_binary ( binary ) do
627
+ case :uri_string . parse ( binary ) do
628
+ % { } = map ->
629
+ uri_from_map ( map )
630
+
631
+ { :error , reason , part } ->
632
+ raise Error , action: :parse , reason: reason , part: Kernel . to_string ( part )
633
+ end
634
+ end
635
+
636
+ defp uri_from_map ( map ) do
637
+ uri = Map . merge ( % URI { } , map )
638
+
639
+ case map do
640
+ % { scheme: scheme } ->
641
+ scheme = String . downcase ( scheme , :ascii )
642
+
643
+ case map do
644
+ % { port: _ } ->
645
+ % { uri | scheme: scheme }
646
+
647
+ % { } ->
648
+ case default_port ( scheme ) do
649
+ nil -> % { uri | scheme: scheme }
650
+ port -> % { uri | scheme: scheme , port: port }
651
+ end
652
+ end
653
+
654
+ % { } ->
655
+ uri
656
+ end
657
+ end
658
+
659
+ @ doc """
660
+ Parses a well-formed URI into its components.
661
+
662
+ This function is deprecated as it fails to raise in case of invalid URIs.
663
+ Use `URI.new!/1` or `URI.new/1` instead. In case you want to mimic the
664
+ behaviour of this function, you can do:
665
+
666
+ case URI.new(path) do
667
+ {:ok, uri} -> uri
668
+ {:error, _, _} -> %URI{path: path}
669
+ end
670
+
671
+ Also note this function sets the authority field, but the field has been
672
+ deprecated and it is not set by `URI.new!/1` and `URI.new/1`.
673
+ """
674
+ @ doc deprecated: "Use URI.new/1 or URI.new!/1 instead"
551
675
@ spec parse ( t | binary ) :: t
552
676
def parse ( % URI { } = uri ) , do: uri
553
677
@@ -582,7 +706,6 @@ defmodule URI do
582
706
parts
583
707
584
708
scheme = nillify ( scheme )
585
- path = nillify ( path )
586
709
query = nillify_query ( query_with_question_mark )
587
710
{ authority , userinfo , host , port } = split_authority ( authority_with_slashes )
588
711
@@ -646,24 +769,6 @@ defmodule URI do
646
769
iex> URI.to_string(uri)
647
770
"foo://bar.baz"
648
771
649
- Note that when creating this string representation, the `:authority` value will be
650
- used if the `:host` is `nil`. Otherwise, the `:userinfo`, `:host`, and `:port` will
651
- be used.
652
-
653
- iex> URI.to_string(%URI{authority: "[email protected] :80"})
654
-
655
-
656
- iex> URI.to_string(%URI{userinfo: "bar", host: "example.org", port: 81})
657
-
658
-
659
- iex> URI.to_string(%URI{
660
- ...> authority: "[email protected] :80",
661
- ...> userinfo: "bar",
662
- ...> host: "example.org",
663
- ...> port: 81
664
- ...> })
665
-
666
-
667
772
"""
668
773
@ spec to_string ( t ) :: binary
669
774
defdelegate to_string ( uri ) , to: String.Chars.URI
@@ -711,7 +816,7 @@ defmodule URI do
711
816
merge ( parse ( base ) , parse ( rel ) )
712
817
end
713
818
714
- defp merge_paths ( nil , rel_path ) , do: merge_paths ( "/" , rel_path )
819
+ defp merge_paths ( "" , rel_path ) , do: merge_paths ( "/" , rel_path )
715
820
defp merge_paths ( _ , "/" <> _ = rel_path ) , do: remove_dot_segments_from_path ( rel_path )
716
821
717
822
defp merge_paths ( base_path , rel_path ) do
@@ -750,12 +855,10 @@ defmodule URI do
750
855
end
751
856
752
857
defimpl String.Chars , for: URI do
753
- def to_string ( % { host: host , authority: authority , path: path } = uri )
754
- when ( host != nil or authority != nil ) and is_binary ( path ) and
755
- path != "" and binary_part ( path , 0 , 1 ) != "/" do
858
+ def to_string ( % { host: host , path: path } = uri )
859
+ when host != nil and path != "" and binary_part ( path , 0 , 1 ) != "/" do
756
860
raise ArgumentError ,
757
- ":path in URI must be nil or an absolute path if :host or :authority are given, " <>
758
- "got: #{ inspect ( uri ) } "
861
+ ":path in URI must be empty or an absolute path if URL has a :host, got: #{ inspect ( uri ) } "
759
862
end
760
863
761
864
def to_string ( % { scheme: scheme , port: port , path: path , query: query , fragment: fragment } = uri ) do
0 commit comments