You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin/Java的InfoPath附件编码与C#签名是否不同?如何输出一致?

InfoPath附件编码:Kotlin与C#输出不一致问题

按照微软文档实现的Kotlin InfoPath附件编码类,生成的Base64字符串和C#版本存在差异,核心差异出现在头部编码部分:

  • C#生成的头部:x0lGQRQAAAABAAAAAAAAAB8uAAAdAAAA
  • Kotlin生成的头部:x0lGQQAAABQAAAABAAAAAAAALh8AAAAd

原Kotlin实现代码

import java.io.*
import java.util.Base64

class InfoPathAttachmentEncoder(private val fullyQualifiedFileName: String) {

    private var base64EncodedFile = ""

    init {
        if (fullyQualifiedFileName.isEmpty())
            throw IllegalArgumentException("Must specify file name")

        val file = File(fullyQualifiedFileName)
        if (!file.exists())
            throw FileNotFoundException("File does not exist: $fullyQualifiedFileName")
    }

    fun toBase64String(): String {
        if (base64EncodedFile.isNotEmpty())
            return base64EncodedFile

        val ms = ByteArrayOutputStream()
        val f = File(fullyQualifiedFileName)

        DataInputStream(f.inputStream()).use { br ->
            val fileName = f.name
            val fileNameLength = fileName.length + 1
            val fileNameBytes = fileName.toByteArray(Charsets.UTF_16LE)

            DataOutputStream(ms).use { bw ->
                // Write the InfoPath attachment signature
                bw.write(byteArrayOf(0xC7.toByte(), 0x49.toByte(), 0x46.toByte(), 0x41.toByte()))

                // Write header info
                bw.writeInt(0x14) // size
                bw.writeInt(0x01) // version
                bw.writeInt(0x00) // reserved

                // Write file size
                bw.writeInt(f.length().toInt())

                // Write file name size
                bw.writeInt(fileNameLength)

                // Write file name and terminator
                bw.write(fileNameBytes)
                bw.write(byteArrayOf(0, 0))

                // Write file content
                val data = ByteArray(64 * 1024)
                var bytesRead: Int
                while (br.read(data, 0, data.size).also { bytesRead = it } > 0) {
                    bw.write(data, 0, bytesRead)
                }
            }
        }

        val encodedBytes = Base64.getEncoder().encode(ms.toByteArray())
        return String(encodedBytes, Charsets.US_ASCII)
    }
}

// 使用示例
// val i = InfoPathAttachmentEncoder(path)
// println(">>>Encoding {} ${i.toBase64String()}")

问题原因

差异的核心是字节序不匹配:

  • C#的BinaryWriter.Write(int)默认使用**小端字节序(Little-Endian)**写入整数
  • Kotlin/Java的DataOutputStream.writeInt()默认使用**大端字节序(Big-Endian)**写入整数
    签名部分(0xC7,0x49,0x46,0x41)是正确的,问题出在后续所有整数类型的头部字段写入。

修正后的Kotlin实现

替换默认的writeInt为自定义的小端写入方法:

import java.io.*
import java.util.Base64

class InfoPathAttachmentEncoder(private val fullyQualifiedFileName: String) {

    private var base64EncodedFile = ""

    init {
        if (fullyQualifiedFileName.isEmpty())
            throw IllegalArgumentException("Must specify file name")

        val file = File(fullyQualifiedFileName)
        if (!file.exists())
            throw FileNotFoundException("File does not exist: $fullyQualifiedFileName")
    }

    fun toBase64String(): String {
        if (base64EncodedFile.isNotEmpty())
            return base64EncodedFile

        val ms = ByteArrayOutputStream()
        val f = File(fullyQualifiedFileName)

        DataInputStream(f.inputStream()).use { br ->
            val fileName = f.name
            val fileNameLength = fileName.length + 1
            val fileNameBytes = fileName.toByteArray(Charsets.UTF_16LE)

            BufferedOutputStream(ms).use { bw ->
                // Write InfoPath attachment signature
                bw.write(byteArrayOf(0xC7.toByte(), 0x49.toByte(), 0x46.toByte(), 0x41.toByte()))

                // Write header fields in Little-Endian
                writeLittleEndianInt(bw, 0x14) // size
                writeLittleEndianInt(bw, 0x01) // version
                writeLittleEndianInt(bw, 0x00) // reserved

                // Write file size
                writeLittleEndianInt(bw, f.length().toInt())

                // Write file name size
                writeLittleEndianInt(bw, fileNameLength)

                // Write file name and terminator
                bw.write(fileNameBytes)
                bw.write(byteArrayOf(0, 0))

                // Write file content
                val data = ByteArray(64 * 1024)
                var bytesRead: Int
                while (br.read(data, 0, data.size).also { bytesRead = it } > 0) {
                    bw.write(data, 0, bytesRead)
                }
            }
        }

        val encodedBytes = Base64.getEncoder().encode(ms.toByteArray())
        return String(encodedBytes, Charsets.US_ASCII)
    }

    // 自定义小端字节序写入整数
    private fun writeLittleEndianInt(output: OutputStream, value: Int) {
        output.write(value and 0xFF)
        output.write((value shr 8) and 0xFF)
        output.write((value shr 16) and 0xFF)
        output.write((value shr 24) and 0xFF)
    }
}

验证说明

修改后,所有整数类型的头部字段会以小端字节序写入,和C#的行为完全一致,生成的Base64字符串将和C#版本完全匹配。

内容的提问来源于stack exchange,提问作者Ret Ret

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 21:44:57