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.
Files changed (54) hide show
  1. package/build/3nstorage/resources/store.d.ts +2 -2
  2. package/build/3nstorage/resources/store.js +1 -1
  3. package/build/3nstorage/resources/users.d.ts +3 -3
  4. package/build/3nstorage/resources/users.js +1 -1
  5. package/build/3nstorage/resources/utils.d.ts +6 -0
  6. package/build/3nstorage/resources/utils.js +41 -0
  7. package/build/3nstorage/routes/owner/archive-obj-version.js +8 -10
  8. package/build/3nstorage/routes/owner/cancel-trans.js +5 -4
  9. package/build/3nstorage/routes/owner/delete-obj.js +7 -8
  10. package/build/3nstorage/routes/owner/get-archived-obj-ver.js +11 -13
  11. package/build/3nstorage/routes/owner/get-current-obj.js +7 -6
  12. package/build/3nstorage/routes/owner/get-obj-status.js +8 -8
  13. package/build/3nstorage/routes/owner/put-current-obj.js +25 -55
  14. package/build/3nstorage/routes/owner/session-params.js +2 -0
  15. package/build/asmail/resources/utils.d.ts +6 -0
  16. package/build/asmail/resources/utils.js +36 -0
  17. package/build/asmail/routes/delivery/finalize-delivery.js +3 -2
  18. package/build/asmail/routes/delivery/pre-flight.js +9 -26
  19. package/build/asmail/routes/delivery/provide-recipient-pubkey.js +4 -3
  20. package/build/asmail/routes/delivery/put-metadata.js +13 -21
  21. package/build/asmail/routes/delivery/put-obj.js +19 -25
  22. package/build/asmail/routes/delivery/restart-session.d.ts +4 -8
  23. package/build/asmail/routes/delivery/restart-session.js +10 -23
  24. package/build/asmail/routes/delivery/sender-authorization.js +5 -5
  25. package/build/asmail/routes/delivery/start-session.js +9 -24
  26. package/build/asmail/routes/retrieval/get-message-meta.js +4 -7
  27. package/build/asmail/routes/retrieval/get-message-obj.js +10 -7
  28. package/build/asmail/routes/retrieval/list-messages.js +4 -4
  29. package/build/asmail/routes/retrieval/remove-message.js +3 -2
  30. package/package.json +1 -1
  31. package/ts-code/3nstorage/resources/store.ts +3 -3
  32. package/ts-code/3nstorage/resources/users.ts +5 -5
  33. package/ts-code/3nstorage/resources/utils.ts +45 -0
  34. package/ts-code/3nstorage/routes/owner/archive-obj-version.ts +8 -10
  35. package/ts-code/3nstorage/routes/owner/cancel-trans.ts +5 -3
  36. package/ts-code/3nstorage/routes/owner/delete-obj.ts +9 -13
  37. package/ts-code/3nstorage/routes/owner/get-archived-obj-ver.ts +13 -15
  38. package/ts-code/3nstorage/routes/owner/get-current-obj.ts +7 -5
  39. package/ts-code/3nstorage/routes/owner/get-obj-status.ts +8 -8
  40. package/ts-code/3nstorage/routes/owner/put-current-obj.ts +33 -60
  41. package/ts-code/3nstorage/routes/owner/session-params.ts +4 -3
  42. package/ts-code/asmail/resources/utils.ts +41 -0
  43. package/ts-code/asmail/routes/delivery/finalize-delivery.ts +3 -2
  44. package/ts-code/asmail/routes/delivery/pre-flight.ts +15 -29
  45. package/ts-code/asmail/routes/delivery/provide-recipient-pubkey.ts +4 -4
  46. package/ts-code/asmail/routes/delivery/put-metadata.ts +21 -30
  47. package/ts-code/asmail/routes/delivery/put-obj.ts +20 -34
  48. package/ts-code/asmail/routes/delivery/restart-session.ts +11 -24
  49. package/ts-code/asmail/routes/delivery/sender-authorization.ts +5 -5
  50. package/ts-code/asmail/routes/delivery/start-session.ts +17 -32
  51. package/ts-code/asmail/routes/retrieval/get-message-meta.ts +5 -8
  52. package/ts-code/asmail/routes/retrieval/get-message-obj.ts +12 -15
  53. package/ts-code/asmail/routes/retrieval/list-messages.ts +4 -6
  54. 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
- res.status(retrieval_1.rmMsg.SC.unknownMsg).send("Message " + msgId + " is unknown.");
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
- res.status(retrieval_1.ERR_SC.server).send("Recipient disappeared from the system.");
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,6 +1,6 @@
1
1
  {
2
2
  "name": "spec-3nweb-server",
3
- "version": "1.8.6",
3
+ "version": "1.8.7",
4
4
  "description": "3NWeb spec server.",
5
5
  "main": "build/lib.js",
6
6
  "types": "build/lib.d.ts",
@@ -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: string = (root ? null as any : req.params.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
- res.status(ERR_SC.malformed).send("Bad query parameters");
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
- res.status(SC.unknownObj).send(root ?
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
- res.status(ERR_SC.server).send(
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: string = (root ? null as any : req.params.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: string|null = (root ? null : req.params.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
- res.status(ERR_SC.malformed).send("Bad query parameters");
46
- return;
47
+ return replyWithErr(ERR_SC.malformed, "Bad query parameters", res);
47
48
  }
48
49
  } else {
49
50
  if (ver < 1) {
50
- res.status(ERR_SC.malformed).send("Bad query parameters");
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: string = (root ? null as any : req.params.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
- res.status(ERR_SC.malformed).send("Bad query parameters");
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
- res.status(api.SC.unknownObj).send(objId ?
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
- res.status(api.SC.unknownObjVer).send(objId ?
97
- `Object ${objId}, version ${opts.ver} is unknown.` :
98
- `Root object version ${opts.ver} is not known.`);
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
- res.status(ERR_SC.server).send(
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: string = (root ? null as any : req.params.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
- res.status(api.SC.unknownObj).send(objId ? `Object ${objId} is unknown.` : `Root object is not set.`);
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
- res.status(ERR_SC.server).send(`Recipient disappeared from the system.`);
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: string = (root ? null as any : req.params.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
- res.status(api.SC.unknownObj).send(root ?
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
- res.status(ERR_SC.server).send(
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, base64urlSafe } from '../../../lib-common/buffer-utils';
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
- attachByteDrainToRequest(req);
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) { return replyToMalformed(req, "Bad query parameters", res); }
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 replyToMalformed(req, "Bad query parameters", res);
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
- return replyToMalformed(req, "Cannot read diff bytes", res);
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 replyToMalformed(req, "Malformed diff info", res);
84
+ return replyWithErr(ERR_SC.malformed, "Malformed diff info", res, req);
91
85
  }
92
86
  } catch (err) {
93
- return replyToMalformed(req, "Malformed diff info", res);
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 replyToMalformed(req, "No segment bytes", res);
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, `${(err as MismatchedObjVerException).current_version}`);
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
- res.status(api.SC.unknownObj).json( <ErrorReply> {
125
- error: `Unknown object ${objId}.`
126
- });
117
+ replyWithErr(api.SC.unknownObj, `Unknown object ${objId}.`, res);
127
118
  } else if (err === saveSC.TRANSACTION_UNKNOWN) {
128
- res.status(api.SC.unknownTransaction).json( <ErrorReply> {
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
- res.status(api.SC.objAlreadyExists).json( <ErrorReply> {
134
- error: "Object already exists."
135
- });
122
+ replyWithErr(api.SC.objAlreadyExists, "Object already exists.", res);
136
123
  } else if (err === saveSC.CONCURRENT_TRANSACTION) {
137
- res.status(api.SC.concurrentTransaction).json( <ErrorReply> {
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
- res.status(ERR_SC.server).json( <ErrorReply> {
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
- res.status(ERR_SC.noSpace).json( <ErrorReply> {
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
- res.status(api.SC.objIncomplete).json( <ErrorReply> {
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
- res.status(ERR_SC.contentLenMissing).json( <ErrorReply> {
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
- res.status(ERR_SC.contentTooLong).json( <ErrorReply> {
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) { throw new Error(
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));