You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

130 lines
3.9 KiB

  1. %%% @doc multi-OTP version compatibility shim for working with URIs
  2. -module(rebar_uri).
  3. -export([
  4. parse/1, parse/2, scheme_defaults/0,
  5. append_path/2
  6. ]).
  7. -ifdef(OTP_RELEASE).
  8. -spec parse(URIString) -> URIMap when
  9. URIString :: uri_string:uri_string(),
  10. URIMap :: uri_string:uri_map() | uri_string:error().
  11. parse(URIString) ->
  12. parse(URIString, []).
  13. parse(URIString, URIOpts) ->
  14. case uri_string:parse(URIString) of
  15. #{path := ""} = Map -> apply_opts(Map#{path => "/"}, URIOpts);
  16. Map -> apply_opts(Map, URIOpts)
  17. end.
  18. -else.
  19. -spec parse(URIString) -> URIMap when
  20. URIString :: iodata(),
  21. URIMap :: map() | {error, atom(), term()}.
  22. parse(URIString) ->
  23. parse(URIString, []).
  24. parse(URIString, URIOpts) ->
  25. case http_uri:parse(URIString, URIOpts) of
  26. {error, Reason} ->
  27. %% no additional parser/term info available to us,
  28. %% e.g. see what uri_string returns in
  29. %% uri_string:parse(<<"h$ttp:::://////lolz">>).
  30. {error, Reason, ""};
  31. {ok, {Scheme, UserInfo, Host, Port, Path, Query}} ->
  32. #{
  33. scheme => rebar_utils:to_list(Scheme),
  34. host => Host,
  35. port => Port,
  36. path => Path,
  37. %% http_uri:parse/1 includes the leading question mark
  38. %% in query string but uri_string:parse/1 leaves it out.
  39. %% string:slice/2 isn't available in OTP <= 19.
  40. query => case Query of
  41. [] -> "";
  42. _ -> string:substr(Query, 2)
  43. end,
  44. userinfo => UserInfo
  45. }
  46. end.
  47. -endif.
  48. %% OTP 21+
  49. -ifdef(OTP_RELEASE).
  50. append_path(Url, ExtraPath) ->
  51. case parse(Url) of
  52. #{path := Path} = Map ->
  53. FullPath = join(Path, ExtraPath),
  54. {ok, uri_string:recompose(maps:update(path, FullPath, Map))};
  55. _ ->
  56. error
  57. end.
  58. -else.
  59. append_path(Url, ExtraPath) ->
  60. case parse(Url) of
  61. #{scheme := Scheme, userinfo := UserInfo, host := Host,
  62. port := Port, path := Path, query := Query} ->
  63. ListScheme = rebar_utils:to_list(Scheme),
  64. PrefixedQuery = case Query of
  65. [] -> [];
  66. Other -> lists:append(["?", Other])
  67. end,
  68. NormPath = case Path of
  69. "" -> "/";
  70. _ -> Path
  71. end,
  72. {ok, maybe_port(
  73. Url, lists:append([ListScheme, "://", UserInfo, Host]),
  74. [$: | rebar_utils:to_list(Port)],
  75. lists:append([join(NormPath, ExtraPath), PrefixedQuery])
  76. )};
  77. _ ->
  78. error
  79. end.
  80. -endif.
  81. %% OTP 21+
  82. -ifdef(OTP_RELEASE).
  83. scheme_defaults() ->
  84. %% no scheme defaults here; just custom ones
  85. [].
  86. -else.
  87. scheme_defaults() ->
  88. http_uri:scheme_defaults().
  89. -endif.
  90. join(URI, "") -> URI;
  91. join(URI, "/") -> URI;
  92. join("/", [$/|_] = Path) -> Path;
  93. join("/", Path) -> [$/ | Path];
  94. join("", [$/|_] = Path) -> Path;
  95. join("", Path) -> [$/ | Path];
  96. join([H|T], Path) -> [H | join(T, Path)].
  97. -ifdef(OTP_RELEASE).
  98. apply_opts(Map = #{port := _}, _) ->
  99. Map;
  100. apply_opts(Map = #{scheme := Scheme}, URIOpts) ->
  101. SchemeDefaults = proplists:get_value(scheme_defaults, URIOpts, []),
  102. %% Here is the funky bit: don't add the port number if it's in a default
  103. %% to maintain proper default behaviour.
  104. try lists:keyfind(list_to_existing_atom(Scheme), 1, SchemeDefaults) of
  105. {_, Port} ->
  106. Map#{port => Port};
  107. false ->
  108. Map
  109. catch
  110. error:badarg -> % not an existing atom, not in the list
  111. Map
  112. end.
  113. -else.
  114. maybe_port(Url, Host, Port, PathQ) ->
  115. case lists:prefix(Host ++ Port, Url) of
  116. true -> Host ++ Port ++ PathQ; % port was explicit
  117. false -> Host ++ PathQ % port was implicit
  118. end.
  119. -endif.