@oxy.so/federation 1.0.0 → 1.0.1

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.
@@ -132,6 +132,8 @@ function parseUpstreamProfileUrl(candidateUrl, networks = Object.values(exports.
132
132
  }
133
133
  if (url.protocol !== 'https:' && url.protocol !== 'http:')
134
134
  return undefined;
135
+ if (url.username || url.password || url.port)
136
+ return undefined;
135
137
  const host = (0, apUri_1.canonicalFederationHost)(url.hostname);
136
138
  for (const network of networks) {
137
139
  if (!network.profileHosts.some((allowed) => (0, apUri_1.canonicalFederationHost)(allowed) === host))
@@ -188,6 +190,8 @@ function profileUrlHandle(href, allowedHosts, pathPrefix) {
188
190
  }
189
191
  if (url.protocol !== 'https:' && url.protocol !== 'http:')
190
192
  return undefined;
193
+ if (url.username || url.password || url.port)
194
+ return undefined;
191
195
  const host = (0, apUri_1.canonicalFederationHost)(url.hostname);
192
196
  if (!allowedHosts.some((allowed) => (0, apUri_1.canonicalFederationHost)(allowed) === host))
193
197
  return undefined;
@@ -201,8 +205,19 @@ function profileUrlHandle(href, allowedHosts, pathPrefix) {
201
205
  return decodeURIComponent(segments[pathPrefix.length]);
202
206
  }
203
207
  /** Every `href="…"` in a sanitized field value, in document order. */
204
- function fieldHrefs(value) {
208
+ function fieldHrefs(value, requireRelMe = false) {
205
209
  const hrefs = [];
210
+ if (requireRelMe) {
211
+ for (const anchor of value.matchAll(/<a\b([^>]*)>/gi)) {
212
+ const rel = /\brel\s*=\s*(?:"([^"]*)"|'([^']*)')/i.exec(anchor[1]);
213
+ if (!(rel?.[1] ?? rel?.[2] ?? '').toLowerCase().split(/\s+/).includes('me'))
214
+ continue;
215
+ const href = /\bhref\s*=\s*(?:"([^"]*)"|'([^']*)')/i.exec(anchor[1]);
216
+ if (href)
217
+ hrefs.push(href[1] ?? href[2]);
218
+ }
219
+ return hrefs;
220
+ }
206
221
  const pattern = /href="([^"]*)"/gi;
207
222
  let match = pattern.exec(value);
208
223
  while (match !== null) {
@@ -221,16 +236,29 @@ function upstreamHandleFromProfileField(options) {
221
236
  const wanted = options.fieldName.toLowerCase();
222
237
  const prefix = options.pathPrefix ?? [];
223
238
  return (candidate) => {
239
+ const handles = new Map();
224
240
  for (const field of candidate.fields) {
225
241
  if (field.name.trim().toLowerCase() !== wanted)
226
242
  continue;
227
- for (const href of fieldHrefs(field.value)) {
228
- const handle = profileUrlHandle(href, options.hosts, prefix);
243
+ for (const href of fieldHrefs(field.value, options.requireRelMe)) {
244
+ // Observed bird.makeup Official assertion, 2026-09-13:
245
+ // https://https://twitter.com/jordievole. Repair exactly one duplicated
246
+ // HTTPS prefix, then run the ordinary host/path/credential validation.
247
+ const candidateHref = options.repairRepeatedHttpsScheme && href.startsWith('https://https://')
248
+ ? href.slice('https://'.length) : href;
249
+ let handle;
250
+ try {
251
+ handle = profileUrlHandle(candidateHref, options.hosts, prefix);
252
+ }
253
+ catch {
254
+ continue;
255
+ }
229
256
  if (handle !== undefined && handle.length > 0)
230
- return handle;
257
+ handles.set(handle.toLowerCase(), handle);
231
258
  }
232
259
  }
233
- return undefined;
260
+ // Conflicting source assertions cannot be settled by document order.
261
+ return handles.size === 1 ? handles.values().next().value : undefined;
234
262
  };
235
263
  }
236
264
  /**