将流式传输(utf8)数据转换为字符串的安全方法是什么?

时间:2016-01-04 16:02:28

标签: ios swift cocoa utf-8

假设我是用objc / swift编写的服务器。客户端正在向我发送大量数据,这实际上是一个很大的utf8编码字符串。作为服务器,我有我的NSInputStream触发事件,说它有数据要读取。我抓住数据并用它构建一个字符串。

然而,如果我获得的下一块数据落在utf8数据中的不幸位置怎么办?喜欢组成的角色。如果你试图将一块不合规的utf8附加到它上面,似乎会弄乱字符串。

有什么方法可以解决这个问题?我当时认为我可以将数据保存为NSData,但是我无论如何都不知道数据何时被接收(想想数据长度在标题中的HTTP)。

感谢您的任何想法。

1 个答案:

答案 0 :(得分:6)

您可能希望在此处使用的工具是UTF8。它将为您处理所有州的问题。有关您可能适应的简单示例,请参阅How to cast decrypted UInt8 to String?

从UTF-8数据构建字符串的主要问题不是组成字符,而是多字节字符。 "拉丁文小写字母A" +"结合GRAVE ACCENT"即使分别解码每个字符也能正常工作。什么不起作用是收集你的第一个字节,解码它,然后附加解码的第二个字节。但UTF8类型将为您处理此问题。您所需要做的就是将NSInputStreamGeneratorType联系起来。

这是我所谈论的基本(不完全生产就绪)的例子。首先,我们需要一种方法将NSInputStream转换为生成器。这可能是最难的部分:

final class StreamGenerator {
    static let bufferSize = 1024
    let stream: NSInputStream
    var buffer = [UInt8](count: StreamGenerator.bufferSize, repeatedValue: 0)
    var buffGen = IndexingGenerator<ArraySlice<UInt8>>([])

    init(stream: NSInputStream) {
        self.stream = stream
        stream.open()
    }
}

extension StreamGenerator: GeneratorType {
    func next() -> UInt8? {
        // Check the stream status
        switch stream.streamStatus {
        case .NotOpen:
            assertionFailure("Cannot read unopened stream")
            return nil
        case .Writing:
            preconditionFailure("Impossible status")
        case .AtEnd, .Closed, .Error:
            return nil // FIXME: May want a closure to post errors
        case .Opening, .Open, .Reading:
            break
        }

        // First see if we can feed from our buffer
        if let result = buffGen.next() {
            return result
        }

        // Our buffer is empty. Block until there is at least one byte available
        let count = stream.read(&buffer, maxLength: buffer.capacity)

        if count <= 0 { // FIXME: Probably want a closure or something to handle error cases
            stream.close()
            return nil
        }

        buffGen = buffer.prefix(count).generate()
        return buffGen.next()
    }
}

next()的调用可以在此处阻止,因此不应在主队列上调用它,但除此之外,它是一个标准的生成器,它会吐出字节。 (这也是可能有许多我不会处理的小角落的部分,所以你想仔细考虑这一点。但是,它并不复杂。)

有了这个,创建一个UTF-8解码生成器几乎是微不足道的:

final class UnicodeScalarGenerator<ByteGenerator: GeneratorType where ByteGenerator.Element == UInt8> {
    var byteGenerator: ByteGenerator
    var utf8 = UTF8()
    init(byteGenerator: ByteGenerator) {
        self.byteGenerator = byteGenerator
    }
}

extension UnicodeScalarGenerator: GeneratorType {
    func next() -> UnicodeScalar? {
        switch utf8.decode(&byteGenerator) {
        case .Result(let scalar): return scalar
        case .EmptyInput: return nil
        case .Error: return nil // FIXME: Probably want a closure or something to handle error cases
        }
    }
}

您当然可以将其简单地转换为CharacterGenerator(使用Character(_:UnicodeScalar))。

最后一个问题是如果你想要组合所有组合标记,那么&#34;拉丁文小写字母A&#34;其次是&#34; COMBINING GRAVE ACCENT&#34;将永远一起返回(而不是作为他们的两个字符)。这实际上比听起来有点棘手。首先,您需要生成字符串,而不是字符。然后你需要一个很好的方法来了解所有组合字符是什么。这当然是可以知道的,但我在获得一个简单的算法时遇到了一些麻烦。没有&#34; combineMarkCharacterSet&#34;在可可。我还在考虑它。得到的东西&#34;主要是工作&#34;很简单,但我还不确定如何构建它以使其对所有Unicode都正确。

这里有一个小样本程序可以试用:

    let textPath = NSBundle.mainBundle().pathForResource("text.txt", ofType: nil)!
    let inputStream = NSInputStream(fileAtPath: textPath)!
    inputStream.open()

    dispatch_async(dispatch_get_global_queue(0, 0)) {
        let streamGen = StreamGenerator(stream: inputStream)
        let unicodeGen = UnicodeScalarGenerator(byteGenerator: streamGen)
        var string = ""
        for c in GeneratorSequence(unicodeGen) {
            print(c)
            string += String(c)
        }
        print(string)
    }

还有一个小文:

Here is some normalish álfa你好 text
And some Zalgo i̝̲̲̗̹̼n͕͓̘v͇̠͈͕̻̹̫͡o̷͚͍̙͖ke̛̘̜̘͓̖̱̬ composed stuff
And one more line with no newline

(第二行是Zalgo encoded text,非常适合测试。)

我还没有在真正的阻止情况下对此进行任何测试,例如从网络上读取,但它应该根据NSInputStream的工作原理(即它应该阻止直到那里)至少要读取一个字节,但是应该只用可用的任何东西填充缓冲区。

我已完成所有这些匹配GeneratorType,以便轻松插入其他内容,但如果您没有使用GeneratorType而是创建了您的错误处理可能会更好相反,使用next() throws -> Self.Element自己的协议。抛出可以更容易地将错误传播到堆栈中,但会使插入for...in循环变得更加困难。