pagerts 0.4.1 → 1.0.2

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.
@@ -1,9 +0,0 @@
1
- import type { PageMetadata } from '../page/index.js';
2
- import { AbstractResourcePrinter } from './AbstractResourcePrinter.js';
3
-
4
- export class JSONStylePrinter extends AbstractResourcePrinter {
5
- print(...pages: PageMetadata[]): void | Promise<void> {
6
- const json = JSON.stringify(pages);
7
- process.stdout.write(json + '\n');
8
- }
9
- }
@@ -1,30 +0,0 @@
1
- import { isPage, type PageMetadata } from '../page/index.js';
2
- import { AbstractResourcePrinter } from './AbstractResourcePrinter.js';
3
-
4
- export class LogStylePrinter extends AbstractResourcePrinter {
5
- write(str: string): void {
6
- process.stdout.write(str);
7
- }
8
-
9
- async print(...pages: PageMetadata[]): Promise<void> {
10
- for (const page of pages) {
11
- if (!isPage(page)) {
12
- this.write(page.error);
13
- continue;
14
- }
15
-
16
- const { resources, title, url } = page;
17
-
18
- this.write(`Title: ${title}\n`);
19
- this.write(`URL: ${url}\n\n`);
20
-
21
- for (const resource of resources) {
22
- const {
23
- link: { url },
24
- text: { value },
25
- } = resource;
26
- this.write(`${value}: ${url}\n`);
27
- }
28
- }
29
- }
30
- }
@@ -1,3 +0,0 @@
1
- export { AbstractResourcePrinter } from './AbstractResourcePrinter.js';
2
- export { JSONStylePrinter } from './JSONStylePrinter.js';
3
- export { LogStylePrinter } from './LogStylePrinter.js';
package/src/resource.ts DELETED
@@ -1,88 +0,0 @@
1
- /**
2
- * @license MIT
3
- * We are interested in visualising a page as a collection of tags.
4
- *
5
- * We wish to work with tags that can be compactly previewed on a webpage.
6
- * Here we must declare all of the element types that can be used to represent
7
- * a resource that can be hyperlinked off a webpage.
8
- */
9
- type Tags = HTMLElementTagNameMap;
10
-
11
- function findDefinedKey(element: Resource, keys: LinkKey[]): LinkKey | undefined {
12
- for (const key of keys) {
13
- if (isKeyDefined(key, element)) {
14
- return key;
15
- }
16
- }
17
-
18
- return undefined;
19
- }
20
-
21
- export const RESOURCE_DISPLAYABLE_KEYS = [
22
- 'id',
23
- 'innerText',
24
- 'textContent',
25
- 'class',
26
- 'ariaLabel',
27
- 'ariaDescription',
28
- 'alt',
29
- ] as const;
30
-
31
- export type DisplayableKey = (typeof RESOURCE_DISPLAYABLE_KEYS)[number];
32
-
33
- export type ResourceKey = {
34
- key: DisplayableKey;
35
- value: string;
36
- };
37
-
38
- export const RESOURCE_LINK_KEYS = ['href', 'data-src', 'target', 'action', 'src', 'url'] as const;
39
-
40
- export type LinkKey = (typeof RESOURCE_LINK_KEYS)[number];
41
-
42
- export type ResourceLink = {
43
- key: LinkKey;
44
- url: string;
45
- };
46
-
47
- export function findResourceText(element: Resource): ResourceKey | undefined {
48
- for (const key of RESOURCE_DISPLAYABLE_KEYS) {
49
- const value = element[key];
50
- if (value && typeof value === 'string' && value.trim() !== '') return { key, value };
51
- }
52
-
53
- return undefined;
54
- }
55
-
56
- export function findResourceLink(element: Resource): ResourceLink | undefined {
57
- const key = findDefinedKey(element, [...RESOURCE_LINK_KEYS]);
58
- if (!key) {
59
- return undefined;
60
- }
61
-
62
- const url = element[key];
63
- if (url && typeof url === 'string' && url.trim() !== '') return { key, url };
64
-
65
- return undefined;
66
- }
67
-
68
- export type ExternalResource = {
69
- text: ResourceKey;
70
- link: ResourceLink;
71
- };
72
-
73
- export const isResourceKey = (key: string): key is LinkKey => key in RESOURCE_LINK_KEYS;
74
-
75
- export const isKeyDefined = (key: DisplayableKey | LinkKey, element: Resource): boolean =>
76
- key in element && element[key] !== undefined;
77
-
78
- export type ResourceElement<T, U> = {
79
- [K in keyof T]: U extends keyof T[K] ? T[K] : never;
80
- }[keyof T];
81
-
82
- export type Tag = keyof Tags;
83
-
84
- export type Resource = HTMLElement & {
85
- [K in DisplayableKey | LinkKey]?: string | null;
86
- };
87
-
88
- export type ResourceByName<T extends keyof Tags> = Tags[T];
package/src/security.ts DELETED
@@ -1,184 +0,0 @@
1
- /**
2
- * Security utilities for URL validation and sanitization
3
- */
4
-
5
- const ALLOWED_PROTOCOLS = ['http:', 'https:', 'file:'];
6
- const MAX_URL_LENGTH = 2048;
7
- const SUSPICIOUS_PATTERNS = [
8
- /javascript:/i,
9
- /data:/i,
10
- /vbscript:/i,
11
- /<script/i,
12
- /on\w+=/i, // Event handlers like onclick=
13
- ];
14
-
15
- export interface ValidationResult {
16
- isValid: boolean;
17
- error?: string;
18
- sanitizedUrl?: string;
19
- }
20
-
21
- /**
22
- * Validates a URL for security concerns
23
- * @param url - The URL to validate
24
- * @returns ValidationResult object with validation status
25
- */
26
- export function validateUrl(url: string): ValidationResult {
27
- // Check if URL is empty or whitespace
28
- if (!url || !url.trim()) {
29
- return {
30
- isValid: false,
31
- error: 'URL cannot be empty',
32
- };
33
- }
34
-
35
- const trimmedUrl = url.trim();
36
-
37
- // Check URL length to prevent DoS
38
- if (trimmedUrl.length > MAX_URL_LENGTH) {
39
- return {
40
- isValid: false,
41
- error: `URL exceeds maximum length of ${MAX_URL_LENGTH} characters`,
42
- };
43
- }
44
-
45
- // Check for suspicious patterns
46
- for (const pattern of SUSPICIOUS_PATTERNS) {
47
- if (pattern.test(trimmedUrl)) {
48
- return {
49
- isValid: false,
50
- error: 'URL contains suspicious patterns',
51
- };
52
- }
53
- }
54
-
55
- // Parse the URL
56
- let parsedUrl: URL;
57
- try {
58
- parsedUrl = new URL(trimmedUrl);
59
- } catch (error) {
60
- // If URL parsing fails, it might be a file path
61
- if (trimmedUrl.startsWith('file://')) {
62
- return {
63
- isValid: true,
64
- sanitizedUrl: trimmedUrl,
65
- };
66
- }
67
- return {
68
- isValid: false,
69
- error: 'Invalid URL format',
70
- };
71
- }
72
-
73
- // Check protocol
74
- if (!ALLOWED_PROTOCOLS.includes(parsedUrl.protocol)) {
75
- return {
76
- isValid: false,
77
- error: `Protocol ${parsedUrl.protocol} is not allowed. Allowed protocols: ${ALLOWED_PROTOCOLS.join(', ')}`,
78
- };
79
- }
80
-
81
- // Check for localhost/internal IPs in production (security consideration)
82
- const hostname = parsedUrl.hostname.toLowerCase();
83
- const isLocalhost =
84
- hostname === 'localhost' ||
85
- hostname === '127.0.0.1' ||
86
- hostname === '::1' ||
87
- hostname.startsWith('192.168.') ||
88
- hostname.startsWith('10.') ||
89
- /^172\.(1[6-9]|2\d|3[01])\./.test(hostname);
90
-
91
- if (isLocalhost && parsedUrl.protocol !== 'file:') {
92
- // Allow but warn about localhost URLs
93
- console.warn(`Warning: Accessing local network resource: ${trimmedUrl}`);
94
- }
95
-
96
- return {
97
- isValid: true,
98
- sanitizedUrl: parsedUrl.toString(),
99
- };
100
- }
101
-
102
- /**
103
- * Validates an array of URLs
104
- * @param urls - Array of URLs to validate
105
- * @returns Object with valid URLs and errors
106
- */
107
- export function validateUrls(urls: string[]): {
108
- validUrls: string[];
109
- errors: Array<{ url: string; error: string }>;
110
- } {
111
- const validUrls: string[] = [];
112
- const errors: Array<{ url: string; error: string }> = [];
113
-
114
- for (const url of urls) {
115
- const result = validateUrl(url);
116
- if (result.isValid && result.sanitizedUrl) {
117
- validUrls.push(result.sanitizedUrl);
118
- } else {
119
- errors.push({
120
- url,
121
- error: result.error || 'Unknown validation error',
122
- });
123
- }
124
- }
125
-
126
- return { validUrls, errors };
127
- }
128
-
129
- /**
130
- * Rate limiter to prevent abuse
131
- */
132
- export class RateLimiter {
133
- private requests: number[] = [];
134
- private readonly maxRequests: number;
135
- private readonly windowMs: number;
136
-
137
- constructor(maxRequests = 10, windowMs = 60000) {
138
- this.maxRequests = maxRequests;
139
- this.windowMs = windowMs;
140
- }
141
-
142
- /**
143
- * Check if a request is allowed under rate limiting
144
- * @returns true if request is allowed, false otherwise
145
- */
146
- public isAllowed(): boolean {
147
- const now = Date.now();
148
-
149
- // Remove old requests outside the time window
150
- this.requests = this.requests.filter((time) => now - time < this.windowMs);
151
-
152
- if (this.requests.length >= this.maxRequests) {
153
- return false;
154
- }
155
-
156
- this.requests.push(now);
157
- return true;
158
- }
159
-
160
- /**
161
- * Get remaining requests in current window
162
- */
163
- public getRemainingRequests(): number {
164
- const now = Date.now();
165
- this.requests = this.requests.filter((time) => now - time < this.windowMs);
166
- return Math.max(0, this.maxRequests - this.requests.length);
167
- }
168
- }
169
-
170
- /**
171
- * Sanitizes HTML content to prevent XSS attacks
172
- * @param text - Text to sanitize
173
- * @returns Sanitized text
174
- */
175
- export function sanitizeText(text: string): string {
176
- if (!text) return '';
177
-
178
- return text
179
- .replace(/</g, '&lt;')
180
- .replace(/>/g, '&gt;')
181
- .replace(/"/g, '&quot;')
182
- .replace(/'/g, '&#x27;')
183
- .replace(/\//g, '&#x2F;');
184
- }
@@ -1,5 +0,0 @@
1
- {
2
- "extends": "./tsconfig.json",
3
- "include": ["src/**/*.ts"],
4
- "exclude": ["node_modules", "bin", "coverage"]
5
- }
package/tsconfig.json DELETED
@@ -1,28 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "module": "NodeNext",
4
- "target": "ES2022",
5
- "lib": ["ES2022"],
6
- "moduleResolution": "NodeNext",
7
- "resolveJsonModule": true,
8
- "outDir": "bin",
9
- "sourceMap": true,
10
- "strict": true,
11
- "noImplicitAny": true,
12
- "strictNullChecks": true,
13
- "strictFunctionTypes": true,
14
- "strictBindCallApply": true,
15
- "strictPropertyInitialization": true,
16
- "noImplicitThis": true,
17
- "alwaysStrict": true,
18
- "noUnusedLocals": true,
19
- "noUnusedParameters": true,
20
- "noImplicitReturns": true,
21
- "noFallthroughCasesInSwitch": true,
22
- "esModuleInterop": true,
23
- "skipLibCheck": true,
24
- "forceConsistentCasingInFileNames": true
25
- },
26
- "include": ["src/**/*"],
27
- "exclude": ["node_modules", "bin", "coverage", "**/*.test.ts", "**/*.spec.ts"]
28
- }