1pif-to-kdbx/onepif/OnepifEntry.py

154 lines
5.5 KiB
Python

import sys
from . import OnepifEntryProperty as oep
TYPES = {
"112": "API Credential",
"wallet.financial.BankAccountUS": "Bank Account",
"wallet.financial.CreditCard": "Credit Card",
"wallet.computer.Database": "Database",
# Not exported: Document
"wallet.government.DriversLicense": "Driver License",
"wallet.onlineservices.Email.v2": "Email Account",
"identities.Identity": "Identity",
"webforms.WebForm": "Login",
"113": "Medical Record",
"wallet.membership.Membership": "Membership",
"wallet.government.HuntingLicense": "Outdoor License",
"wallet.government.Passport": "Passport",
"passwords.Password": "Password",
"wallet.membership.RewardProgram": "Reward Program",
"securenotes.SecureNote": "Secure Note",
"wallet.computer.UnixServer": "Server",
"wallet.government.SsnUS": "Social Security Number",
"wallet.computer.License": "Software License",
"wallet.computer.Router": "Wireless Router",
}
ANSI_RED = u"\u001b[1;31m"
ANSI_RESET = u"\u001b[0m"
class OnepifEntry():
def __init__(self, data):
self.raw = data
self.set_type(data["typeName"])
self.properties = []
self.parse()
def set_type(self, new_type: str):
if new_type not in TYPES:
raise Exception("Unknown record type: {}".format(new_type))
self.type = new_type
self.type_name = TYPES[new_type]
def get_tags(self) -> list:
if "openContents" not in self.raw:
return []
if "tags" not in self.raw["openContents"]:
return []
return self.raw["openContents"]["tags"]
def get_totps(self) -> list:
totp_fields = []
if "sections" in self.raw["secureContents"]:
for section in self.raw["secureContents"]["sections"]:
if "fields" not in section:
continue
for field in section["fields"]:
if field["n"][:5] == "TOTP_":
totp_fields.append([
field["v"],
field["t"], # Custom title, if set (isn't displayed in 1P)
])
if len(totp_fields) == 0:
return None
return totp_fields
def is_trash(self) -> bool:
if "trashed" in self.raw:
return self.raw["trashed"]
return False
def add_property(self, property: oep.OnepifEntryProperty):
self.properties.append(property)
def get_property_keys(self):
keys = []
for p in self.properties:
keys.append(p.name)
keys = list(set(keys))
return keys
def get_property(self, key: str):
props = []
for p in self.properties:
if p.name == key:
props.append(p)
if not props:
return None
elif len(props) > 1:
print("{}Warning: Multiple properties matching '{}' found: {}. Ignoring all but the first.{}".format(ANSI_RED, key, repr(props), ANSI_RESET))
return props[0]
def add_with_unique_key(self, prop_dict: dict, new_key: str, new_value):
suffix_ctr = 0
tmp_key = new_key
while tmp_key in prop_dict:
suffix_ctr += 1
tmp_key = "{}_{}".format(new_key, suffix_ctr)
if type(new_value) is str:
# For some reason 1P sometimes exports 0x10 character for empty strings
new_value = new_value.replace("\x10", "")
prop_dict[tmp_key] = new_value
def parse_section(self, section: dict):
sect_title = section["title"]
for f in section["fields"]:
if "v" not in f:
# Skip fields without data
continue
prop = oep.OnepifEntryProperty.from_sectionfield(f, sect_title)
self.add_property(prop)
def parse_fields(self, fields: list):
for f in fields:
prop = oep.OnepifEntryProperty.from_webfield(f)
if prop:
self.add_property(prop)
def add_simple_prop(self, key: str, value):
if value == "\x10":
# this seems to be an "empty" indicator, so skip this
return False
prop = oep.OnepifEntryProperty(key, value)
self.add_property(prop)
def parse(self):
for k, v in self.raw.items():
if k in ["openContents", "secureContents"]:
# handle open/secure groups of properties
for k2, v2 in v.items():
if k2 == "unknown_details":
# special handling aka. black magic
if "sections" in v2:
k2 = "sections"
v2 = v2["sections"]
if k2 == "sections":
# handle section
for s in v2:
if "fields" not in s:
# Skip empty sections
continue
self.parse_section(s)
continue
elif k2 == "fields":
# For some reason this differs from the "fields" in a section
self.parse_fields(v2)
continue
# Handle all other values (most probably string or int)
self.add_simple_prop(k2, v2)
continue
# Handle all other values
self.add_simple_prop(k, v)
# TODO: Maybe walk all keys and see if there's (xxx_dd), xxx_mm, xxx_yy and turn them into a date