Skip to content

Commit e3cf8b3

Browse files
committed
Vendor preferredMediaType() function
Removed dependency on @std/http
1 parent f522d04 commit e3cf8b3

3 files changed

Lines changed: 137 additions & 3 deletions

File tree

fedify/deno.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
"@opentelemetry/semantic-conventions": "npm:@opentelemetry/semantic-conventions@^1.27.0",
2626
"@phensley/language-tag": "npm:@phensley/language-tag@^1.9.0",
2727
"@std/assert": "jsr:@std/assert@^0.226.0",
28-
"@std/http": "jsr:@std/http@^1.0.6",
2928
"@std/testing": "jsr:@std/testing@^0.224.0",
3029
"@std/url": "jsr:@std/url@^0.225.1",
3130
"@std/yaml": "jsr:@std/yaml@^0.224.3",

fedify/federation/handler.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { getLogger } from "@logtape/logtape";
22
import type { Span, TracerProvider } from "@opentelemetry/api";
33
import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
4-
import { accepts } from "@std/http/negotiation";
54
import metadata from "../deno.json" with { type: "json" };
65
import type { DocumentLoader } from "../runtime/docloader.ts";
76
import { verifyRequest } from "../sig/http.ts";
@@ -33,9 +32,11 @@ import { type InboxListenerSet, routeActivity } from "./inbox.ts";
3332
import { KvKeyCache } from "./keycache.ts";
3433
import type { KvKey, KvStore } from "./kv.ts";
3534
import type { MessageQueue } from "./mq.ts";
35+
import { preferredMediaTypes } from "./negotiation.ts";
3636

3737
export function acceptsJsonLd(request: Request): boolean {
38-
const types = accepts(request);
38+
const accept = request.headers.get("Accept");
39+
const types = accept ? preferredMediaTypes(accept) : ["*/*"];
3940
if (types == null) return true;
4041
if (types[0] === "text/html" || types[0] === "application/xhtml+xml") {
4142
return false;

fedify/federation/negotiation.ts

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
/*!
2+
* Adapted directly from negotiator at https://github.com/jshttp/negotiator/
3+
* which is licensed as follows:
4+
*
5+
* (The MIT License)
6+
*
7+
* Copyright (c) 2012-2014 Federico Romero
8+
* Copyright (c) 2012-2014 Isaac Z. Schlueter
9+
* Copyright (c) 2014-2015 Douglas Christopher Wilson
10+
*
11+
* Permission is hereby granted, free of charge, to any person obtaining
12+
* a copy of this software and associated documentation files (the
13+
* 'Software'), to deal in the Software without restriction, including
14+
* without limitation the rights to use, copy, modify, merge, publish,
15+
* distribute, sublicense, and/or sell copies of the Software, and to
16+
* permit persons to whom the Software is furnished to do so, subject to
17+
* the following conditions:
18+
*
19+
* The above copyright notice and this permission notice shall be
20+
* included in all copies or substantial portions of the Software.
21+
*
22+
* THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
23+
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24+
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
25+
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
26+
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
27+
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
28+
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29+
*/
30+
31+
export interface Specificity {
32+
i: number;
33+
o: number | undefined;
34+
q: number;
35+
s: number | undefined;
36+
}
37+
38+
function compareSpecs(a: Specificity, b: Specificity): number {
39+
return (
40+
b.q - a.q ||
41+
(b.s ?? 0) - (a.s ?? 0) ||
42+
(a.o ?? 0) - (b.o ?? 0) ||
43+
a.i - b.i ||
44+
0
45+
);
46+
}
47+
48+
function isQuality(spec: Specificity): boolean {
49+
return spec.q > 0;
50+
}
51+
52+
interface MediaTypeSpecificity extends Specificity {
53+
type: string;
54+
subtype: string;
55+
params: { [param: string]: string | undefined };
56+
}
57+
58+
const simpleMediaTypeRegExp = /^\s*([^\s\/;]+)\/([^;\s]+)\s*(?:;(.*))?$/;
59+
60+
function splitKeyValuePair(str: string): [string, string | undefined] {
61+
const [key, value] = str.split("=");
62+
return [key!.toLowerCase(), value];
63+
}
64+
65+
function parseMediaType(
66+
str: string,
67+
i: number,
68+
): MediaTypeSpecificity | undefined {
69+
const match = simpleMediaTypeRegExp.exec(str);
70+
71+
if (!match) {
72+
return;
73+
}
74+
75+
const [, type, subtype, parameters] = match;
76+
if (!type || !subtype) {
77+
return;
78+
}
79+
80+
const params: { [param: string]: string | undefined } = Object.create(null);
81+
let q = 1;
82+
if (parameters) {
83+
const kvps = parameters.split(";").map((p) => p.trim()).map(
84+
splitKeyValuePair,
85+
);
86+
87+
for (const [key, val] of kvps) {
88+
const value = val && val[0] === `"` && val[val.length - 1] === `"`
89+
? val.slice(1, val.length - 1)
90+
: val;
91+
92+
if (key === "q" && value) {
93+
q = parseFloat(value);
94+
break;
95+
}
96+
97+
params[key] = value;
98+
}
99+
}
100+
101+
return { type, subtype, params, i, o: undefined, q, s: undefined };
102+
}
103+
104+
function parseAccept(accept: string): MediaTypeSpecificity[] {
105+
const accepts = accept.split(",").map((p) => p.trim());
106+
107+
const mediaTypes: MediaTypeSpecificity[] = [];
108+
for (const [index, accept] of accepts.entries()) {
109+
const mediaType = parseMediaType(accept.trim(), index);
110+
111+
if (mediaType) {
112+
mediaTypes.push(mediaType);
113+
}
114+
}
115+
116+
return mediaTypes;
117+
}
118+
119+
function getFullType(spec: MediaTypeSpecificity) {
120+
return `${spec.type}/${spec.subtype}`;
121+
}
122+
123+
export function preferredMediaTypes(
124+
accept?: string | null,
125+
): string[] {
126+
const accepts = parseAccept(accept === undefined ? "*/*" : accept ?? "");
127+
128+
return accepts
129+
.filter(isQuality)
130+
.sort(compareSpecs)
131+
.map(getFullType);
132+
}
133+
134+
// cSpell: ignore Schlueter kvps

0 commit comments

Comments
 (0)