1 // Copyright © 2021 650 Industries. All rights reserved. 2 3 import Foundation 4 5 public enum SerializerError: Error { 6 case emptyKey 7 case invalidCharacterInKey(key: String, character: Character) 8 case invalidCharacterInString(string: String, character: Character) 9 } 10 11 private extension Character { fromHexnull12 static func fromHex(_ hex: String) -> Character { 13 // swiftlint:disable:next force_unwrapping 14 return Character(Unicode.Scalar(Int(hex, radix: 16)!)!) 15 } 16 17 var isDigit: Bool { 18 return self >= Character("0") && self <= Character("9") 19 } 20 21 var isLcAlpha: Bool { 22 self >= Character("a") && self <= Character("z") 23 } 24 25 var isAlpha: Bool { 26 return (self >= Character("A") && self <= Character("Z")) || isLcAlpha 27 } 28 } 29 30 /** 31 Derived from expo-structured-headers Android implementation. 32 */ 33 public class StringStringDictionarySerializer { checkKeynull34 private static func checkKey(key: String) throws { 35 guard !key.isEmpty else { 36 throw SerializerError.emptyKey 37 } 38 39 for (i, c) in key.enumerated() { 40 let failureCondition1 = i == 0 && (c != Character("*") && !c.isLcAlpha) 41 let failureCondition2 = !(c.isLcAlpha || c.isDigit || c == Character("_") || c == Character("-") || c == Character(".") || c == Character("*")) 42 if failureCondition1 || failureCondition2 { 43 throw SerializerError.invalidCharacterInKey(key: key, character: c) 44 } 45 } 46 } 47 checkValuenull48 private static func checkValue(value: String) throws { 49 for c in value { 50 if c < Character.fromHex("20") || c >= Character.fromHex("7f") { 51 throw SerializerError.invalidCharacterInString(string: value, character: c) 52 } 53 } 54 } 55 serializeValuenull56 private static func serializeValue(value: String) -> String { 57 let escapedString = value.replacingOccurrences(of: "\\", with: "\\\\").replacingOccurrences(of: "\"", with: "\\\"") 58 return "\"\(escapedString)\"" 59 } 60 serializenull61 public static func serialize(dictionary: [String: String]) throws -> String { 62 return try dictionary 63 .map { (key: String, value: String) in 64 try checkKey(key: key) 65 try checkValue(value: value) 66 return "\(key)=\(serializeValue(value: value))" 67 } 68 .joined(separator: ", ") 69 } 70 } 71