Attic is a cozy space with lofty ambitions.
attic.social
1// Copyright 2018-2026 the Deno authors. MIT license.
2/*!
3 * Adapted directly from negotiator at https://github.com/jshttp/negotiator/
4 * which is licensed as follows:
5 *
6 * (The MIT License)
7 *
8 * Copyright (c) 2012-2014 Federico Romero
9 * Copyright (c) 2012-2014 Isaac Z. Schlueter
10 * Copyright (c) 2014-2015 Douglas Christopher Wilson
11 *
12 * Permission is hereby granted, free of charge, to any person obtaining
13 * a copy of this software and associated documentation files (the
14 * 'Software'), to deal in the Software without restriction, including
15 * without limitation the rights to use, copy, modify, merge, publish,
16 * distribute, sublicense, and/or sell copies of the Software, and to
17 * permit persons to whom the Software is furnished to do so, subject to
18 * the following conditions:
19 *
20 * The above copyright notice and this permission notice shall be
21 * included in all copies or substantial portions of the Software.
22 *
23 * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
24 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
26 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
27 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
28 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
29 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 */
31
32import { compareSpecs, isQuality, type Specificity } from "./common.ts";
33
34interface EncodingSpecificity extends Specificity {
35 encoding?: string;
36}
37
38const simpleEncodingRegExp = /^\s*([^\s;]+)\s*(?:;(.*))?$/;
39
40function parseEncoding(
41 str: string,
42 i: number,
43): EncodingSpecificity | undefined {
44 const match = simpleEncodingRegExp.exec(str);
45 if (!match) {
46 return undefined;
47 }
48
49 const encoding = match[1]!;
50 let q = 1;
51 if (match[2]) {
52 const params = match[2].split(";");
53 for (const param of params) {
54 const p = param.trim().split("=");
55 if (p[0] === "q" && p[1]) {
56 q = parseFloat(p[1]);
57 break;
58 }
59 }
60 }
61
62 return { encoding, o: undefined, q, i, s: undefined };
63}
64
65function specify(
66 encoding: string,
67 spec: EncodingSpecificity,
68 i = -1,
69): Specificity | undefined {
70 if (!spec.encoding) {
71 return;
72 }
73 let s = 0;
74 if (spec.encoding.toLowerCase() === encoding.toLowerCase()) {
75 s = 1;
76 } else if (spec.encoding !== "*") {
77 return;
78 }
79
80 return {
81 i,
82 o: spec.i,
83 q: spec.q,
84 s,
85 };
86}
87
88function parseAcceptEncoding(accept: string): EncodingSpecificity[] {
89 const accepts = accept.split(",");
90 const parsedAccepts: EncodingSpecificity[] = [];
91 let hasIdentity = false;
92 let minQuality = 1;
93
94 for (const [i, accept] of accepts.entries()) {
95 const encoding = parseEncoding(accept.trim(), i);
96
97 if (encoding) {
98 parsedAccepts.push(encoding);
99 hasIdentity = hasIdentity || !!specify("identity", encoding);
100 minQuality = Math.min(minQuality, encoding.q || 1);
101 }
102 }
103
104 if (!hasIdentity) {
105 parsedAccepts.push({
106 encoding: "identity",
107 o: undefined,
108 q: minQuality,
109 i: accepts.length - 1,
110 s: undefined,
111 });
112 }
113
114 return parsedAccepts;
115}
116
117function getEncodingPriority(
118 encoding: string,
119 accepted: Specificity[],
120 index: number,
121): Specificity {
122 let priority: Specificity = { o: -1, q: 0, s: 0, i: 0 };
123
124 for (const s of accepted) {
125 const spec = specify(encoding, s, index);
126
127 if (
128 spec &&
129 (priority.s! - spec.s! || priority.q - spec.q ||
130 priority.o! - spec.o!) <
131 0
132 ) {
133 priority = spec;
134 }
135 }
136
137 return priority;
138}
139
140/** Given an `Accept-Encoding` string, parse out the encoding returning a
141 * negotiated encoding based on the `provided` encodings otherwise just a
142 * prioritized array of encodings. */
143export function preferredEncodings(
144 accept: string,
145 provided?: string[],
146): string[] {
147 const accepts = parseAcceptEncoding(accept);
148
149 if (!provided) {
150 return accepts
151 .filter(isQuality)
152 .sort(compareSpecs)
153 .map((spec) => spec.encoding!);
154 }
155
156 const priorities = provided.map((type, index) =>
157 getEncodingPriority(type, accepts, index)
158 );
159
160 return priorities
161 .filter(isQuality)
162 .sort(compareSpecs)
163 .map((priority) => provided[priorities.indexOf(priority)]!);
164}