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