【问题标题】:NSNetService dictionaryFromTXTRecord fails an assertion on invalid inputNSNetService dictionaryFromTXTRecord 对无效输入的断言失败
【发布时间】:2016-10-22 15:36:17
【问题描述】:

dictionary(fromTXTRecord:) 的输入来自网络,可能来自应用程序外部,甚至是设备。然而,苹果的docs 说:

...如果 txtData 不能表示为 NSDictionary 对象,则断言失败。

断言失败会使程序员(我)无法处理错误,这对于处理外部数据的方法来说似乎不合逻辑。

如果我在 Mac 上的终端中运行它:

dns-sd -R 'My Service Name' _myservice._tcp local 4567 asdf asdf

我在 iPhone 上运行的应用程序崩溃了。

dictionary(fromTXTRecord:) 期望 TXT 记录数据 (asdf asdf) 采用 key=val 形式。如果像上面一样,一个词不包含任何=,则该方法将无法解析它并导致断言失败。

除了完全不使用该方法并实现我自己的解析之外,我认为没有其他方法可以解决这个问题,这感觉不对。

我错过了什么吗?

【问题讨论】:

标签: ios swift cocoa nsnetservice


【解决方案1】:

这是 Swift 4.2 中的一个解决方案,假设 TXT 记录只有字符串:

/// Decode the TXT record as a string dictionary, or [:] if the data is malformed
public func dictionary(fromTXTRecord txtData: Data) -> [String: String] {

    var result = [String: String]()
    var data = txtData

    while !data.isEmpty {
        // The first byte of each record is its length, so prefix that much data
        let recordLength = Int(data.removeFirst())
        guard data.count >= recordLength else { return [:] }
        let recordData = data[..<(data.startIndex + recordLength)]
        data = data.dropFirst(recordLength)

        guard let record = String(bytes: recordData, encoding: .utf8) else { return [:] }
        // The format of the entry is "key=value"
        // (According to the reference implementation, = is optional if there is no value,
        // and any equals signs after the first are part of the value.)
        // `ommittingEmptySubsequences` is necessary otherwise an empty string will crash the next line
        let keyValue = record.split(separator: "=", maxSplits: 1, omittingEmptySubsequences: false)
        let key = String(keyValue[0])
        // If there's no value, make the value the empty string
        switch keyValue.count {
        case 1:
            result[key] = ""
        case 2:
            result[key] = String(keyValue[1])
        default:
            fatalError()
        }
    }

    return result
}

【讨论】:

    【解决方案2】:

    我仍然希望我在这里缺少一些东西,但与此同时,我最终检查了数据的正确性,然后才调用 Apple 自己的方法。

    这是我的解决方法:

    func dictionaryFromTXTRecordData(data: NSData) -> [String:NSData] {
        let buffer = UnsafeBufferPointer<UInt8>(start: UnsafePointer(data.bytes), count: data.length)
        var pos = 0
        while pos < buffer.count {
            let len = Int(buffer[pos])
            if len > (buffer.count - pos + 1) {
                return [:]
            }
            let subdata = data.subdataWithRange(NSRange(location: pos + 1, length: len))
            guard let substring = String(data: subdata, encoding: NSUTF8StringEncoding) else {
                return [:]
            }
            if !substring.containsString("=") {
                return [:]
            }
            pos = pos + len + 1
        }
        return NSNetService.dictionaryFromTXTRecordData(data)
    }
    

    我在这里使用 Swift 2。欢迎所有贡献。 Swift 3 版本、Objective-C 版本、改进、更正。

    【讨论】:

      【解决方案3】:

      我刚刚使用 Swift 3 遇到了这个问题。就我而言,问题仅在我使用 NetService.dictionary(fromTXTRecord:) 时发生,但在我切换到 Objective-C 并调用 NSNetService dictionaryFromTXTRecord: 时没有发生。当 Objective-C 调用遇到一个没有等号的条目时,它会创建一个包含数据的键并将其推入带有 NSNull 值的字典中。据我所知,Swift 版本会枚举该字典并在看到NSNull 时抛出一个合适的结果。我的解决方案是添加一个 Objective-C 文件和一个调用 dictionaryFromTXTRecord: 的实用程序函数,并在将结果返回给我的 Swift 代码之前对其进行清理。

      【讨论】:

      • 好电话。我没想过从Objective-C尝试。但是,重申一下我的问题,这不是苹果方面的错误吗?
      • 这种行为使这个调用的 Swift 版本无用,所以它看起来像一个错误。另一方面,尚不清楚苹果将如何修复它。 NSNull 不能转移到 [String:Data] 类型的 Swift 字典中。它不能变成零,因为这相当于删除了密钥。它不能被映射到一个空的Data对象而不失去“hello”和“hello=”的区别;在 Objective-C 中,前者返回一个 NSNull 值,后者返回一个空 NSData 对象的值。因此,Apple 只是在这个问题上举手。
      猜你喜欢
      • 1970-01-01
      • 2020-09-16
      • 1970-01-01
      • 1970-01-01
      • 2016-01-05
      • 2011-05-27
      • 1970-01-01
      • 2023-02-22
      相关资源
      最近更新 更多