skip to Main Content

I need to construct a URL with a string path received from my application server which contains the character: ジ

However, in Swift, the fileURLWithPath seems to encode it incorrectly.

let path = "ジ"
print(URL(fileURLWithPath: path))
print(URL(fileURLWithPath: path.precomposedStringWithCanonicalMapping))

Both print:

%E3%82%B7%E3%82%99

This expected URL path should be:

%E3%82%B8

What am I missing or doing wrong? Any help is appreciated.

2

Answers


  1. you could try this approach using dataRepresentation:

    if let path = "ジ".data(using: .utf8),
       let url = URL(dataRepresentation: path, relativeTo: nil) {
        print("n---> url: (url) n") //---> url: %E3%82%B8
    }
    
    Login or Signup to reply.
  2. There are two different characters, and ジ. They may look the same, but they have different internal representations.

    • The former is “katakana letter zi”, comprised of a single Unicode scalar which percent-encodes as %E3%82%B8.

    • The latter is still a single Swift character, but is comprised of two Unicode scalars (the “katakana letter si” and “combining voiced sound mark”), and these two Unicode scalars percent-encode to %E3%82%B7%E3%82%99.

    One can normalize characters in a string with precomposedStringWithCanonicalMapping, for example. That can convert a character with the two Unicode scalars into a character with a single Unicode scalar.

    But your local file system (or, init(fileURLWithPath:), at least) decomposes diacritics. It is logical that the local file system ensures that diacritics are encoded in some consistent manner. (See Diacritics in file names on macOS behave strangely.) The fact that they are decomposed rather than precomposed is, for the sake of this discussion, a bit academic. When you send it to the server, you want it precomposed, regardless of what is happening in your local file system.

    Now, you tell us that the “url path is rejected by the server”. That does not make sense. One would generally not provide a local file system URL to a remote server. One would generally extract a file name from a local file system URL and send that to the server. This might be done in a variety of ways:

    • You can use precomposedStringWithCanonicalMapping when adding a filename to a server URL, and it honors that mapping, unlike a file URL:

      let path = "ジ"      // actually `%E3%82%B7%E3%82%99` variant
      let url = URL(string: "https://example.com")!
          .appendingPathComponent(path.precomposedStringWithCanonicalMapping)
      print(url)          // https://example.com/%E3%82%B8
      
    • If sending it in the body of a request, use precomposedStringWithCanonicalMapping. E.g. if a filename in a multipart/form-data request:

      body.append("--(boundary)rn")
      body.append("Content-Disposition: form-data; name="(filePathKey)"; filename="(filename.precomposedStringWithCanonicalMapping)"rn")
      body.append("Content-Type: (mimeType)rnrn")
      body.append(data)
      body.append("rn")
      

    Now, those are two random examples of how a filename might be provided to the server. Yours may vary. But the idea is that when you provide the filename, that you precompose the string in its canonical format, rather than relying upon what a file URL in your local file system uses.

    But I would advise avoiding URL(fileURLWithPath:) for manipulating strings provided by the server. It is only to be used when actually referring to files within your local file system. If you just want to percent-encode strings, I would advise using the String method addingPercentEncoding(withAllowedCharacters: .urlPathAllowed). That will not override the precomposedStringWithCanonicalMapping output.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search