• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

yast / yast-yast2 / 3883102253

pending completion
3883102253

push

github

Unknown Committer
Unknown Commit Message

1 of 1 new or added line in 1 file covered. (100.0%)

12403 of 29724 relevant lines covered (41.73%)

10.9 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.37
/library/types/src/modules/URL.rb
1
# ***************************************************************************
2
#
3
# Copyright (c) 2002 - 2012 Novell, Inc.
4
# All Rights Reserved.
5
#
6
# This program is free software; you can redistribute it and/or
7
# modify it under the terms of version 2 of the GNU General Public License as
8
# published by the Free Software Foundation.
9
#
10
# This program is distributed in the hope that it will be useful,
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.   See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with this program; if not, contact Novell, Inc.
17
#
18
# To contact Novell about this file by physical or electronic mail,
19
# you may find current contact information at www.novell.com
20
#
21
# ***************************************************************************
22
# File:  modules/URL.ycp
23
# Package:  yast2
24
# Summary:  Manipulate and Parse URLs
25
# Authors:  Michal Svec <msvec@suse.cz>
26
#    Anas Nashif <nashif@suse.cz>
27
# Flags:  Stable
28
#
29
# $Id$
30
require "yast"
1✔
31

32
module Yast
1✔
33
  # A module for dealing with URLs
34
  #
35
  # @note This is legacy code ported from the
36
  # {https://news.opensuse.org/2007/08/29/yast-independence-of-ycp/ YCP} era. Its use is highly
37
  # discouraged in favor of the {https://ruby-doc.org/stdlib-3.0.2/libdoc/uri/rdoc/URI.html URI}
38
  # standard library or the new {Y2Packager::ZyppUrl} (available from SLE-15-SP3 on) when working
39
  # with libzypp URLs.
40
  class URLClass < Module
1✔
41
    def main
1✔
42
      textdomain "base"
1✔
43

44
      Yast.import "Hostname"
1✔
45
      Yast.import "String"
1✔
46
      Yast.import "IP"
1✔
47
      Yast.import "URLRecode"
1✔
48

49
      # TODO: read URI(3), esp. compare the regex mentioned in the URI(3) with ours:
50
      #   my($scheme, $authority, $path, $query, $fragment) =
51
      #   $uri =~ m|^(?:([^:/?#]+):)?(?://([^/?#]*))?([^?#]*)(?:\?([^#]*))?(?:#(.*))?|;
52

53
      # Valid characters in URL
54
      #
55
      # bnc#694582 - addedd @ as it is allowed in authority part of URI.
56
      # for details see RFC2616 and RFC2396
57
      #
58
      @ValidChars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.:_-/%@"
1✔
59

60
      # Transform map used for (un)escaping characters in username/password part of an URL.
61
      # It doesn't contain '%' because this character must be used in a particular
62
      # order (the first or the last) during processing
63
      @transform_map_passwd = {
64
        ";" => "%3b",
1✔
65
        "/" => "%2f",
66
        "?" => "%3f",
67
        ":" => "%3a",
68
        "@" => "%40",
69
        "&" => "%26",
70
        "=" => "%3d",
71
        "+" => "%2b",
72
        "$" => "%24",
73
        "," => "%2c",
74
        " " => "%20"
75
      }
76

77
      # Transform map used for (un)escaping characters in file location part of an URL.
78
      # It doesn't contain '%' because this character must be used in a particular
79
      # order (the first or the last) during processing
80
      @transform_map_filename = {
81
        ";" => "%3b",
1✔
82
        "?" => "%3f",
83
        ":" => "%3a",
84
        "@" => "%40",
85
        "&" => "%26",
86
        "=" => "%3d",
87
        "+" => "%2b",
88
        "$" => "%24",
89
        "," => "%2c",
90
        " " => "%20"
91
      }
92

93
      # Transform map used for (un)escaping characters in query part of a URL.
94
      # It doesn't contain '%' because this character must be used in a particular
95
      # order (the first or the last) during processing
96
      @transform_map_query = {
97
        ";" => "%3b",
1✔
98
        "?" => "%3f",
99
        "@" => "%40",
100
        "+" => "%2b",
101
        "$" => "%24",
102
        "," => "%2c",
103
        " " => "%20"
104
      }
105
    end
106

107
    # Escape reserved characters in string used as a part of URL (e.g. '%25' => '%', '%40' => '@'...)
108
    #
109
    # @param [String] in input string to escape
110
    # @param transformation map
111
    # @return [String] unescaped string
112
    #
113
    # @example
114
    #  URL::UnEscapeString ("http%3a%2f%2fsome.nice.url%2f%3awith%3a%2f%24p#ci%26l%2fch%40rs%2f", URL::transform_map_passwd)
115
    #    -> http://some.nice.url/:with:/$p#ci&l/ch@rs/
116

117
    def UnEscapeString(in_, transform)
1✔
118
      transform = deep_copy(transform)
7✔
119
      return "" if in_.nil? || in_ == ""
7✔
120

121
      # replace the other reserved characters
122
      Builtins.foreach(transform) do |tgt, src|
5✔
123
        # replace both upper and lower case escape sequences
124
        in_ = String.Replace(in_, Builtins.tolower(src), tgt)
55✔
125
        in_ = String.Replace(in_, Builtins.toupper(src), tgt)
55✔
126
      end
127

128
      # replace % at the end
129
      String.Replace(in_, "%25", "%")
5✔
130
    end
131

132
    # Escape reserved characters in string used as a part of URL (e.g. '%' => '%25', '@' => '%40'...)
133
    #
134
    # @param [String] in input string to escape
135
    # @param transformation map
136
    # @return [String] escaped string
137
    #
138
    # @example
139
    #  URL::EscapeString ("http://some.nice.url/:with:/$p#ci&l/ch@rs/", URL::transform_map_passwd)
140
    #    -> http%3a%2f%2fsome.nice.url%2f%3awith%3a%2f%24p#ci%26l%2fch%40rs%2f
141

142
    def EscapeString(in_, transform)
1✔
143
      transform = deep_copy(transform)
8✔
144
      ret = ""
8✔
145

146
      return ret if in_.nil? || in_ == ""
8✔
147

148
      # replace % at first
149
      ret = Builtins.mergestring(Builtins.splitstring(in_, "%"), "%25")
6✔
150

151
      # replace the other reserved characters
152
      Builtins.foreach(transform) do |src, tgt|
6✔
153
        ret = Builtins.mergestring(Builtins.splitstring(ret, src), tgt)
66✔
154
      end
155

156
      ret
6✔
157
    end
158

159
    # Tokenize URL
160
    # @param [String] url URL to be parsed
161
    # @return URL split to tokens
162
    # @example Parse("http://name:pass@www.suse.cz:80/path/index.html?question#part") ->
163
    #     $[
164
    #         "scheme"  : "http",
165
    #         "host"    : "www.suse.cz"
166
    #         "port"    : "80",
167
    #         "path"    : /path/index.html",
168
    #         "user"    : "name",
169
    #         "pass"    : "pass",
170
    #         "query"   : "question",
171
    #         "fragment": "part"
172
    #     ]
173
    def Parse(url)
1✔
174
      Builtins.y2debug("url=%1", url)
74✔
175

176
      # We don't parse empty URLs
177
      return {} if url.nil? || Ops.less_than(Builtins.size(url), 1)
74✔
178

179
      # Extract basic URL parts: scheme://host/path?question#part
180
      rawtokens = Builtins.regexptokenize(
74✔
181
        url,
182
        # 0,1: http://
183
        # 2: user:pass@www.suse.cz:23
184
        # 3: /some/path
185
        # 4,5: ?question
186
        # 6,7: #fragment
187
        "^" \
188
        "(([^:/?#]+):[/]{0,2})?" \
189
        "([^/?#]*)?" \
190
        "([^?#]*)?" \
191
        "(\\?([^#]*))?" \
192
        "(#(.*))?"
193
      )
194
      Builtins.y2debug("rawtokens=%1", rawtokens)
74✔
195
      tokens = {}
74✔
196
      Ops.set(tokens, "scheme", Ops.get_string(rawtokens, 1, ""))
74✔
197
      pth = Ops.get_string(rawtokens, 3, "")
74✔
198
      if Ops.get_string(tokens, "scheme", "") == "ftp"
74✔
199
        if Builtins.substring(pth, 0, 4) == "/%2f"
8✔
200
          pth = Ops.add("/", Builtins.substring(pth, 4))
5✔
201
        elsif pth != ""
3✔
202
          pth = Builtins.substring(pth, 1)
3✔
203
        end
204
      end
205
      Ops.set(tokens, "path", URLRecode.UnEscape(pth))
74✔
206
      Ops.set(
74✔
207
        tokens,
208
        "query",
209
        URLRecode.UnEscape(Ops.get_string(rawtokens, 5, ""))
210
      )
211
      Ops.set(
74✔
212
        tokens,
213
        "fragment",
214
        URLRecode.UnEscape(Ops.get_string(rawtokens, 7, ""))
215
      )
216

217
      # Extract username:pass@host:port
218
      userpass = Builtins.regexptokenize(
74✔
219
        Ops.get_string(rawtokens, 2, ""),
220
        # 0,1,2,3: user:pass@
221
        # 4,5,6,7: hostname|[xxx]
222
        # FIXME: "(([^:@]+)|(\\[([^]]+)\\]))" +
223
        # 8,9: port
224
        "^" \
225
        "(([^@:]+)(:([^@:]+))?@)?" \
226
        "(([^:@]+))" \
227
        "(:([^:@]+))?"
228
      )
229
      Builtins.y2debug("userpass=%1", userpass)
74✔
230

231
      Ops.set(
74✔
232
        tokens,
233
        "user",
234
        URLRecode.UnEscape(Ops.get_string(userpass, 1, ""))
235
      )
236
      Ops.set(
74✔
237
        tokens,
238
        "pass",
239
        URLRecode.UnEscape(Ops.get_string(userpass, 3, ""))
240
      )
241
      Ops.set(tokens, "port", Ops.get_string(userpass, 7, ""))
74✔
242

243
      if Ops.get_string(userpass, 5, "") == ""
74✔
244
        Ops.set(tokens, "host", Ops.get_string(userpass, 7, ""))
27✔
245
      else
246
        Ops.set(tokens, "host", Ops.get_string(userpass, 5, ""))
47✔
247
      end
248

249
      hostport6 = Builtins.substring(
74✔
250
        Ops.get_string(rawtokens, 2, ""),
251
        Builtins.size(Ops.get_string(userpass, 0, ""))
252
      )
253
      Builtins.y2debug("hostport6: %1", hostport6)
74✔
254

255
      # check if there is an IPv6 address
256
      host6 = Builtins.regexpsub(hostport6, "^\\[(.*)\\]", "\\1")
74✔
257

258
      if !host6.nil? && host6 != ""
74✔
259
        Builtins.y2milestone("IPv6 host detected: %1", host6)
6✔
260
        Ops.set(tokens, "host", host6)
6✔
261
        port6 = Builtins.regexpsub(hostport6, "^\\[.*\\]:(.*)", "\\1")
6✔
262
        Builtins.y2debug("port: %1", port6)
6✔
263
        Ops.set(tokens, "port", port6 || "")
6✔
264
      end
265

266
      # some exceptions for samba scheme (there is optional extra option "domain")
267
      if Ops.get_string(tokens, "scheme", "") == "samba" ||
74✔
268
          Ops.get_string(tokens, "scheme", "") == "smb"
269
        # NOTE: CUPS uses different URL syntax for Samba printers:
270
        #     smb://username:password@workgroup/server/printer
271
        # Fortunately yast2-printer does not use URL.ycp, so we can safely support libzypp syntax only:
272
        #     smb://username:passwd@servername/share/path/on/the/share?workgroup=mygroup
273

274
        options = MakeMapFromParams(Ops.get_string(tokens, "query", ""))
4✔
275

276
        Ops.set(tokens, "domain", Ops.get(options, "workgroup", "")) if Builtins.haskey(options, "workgroup")
4✔
277
      end
278

279
      # merge host and path if the scheme does not allow a host (bsc#991935)
280
      tokens = merge_host_and_path(tokens) if SCHEMES_WO_HOST.include?(tokens["scheme"].downcase)
74✔
281

282
      Builtins.y2debug("tokens=%1", tokens)
74✔
283
      deep_copy(tokens)
74✔
284
    end
285

286
    # Check URL
287
    # @param [String] url URL to be checked
288
    # @return true if correct
289
    # @see RFC 2396 (updated by RFC 2732)
290
    # @see also perl-URI: URI(3)
291
    def Check(url)
1✔
292
      # We don't allow empty URLs
293
      return false if url.nil? || Ops.less_than(Builtins.size(url), 1)
43✔
294

295
      # We don't allow URLs with spaces
296
      return false if url.include?(" ")
43✔
297

298
      tokens = Parse(url)
43✔
299

300
      Builtins.y2debug("tokens: %1", tokens)
43✔
301

302
      # Check "scheme"  : "http"
303
      if !Builtins.regexpmatch(
43✔
304
        Ops.get_string(tokens, "scheme", ""),
305
        "^[[:alpha:]]*$"
306
      )
307
        return false
×
308
      end
309

310
      # Check "host"    : "www.suse.cz"
311
      if !Hostname.CheckFQ(Ops.get_string(tokens, "host", "")) &&
43✔
312
          !IP.Check(Ops.get_string(tokens, "host", "")) &&
313
          Ops.get_string(tokens, "host", "") != ""
314
        return false
×
315
      end
316

317
      # Check "path"    : /path/index.html"
318

319
      # Check "port"    : "80"
320
      return false if !Builtins.regexpmatch(Ops.get_string(tokens, "port", ""), "^[0-9]*$")
43✔
321

322
      # Check "user"    : "name"
323

324
      # Check "pass"    : "pass"
325

326
      # Check "query"   : "question"
327

328
      # Check "fragment": "part"
329

330
      true
43✔
331
    end
332

333
    # Build URL from tokens as parsed with Parse
334
    # @param map token as returned from Parse
335
    # @return [String] url, empty string if invalid data is used to build the url.
336
    # @see RFC 2396 (updated by RFC 2732)
337
    # @see also perl-URI: URI(3)
338
    def Build(tokens)
1✔
339
      tokens = deep_copy(tokens)
43✔
340
      url = ""
43✔
341
      userpass = ""
43✔
342

343
      Builtins.y2debug("URL::Build(): input: %1", tokens)
43✔
344

345
      if Builtins.regexpmatch(
43✔
346
        Ops.get_string(tokens, "scheme", ""),
347
        "^[[:alpha:]]*$"
348
      )
349
        # if (tokens["scheme"]:"" == "samba") url="smb";
350
        #     else
351
        url = Ops.get_string(tokens, "scheme", "")
43✔
352
      end
353
      Builtins.y2debug("url: %1", url)
43✔
354
      if Ops.get_string(tokens, "user", "") != ""
43✔
355
        userpass = URLRecode.EscapePassword(Ops.get_string(tokens, "user", ""))
9✔
356
        Builtins.y2milestone(
9✔
357
          "Escaped username '%1' => '%2'",
358
          Ops.get_string(tokens, "user", ""),
359
          userpass
360
        )
361
      end
362
      if Builtins.size(userpass) != 0 &&
43✔
363
          Ops.get_string(tokens, "pass", "") != ""
364
        userpass = Builtins.sformat(
9✔
365
          "%1:%2",
366
          userpass,
367
          URLRecode.EscapePassword(Ops.get_string(tokens, "pass", ""))
368
        )
369
      end
370
      userpass = Ops.add(userpass, "@") if Ops.greater_than(Builtins.size(userpass), 0)
43✔
371

372
      url = Builtins.sformat("%1://%2", url, userpass)
43✔
373
      Builtins.y2debug("url: %1", url)
43✔
374

375
      if Hostname.CheckFQ(Ops.get_string(tokens, "host", "")) ||
43✔
376
          IP.Check(Ops.get_string(tokens, "host", ""))
377
        # enclose an IPv6 address in square brackets
378
        url = if IP.Check6(Ops.get_string(tokens, "host", ""))
27✔
379
          Builtins.sformat("%1[%2]", url, Ops.get_string(tokens, "host", ""))
3✔
380
        else
381
          Builtins.sformat("%1%2", url, Ops.get_string(tokens, "host", ""))
24✔
382
        end
383
      end
384
      Builtins.y2debug("url: %1", url)
43✔
385

386
      if Builtins.regexpmatch(Ops.get_string(tokens, "port", ""), "^[0-9]*$") &&
43✔
387
          Ops.get_string(tokens, "port", "") != ""
388
        url = Builtins.sformat("%1:%2", url, Ops.get_string(tokens, "port", ""))
6✔
389
      end
390
      Builtins.y2debug("url: %1", url)
43✔
391

392
      # path is not empty and doesn't start with "/"
393
      if Ops.get_string(tokens, "path", "") != "" &&
43✔
394
          !Builtins.regexpmatch(Ops.get_string(tokens, "path", ""), "^/")
395
        url = Builtins.sformat(
4✔
396
          "%1/%2",
397
          url,
398
          URLRecode.EscapePath(Ops.get_string(tokens, "path", ""))
399
        )
400
      # patch is not empty and starts with "/"
401
      elsif Ops.get_string(tokens, "path", "") != "" &&
39✔
402
          Builtins.regexpmatch(Ops.get_string(tokens, "path", ""), "^/")
403
        while Builtins.substring(Ops.get_string(tokens, "path", ""), 0, 2) == "//"
35✔
404
          Ops.set(
9✔
405
            tokens,
406
            "path",
407
            Builtins.substring(Ops.get_string(tokens, "path", ""), 1)
408
          )
409
        end
410
        url = if Ops.get_string(tokens, "scheme", "") == "ftp"
35✔
411
          Builtins.sformat(
5✔
412
            "%1/%%2f%2",
413
            url,
414
            Builtins.substring(
415
              URLRecode.EscapePath(Ops.get_string(tokens, "path", "")),
416
              1
417
            )
418
          )
419
        else
420
          Builtins.sformat(
30✔
421
            "%1%2",
422
            url,
423
            URLRecode.EscapePath(Ops.get_string(tokens, "path", ""))
424
          )
425
        end
426
      end
427
      Builtins.y2debug("url: %1", url)
43✔
428

429
      query_map = MakeMapFromParams(Ops.get_string(tokens, "query", ""))
43✔
430

431
      if Ops.get_string(tokens, "scheme", "") == "smb" &&
43✔
432
          Ops.greater_than(
433
            Builtins.size(Ops.get_string(tokens, "domain", "")),
434
            0
435
          ) &&
436
          Ops.get(query_map, "workgroup", "") !=
437
              Ops.get_string(tokens, "domain", "")
438
        Ops.set(query_map, "workgroup", Ops.get_string(tokens, "domain", ""))
1✔
439

440
        Ops.set(tokens, "query", MakeParamsFromMap(query_map))
1✔
441
      end
442

443
      if Ops.get_string(tokens, "query", "") != ""
43✔
444
        url = Builtins.sformat(
11✔
445
          "%1?%2",
446
          url,
447
          URLRecode.EscapeQuery(Ops.get_string(tokens, "query", ""))
448
        )
449
      end
450

451
      if Ops.get_string(tokens, "fragment", "") != ""
43✔
452
        url = Builtins.sformat(
6✔
453
          "%1#%2",
454
          url,
455
          URLRecode.EscapePassword(Ops.get_string(tokens, "fragment", ""))
456
        )
457
      end
458
      Builtins.y2debug("url: %1", url)
43✔
459

460
      if !Check(url)
43✔
461
        Builtins.y2error("Invalid URL: %1", url)
×
462
        return ""
×
463
      end
464

465
      Builtins.y2debug("URL::Build(): result: %1", url)
43✔
466

467
      url
43✔
468
    end
469

470
    #  * Format URL - truncate the middle part of the directory to fit to the requested lenght.
471
    #  *
472
    #  * Elements in the middle of the path specification are replaced by ellipsis (...).
473
    #  * The result migth be longer that requested size if other URL parts are longer than the requested size.
474
    #  * If the requested size is greater than size of the full URL then full URL is returned.
475
    #  * Only path element of the URL is changed the other parts are not modified (e.g. protocol name)
476
    #  *
477
    #  * @example FormatURL("http://download.opensuse.org/very/log/path/which/will/be/truncated/target_file", 45)
478
    # &nbsp;&nbsp;&nbsp;&nbsp;-> "http://download.opensuse.org/.../target_file"
479
    #  * @example FormatURL("http://download.opensuse.org/very/log/path/which/will/be/truncated/target_file", 60)
480
    # &nbsp;&nbsp;&nbsp;&nbsp;-> "http://download.opensuse.org/very/.../be/truncated/target_file"
481
    #  *
482
    #  * @param tokens parsed URL
483
    #  * @see Parse should be used to convert URL string to a map (tokens parameter)
484
    #  * @param len requested maximum lenght of the output string
485
    #  * @return string Truncated URL
486
    def FormatURL(tokens, len)
1✔
487
      tokens = deep_copy(tokens)
4✔
488
      ret = Build(tokens)
4✔
489

490
      # full URL is shorter than requested, no truncation needed
491
      return ret if Ops.less_or_equal(Builtins.size(ret), len)
4✔
492

493
      # it's too long, some parts must be removed
494
      pth = Ops.get_string(tokens, "path", "")
3✔
495
      Ops.set(tokens, "path", "")
3✔
496

497
      no_path = Build(tokens)
3✔
498
      # size for the directory part
499
      dir_size = Ops.subtract(len, Builtins.size(no_path))
3✔
500

501
      # remove the path in the middle
502
      new_path = String.FormatFilename(pth, dir_size)
3✔
503

504
      # build the url with the new path
505
      Ops.set(tokens, "path", new_path)
3✔
506
      Build(tokens)
3✔
507
    end
508

509
    # y2milestone("%1", Parse("http://a:b@www.suse.cz:33/ahoj/nekde?neco#blah"));
510
    # y2milestone("%1", Parse("ftp://www.suse.cz/ah"));
511
    # y2milestone("%1", Parse("ftp://www.suse.cz:22/ah"));
512
    # y2milestone("%1", Parse("www.suse.cz/ah"));
513
    #
514
    # y2milestone("%1", Check("http://a:b@www.suse.cz:33/ahoj/nekde?neco#blah"));
515
    # y2milestone("%1", Check("ftp://www.suse.cz/ah"));
516
    # y2milestone("%1", Check("ftp://www.suse.cz:22/ah"));
517
    # y2milestone("%1", Check("www.suse.cz/ah"));
518
    # y2milestone("%1", Check("www.suse.cz ah"));
519
    # y2milestone("%1", Check(""));
520
    # y2milestone("%1", Check(nil));
521

522
    # Reads list of HTTP params and returns them as map.
523
    # (Useful also for cd:/, dvd:/, nfs:/ ... params)
524
    # Neither keys nor values are HTML-unescaped, see UnEscapeString().
525
    #
526
    # @param [String] params
527
    # @return [Hash{String => String}] params
528
    #
529
    # @example
530
    #      MakeMapFromParams ("device=sda3&login=aaa&password=bbb") -> $[
531
    #              "device"   : "sda3",
532
    #              "login"    : "aaa",
533
    #              "password" : "bbb"
534
    #      ]
535
    def MakeMapFromParams(params)
1✔
536
      # Error
537
      if params.nil?
47✔
538
        Builtins.y2error("Erroneous (nil) params!")
×
539
        return nil
×
540
        # Empty
541
      elsif params == ""
47✔
542
        return {}
33✔
543
      end
544

545
      params_list = Builtins.splitstring(params, "&")
14✔
546

547
      params_list = Builtins.filter(params_list) do |one_param|
14✔
548
        one_param != "" && !one_param.nil?
19✔
549
      end
550

551
      ret = {}
14✔
552
      eq_pos = nil
14✔
553
      opt = ""
14✔
554
      val = ""
14✔
555

556
      Builtins.foreach(params_list) do |one_param|
14✔
557
        eq_pos = Builtins.search(one_param, "=")
19✔
558
        if eq_pos.nil?
19✔
559
          Ops.set(ret, one_param, "")
3✔
560
        else
561
          opt = Builtins.substring(one_param, 0, eq_pos)
16✔
562
          val = Builtins.substring(one_param, Ops.add(eq_pos, 1))
16✔
563

564
          Ops.set(ret, opt, val)
16✔
565
        end
566
      end
567

568
      deep_copy(ret)
14✔
569
    end
570

571
    # Returns string made of HTTP params. It's a reverse function to MakeMapFromParams().
572
    # Neither keys nor values are HTML-escaped, use EscapeString() if needed.
573
    #
574
    # @param map <string, string>
575
    #
576
    # @see #MakeMapFromParams
577
    #
578
    # @example
579
    #   MakeMapFromParams ($[
580
    #     "param1" : "a",
581
    #     "param2" : "b",
582
    #     "param3" : "c",
583
    #   ]) -> "param1=a&param2=b&param3=c"
584
    def MakeParamsFromMap(params_map)
1✔
585
      params_map = deep_copy(params_map)
1✔
586
      # ["key1=value1", "key2=value2", ...] -> "key1=value1&key2=value2"
587
      Builtins.mergestring(
1✔
588
        # ["key" : "value", ...] -> ["key=value", ...]
589
        Builtins.maplist(params_map) do |key, value|
590
          if value.nil?
1✔
591
            Builtins.y2warning("Empty value for key %1", key)
×
592
            value = ""
×
593
          end
594
          if key.nil? || key == ""
1✔
595
            Builtins.y2error("Empty key (will be skipped)")
×
596
            next ""
×
597
          end
598
          # "key=value"
599
          Builtins.sformat("%1=%2", key, value)
1✔
600
        end,
601
        "&"
602
      )
603
    end
604

605
    # Hide password in an URL - replaces the password in the URL by 'PASSWORD' string.
606
    # If there is no password in the URL the original URL is returned.
607
    # It should be used when an URL is logged to y2log or when it is displayed to user.
608
    # @param [String] url original URL
609
    # @return [String] new URL with 'PASSWORD' password or unmodified URL if there is no password
610
    def HidePassword(url)
1✔
611
      # Url::Build(Url::Parse) transforms the URL too much, see #247249#c41
612
      # replace ://user:password@ by ://user:PASSWORD@
613
      subd = Builtins.regexpsub(
×
614
        url,
615
        "(.*)(://[^/:]*):[^/@]*@(.*)",
616
        "\\1\\2:PASSWORD@\\3"
617
      )
618
      subd.nil? ? url : subd
×
619
    end
620

621
    # Hide password token in parsed URL (by URL::Parse()) - the password is replaced by 'PASSWORD' string.
622
    # Similar to HidePassword() but uses a parsed URL as the input.
623
    # @param [Hash] tokens input
624
    # @return [Hash] map with replaced password
625
    def HidePasswordToken(tokens)
1✔
626
      tokens = deep_copy(tokens)
×
627
      ret = deep_copy(tokens)
×
628

629
      # hide the password if it's there
630
      if Builtins.haskey(ret, "pass") &&
×
631
          Ops.greater_than(Builtins.size(Ops.get_string(ret, "pass", "")), 0)
632
        Ops.set(ret, "pass", "PASSWORD")
×
633
      end
634

635
      deep_copy(ret)
×
636
    end
637

638
    publish variable: :ValidChars, type: "string"
1✔
639
    publish variable: :transform_map_passwd, type: "map <string, string>"
1✔
640
    publish variable: :transform_map_filename, type: "map <string, string>"
1✔
641
    publish variable: :transform_map_query, type: "map <string, string>"
1✔
642
    publish function: :UnEscapeString, type: "string (string, map <string, string>)"
1✔
643
    publish function: :EscapeString, type: "string (string, map <string, string>)"
1✔
644
    publish function: :MakeMapFromParams, type: "map <string, string> (string)"
1✔
645
    publish function: :MakeParamsFromMap, type: "string (map <string, string>)"
1✔
646
    publish function: :Parse, type: "map (string)"
1✔
647
    publish function: :Check, type: "boolean (string)"
1✔
648
    publish function: :Build, type: "string (map)"
1✔
649
    publish function: :FormatURL, type: "string (map, integer)"
1✔
650
    publish function: :HidePassword, type: "string (string)"
1✔
651
    publish function: :HidePasswordToken, type: "map (map)"
1✔
652

653
  private
1✔
654

655
    # Schemes which should not include a host.Should be kept in sync with libzypp.
656
    # @see https://github.com/openSUSE/libzypp/blob/d9c97b883ac1561225c4d728a5f6c8a34498d5b9/zypp/Url.cc#L184-L190
657
    # @see #merge_host_and_path
658
    SCHEMES_WO_HOST = ["cd", "dvd", "hd", "iso", "dir"].freeze
1✔
659

660
    # Merges host and path tokens
661
    #
662
    # In schemes like 'cd' or 'dvd' the host part is not allowed.
663
    # It leads to conversions like: "cd:/?device=/dev/sr0" to "cd://?device=/dev/sr0"
664
    # or "cd:/info" to "cd://info".
665
    #
666
    # If no host or path are specified, the path is set to "/".
667
    #
668
    # @param  [Hash<String,String>] URL tokens
669
    # @return [Hash<String,String>] URL tokens with host and path merged
670
    def merge_host_and_path(tokens)
1✔
671
      parts = [tokens["host"], tokens["path"]].reject(&:empty?)
30✔
672
      tokens.merge(
30✔
673
        "path" => File.join("/", *parts),
674
        "host" => ""
675
      )
676
    end
677
  end
678

679
  URL = URLClass.new
1✔
680
  URL.main
1✔
681
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc