@marianmeres/safe-html 0.2.0

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/AGENTS.md ADDED
@@ -0,0 +1,92 @@
1
+ # @marianmeres/safe-html — Agent Guide
2
+
3
+ ## Quick Reference
4
+
5
+ - **What**: `html` tagged template with contextual escaping + helpers (`attrs`, `url`,
6
+ `srcset`, `jsonScript`, `scriptText`, `styleText`, `join`, `unsafeRaw`, `createHtml`).
7
+ - **Stack**: TypeScript, Deno (primary), npm via `@marianmeres/npmbuild`. Zero runtime deps.
8
+ - **Test**: `deno task test` | **Types**: `deno task check` | **Bench**: `deno task bench` |
9
+ **npm build**: `deno task npm:build`
10
+ - **Dev-only dep**: `npm:parse5` (tests only; parses rendered output as a browser would).
11
+ - **Status**: `0.x`; stay there until a real page has been ported onto it.
12
+
13
+ ## Project Structure
14
+
15
+ ```
16
+ src/mod.ts public exports only
17
+ src/types.ts public types (Trusted, Renderable, SlotContext, HtmlKit, …)
18
+ src/trusted.ts TrustedValue (brand via Symbol.for keys, private fields), isTrusted
19
+ src/escape.ts escapeHtml
20
+ src/url.ts URL policy: urlScheme (URL-parser-accurate), vetUrl, createPolicy
21
+ src/scanner.ts context analysis: tokenizer model, slot rules, neutrality, WeakMap cache
22
+ src/render.ts value × context rules (renderValue)
23
+ src/helpers.ts policy-free helpers: jsonScript, scriptText, styleText, unsafeRaw, join
24
+ src/kit.ts createHtml (html, attrs, url, srcset bound to a policy) + default kit
25
+ tests/ golden scanner tests, render table, helpers, SVG/MathML, parse5 invariance
26
+ bench/ deno bench vs hand-written concatenation
27
+ docs/design.md normative rules (read before changing scanner or render behavior)
28
+ ```
29
+
30
+ ## Critical Conventions
31
+
32
+ 1. **Safety invariants** (see [docs/design.md](./docs/design.md) §2): given templates that
33
+ pass analysis and no `unsafeRaw()`, no string value may change document structure. Every
34
+ change must keep `tests/invariance.test.ts` green.
35
+ 2. **Errors depend on the template or the value's type/kind, never on string content.**
36
+ Blocked URLs are replaced, `</script` is rewritten — never thrown.
37
+ 3. **Error messages never contain interpolated values** (attribute names in `attrs()` too:
38
+ refer to them by index). Template excerpts contain static text only.
39
+ 4. **Strictness**: anything the scanner does not model is an `HtmlTemplateError`, never a
40
+ guess. Relaxing a rule is a minor change; tightening one breaks users.
41
+ 5. **Analysis is kit-independent** (one shared cache). Kit-dependent checks (static URL
42
+ scheme) run at render from `SlotInfo.scheme`.
43
+ 6. **`SlotInfo` objects always have the same shape** (create via `slotInfo()`); rendering is
44
+ hot and relies on monomorphic property access.
45
+ 7. **No `Object.freeze` on trusted values** (costs more than a small render); immutability
46
+ comes from private fields.
47
+ 8. **`deno fmt` rewrites the contents of `html`-tagged templates.** Every file whose `html`
48
+ templates must stay byte-exact (all tests, bench, API.md) starts with
49
+ `// deno-fmt-ignore-file` (`<!-- deno-fmt-ignore-file -->` in Markdown). Keep it when
50
+ adding such files.
51
+ 9. **No `console.*`** in `src/` (visibility goes through `onBlockedUrl`).
52
+ 10. **U+2028 / U+2029 in source**: always write them as `\u` escapes. Some tools turn escapes
53
+ into the literal characters, which are line terminators and break regex literals.
54
+
55
+ ## Common Tasks
56
+
57
+ ### Add a URL attribute
58
+
59
+ 1. Add the lowercased name to `URL_ATTRIBUTES` in `src/scanner.ts` (also used by `attrs()`).
60
+ 2. Add a golden case to `tests/scanner.test.ts` ("URL attributes") and to `URL_ATTRS` in
61
+ `tests/invariance.test.ts`.
62
+ 3. Update docs/design.md §3.4, API.md (`attrs`).
63
+
64
+ ### Change a scanner rule
65
+
66
+ 1. Read docs/design.md §3; locate the state in `Scanner.chunk()`, the slot rule in
67
+ `Scanner.slot()` / `valueSlot()`, tag effects in `emitTag()` / `endTag()`.
68
+ 2. Add golden tests (context or exact error substring), and a corpus template in
69
+ `tests/invariance.test.ts` if a new context shape is involved.
70
+ 3. Consider the neutrality scan (`new Scanner(strings, true)`): a rule that differs between
71
+ HTML and foreign content must make the two scans disagree, not silently match.
72
+ 4. Update docs/design.md (and §8 if it departs from earlier behavior).
73
+
74
+ ### Change the value × context table
75
+
76
+ Edit `renderValue` / `renderTrusted` in `src/render.ts`, the table test in
77
+ `tests/render.test.ts`, and docs/design.md §4 plus the table in API.md.
78
+
79
+ ## Before Making Changes
80
+
81
+ - [ ] Read docs/design.md for the rule you touch
82
+ - [ ] `deno task test` (includes parse5 invariance and round-trips) and `deno task check`
83
+ - [ ] `deno lint` and `deno fmt --check`
84
+ - [ ] `deno task bench` if touching `render.ts`, `kit.ts`, `trusted.ts`, `escape.ts`, `url.ts`
85
+ - [ ] Update README / API.md / docs/design.md for user-visible behavior
86
+
87
+ ## Documentation Index
88
+
89
+ - [README.md](./README.md) — overview, safety lists, usage, porting from Svelte
90
+ - [API.md](./API.md) — full API reference
91
+ - [docs/design.md](./docs/design.md) — normative analysis and rendering rules, algorithms,
92
+ changes from the original draft
package/API.md ADDED
@@ -0,0 +1,404 @@
1
+ <!-- deno-fmt-ignore-file -->
2
+ <!-- (deno fmt would rewrite the html`…` templates in the examples) -->
3
+
4
+ # API
5
+
6
+ All exports come from the package root:
7
+
8
+ ```ts
9
+ import { html, attrs, url, srcset, createHtml, jsonScript, scriptText, styleText,
10
+ join, unsafeRaw, escapeHtml, isTrusted, HtmlTemplateError, HtmlValueError,
11
+ DEFAULT_URL_SCHEMES } from "@marianmeres/safe-html";
12
+ ```
13
+
14
+ The rules for which value may go where are in [docs/design.md](docs/design.md) (§3 analysis,
15
+ §4 value × context table).
16
+
17
+ ## Template tag
18
+
19
+ ### `` html`…` ``
20
+
21
+ ```ts
22
+ function html(strings: TemplateStringsArray, ...values: Renderable[]): SafeHtml;
23
+ ```
24
+
25
+ The template tag. Returns a trusted, immutable `SafeHtml`. Each interpolated value is rendered
26
+ for the context its slot sits in, as worked out once per call site from the static markup:
27
+
28
+ | Context | Where | Accepts |
29
+ | ------------ | --------------------------------------- | -------------------------------------------------------------- |
30
+ | `text` | element content | strings and numbers (escaped), `html`/`join()` fragments, `url()`, arrays |
31
+ | `rcdata` | `<title>`, `<textarea>` content | strings and numbers (escaped), `url()`, arrays |
32
+ | `attr-list` | `<tag ${…}>` | `attrs()`, arrays of them |
33
+ | `attr-value` | inside a quoted attribute value | strings and numbers (escaped), `url()`, `srcset()` |
34
+ | `url` | a URL attribute value the slot starts | strings and numbers (URL policy, then escaped), `url()` |
35
+ | `srcset` | a whole `srcset`/`imagesrcset` value | `srcset()` |
36
+ | `script` | `<script>` content | `jsonScript()`, `scriptText()` |
37
+ | `style` | `<style>` content | `styleText()` |
38
+
39
+ Everywhere: `null`, `undefined`, `false` and `true` render nothing; `unsafeRaw()` is inserted
40
+ verbatim. Anything else throws `HtmlValueError`.
41
+
42
+ **Throws:** `HtmlTemplateError` if the template is refused (slot in an unquoted attribute,
43
+ event handler, tag name, comment, …) or `html` is not called as a tagged template;
44
+ `HtmlValueError` if a value has no rendering in its context.
45
+
46
+ **Example:**
47
+
48
+ ```ts
49
+ const Card = (c: { title: string; href: string; tags: string[] }) =>
50
+ html`<article>
51
+ <h2><a href="${c.href}">${c.title}</a></h2>
52
+ ${c.tags.length > 0 && html`<p>${join(c.tags, ", ")}</p>`}
53
+ </article>`;
54
+
55
+ String(Card({ title: "<Hi>", href: "javascript:x", tags: [] }));
56
+ // <article>
57
+ // <h2><a href="about:invalid#blocked">&lt;Hi&gt;</a></h2>
58
+ //
59
+ // </article>
60
+ ```
61
+
62
+ ---
63
+
64
+ ## Kit (URL policy)
65
+
66
+ ### `createHtml(options?)`
67
+
68
+ ```ts
69
+ function createHtml(options?: HtmlOptions): HtmlKit;
70
+ ```
71
+
72
+ Creates a kit whose `html`, `attrs`, `url` and `srcset` use the given URL policy. The default
73
+ exports (`html`, `attrs`, `url`, `srcset`) are `createHtml()` with default options. Analyses are
74
+ cached per call site and shared by all kits.
75
+
76
+ **Parameters:**
77
+ - `options.urlSchemes` (`readonly string[]`, optional) — allowed schemes, case-insensitive, with
78
+ or without the colon. Default: `DEFAULT_URL_SCHEMES` (`http`, `https`, `mailto`, `tel`).
79
+ - `options.blockedUrl` (`string`, optional) — what a blocked URL renders as. Default:
80
+ `"about:invalid#blocked"`.
81
+ - `options.onBlockedUrl` (`(info: BlockedUrlInfo) => void`, optional) — called once per blocked
82
+ URL (e.g. to log a probe). If it throws, the render throws.
83
+
84
+ **Returns:** `HtmlKit` — `{ html, attrs, url, srcset }` (frozen; the functions need no `this`).
85
+
86
+ **Throws:** `TypeError` on malformed options.
87
+
88
+ **Example:**
89
+
90
+ ```ts
91
+ export const { html, attrs, url, srcset } = createHtml({
92
+ urlSchemes: [...DEFAULT_URL_SCHEMES, "sms"],
93
+ onBlockedUrl: ({ tag, attribute }) => console.warn("blocked URL", tag, attribute),
94
+ });
95
+ ```
96
+
97
+ A template whose static URL text writes a scheme (`href="mailto:${a}"`) is checked against the
98
+ kit's list at render: a scheme outside it (`href="javascript:go('${id}')"`) is an
99
+ `HtmlTemplateError`.
100
+
101
+ ### `attrs(record)`
102
+
103
+ ```ts
104
+ function attrs(record: Readonly<Record<string, AttrValue>>): SafeAttrs;
105
+ ```
106
+
107
+ An attribute list for an `attr-list` slot: `<button ${attrs({ type: "submit", disabled })}>`.
108
+
109
+ - Each attribute renders as ` name="value"` (leading space, value escaped), in key order.
110
+ - `true` → ` name` (boolean attribute); `false`, `null`, `undefined` → omitted.
111
+ - URL attributes (`href`, `src`, `action`, `formaction`, `poster`, `cite`, `background`,
112
+ `longdesc`, `manifest`, `codebase`, `icon`, `xlink:href`, `data`) go through the URL policy
113
+ unless the value is `url()` or `unsafeRaw()`.
114
+ - `srcset` / `imagesrcset` take `srcset()` (or `unsafeRaw()`).
115
+ - Values may also be `url()` / `srcset()` (escaped) or `unsafeRaw()` (verbatim) anywhere.
116
+
117
+ **Throws:** `HtmlValueError` for a name not matching `/^[A-Za-z_][A-Za-z0-9_.:-]*$/`, an `on*`
118
+ name, `srcdoc` or `ping`, or a value with no attribute rendering (object, `html`, …). Names
119
+ are expected to come from code.
120
+
121
+ **Example:**
122
+
123
+ ```ts
124
+ html`<input ${attrs({ type: "email", name: "email", required: true, value: draft })}>`;
125
+ // <input type="email" name="email" required value="…">
126
+ ```
127
+
128
+ ### `url(value, options?)`
129
+
130
+ ```ts
131
+ function url(value: string, options?: { schemes?: readonly string[] }): SafeUrl;
132
+ ```
133
+
134
+ Checks `value` against the scheme allowlist and returns a `SafeUrl`: vetted, **not escaped**
135
+ (it is escaped wherever it is rendered). A blocked value becomes the kit's `blockedUrl`
136
+ (and `onBlockedUrl` is called); it never throws on content. `options.schemes` replaces the
137
+ kit's list for this one value.
138
+
139
+ Relative URLs (`/x`, `./x`, `?q`, `#f`, `//host`, `x/y`) are allowed. Scheme detection
140
+ follows the URL parser: leading control characters and spaces are skipped and tab/LF/CR are
141
+ ignored, so `" java\tscript:"` is caught.
142
+
143
+ **Example:**
144
+
145
+ ```ts
146
+ html`<img src="${url(`data:image/png;base64,${b64}`, { schemes: ["data"] })}">`;
147
+ ```
148
+
149
+ ### `srcset(candidates)`
150
+
151
+ ```ts
152
+ function srcset(candidates: readonly SrcsetCandidate[]): SafeSrcset;
153
+ ```
154
+
155
+ A `srcset` value. Each candidate's URL goes through the URL policy unless it is a `SafeUrl`;
156
+ blocked and empty candidates are **dropped**. ASCII whitespace and leading/trailing commas in
157
+ a URL are percent-encoded. Returns an unescaped, vetted value (escaped where rendered).
158
+
159
+ **Throws:** `HtmlValueError` for a descriptor not matching `/^\d+(\.\d+)?x$|^\d+w$/` (e.g.
160
+ `"2x"`, `"1.5x"`, `"800w"`), or a malformed candidate.
161
+
162
+ **Example:**
163
+
164
+ ```ts
165
+ html`<img src="${img.src}" srcset="${srcset([
166
+ { url: img.src },
167
+ { url: img.src2x, descriptor: "2x" },
168
+ ])}" alt="${img.alt}">`;
169
+ ```
170
+
171
+ ---
172
+
173
+ ## Policy-free helpers
174
+
175
+ ### `jsonScript(value, space?)`
176
+
177
+ ```ts
178
+ function jsonScript(value: unknown, space?: number): Trusted<"json">;
179
+ ```
180
+
181
+ `JSON.stringify(value, null, space)` with `<`, `>`, `&`, U+2028 and U+2029 written as `\uXXXX`
182
+ escapes: valid JSON of the same value, a valid JS expression, and unable to end a `<script>`.
183
+ Accepted in `script` context only. Use it for `application/ld+json`, `application/json` data
184
+ islands, or `const x = ${jsonScript(v)};`. `SafeHtml` values inside serialize as their string.
185
+
186
+ **Throws:** `HtmlValueError` when there is no JSON representation (`undefined`, a function or
187
+ a symbol at the root). `JSON.stringify`'s own `TypeError`s (cycles, `BigInt`) propagate.
188
+
189
+ **Example:**
190
+
191
+ ```ts
192
+ html`<script type="application/ld+json">${jsonScript({ "@type": "Article", headline })}</script>`;
193
+ ```
194
+
195
+ ### `scriptText(source)`
196
+
197
+ ```ts
198
+ function scriptText(source: string): Trusted<"js">;
199
+ ```
200
+
201
+ Inline JavaScript that is code, not data (typically a constant). Every case-insensitive
202
+ `</script` becomes `<\/script`, every `<!--` becomes `\x3C!--`, and a `<` that ends the source
203
+ (alone or followed by the start of `/script` or `!--`) becomes `\x3C`. These keep the meaning
204
+ inside strings, template literals and regexes. Accepted in `script` context only. Never throws
205
+ on content.
206
+
207
+ **Example:**
208
+
209
+ ```ts
210
+ html`<script>${scriptText(`document.documentElement.dataset.theme = "dark";`)}</script>`;
211
+ ```
212
+
213
+ ### `styleText(source)`
214
+
215
+ ```ts
216
+ function styleText(source: string): Trusted<"css">;
217
+ ```
218
+
219
+ Inline CSS. Every case-insensitive `</style` becomes `<\/style`, and a `<` that ends the source
220
+ (alone or followed by the start of `/style`) becomes `\00003C`. Other `<` are kept
221
+ (media-query ranges use them). Accepted in `style` context only. Prevents breakout, **not**
222
+ CSS injection: build dynamic CSS from validated tokens.
223
+
224
+ **Example:**
225
+
226
+ ```ts
227
+ html`<style>${styleText(`:root { --accent: ${validatedHexColor}; }`)}</style>`;
228
+ ```
229
+
230
+ ### `unsafeRaw(value)`
231
+
232
+ ```ts
233
+ function unsafeRaw(value: string): Trusted<"unsafe">;
234
+ ```
235
+
236
+ The escape hatch: inserted verbatim in **any** context, with no checks. It means *trusted*,
237
+ never *cleaned*; this package is not an HTML sanitizer. The name is loud on purpose so every
238
+ use is greppable.
239
+
240
+ **Example:**
241
+
242
+ ```ts
243
+ html`<article>${unsafeRaw(sanitizedByYourSanitizer)}</article>`;
244
+ ```
245
+
246
+ ### `join(values, separator?)`
247
+
248
+ ```ts
249
+ function join(values: readonly Renderable[], separator?: string | SafeHtml): SafeHtml;
250
+ ```
251
+
252
+ Renders each value under the `text` rules, separated by `separator` (default `""`; a string is
253
+ escaped, `html` is inserted as is). `null`, `undefined` and booleans are skipped, so
254
+ conditional items leave no stray separator. The result is `html`, so `<title>` and
255
+ `<textarea>` refuse it: pass a plain string there (`parts.join(" | ")`).
256
+
257
+ **Example:**
258
+
259
+ ```ts
260
+ html`<p>${join([author, date, isDraft && html`<em>draft</em>`], " · ")}</p>`;
261
+ ```
262
+
263
+ ### `escapeHtml(value)`
264
+
265
+ ```ts
266
+ function escapeHtml(value: string): string;
267
+ ```
268
+
269
+ Escapes `&`, `<`, `>`, `"`, `'` (as `&amp;`, `&lt;`, `&gt;`, `&quot;`, `&#39;`). Returns the input
270
+ unchanged when there is nothing to escape. For use outside templates.
271
+
272
+ ### `isTrusted(v, kind?)`
273
+
274
+ ```ts
275
+ function isTrusted<K extends TrustedKind>(v: unknown, kind?: K): v is Trusted<K>;
276
+ ```
277
+
278
+ Whether `v` is a value produced by this package (optionally of the given kind). Recognizes
279
+ values from other copies of the package in the same process; never true for parsed JSON.
280
+
281
+ ---
282
+
283
+ ## Errors
284
+
285
+ ### `HtmlTemplateError`
286
+
287
+ ```ts
288
+ class HtmlTemplateError extends Error {
289
+ readonly slot?: number; // offending slot, when the error is about a slot
290
+ readonly excerpt: string; // static text, slots as ${…}, the error at ⟨here⟩
291
+ }
292
+ ```
293
+
294
+ A refused template, or a call that is not a genuine tagged template. Depends on the template
295
+ only (and, for a static URL scheme, on the kit's allowlist). A failed analysis is cached and
296
+ rethrown on every render of that call site. The excerpt contains only developer-written text,
297
+ so it is safe to log:
298
+
299
+ ```
300
+ HtmlTemplateError: unquoted attribute value: quote it, e.g. name="${…}"
301
+ slot 0 in: <a href=${⟨here⟩}>
302
+ ```
303
+
304
+ ### `HtmlValueError`
305
+
306
+ ```ts
307
+ class HtmlValueError extends TypeError {
308
+ readonly slot?: number; // when raised while rendering a template
309
+ readonly context?: SlotContext; // when raised while rendering a template or join()
310
+ readonly received: string; // type or kind, e.g. "object", "Promise", "Trusted<html>"
311
+ }
312
+ ```
313
+
314
+ A value with no rendering in its context (depends on the value's type or kind), an invalid
315
+ attribute name in `attrs()` or descriptor in `srcset()` (content expected from code), or
316
+ arrays nested deeper than 100 (a cycle). Messages never include values.
317
+
318
+ ---
319
+
320
+ ## Constants
321
+
322
+ ### `DEFAULT_URL_SCHEMES`
323
+
324
+ `readonly ["http", "https", "mailto", "tel"]` — the default scheme allowlist (frozen).
325
+
326
+ ---
327
+
328
+ ## Types
329
+
330
+ ### `Trusted`, `TrustedKind`
331
+
332
+ ```ts
333
+ type TrustedKind = "html" | "attrs" | "url" | "srcset" | "json" | "js" | "css" | "unsafe";
334
+
335
+ interface Trusted<K extends TrustedKind = TrustedKind> {
336
+ readonly kind: K;
337
+ toString(): string; // also String(v), `${v}`
338
+ toJSON(): string; // JSON.stringify(v) gives the string
339
+ }
340
+
341
+ type SafeHtml = Trusted<"html">;
342
+ type SafeAttrs = Trusted<"attrs">;
343
+ type SafeUrl = Trusted<"url">;
344
+ type SafeSrcset = Trusted<"srcset">;
345
+ ```
346
+
347
+ Immutable, branded values. `SafeUrl` and `SafeSrcset` hold the vetted, unescaped value.
348
+
349
+ ### `Renderable`
350
+
351
+ ```ts
352
+ type Renderable =
353
+ | Trusted | string | number | bigint | boolean | null | undefined
354
+ | readonly Renderable[];
355
+ ```
356
+
357
+ What `html` accepts. Objects, functions, symbols, dates and promises are type errors.
358
+
359
+ ### `AttrValue`
360
+
361
+ ```ts
362
+ type AttrValue =
363
+ | string | number | bigint | boolean | null | undefined
364
+ | Trusted<"url" | "srcset" | "unsafe">;
365
+ ```
366
+
367
+ ### `SrcsetCandidate`
368
+
369
+ ```ts
370
+ interface SrcsetCandidate {
371
+ url: string | SafeUrl;
372
+ descriptor?: string; // "2x", "1.5x", "800w"; omitted = 1x
373
+ }
374
+ ```
375
+
376
+ ### `HtmlOptions`, `HtmlKit`, `BlockedUrlInfo`
377
+
378
+ ```ts
379
+ interface HtmlOptions {
380
+ urlSchemes?: readonly string[];
381
+ blockedUrl?: string;
382
+ onBlockedUrl?: (info: BlockedUrlInfo) => void;
383
+ }
384
+
385
+ interface HtmlKit {
386
+ html(strings: TemplateStringsArray, ...values: Renderable[]): SafeHtml;
387
+ attrs(record: Readonly<Record<string, AttrValue>>): SafeAttrs;
388
+ url(value: string, options?: { schemes?: readonly string[] }): SafeUrl;
389
+ srcset(candidates: readonly SrcsetCandidate[]): SafeSrcset;
390
+ }
391
+
392
+ interface BlockedUrlInfo {
393
+ url: string; // as given
394
+ tag?: string; // lowercased, when known
395
+ attribute?: string; // lowercased, when known
396
+ }
397
+ ```
398
+
399
+ ### `SlotContext`
400
+
401
+ ```ts
402
+ type SlotContext =
403
+ | "text" | "rcdata" | "attr-list" | "attr-value" | "url" | "srcset" | "script" | "style";
404
+ ```
package/CLAUDE.md ADDED
@@ -0,0 +1,3 @@
1
+ # Project Instructions
2
+
3
+ See [AGENTS.md](./AGENTS.md) for complete project documentation and AI agent instructions.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Marian Meres
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.