mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Remove Little Endian output stream
This commit is contained in:
@@ -183,12 +183,10 @@ class ValuesTest : TestCase() {
|
||||
ulongBytes[i] = -1
|
||||
}
|
||||
|
||||
val bos = ByteArrayOutputStream()
|
||||
val leos = LittleEndianDataOutputStream(bos)
|
||||
leos.writeLong(UnsignedLong.MAX_VALUE)
|
||||
leos.close()
|
||||
|
||||
val uLongMax = bos.toByteArray()
|
||||
val byteArrayOutputStream = ByteArrayOutputStream()
|
||||
byteArrayOutputStream.write8BytesLong(UnsignedLong.MAX_VALUE)
|
||||
byteArrayOutputStream.close()
|
||||
val uLongMax = byteArrayOutputStream.toByteArray()
|
||||
|
||||
assertArrayEquals(ulongBytes, uLongMax)
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import com.kunzisoft.keepass.database.exception.DatabaseOutputException
|
||||
import com.kunzisoft.keepass.database.file.DatabaseHeader
|
||||
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
|
||||
import com.kunzisoft.keepass.stream.*
|
||||
import com.kunzisoft.keepass.utils.UnsignedInt
|
||||
import com.kunzisoft.keepass.utils.UnsignedLong
|
||||
import com.kunzisoft.keepass.utils.VariantDictionary
|
||||
import java.io.ByteArrayOutputStream
|
||||
@@ -42,7 +43,6 @@ constructor(private val databaseKDBX: DatabaseKDBX,
|
||||
private val header: DatabaseHeaderKDBX,
|
||||
outputStream: OutputStream) {
|
||||
|
||||
private val los: LittleEndianDataOutputStream
|
||||
private val mos: MacOutputStream
|
||||
private val dos: DigestOutputStream
|
||||
lateinit var headerHmac: ByteArray
|
||||
@@ -79,15 +79,14 @@ constructor(private val databaseKDBX: DatabaseKDBX,
|
||||
|
||||
dos = DigestOutputStream(outputStream, md)
|
||||
mos = MacOutputStream(dos, hmac)
|
||||
los = LittleEndianDataOutputStream(mos)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun output() {
|
||||
|
||||
los.writeUInt(DatabaseHeader.PWM_DBSIG_1)
|
||||
los.writeUInt(DatabaseHeaderKDBX.DBSIG_2)
|
||||
los.writeUInt(header.version)
|
||||
mos.write4BytesUInt(DatabaseHeader.PWM_DBSIG_1)
|
||||
mos.write4BytesUInt(DatabaseHeaderKDBX.DBSIG_2)
|
||||
mos.write4BytesUInt(header.version)
|
||||
|
||||
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.CipherID, uuidTo16Bytes(databaseKDBX.dataCipher))
|
||||
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.CompressionFlags, uIntTo4Bytes(DatabaseHeaderKDBX.getFlagFromCompression(databaseKDBX.compressionAlgorithm)))
|
||||
@@ -112,14 +111,13 @@ constructor(private val databaseKDBX: DatabaseKDBX,
|
||||
|
||||
if (databaseKDBX.containsPublicCustomData()) {
|
||||
val bos = ByteArrayOutputStream()
|
||||
val los = LittleEndianDataOutputStream(bos)
|
||||
VariantDictionary.serialize(databaseKDBX.publicCustomData, los)
|
||||
VariantDictionary.serialize(databaseKDBX.publicCustomData, bos)
|
||||
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.PublicCustomData, bos.toByteArray())
|
||||
}
|
||||
|
||||
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.EndOfHeader, EndHeaderValue)
|
||||
|
||||
los.flush()
|
||||
mos.flush()
|
||||
hashOfHeader = dos.messageDigest.digest()
|
||||
headerHmac = mos.mac
|
||||
}
|
||||
@@ -127,11 +125,11 @@ constructor(private val databaseKDBX: DatabaseKDBX,
|
||||
@Throws(IOException::class)
|
||||
private fun writeHeaderField(fieldId: Byte, pbData: ByteArray?) {
|
||||
// Write the field id
|
||||
los.write(fieldId.toInt())
|
||||
mos.write(fieldId.toInt())
|
||||
|
||||
if (pbData != null) {
|
||||
writeHeaderFieldSize(pbData.size)
|
||||
los.write(pbData)
|
||||
mos.write(pbData)
|
||||
} else {
|
||||
writeHeaderFieldSize(0)
|
||||
}
|
||||
@@ -140,9 +138,9 @@ constructor(private val databaseKDBX: DatabaseKDBX,
|
||||
@Throws(IOException::class)
|
||||
private fun writeHeaderFieldSize(size: Int) {
|
||||
if (header.version.toKotlinLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong()) {
|
||||
los.writeUShort(size)
|
||||
mos.write2BytesUShort(size)
|
||||
} else {
|
||||
los.writeInt(size)
|
||||
mos.write4BytesUInt(UnsignedInt(size))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -26,8 +26,9 @@ import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
|
||||
import com.kunzisoft.keepass.database.exception.DatabaseOutputException
|
||||
import com.kunzisoft.keepass.database.file.DatabaseHeader
|
||||
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB
|
||||
import com.kunzisoft.keepass.stream.LittleEndianDataOutputStream
|
||||
import com.kunzisoft.keepass.stream.NullOutputStream
|
||||
import com.kunzisoft.keepass.stream.write2BytesUShort
|
||||
import com.kunzisoft.keepass.stream.write4BytesUInt
|
||||
import com.kunzisoft.keepass.utils.UnsignedInt
|
||||
import java.io.BufferedOutputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
@@ -198,14 +199,13 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
|
||||
@Suppress("CAST_NEVER_SUCCEEDS")
|
||||
@Throws(DatabaseOutputException::class)
|
||||
fun outputPlanGroupAndEntries(outputStream: OutputStream) {
|
||||
val littleEndianDataOutputStream = LittleEndianDataOutputStream(outputStream)
|
||||
|
||||
// useHeaderHash
|
||||
if (headerHashBlock != null) {
|
||||
try {
|
||||
littleEndianDataOutputStream.writeUShort(0x0000)
|
||||
littleEndianDataOutputStream.writeInt(headerHashBlock!!.size)
|
||||
littleEndianDataOutputStream.write(headerHashBlock!!)
|
||||
outputStream.write2BytesUShort(0x0000)
|
||||
outputStream.write4BytesUInt(UnsignedInt(headerHashBlock!!.size))
|
||||
outputStream.write(headerHashBlock!!)
|
||||
} catch (e: IOException) {
|
||||
throw DatabaseOutputException("Failed to output header hash.", e)
|
||||
}
|
||||
@@ -252,24 +252,22 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun writeExtData(headerDigest: ByteArray, os: OutputStream) {
|
||||
val los = LittleEndianDataOutputStream(os)
|
||||
|
||||
writeExtDataField(los, 0x0001, headerDigest, headerDigest.size)
|
||||
private fun writeExtData(headerDigest: ByteArray, outputStream: OutputStream) {
|
||||
writeExtDataField(outputStream, 0x0001, headerDigest, headerDigest.size)
|
||||
val headerRandom = ByteArray(32)
|
||||
val rand = SecureRandom()
|
||||
rand.nextBytes(headerRandom)
|
||||
writeExtDataField(los, 0x0002, headerRandom, headerRandom.size)
|
||||
writeExtDataField(los, 0xFFFF, null, 0)
|
||||
writeExtDataField(outputStream, 0x0002, headerRandom, headerRandom.size)
|
||||
writeExtDataField(outputStream, 0xFFFF, null, 0)
|
||||
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun writeExtDataField(los: LittleEndianDataOutputStream, fieldType: Int, data: ByteArray?, fieldSize: Int) {
|
||||
los.writeUShort(fieldType)
|
||||
los.writeInt(fieldSize)
|
||||
private fun writeExtDataField(outputStream: OutputStream, fieldType: Int, data: ByteArray?, fieldSize: Int) {
|
||||
outputStream.write2BytesUShort(fieldType)
|
||||
outputStream.write4BytesUInt(UnsignedInt(fieldSize))
|
||||
if (data != null) {
|
||||
los.write(data)
|
||||
outputStream.write(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -122,18 +122,16 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
|
||||
@Throws(IOException::class)
|
||||
private fun outputInnerHeader(database: DatabaseKDBX,
|
||||
header: DatabaseHeaderKDBX,
|
||||
outputStream: OutputStream) {
|
||||
val dataOutputStream = LittleEndianDataOutputStream(outputStream)
|
||||
|
||||
dataOutputStream: OutputStream) {
|
||||
dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.InnerRandomStreamID)
|
||||
dataOutputStream.writeInt(4)
|
||||
dataOutputStream.write4BytesUInt(UnsignedInt(4))
|
||||
if (header.innerRandomStream == null)
|
||||
throw IOException("Can't write innerRandomStream")
|
||||
dataOutputStream.writeUInt(header.innerRandomStream!!.id)
|
||||
dataOutputStream.write4BytesUInt(header.innerRandomStream!!.id)
|
||||
|
||||
val streamKeySize = header.innerRandomStreamKey.size
|
||||
dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.InnerRandomstreamKey)
|
||||
dataOutputStream.writeInt(streamKeySize)
|
||||
dataOutputStream.write4BytesUInt(UnsignedInt(streamKeySize))
|
||||
dataOutputStream.write(header.innerRandomStreamKey)
|
||||
|
||||
val binaryCache = database.binaryCache
|
||||
@@ -143,7 +141,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
|
||||
// Write type binary
|
||||
dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary)
|
||||
// Write size
|
||||
dataOutputStream.writeUInt(UnsignedInt.fromKotlinLong(binary.getSize() + 1))
|
||||
dataOutputStream.write4BytesUInt(UnsignedInt.fromKotlinLong(binary.getSize() + 1))
|
||||
// Write protected flag
|
||||
var flag = DatabaseHeaderKDBX.KdbxBinaryFlags.None
|
||||
if (binary.isProtected) {
|
||||
@@ -159,7 +157,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
|
||||
}
|
||||
|
||||
dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.EndOfHeader)
|
||||
dataOutputStream.writeInt(0)
|
||||
dataOutputStream.write4BytesUInt(UnsignedInt(0))
|
||||
}
|
||||
|
||||
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
|
||||
|
||||
@@ -28,7 +28,7 @@ import kotlin.math.min
|
||||
|
||||
class HashedBlockOutputStream : OutputStream {
|
||||
|
||||
private lateinit var baseStream: LittleEndianDataOutputStream
|
||||
private lateinit var baseStream: OutputStream
|
||||
private lateinit var buffer: ByteArray
|
||||
private var bufferPos = 0
|
||||
private var bufferIndex: Long = 0
|
||||
@@ -47,7 +47,7 @@ class HashedBlockOutputStream : OutputStream {
|
||||
}
|
||||
|
||||
private fun init(os: OutputStream, bufferSize: Int) {
|
||||
baseStream = LittleEndianDataOutputStream(os)
|
||||
baseStream = os
|
||||
buffer = ByteArray(bufferSize)
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ class HashedBlockOutputStream : OutputStream {
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun writeHashedBlock() {
|
||||
baseStream.writeUInt(UnsignedInt.fromKotlinLong(bufferIndex))
|
||||
baseStream.write4BytesUInt(UnsignedInt.fromKotlinLong(bufferIndex))
|
||||
bufferIndex++
|
||||
|
||||
if (bufferPos > 0) {
|
||||
@@ -117,13 +117,13 @@ class HashedBlockOutputStream : OutputStream {
|
||||
|
||||
} else {
|
||||
// Write 32-bits of zeros
|
||||
baseStream.writeLong(0L)
|
||||
baseStream.writeLong(0L)
|
||||
baseStream.writeLong(0L)
|
||||
baseStream.writeLong(0L)
|
||||
baseStream.write8BytesLong(0L)
|
||||
baseStream.write8BytesLong(0L)
|
||||
baseStream.write8BytesLong(0L)
|
||||
baseStream.write8BytesLong(0L)
|
||||
}
|
||||
|
||||
baseStream.writeInt(bufferPos)
|
||||
baseStream.write4BytesUInt(UnsignedInt(bufferPos))
|
||||
|
||||
if (bufferPos > 0) {
|
||||
baseStream.write(buffer, 0, bufferPos)
|
||||
|
||||
@@ -28,12 +28,10 @@ import java.security.NoSuchAlgorithmException
|
||||
import javax.crypto.Mac
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
class HmacBlockOutputStream(outputStream: OutputStream,
|
||||
class HmacBlockOutputStream(private val baseStream: OutputStream,
|
||||
private val key: ByteArray)
|
||||
: OutputStream() {
|
||||
|
||||
private val baseStream: LittleEndianDataOutputStream = LittleEndianDataOutputStream(outputStream)
|
||||
|
||||
private val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
|
||||
private var bufferPos = 0
|
||||
private var blockIndex: Long = 0
|
||||
|
||||
@@ -33,19 +33,14 @@ object HmacBlockStream {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
|
||||
val nos = NullOutputStream()
|
||||
val dos = DigestOutputStream(nos, hash)
|
||||
val leos = LittleEndianDataOutputStream(dos)
|
||||
|
||||
val digestOutputStream = DigestOutputStream(NullOutputStream(), hash)
|
||||
try {
|
||||
leos.writeLong(blockIndex)
|
||||
leos.write(key)
|
||||
leos.close()
|
||||
digestOutputStream.write8BytesLong(blockIndex)
|
||||
digestOutputStream.write(key)
|
||||
digestOutputStream.close()
|
||||
} catch (e: IOException) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
|
||||
//assert(hashKey.length == 64);
|
||||
return hash.digest()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.stream
|
||||
|
||||
import com.kunzisoft.keepass.utils.UnsignedInt
|
||||
import java.io.IOException
|
||||
import java.io.OutputStream
|
||||
|
||||
|
||||
/**
|
||||
* Little Endian version of the DataOutputStream
|
||||
* @author bpellin
|
||||
*/
|
||||
class LittleEndianDataOutputStream(private val baseStream: OutputStream) : OutputStream() {
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun writeUInt(uInt: UnsignedInt) {
|
||||
baseStream.write(uIntTo4Bytes(uInt))
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun close() {
|
||||
baseStream.close()
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun flush() {
|
||||
baseStream.flush()
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun write(buffer: ByteArray, offset: Int, count: Int) {
|
||||
baseStream.write(buffer, offset, count)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun write(buffer: ByteArray) {
|
||||
baseStream.write(buffer)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun write(oneByte: Int) {
|
||||
baseStream.write(oneByte)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun writeByte(byte: Byte) {
|
||||
baseStream.write(byte.toInt())
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun writeLong(value: Long) {
|
||||
baseStream.write(longTo8Bytes(value))
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun writeInt(value: Int) {
|
||||
baseStream.write(uIntTo4Bytes(UnsignedInt(value)))
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun writeUShort(value: Int) {
|
||||
baseStream.write(uShortTo2Bytes(value))
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import com.kunzisoft.keepass.utils.StringDatabaseKDBUtils.bytesToString
|
||||
import com.kunzisoft.keepass.utils.UnsignedInt
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
@@ -118,6 +119,22 @@ fun InputStream.readBytesLength(length: Int): ByteArray {
|
||||
return buf
|
||||
}
|
||||
|
||||
fun OutputStream.write4BytesUInt(value: UnsignedInt) {
|
||||
this.write(uIntTo4Bytes(value))
|
||||
}
|
||||
|
||||
fun OutputStream.writeByte(byte: Byte) {
|
||||
this.write(byte.toInt())
|
||||
}
|
||||
|
||||
fun OutputStream.write8BytesLong(value: Long) {
|
||||
this.write(longTo8Bytes(value))
|
||||
}
|
||||
|
||||
fun OutputStream.write2BytesUShort(value: Int) {
|
||||
this.write(uShortTo2Bytes(value))
|
||||
}
|
||||
|
||||
/**
|
||||
* Read an unsigned 16-bit value.
|
||||
*/
|
||||
|
||||
@@ -21,10 +21,7 @@ package com.kunzisoft.keepass.utils
|
||||
|
||||
import com.kunzisoft.keepass.crypto.keyDerivation.KdfParameters
|
||||
import com.kunzisoft.keepass.stream.*
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.*
|
||||
import java.nio.charset.Charset
|
||||
import java.util.*
|
||||
|
||||
@@ -123,8 +120,7 @@ open class VariantDictionary {
|
||||
@Throws(IOException::class)
|
||||
fun serialize(kdfParameters: KdfParameters): ByteArray {
|
||||
val byteArrayOutputStream = ByteArrayOutputStream()
|
||||
val outputStream = LittleEndianDataOutputStream(byteArrayOutputStream)
|
||||
serialize(kdfParameters, outputStream)
|
||||
serialize(kdfParameters, byteArrayOutputStream)
|
||||
return byteArrayOutputStream.toByteArray()
|
||||
}
|
||||
|
||||
@@ -182,48 +178,48 @@ open class VariantDictionary {
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun serialize(variantDictionary: VariantDictionary,
|
||||
outputStream: LittleEndianDataOutputStream?) {
|
||||
outputStream: OutputStream?) {
|
||||
if (outputStream == null) {
|
||||
return
|
||||
}
|
||||
outputStream.writeUShort(VdVersion)
|
||||
outputStream.write2BytesUShort(VdVersion)
|
||||
for ((name, vd) in variantDictionary.dict) {
|
||||
val nameBuf = name.toByteArray(UTF8Charset)
|
||||
outputStream.write(vd.type.toInt())
|
||||
outputStream.writeInt(nameBuf.size)
|
||||
outputStream.write4BytesUInt(UnsignedInt(nameBuf.size))
|
||||
outputStream.write(nameBuf)
|
||||
var buf: ByteArray
|
||||
when (vd.type) {
|
||||
VdType.UInt32 -> {
|
||||
outputStream.writeInt(4)
|
||||
outputStream.writeUInt((vd.value as UnsignedInt))
|
||||
outputStream.write4BytesUInt(UnsignedInt(4))
|
||||
outputStream.write4BytesUInt(vd.value as UnsignedInt)
|
||||
}
|
||||
VdType.UInt64 -> {
|
||||
outputStream.writeInt(8)
|
||||
outputStream.writeLong(vd.value as Long)
|
||||
outputStream.write4BytesUInt(UnsignedInt(8))
|
||||
outputStream.write8BytesLong(vd.value as Long)
|
||||
}
|
||||
VdType.Bool -> {
|
||||
outputStream.writeInt(1)
|
||||
outputStream.write4BytesUInt(UnsignedInt(1))
|
||||
val bool = if (vd.value as Boolean) 1.toByte() else 0.toByte()
|
||||
outputStream.write(bool.toInt())
|
||||
}
|
||||
VdType.Int32 -> {
|
||||
outputStream.writeInt(4)
|
||||
outputStream.writeInt(vd.value as Int)
|
||||
outputStream.write4BytesUInt(UnsignedInt(4))
|
||||
outputStream.write4BytesUInt(UnsignedInt(vd.value as Int))
|
||||
}
|
||||
VdType.Int64 -> {
|
||||
outputStream.writeInt(8)
|
||||
outputStream.writeLong(vd.value as Long)
|
||||
outputStream.write4BytesUInt(UnsignedInt(8))
|
||||
outputStream.write8BytesLong(vd.value as Long)
|
||||
}
|
||||
VdType.String -> {
|
||||
val value = vd.value as String
|
||||
buf = value.toByteArray(UTF8Charset)
|
||||
outputStream.writeInt(buf.size)
|
||||
outputStream.write4BytesUInt(UnsignedInt(buf.size))
|
||||
outputStream.write(buf)
|
||||
}
|
||||
VdType.ByteArray -> {
|
||||
buf = vd.value as ByteArray
|
||||
outputStream.writeInt(buf.size)
|
||||
outputStream.write4BytesUInt(UnsignedInt(buf.size))
|
||||
outputStream.write(buf)
|
||||
}
|
||||
else -> {
|
||||
|
||||
Reference in New Issue
Block a user