草庐IT

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

coder 2023-09-06 原文

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

但是,如果我获得的下一个数据 block 落在 utf8 数据中的一个不幸位置怎么办?就像一个沉着的角色。如果您尝试向它附加一大块不兼容的 utf8,它似乎会弄乱字符串。

处理这个问题的合适方法是什么?我在想我可以将数据保留为 NSData,但无论如何我都不知道数据何时完成接收(想想 HTTP,其中数据长度在 header 中)。

感谢任何想法。

最佳答案

您可能想在这里使用的工具是 UTF8 .它将为您处理所有状态问题。参见 How to cast decrypted UInt8 to String?对于一个您可能可以适应的简单示例。

从 UTF-8 数据构建字符串的主要问题不是组合字符,而是多字节字符。 "LATIN SMALL LETTER A"+ "COMBINING GRAVE ACCENT"即使分别解码每个字符也能正常工作。不起作用的是收集你的第一个字节,解码它,然后附加解码的第二个字节。不过,UTF8 类型会为您处理这个问题。您需要做的就是将您的 NSInputStream 桥接到 GeneratorType

这是我所说内容的一个基本示例(未完全准备好生产)。首先,我们需要一种将 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))。

最后一个问题是,如果您想组合所有组合标记,那么“LATIN SMALL LETTER A”后跟“COMBINING GRAVE ACCENT”将始终一起返回(而不是作为它们的两个字符)。这实际上比听起来有点棘手。首先,您需要生成字符串,而不是字符。然后你需要一个好方法来知道所有的组合字符是什么。这当然是众所周知的,但我在推导一个简单的算法时遇到了一些麻烦。 Cocoa 中没有“combiningMarkCharacterSet”。我还在考虑。获得“大部分工作”的东西很容易,但我还不确定如何构建它以使其对所有 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 创建了你自己的协议(protocol)。抛出可以更容易地将错误传播到堆栈中,但会使插入 for...in 循环变得更加困难。

关于ios - 将流式 (utf8) 数据转换为字符串的安全方法是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34595070/

有关ios - 将流式 (utf8) 数据转换为字符串的安全方法是什么?的更多相关文章

  1. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  2. Ruby 解析字符串 - 2

    我有一个字符串input="maybe(thisis|thatwas)some((nice|ugly)(day|night)|(strange(weather|time)))"Ruby中解析该字符串的最佳方法是什么?我的意思是脚本应该能够像这样构建句子:maybethisissomeuglynightmaybethatwassomenicenightmaybethiswassomestrangetime等等,你明白了......我应该一个字符一个字符地读取字符串并构建一个带有堆栈的状态机来存储括号值以供以后计算,还是有更好的方法?也许为此目的准备了一个开箱即用的库?

  3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  4. ruby-on-rails - 在 Rails 中将文件大小字符串转换为等效千字节 - 2

    我的目标是转换表单输入,例如“100兆字节”或“1GB”,并将其转换为我可以存储在数据库中的文件大小(以千字节为单位)。目前,我有这个:defquota_convert@regex=/([0-9]+)(.*)s/@sizes=%w{kilobytemegabytegigabyte}m=self.quota.match(@regex)if@sizes.include?m[2]eval("self.quota=#{m[1]}.#{m[2]}")endend这有效,但前提是输入是倍数(“gigabytes”,而不是“gigabyte”)并且由于使用了eval看起来疯狂不安全。所以,功能正常,

  5. ruby-on-rails - unicode 字符串的长度 - 2

    在我的Rails(2.3,Ruby1.8.7)应用程序中,我需要将字符串截断到一定长度。该字符串是unicode,在控制台中运行测试时,例如'א'.length,我意识到返回了双倍长度。我想要一个与编码无关的长度,以便对unicode字符串或latin1编码字符串进行相同的截断。我已经了解了Ruby的大部分unicode资料,但仍然有些一头雾水。应该如何解决这个问题? 最佳答案 Rails有一个返回多字节字符的mb_chars方法。试试unicode_string.mb_chars.slice(0,50)

  6. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  7. ruby - 将差异补丁应用于字符串/文件 - 2

    对于具有离线功能的智能手机应用程序,我正在为Xml文件创建单向文本同步。我希望我的服务器将增量/差异(例如GNU差异补丁)发送到目标设备。这是计划:Time=0Server:hasversion_1ofXmlfile(~800kiB)Client:hasversion_1ofXmlfile(~800kiB)Time=1Server:hasversion_1andversion_2ofXmlfile(each~800kiB)computesdeltaoftheseversions(=patch)(~10kiB)sendspatchtoClient(~10kiBtransferred)Cl

  8. ruby-on-rails - Rails 常用字符串(用于通知和错误信息等) - 2

    大约一年前,我决定确保每个包含非唯一文本的Flash通知都将从模块中的方法中获取文本。我这样做的最初原因是为了避免一遍又一遍地输入相同的字符串。如果我想更改措辞,我可以在一个地方轻松完成,而且一遍又一遍地重复同一件事而出现拼写错误的可能性也会降低。我最终得到的是这样的:moduleMessagesdefformat_error_messages(errors)errors.map{|attribute,message|"Error:#{attribute.to_s.titleize}#{message}."}enddeferror_message_could_not_find(obje

  9. ruby - 如何以所有可能的方式将字符串拆分为长度最多为 3 的连续子字符串? - 2

    我试图获取一个长度在1到10之间的字符串,并输出将字符串分解为大小为1、2或3的连续子字符串的所有可能方式。例如:输入:123456将整数分割成单个字符,然后继续查找组合。该代码将返回以下所有数组。[1,2,3,4,5,6][12,3,4,5,6][1,23,4,5,6][1,2,34,5,6][1,2,3,45,6][1,2,3,4,56][12,34,5,6][12,3,45,6][12,3,4,56][1,23,45,6][1,2,34,56][1,23,4,56][12,34,56][123,4,5,6][1,234,5,6][1,2,345,6][1,2,3,456][123

  10. ruby - 什么是填充的 Base64 编码字符串以及如何在 ruby​​ 中生成它们? - 2

    我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%

随机推荐