@rapidmx/autodiscover-plugin 1.0.0-beta.1 → 1.0.0-beta.2

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/README.md CHANGED
@@ -2,11 +2,11 @@
2
2
 
3
3
  [![CI](https://github.com/RapidMX/autodiscover/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/RapidMX/autodiscover/actions/workflows/build.yml)
4
4
  [![Coverage Status](https://coveralls.io/repos/github/RapidMX/autodiscover/badge.svg?branch=main)](https://coveralls.io/github/RapidMX/autodiscover?branch=main)
5
- [![npm version](https://img.shields.io/npm/v/@rapidmx/autodiscover)](https://www.npmjs.com/package/@rapidmx/autodiscover)
5
+ [![npm version](https://img.shields.io/npm/v/@rapidmx/autodiscover-plugin)](https://www.npmjs.com/package/@rapidmx/autodiscover-plugin)
6
6
 
7
7
  Autodiscover support for a [RapidMX](https://rapidmx.io) mail server. Lets a real mail client find this
8
- deployment's [`@rapidmx/activesync`](https://github.com/RapidMX/activesync) (EAS) and
9
- [`@rapidmx/mapi`](https://github.com/RapidMX/mapi) server URLs from just an email address: classic
8
+ deployment's [`@rapidmx/activesync-plugin`](https://github.com/RapidMX/activesync) (EAS) and
9
+ [`@rapidmx/mapi-plugin`](https://github.com/RapidMX/mapi) server URLs from just an email address: classic
10
10
  POX (`POST /autodiscover/autodiscover.xml`, serving either the EAS-only MobileSync response or, when a real
11
11
  Outlook desktop client requests it via `AcceptableResponseSchema`, an Outlook/EXCH response pointing at the
12
12
  MAPI/HTTP endpoint) and the modern JSON variant Microsoft calls "Autodiscover v2"
@@ -32,20 +32,20 @@ server is mounted — an ops/deployment task, not something this package configu
32
32
  * Mailbox resolution against both `Mailbox.primarySmtpAddress` and `Mailbox.aliasAddresses`
33
33
  * Deliberately unauthenticated by design — reveals only deployment-wide server URLs, never a per-mailbox
34
34
  secret
35
- * MongoDB and SQL persistence backends (`@rapidmx/autodiscover/mongo`, `@rapidmx/autodiscover/sql`)
35
+ * MongoDB and SQL persistence backends (`@rapidmx/autodiscover-plugin/mongo`, `@rapidmx/autodiscover-plugin/sql`)
36
36
 
37
37
  ## Installation
38
38
 
39
39
  ### NPM
40
40
 
41
41
  ```
42
- npm i @rapidmx/autodiscover
42
+ npm i @rapidmx/autodiscover-plugin
43
43
  ```
44
44
 
45
45
  ### Yarn
46
46
 
47
47
  ```
48
- yarn add @rapidmx/autodiscover
48
+ yarn add @rapidmx/autodiscover-plugin
49
49
  ```
50
50
 
51
51
  ## Requirements
@@ -62,11 +62,11 @@ The following peer dependencies are required:
62
62
 
63
63
  ## Usage
64
64
 
65
- Mount `AutodiscoverRouteMongo`/`AutodiscoverRouteSQL` (from `@rapidmx/autodiscover/mongo` or `/sql`) with a
65
+ Mount `AutodiscoverRouteMongo`/`AutodiscoverRouteSQL` (from `@rapidmx/autodiscover-plugin/mongo` or `/sql`) with a
66
66
  one-line subclass supplying your deployment's EAS and MAPI URLs:
67
67
 
68
68
  ```ts
69
- import { AutodiscoverRouteMongo } from "@rapidmx/autodiscover/mongo";
69
+ import { AutodiscoverRouteMongo } from "@rapidmx/autodiscover-plugin/mongo";
70
70
  import { RouteDecorators } from "@rapidrest/service-core";
71
71
  const { Route } = RouteDecorators;
72
72
 
@@ -11,15 +11,115 @@
11
11
  * This follows the same "hand-build the wire format precisely, no library" approach Phase 2's WBXML codec
12
12
  * already took, just for text XML instead of binary.
13
13
  */
14
- const EMAIL_ELEMENT_PATTERN = /<[\w:]*EMailAddress[^>]*>\s*([^<]*?)\s*<\/[\w:]*EMailAddress>/i;
14
+ /** Reads an XML name (`[\w:.-]*`) starting at `start`, returning it lower-cased plus the index just past it. */
15
+ function readTagName(xml, start) {
16
+ let end = start;
17
+ while (end < xml.length && /[\w:.-]/.test(xml[end])) {
18
+ end++;
19
+ }
20
+ return { name: xml.slice(start, end).toLowerCase(), end };
21
+ }
22
+ /** Whether a (lower-cased) tag name is `localName`, optionally namespace-prefixed (`ns:localName`). */
23
+ function isElement(name, localName) {
24
+ return name === localName || name.endsWith(`:${localName}`);
25
+ }
26
+ const COMMENT_OPEN = "<!--";
27
+ const COMMENT_CLOSE = "-->";
28
+ const CDATA_OPEN = "<![CDATA[";
29
+ const CDATA_CLOSE = "]]>";
30
+ /**
31
+ * If a `<!-- -->` comment or `<![CDATA[...]]>` section starts at `lt`, returns the index just past its end
32
+ * (`-1` when it's unterminated) plus the CDATA section's verbatim content; `undefined` for anything else.
33
+ */
34
+ function skipCommentOrCdata(xml, lt) {
35
+ const comment = xml.startsWith(COMMENT_OPEN, lt);
36
+ if (!comment && !xml.startsWith(CDATA_OPEN, lt)) {
37
+ return undefined;
38
+ }
39
+ const [open, close] = comment ? [COMMENT_OPEN, COMMENT_CLOSE] : [CDATA_OPEN, CDATA_CLOSE];
40
+ const closeAt = xml.indexOf(close, lt + open.length);
41
+ if (closeAt === -1) {
42
+ return { end: -1 };
43
+ }
44
+ return { end: closeAt + close.length, cdata: comment ? undefined : xml.slice(lt + open.length, closeAt) };
45
+ }
46
+ /** Returns the index of the first non-whitespace character at or after `start`. */
47
+ function skipWhitespace(xml, start) {
48
+ let end = start;
49
+ while (end < xml.length && /\s/.test(xml[end])) {
50
+ end++;
51
+ }
52
+ return end;
53
+ }
54
+ /**
55
+ * Returns the text content of the first `<localName>text</localName>` element (namespace prefix and attributes
56
+ * allowed, case-insensitive, whitespace allowed before the closing tag's `>`) whose content is plain text:
57
+ * entities are decoded, `<![CDATA[...]]>` sections are unwrapped verbatim and `<!-- -->` comments are dropped.
58
+ * Comments/CDATA outside such an element are skipped whole, so a commented-out element never matches. A single
59
+ * forward, linear-time scan - deliberately NOT a regex: this runs on an unauthenticated request body, and the
60
+ * previous `\s*([^<]*?)\s*<\/...>` pattern backtracked cubically on an unterminated element padded with
61
+ * whitespace. Every `indexOf` below starts past the previous one, so no character is rescanned more than a
62
+ * constant number of times.
63
+ */
64
+ function extractElementText(xml, localName) {
65
+ const target = localName.toLowerCase();
66
+ let pos = 0;
67
+ while ((pos = xml.indexOf("<", pos)) !== -1) {
68
+ const special = skipCommentOrCdata(xml, pos);
69
+ if (special) {
70
+ if (special.end === -1) {
71
+ return undefined;
72
+ }
73
+ pos = special.end;
74
+ continue;
75
+ }
76
+ const open = readTagName(xml, pos + 1);
77
+ pos = open.end;
78
+ if (!isElement(open.name, target)) {
79
+ continue;
80
+ }
81
+ const gt = xml.indexOf(">", pos);
82
+ if (gt === -1) {
83
+ return undefined;
84
+ }
85
+ pos = gt + 1;
86
+ if (xml[gt - 1] === "/") {
87
+ continue; // Self-closing, so no text content.
88
+ }
89
+ const parts = [];
90
+ let lt;
91
+ while ((lt = xml.indexOf("<", pos)) !== -1) {
92
+ parts.push(decodeXmlEntities(xml.slice(pos, lt)));
93
+ const special = skipCommentOrCdata(xml, lt);
94
+ if (!special) {
95
+ break;
96
+ }
97
+ if (special.end === -1) {
98
+ return undefined;
99
+ }
100
+ parts.push(special.cdata ?? "");
101
+ pos = special.end;
102
+ }
103
+ if (lt === -1) {
104
+ return undefined;
105
+ }
106
+ if (xml[lt + 1] === "/") {
107
+ const close = readTagName(xml, lt + 2);
108
+ if (isElement(close.name, target) && xml[skipWhitespace(xml, close.end)] === ">") {
109
+ return parts.join("");
110
+ }
111
+ }
112
+ // A child element or a mismatched close tag, so not plain text content: resume the outer scan here.
113
+ pos = lt;
114
+ }
115
+ return undefined;
116
+ }
15
117
  /**
16
- * Extracts the request's `<EMailAddress>` text content via a small, tightly-scoped regex rather than a DOM
118
+ * Extracts the request's `<EMailAddress>` text content via a small, linear-time tag scan rather than a DOM
17
119
  * parse. Never resolves entities/DTDs - only the literal text between the open/close tags is ever inspected.
18
120
  */
19
121
  export function extractEmailAddress(xml) {
20
- const match = EMAIL_ELEMENT_PATTERN.exec(xml);
21
- const value = match?.[1]?.trim();
22
- return value ? decodeXmlEntities(value) : undefined;
122
+ return extractElementText(xml, "EMailAddress")?.trim() || undefined;
23
123
  }
24
124
  /** Escapes the 5 XML-predefined-entity characters for safe inclusion as element text content. */
25
125
  export function escapeXml(value) {
@@ -38,19 +138,16 @@ function decodeXmlEntities(value) {
38
138
  .replace(/&apos;/g, "'")
39
139
  .replace(/&amp;/g, "&");
40
140
  }
41
- const ACCEPTABLE_RESPONSE_SCHEMA_PATTERN = /<[\w:]*AcceptableResponseSchema[^>]*>\s*([^<]*?)\s*<\/[\w:]*AcceptableResponseSchema>/i;
42
141
  /** The `AcceptableResponseSchema` value a real Outlook desktop client sends to request the Outlook/EXCH
43
142
  * response shape (`buildOutlookSuccessXml`) instead of the EAS-only MobileSync one - confirmed against
44
143
  * `[MS-OXDSCLI]`'s own Autodiscover Response XSD, whose target namespace is exactly this value. */
45
144
  export const OUTLOOK_RESPONSE_SCHEMA = "http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a";
46
145
  /**
47
- * Extracts the request's `<AcceptableResponseSchema>` text content via the same small, tightly-scoped regex
48
- * approach as `extractEmailAddress` - never a DOM parse, same rationale (unauthenticated, internet-facing
49
- * endpoint).
146
+ * Extracts the request's `<AcceptableResponseSchema>` text content via the same linear-time tag scan as
147
+ * `extractEmailAddress` - never a DOM parse, same rationale (unauthenticated, internet-facing endpoint).
50
148
  */
51
149
  export function extractAcceptableResponseSchema(xml) {
52
- const match = ACCEPTABLE_RESPONSE_SCHEMA_PATTERN.exec(xml);
53
- return match?.[1]?.trim() || undefined;
150
+ return extractElementText(xml, "AcceptableResponseSchema")?.trim() || undefined;
54
151
  }
55
152
  /**
56
153
  * Builds a real `mobilesync:Response` success document per `[MS-ASCMD]`'s `AutodiscoverMobileSync.xsd` -
@@ -1 +1 @@
1
- {"version":3,"file":"AutodiscoverXml.js","sourceRoot":"","sources":["../../src/AutodiscoverXml.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,mEAAmE;AACnE,mCAAmC;AACnC,+EAA+E;AAE/E;;;;;;;;GAQG;AAEH,MAAM,qBAAqB,GAAG,gEAAgE,CAAC;AAE/F;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,GAAW;IAC3C,MAAM,KAAK,GAAG,qBAAqB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC9C,MAAM,KAAK,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;IACjC,OAAO,KAAK,CAAC,CAAC,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AACxD,CAAC;AAED,iGAAiG;AACjG,MAAM,UAAU,SAAS,CAAC,KAAa;IACnC,OAAO,KAAK;SACP,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AACjC,CAAC;AAED,SAAS,iBAAiB,CAAC,KAAa;IACpC,OAAO,KAAK;SACP,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC;SACrB,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC;SACrB,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC;SACvB,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC;SACvB,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;AAChC,CAAC;AAQD,MAAM,kCAAkC,GACpC,wFAAwF,CAAC;AAE7F;;mGAEmG;AACnG,MAAM,CAAC,MAAM,uBAAuB,GAAG,iFAAiF,CAAC;AAEzH;;;;GAIG;AACH,MAAM,UAAU,+BAA+B,CAAC,GAAW;IACvD,MAAM,KAAK,GAAG,kCAAkC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC3D,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,SAAS,CAAC;AAC3C,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAAC,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,EAA0B;IAC5F,MAAM,KAAK,GAAG,SAAS,CAAC,YAAY,CAAC,CAAC;IACtC,MAAM,GAAG,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;IAC9B,MAAM,kBAAkB,GAAG,WAAW;QAClC,CAAC,CAAC,2CAA2C,SAAS,CAAC,WAAW,CAAC,6BAA6B;QAChG,CAAC,CAAC,EAAE,CAAC;IACT,OAAO;;;;6BAIkB,kBAAkB;yCACN,KAAK;;;;;;wCAMN,GAAG;yCACF,GAAG;;;;;gBAK5B,CAAC;AACjB,CAAC;AAQD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,sBAAsB,CAAC,EAAE,YAAY,EAAE,WAAW,EAAE,OAAO,EAA8B;IACrG,MAAM,KAAK,GAAG,SAAS,CAAC,YAAY,CAAC,CAAC;IACtC,MAAM,GAAG,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC;IAC/B,MAAM,IAAI,GAAG,SAAS,CAAC,WAAW,IAAI,YAAY,CAAC,CAAC;IACpD,MAAM,QAAQ,GAAG,SAAS,CACtB,uFAAuF,YAAY,EAAE,CACxG,CAAC;IACF,OAAO;;uBAEY,uBAAuB;;2BAEnB,IAAI;wBACP,QAAQ;uCACO,KAAK;;;;;;;;;mCAST,GAAG;mCACH,GAAG;;;;;gBAKtB,CAAC;AACjB,CAAC"}
1
+ {"version":3,"file":"AutodiscoverXml.js","sourceRoot":"","sources":["../../src/AutodiscoverXml.ts"],"names":[],"mappings":"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"}
@@ -15,14 +15,20 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
15
15
  // SPDX-License-Identifier: MPL-2.0
16
16
  ///////////////////////////////////////////////////////////////////////////////
17
17
  import { ObjectDecorators } from "@rapidrest/core";
18
- import { RepoUtils, RouteDecorators } from "@rapidrest/service-core";
18
+ import { ModelUtils, RepoUtils, RouteDecorators } from "@rapidrest/service-core";
19
19
  import { buildOutlookSuccessXml, buildPoxSuccessXml, extractAcceptableResponseSchema, extractEmailAddress, OUTLOOK_RESPONSE_SCHEMA, } from "./AutodiscoverXml.js";
20
20
  import { PluginRegistry } from "@rapidmx/restapi";
21
21
  const { Config, Init, Logger } = ObjectDecorators;
22
22
  /** The plugin packages whose endpoints Autodiscover advertises. */
23
- export const ACTIVESYNC_PLUGIN = "@rapidmx/activesync";
24
- export const MAPI_PLUGIN = "@rapidmx/mapi";
23
+ export const ACTIVESYNC_PLUGIN = "@rapidmx/activesync-plugin";
24
+ export const MAPI_PLUGIN = "@rapidmx/mapi-plugin";
25
25
  const { Get, Param, Post, Query, Request, Response } = RouteDecorators;
26
+ /** The largest POX request body (in bytes) `pox()` will scan; anything bigger gets a `413`. */
27
+ export const MAX_POX_BODY_BYTES = 16 * 1024;
28
+ /** The longest email address looked up (RFC 5321's 254-character forward-path limit). */
29
+ export const MAX_ADDRESS_LENGTH = 254;
30
+ /** A single plain `local@domain` address: nothing the search query parser could read as an operator or a list. */
31
+ const PLAIN_ADDRESS_PATTERN = /^[^\s@(),"\\]+@[^\s@(),"\\]+$/;
26
32
  /**
27
33
  * Abstract base for the two Autodiscover endpoints a real mail client uses to find this deployment's EAS
28
34
  * server URL from just an email address - classic POX (`POST /autodiscover/autodiscover.xml`, per
@@ -51,7 +57,7 @@ const { Get, Param, Post, Query, Request, Response } = RouteDecorators;
51
57
  *
52
58
  * `mailboxClass` is supplied by the Mongo/SQL concrete subclasses following the exact one-line-per-backend
53
59
  * pattern used throughout this library. The advertised URLs are built from the `mail:autodiscover:public_url`
54
- * setting, and each protocol is only advertised while its plugin (`@rapidmx/activesync`, `@rapidmx/mapi`) is
60
+ * setting, and each protocol is only advertised while its plugin (`@rapidmx/activesync-plugin`, `@rapidmx/mapi-plugin`) is
55
61
  * loaded, so a client is never pointed at an endpoint this deployment doesn't serve.
56
62
  *
57
63
  * @author Jean-Philippe Steinmetz
@@ -74,31 +80,75 @@ export class BaseAutodiscoverRoute {
74
80
  * Builds the query value used to match `Mailbox.aliasAddresses` against the given address. See
75
81
  * `BaseMailIngestRoute.aliasQueryValue()`'s identical doc comment - same Mongo-array vs.
76
82
  * SQL-`simple-json`-column backend split, same override point (`AutodiscoverRouteSQL` overrides this
77
- * identically to `MailIngestRouteSQL`).
83
+ * identically to `MailIngestRouteSQL`). The base form is a `ModelUtils.literal()` exact match.
78
84
  */
79
85
  aliasQueryValue(address) {
80
- return address;
86
+ return ModelUtils.literal(address);
87
+ }
88
+ /**
89
+ * Normalizes `publicUrl` into a base URL (origin plus path, no trailing slash), or `undefined` when it's
90
+ * unset or unsafe to advertise: it must parse, use `https:` (plain `http:` is only tolerated for a loopback
91
+ * host, for local development), and carry no credentials, query string or fragment - any of which would
92
+ * produce a broken or insecure endpoint URL once a path is appended.
93
+ */
94
+ get baseUrl() {
95
+ const value = this.publicUrl.trim();
96
+ if (!value) {
97
+ return undefined;
98
+ }
99
+ let url;
100
+ try {
101
+ url = new URL(value);
102
+ }
103
+ catch {
104
+ return undefined;
105
+ }
106
+ const loopback = ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
107
+ if (url.protocol !== "https:" && !(url.protocol === "http:" && loopback)) {
108
+ return undefined;
109
+ }
110
+ // Checked on the raw string: `URL.search`/`hash` are empty for a bare trailing `?`/`#`.
111
+ if (value.includes("?") || value.includes("#") || url.username || url.password) {
112
+ return undefined;
113
+ }
114
+ return `${url.origin}${url.pathname.replace(/\/+$/, "")}`;
81
115
  }
82
116
  endpointUrl(plugin, path) {
83
- if (!this.publicUrl || !PluginRegistry.isActive(plugin)) {
117
+ const base = this.baseUrl;
118
+ if (!base || !PluginRegistry.isActive(plugin)) {
84
119
  return undefined;
85
120
  }
86
- return `${this.publicUrl.replace(/\/+$/, "")}${path}`;
121
+ return `${base}${path}`;
87
122
  }
88
123
  async init() {
89
- if (!this.publicUrl) {
124
+ if (!this.publicUrl.trim()) {
90
125
  this.logger?.warn("Autodiscover has no mail:autodiscover:public_url set, so it can't point clients at any protocol.");
91
126
  }
127
+ else if (!this.baseUrl) {
128
+ this.logger?.warn("Autodiscover's mail:autodiscover:public_url is not a valid https:// URL without a query string or " +
129
+ "fragment, so it can't point clients at any protocol.");
130
+ }
92
131
  this.mailboxRepo = await this._objectFactory.newInstance(RepoUtils, {
93
132
  name: this.mailboxClass.name,
94
133
  args: [this.mailboxClass],
95
134
  });
96
135
  }
97
- async resolveMailbox(email) {
136
+ /**
137
+ * Normalizes an unauthenticated caller's email value into a single plain address, or `undefined` when it isn't
138
+ * one. service-core's search query parser interprets `op(value)` strings (`like(*)`, `regex(^a)`, `in(a,b)`,
139
+ * `ne(x)`, ...), so an arbitrary value reaching `find()` would let an anonymous caller enumerate the mailbox
140
+ * directory; rejecting whitespace, parentheses, commas, quotes and backslashes (plus RFC 5321's 254-character
141
+ * length cap) up front means no query is ever issued for such a value.
142
+ */
143
+ normalizeAddress(email) {
98
144
  const address = email.trim().toLowerCase();
145
+ return address.length <= MAX_ADDRESS_LENGTH && PLAIN_ADDRESS_PATTERN.test(address) ? address : undefined;
146
+ }
147
+ /** Looks up a mailbox by an already-`normalizeAddress()`ed address, always as a `ModelUtils.literal()` value. */
148
+ async resolveMailbox(address) {
99
149
  const [byPrimary, byAlias] = await Promise.all([
100
- this.mailboxRepo.find({ primarySmtpAddress: address }, { ignoreACL: true, limit: 1 }),
101
- this.mailboxRepo.find({ aliasAddresses: this.aliasQueryValue(address) }, { ignoreACL: true, limit: 1 }),
150
+ this.mailboxRepo.find({ primarySmtpAddress: ModelUtils.literal(address), limit: 1 }, { ignoreACL: true, limit: 1 }),
151
+ this.mailboxRepo.find({ aliasAddresses: this.aliasQueryValue(address), limit: 1 }, { ignoreACL: true, limit: 1 }),
102
152
  ]);
103
153
  return byPrimary[0] ?? byAlias[0];
104
154
  }
@@ -118,8 +168,15 @@ export class BaseAutodiscoverRoute {
118
168
  res.status(500).send();
119
169
  return;
120
170
  }
171
+ // A real Autodiscover request is well under 1KB; refuse to even scan anything larger on this
172
+ // unauthenticated endpoint.
173
+ if (req.rawBody && req.rawBody.length > MAX_POX_BODY_BYTES) {
174
+ res.status(413).send();
175
+ return;
176
+ }
121
177
  const body = req.rawBody ? req.rawBody.toString("utf-8") : "";
122
- const email = extractEmailAddress(body);
178
+ const extracted = extractEmailAddress(body);
179
+ const email = extracted ? this.normalizeAddress(extracted) : undefined;
123
180
  if (!email) {
124
181
  res.status(400).send();
125
182
  return;
@@ -136,9 +193,11 @@ export class BaseAutodiscoverRoute {
136
193
  res.status(404).send();
137
194
  return;
138
195
  }
196
+ // The display name is never disclosed to this anonymous caller (see the class doc): the address they
197
+ // already supplied stands in for it, which keeps the Outlook schema's required `DisplayName` populated.
139
198
  const xml = outlook
140
- ? buildOutlookSuccessXml({ emailAddress: email, displayName: mailbox.displayName, mapiUrl: url })
141
- : buildPoxSuccessXml({ emailAddress: email, displayName: mailbox.displayName, easUrl: url });
199
+ ? buildOutlookSuccessXml({ emailAddress: email, displayName: email, mapiUrl: url })
200
+ : buildPoxSuccessXml({ emailAddress: email, displayName: email, easUrl: url });
142
201
  res.setHeader("Content-Type", "application/xml; charset=utf-8").status(200).send(xml);
143
202
  }
144
203
  /**
@@ -160,7 +219,9 @@ export class BaseAutodiscoverRoute {
160
219
  });
161
220
  return;
162
221
  }
163
- const mailbox = await this.resolveMailbox(decodeURIComponent(email));
222
+ // The router has already percent-decoded path params; decoding again would throw (-> 500) on a literal `%`.
223
+ const address = this.normalizeAddress(email);
224
+ const mailbox = address ? await this.resolveMailbox(address) : undefined;
164
225
  if (!mailbox) {
165
226
  res.status(404).json({
166
227
  ErrorCode: "UserNotFound",
@@ -1 +1 @@
1
- {"version":3,"file":"BaseAutodiscoverRoute.js","sourceRoot":"","sources":["../../src/BaseAutodiscoverRoute.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"BaseAutodiscoverRoute.js","sourceRoot":"","sources":["../../src/BaseAutodiscoverRoute.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Extracts the request's `<EMailAddress>` text content via a small, tightly-scoped regex rather than a DOM
2
+ * Extracts the request's `<EMailAddress>` text content via a small, linear-time tag scan rather than a DOM
3
3
  * parse. Never resolves entities/DTDs - only the literal text between the open/close tags is ever inspected.
4
4
  */
5
5
  export declare function extractEmailAddress(xml: string): string | undefined;
@@ -15,9 +15,8 @@ export interface AutodiscoverPoxSuccess {
15
15
  * `[MS-OXDSCLI]`'s own Autodiscover Response XSD, whose target namespace is exactly this value. */
16
16
  export declare const OUTLOOK_RESPONSE_SCHEMA = "http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a";
17
17
  /**
18
- * Extracts the request's `<AcceptableResponseSchema>` text content via the same small, tightly-scoped regex
19
- * approach as `extractEmailAddress` - never a DOM parse, same rationale (unauthenticated, internet-facing
20
- * endpoint).
18
+ * Extracts the request's `<AcceptableResponseSchema>` text content via the same linear-time tag scan as
19
+ * `extractEmailAddress` - never a DOM parse, same rationale (unauthenticated, internet-facing endpoint).
21
20
  */
22
21
  export declare function extractAcceptableResponseSchema(xml: string): string | undefined;
23
22
  /**
@@ -1,8 +1,12 @@
1
1
  import { HttpRequest, HttpResponse } from "@rapidrest/service-core";
2
2
  import { Mailbox } from "@rapidmx/restapi";
3
3
  /** The plugin packages whose endpoints Autodiscover advertises. */
4
- export declare const ACTIVESYNC_PLUGIN = "@rapidmx/activesync";
5
- export declare const MAPI_PLUGIN = "@rapidmx/mapi";
4
+ export declare const ACTIVESYNC_PLUGIN = "@rapidmx/activesync-plugin";
5
+ export declare const MAPI_PLUGIN = "@rapidmx/mapi-plugin";
6
+ /** The largest POX request body (in bytes) `pox()` will scan; anything bigger gets a `413`. */
7
+ export declare const MAX_POX_BODY_BYTES: number;
8
+ /** The longest email address looked up (RFC 5321's 254-character forward-path limit). */
9
+ export declare const MAX_ADDRESS_LENGTH = 254;
6
10
  /**
7
11
  * Abstract base for the two Autodiscover endpoints a real mail client uses to find this deployment's EAS
8
12
  * server URL from just an email address - classic POX (`POST /autodiscover/autodiscover.xml`, per
@@ -31,7 +35,7 @@ export declare const MAPI_PLUGIN = "@rapidmx/mapi";
31
35
  *
32
36
  * `mailboxClass` is supplied by the Mongo/SQL concrete subclasses following the exact one-line-per-backend
33
37
  * pattern used throughout this library. The advertised URLs are built from the `mail:autodiscover:public_url`
34
- * setting, and each protocol is only advertised while its plugin (`@rapidmx/activesync`, `@rapidmx/mapi`) is
38
+ * setting, and each protocol is only advertised while its plugin (`@rapidmx/activesync-plugin`, `@rapidmx/mapi-plugin`) is
35
39
  * loaded, so a client is never pointed at an endpoint this deployment doesn't serve.
36
40
  *
37
41
  * @author Jean-Philippe Steinmetz
@@ -52,11 +56,27 @@ export declare abstract class BaseAutodiscoverRoute<M extends Mailbox> {
52
56
  * Builds the query value used to match `Mailbox.aliasAddresses` against the given address. See
53
57
  * `BaseMailIngestRoute.aliasQueryValue()`'s identical doc comment - same Mongo-array vs.
54
58
  * SQL-`simple-json`-column backend split, same override point (`AutodiscoverRouteSQL` overrides this
55
- * identically to `MailIngestRouteSQL`).
59
+ * identically to `MailIngestRouteSQL`). The base form is a `ModelUtils.literal()` exact match.
56
60
  */
57
61
  protected aliasQueryValue(address: string): any;
62
+ /**
63
+ * Normalizes `publicUrl` into a base URL (origin plus path, no trailing slash), or `undefined` when it's
64
+ * unset or unsafe to advertise: it must parse, use `https:` (plain `http:` is only tolerated for a loopback
65
+ * host, for local development), and carry no credentials, query string or fragment - any of which would
66
+ * produce a broken or insecure endpoint URL once a path is appended.
67
+ */
68
+ private get baseUrl();
58
69
  private endpointUrl;
59
70
  init(): Promise<void>;
71
+ /**
72
+ * Normalizes an unauthenticated caller's email value into a single plain address, or `undefined` when it isn't
73
+ * one. service-core's search query parser interprets `op(value)` strings (`like(*)`, `regex(^a)`, `in(a,b)`,
74
+ * `ne(x)`, ...), so an arbitrary value reaching `find()` would let an anonymous caller enumerate the mailbox
75
+ * directory; rejecting whitespace, parentheses, commas, quotes and backslashes (plus RFC 5321's 254-character
76
+ * length cap) up front means no query is ever issued for such a value.
77
+ */
78
+ private normalizeAddress;
79
+ /** Looks up a mailbox by an already-`normalizeAddress()`ed address, always as a `ModelUtils.literal()` value. */
60
80
  private resolveMailbox;
61
81
  /**
62
82
  * Classic POX Autodiscover. Per Microsoft's own client-behavior documentation, a permanent failure is just
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rapidmx/autodiscover-plugin",
3
- "version": "1.0.0-beta.1",
3
+ "version": "1.0.0-beta.2",
4
4
  "description": "A RapidMX plugin that provides Autodiscover protocol support.",
5
5
  "repository": "https://github.com/RapidMX/autodiscover.git",
6
6
  "author": "RapidREST <rapidrests@gmail.com>",
@@ -24,12 +24,12 @@
24
24
  "peerDependencies": {
25
25
  "@rapidmx/restapi": "0.x",
26
26
  "@rapidrest/core": "5.x",
27
- "@rapidrest/service-core": "2.x"
27
+ "@rapidrest/service-core": "^2.x"
28
28
  },
29
29
  "devDependencies": {
30
- "@rapidmx/restapi": "^0.9.0",
30
+ "@rapidmx/restapi": "^0.10.0",
31
31
  "@rapidrest/core": "^5.2.2",
32
- "@rapidrest/service-core": "^2.0.0",
32
+ "@rapidrest/service-core": "^2.1.0",
33
33
  "@swc/core": "^1.16.2",
34
34
  "@types/nconf": "^0.10.7",
35
35
  "@types/node": "^26.5.0",
@@ -99,7 +99,11 @@
99
99
  "type": "string",
100
100
  "help": "The address clients reach this server at, for example https://mail.example.com. Nothing is advertised until this is set."
101
101
  }
102
- ]
102
+ ],
103
+ "requires": {
104
+ "@rapidmx/activesync-plugin": "^1.0.0-beta.2",
105
+ "@rapidmx/mapi-plugin": "^1.0.0-beta.3"
106
+ }
103
107
  }
104
108
  }
105
109
  }