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
相关产品推荐
相关产品推荐

