Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be2b168c6c | ||
|
|
6b296d6110 | ||
|
|
6757348ac9 | ||
|
|
a7e6bed071 | ||
|
|
8b8c5bdb98 | ||
|
|
5b77b23e9f | ||
|
|
05313f9530 | ||
|
|
ce4e55fcc7 | ||
|
|
b26d6a7ffb |
@@ -38,3 +38,4 @@ jobs:
|
|||||||
path: |
|
path: |
|
||||||
main.js
|
main.js
|
||||||
manifest.json
|
manifest.json
|
||||||
|
styles.css
|
||||||
|
|||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"id": "obsdian-save-remote",
|
"id": "obsidian-save-remote",
|
||||||
"name": "Save remote",
|
"name": "Save remote",
|
||||||
"version": "0.0.12",
|
"version": "0.0.14",
|
||||||
"minAppVersion": "0.12.15",
|
"minAppVersion": "0.12.15",
|
||||||
"description": "This is yet another plugin allowing users to sync notes between local device and the cloud.",
|
"description": "This is yet another plugin allowing users to sync notes between local device and the cloud.",
|
||||||
"author": "fyears",
|
"author": "fyears",
|
||||||
|
|||||||
+4
-2
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "obsidian-save-remote",
|
"name": "obsidian-save-remote",
|
||||||
"version": "0.0.12",
|
"version": "0.0.14",
|
||||||
"description": "This is yet another sync plugin for Obsidian app.",
|
"description": "This is yet another sync plugin for Obsidian app.",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "webpack --mode development --watch",
|
"dev": "webpack --mode development --watch",
|
||||||
@@ -35,6 +35,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "^3.37.0",
|
"@aws-sdk/client-s3": "^3.37.0",
|
||||||
|
"@aws-sdk/lib-storage": "^3.40.1",
|
||||||
"@aws-sdk/signature-v4-crt": "^3.37.0",
|
"@aws-sdk/signature-v4-crt": "^3.37.0",
|
||||||
"acorn": "^8.5.0",
|
"acorn": "^8.5.0",
|
||||||
"aws-crt": "^1.10.1",
|
"aws-crt": "^1.10.1",
|
||||||
@@ -49,6 +50,7 @@
|
|||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"stream-browserify": "^3.0.0",
|
"stream-browserify": "^3.0.0",
|
||||||
"webdav": "^4.7.0",
|
"webdav": "^4.7.0",
|
||||||
"webdav-fs": "^4.0.0"
|
"webdav-fs": "^4.0.0",
|
||||||
|
"xregexp": "^5.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,6 +105,7 @@ export default class SaveRemotePlugin extends Plugin {
|
|||||||
this.settings.password
|
this.settings.password
|
||||||
);
|
);
|
||||||
if (!passwordCheckResult.ok) {
|
if (!passwordCheckResult.ok) {
|
||||||
|
new Notice("something goes wrong while checking password");
|
||||||
throw Error(passwordCheckResult.reason);
|
throw Error(passwordCheckResult.reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+19
@@ -2,6 +2,7 @@ import { Vault } from "obsidian";
|
|||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
|
|
||||||
import { base32 } from "rfc4648";
|
import { base32 } from "rfc4648";
|
||||||
|
import XRegExp from "xregexp";
|
||||||
|
|
||||||
export type SUPPORTED_SERVICES_TYPE = "s3" | "webdav" | "ftp";
|
export type SUPPORTED_SERVICES_TYPE = "s3" | "webdav" | "ftp";
|
||||||
|
|
||||||
@@ -111,3 +112,21 @@ export const hexStringToTypedArray = (hex: string) => {
|
|||||||
export const base64ToBase32 = (a: string) => {
|
export const base64ToBase32 = (a: string) => {
|
||||||
return base32.stringify(Buffer.from(a, "base64"));
|
return base32.stringify(Buffer.from(a, "base64"));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* iOS Safari could decrypt string with invalid password!
|
||||||
|
* So we need an extra way to test the decrypted result.
|
||||||
|
* One simple way is testing the result are "valid", printable chars or not.
|
||||||
|
*
|
||||||
|
* https://stackoverflow.com/questions/6198986
|
||||||
|
* https://www.regular-expressions.info/unicode.html
|
||||||
|
* Manual test shows that emojis like '🍎' match '\\p{Cs}',
|
||||||
|
* so we need to write the regrex in a form that \p{C} minus \p{Cs}
|
||||||
|
* @param a
|
||||||
|
*/
|
||||||
|
export const isVaildText = (a: string) => {
|
||||||
|
// If the regex matches, the string is invalid.
|
||||||
|
return !XRegExp("\\p{Cc}|\\p{Cf}|\\p{Co}|\\p{Cn}|\\p{Zl}|\\p{Zp}", "A").test(
|
||||||
|
a
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { Readable } from "stream";
|
|||||||
|
|
||||||
import { Vault } from "obsidian";
|
import { Vault } from "obsidian";
|
||||||
|
|
||||||
|
import { Upload } from "@aws-sdk/lib-storage";
|
||||||
import {
|
import {
|
||||||
S3Client,
|
S3Client,
|
||||||
ListObjectsV2Command,
|
ListObjectsV2Command,
|
||||||
@@ -115,14 +116,24 @@ export const uploadToRemote = async (
|
|||||||
remoteContent = await encryptArrayBuffer(localContent, password);
|
remoteContent = await encryptArrayBuffer(localContent, password);
|
||||||
}
|
}
|
||||||
const body = arrayBufferToBuffer(remoteContent);
|
const body = arrayBufferToBuffer(remoteContent);
|
||||||
await s3Client.send(
|
|
||||||
new PutObjectCommand({
|
const upload = new Upload({
|
||||||
|
client: s3Client,
|
||||||
|
queueSize: 20, // concurrency
|
||||||
|
partSize: 5242880, // minimal 5MB by default
|
||||||
|
leavePartsOnError: false,
|
||||||
|
params: {
|
||||||
Bucket: s3Config.s3BucketName,
|
Bucket: s3Config.s3BucketName,
|
||||||
Key: uploadFile,
|
Key: uploadFile,
|
||||||
Body: body,
|
Body: body,
|
||||||
ContentType: contentType,
|
ContentType: contentType,
|
||||||
})
|
},
|
||||||
);
|
});
|
||||||
|
upload.on("httpUploadProgress", (progress) => {
|
||||||
|
// console.log(progress);
|
||||||
|
});
|
||||||
|
await upload.done();
|
||||||
|
|
||||||
return await getRemoteMeta(s3Client, s3Config, uploadFile);
|
return await getRemoteMeta(s3Client, s3Config, uploadFile);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
+21
-5
@@ -16,7 +16,12 @@ import {
|
|||||||
deleteFromRemote,
|
deleteFromRemote,
|
||||||
downloadFromRemote,
|
downloadFromRemote,
|
||||||
} from "./s3";
|
} from "./s3";
|
||||||
import { mkdirpInVault, SUPPORTED_SERVICES_TYPE, isHiddenPath } from "./misc";
|
import {
|
||||||
|
mkdirpInVault,
|
||||||
|
SUPPORTED_SERVICES_TYPE,
|
||||||
|
isHiddenPath,
|
||||||
|
isVaildText,
|
||||||
|
} from "./misc";
|
||||||
import {
|
import {
|
||||||
decryptBase32ToString,
|
decryptBase32ToString,
|
||||||
encryptStringToBase32,
|
encryptStringToBase32,
|
||||||
@@ -74,6 +79,7 @@ export interface PasswordCheckType {
|
|||||||
| "remote_encrypted_local_no_password"
|
| "remote_encrypted_local_no_password"
|
||||||
| "password_matched"
|
| "password_matched"
|
||||||
| "password_not_matched"
|
| "password_not_matched"
|
||||||
|
| "invalid_text_after_decryption"
|
||||||
| "remote_not_encrypted_local_has_password"
|
| "remote_not_encrypted_local_has_password"
|
||||||
| "no_password_both_sides";
|
| "no_password_both_sides";
|
||||||
}
|
}
|
||||||
@@ -101,10 +107,20 @@ export const isPasswordOk = async (
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const res = await decryptBase32ToString(santyCheckKey, password);
|
const res = await decryptBase32ToString(santyCheckKey, password);
|
||||||
return {
|
|
||||||
ok: true,
|
// additional test
|
||||||
reason: "password_matched",
|
// because iOS Safari bypasses decryption with wrong password!
|
||||||
} as PasswordCheckType;
|
if (isVaildText(res)) {
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
reason: "password_matched",
|
||||||
|
} as PasswordCheckType;
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
ok: false,
|
||||||
|
reason: "invalid_text_after_decryption",
|
||||||
|
} as PasswordCheckType;
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return {
|
return {
|
||||||
ok: false,
|
ok: false,
|
||||||
|
|||||||
@@ -70,3 +70,22 @@ describe("Misc: get folder levels", () => {
|
|||||||
expect(misc.getFolderLevels(item3)).to.deep.equal(res3);
|
expect(misc.getFolderLevels(item3)).to.deep.equal(res3);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("Misc: vaild file name tests", () => {
|
||||||
|
it("should treat no ascii correctly", async () => {
|
||||||
|
const x = misc.isVaildText("😄🍎 apple 苹果");
|
||||||
|
// console.log(x)
|
||||||
|
expect(x).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should find not-printable chars correctly", async () => {
|
||||||
|
const x = misc.isVaildText("😄🍎 apple 苹果\u0000");
|
||||||
|
// console.log(x)
|
||||||
|
expect(x).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should allow spaces/slashes/...", async () => {
|
||||||
|
const x = misc.isVaildText("😄🍎 apple 苹果/-_=/\\*%^&@#$`");
|
||||||
|
expect(x).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"0.0.12": "0.12.15"
|
"0.0.14": "0.12.15"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user