mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
356 lines
10 KiB
Kotlin
356 lines
10 KiB
Kotlin
/*
|
|
* Copyright 2019 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.utils
|
|
|
|
import com.kunzisoft.keepass.database.element.DateInstant
|
|
import org.joda.time.DateTime
|
|
import org.joda.time.Instant
|
|
import java.io.IOException
|
|
import java.io.InputStream
|
|
import java.io.OutputStream
|
|
import java.nio.charset.Charset
|
|
import java.util.*
|
|
|
|
/**
|
|
* Read all data of stream and invoke [readBytes] each time the buffer is full or no more data to read.
|
|
*/
|
|
@Throws(IOException::class)
|
|
fun InputStream.readAllBytes(bufferSize: Int = DEFAULT_BUFFER_SIZE,
|
|
cancelCondition: ()-> Boolean = { false },
|
|
readBytes: (bytesRead: ByteArray) -> Unit) {
|
|
val buffer = ByteArray(bufferSize)
|
|
var read = 0
|
|
while (read != -1 && !cancelCondition()) {
|
|
read = this.read(buffer, 0, buffer.size)
|
|
if (read != -1) {
|
|
val optimizedBuffer: ByteArray = if (buffer.size == read) {
|
|
buffer
|
|
} else {
|
|
buffer.copyOf(read)
|
|
}
|
|
readBytes.invoke(optimizedBuffer)
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Read number of bytes defined by [length] and invoke [readBytes] each time the buffer is full or no more data to read.
|
|
*/
|
|
@Throws(IOException::class)
|
|
fun InputStream.readBytes(length: Int, bufferSize: Int = DEFAULT_BUFFER_SIZE,
|
|
readBytes: (bytesRead: ByteArray) -> Unit) {
|
|
var bufferLength = bufferSize
|
|
var buffer = ByteArray(bufferLength)
|
|
|
|
var offset = 0
|
|
var read = 0
|
|
while (offset < length && read != -1) {
|
|
|
|
// To reduce the buffer for the last bytes reads
|
|
if (length - offset < bufferLength) {
|
|
bufferLength = length - offset
|
|
buffer = ByteArray(bufferLength)
|
|
}
|
|
read = this.read(buffer, 0, bufferLength)
|
|
|
|
// To get only the bytes read
|
|
val optimizedBuffer: ByteArray = if (read >= 0 && buffer.size > read) {
|
|
buffer.copyOf(read)
|
|
} else {
|
|
buffer
|
|
}
|
|
readBytes.invoke(optimizedBuffer)
|
|
offset += read
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Read a 32-bit value and return it as a long, so that it can
|
|
* be interpreted as an unsigned integer.
|
|
*/
|
|
@Throws(IOException::class)
|
|
fun InputStream.readBytes4ToUInt(): UnsignedInt {
|
|
return bytes4ToUInt(readBytesLength(4))
|
|
}
|
|
|
|
@Throws(IOException::class)
|
|
fun InputStream.readBytes2ToUShort(): Int {
|
|
return bytes2ToUShort(readBytesLength(2))
|
|
}
|
|
|
|
@Throws(IOException::class)
|
|
fun InputStream.readBytes5ToDate(): DateInstant {
|
|
return bytes5ToDate(readBytesLength(5))
|
|
}
|
|
|
|
@Throws(IOException::class)
|
|
fun InputStream.readBytes16ToUuid(): UUID {
|
|
return bytes16ToUuid(readBytesLength(16))
|
|
}
|
|
|
|
@Throws(IOException::class)
|
|
fun InputStream.readBytesToString(length: Int, replaceCRLF: Boolean = true): String {
|
|
return bytesToString(this.readBytesLength(length), replaceCRLF)
|
|
}
|
|
|
|
@Throws(IOException::class)
|
|
fun InputStream.readBytesLength(length: Int): ByteArray {
|
|
val buf = ByteArray(length)
|
|
// WARNING this.read(buf, 0, length) Doesn't work
|
|
for (i in 0 until length) {
|
|
buf[i] = this.read().toByte()
|
|
}
|
|
return buf
|
|
}
|
|
|
|
@Throws(IOException::class)
|
|
fun OutputStream.write4BytesUInt(value: UnsignedInt) {
|
|
this.write(uIntTo4Bytes(value))
|
|
}
|
|
|
|
@Throws(IOException::class)
|
|
fun OutputStream.writeBooleanByte(value: Boolean) {
|
|
this.writeByte(if (value) 1.toByte() else 0.toByte())
|
|
}
|
|
|
|
@Throws(IOException::class)
|
|
fun OutputStream.writeByte(byte: Byte) {
|
|
this.write(byte.toInt())
|
|
}
|
|
|
|
@Throws(IOException::class)
|
|
fun OutputStream.write8BytesLong(value: Long) {
|
|
this.write(longTo8Bytes(value))
|
|
}
|
|
|
|
@Throws(IOException::class)
|
|
fun OutputStream.write8BytesLong(value: UnsignedLong) {
|
|
this.write(uLongTo8Bytes(value))
|
|
}
|
|
|
|
@Throws(IOException::class)
|
|
fun OutputStream.write2BytesUShort(value: Int) {
|
|
this.write(uShortTo2Bytes(value))
|
|
}
|
|
|
|
/**
|
|
* Read an unsigned 16-bit value.
|
|
*/
|
|
fun bytes2ToUShort(buf: ByteArray): Int {
|
|
return ((buf[0].toInt() and 0xFF)
|
|
+ (buf[1].toInt() and 0xFF shl 8))
|
|
}
|
|
|
|
/**
|
|
* Read a 64 bit to unsigned long
|
|
*/
|
|
fun bytes64ToULong(buf: ByteArray): UnsignedLong {
|
|
return UnsignedLong((buf[0].toLong() and 0xFF)
|
|
+ (buf[1].toLong() and 0xFF shl 8)
|
|
+ (buf[2].toLong() and 0xFF shl 16)
|
|
+ (buf[3].toLong() and 0xFF shl 24)
|
|
+ (buf[4].toLong() and 0xFF shl 32)
|
|
+ (buf[5].toLong() and 0xFF shl 40)
|
|
+ (buf[6].toLong() and 0xFF shl 48)
|
|
+ (buf[7].toLong() and 0xFF shl 56))
|
|
}
|
|
|
|
/**
|
|
* Read a 64 bit long
|
|
*/
|
|
fun bytes64ToLong(buf: ByteArray): Long {
|
|
return ((buf[0].toLong() and 0xFF)
|
|
+ (buf[1].toLong() and 0xFF shl 8)
|
|
+ (buf[2].toLong() and 0xFF shl 16)
|
|
+ (buf[3].toLong() and 0xFF shl 24)
|
|
+ (buf[4].toLong() and 0xFF shl 32)
|
|
+ (buf[5].toLong() and 0xFF shl 40)
|
|
+ (buf[6].toLong() and 0xFF shl 48)
|
|
+ (buf[7].toLong() and 0xFF shl 56))
|
|
}
|
|
|
|
/**
|
|
* Read a 32-bit value.
|
|
*/
|
|
fun bytes4ToUInt(buf: ByteArray): UnsignedInt {
|
|
return UnsignedInt((buf[0].toInt() and 0xFF)
|
|
+ (buf[1].toInt() and 0xFF shl 8)
|
|
+ (buf[2].toInt() and 0xFF shl 16)
|
|
+ (buf[3].toInt() and 0xFF shl 24))
|
|
}
|
|
|
|
fun bytes16ToUuid(buf: ByteArray): UUID {
|
|
var lsb: Long = 0
|
|
for (i in 15 downTo 8) {
|
|
lsb = lsb shl 8 or (buf[i].toLong() and 0xff)
|
|
}
|
|
|
|
var msb: Long = 0
|
|
for (i in 7 downTo 0) {
|
|
msb = msb shl 8 or (buf[i].toLong() and 0xff)
|
|
}
|
|
|
|
return UUID(msb, lsb)
|
|
}
|
|
|
|
/**
|
|
* Unpack date from 5 byte format. The five bytes at 'offset' are unpacked
|
|
* to a java.util.Date instance.
|
|
*/
|
|
fun bytes5ToDate(buf: ByteArray): DateInstant {
|
|
val dateSize = 5
|
|
val cDate = ByteArray(dateSize)
|
|
System.arraycopy(buf, 0, cDate, 0, dateSize)
|
|
|
|
val readOffset = 0
|
|
val dw1 = cDate[readOffset].toInt() and 0xFF
|
|
val dw2 = cDate[readOffset + 1].toInt() and 0xFF
|
|
val dw3 = cDate[readOffset + 2].toInt() and 0xFF
|
|
val dw4 = cDate[readOffset + 3].toInt() and 0xFF
|
|
val dw5 = cDate[readOffset + 4].toInt() and 0xFF
|
|
|
|
// Unpack 5 byte structure to date and time
|
|
val year = dw1 shl 6 or (dw2 shr 2)
|
|
val month = dw2 and 0x00000003 shl 2 or (dw3 shr 6)
|
|
|
|
val day = dw3 shr 1 and 0x0000001F
|
|
val hour = dw3 and 0x00000001 shl 4 or (dw4 shr 4)
|
|
val minute = dw4 and 0x0000000F shl 2 or (dw5 shr 6)
|
|
val second = dw5 and 0x0000003F
|
|
|
|
return DateInstant(Instant.ofEpochMilli(DateTime(
|
|
year,
|
|
month,
|
|
day,
|
|
hour,
|
|
minute,
|
|
second
|
|
).millis))
|
|
}
|
|
|
|
|
|
/**
|
|
* Write an unsigned 16-bit value
|
|
*/
|
|
fun uShortTo2Bytes(value: Int): ByteArray {
|
|
val buf = ByteArray(2)
|
|
buf[0] = (value and 0x00FF).toByte()
|
|
buf[1] = (value and 0xFF00 shr 8).toByte()
|
|
return buf
|
|
}
|
|
|
|
/**
|
|
* Write a 32-bit Int value.
|
|
*/
|
|
fun uIntTo4Bytes(value: UnsignedInt): ByteArray {
|
|
val buf = ByteArray(4)
|
|
for (i in 0 until 4) {
|
|
buf[i] = (value.toKotlinInt().ushr(8 * i) and 0xFF).toByte()
|
|
}
|
|
return buf
|
|
}
|
|
|
|
fun uLongTo8Bytes(value: UnsignedLong): ByteArray {
|
|
return longTo8Bytes(value.toKotlinLong())
|
|
}
|
|
|
|
fun longTo8Bytes(value: Long): ByteArray {
|
|
val buf = ByteArray(8)
|
|
for (i in 0 until 8) {
|
|
buf[i] = (value.ushr(8 * i) and 0xFF).toByte()
|
|
}
|
|
return buf
|
|
}
|
|
|
|
fun uuidTo16Bytes(uuid: UUID): ByteArray {
|
|
val buf = ByteArray(16)
|
|
for (i in 0 until 8) {
|
|
buf[i] = (uuid.mostSignificantBits.ushr(8 * i) and 0xFF).toByte()
|
|
}
|
|
for (i in 8 until 16) {
|
|
buf[i] = (uuid.leastSignificantBits.ushr(8 * i) and 0xFF).toByte()
|
|
}
|
|
return buf
|
|
}
|
|
|
|
fun dateTo5Bytes(dateInstant: DateInstant): ByteArray {
|
|
val year = dateInstant.getYear()
|
|
val month = dateInstant.getMonth()
|
|
val day = dateInstant.getDay()
|
|
val hour = dateInstant.getHour()
|
|
val minute = dateInstant.getMinute()
|
|
val second = dateInstant.getSecond()
|
|
|
|
val buf = ByteArray(5)
|
|
buf[0] = UnsignedInt(year shr 6 and 0x0000003F).toKotlinByte()
|
|
buf[1] = UnsignedInt(year and 0x0000003F shl 2 or (month shr 2 and 0x00000003)).toKotlinByte()
|
|
buf[2] = (month and 0x00000003 shl 6
|
|
or (day and 0x0000001F shl 1) or (hour shr 4 and 0x00000001)).toByte()
|
|
buf[3] = (hour and 0x0000000F shl 4 or (minute shr 2 and 0x0000000F)).toByte()
|
|
buf[4] = (minute and 0x00000003 shl 6 or (second and 0x0000003F)).toByte()
|
|
|
|
return buf
|
|
}
|
|
|
|
private val defaultCharset = Charset.forName("UTF-8")
|
|
|
|
private val CRLFbuf = byteArrayOf(0x0D, 0x0A)
|
|
private val CRLF = String(CRLFbuf)
|
|
private val SEP = System.getProperty("line.separator")
|
|
private val REPLACE = SEP != CRLF
|
|
|
|
fun bytesToString(buf: ByteArray, replaceCRLF: Boolean = true): String {
|
|
// length of null-terminated string (i.e. distance to null) within a byte buffer.
|
|
var len = 0
|
|
while (buf[len].toInt() != 0) {
|
|
len++
|
|
}
|
|
// Get string
|
|
var jstring = String(buf, 0, len, defaultCharset)
|
|
if (replaceCRLF && REPLACE) {
|
|
jstring = jstring.replace(CRLF, SEP!!)
|
|
}
|
|
return jstring
|
|
}
|
|
|
|
@Throws(IOException::class)
|
|
fun writeStringToStream(outputStream: OutputStream, string: String?): Int {
|
|
var str = string
|
|
if (str == null) {
|
|
// Write out a null character
|
|
outputStream.write(uIntTo4Bytes(UnsignedInt(1)))
|
|
outputStream.write(0x00)
|
|
return 0
|
|
}
|
|
|
|
if (REPLACE) {
|
|
str = str.replace(SEP!!, CRLF)
|
|
}
|
|
|
|
val initial = str.toByteArray(defaultCharset)
|
|
|
|
val length = initial.size + 1
|
|
outputStream.write(uIntTo4Bytes(UnsignedInt(length)))
|
|
outputStream.write(initial)
|
|
outputStream.write(0x00)
|
|
|
|
return length
|
|
}
|