/* * 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 . * */ package com.kunzisoft.keepass.model import android.os.Parcel import android.os.ParcelUuid import android.os.Parcelable import com.kunzisoft.keepass.database.element.Attachment import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.DateInstant import com.kunzisoft.keepass.database.element.Field import com.kunzisoft.keepass.database.element.security.ProtectedString import com.kunzisoft.keepass.database.element.template.TemplateField import com.kunzisoft.keepass.otp.OtpElement import com.kunzisoft.keepass.otp.OtpEntryFields import com.kunzisoft.keepass.otp.OtpEntryFields.OTP_TOKEN_FIELD import java.util.* class EntryInfo : NodeInfo { var id: UUID = UUID.randomUUID() var username: String = "" var password: String = "" var url: String = "" var notes: String = "" var customFields: MutableList = mutableListOf() var attachments: MutableList = mutableListOf() var otpModel: OtpModel? = null var isTemplate: Boolean = false constructor() : super() constructor(parcel: Parcel) : super(parcel) { id = parcel.readParcelable(ParcelUuid::class.java.classLoader)?.uuid ?: id username = parcel.readString() ?: username password = parcel.readString() ?: password url = parcel.readString() ?: url notes = parcel.readString() ?: notes parcel.readList(customFields, Field::class.java.classLoader) parcel.readList(attachments, Attachment::class.java.classLoader) otpModel = parcel.readParcelable(OtpModel::class.java.classLoader) ?: otpModel isTemplate = parcel.readByte().toInt() != 0 } override fun describeContents(): Int { return 0 } override fun writeToParcel(parcel: Parcel, flags: Int) { super.writeToParcel(parcel, flags) parcel.writeParcelable(ParcelUuid(id), flags) parcel.writeString(username) parcel.writeString(password) parcel.writeString(url) parcel.writeString(notes) parcel.writeList(customFields) parcel.writeList(attachments) parcel.writeParcelable(otpModel, flags) parcel.writeByte((if (isTemplate) 1 else 0).toByte()) } fun containsCustomFieldsProtected(): Boolean { return customFields.any { it.protectedValue.isProtected } } fun containsCustomFieldsNotProtected(): Boolean { return customFields.any { !it.protectedValue.isProtected } } fun containsCustomField(label: String): Boolean { return customFields.lastOrNull { it.name == label } != null } fun getGeneratedFieldValue(label: String): String { if (label == OTP_TOKEN_FIELD) { otpModel?.let { return OtpElement(it).token } } return customFields.lastOrNull { it.name == label }?.protectedValue?.toString() ?: "" } private fun addUniqueField(field: Field, number: Int = 0) { var sameName = false var sameValue = false val suffix = if (number > 0) "_$number" else "" customFields.forEach { currentField -> // Not write the same data again if (currentField.protectedValue.stringValue == field.protectedValue.stringValue) { sameValue = true return } // Same name but new value, create a new suffix if (currentField.name == field.name + suffix) { sameName = true addUniqueField(field, number + 1) return } } if (!sameName && !sameValue) (customFields as ArrayList).add(Field(field.name + suffix, field.protectedValue)) } fun saveSearchInfo(database: Database?, searchInfo: SearchInfo) { searchInfo.otpString?.let { otpString -> // Replace the OTP field OtpEntryFields.parseOTPUri(otpString)?.let { otpElement -> if (title.isEmpty()) title = otpElement.issuer if (username.isEmpty()) username = otpElement.name // Add OTP field val mutableCustomFields = customFields as ArrayList val otpField = OtpEntryFields.buildOtpField(otpElement, null, null) if (mutableCustomFields.contains(otpField)) { mutableCustomFields.remove(otpField) } mutableCustomFields.add(otpField) } } ?: searchInfo.webDomain?.let { webDomain -> // If unable to save web domain in custom field or URL not populated, save in URL val scheme = searchInfo.webScheme val webScheme = if (scheme.isNullOrEmpty()) "http" else scheme val webDomainToStore = "$webScheme://$webDomain" if (database?.allowEntryCustomFields() != true || url.isEmpty()) { url = webDomainToStore } else if (url != webDomainToStore) { // Save web domain in custom field addUniqueField(Field(WEB_DOMAIN_FIELD_NAME, ProtectedString(false, webDomainToStore)), 1 // Start to one because URL is a standard field name ) } } ?: run { // Save application id in custom field if (database?.allowEntryCustomFields() == true) { searchInfo.applicationId?.let { applicationId -> addUniqueField(Field(APPLICATION_ID_FIELD_NAME, ProtectedString(false, applicationId)) ) } } } } fun saveRegisterInfo(database: Database?, registerInfo: RegisterInfo) { registerInfo.username?.let { username = it } registerInfo.password?.let { password = it } if (database?.allowEntryCustomFields() == true) { val creditCard: CreditCard? = registerInfo.creditCard creditCard?.cardholder?.let { addUniqueField(Field(TemplateField.LABEL_HOLDER, ProtectedString(false, it))) } creditCard?.expiration?.let { expires = true expiryTime = DateInstant(creditCard.expiration.millis) } creditCard?.number?.let { addUniqueField(Field(TemplateField.LABEL_NUMBER, ProtectedString(false, it))) } creditCard?.cvv?.let { addUniqueField(Field(TemplateField.LABEL_CVV, ProtectedString(true, it))) } } } override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is EntryInfo) return false if (!super.equals(other)) return false if (id != other.id) return false if (username != other.username) return false if (password != other.password) return false if (url != other.url) return false if (notes != other.notes) return false if (customFields != other.customFields) return false if (attachments != other.attachments) return false if (otpModel != other.otpModel) return false if (isTemplate != other.isTemplate) return false return true } override fun hashCode(): Int { var result = super.hashCode() result = 31 * result + id.hashCode() result = 31 * result + username.hashCode() result = 31 * result + password.hashCode() result = 31 * result + url.hashCode() result = 31 * result + notes.hashCode() result = 31 * result + customFields.hashCode() result = 31 * result + attachments.hashCode() result = 31 * result + (otpModel?.hashCode() ?: 0) result = 31 * result + isTemplate.hashCode() return result } companion object { const val WEB_DOMAIN_FIELD_NAME = "URL" const val APPLICATION_ID_FIELD_NAME = "AndroidApp" @JvmField val CREATOR: Parcelable.Creator = object : Parcelable.Creator { override fun createFromParcel(parcel: Parcel): EntryInfo { return EntryInfo(parcel) } override fun newArray(size: Int): Array { return arrayOfNulls(size) } } } }