bitwarden-estensione-browser/libs/common/src/importers/baseImporter.ts

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

467 lines
12 KiB
TypeScript
Raw Normal View History

import * as papa from "papaparse";
import { LogService } from "../abstractions/log.service";
2022-02-22 15:39:11 +01:00
import { CipherType } from "../enums/cipherType";
import { FieldType } from "../enums/fieldType";
import { SecureNoteType } from "../enums/secureNoteType";
import { Utils } from "../misc/utils";
2018-07-05 23:29:35 +02:00
import { ImportResult } from "../models/domain/importResult";
2018-07-10 23:51:47 +02:00
import { CipherView } from "../models/view/cipherView";
2018-07-05 23:29:35 +02:00
import { CollectionView } from "../models/view/collectionView";
2018-07-10 23:51:47 +02:00
import { FieldView } from "../models/view/fieldView";
2018-07-12 15:48:39 +02:00
import { FolderView } from "../models/view/folderView";
2022-02-22 15:39:11 +01:00
import { LoginUriView } from "../models/view/loginUriView";
2018-07-10 23:51:47 +02:00
import { LoginView } from "../models/view/loginView";
import { SecureNoteView } from "../models/view/secureNoteView";
import { ConsoleLogService } from "../services/consoleLog.service";
export abstract class BaseImporter {
organizationId: string = null;
2021-12-16 13:36:21 +01:00
protected logService: LogService = new ConsoleLogService(false);
2021-12-16 13:36:21 +01:00
protected newLineRegex = /(?:\r\n|\r|\n)/;
2021-12-16 13:36:21 +01:00
protected passwordFieldNames = [
"password",
"pass word",
"passphrase",
"pass phrase",
"pass",
"code",
"code word",
"codeword",
"secret",
"secret word",
"personpwd",
"key",
"keyword",
"key word",
"keyphrase",
"key phrase",
"form_pw",
"wppassword",
"pin",
"pwd",
"pw",
"pword",
"passwd",
"p",
"serial",
"serial#",
"license key",
"reg #",
2021-12-16 13:36:21 +01:00
// Non-English names
2018-06-28 04:09:39 +02:00
"passwort",
];
2021-12-16 13:36:21 +01:00
protected usernameFieldNames = [
"user",
"name",
"user name",
"username",
"login name",
"email",
"e-mail",
"id",
"userid",
"user id",
"login",
"form_loginname",
"wpname",
"mail",
"loginid",
"login id",
"log",
"personlogin",
"first name",
"last name",
"card#",
"account #",
"member",
"member #",
2021-12-16 13:36:21 +01:00
// Non-English names
2018-06-28 04:09:39 +02:00
"nom",
"benutzername",
];
2021-12-16 13:36:21 +01:00
protected notesFieldNames = [
2018-06-28 04:09:39 +02:00
"note",
"notes",
"comment",
"comments",
"memo",
"description",
"free form",
"freeform",
"free text",
2021-12-16 13:36:21 +01:00
"freetext",
"free",
// Non-English names
2018-06-28 04:09:39 +02:00
"kommentar",
];
2021-12-16 13:36:21 +01:00
protected uriFieldNames: string[] = [
"url",
"hyper link",
"hyperlink",
"link",
"host",
"hostname",
"host name",
2018-06-28 04:09:39 +02:00
"server",
"address",
2021-12-16 13:36:21 +01:00
"hyper ref",
"href",
2021-12-16 13:36:21 +01:00
"web",
"website",
"web site",
2021-12-16 13:36:21 +01:00
"site",
"web-site",
"uri",
// Non-English names
2021-12-16 13:36:21 +01:00
"ort",
"adresse",
];
protected parseCsvOptions = {
encoding: "UTF-8",
skipEmptyLines: false,
};
2021-12-16 13:36:21 +01:00
protected get organization() {
return this.organizationId != null;
}
2021-12-16 13:36:21 +01:00
protected parseXml(data: string): Document {
const parser = new DOMParser();
const doc = parser.parseFromString(data, "application/xml");
return doc != null && doc.querySelector("parsererror") == null ? doc : null;
}
2021-12-16 13:36:21 +01:00
protected parseCsv(data: string, header: boolean, options: any = {}): any[] {
2021-12-09 15:00:26 +01:00
const parseOptions: papa.ParseConfig<string> = Object.assign(
{ header: header },
this.parseCsvOptions,
options
);
2018-07-21 14:21:08 +02:00
data = this.splitNewLine(data).join("\n").trim();
const result = papa.parse(data, parseOptions);
if (result.errors != null && result.errors.length > 0) {
result.errors.forEach((e) => {
2018-10-11 22:49:11 +02:00
if (e.row != null) {
this.logService.warning("Error parsing row " + e.row + ": " + e.message);
2018-10-11 22:49:11 +02:00
}
});
}
2018-06-23 20:46:23 +02:00
return result.data && result.data.length > 0 ? result.data : null;
2021-12-16 13:36:21 +01:00
}
protected parseSingleRowCsv(rowData: string) {
if (this.isNullOrWhitespace(rowData)) {
return null;
}
const parsedRow = this.parseCsv(rowData, false);
if (parsedRow != null && parsedRow.length > 0 && parsedRow[0].length > 0) {
return parsedRow[0];
2021-12-16 13:36:21 +01:00
}
return null;
2021-12-16 13:36:21 +01:00
}
protected makeUriArray(uri: string | string[]): LoginUriView[] {
if (uri == null) {
2018-07-19 21:12:58 +02:00
return null;
}
if (typeof uri === "string") {
const loginUri = new LoginUriView();
loginUri.uri = this.fixUri(uri);
2018-07-19 21:12:58 +02:00
if (this.isNullOrWhitespace(loginUri.uri)) {
return null;
2021-12-16 13:36:21 +01:00
}
loginUri.match = null;
return [loginUri];
}
if (uri.length > 0) {
const returnArr: LoginUriView[] = [];
uri.forEach((u) => {
const loginUri = new LoginUriView();
loginUri.uri = this.fixUri(u);
2018-07-19 21:12:58 +02:00
if (this.isNullOrWhitespace(loginUri.uri)) {
2021-12-16 13:36:21 +01:00
return;
}
loginUri.match = null;
returnArr.push(loginUri);
});
return returnArr.length === 0 ? null : returnArr;
}
2018-06-25 21:19:51 +02:00
return null;
}
protected fixUri(uri: string) {
if (uri == null) {
2019-01-15 17:34:35 +01:00
return null;
}
uri = uri.trim();
if (uri.indexOf("://") === -1 && uri.indexOf(".") >= 0) {
uri = "http://" + uri;
}
if (uri.length > 1000) {
return uri.substring(0, 1000);
}
return uri;
2021-12-16 13:36:21 +01:00
}
protected nameFromUrl(url: string) {
const hostname = Utils.getHostname(url);
if (this.isNullOrWhitespace(hostname)) {
return null;
}
return hostname.startsWith("www.") ? hostname.replace("www.", "") : hostname;
}
protected isNullOrWhitespace(str: string): boolean {
return Utils.isNullOrWhitespace(str);
}
protected getValueOrDefault(str: string, defaultValue: string = null): string {
if (this.isNullOrWhitespace(str)) {
return defaultValue;
2021-12-16 13:36:21 +01:00
}
return str;
}
protected splitNewLine(str: string): string[] {
return str.split(this.newLineRegex);
}
// ref https://stackoverflow.com/a/5911300
protected getCardBrand(cardNum: string) {
if (this.isNullOrWhitespace(cardNum)) {
return null;
}
2021-12-16 13:36:21 +01:00
// Visa
let re = new RegExp("^4");
if (cardNum.match(re) != null) {
return "Visa";
}
2018-07-05 23:29:35 +02:00
// Mastercard
// Updated for Mastercard 2017 BINs expansion
if (
/^(5[1-5][0-9]{14}|2(22[1-9][0-9]{12}|2[3-9][0-9]{13}|[3-6][0-9]{14}|7[0-1][0-9]{13}|720[0-9]{12}))$/.test(
cardNum
2021-12-16 13:36:21 +01:00
)
) {
return "Mastercard";
}
2021-12-16 13:36:21 +01:00
// AMEX
re = new RegExp("^3[47]");
if (cardNum.match(re) != null) {
2018-07-05 23:29:35 +02:00
return "Amex";
}
// Discover
re = new RegExp(
"^(6011|622(12[6-9]|1[3-9][0-9]|[2-8][0-9]{2}|9[0-1][0-9]|92[0-5]|64[4-9])|65)"
2021-12-16 13:36:21 +01:00
);
if (cardNum.match(re) != null) {
return "Discover";
}
// Diners
re = new RegExp("^36");
if (cardNum.match(re) != null) {
return "Diners Club";
}
2018-07-10 23:51:47 +02:00
// Diners - Carte Blanche
re = new RegExp("^30[0-5]");
if (cardNum.match(re) != null) {
return "Diners Club";
}
2021-12-16 13:36:21 +01:00
// JCB
2018-07-10 23:51:47 +02:00
re = new RegExp("^35(2[89]|[3-8][0-9])");
if (cardNum.match(re) != null) {
return "JCB";
}
// Visa Electron
2018-07-11 23:43:26 +02:00
re = new RegExp("^(4026|417500|4508|4844|491(3|7))");
2018-07-10 23:51:47 +02:00
if (cardNum.match(re) != null) {
return "Visa";
}
2018-07-12 15:48:39 +02:00
return null;
2021-12-16 13:36:21 +01:00
}
2018-07-12 15:48:39 +02:00
protected setCardExpiration(cipher: CipherView, expiration: string): boolean {
if (!this.isNullOrWhitespace(expiration)) {
expiration = expiration.replace(/\s/g, "");
const parts = expiration.split("/");
if (parts.length === 2) {
let month: string = null;
let year: string = null;
if (parts[0].length === 1 || parts[0].length === 2) {
month = parts[0];
if (month.length === 2 && month[0] === "0") {
2018-07-12 15:48:39 +02:00
month = month.substr(1, 1);
2021-12-16 13:36:21 +01:00
}
}
if (parts[1].length === 2 || parts[1].length === 4) {
year = month.length === 2 ? "20" + parts[1] : parts[1];
2021-12-16 13:36:21 +01:00
}
if (month != null && year != null) {
cipher.card.expMonth = month;
cipher.card.expYear = year;
return true;
2021-12-16 13:36:21 +01:00
}
}
}
2018-07-12 15:48:39 +02:00
return false;
2021-12-16 13:36:21 +01:00
}
2018-07-12 15:48:39 +02:00
protected moveFoldersToCollections(result: ImportResult) {
result.folderRelationships.forEach((r) => result.collectionRelationships.push(r));
result.collections = result.folders.map((f) => {
const collection = new CollectionView();
2018-07-05 23:29:35 +02:00
collection.name = f.name;
2018-07-12 15:48:39 +02:00
return collection;
2021-12-16 13:36:21 +01:00
});
2018-07-12 15:48:39 +02:00
result.folderRelationships = [];
result.folders = [];
2021-12-16 13:36:21 +01:00
}
protected querySelectorDirectChild(parentEl: Element, query: string) {
const els = this.querySelectorAllDirectChild(parentEl, query);
return els.length === 0 ? null : els[0];
2021-12-16 13:36:21 +01:00
}
protected querySelectorAllDirectChild(parentEl: Element, query: string) {
return Array.from(parentEl.querySelectorAll(query)).filter((el) => el.parentNode === parentEl);
2021-12-16 13:36:21 +01:00
}
2018-07-10 23:51:47 +02:00
protected initLoginCipher() {
const cipher = new CipherView();
cipher.favorite = false;
cipher.notes = "";
cipher.fields = [];
cipher.login = new LoginView();
cipher.type = CipherType.Login;
return cipher;
2021-12-16 13:36:21 +01:00
}
2018-07-10 23:51:47 +02:00
protected cleanupCipher(cipher: CipherView) {
2018-07-12 15:48:39 +02:00
if (cipher == null) {
2021-12-16 13:36:21 +01:00
return;
}
2018-07-12 15:48:39 +02:00
if (cipher.type !== CipherType.Login) {
cipher.login = null;
2021-12-16 13:36:21 +01:00
}
2018-07-12 15:48:39 +02:00
if (this.isNullOrWhitespace(cipher.name)) {
cipher.name = "--";
2021-12-16 13:36:21 +01:00
}
2018-07-10 23:51:47 +02:00
if (this.isNullOrWhitespace(cipher.notes)) {
cipher.notes = null;
2021-12-16 13:36:21 +01:00
} else {
2018-07-10 23:51:47 +02:00
cipher.notes = cipher.notes.trim();
2021-12-16 13:36:21 +01:00
}
2018-07-12 15:48:39 +02:00
if (cipher.fields != null && cipher.fields.length === 0) {
2018-07-10 23:51:47 +02:00
cipher.fields = null;
2021-12-16 13:36:21 +01:00
}
}
protected processKvp(
cipher: CipherView,
key: string,
value: string,
2018-07-12 15:48:39 +02:00
type: FieldType = FieldType.Text
2021-12-16 13:36:21 +01:00
) {
2018-07-10 23:51:47 +02:00
if (this.isNullOrWhitespace(value)) {
2018-07-12 15:48:39 +02:00
return;
2021-12-16 13:36:21 +01:00
}
2018-07-10 23:51:47 +02:00
if (this.isNullOrWhitespace(key)) {
2021-12-16 13:36:21 +01:00
key = "";
}
2018-07-11 23:43:26 +02:00
if (value.length > 200 || value.trim().search(this.newLineRegex) > -1) {
2018-07-10 23:51:47 +02:00
if (cipher.notes == null) {
cipher.notes = "";
2021-12-16 13:36:21 +01:00
}
2018-07-12 22:27:24 +02:00
cipher.notes += key + ": " + this.splitNewLine(value).join("\n") + "\n";
2021-12-16 13:36:21 +01:00
} else {
2018-07-10 23:51:47 +02:00
if (cipher.fields == null) {
cipher.fields = [];
2021-12-16 13:36:21 +01:00
}
2018-07-10 23:51:47 +02:00
const field = new FieldView();
field.type = type;
2018-07-10 23:51:47 +02:00
field.name = key;
field.value = value;
cipher.fields.push(field);
2021-12-16 13:36:21 +01:00
}
}
2018-07-12 15:48:39 +02:00
protected processFolder(result: ImportResult, folderName: string) {
let folderIndex = result.folders.length;
const hasFolder = !this.isNullOrWhitespace(folderName);
let addFolder = hasFolder;
if (hasFolder) {
for (let i = 0; i < result.folders.length; i++) {
if (result.folders[i].name === folderName) {
addFolder = false;
folderIndex = i;
break;
}
2021-12-16 13:36:21 +01:00
}
2018-07-12 15:48:39 +02:00
}
2018-07-12 15:48:39 +02:00
if (addFolder) {
const f = new FolderView();
f.name = folderName;
result.folders.push(f);
2021-12-16 13:36:21 +01:00
}
2018-07-12 15:48:39 +02:00
if (hasFolder) {
result.folderRelationships.push([result.ciphers.length, folderIndex]);
2021-12-16 13:36:21 +01:00
}
}
protected convertToNoteIfNeeded(cipher: CipherView) {
if (
cipher.type === CipherType.Login &&
this.isNullOrWhitespace(cipher.login.username) &&
this.isNullOrWhitespace(cipher.login.password) &&
(cipher.login.uris == null || cipher.login.uris.length === 0)
) {
cipher.type = CipherType.SecureNote;
cipher.secureNote = new SecureNoteView();
cipher.secureNote.type = SecureNoteType.Generic;
}
2021-12-16 13:36:21 +01:00
}
protected processFullName(cipher: CipherView, fullName: string) {
if (this.isNullOrWhitespace(fullName)) {
return;
}
const nameParts = fullName.split(" ");
if (nameParts.length > 0) {
cipher.identity.firstName = this.getValueOrDefault(nameParts[0]);
}
if (nameParts.length === 2) {
cipher.identity.lastName = this.getValueOrDefault(nameParts[1]);
} else if (nameParts.length >= 3) {
cipher.identity.middleName = this.getValueOrDefault(nameParts[1]);
cipher.identity.lastName = nameParts.slice(2, nameParts.length).join(" ");
}
}
}