From 54e27f2ad97bc0d5fc5ddd7f0f34ddf62f332d99 Mon Sep 17 00:00:00 2001 From: Fenris Wolf Date: Thu, 14 Nov 2024 19:57:13 +0100 Subject: [PATCH] [int] --- conf/example.json | 3 +- lib/plankton/plankton.d.ts | 56 ++- lib/plankton/plankton.js | 542 +++++++++++++++++------------ source/api/actions/caldav_probe.ts | 110 +++--- source/api/actions/caldav_sniff.ts | 46 +++ source/api/base.ts | 91 +++++ source/api/functions.ts | 1 + source/conf.ts | 12 +- source/main.ts | 30 +- source/services/calendar.ts | 8 +- tools/makefile | 1 + 11 files changed, 589 insertions(+), 311 deletions(-) create mode 100644 source/api/actions/caldav_sniff.ts diff --git a/conf/example.json b/conf/example.json index 5821d0b..ea0df88 100644 --- a/conf/example.json +++ b/conf/example.json @@ -4,7 +4,8 @@ { "kind": "stdout", "data": { - "threshold": "info" + "threshold": "info", + "format": "human_readable" } } ], diff --git a/lib/plankton/plankton.d.ts b/lib/plankton/plankton.d.ts index ff94071..355d916 100644 --- a/lib/plankton/plankton.d.ts +++ b/lib/plankton/plankton.d.ts @@ -227,6 +227,12 @@ declare namespace lib_plankton.base { /** */ function object_merge(core: Record, mantle: Record): Record; + /** + */ + function buffer_show(buffer: Buffer, { "block_size": option_block_size, "break_char": option_break_char, }?: { + block_size?: int; + break_char?: string; + }): string; } declare module lib_plankton.pod { /** @@ -619,6 +625,12 @@ declare namespace lib_plankton.call { value: (null | type_value); error: (null | any); }; + /** + */ + function try_catch_wrap_async(get_value: (() => Promise)): Promise<{ + value: (null | type_value); + error: (null | any); + }>; } declare namespace lib_plankton.email { /** @@ -3472,7 +3484,6 @@ declare namespace lib_plankton.ical { } declare namespace lib_plankton.http_base { /** - * @author roydfalk */ type type_request = { scheme: ("http" | "https"); @@ -3485,30 +3496,25 @@ declare namespace lib_plankton.http_base { body: (null | Buffer); }; /** - * @author fenris */ type type_response = { version: (null | string); status_code: type_status_code; headers: Record; - body: Buffer; + body: (null | Buffer); }; } declare namespace lib_plankton.http_base { /** - * @author fenris */ function encode_request(encode_method: ((method: type_method) => string), request: type_request): string; /** - * @author fenris */ function decode_request(decode_method: ((method_raw: string) => type_method), has_body: ((method: type_method) => boolean), request_raw: string): type_request; /** - * @author fenris */ function encode_response(encode_status_code: ((status_code: type_status_code) => string), get_status_text: ((status_code: type_status_code) => string), response: type_response): string; /** - * @author fenris */ function decode_response(decode_status_code: ((status_code_raw: string) => type_status_code), response_raw: string): type_response; /** @@ -3642,79 +3648,60 @@ declare namespace lib_plankton.http { implementation?: ("fetch" | "http_module"); }): Promise; } -/** - * @author fenris - */ declare namespace lib_plankton.xml { /** - * @author fenris */ abstract class class_node { /** - * @author fenris */ abstract compile(depth?: int): string; } /** - * @author fenris */ class class_node_text extends class_node { /** - * @author fenris */ protected content: string; /** - * @author fenris */ constructor(content: string); /** - * @author fenris */ compile(depth?: int): string; } /** - * @author fenris */ class class_node_comment extends class_node { /** - * @author fenris */ protected content: string; /** - * @author fenris */ constructor(content: string); /** - * @author fenris */ compile(depth?: int): string; } /** - * @author fenris */ class class_node_complex extends class_node { /** - * @author fenris */ protected name: string; /** - * @author fenris */ protected attributes: { [key: string]: string; }; /** - * @author fenris */ protected children: Array; /** - * @author fenris */ constructor(name: string, attributes?: { [key: string]: string; }, children?: any[]); /** - * @author fenris */ compile(depth?: int): string; } @@ -4186,6 +4173,7 @@ declare namespace lib_plankton.rest_base { }) => Promise<{ status_code: int; data: type_output; + headers?: (null | Record); }>); /** */ @@ -4209,7 +4197,7 @@ declare namespace lib_plankton.rest_base { request_body_mimetype: string; request_body_decode: ((http_request_body: Buffer, http_request_header_content_type: (null | string)) => any); response_body_mimetype: string; - response_body_encode: ((output: any) => Buffer); + response_body_encode: ((output: any) => (null | Buffer)); }; /** */ @@ -4316,15 +4304,16 @@ declare namespace lib_plankton.rest_base { * @todo check request body mimetype? * @todo check query paramater validity */ - function call(encode_http_method: ((http_method: type_http_method) => string), decode_status_code: ((status_code_raw: int) => type_http_status_code), rest: type_rest, http_request: lib_plankton.http_base.type_request, { "checklevel_restriction": option_checklevel_restriction, "checklevel_input": option_checklevel_input, "checklevel_output": option_checklevel_output, }?: { + function call(encode_http_method: ((http_method: type_http_method) => string), is_options_request: ((http_method: type_http_method) => boolean), decode_status_code: ((status_code_raw: int) => type_http_status_code), rest: type_rest, http_request: lib_plankton.http_base.type_request, { "checklevel_restriction": option_checklevel_restriction, "checklevel_input": option_checklevel_input, "checklevel_output": option_checklevel_output, "set_content_length": option_set_content_length, }?: { checklevel_restriction?: lib_plankton.api.enum_checklevel; checklevel_input?: lib_plankton.api.enum_checklevel; checklevel_output?: lib_plankton.api.enum_checklevel; + set_content_length?: boolean; }): Promise>; /** * @see https://swagger.io/specification/#openrest-object */ - function to_oas(http_request_method_to_oas: ((http_request_method: type_method) => string), has_body: ((method: type_method) => boolean), rest: type_rest, options?: { + function to_oas(http_request_method_to_oas: ((http_request_method: type_method) => string), has_body: ((method: type_method) => boolean), rest: type_rest, { "version": option_version, "servers": option_servers, }?: { version?: (null | string); servers?: Array; }): any; @@ -4420,10 +4409,11 @@ declare namespace lib_plankton.rest_caldav { * @todo check query paramater validity * @todo improve status code mapping */ - function call(rest: type_rest, http_request: lib_plankton.caldav.type_request, options?: { + function call(rest: type_rest, http_request: lib_plankton.caldav.type_request, { "checklevel_restriction": option_checklevel_restriction, "checklevel_input": option_checklevel_input, "checklevel_output": option_checklevel_output, "set_content_length": option_set_content_length, }?: { checklevel_restriction?: lib_plankton.api.enum_checklevel; checklevel_input?: lib_plankton.api.enum_checklevel; checklevel_output?: lib_plankton.api.enum_checklevel; + set_content_length?: boolean; }): Promise; /** * @see https://swagger.io/specification/#openrest-object @@ -4435,13 +4425,11 @@ declare namespace lib_plankton.rest_caldav { } declare namespace lib_plankton.server { /** - * @author fenris */ type type_metadata = { ip_address: string; }; /** - * @author fenris */ type type_subject = { host: string; @@ -4451,7 +4439,6 @@ declare namespace lib_plankton.server { serverobj: any; }; /** - * @author fenris */ function make(handle: ((input: string, metadata?: type_metadata) => Promise), options?: { host?: string; @@ -4459,17 +4446,14 @@ declare namespace lib_plankton.server { threshold?: (null | float); }): type_subject; /** - * @author fenris * @deprecated */ function make_old(port: int, handle: ((input: string, metadata?: type_metadata) => Promise)): type_subject; /** - * @author fenris * @see https://nodejs.org/api/net.html#serverlistenport-host-backlog-callback */ function start(subject: type_subject): Promise; /** - * @author fenris */ function kill(subject: type_subject): void; } diff --git a/lib/plankton/plankton.js b/lib/plankton/plankton.js index bda12c3..c5cc5ee 100644 --- a/lib/plankton/plankton.js +++ b/lib/plankton/plankton.js @@ -1,18 +1,3 @@ -var __extends = (this && this.__extends) || (function () { - var extendStatics = function (d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; - return extendStatics(d, b); - }; - return function (d, b) { - if (typeof b !== "function" && b !== null) - throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; -})(); /* This file is part of »bacterio-plankton:base«. @@ -115,7 +100,7 @@ along with »bacterio-plankton:base«. If not, see this.actions[id](information)); } - }; + } /** * @author frac */ - class_observer.prototype.rollout = function () { - var _this = this; - this.buffer.forEach(function (information) { return _this.notify(information, false); }); + rollout() { + this.buffer.forEach(information => this.notify(information, false)); this.buffer = []; - }; - return class_observer; -}()); + } +} /** * @author frac */ @@ -389,27 +369,23 @@ along with »bacterio-plankton:base«. If not, see x.toString()).join(",") + "]")); + } +} /* This file is part of »bacterio-plankton:base«. @@ -438,9 +414,8 @@ var lib_plankton; * * @author fenris */ - function get_current_timestamp(rounded) { - if (rounded === void 0) { rounded = false; } - var x = (Date.now() / 1000); + function get_current_timestamp(rounded = false) { + const x = (Date.now() / 1000); return (rounded ? Math.round(x) : x); ; } @@ -451,6 +426,24 @@ var lib_plankton; return Object.assign(core, mantle); } base.object_merge = object_merge; + /** + */ + function buffer_show(buffer, { "block_size": option_block_size = 20, "break_char": option_break_char = "\n", } = {}) { + let output = ""; + let count = 0; + // @ts-ignore + for (const entry of buffer) { + count = ((count + 1) % option_block_size); + output += ((typeof (entry) === "string") + ? + entry.charCodeAt(0) + : + entry).toString(16).toUpperCase().padStart(2, "0"); + output += ((count === 0) ? option_break_char : " "); + } + return output; + } + base.buffer_show = buffer_show; })(base = lib_plankton.base || (lib_plankton.base = {})); })(lib_plankton || (lib_plankton = {})); /* @@ -1427,6 +1420,20 @@ var lib_plankton; } } call.try_catch_wrap = try_catch_wrap; + /** + */ + function try_catch_wrap_async(get_value) { + return (get_value() + .then((value) => Promise.resolve({ + "value": value, + "error": null, + })) + .catch((reason) => Promise.resolve({ + "value": null, + "error": reason, + }))); + } + call.try_catch_wrap_async = try_catch_wrap_async; })(call = lib_plankton.call || (lib_plankton.call = {})); })(lib_plankton || (lib_plankton = {})); var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { @@ -1667,7 +1674,7 @@ var lib_plankton; var timestamp = (now / 1000); var datetime = (new Date(now)).toISOString(); return (JSON.stringify({ - "datetime_timestamp": timestamp.toFixed(0), + "datetime_timestamp": Math.round(timestamp), "datetime_string": datetime /*.slice(0, 19)*/, "level_numeric": entry.level, "level_name": log.level_show(entry.level, { "abbreviated": false }), @@ -12264,6 +12271,7 @@ var lib_plankton; ical.ics_encode = ics_encode; })(ical = lib_plankton.ical || (lib_plankton.ical = {})); })(lib_plankton || (lib_plankton = {})); +"use strict"; /* This file is part of »bacterio-plankton:http_base«. @@ -12307,7 +12315,6 @@ var lib_plankton; var http_base; (function (http_base) { /** - * @author fenris */ const linebreak = "\r\n"; /** @@ -12316,7 +12323,6 @@ var lib_plankton; return str.split("-").map(x => lib_plankton.string.capitalize(x)).join("-"); } /** - * @author fenris */ function encode_request(encode_method, request) { let request_raw = ""; @@ -12354,11 +12360,11 @@ var lib_plankton; } http_base.encode_request = encode_request; /** - * @author fenris */ function decode_request(decode_method, has_body, request_raw) { const lines = request_raw.split(linebreak); - const first = lines.shift(); + const first = lines[0]; + lines.shift(); const parts = first.split(" "); const method = decode_method(parts[0]); const path_and_query = parts[1]; @@ -12368,7 +12374,8 @@ var lib_plankton; const version = parts[2]; let headers = {}; while (true) { - const line = lines.shift(); + const line = lines[0]; + lines.shift(); if (line === "") { break; } @@ -12396,12 +12403,11 @@ var lib_plankton; } http_base.decode_request = decode_request; /** - * @author fenris */ function encode_response(encode_status_code, get_status_text, response) { let response_raw = ""; response_raw += lib_plankton.string.coin("{{version}} {{status_code}} {{status_text}}{{linebreak}}", { - "version": response.version, + "version": (response.version ?? ""), "status_code": encode_status_code(response.status_code), "status_text": get_status_text(response.status_code), "linebreak": linebreak, @@ -12414,23 +12420,29 @@ var lib_plankton; }); } response_raw += linebreak; - response_raw += response.body.toString(); + if (response.body === null) { + // do nothing + } + else { + response_raw += response.body.toString(); + } return response_raw; } http_base.encode_response = encode_response; /** - * @author fenris */ function decode_response(decode_status_code, response_raw) { const lines = response_raw.split(linebreak); - const first = lines.shift(); + const first = lines[0]; + lines.shift(); const first_parts = first.split(" "); const version = first_parts[0]; const status_code = decode_status_code(first_parts[1]); // first_parts.slice(2) ? probably irrelevant let headers = {}; while (true) { - const line = lines.shift(); + const line = lines[0]; + lines.shift(); if (line === "") { break; } @@ -12459,7 +12471,7 @@ var lib_plankton; async function call(has_body, encode_method, decode_status_code, request, { "timeout": option_timeout = 5.0, "follow_redirects": option_follow_redirects = false, "implementation": option_implementation = "fetch", } = {}) { const target = lib_plankton.string.coin("{{scheme}}://{{host}}{{path}}{{query}}", { "scheme": request.scheme, - "host": request.host, + "host": (request.host ?? ""), "path": request.path, "query": (request.query ?? ""), }); @@ -12551,7 +12563,9 @@ var lib_plankton; .request(target, { "method": request.method, "headers": request.headers, - }, (res) => { + }, + // @ts-ignore + (res) => { try { let response_body = ""; res.setEncoding("utf8"); @@ -12898,21 +12912,16 @@ GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with »bacterio-plankton:xml«. If not, see . */ -/** - * @author fenris - */ var lib_plankton; (function (lib_plankton) { var xml; (function (xml) { /** - * @author fenris */ function string_repeat(symbol, count) { return ((count <= 0) ? "" : (string_repeat(symbol, count - 1) + symbol)); } /** - * @author fenris */ var class_node = /** @class */ (function () { function class_node() { @@ -12921,12 +12930,10 @@ var lib_plankton; }()); xml.class_node = class_node; /** - * @author fenris */ var class_node_text = /** @class */ (function (_super) { __extends(class_node_text, _super); /** - * @author fenris */ function class_node_text(content) { var _this = _super.call(this) || this; @@ -12934,22 +12941,19 @@ var lib_plankton; return _this; } /** - * @author fenris */ class_node_text.prototype.compile = function (depth) { if (depth === void 0) { depth = 0; } - return (string_repeat("\t", depth) + this.content + "\n"); + return (string_repeat("\t", depth) + this.content /* + "\n"*/); }; return class_node_text; }(class_node)); xml.class_node_text = class_node_text; /** - * @author fenris */ var class_node_comment = /** @class */ (function (_super) { __extends(class_node_comment, _super); /** - * @author fenris */ function class_node_comment(content) { var _this = _super.call(this) || this; @@ -12957,7 +12961,6 @@ var lib_plankton; return _this; } /** - * @author fenris */ class_node_comment.prototype.compile = function (depth) { if (depth === void 0) { depth = 0; } @@ -12967,12 +12970,10 @@ var lib_plankton; }(class_node)); xml.class_node_comment = class_node_comment; /** - * @author fenris */ var class_node_complex = /** @class */ (function (_super) { __extends(class_node_complex, _super); /** - * @author fenris */ function class_node_complex(name, attributes, children) { if (attributes === void 0) { attributes = {}; } @@ -12984,7 +12985,6 @@ var lib_plankton; return _this; } /** - * @author fenris */ class_node_complex.prototype.compile = function (depth) { var _this = this; @@ -12994,9 +12994,24 @@ var lib_plankton; .filter(function (key) { return (_this.attributes[key] !== null); }) .map(function (key) { return (" " + key + "=" + ("\"" + _this.attributes[key] + "\"")); }) .join("")); - output += (string_repeat("\t", depth) + "<" + this.name + attributes + ">" + "\n"); - this.children.forEach(function (child) { return (output += child.compile(depth + 1)); }); - output += (string_repeat("\t", depth) + "" + "\n"); + if ((this.children.length === 1) + && + (this.children[0] instanceof class_node_text)) { + output += (string_repeat("\t", depth) + + + ("<" + this.name + attributes + ">") + + + this.children[0].compile(0) + + + ("") + + + "\n"); + } + else { + output += (string_repeat("\t", depth) + "<" + this.name + attributes + ">" + "\n"); + this.children.forEach(function (child) { return (output += child.compile(depth + 1)); }); + output += (string_repeat("\t", depth) + "" + "\n"); + } return output; }; return class_node_complex; @@ -13316,31 +13331,33 @@ var lib_plankton; /** */ function data_href_encode_xml(data_href) { - return (new lib_plankton.xml.class_node_complex("d:href", {}, [ + return (new lib_plankton.xml.class_node_complex("D:href", {}, [ new lib_plankton.xml.class_node_text(data_href), ])); } /** */ function data_status_encode_xml(data_status) { - return (new lib_plankton.xml.class_node_complex("d:status", {}, [ + return (new lib_plankton.xml.class_node_complex("D:status", {}, [ new lib_plankton.xml.class_node_text(data_status), ])); } /** */ function data_prop_encode_xml(data_prop) { - return (new lib_plankton.xml.class_node_complex(data_prop.name, {}, [ - new lib_plankton.xml.class_node_text(data_prop.value ?? ""), + return (new lib_plankton.xml.class_node_complex(("D:" + data_prop.name), {}, [ + new lib_plankton.xml.class_node_text(data_prop.value + ?? + ""), ])); } /** */ function data_propstat_encode_xml(data_propstat) { - return (new lib_plankton.xml.class_node_complex("d:propstat", { + return (new lib_plankton.xml.class_node_complex("D:propstat", { // todo xmlns:R }, [ - new lib_plankton.xml.class_node_complex("d:prop", { + new lib_plankton.xml.class_node_complex("D:prop", { // todo xmlns:R }, data_propstat.prop.map(data_prop_encode_xml)), data_status_encode_xml(data_propstat.status), @@ -13349,7 +13366,7 @@ var lib_plankton; /** */ function data_response_encode_xml(data_response) { - return (new lib_plankton.xml.class_node_complex("d:response", {}, ([ + return (new lib_plankton.xml.class_node_complex("D:response", {}, ([ data_href_encode_xml(data_response.href), ] .concat(("hrefs" in data_response.body) @@ -13362,12 +13379,14 @@ var lib_plankton; data_response.body.propstats.map(data_propstat_encode_xml))))); } /** + * @todo description */ function data_multistatus_encode_xml(data_multistatus) { - return (new lib_plankton.xml.class_node_complex("d:multistatus", { - "xmlns:d": "DAV:", - }, (data_multistatus.responses.map(data_response_encode_xml) - .concat()))); + return (new lib_plankton.xml.class_node_complex("D:multistatus", { + "xmlns:D": "DAV:", + "xmlns:C": "urn:ietf:params:xml:ns:caldav", + "xmlns:CS": "http://calendarserver.org/ns/", + }, data_multistatus.responses.map(data_response_encode_xml))); } /** */ @@ -13546,7 +13565,10 @@ var lib_plankton; case caldav.enum_method.move: return "MOVE"; case caldav.enum_method.lock: return "LOCK"; case caldav.enum_method.unlock: return "UNLOCK"; - default: throw (new Error("impossible")); + case caldav.enum_method.report: return "REPORT"; + case caldav.enum_method.mkcalendar: return "MKCALENDAR"; + case caldav.enum_method.acl: return "ACL"; + default: throw (new Error("unhandled method: " + method)); } } caldav.encode_method = encode_method; @@ -13568,6 +13590,9 @@ var lib_plankton; case "MOVE": return caldav.enum_method.move; case "LOCK": return caldav.enum_method.lock; case "UNLOCK": return caldav.enum_method.unlock; + case "REPORT": return caldav.enum_method.report; + case "MKCALENDAR": return caldav.enum_method.mkcalendar; + case "ACL": return caldav.enum_method.acl; default: throw (new Error("unhandled method: " + method_raw)); } } @@ -14240,7 +14265,7 @@ var lib_plankton; // do nothing } else { - lib_plankton.log.warning("rest_overwriting_path_parameter", { + lib_plankton.log.warning("plankton.rest_base.overwriting_path_parameter", { "key": routenode.sub_wildcard.name, "value_old": result.parameters[routenode.sub_wildcard.name], "value_new": path_head, @@ -14276,7 +14301,7 @@ var lib_plankton; // do nothing } else { - lib_plankton.log.warning("rest_overwriting_action", { + lib_plankton.log.warning("plankton.rest_base.overwriting_action", { "http_method": http_method, "steps": steps, }); @@ -14304,7 +14329,7 @@ var lib_plankton; if (!(routenode.sub_wildcard.name === wildcard_name)) { /* lib_plankton.log.warning( - "rest_overwriting_wildcard_node", + "plankton.rest_base.overwriting_wildcard_node", { "wildcard_name": wildcard_name, } @@ -14396,7 +14421,9 @@ var lib_plankton; ? http_request_body.toString() : null))), "response_body_mimetype": "application/json", - // TODO: no "from"? + /** + * @todo no "from"? + */ "response_body_encode": ((output) => Buffer["from"](JSON.stringify(output))), }, options); const steps = lib_plankton.string.split(path, "/").slice(1); @@ -14440,7 +14467,7 @@ var lib_plankton; // "input_shape": options.input_type, // "output_shape": options.output_type, }); - lib_plankton.log.debug("rest_route_added", { + lib_plankton.log.debug("plankton.rest_base.route_added", { "http_method": http_method, "path": path, // "routetree": rest.routetree, @@ -14451,18 +14478,22 @@ var lib_plankton; * @todo check request body mimetype? * @todo check query paramater validity */ - async function call(encode_http_method, decode_status_code, rest, http_request, { "checklevel_restriction": option_checklevel_restriction = lib_plankton.api.enum_checklevel.hard, "checklevel_input": option_checklevel_input = lib_plankton.api.enum_checklevel.soft, "checklevel_output": option_checklevel_output = lib_plankton.api.enum_checklevel.soft, } = {}) { - lib_plankton.log.info("rest_call", { - "http_request": { - "scheme": http_request.scheme, - "host": http_request.host, - "path": http_request.path, - "version": http_request.version, - "method": http_request.method, - "query": http_request.query, - "headers": http_request.headers, - "body": String(http_request.body), - } + async function call(encode_http_method, is_options_request, decode_status_code, rest, http_request, { "checklevel_restriction": option_checklevel_restriction = lib_plankton.api.enum_checklevel.hard, "checklevel_input": option_checklevel_input = lib_plankton.api.enum_checklevel.soft, "checklevel_output": option_checklevel_output = lib_plankton.api.enum_checklevel.soft, "set_content_length": option_set_content_length = false, } = {}) { + lib_plankton.log.info("plankton.rest_base.call_request", { + "scheme": http_request.scheme, + "host": http_request.host, + "path": http_request.path, + "version": http_request.version, + "method": http_request.method, + "query": http_request.query, + "headers": http_request.headers, + "body": ((http_request.body === null) + ? + null + : + lib_plankton.string.limit(http_request.body.toString(), { + "length": 200, + })), }); // parse target and query parameters // const url_stuff : URL = new URL("http://dummy" + http_request.target); @@ -14480,6 +14511,7 @@ var lib_plankton; // resolve const stuff = routenode_path_read(rest.routetree, steps); const allowed_methods = (Object.keys(stuff.routenode.operations) + .map(x => x.toUpperCase()) .join(", ")); // get version let version; @@ -14510,58 +14542,84 @@ var lib_plankton; } const additional_response_headers = (rest.set_access_control_headers ? { - "Access-Control-Allow-Headers": (([ - "Content-Type", - "X-Api-Key", - ] - .concat((rest.versioning_header_name !== null) - ? [rest.versioning_header_name] - : []) - .concat((rest.authentication.kind === "key_header") - ? [rest.authentication.parameters["name"]] - : [])) - .join(", ")), + /* + "Access-Control-Allow-Headers": ( + ( + [ + "Content-Type", + "X-Api-Key", + ] + .concat( + (rest.versioning_header_name !== null) + ? [rest.versioning_header_name] + : [] + ) + .concat( + (rest.authentication.kind === "key_header") + ? [rest.authentication.parameters["name"]] + : [] + ) + ) + .join(", ") + ), + */ "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Methods": allowed_methods, + // "Access-Control-Allow-Methods": allowed_methods, } : {}); + let response; if (stuff.rest.length > 0) { - return { - "version": "HTTP/1.1", + response = { + "version": http_request.version, "status_code": decode_status_code(404), "headers": {}, "body": null, }; } else { - if (http_request.method === "OPTIONS") { - return { - "version": "HTTP/1.1", - "status_code": decode_status_code(200), - "headers": Object.assign({}, additional_response_headers), + /*if (is_options_request(http_request.method)) { + response = { + "version": http_request.version, + "status_code": decode_status_code(option_options_response_status_code), + "headers": Object.assign( + { + }, + additional_response_headers + ), "body": null, }; } - else { + else*/ { const http_method_encoded = encode_http_method(http_request.method).toLowerCase(); if (!(http_method_encoded in stuff.routenode.operations)) { - if (Object.keys(stuff.routenode.operations).length <= 0) { - return { - "version": "HTTP/1.1", - "status_code": decode_status_code(404), + // fallback OPTIONS response + if (is_options_request(http_request.method)) { + response = { + "version": http_request.version, + "status_code": decode_status_code(200), "headers": Object.assign({}, additional_response_headers), "body": null, }; } else { - return { - "version": "HTTP/1.1", - "status_code": decode_status_code(405), - "headers": Object.assign({ - "Allow": allowed_methods, - }, additional_response_headers), - "body": null, - }; + if (Object.keys(stuff.routenode.operations).length <= 0) { + return { + "version": http_request.version, + "status_code": decode_status_code(404), + "headers": Object.assign({}, additional_response_headers), + "body": null, + }; + } + else { + return { + "version": http_request.version, + "status_code": decode_status_code(405), + "headers": Object.assign({ + "Allow": allowed_methods, + }, additional_response_headers), + "body": null, + }; + } } } else { @@ -14582,18 +14640,15 @@ var lib_plankton; ?? null))), }; - /* - const allowed : boolean = ( + /*const allowed : boolean = ( (operation.restriction === null) ? true : operation.restriction(stuff_) ); - */ - let response; - /* + if (! allowed) { lib_plankton.log.error( - "rest_access_denied", + "plankton.rest_base.access_denied", { "http_request": { "target": http_request.target, @@ -14601,14 +14656,21 @@ var lib_plankton; "headers": http_request.headers, "body": ( (http_request.body === null) - ? null - : lib_plankton.string.limit(http_request.body.toString(), {"length": 200}) + ? + null + : + lib_plankton.string.limit( + http_request.body.toString(), + { + "length": 200, + } + ) ), }, } ); response = { - "version": "HTTP/1.1", + "version": http_request.version, "status_code": 403, "headers": Object.assign( { @@ -14619,36 +14681,29 @@ var lib_plankton; }; } else*/ { - try { - result = await lib_plankton.api.call(rest.api, operation.action_name, { - "version": stuff_.version, - "environment": { - "headers": stuff_.headers, - "path_parameters": stuff_.path_parameters, - "query_parameters": stuff_.query_parameters, - }, - "input": stuff_.input, - "checklevel_restriction": option_checklevel_restriction, - "checklevel_input": option_checklevel_input, - "checklevel_output": option_checklevel_output, - }); - error = null; - } - catch (error_) { - result = null; - error = error_; - } + const { "value": result, "error": error } = await lib_plankton.call.try_catch_wrap_async(() => lib_plankton.api.call(rest.api, operation.action_name, { + "version": stuff_.version, + "environment": { + "headers": stuff_.headers, + "path_parameters": stuff_.path_parameters, + "query_parameters": stuff_.query_parameters, + }, + "input": stuff_.input, + "checklevel_restriction": option_checklevel_restriction, + "checklevel_input": option_checklevel_input, + "checklevel_output": option_checklevel_output, + })); if ((result === null) || (error !== null)) { if (error instanceof lib_plankton.api.class_error_permission_denied) { response = { - "version": "HTTP/1.1", + "version": http_request.version, "status_code": decode_status_code(403), "headers": Object.assign({}, additional_response_headers), "body": null, }; } else { - lib_plankton.log.error("rest_execution_failed", { + lib_plankton.log.error("plankton.rest_base.execution_failed", { "http_request": { "version": http_request.version, "scheme": http_request.scheme, @@ -14657,53 +14712,82 @@ var lib_plankton; "query": http_request.query, "headers": http_request.headers, "body": ((http_request.body === null) - ? null - : lib_plankton.string.limit(http_request.body.toString(), { "length": 200 })), - }, - "error": { - "message": error.toString(), - "file_name": error.fileName, - "line_number": error.lineNumber, - "stack": error.stack, + ? + null + : + lib_plankton.string.limit(http_request.body.toString(), { + "length": 200 + })), }, + "error": ((error === null) + ? + null + : + { + "message": error.toString(), + "file_name": error.fileName, + "line_number": error.lineNumber, + "stack": error.stack, + }), }); response = { - "version": "HTTP/1.1", + "version": http_request.version, "status_code": decode_status_code(500), "headers": Object.assign({}, additional_response_headers), - "body": Buffer["from"]("internal error"), + // @ts-ignore + "body": Buffer.from("internal error"), }; } } else { // encode + const body = operation.response_body_encode(result.data); response = { - "version": "HTTP/1.1", + "version": http_request.version, "status_code": decode_status_code(result.status_code), - "headers": Object.assign({ - "Content-Type": operation.response_body_mimetype, - }, additional_response_headers), - "body": operation.response_body_encode(result.data), + "headers": Object.assign({}, additional_response_headers, ((body !== null) + ? + { + "Content-Type": operation.response_body_mimetype, + } + : + {}), ((option_set_content_length + && + (body !== null)) + ? + // @ts-ignore + { "Content-Length": body.length } + : + {}), (result.extra_headers ?? {})), + "body": body, }; } } - return response; } } } + lib_plankton.log.info("plankton.rest_base.call_response", { + "version": response.version, + "status_code": response.status_code, + "headers": response.headers, + "body": ((response.body === null) + ? + null + : + lib_plankton.string.limit(response.body.toString(), { + "length": 200, + })), + }); + return response; } } rest_base.call = call; /** * @see https://swagger.io/specification/#openrest-object */ - function to_oas(http_request_method_to_oas, has_body, rest, options = {}) { - options = lib_plankton.object.patched({ - "version": null, - "servers": [], - }, options); + function to_oas(http_request_method_to_oas, has_body, rest, { "version": option_version = null, "servers": option_servers = [], } = {}) { const subject = rest; - const version = (options.version ?? "-"); + const version = (option_version ?? "-"); return { "openapi": "3.0.3", "info": { @@ -14711,7 +14795,7 @@ var lib_plankton; "title": (rest.api.title ?? "API"), // "description": (rest.api.description ?? undefined), }, - "servers": options.servers.map(url => ({ "url": url })), + "servers": option_servers.map(url => ({ "url": url })), "components": { "securitySchemes": (((description) => ({ "none": {}, @@ -14783,7 +14867,7 @@ var lib_plankton; })), ]), // query parameters - operation.query_parameters(options.version).map((query_parameter) => ({ + operation.query_parameters(option_version).map((query_parameter) => ({ "name": query_parameter.name, "in": "query", "required": query_parameter.required, @@ -14799,7 +14883,7 @@ var lib_plankton; [ operation.request_body_mimetype, { - "schema": operation.input_schema(options.version), + "schema": operation.input_schema(option_version), } ] ]) @@ -14811,7 +14895,7 @@ var lib_plankton; [ operation.response_body_mimetype, { - "schema": operation.output_schema(options.version), + "schema": operation.output_schema(option_version), } ] ]), @@ -14914,8 +14998,13 @@ var lib_plankton; * @todo check query paramater validity * @todo improve status code mapping */ - async function call(rest, http_request, options = {}) { - return lib_plankton.rest_base.call(lib_plankton.caldav.encode_method, (x => x), rest, http_request, options); + async function call(rest, http_request, { "checklevel_restriction": option_checklevel_restriction = lib_plankton.api.enum_checklevel.hard, "checklevel_input": option_checklevel_input = lib_plankton.api.enum_checklevel.soft, "checklevel_output": option_checklevel_output = lib_plankton.api.enum_checklevel.soft, "set_content_length": option_set_content_length = false, } = {}) { + return lib_plankton.rest_base.call(lib_plankton.caldav.encode_method, (x => (x === lib_plankton.caldav.enum_method.options)), (x => x), rest, http_request, { + "checklevel_restriction": option_checklevel_restriction, + "checklevel_input": option_checklevel_input, + "checklevel_output": option_checklevel_output, + "set_content_length": option_set_content_length, + }); } rest_caldav.call = call; /** @@ -14951,7 +15040,6 @@ var lib_plankton; var server; (function (server) { /** - * @author fenris */ function make(handle, options = {}) { options = Object.assign({ @@ -14969,7 +15057,6 @@ var lib_plankton; } server.make = make; /** - * @author fenris * @deprecated */ function make_old(port, handle) { @@ -14981,7 +15068,6 @@ var lib_plankton; } server.make_old = make_old; /** - * @author fenris * @see https://nodejs.org/api/net.html#serverlistenport-host-backlog-callback */ function start(subject) { @@ -15002,20 +15088,21 @@ var lib_plankton; "ip_address": socket.remoteAddress, }; */ - lib_plankton.log.debug("server_process_input", { - "input": input, + lib_plankton.log.debug("plankton.server.process_input", { + "input": lib_plankton.base.buffer_show(input, { "break_char": "|" }), }); (subject.handle(input /*, metadata*/) .then((output) => { - lib_plankton.log.debug("server_writing", { - "output": output, + lib_plankton.log.debug("plankton.server.writing", { + "output": lib_plankton.base.buffer_show(output, { "break_char": "|" }), }); socket.write(output); socket.end(); }) .catch((error) => { - lib_plankton.log.warning("server_handle_failed", { + lib_plankton.log.warning("plankton.server.handle_failed", { "error": error.toString(), + "stack_trace": error.stack, }); // socket.write(""); socket.end(); @@ -15029,7 +15116,7 @@ var lib_plankton; // do nothing } else { - lib_plankton.log.debug("server_timeout_cancelling"); + lib_plankton.log.debug("plankton.server.timeout_cancelling"); clearTimeout(timeout_handler); timeout_handler = null; } @@ -15041,21 +15128,25 @@ var lib_plankton; else { if (timeout_handler === null) { timeout_handler = setTimeout(() => { - lib_plankton.log.debug("server_timeout_reached"); + lib_plankton.log.debug("plankton.server.timeout_reached"); timeout_handler = null; process_input(); }, (subject.threshold * 1000)); } else { - lib_plankton.log.warning("server_timeout_already_started"); + lib_plankton.log.warning("plankton.server.timeout_already_started"); // do nothing } } }; - lib_plankton.log.info("server_client connected", {}); + lib_plankton.log.info("plankton.server.client_connected"); socket.on("data", (input_chunk_raw) => { - lib_plankton.log.debug("server_reading_chunk", { - "chunk_raw": input_chunk_raw, + lib_plankton.log.debug("plankton.server.reading_chunk", { + "chunk_raw": ((input_chunk_raw instanceof Buffer) + ? + lib_plankton.base.buffer_show(input_chunk_raw, { "break_char": "|" }) + : + input_chunk_raw), }); timeout_stop(); const input_chunk = ((input_chunk_raw instanceof Buffer) @@ -15069,12 +15160,12 @@ var lib_plankton; }); socket.on("end", () => { if (!ended) { - lib_plankton.log.info("server_client_disconnected", {}); + lib_plankton.log.info("plankton.server.client_disconnected"); ended = true; timeout_stop(); } else { - lib_plankton.log.info("server_socket_already_ended"); + lib_plankton.log.info("plankton.server.socket_already_ended"); // do nothing } }); @@ -15084,7 +15175,7 @@ var lib_plankton; process.stderr.write("net_error: " + String(error) + "\n\n"); }); subject.serverobj.listen(subject.port, subject.host, 511, () => { - lib_plankton.log.info("server_listenting", { + lib_plankton.log.info("plankton.server.listenting", { "host": subject.host, "port": subject.port, }); @@ -15094,11 +15185,10 @@ var lib_plankton; } server.start = start; /** - * @author fenris */ function kill(subject) { subject.serverobj.close(); - lib_plankton.log.info("server_stopped", {}); + lib_plankton.log.info("plankton.server.stopped"); } server.kill = kill; })(server = lib_plankton.server || (lib_plankton.server = {})); diff --git a/source/api/actions/caldav_probe.ts b/source/api/actions/caldav_probe.ts index 58b80e2..f6f886d 100644 --- a/source/api/actions/caldav_probe.ts +++ b/source/api/actions/caldav_probe.ts @@ -9,8 +9,8 @@ namespace _zeitbild.api ) : void { register< - any, - any + null, + string >( rest_subject, lib_plankton.caldav.enum_method.propfind, @@ -42,56 +42,82 @@ namespace _zeitbild.api "nullable": false, "type": "string", }), - "response_body_mimetype": "application/xml", + "response_body_mimetype": "text/xml; charset=utf-8", "response_body_encode": output => Buffer.from(output), + // "restriction": restriction_basic_auth, "restriction": restriction_none, /** * @todo examine body */ "execution": async (stuff) => { - return Promise.resolve( - { - "status_code": 207, - "data": ( - /*"\n" - +*/ - lib_plankton.webdav.data_multistatus_encode( - { - "responses": [ + const user_id : (null | type_user_id) = await _zeitbild.api.web_auth( + stuff.headers["Authorization"] + ?? + stuff.headers["authorization"] + ?? + null + ); + if (user_id === null) { + return Promise.resolve( + { + "status_code": 401, + "data": "", + "extra_headers": { + "WWW-Authenticate": "Basic realm=Restricted", + } + } + ); + } + else { + return ( + _zeitbild.service.calendar.overview(user_id) + .then( + (data_raw) => Promise.resolve( + data_raw + .map( + (entry) => ({ + "id": entry.id, + "name": entry.name, + "access_level": _zeitbild.value_object.access_level.to_string(entry.access_level), + }) + ) + ) + ) + .then( + (data) => Promise.resolve({ + "status_code": 207, + "data": ( + "\n" + + + lib_plankton.webdav.data_multistatus_encode( { - "href": "/caldav/events", - "body": { - "propstats": [ - { - "prop": [ - {"name": "d:displayname", "value": "default"}, - // {"name": "cs:getctag", "value": "47"}, // TODO correct value - // {"name": "current-user-privilege-set", "value": ""}, - /* - "uid", - "dtstamp", - "dtstart", - "dtend", - "summary", - "description", - "url", - "location", - */ - ], - "status": "HTTP/2.0 200 OK", - "description": null, + "responses": [ + { + "href": "/caldav/events", + "body": { + "propstats": data.map( + (entry) => ({ + "prop": [ + {"name": "displayname", "value": entry.name}, + // {"name": "cs:getctag", "value": "47"}, // TODO correct value + // {"name": "current-user-privilege-set", "value": ""}, + ], + "status": "HTTP/2.0 200 OK", + "description": entry.access_level, + }) + ), }, - ] - }, + "description": null, + } + ], "description": null, } - ], - "description": null, - } - ) - ), - } - ); + ) + ), + }) + ) + ); + } } } ); diff --git a/source/api/actions/caldav_sniff.ts b/source/api/actions/caldav_sniff.ts new file mode 100644 index 0000000..efee5c9 --- /dev/null +++ b/source/api/actions/caldav_sniff.ts @@ -0,0 +1,46 @@ + +namespace _zeitbild.api +{ + + /** + */ + export function register_caldav_sniff( + rest_subject : lib_plankton.rest_caldav.type_rest + ) : void + { + register< + any, + null + >( + rest_subject, + lib_plankton.caldav.enum_method.options, + "/caldav", + { + "restriction": restriction_none, + "execution": async (stuff) => { + const permitted : boolean = await restriction_basic_auth(stuff); + if (! permitted) { + return Promise.resolve( + { + "status_code": 401, + "data": null, + "extra_headers": { + "WWW-Authenticate": "Basic realm=Restricted", + } + } + ); + } + else { + return Promise.resolve( + { + "status_code": 200, + "data": null + } + ); + } + } + } + ); + } + +} diff --git a/source/api/base.ts b/source/api/base.ts index 4f85f52..8039c23 100644 --- a/source/api/base.ts +++ b/source/api/base.ts @@ -25,6 +25,77 @@ namespace _zeitbild.api } + /** + * @todo outsource? + */ + export async function web_auth( + authorization_string : (null | string) + ) : Promise<(null | _zeitbild.type_user_id)> + { + if (authorization_string === null) { + return Promise.resolve<(null | _zeitbild.type_user_id)>(null); + } + else { + const parts : Array = authorization_string.split(" "); + const strategy : string = parts[0]; + const data_raw : string = parts.slice(1).join(" "); + switch (strategy) { + default: { + lib_plankton.log.notice( + "zeitbild.web_auth.unhandled_strategy", + { + "strategy": strategy, + } + ); + return Promise.resolve<(null | _zeitbild.type_user_id)>(null); + break; + } + case "Basic": { + const data_raw_decoded : string = lib_plankton.base64.decode(data_raw); + const parts_ : Array = data_raw_decoded.split(":"); + const username : string = parts_[0]; + const password_is : string = parts_.slice(1).join(":"); + const {"value": user_id, "error": error} = await lib_plankton.call.try_catch_wrap_async<_zeitbild.type_user_id>( + () => _zeitbild.service.user.identify(username) + ); + if (error !== null) { + lib_plankton.log.notice( + "zeitbild.web_auth.unknown_user", + { + "username": username, + } + ); + return Promise.resolve<(null | _zeitbild.type_user_id)>(null); + } + else { + const password_shall : string = lib_plankton.sha256.get( + username, + _zeitbild.conf.get()["misc"]["auth_salt"] + ); + if (! (password_is === password_shall)) { + /** + * @todo remove + */ + lib_plankton.log.notice( + "zeitbild.web_auth.wrong_pasword", + { + "shall": password_shall, + "is": password_is, + } + ); + return Promise.resolve<(null | _zeitbild.type_user_id)>(null); + } + else { + return Promise.resolve<(null | _zeitbild.type_user_id)>(user_id); + } + } + break; + } + } + } + } + + /** */ export const restriction_logged_in : lib_plankton.rest_caldav.type_restriction = ( @@ -36,6 +107,26 @@ namespace _zeitbild.api ); + /** + */ + export const restriction_basic_auth : lib_plankton.rest_caldav.type_restriction = ( + (stuff) => ( + web_auth( + stuff.headers["Authorization"] + ?? + stuff.headers["authorization"] + ?? + null + ) + .then( + (user_id) => Promise.resolve( + (user_id !== null) + ) + ) + ) + ); + + /** */ export const restriction_none : lib_plankton.rest_caldav.type_restriction = ( diff --git a/source/api/functions.ts b/source/api/functions.ts index 8344792..3b346bb 100644 --- a/source/api/functions.ts +++ b/source/api/functions.ts @@ -52,6 +52,7 @@ namespace _zeitbild.api } // caldav { + _zeitbild.api.register_caldav_sniff(rest_subject); _zeitbild.api.register_caldav_probe(rest_subject); _zeitbild.api.register_caldav_get(rest_subject); } diff --git a/source/conf.ts b/source/conf.ts index ebb56d7..a94484e 100644 --- a/source/conf.ts +++ b/source/conf.ts @@ -41,7 +41,17 @@ namespace _zeitbild.conf "error" ], "default": "info" - } + }, + "format": { + "nullable": false, + "type": "string", + "enum": [ + "human_readable", + "jsonl", + "jsonl_structured", + ], + "default": "human_readable", + }, }, "required": [ ] diff --git a/source/main.ts b/source/main.ts index 9acebe2..72c91ce 100644 --- a/source/main.ts +++ b/source/main.ts @@ -273,11 +273,31 @@ async function main( "kind": "std", "data": { "target": "stdout", - "format": { - "kind": "human_readable", - "data": { + "format": lib_plankton.call.distinguish( + { + "kind": log_output.data.format, + "data": null, + }, + { + "jsonl": () => ({ + "kind": "jsonl", + "data": { + "structured": false, + } + }), + "jsonl_structured": () => ({ + "kind": "jsonl", + "data": { + "structured": true, + } + }), + "human_readable": () => ({ + "kind": "human_readable", + "data": { + } + }), } - } + ), } }, "threshold": log_output.data.threshold, @@ -419,6 +439,7 @@ async function main( "checklevel_restriction": lib_plankton.api.enum_checklevel.hard, // "checklevel_input": lib_plankton.api.enum_checklevel.soft, // "checklevel_output": lib_plankton.api.enum_checklevel.soft, + "set_content_length": false, } ); const output : string = lib_plankton.caldav.encode_response(http_response); @@ -448,6 +469,7 @@ async function main( ) .catch( (error) => { + // console.error(error); process.stderr.write(String(error) + "\n"); } ) diff --git a/source/services/calendar.ts b/source/services/calendar.ts index 8262b68..28686f4 100644 --- a/source/services/calendar.ts +++ b/source/services/calendar.ts @@ -348,7 +348,13 @@ namespace _zeitbild.service.calendar { } ); - const ics_raw : string = http_response.body.toString(); + const ics_raw : string = ( + (http_response.body === null) + ? + "" + : + http_response.body.toString() + ); const vcalendar_list : Array = lib_plankton.ical.ics_decode_multi( ics_raw, { diff --git a/tools/makefile b/tools/makefile index ebedf93..6d5ca33 100644 --- a/tools/makefile +++ b/tools/makefile @@ -76,6 +76,7 @@ ${dir_temp}/zeitbild-unlinked.js: \ ${dir_source}/api/actions/calendar_event_remove.ts \ ${dir_source}/api/actions/events.ts \ ${dir_source}/api/actions/export_ical.ts \ + ${dir_source}/api/actions/caldav_sniff.ts \ ${dir_source}/api/actions/caldav_probe.ts \ ${dir_source}/api/actions/caldav_get.ts \ ${dir_source}/api/functions.ts \