PercentEncoding.swift 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. /*
  2. * Copyright 2024, gRPC Authors All rights reserved.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package enum PercentEncoding {
  17. package static func encodeAuthority(_ input: String) -> String {
  18. Self.encode(input) {
  19. Self.isAuthorityChar($0)
  20. }
  21. }
  22. package static func encode(
  23. _ input: String,
  24. isValidCharacter: (UInt8) -> Bool
  25. ) -> String {
  26. var output: [UInt8] = []
  27. output.reserveCapacity(input.utf8.count)
  28. for char in input.utf8 {
  29. if isValidCharacter(char) {
  30. output.append(char)
  31. } else {
  32. output.append(UInt8(ascii: "%"))
  33. output.append(Self.hexByte(char >> 4))
  34. output.append(Self.hexByte(char & 0xF))
  35. }
  36. }
  37. return String(decoding: output, as: UTF8.self)
  38. }
  39. private static func hexByte(_ nibble: UInt8) -> UInt8 {
  40. assert(nibble & 0xF == nibble)
  41. switch nibble {
  42. case 0 ... 9:
  43. return nibble &+ UInt8(ascii: "0")
  44. default:
  45. return nibble &+ (UInt8(ascii: "A") &- 10)
  46. }
  47. }
  48. // Characters from RFC 3986 § 2.2
  49. private static func isAlphaNumericChar(_ char: UInt8) -> Bool {
  50. switch char {
  51. case UInt8(ascii: "a") ... UInt8(ascii: "z"):
  52. return true
  53. case UInt8(ascii: "A") ... UInt8(ascii: "Z"):
  54. return true
  55. case UInt8(ascii: "0") ... UInt8(ascii: "9"):
  56. return true
  57. default:
  58. return false
  59. }
  60. }
  61. // Characters from RFC 3986 § 2.2
  62. private static func isSubDelimChar(_ char: UInt8) -> Bool {
  63. switch char {
  64. case UInt8(ascii: "!"):
  65. return true
  66. case UInt8(ascii: "$"):
  67. return true
  68. case UInt8(ascii: "&"):
  69. return true
  70. case UInt8(ascii: "'"):
  71. return true
  72. case UInt8(ascii: "("):
  73. return true
  74. case UInt8(ascii: ")"):
  75. return true
  76. case UInt8(ascii: "*"):
  77. return true
  78. case UInt8(ascii: "+"):
  79. return true
  80. case UInt8(ascii: ","):
  81. return true
  82. case UInt8(ascii: ";"):
  83. return true
  84. case UInt8(ascii: "="):
  85. return true
  86. default:
  87. return false
  88. }
  89. }
  90. // Characters from RFC 3986 § 2.3
  91. private static func isUnreservedChar(_ char: UInt8) -> Bool {
  92. if Self.isAlphaNumericChar(char) { return true }
  93. switch char {
  94. case UInt8(ascii: "-"):
  95. return true
  96. case UInt8(ascii: "."):
  97. return true
  98. case UInt8(ascii: "_"):
  99. return true
  100. case UInt8(ascii: "~"):
  101. return true
  102. default:
  103. return false
  104. }
  105. }
  106. // Characters from RFC 3986 § 3.2
  107. private static func isAuthorityChar(_ char: UInt8) -> Bool {
  108. if Self.isUnreservedChar(char) { return true }
  109. if Self.isSubDelimChar(char) { return true }
  110. switch char {
  111. case UInt8(ascii: ":"):
  112. return true
  113. case UInt8(ascii: "["):
  114. return true
  115. case UInt8(ascii: "]"):
  116. return true
  117. case UInt8(ascii: "@"):
  118. return true
  119. default:
  120. return false
  121. }
  122. }
  123. }