koa-classic-server 3.0.0 → 3.1.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.
Files changed (105) hide show
  1. package/README.md +44 -120
  2. package/index.cjs +236 -21
  3. package/package.json +10 -5
  4. package/.github/workflows/npm-publish.yml +0 -98
  5. package/CLAUDE.md +0 -101
  6. package/__tests__/benchmark-results-baseline-v1.2.0.txt +0 -354
  7. package/__tests__/benchmark-results-optimized-v2.0.0.txt +0 -354
  8. package/__tests__/benchmark-results-v3.0.0.txt +0 -372
  9. package/__tests__/benchmark.js +0 -239
  10. package/__tests__/caching-headers.test.js +0 -556
  11. package/__tests__/compression-fixtures/data.json +0 -1
  12. package/__tests__/compression-fixtures/large.txt +0 -1
  13. package/__tests__/compression-fixtures/small.txt +0 -1
  14. package/__tests__/compression.test.js +0 -284
  15. package/__tests__/customTest/README.md +0 -6
  16. package/__tests__/customTest/loadConfig.util.js +0 -41
  17. package/__tests__/customTest/serversToLoad.util.js +0 -93
  18. package/__tests__/demo-regex-index.js +0 -140
  19. package/__tests__/deprecation-warnings.test.js +0 -105
  20. package/__tests__/directory-sorting-links.test.js +0 -135
  21. package/__tests__/dt-unknown.test.js +0 -635
  22. package/__tests__/ejs.test.js +0 -299
  23. package/__tests__/hidden-fixtures/.dot-dir/inside.txt +0 -1
  24. package/__tests__/hidden-fixtures/.env +0 -2
  25. package/__tests__/hidden-fixtures/.well-known/acme-challenge.txt +0 -1
  26. package/__tests__/hidden-fixtures/config/secrets/password.txt +0 -1
  27. package/__tests__/hidden-fixtures/data.key +0 -1
  28. package/__tests__/hidden-fixtures/file.secret +0 -1
  29. package/__tests__/hidden-fixtures/index.html +0 -1
  30. package/__tests__/hidden-fixtures/normal.txt +0 -1
  31. package/__tests__/hidden-fixtures/subdir/.env +0 -1
  32. package/__tests__/hidden-fixtures/subdir/regular.txt +0 -1
  33. package/__tests__/hidden-option.test.js +0 -407
  34. package/__tests__/hideExtension.test.js +0 -607
  35. package/__tests__/index-option.test.js +0 -449
  36. package/__tests__/index.test.js +0 -345
  37. package/__tests__/listing.test.js +0 -437
  38. package/__tests__/logger.test.js +0 -232
  39. package/__tests__/performance.test.js +0 -370
  40. package/__tests__/publicWwwTest/cartella/sottocartella/ciao.html +0 -11
  41. package/__tests__/publicWwwTest/cartella/sottocartella/provaEjs/testEjs.ejs +0 -11
  42. package/__tests__/publicWwwTest/cartella vuota con spazi nel nome/file con spazio nel nome .txt +0 -1
  43. package/__tests__/publicWwwTest/cartella vuota con spazi nel nome /file con spazio nel nome .txt +0 -1
  44. package/__tests__/publicWwwTest/ejs-templates/complex.ejs +0 -56
  45. package/__tests__/publicWwwTest/ejs-templates/index.ejs +0 -30
  46. package/__tests__/publicWwwTest/ejs-templates/simple.ejs +0 -13
  47. package/__tests__/publicWwwTest/ejs-templates/with-conditional.ejs +0 -28
  48. package/__tests__/publicWwwTest/ejs-templates/with-escaping.ejs +0 -26
  49. package/__tests__/publicWwwTest/ejs-templates/with-loop.ejs +0 -16
  50. package/__tests__/publicWwwTest/hideext-test/about/index.html +0 -1
  51. package/__tests__/publicWwwTest/hideext-test/about.EJS +0 -1
  52. package/__tests__/publicWwwTest/hideext-test/about.ejs +0 -2
  53. package/__tests__/publicWwwTest/hideext-test/blog/articolo.ejs +0 -2
  54. package/__tests__/publicWwwTest/hideext-test/conflict-test/pagina +0 -1
  55. package/__tests__/publicWwwTest/hideext-test/conflict-test/pagina.ejs +0 -1
  56. package/__tests__/publicWwwTest/hideext-test/file.ejs.bak +0 -1
  57. package/__tests__/publicWwwTest/hideext-test/index.ejs +0 -2
  58. package/__tests__/publicWwwTest/hideext-test/photo.txt +0 -1
  59. package/__tests__/publicWwwTest/hideext-test/sezione/index.ejs +0 -1
  60. package/__tests__/publicWwwTest/hideext-test/style.css +0 -1
  61. package/__tests__/publicWwwTest/ile_vuoto.txt +0 -0
  62. package/__tests__/publicWwwTest/index.html +0 -11
  63. package/__tests__/publicWwwTest/prova file .txt +0 -2
  64. package/__tests__/publicWwwTest/semplicetxt.txt +0 -1
  65. package/__tests__/publicWwwTest/test-page.html +0 -1
  66. package/__tests__/publicWwwTest/test.txt +0 -1
  67. package/__tests__/range-fixtures/sample.txt +0 -1
  68. package/__tests__/range.test.js +0 -223
  69. package/__tests__/security-headers.test.js +0 -165
  70. package/__tests__/security.test.js +0 -322
  71. package/__tests__/server-cache-fixtures/large.txt +0 -1
  72. package/__tests__/server-cache-fixtures/small.txt +0 -1
  73. package/__tests__/server-cache.test.js +0 -594
  74. package/__tests__/setup-benchmark.js +0 -178
  75. package/__tests__/symlink.test.js +0 -441
  76. package/__tests__/template-timeout.test.js +0 -321
  77. package/__tests__/test-regex-quick.js +0 -158
  78. package/__tests__/useOriginalUrl.test.js +0 -213
  79. package/docs/ACTION_PLAN.md +0 -293
  80. package/docs/BENCHMARKS.md +0 -317
  81. package/docs/CHANGELOG.md +0 -880
  82. package/docs/CODE_REVIEW.md +0 -300
  83. package/docs/DEBUG_REPORT.md +0 -593
  84. package/docs/DOCUMENTATION.md +0 -1864
  85. package/docs/EXAMPLES_INDEX_OPTION.md +0 -395
  86. package/docs/FLOW_DIAGRAM.md +0 -954
  87. package/docs/INDEX_OPTION_PRIORITY.md +0 -527
  88. package/docs/OPTIMIZATION_HTTP_CACHING.md +0 -689
  89. package/docs/OPTIMIZATION_ROADMAP_for_V3.md +0 -864
  90. package/docs/PERFORMANCE_ANALYSIS.md +0 -839
  91. package/docs/PERFORMANCE_COMPARISON.md +0 -388
  92. package/docs/noteExports.md +0 -148
  93. package/docs/security_improvement_for_V3.md +0 -421
  94. package/docs/template-engine/TEMPLATE_ENGINE_GUIDE.md +0 -1734
  95. package/docs/template-engine/esempi-incrementali.js +0 -192
  96. package/docs/template-engine/examples/esempio1-nessun-dato.ejs +0 -12
  97. package/docs/template-engine/examples/esempio2-una-variabile.ejs +0 -11
  98. package/docs/template-engine/examples/esempio3-piu-variabili.ejs +0 -15
  99. package/docs/template-engine/examples/esempio4-condizionale.ejs +0 -18
  100. package/docs/template-engine/examples/esempio5-loop.ejs +0 -18
  101. package/docs/template-engine/examples/index-esempi.html +0 -181
  102. package/docs/template-engine/examples/index.html +0 -40
  103. package/docs/template-engine/examples/test.ejs +0 -64
  104. package/eslint.config.mjs +0 -17
  105. package/jest.config.js +0 -18
@@ -1,322 +0,0 @@
1
- //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
2
- //
3
- // SECURITY & BUG TESTS
4
- // These tests verify the security properties and bug fixes documented in DEBUG_REPORT.md
5
- //
6
- //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
7
-
8
- const supertest = require('supertest');
9
- const koaClassicServer = require('../index.cjs');
10
- const Koa = require('koa');
11
- const fs = require('fs');
12
- const path = require('path');
13
-
14
- const rootDir = path.join(__dirname, 'publicWwwTest');
15
-
16
- // ─── Path Traversal ───────────────────────────────────────────────────────────
17
-
18
- describe('Security Tests - Path Traversal', () => {
19
- let app;
20
- let server;
21
-
22
- beforeAll(() => {
23
- app = new Koa();
24
- app.use(koaClassicServer(rootDir, {
25
- dirListing: { enabled: true }
26
- }));
27
- server = app.listen();
28
- });
29
-
30
- afterAll(() => {
31
- server.close();
32
- });
33
-
34
- // On Linux, path.normalize() cannot escape '/' (the OS root), so '/../package.json'
35
- // resolves to rootDir/package.json — which simply does not exist → 404.
36
- // On Windows, backslash sequences can escape rootDir and the startsWith() check
37
- // fires → 403. Both outcomes prove the traversal was blocked (no 200 with file content).
38
-
39
- test('../ traversal is blocked (403 or 404, never 200 with file content)', async () => {
40
- const res = await supertest(server).get('/../package.json');
41
- expect([403, 404]).toContain(res.status);
42
- expect(res.text).not.toContain('"name"');
43
- });
44
-
45
- test('URL-encoded ../ traversal (%2e%2e%2f) is blocked (403 or 404)', async () => {
46
- const res = await supertest(server).get('/%2e%2e%2f%2e%2e%2fpackage.json');
47
- expect([403, 404]).toContain(res.status);
48
- expect(res.text).not.toContain('"name"');
49
- });
50
-
51
- test('multi-level traversal (/../../../etc/hosts) is blocked (403 or 404)', async () => {
52
- const res = await supertest(server).get('/../../../etc/hosts');
53
- expect([403, 404]).toContain(res.status);
54
- });
55
-
56
- test('null byte in path is rejected with 400', async () => {
57
- // path.normalize() throws ERR_INVALID_ARG_VALUE for paths with \0;
58
- // the null byte guard returns 400 before reaching fs operations.
59
- const res = await supertest(server).get('/file%00.txt');
60
- expect(res.status).toBe(400);
61
- });
62
-
63
- test('backslash sequences are not treated as path separators on Linux', async () => {
64
- // On Linux, \\ is a literal filename character, not a path separator.
65
- // path.normalize leaves it intact and the resolved path stays within rootDir.
66
- // The file simply does not exist → 404 (not a traversal escape).
67
- const res = await supertest(server).get('/..%5C..%5Cetc%5Chosts');
68
- expect([403, 404]).toContain(res.status);
69
- // On Windows, path.normalize converts \ to / and the traversal check catches it → 403.
70
- });
71
- });
72
-
73
- // ─── Status Code 404 ─────────────────────────────────────────────────────────
74
-
75
- describe('Bug Tests - Status Code 404', () => {
76
- let app;
77
- let server;
78
-
79
- beforeAll(() => {
80
- app = new Koa();
81
- app.use(koaClassicServer(rootDir, {
82
- dirListing: { enabled: true }
83
- }));
84
- server = app.listen();
85
- });
86
-
87
- test('FIXED: Non-existent file returns 404', async () => {
88
- const res = await supertest(server).get('/file-che-non-esiste-xyz123.txt');
89
- expect(res.status).toBe(404);
90
- expect(res.text).toContain('Not Found');
91
- });
92
-
93
- test('FIXED: Directory with dirListing.enabled=false returns 404', async () => {
94
- const app2 = new Koa();
95
- app2.use(koaClassicServer(rootDir, {
96
- dirListing: { enabled: false }
97
- }));
98
- const server2 = app2.listen();
99
-
100
- const res = await supertest(server2).get('/');
101
- expect(res.status).toBe(404);
102
-
103
- server2.close();
104
- });
105
-
106
- afterAll(() => {
107
- server.close();
108
- });
109
- });
110
-
111
- // ─── Template Rendering Errors ────────────────────────────────────────────────
112
-
113
- describe('Bug Tests - Template Rendering Errors', () => {
114
- test('FIXED: Template render error returns 500 HTML page, server does not crash', async () => {
115
- const app = new Koa();
116
-
117
- const brokenRender = async (ctx, next, filePath) => {
118
- throw new Error('Simulated template rendering error');
119
- };
120
-
121
- app.use(koaClassicServer(rootDir, {
122
- template: {
123
- render: brokenRender,
124
- ext: ['txt']
125
- }
126
- }));
127
-
128
- const server = app.listen();
129
- const testFile = path.join(rootDir, 'test-template.txt');
130
- fs.writeFileSync(testFile, 'test content');
131
-
132
- try {
133
- const res = await supertest(server).get('/test-template.txt');
134
-
135
- expect(res.status).toBe(500);
136
- expect(res.headers['content-type']).toMatch(/html/);
137
- expect(res.text).toContain('Internal Server Error');
138
- } finally {
139
- if (fs.existsSync(testFile)) fs.unlinkSync(testFile);
140
- server.close();
141
- }
142
- });
143
- });
144
-
145
- // ─── File Extension Extraction ────────────────────────────────────────────────
146
-
147
- describe('Bug Tests - File Extension Extraction', () => {
148
- let app;
149
- let server;
150
-
151
- beforeAll(() => {
152
- app = new Koa();
153
-
154
- const trackingRender = async (ctx, next, filePath) => {
155
- ctx.body = 'Rendered: ' + path.basename(filePath);
156
- };
157
-
158
- app.use(koaClassicServer(rootDir, {
159
- template: {
160
- render: trackingRender,
161
- ext: ['txt']
162
- }
163
- }));
164
-
165
- server = app.listen();
166
- });
167
-
168
- afterAll(() => {
169
- server.close();
170
- });
171
-
172
- test('FIXED: File without extension does not trigger template rendering', async () => {
173
- const testFile = path.join(rootDir, 'README');
174
- fs.writeFileSync(testFile, 'readme content');
175
-
176
- try {
177
- const res = await supertest(server).get('/README');
178
- const responseBody = res.text !== undefined ? res.text : res.body.toString('utf8');
179
- expect(responseBody).not.toContain('Rendered');
180
- } finally {
181
- if (fs.existsSync(testFile)) fs.unlinkSync(testFile);
182
- }
183
- });
184
-
185
- test('Unix hidden file (.gitignore) is served as a regular file, not rendered', async () => {
186
- // dotFiles are hidden by default; make them visible for this test so we can
187
- // verify that the extension check (not the template renderer) handles the file.
188
- const appVisible = new Koa();
189
- appVisible.use(koaClassicServer(rootDir, {
190
- hidden: { dotFiles: { default: 'visible' } },
191
- template: {
192
- render: async (ctx, next, filePath) => { ctx.body = 'Rendered: ' + path.basename(filePath); },
193
- ext: ['txt']
194
- }
195
- }));
196
- const serverVisible = appVisible.listen();
197
-
198
- const testFile = path.join(rootDir, '.gitignore');
199
- fs.writeFileSync(testFile, 'node_modules/');
200
-
201
- try {
202
- const res = await supertest(serverVisible).get('/.gitignore');
203
- // .gitignore has no .txt extension — template must not be invoked
204
- expect(res.status).toBe(200);
205
- const responseBody = res.text !== undefined ? res.text : res.body.toString('utf8');
206
- expect(responseBody).not.toContain('Rendered');
207
- expect(responseBody).toContain('node_modules/');
208
- } finally {
209
- if (fs.existsSync(testFile)) fs.unlinkSync(testFile);
210
- serverVisible.close();
211
- }
212
- });
213
- });
214
-
215
- // ─── Race Condition File Access ───────────────────────────────────────────────
216
- //
217
- // BEHAVIOUR GUARANTEE (not tested — timing is non-deterministic):
218
- // If a file is deleted between the access check and the stream open,
219
- // the stream error handler fires and — if headers have not been sent yet —
220
- // the server returns 500. When rawFile cache is warm the race cannot occur
221
- // because the file is served entirely from memory.
222
- //
223
- // ─────────────────────────────────────────────────────────────────────────────
224
-
225
- // ─── Directory Read Errors ────────────────────────────────────────────────────
226
-
227
- describe('Bug Tests - Directory Read Errors', () => {
228
- test('FIXED: Unreadable directory returns 500, server does not crash', async () => {
229
- // Skip on Windows (chmod has no effect) and when running as root
230
- // (root ignores permission bits, so chmod 0o000 has no effect).
231
- if (process.platform === 'win32') return;
232
- if (typeof process.getuid === 'function' && process.getuid() === 0) return;
233
-
234
- const app = new Koa();
235
- const tempDir = path.join(rootDir, 'temp-perm-test-dir');
236
- if (!fs.existsSync(tempDir)) fs.mkdirSync(tempDir);
237
-
238
- app.use(koaClassicServer(rootDir, { dirListing: { enabled: true } }));
239
- const server = app.listen();
240
-
241
- try {
242
- const res1 = await supertest(server).get('/temp-perm-test-dir');
243
- expect(res1.status).toBe(200);
244
-
245
- fs.chmodSync(tempDir, 0o000);
246
- const res2 = await supertest(server).get('/temp-perm-test-dir');
247
- expect([403, 500]).toContain(res2.status);
248
- } finally {
249
- try { fs.chmodSync(tempDir, 0o755); } catch { /* ignore */ }
250
- if (fs.existsSync(tempDir)) fs.rmdirSync(tempDir);
251
- server.close();
252
- }
253
- });
254
- });
255
-
256
- // ─── Content-Disposition ──────────────────────────────────────────────────────
257
-
258
- describe('Bug Tests - Content-Disposition', () => {
259
- let app;
260
- let server;
261
-
262
- beforeAll(() => {
263
- app = new Koa();
264
- app.use(koaClassicServer(rootDir));
265
- server = app.listen();
266
- });
267
-
268
- afterAll(() => {
269
- server.close();
270
- });
271
-
272
- test('FIXED: Filename with spaces is quoted in Content-Disposition', async () => {
273
- const testFile = path.join(rootDir, 'file with spaces.txt');
274
- fs.writeFileSync(testFile, 'test content');
275
-
276
- try {
277
- const res = await supertest(server).get('/file%20with%20spaces.txt');
278
- const contentDisp = res.headers['content-disposition'];
279
- // quoted-string form must wrap the filename in double quotes
280
- expect(contentDisp).toMatch(/filename="file with spaces\.txt"/);
281
- } finally {
282
- if (fs.existsSync(testFile)) fs.unlinkSync(testFile);
283
- }
284
- });
285
-
286
- test('FIXED: Filename with special chars has RFC 5987 extended form', async () => {
287
- const testFile = path.join(rootDir, 'file with spaces & special.txt');
288
- fs.writeFileSync(testFile, 'test content');
289
-
290
- try {
291
- const res = await supertest(server).get('/file%20with%20spaces%20%26%20special.txt');
292
- const contentDisp = res.headers['content-disposition'];
293
- // RFC 5987 extended form must be present with UTF-8 encoding prefix
294
- expect(contentDisp).toMatch(/filename\*=UTF-8''/);
295
- // The & must be percent-encoded as %26 in the extended form
296
- expect(contentDisp).toContain('%26');
297
- } finally {
298
- if (fs.existsSync(testFile)) fs.unlinkSync(testFile);
299
- }
300
- });
301
-
302
- test('FIXED: Filename with double-quote is safely escaped', async () => {
303
- // Create file with a double-quote in its name (valid on Linux)
304
- let testFile;
305
- try {
306
- testFile = path.join(rootDir, 'file"name.txt');
307
- fs.writeFileSync(testFile, 'test');
308
- } catch {
309
- // Some filesystems disallow " in filenames — skip gracefully
310
- return;
311
- }
312
-
313
- try {
314
- const res = await supertest(server).get('/file%22name.txt');
315
- const contentDisp = res.headers['content-disposition'];
316
- // The " inside the quoted-string must be escaped as \"
317
- expect(contentDisp).toMatch(/filename="file\\"name\.txt"/);
318
- } finally {
319
- if (fs.existsSync(testFile)) fs.unlinkSync(testFile);
320
- }
321
- });
322
- });
@@ -1 +0,0 @@
1
- BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
@@ -1 +0,0 @@
1
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA