mirror of
https://github.com/laurent22/joplin.git
synced 2025-05-31 22:29:43 +02:00
149 lines
5.5 KiB
JavaScript
149 lines
5.5 KiB
JavaScript
const BaseModel = require("lib/BaseModel.js");
|
|
const BaseItem = require("lib/models/BaseItem.js");
|
|
const Setting = require("lib/models/Setting.js");
|
|
const ArrayUtils = require("lib/ArrayUtils.js");
|
|
const pathUtils = require("lib/path-utils.js");
|
|
const { mime } = require("lib/mime-utils.js");
|
|
const { filename } = require("lib/path-utils.js");
|
|
const { FsDriverDummy } = require("lib/fs-driver-dummy.js");
|
|
const { markdownUtils } = require("lib/markdown-utils.js");
|
|
const JoplinError = require("lib/JoplinError");
|
|
|
|
class Resource extends BaseItem {
|
|
static tableName() {
|
|
return "resources";
|
|
}
|
|
|
|
static modelType() {
|
|
return BaseModel.TYPE_RESOURCE;
|
|
}
|
|
|
|
static encryptionService() {
|
|
if (!this.encryptionService_) throw new Error("Resource.encryptionService_ is not set!!");
|
|
return this.encryptionService_;
|
|
}
|
|
|
|
static isSupportedImageMimeType(type) {
|
|
const imageMimeTypes = ["image/jpg", "image/jpeg", "image/png", "image/gif"];
|
|
return imageMimeTypes.indexOf(type.toLowerCase()) >= 0;
|
|
}
|
|
|
|
static fsDriver() {
|
|
if (!Resource.fsDriver_) Resource.fsDriver_ = new FsDriverDummy();
|
|
return Resource.fsDriver_;
|
|
}
|
|
|
|
static async serialize(item, type = null, shownKeys = null) {
|
|
let fieldNames = this.fieldNames();
|
|
fieldNames.push("type_");
|
|
//fieldNames = ArrayUtils.removeElement(fieldNames, 'encryption_blob_encrypted');
|
|
return super.serialize(item, "resource", fieldNames);
|
|
}
|
|
|
|
static filename(resource, encryptedBlob = false) {
|
|
let extension = encryptedBlob ? "crypted" : resource.file_extension;
|
|
if (!extension) extension = resource.mime ? mime.toFileExtension(resource.mime) : "";
|
|
extension = extension ? "." + extension : "";
|
|
return resource.id + extension;
|
|
}
|
|
|
|
static fullPath(resource, encryptedBlob = false) {
|
|
return Setting.value("resourceDir") + "/" + this.filename(resource, encryptedBlob);
|
|
}
|
|
|
|
// For resources, we need to decrypt the item (metadata) and the resource binary blob.
|
|
static async decrypt(item) {
|
|
// The item might already be decrypted but not the blob (for instance if it crashes while
|
|
// decrypting the blob or was otherwise interrupted).
|
|
const decryptedItem = item.encryption_cipher_text ? await super.decrypt(item) : Object.assign({}, item);
|
|
if (!decryptedItem.encryption_blob_encrypted) return decryptedItem;
|
|
|
|
const plainTextPath = this.fullPath(decryptedItem);
|
|
const encryptedPath = this.fullPath(decryptedItem, true);
|
|
const noExtPath = pathUtils.dirname(encryptedPath) + "/" + pathUtils.filename(encryptedPath);
|
|
|
|
// When the resource blob is downloaded by the synchroniser, it's initially a file with no
|
|
// extension (since it's encrypted, so we don't know its extension). So here rename it
|
|
// to a file with a ".crypted" extension so that it's better identified, and then decrypt it.
|
|
// Potentially plainTextPath is also a path with no extension if it's an unknown mime type.
|
|
if (await this.fsDriver().exists(noExtPath)) {
|
|
await this.fsDriver().move(noExtPath, encryptedPath);
|
|
}
|
|
|
|
await this.encryptionService().decryptFile(encryptedPath, plainTextPath);
|
|
|
|
decryptedItem.encryption_blob_encrypted = 0;
|
|
return super.save(decryptedItem, { autoTimestamp: false });
|
|
}
|
|
|
|
// Prepare the resource by encrypting it if needed.
|
|
// The call returns the path to the physical file AND a representation of the resource object
|
|
// as it should be uploaded to the sync target. Note that this may be different from what is stored
|
|
// in the database. In particular, the flag encryption_blob_encrypted might be 1 on the sync target
|
|
// if the resource is encrypted, but will be 0 locally because the device has the decrypted resource.
|
|
static async fullPathForSyncUpload(resource) {
|
|
const plainTextPath = this.fullPath(resource);
|
|
|
|
if (!Setting.value("encryption.enabled")) {
|
|
// Normally not possible since itemsThatNeedSync should only return decrypted items
|
|
if (!!resource.encryption_blob_encrypted) throw new Error("Trying to access encrypted resource but encryption is currently disabled");
|
|
return { path: plainTextPath, resource: resource };
|
|
}
|
|
|
|
const encryptedPath = this.fullPath(resource, true);
|
|
if (resource.encryption_blob_encrypted) return { path: encryptedPath, resource: resource };
|
|
|
|
try {
|
|
await this.encryptionService().encryptFile(plainTextPath, encryptedPath);
|
|
} catch (error) {
|
|
if (error.code === "ENOENT") throw new JoplinError("File not found:" + error.toString(), "fileNotFound");
|
|
throw error;
|
|
}
|
|
|
|
const resourceCopy = Object.assign({}, resource);
|
|
resourceCopy.encryption_blob_encrypted = 1;
|
|
return { path: encryptedPath, resource: resourceCopy };
|
|
}
|
|
|
|
static markdownTag(resource) {
|
|
let tagAlt = resource.alt ? resource.alt : resource.title;
|
|
if (!tagAlt) tagAlt = "";
|
|
let lines = [];
|
|
if (Resource.isSupportedImageMimeType(resource.mime)) {
|
|
lines.push("");
|
|
} else {
|
|
lines.push("[");
|
|
lines.push(markdownUtils.escapeLinkText(tagAlt));
|
|
lines.push("](:/" + resource.id + ")");
|
|
}
|
|
return lines.join("");
|
|
}
|
|
|
|
static pathToId(path) {
|
|
return filename(path);
|
|
}
|
|
|
|
static async content(resource) {
|
|
return this.fsDriver().readFile(this.fullPath(resource), "Buffer");
|
|
}
|
|
|
|
static setContent(resource, content) {
|
|
return this.fsDriver().writeBinaryFile(this.fullPath(resource), content);
|
|
}
|
|
|
|
static isResourceUrl(url) {
|
|
return url && url.length === 34 && url[0] === ":" && url[1] === "/";
|
|
}
|
|
|
|
static urlToId(url) {
|
|
if (!this.isResourceUrl(url)) throw new Error("Not a valid resource URL: " + url);
|
|
return url.substr(2);
|
|
}
|
|
}
|
|
|
|
Resource.IMAGE_MAX_DIMENSION = 1920;
|
|
|
|
module.exports = Resource;
|