spec-3nweb-server 1.8.6 → 1.8.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/3nstorage/resources/store.d.ts +2 -2
- package/build/3nstorage/resources/store.js +1 -1
- package/build/3nstorage/resources/users.d.ts +3 -3
- package/build/3nstorage/resources/users.js +1 -1
- package/build/3nstorage/resources/utils.d.ts +6 -0
- package/build/3nstorage/resources/utils.js +41 -0
- package/build/3nstorage/routes/owner/archive-obj-version.js +8 -10
- package/build/3nstorage/routes/owner/cancel-trans.js +5 -4
- package/build/3nstorage/routes/owner/delete-obj.js +7 -8
- package/build/3nstorage/routes/owner/get-archived-obj-ver.js +11 -13
- package/build/3nstorage/routes/owner/get-current-obj.js +7 -6
- package/build/3nstorage/routes/owner/get-obj-status.js +8 -8
- package/build/3nstorage/routes/owner/put-current-obj.js +25 -55
- package/build/3nstorage/routes/owner/session-params.js +2 -0
- package/build/asmail/resources/utils.d.ts +6 -0
- package/build/asmail/resources/utils.js +36 -0
- package/build/asmail/routes/delivery/finalize-delivery.js +3 -2
- package/build/asmail/routes/delivery/pre-flight.js +9 -26
- package/build/asmail/routes/delivery/provide-recipient-pubkey.js +4 -3
- package/build/asmail/routes/delivery/put-metadata.js +13 -21
- package/build/asmail/routes/delivery/put-obj.js +19 -25
- package/build/asmail/routes/delivery/restart-session.d.ts +4 -8
- package/build/asmail/routes/delivery/restart-session.js +10 -23
- package/build/asmail/routes/delivery/sender-authorization.js +5 -5
- package/build/asmail/routes/delivery/start-session.js +9 -24
- package/build/asmail/routes/retrieval/get-message-meta.js +4 -7
- package/build/asmail/routes/retrieval/get-message-obj.js +10 -7
- package/build/asmail/routes/retrieval/list-messages.js +4 -4
- package/build/asmail/routes/retrieval/remove-message.js +3 -2
- package/package.json +1 -1
- package/ts-code/3nstorage/resources/store.ts +3 -3
- package/ts-code/3nstorage/resources/users.ts +5 -5
- package/ts-code/3nstorage/resources/utils.ts +45 -0
- package/ts-code/3nstorage/routes/owner/archive-obj-version.ts +8 -10
- package/ts-code/3nstorage/routes/owner/cancel-trans.ts +5 -3
- package/ts-code/3nstorage/routes/owner/delete-obj.ts +9 -13
- package/ts-code/3nstorage/routes/owner/get-archived-obj-ver.ts +13 -15
- package/ts-code/3nstorage/routes/owner/get-current-obj.ts +7 -5
- package/ts-code/3nstorage/routes/owner/get-obj-status.ts +8 -8
- package/ts-code/3nstorage/routes/owner/put-current-obj.ts +33 -60
- package/ts-code/3nstorage/routes/owner/session-params.ts +4 -3
- package/ts-code/asmail/resources/utils.ts +41 -0
- package/ts-code/asmail/routes/delivery/finalize-delivery.ts +3 -2
- package/ts-code/asmail/routes/delivery/pre-flight.ts +15 -29
- package/ts-code/asmail/routes/delivery/provide-recipient-pubkey.ts +4 -4
- package/ts-code/asmail/routes/delivery/put-metadata.ts +21 -30
- package/ts-code/asmail/routes/delivery/put-obj.ts +20 -34
- package/ts-code/asmail/routes/delivery/restart-session.ts +11 -24
- package/ts-code/asmail/routes/delivery/sender-authorization.ts +5 -5
- package/ts-code/asmail/routes/delivery/start-session.ts +17 -32
- package/ts-code/asmail/routes/retrieval/get-message-meta.ts +5 -8
- package/ts-code/asmail/routes/retrieval/get-message-obj.ts +12 -15
- package/ts-code/asmail/routes/retrieval/list-messages.ts +4 -6
- package/ts-code/asmail/routes/retrieval/remove-message.ts +3 -2
|
@@ -19,6 +19,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
19
19
|
exports.deleteMsg = void 0;
|
|
20
20
|
const recipients_1 = require("../../resources/recipients");
|
|
21
21
|
const retrieval_1 = require("../../../lib-common/service-api/asmail/retrieval");
|
|
22
|
+
const utils_1 = require("../../resources/utils");
|
|
22
23
|
function deleteMsg(delMsgFunc) {
|
|
23
24
|
return async (req, res, next) => {
|
|
24
25
|
const userId = req.session.params.userId;
|
|
@@ -32,10 +33,10 @@ function deleteMsg(delMsgFunc) {
|
|
|
32
33
|
next(err);
|
|
33
34
|
}
|
|
34
35
|
else if (err === recipients_1.SC.MSG_UNKNOWN) {
|
|
35
|
-
|
|
36
|
+
(0, utils_1.replyWithErr)(retrieval_1.rmMsg.SC.unknownMsg, "Message " + msgId + " is unknown.", res);
|
|
36
37
|
}
|
|
37
38
|
else if (err === recipients_1.SC.USER_UNKNOWN) {
|
|
38
|
-
|
|
39
|
+
(0, utils_1.replyWithErr)(retrieval_1.ERR_SC.server, "Recipient disappeared from the system.", res);
|
|
39
40
|
req.session.close();
|
|
40
41
|
}
|
|
41
42
|
else {
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
Copyright (C) 2015 - 2017, 2019 - 2020, 2022, 2025 3NSoft Inc.
|
|
2
|
+
Copyright (C) 2015 - 2017, 2019 - 2020, 2022, 2025 - 2026 3NSoft Inc.
|
|
3
3
|
|
|
4
4
|
This program is free software: you can redistribute it and/or modify it under
|
|
5
5
|
the terms of the GNU General Public License as published by the Free Software
|
|
@@ -511,7 +511,7 @@ export class Store extends UserFiles<StorageParams> {
|
|
|
511
511
|
// file/folder.
|
|
512
512
|
|
|
513
513
|
async archiveCurrentObjVersion(
|
|
514
|
-
objId: string, version: number
|
|
514
|
+
objId: string|null, version: number
|
|
515
515
|
): Promise<void> {
|
|
516
516
|
// XXX need to put archiving transaction, closing it in a finally clause
|
|
517
517
|
// - make in memory transactions for archival and removal, or chaining(?)
|
|
@@ -535,7 +535,7 @@ export class Store extends UserFiles<StorageParams> {
|
|
|
535
535
|
);
|
|
536
536
|
}
|
|
537
537
|
|
|
538
|
-
async getObjStatus(objId: string): Promise<ObjStatus> {
|
|
538
|
+
async getObjStatus(objId: string|null): Promise<ObjStatus> {
|
|
539
539
|
const status = await this.statuses.get(objId);
|
|
540
540
|
return {
|
|
541
541
|
current: status.currentVersion,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
Copyright (C) 2015 - 2017, 2020, 2025 3NSoft Inc.
|
|
2
|
+
Copyright (C) 2015 - 2017, 2020, 2025 - 2026 3NSoft Inc.
|
|
3
3
|
|
|
4
4
|
This program is free software: you can redistribute it and/or modify it under
|
|
5
5
|
the terms of the GNU General Public License as published by the Free Software
|
|
@@ -48,15 +48,15 @@ export type GetArchivedObjVersion = (
|
|
|
48
48
|
) => Promise<ObjReader>;
|
|
49
49
|
|
|
50
50
|
export type CancelTransaction = (
|
|
51
|
-
userId: string, objId: string, transactionId?: string
|
|
51
|
+
userId: string, objId: string|null, transactionId?: string
|
|
52
52
|
) => Promise<void>;
|
|
53
53
|
|
|
54
54
|
export type ArchiveObjCurrentVersion = (
|
|
55
|
-
userId: string, objId: string, version: number
|
|
55
|
+
userId: string, objId: string|null, version: number
|
|
56
56
|
) => Promise<void>;
|
|
57
57
|
|
|
58
58
|
export type GetObjStatus = (
|
|
59
|
-
userId: string, objId: string
|
|
59
|
+
userId: string, objId: string|null
|
|
60
60
|
) => Promise<ObjStatus>;
|
|
61
61
|
|
|
62
62
|
export type DeleteCurrentObjVersion = (
|
|
@@ -181,7 +181,7 @@ export function makeFactory(
|
|
|
181
181
|
}
|
|
182
182
|
|
|
183
183
|
function makeTransactionCanceller(): CancelTransaction {
|
|
184
|
-
return async (userId: string, objId: string, transactionId?: string) => {
|
|
184
|
+
return async (userId: string, objId: string|null, transactionId?: string) => {
|
|
185
185
|
const store = await getStore(userId);
|
|
186
186
|
await store.cancelTransaction(objId, transactionId);
|
|
187
187
|
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (C) 2026 3NSoft Inc.
|
|
3
|
+
|
|
4
|
+
This program is free software: you can redistribute it and/or modify it under
|
|
5
|
+
the terms of the GNU General Public License as published by the Free Software
|
|
6
|
+
Foundation, either version 3 of the License, or (at your option) any later
|
|
7
|
+
version.
|
|
8
|
+
|
|
9
|
+
This program is distributed in the hope that it will be useful, but
|
|
10
|
+
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
12
|
+
See the GNU General Public License for more details.
|
|
13
|
+
|
|
14
|
+
You should have received a copy of the GNU General Public License along with
|
|
15
|
+
this program. If not, see <http://www.gnu.org/licenses/>.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { Response, Request } from 'express';
|
|
19
|
+
import { base64urlSafe } from '../../lib-common/buffer-utils';
|
|
20
|
+
import { ErrorReply } from '../../lib-common/service-api/3nstorage/owner';
|
|
21
|
+
import { attachByteDrainToRequest } from '../../lib-server/middleware/body-parsers';
|
|
22
|
+
|
|
23
|
+
export function getObjIdFromParams(rootObj: boolean, req: Request): {
|
|
24
|
+
objId: string|null; objIdParseErr?: string;
|
|
25
|
+
} {
|
|
26
|
+
if (rootObj) {
|
|
27
|
+
return { objId: null };
|
|
28
|
+
} else {
|
|
29
|
+
const objId = req.params.objId;
|
|
30
|
+
return ((!!objId && base64urlSafe.allCharsFromAlphabet(objId)) ?
|
|
31
|
+
{ objId } :
|
|
32
|
+
{ objId: undefined as any, objIdParseErr: "Bad object id" }
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function replyWithErr(status: number, msg: string, res: Response, reqToDrainBytesFrom?: Request): void {
|
|
38
|
+
if (reqToDrainBytesFrom) {
|
|
39
|
+
attachByteDrainToRequest(reqToDrainBytesFrom);
|
|
40
|
+
}
|
|
41
|
+
res.status(status).json({ error: msg } as ErrorReply);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
Object.freeze(exports);
|
|
@@ -19,24 +19,25 @@ import { RequestHandler } from 'express';
|
|
|
19
19
|
import { ArchiveObjCurrentVersion, MismatchedObjVerException, SC as storeSC } from '../../resources/users';
|
|
20
20
|
import { archiveObj as api, ERR_SC, HTTP_HEADER } from '../../../lib-common/service-api/3nstorage/owner';
|
|
21
21
|
import { Request } from '../../resources/sessions';
|
|
22
|
+
import { getObjIdFromParams, replyWithErr } from '../../resources/utils';
|
|
22
23
|
|
|
23
24
|
const SC = api.SC;
|
|
24
25
|
|
|
25
26
|
export function archiveCurrentObjVersion(
|
|
26
27
|
root: boolean, archiveObjVerFunc: ArchiveObjCurrentVersion
|
|
27
28
|
): RequestHandler {
|
|
28
|
-
if ('function' !== typeof archiveObjVerFunc) { throw new TypeError(
|
|
29
|
-
"Given argument 'archiveObjVerFunc' must be function, but is not."); }
|
|
30
29
|
|
|
31
30
|
return async (req: Request, res, next) => {
|
|
32
31
|
|
|
33
32
|
const userId = req.session.params.userId;
|
|
34
|
-
const objId
|
|
33
|
+
const { objId, objIdParseErr } = getObjIdFromParams(root, req);
|
|
34
|
+
if (objIdParseErr) {
|
|
35
|
+
return replyWithErr(ERR_SC.malformed, objIdParseErr, res);
|
|
36
|
+
}
|
|
35
37
|
|
|
36
38
|
const version = parseInt(req.query.ver as any);
|
|
37
39
|
if (isNaN(version) || (version < 1)) {
|
|
38
|
-
|
|
39
|
-
return;
|
|
40
|
+
return replyWithErr(ERR_SC.malformed, "Bad query parameters", res);
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
try {
|
|
@@ -55,12 +56,9 @@ export function archiveCurrentObjVersion(
|
|
|
55
56
|
next(err);
|
|
56
57
|
}
|
|
57
58
|
} else if (err === storeSC.OBJ_UNKNOWN) {
|
|
58
|
-
|
|
59
|
-
`Root object is not set.` :
|
|
60
|
-
`Object ${objId} is unknown.`);
|
|
59
|
+
replyWithErr(SC.unknownObj, (root ? `Root object is not set.` : `Object ${objId} is unknown.`), res);
|
|
61
60
|
} else if (err === storeSC.USER_UNKNOWN) {
|
|
62
|
-
|
|
63
|
-
"Recipient disappeared from the system.");
|
|
61
|
+
replyWithErr(ERR_SC.server, "Recipient disappeared from the system.", res);
|
|
64
62
|
req.session.close();
|
|
65
63
|
} else {
|
|
66
64
|
next(new Error("Unhandled storage error code: "+err));
|
|
@@ -19,17 +19,19 @@ import { RequestHandler } from 'express';
|
|
|
19
19
|
import { CancelTransaction, SC as storeSC } from '../../resources/users';
|
|
20
20
|
import { cancelTransaction as api, ERR_SC } from '../../../lib-common/service-api/3nstorage/owner';
|
|
21
21
|
import { Request } from '../../resources/sessions';
|
|
22
|
+
import { getObjIdFromParams, replyWithErr } from '../../resources/utils';
|
|
22
23
|
|
|
23
24
|
export function cancelTransaction(
|
|
24
25
|
root: boolean, cancelTransFunc: CancelTransaction
|
|
25
26
|
): RequestHandler {
|
|
26
|
-
if ('function' !== typeof cancelTransFunc) { throw new TypeError(
|
|
27
|
-
"Given argument 'cancelTransFunc' must be function, but is not."); }
|
|
28
27
|
|
|
29
28
|
return async (req: Request, res, next) => {
|
|
30
29
|
|
|
31
30
|
const userId = req.session.params.userId;
|
|
32
|
-
const objId
|
|
31
|
+
const { objId, objIdParseErr } = getObjIdFromParams(root, req);
|
|
32
|
+
if (objIdParseErr) {
|
|
33
|
+
return replyWithErr(ERR_SC.malformed, objIdParseErr, res);
|
|
34
|
+
}
|
|
33
35
|
const transactionId: string = req.params.transactionId;
|
|
34
36
|
|
|
35
37
|
try {
|
|
@@ -20,13 +20,12 @@ import { DeleteArchivedObjVersion, DeleteCurrentObjVersion, SC as storeSC } from
|
|
|
20
20
|
import { ERR_SC, currentObj as api } from '../../../lib-common/service-api/3nstorage/owner';
|
|
21
21
|
import { Request } from '../../resources/sessions';
|
|
22
22
|
import { assert } from '../../../lib-common/assert';
|
|
23
|
+
import { getObjIdFromParams, replyWithErr } from '../../resources/utils';
|
|
23
24
|
|
|
24
25
|
function deleteObj(
|
|
25
26
|
root: boolean, delCurrent: boolean,
|
|
26
27
|
deleteObjFunc: DeleteArchivedObjVersion|DeleteCurrentObjVersion
|
|
27
28
|
): RequestHandler {
|
|
28
|
-
if ('function' !== typeof deleteObjFunc) { throw new TypeError(
|
|
29
|
-
"Given argument 'deleteObjFunc' must be function, but is not."); }
|
|
30
29
|
if (delCurrent) {
|
|
31
30
|
assert(!root);
|
|
32
31
|
}
|
|
@@ -34,7 +33,10 @@ function deleteObj(
|
|
|
34
33
|
return async (req: Request, res, next) => {
|
|
35
34
|
|
|
36
35
|
const userId = req.session.params.userId;
|
|
37
|
-
const objId
|
|
36
|
+
const { objId, objIdParseErr } = getObjIdFromParams(root, req);
|
|
37
|
+
if (objIdParseErr) {
|
|
38
|
+
return replyWithErr(ERR_SC.malformed, objIdParseErr, res);
|
|
39
|
+
}
|
|
38
40
|
|
|
39
41
|
const ver = parseInt(req.query.ver as string);
|
|
40
42
|
let version: number|undefined;
|
|
@@ -42,26 +44,20 @@ function deleteObj(
|
|
|
42
44
|
if (delCurrent) {
|
|
43
45
|
version = undefined
|
|
44
46
|
} else {
|
|
45
|
-
|
|
46
|
-
return;
|
|
47
|
+
return replyWithErr(ERR_SC.malformed, "Bad query parameters", res);
|
|
47
48
|
}
|
|
48
49
|
} else {
|
|
49
50
|
if (ver < 1) {
|
|
50
|
-
|
|
51
|
-
return;
|
|
51
|
+
return replyWithErr(ERR_SC.malformed, "Bad query parameters", res);
|
|
52
52
|
}
|
|
53
53
|
version = ver;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
try {
|
|
57
57
|
if (delCurrent) {
|
|
58
|
-
await (deleteObjFunc as DeleteCurrentObjVersion)(
|
|
59
|
-
userId, objId!, version
|
|
60
|
-
);
|
|
58
|
+
await (deleteObjFunc as DeleteCurrentObjVersion)(userId, objId!, version);
|
|
61
59
|
} else {
|
|
62
|
-
await (deleteObjFunc as DeleteArchivedObjVersion)(
|
|
63
|
-
userId, objId, version!
|
|
64
|
-
);
|
|
60
|
+
await (deleteObjFunc as DeleteArchivedObjVersion)(userId, objId, version!);
|
|
65
61
|
}
|
|
66
62
|
res.status(api.SC.okDelete).send();
|
|
67
63
|
} catch (err) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
Copyright (C) 2017, 2020 3NSoft Inc.
|
|
2
|
+
Copyright (C) 2017, 2020, 2026 3NSoft Inc.
|
|
3
3
|
|
|
4
4
|
This program is free software: you can redistribute it and/or modify it under
|
|
5
5
|
the terms of the GNU General Public License as published by the Free Software
|
|
@@ -21,6 +21,7 @@ import { GetObjQueryOpts, ERR_SC, HTTP_HEADER, BIN_TYPE, archivedObjVersion as a
|
|
|
21
21
|
import { Request } from '../../resources/sessions';
|
|
22
22
|
import { errWithCause } from '../../../lib-common/exceptions/error';
|
|
23
23
|
import { EMPTY_BUFFER } from '../../../lib-common/buffer-utils';
|
|
24
|
+
import { getObjIdFromParams, replyWithErr } from '../../resources/utils';
|
|
24
25
|
|
|
25
26
|
function extractQueryOptions(req: Request): undefined|{
|
|
26
27
|
header: boolean; limit: number|undefined; ofs: number; ver: number
|
|
@@ -51,23 +52,22 @@ function extractQueryOptions(req: Request): undefined|{
|
|
|
51
52
|
export function getArchivedObjVersion(
|
|
52
53
|
root: boolean, getArchivedObjFunc: GetArchivedObjVersion
|
|
53
54
|
): RequestHandler {
|
|
54
|
-
if ('function' !== typeof getArchivedObjFunc) { throw new TypeError(
|
|
55
|
-
"Given argument 'getArchivedObjFunc' must be function, but is not."); }
|
|
56
55
|
|
|
57
56
|
return async (req: Request, res, next) => {
|
|
58
57
|
|
|
59
58
|
const userId = req.session.params.userId;
|
|
60
|
-
const objId
|
|
59
|
+
const { objId, objIdParseErr } = getObjIdFromParams(root, req);
|
|
60
|
+
if (objIdParseErr) {
|
|
61
|
+
return replyWithErr(ERR_SC.malformed, objIdParseErr, res);
|
|
62
|
+
}
|
|
61
63
|
|
|
62
64
|
const opts = extractQueryOptions(req);
|
|
63
65
|
if (!opts) {
|
|
64
|
-
|
|
65
|
-
return;
|
|
66
|
+
return replyWithErr(ERR_SC.malformed, "Bad query parameters", res);
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
try {
|
|
69
|
-
const reader = await getArchivedObjFunc(userId, objId, opts.ver,
|
|
70
|
-
opts.header, opts.ofs, opts.limit);
|
|
70
|
+
const reader = await getArchivedObjFunc(userId, objId, opts.ver, opts.header, opts.ofs, opts.limit);
|
|
71
71
|
res.status(api.SC.okGet);
|
|
72
72
|
res.set(HTTP_HEADER.contentType, BIN_TYPE);
|
|
73
73
|
res.set(HTTP_HEADER.contentLength, `${reader.len}`);
|
|
@@ -90,15 +90,13 @@ export function getArchivedObjVersion(
|
|
|
90
90
|
if ("string" !== typeof err) {
|
|
91
91
|
next(err);
|
|
92
92
|
} else if (err === storeSC.OBJ_UNKNOWN) {
|
|
93
|
-
|
|
94
|
-
`Object ${objId}is unknown.` : `Root object is not set`);
|
|
93
|
+
replyWithErr(api.SC.unknownObj, (objId ? `Object ${objId}is unknown.` : `Root object is not set`), res);
|
|
95
94
|
} else if (err === storeSC.OBJ_VER_UNKNOWN) {
|
|
96
|
-
|
|
97
|
-
`Object ${objId}, version ${opts.ver} is unknown.` :
|
|
98
|
-
|
|
95
|
+
replyWithErr(api.SC.unknownObjVer, (objId ?
|
|
96
|
+
`Object ${objId}, version ${opts.ver} is unknown.` : `Root object version ${opts.ver} is not known.`
|
|
97
|
+
), res);
|
|
99
98
|
} else if (err === storeSC.USER_UNKNOWN) {
|
|
100
|
-
|
|
101
|
-
`Recipient disappeared from the system.`);
|
|
99
|
+
replyWithErr(ERR_SC.server, `Recipient disappeared from the system.`, res);
|
|
102
100
|
req.session.close();
|
|
103
101
|
} else {
|
|
104
102
|
next(new Error(`Unhandled storage error code: ${err}`));
|
|
@@ -21,6 +21,7 @@ import { GetObjQueryOpts, ERR_SC, HTTP_HEADER, BIN_TYPE, currentObj as api } fro
|
|
|
21
21
|
import { Request } from '../../resources/sessions';
|
|
22
22
|
import { errWithCause, stringifyErr } from '../../../lib-common/exceptions/error';
|
|
23
23
|
import { EMPTY_BUFFER } from '../../../lib-common/buffer-utils';
|
|
24
|
+
import { getObjIdFromParams, replyWithErr } from '../../resources/utils';
|
|
24
25
|
|
|
25
26
|
function extractQueryOptions(req: Request): undefined|{
|
|
26
27
|
header: boolean; limit: number|undefined; ofs: number;
|
|
@@ -58,13 +59,14 @@ function extractQueryOptions(req: Request): undefined|{
|
|
|
58
59
|
export function getCurrentObj(
|
|
59
60
|
root: boolean, getCurrentObjFunc: GetCurrentObj
|
|
60
61
|
): RequestHandler {
|
|
61
|
-
if ('function' !== typeof getCurrentObjFunc) { throw new TypeError(
|
|
62
|
-
"Given argument 'getCurrentObjFunc' must be function, but is not."); }
|
|
63
62
|
|
|
64
63
|
return async (req: Request, res, next) => {
|
|
65
64
|
|
|
66
65
|
const userId = req.session.params.userId;
|
|
67
|
-
const objId
|
|
66
|
+
const { objId, objIdParseErr } = getObjIdFromParams(root, req);
|
|
67
|
+
if (objIdParseErr) {
|
|
68
|
+
return replyWithErr(ERR_SC.malformed, objIdParseErr, res);
|
|
69
|
+
}
|
|
68
70
|
|
|
69
71
|
const opts = extractQueryOptions(req);
|
|
70
72
|
if (!opts) {
|
|
@@ -107,9 +109,9 @@ export function getCurrentObj(
|
|
|
107
109
|
next(err);
|
|
108
110
|
} else if ((err === storeSC.OBJ_UNKNOWN)
|
|
109
111
|
|| (err === storeSC.OBJ_VER_UNKNOWN)) {
|
|
110
|
-
|
|
112
|
+
replyWithErr(api.SC.unknownObj, (objId ? `Object ${objId} is unknown.` : `Root object is not set.`), res);
|
|
111
113
|
} else if (err === storeSC.USER_UNKNOWN) {
|
|
112
|
-
|
|
114
|
+
replyWithErr(ERR_SC.server, `Recipient disappeared from the system.`, res);
|
|
113
115
|
req.session.close();
|
|
114
116
|
} else {
|
|
115
117
|
next(new Error(`Unhandled storage error code: ${err}`));
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
Copyright (C) 2022 3NSoft Inc.
|
|
2
|
+
Copyright (C) 2022, 2026 3NSoft Inc.
|
|
3
3
|
|
|
4
4
|
This program is free software: you can redistribute it and/or modify it under
|
|
5
5
|
the terms of the GNU General Public License as published by the Free Software
|
|
@@ -19,17 +19,19 @@ import { RequestHandler } from 'express';
|
|
|
19
19
|
import { GetObjStatus, SC as storeSC } from '../../resources/users';
|
|
20
20
|
import { objStatus as api, ERR_SC } from '../../../lib-common/service-api/3nstorage/owner';
|
|
21
21
|
import { Request } from '../../resources/sessions';
|
|
22
|
+
import { getObjIdFromParams, replyWithErr } from '../../resources/utils';
|
|
22
23
|
|
|
23
24
|
export function getObjStatus(
|
|
24
25
|
root: boolean, getObjStatusFunc: GetObjStatus
|
|
25
26
|
): RequestHandler {
|
|
26
|
-
if ('function' !== typeof getObjStatusFunc) { throw new TypeError(
|
|
27
|
-
"Given argument 'getObjStatusFunc' must be function, but is not."); }
|
|
28
27
|
|
|
29
28
|
return async (req: Request, res, next) => {
|
|
30
29
|
|
|
31
30
|
const userId = req.session.params.userId;
|
|
32
|
-
const objId
|
|
31
|
+
const { objId, objIdParseErr } = getObjIdFromParams(root, req);
|
|
32
|
+
if (objIdParseErr) {
|
|
33
|
+
return replyWithErr(ERR_SC.malformed, objIdParseErr, res);
|
|
34
|
+
}
|
|
33
35
|
|
|
34
36
|
try {
|
|
35
37
|
const lst = await getObjStatusFunc(userId, objId);
|
|
@@ -38,11 +40,9 @@ export function getObjStatus(
|
|
|
38
40
|
if ("string" !== typeof err) {
|
|
39
41
|
next(err);
|
|
40
42
|
} else if (err === storeSC.OBJ_UNKNOWN) {
|
|
41
|
-
|
|
42
|
-
`Root object is set.` : `Object ${objId} is unknown.`);
|
|
43
|
+
replyWithErr(api.SC.unknownObj, (root ? `Root object is set.` : `Object ${objId} is unknown.`), res);
|
|
43
44
|
} else if (err === storeSC.USER_UNKNOWN) {
|
|
44
|
-
|
|
45
|
-
"Recipient disappeared from the system.");
|
|
45
|
+
replyWithErr(ERR_SC.server, "Recipient disappeared from the system.", res);
|
|
46
46
|
req.session.close();
|
|
47
47
|
} else {
|
|
48
48
|
next(new Error("Unhandled storage error code: "+err));
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
Copyright (C) 2017, 2019 - 2020 3NSoft Inc.
|
|
2
|
+
Copyright (C) 2017, 2019 - 2020, 2026 3NSoft Inc.
|
|
3
3
|
|
|
4
4
|
This program is free software: you can redistribute it and/or modify it under
|
|
5
5
|
the terms of the GNU General Public License as published by the Free Software
|
|
@@ -24,7 +24,8 @@ import { attachByteDrainToRequest } from '../../../lib-server/middleware/body-pa
|
|
|
24
24
|
import { Readable as ReadableStream } from 'stream';
|
|
25
25
|
import { BytesFIFOBuffer } from '../../../lib-common/byte-streaming/common';
|
|
26
26
|
import { defer, Deferred } from '../../../lib-common/processes';
|
|
27
|
-
import { utf8
|
|
27
|
+
import { utf8 } from '../../../lib-common/buffer-utils';
|
|
28
|
+
import { getObjIdFromParams, replyWithErr } from '../../resources/utils';
|
|
28
29
|
|
|
29
30
|
export function saveCurrentObj(
|
|
30
31
|
root: boolean, saveObjFunc: SaveNewObjVersion, chunkLimit: string|number
|
|
@@ -36,43 +37,36 @@ export function saveCurrentObj(
|
|
|
36
37
|
return async (req: Request, res, next) => {
|
|
37
38
|
|
|
38
39
|
if (!req.is(BIN_TYPE)) {
|
|
39
|
-
|
|
40
|
-
res.status(ERR_SC.wrongContentType).json( <ErrorReply> {
|
|
41
|
-
error: `Content-Type must be ${BIN_TYPE} for this call.`
|
|
42
|
-
});
|
|
43
|
-
return;
|
|
40
|
+
return replyWithErr(ERR_SC.wrongContentType, `Content-Type must be ${BIN_TYPE} for this call.`, res, req);
|
|
44
41
|
}
|
|
45
|
-
|
|
46
|
-
const userId = req.session.params.userId;
|
|
47
|
-
const objId: string = (root ? null as any : req.params.objId);
|
|
48
42
|
|
|
49
43
|
// get and check Content-Length, implicitly sending replies for bad length
|
|
50
44
|
let len = getContentLenOrSendError(req, res, maxChunkSize);
|
|
51
45
|
if (len === undefined) {
|
|
52
|
-
attachByteDrainToRequest(req);
|
|
53
46
|
return;
|
|
54
47
|
}
|
|
48
|
+
|
|
49
|
+
const userId = req.session.params.userId;
|
|
50
|
+
const { objId, objIdParseErr } = getObjIdFromParams(root, req);
|
|
51
|
+
if (objIdParseErr) {
|
|
52
|
+
return replyWithErr(ERR_SC.malformed, objIdParseErr, res, req);
|
|
53
|
+
}
|
|
55
54
|
|
|
56
55
|
// extract and check query parameters
|
|
57
56
|
const opts = extractQueryOptions(req);
|
|
58
|
-
if (!opts) {
|
|
57
|
+
if (!opts) {
|
|
58
|
+
return replyWithErr(ERR_SC.malformed, "Bad query parameters", res, req);
|
|
59
|
+
}
|
|
59
60
|
const { fstReq, sndReq } = opts;
|
|
60
61
|
|
|
61
62
|
let diff: DiffInfo|undefined;
|
|
62
63
|
|
|
63
64
|
if (fstReq) {
|
|
64
65
|
|
|
65
|
-
if (!root && objId) {
|
|
66
|
-
if (!base64urlSafe.allCharsFromAlphabet(objId!)) {
|
|
67
|
-
return replyToMalformed(req, "Bad object id", res);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
66
|
// cross-check given lengths
|
|
72
|
-
const diffAndHeader = fstReq.header +
|
|
73
|
-
(fstReq.diff ? fstReq.diff : 0);
|
|
67
|
+
const diffAndHeader = fstReq.header + (fstReq.diff ? fstReq.diff : 0);
|
|
74
68
|
if (diffAndHeader > len) {
|
|
75
|
-
return
|
|
69
|
+
return replyWithErr(ERR_SC.malformed, "Bad query parameters", res, req);
|
|
76
70
|
}
|
|
77
71
|
|
|
78
72
|
// read diff, if it is expected
|
|
@@ -81,30 +75,29 @@ export function saveCurrentObj(
|
|
|
81
75
|
if (diffBytes) {
|
|
82
76
|
len -= fstReq.diff;
|
|
83
77
|
} else {
|
|
84
|
-
|
|
78
|
+
return replyWithErr(ERR_SC.malformed, "Cannot read diff bytes", res, req);
|
|
85
79
|
}
|
|
86
80
|
try {
|
|
87
81
|
const diffFromReq = JSON.parse(utf8.open(diffBytes));
|
|
88
82
|
diff = sanitizedDiff(diffFromReq, fstReq.ver);
|
|
89
83
|
if (!diff) {
|
|
90
|
-
return
|
|
84
|
+
return replyWithErr(ERR_SC.malformed, "Malformed diff info", res, req);
|
|
91
85
|
}
|
|
92
86
|
} catch (err) {
|
|
93
|
-
|
|
87
|
+
return replyWithErr(ERR_SC.malformed, "Malformed diff info", res, req);
|
|
94
88
|
}
|
|
95
89
|
}
|
|
96
90
|
|
|
97
91
|
} else if (sndReq) {
|
|
98
92
|
|
|
99
93
|
if ((len === 0) && !sndReq.last) {
|
|
100
|
-
return
|
|
94
|
+
return replyWithErr(ERR_SC.malformed, "No segment bytes", res, req);
|
|
101
95
|
}
|
|
102
96
|
|
|
103
97
|
}
|
|
104
98
|
|
|
105
99
|
try {
|
|
106
|
-
const transactionId = await saveObjFunc(
|
|
107
|
-
userId, objId, fstReq, diff, sndReq, len, req);
|
|
100
|
+
const transactionId = await saveObjFunc(userId, objId, fstReq, diff, sndReq, len, req);
|
|
108
101
|
const reply: api.ReplyToPut = (transactionId ? { transactionId } : {});
|
|
109
102
|
res.status(api.SC.okPut).json(reply);
|
|
110
103
|
} catch (err) {
|
|
@@ -114,42 +107,28 @@ export function saveCurrentObj(
|
|
|
114
107
|
error: "Current object version doesn't match assumed one.",
|
|
115
108
|
current_version: (err as MismatchedObjVerException).current_version
|
|
116
109
|
};
|
|
117
|
-
res.set(HTTP_HEADER.objVersion, `${
|
|
110
|
+
res.set(HTTP_HEADER.objVersion, `${rep.current_version}`);
|
|
118
111
|
res.status(api.SC.mismatchedObjVer).json(rep);
|
|
119
112
|
} else {
|
|
120
113
|
next(err);
|
|
121
114
|
}
|
|
122
115
|
} else if ((err === saveSC.OBJ_UNKNOWN)
|
|
123
116
|
|| (err === saveSC.OBJ_VER_UNKNOWN)) {
|
|
124
|
-
|
|
125
|
-
error: `Unknown object ${objId}.`
|
|
126
|
-
});
|
|
117
|
+
replyWithErr(api.SC.unknownObj, `Unknown object ${objId}.`, res);
|
|
127
118
|
} else if (err === saveSC.TRANSACTION_UNKNOWN) {
|
|
128
|
-
|
|
129
|
-
error: "Unknown transaction."
|
|
130
|
-
});
|
|
119
|
+
replyWithErr(api.SC.unknownTransaction, "Unknown transaction.", res);
|
|
131
120
|
} else if ((err === saveSC.OBJ_EXIST)
|
|
132
121
|
|| (err === saveSC.OBJ_VER_EXIST)) {
|
|
133
|
-
|
|
134
|
-
error: "Object already exists."
|
|
135
|
-
});
|
|
122
|
+
replyWithErr(api.SC.objAlreadyExists, "Object already exists.", res);
|
|
136
123
|
} else if (err === saveSC.CONCURRENT_TRANSACTION) {
|
|
137
|
-
|
|
138
|
-
error: `Object ${objId} is currently under a transaction.`
|
|
139
|
-
});
|
|
124
|
+
replyWithErr(api.SC.concurrentTransaction, `Object ${objId} is currently under a transaction.`, res);
|
|
140
125
|
} else if (err === saveSC.USER_UNKNOWN) {
|
|
141
|
-
|
|
142
|
-
error: "Recipient disappeared from the system."
|
|
143
|
-
});
|
|
126
|
+
replyWithErr(ERR_SC.server, "Recipient disappeared from the system.", res);
|
|
144
127
|
req.session.close();
|
|
145
128
|
} else if (err === saveSC.NOT_ENOUGH_SPACE) {
|
|
146
|
-
|
|
147
|
-
error: "Reached storage limits."
|
|
148
|
-
});
|
|
129
|
+
replyWithErr(ERR_SC.noSpace, "Reached storage limits.", res);
|
|
149
130
|
} else if (err === saveSC.OBJ_FILE_INCOMPLETE) {
|
|
150
|
-
|
|
151
|
-
error: "Object version file is incomplete."
|
|
152
|
-
});
|
|
131
|
+
replyWithErr(api.SC.objIncomplete, "Object version file is incomplete.", res);
|
|
153
132
|
} else {
|
|
154
133
|
next(new Error(`Unhandled storage error code: ${err}`));
|
|
155
134
|
}
|
|
@@ -202,11 +181,9 @@ function getContentLenOrSendError(
|
|
|
202
181
|
): number|undefined {
|
|
203
182
|
const contentLength = parseInt(req.get(HTTP_HEADER.contentLength)!);
|
|
204
183
|
if (isNaN(contentLength)) {
|
|
205
|
-
|
|
206
|
-
error: "Content-Length header is required with proper number." });
|
|
184
|
+
replyWithErr(ERR_SC.contentLenMissing, "Content-Length header is required with proper number.", res, req);
|
|
207
185
|
} else if (contentLength > maxChunkSize) {
|
|
208
|
-
|
|
209
|
-
error: "Request body is too long." });
|
|
186
|
+
replyWithErr(ERR_SC.contentTooLong, "Request body is too long.", res, req);
|
|
210
187
|
} else {
|
|
211
188
|
return contentLength;
|
|
212
189
|
}
|
|
@@ -225,8 +202,9 @@ const EARLY_END_OF_STREAM_ERR_STR = `Unexpected end of stream`;
|
|
|
225
202
|
function readChunkFrom(
|
|
226
203
|
stream: ReadableStream, bytesToRead: number
|
|
227
204
|
): Promise<Uint8Array> {
|
|
228
|
-
if (bytesToRead < 1) {
|
|
229
|
-
`Illegal number of bytes to read from stream: ${bytesToRead}`);
|
|
205
|
+
if (bytesToRead < 1) {
|
|
206
|
+
throw new Error(`Illegal number of bytes to read from stream: ${bytesToRead}`);
|
|
207
|
+
}
|
|
230
208
|
|
|
231
209
|
const buffer = new BytesFIFOBuffer();
|
|
232
210
|
let deferred: Deferred<Uint8Array>|undefined = defer<Uint8Array>();
|
|
@@ -274,11 +252,6 @@ function readChunkFrom(
|
|
|
274
252
|
return deferred.promise;
|
|
275
253
|
}
|
|
276
254
|
|
|
277
|
-
function replyToMalformed(req: Request, msg: string, res: Response): void {
|
|
278
|
-
attachByteDrainToRequest(req);
|
|
279
|
-
res.status(ERR_SC.malformed).json( <ErrorReply> { error: msg });
|
|
280
|
-
}
|
|
281
|
-
|
|
282
255
|
function noop() {}
|
|
283
256
|
|
|
284
257
|
Object.freeze(exports);
|
|
@@ -26,17 +26,18 @@ export function sessionParams(maxChunk: number|string): RequestHandler {
|
|
|
26
26
|
|
|
27
27
|
return async (req: Request, res, next) => {
|
|
28
28
|
|
|
29
|
+
// XXX currently session params are same for all, hence, userId is unused, but, this whole
|
|
30
|
+
// thing must be within a session
|
|
29
31
|
const userId = req.session.params.userId;
|
|
30
32
|
|
|
31
|
-
try{
|
|
33
|
+
try {
|
|
32
34
|
const reply: api.Reply = { maxChunkSize };
|
|
33
35
|
res.status(api.SC.ok).json(reply);
|
|
34
36
|
} catch (err) {
|
|
35
37
|
if ("string" !== typeof err) {
|
|
36
38
|
next(err);
|
|
37
39
|
} else if (err === storeSC.USER_UNKNOWN) {
|
|
38
|
-
res.status(ERR_SC.server).send(
|
|
39
|
-
"Recipient disappeared from the system.");
|
|
40
|
+
res.status(ERR_SC.server).send("Recipient disappeared from the system.");
|
|
40
41
|
req.session.close();
|
|
41
42
|
} else {
|
|
42
43
|
next(new Error("Unhandled storage error code: "+err));
|