hypercore-fetch 9.4.0 → 9.5.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/README.md +12 -0
- package/index.js +19 -1
- package/package.json +1 -1
- package/test.js +55 -1
package/README.md
CHANGED
|
@@ -53,6 +53,10 @@ Thus you can get the previous version of a file by using `hyper://NAME/$/version
|
|
|
53
53
|
|
|
54
54
|
If the resource is a file, it may contain the `Last-Modified` header if the file has had a `metadata.mtime` flag set upon update.
|
|
55
55
|
|
|
56
|
+
If the resource is a directory, it will contain the `Allow` header to
|
|
57
|
+
indicate whether a hyperdrive is writable (`'HEAD,GET'`) or not
|
|
58
|
+
(`'HEAD,GET,PUT,DELETE'`).
|
|
59
|
+
|
|
56
60
|
### `fetch('hyper://NAME/example.txt', {method: 'GET'})`
|
|
57
61
|
|
|
58
62
|
This will attempt to load `example.txt` from the archive labeled by `NAME`.
|
|
@@ -124,6 +128,9 @@ The `body` can be any of the options supported by the Fetch API such as a `Strin
|
|
|
124
128
|
|
|
125
129
|
Note that this is only available with the `writable: true` flag.
|
|
126
130
|
|
|
131
|
+
An attempt to `PUT` a file to a hyperdrive which is not writable will
|
|
132
|
+
fail with status `403`.
|
|
133
|
+
|
|
127
134
|
### `fetch('hyper://NAME/folder/', {method: 'PUT', body: new FormData()})`
|
|
128
135
|
|
|
129
136
|
You can add multiple files to a folder using the `PUT` method with a [FormData](https://developer.mozilla.org/en-US/docs/Web/API/FormData) body.
|
|
@@ -141,6 +148,11 @@ You can delete a file or directory tree in a Hyperdrive by using the `DELETE` me
|
|
|
141
148
|
|
|
142
149
|
`NAME` can either be the 52 character [z32 encoded](https://github.com/mafintosh/z32) key for a Hyperdrive or Hypercore , or a domain to parse with the [DNSLink](https://www.dnslink.io/) standard.
|
|
143
150
|
|
|
151
|
+
Note that this is only available with the `writable: true` flag.
|
|
152
|
+
|
|
153
|
+
An attempt to `DELETE` a file in a hyperdrive which is not writable
|
|
154
|
+
will fail with status `403`.
|
|
155
|
+
|
|
144
156
|
### `fetch('hyper://NAME/$/extensions/')`
|
|
145
157
|
|
|
146
158
|
You can list the current [hypercore extensions](https://github.com/hypercore-protocol/hypercore#ext--feedregisterextensionname-handlers) that are enabled by doing a `GET` on the `/$/extensions/` directory.
|
package/index.js
CHANGED
|
@@ -89,7 +89,9 @@ export default async function makeHyperFetch ({
|
|
|
89
89
|
router.get(`hyper://${SPECIAL_DOMAIN}/`, getKey)
|
|
90
90
|
router.post(`hyper://${SPECIAL_DOMAIN}/`, createKey)
|
|
91
91
|
|
|
92
|
+
router.put(`hyper://*/${SPECIAL_FOLDER}/${VERSION_FOLDER_NAME}/**`, putFilesVersioned)
|
|
92
93
|
router.put('hyper://*/**', putFiles)
|
|
94
|
+
router.delete(`hyper://*/${SPECIAL_FOLDER}/${VERSION_FOLDER_NAME}/**`, deleteFilesVersioned)
|
|
93
95
|
router.delete('hyper://*/**', deleteFiles)
|
|
94
96
|
}
|
|
95
97
|
|
|
@@ -361,6 +363,10 @@ export default async function makeHyperFetch ({
|
|
|
361
363
|
|
|
362
364
|
const drive = await getDrive(`hyper://${hostname}`)
|
|
363
365
|
|
|
366
|
+
if (!drive.db.feed.writable) {
|
|
367
|
+
return { status: 403, body: `Cannot PUT file to read-only drive: ${drive.url}`, headers: { Location: request.url } }
|
|
368
|
+
}
|
|
369
|
+
|
|
364
370
|
if (isFormData) {
|
|
365
371
|
// It's a form! Get the files out and process them
|
|
366
372
|
const formData = await request.formData()
|
|
@@ -394,12 +400,20 @@ export default async function makeHyperFetch ({
|
|
|
394
400
|
return { status: 201, headers: { Location: request.url } }
|
|
395
401
|
}
|
|
396
402
|
|
|
403
|
+
function putFilesVersioned (request) {
|
|
404
|
+
return { status: 405, body: 'Cannot PUT file to old version', headers: { Location: request.url } }
|
|
405
|
+
}
|
|
406
|
+
|
|
397
407
|
async function deleteFiles (request) {
|
|
398
408
|
const { hostname, pathname: rawPathname } = new URL(request.url)
|
|
399
409
|
const pathname = decodeURI(ensureLeadingSlash(rawPathname))
|
|
400
410
|
|
|
401
411
|
const drive = await getDrive(`hyper://${hostname}`)
|
|
402
412
|
|
|
413
|
+
if (!drive.db.feed.writable) {
|
|
414
|
+
return { status: 403, body: `Cannot DELETE file in read-only drive: ${drive.url}`, headers: { Location: request.url } }
|
|
415
|
+
}
|
|
416
|
+
|
|
403
417
|
if (pathname.endsWith('/')) {
|
|
404
418
|
let didDelete = false
|
|
405
419
|
for await (const entry of drive.list(pathname)) {
|
|
@@ -422,6 +436,10 @@ export default async function makeHyperFetch ({
|
|
|
422
436
|
return { status: 200 }
|
|
423
437
|
}
|
|
424
438
|
|
|
439
|
+
function deleteFilesVersioned (request) {
|
|
440
|
+
return { status: 405, body: 'Cannot DELETE old version', headers: { Location: request.url } }
|
|
441
|
+
}
|
|
442
|
+
|
|
425
443
|
async function headFilesVersioned (request) {
|
|
426
444
|
const url = new URL(request.url)
|
|
427
445
|
const { hostname, pathname: rawPathname, searchParams } = url
|
|
@@ -460,7 +478,7 @@ export default async function makeHyperFetch ({
|
|
|
460
478
|
const isDirectory = pathname.endsWith('/')
|
|
461
479
|
const fullURL = new URL(pathname, drive.url).href
|
|
462
480
|
|
|
463
|
-
const isWritable = writable && drive.writable
|
|
481
|
+
const isWritable = writable && drive.db.feed.writable
|
|
464
482
|
|
|
465
483
|
const Allow = isWritable ? BASIC_METHODS.concat(WRITABLE_METHODS) : BASIC_METHODS
|
|
466
484
|
|
package/package.json
CHANGED
package/test.js
CHANGED
|
@@ -484,7 +484,7 @@ test('Error on invalid hostname', async (t) => {
|
|
|
484
484
|
}
|
|
485
485
|
})
|
|
486
486
|
|
|
487
|
-
test('
|
|
487
|
+
test('Old versions in VERSION folder', async (t) => {
|
|
488
488
|
const created = await nextURL(t)
|
|
489
489
|
|
|
490
490
|
const fileName = 'example.txt'
|
|
@@ -512,6 +512,27 @@ test('GET older version of file from VERSION folder', async (t) => {
|
|
|
512
512
|
await checkResponse(versionedRootResponse, t, 'Able to GET versioned root')
|
|
513
513
|
const versionedRootContents = await versionedRootResponse.json()
|
|
514
514
|
t.deepEqual(versionedRootContents, [], 'Old root content got loaded')
|
|
515
|
+
|
|
516
|
+
// PUT on old version should fail
|
|
517
|
+
const putResponse = await fetch(versionFileURL, {
|
|
518
|
+
method: 'PUT',
|
|
519
|
+
body: SAMPLE_CONTENT
|
|
520
|
+
})
|
|
521
|
+
if (putResponse.ok) {
|
|
522
|
+
throw new Error('PUT old version of file should have failed')
|
|
523
|
+
} else {
|
|
524
|
+
t.equal(putResponse.status, 405, 'PUT old version returned status 405 Not Allowed')
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// DELETE on old version should fail
|
|
528
|
+
const deleteResponse = await fetch(versionFileURL, {
|
|
529
|
+
method: 'delete'
|
|
530
|
+
})
|
|
531
|
+
if (deleteResponse.ok) {
|
|
532
|
+
throw new Error('DELETE old version of file should have failed')
|
|
533
|
+
} else {
|
|
534
|
+
t.equal(deleteResponse.status, 405, 'DELETE old version returned status 405 Not Allowed')
|
|
535
|
+
}
|
|
515
536
|
})
|
|
516
537
|
|
|
517
538
|
test('Handle empty string pathname', async (t) => {
|
|
@@ -565,6 +586,39 @@ test('Handle empty string pathname', async (t) => {
|
|
|
565
586
|
t.deepEqual(await versionedGetResponse.json(), ['example.txt', 'example2.txt'], 'Returns root directory prior to DELETE')
|
|
566
587
|
})
|
|
567
588
|
|
|
589
|
+
test('Return status 403 Forbidden on attempt to modify read-only hyperdrive', async (t) => {
|
|
590
|
+
const readOnlyURL = 'hyper://blog.mauve.moe/new-file.txt'
|
|
591
|
+
const putResponse = await fetch(readOnlyURL, { method: 'PUT', body: SAMPLE_CONTENT })
|
|
592
|
+
if (putResponse.ok) {
|
|
593
|
+
throw new Error('PUT file to read-only drive should have failed')
|
|
594
|
+
} else {
|
|
595
|
+
t.equal(putResponse.status, 403, 'PUT file to read-only drive returned status 403 Forbidden')
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
const deleteResponse = await fetch(readOnlyURL, { method: 'DELETE' })
|
|
599
|
+
if (deleteResponse.ok) {
|
|
600
|
+
throw new Error('DELETE file in read-only drive should have failed')
|
|
601
|
+
} else {
|
|
602
|
+
t.equal(deleteResponse.status, 403, 'DELETE file to read-only drive returned status 403 Forbidden')
|
|
603
|
+
}
|
|
604
|
+
})
|
|
605
|
+
|
|
606
|
+
test('Check hyperdrive writability', async (t) => {
|
|
607
|
+
const created = await nextURL(t)
|
|
608
|
+
|
|
609
|
+
const readOnlyRootDirectory = 'hyper://blog.mauve.moe/?noResolve'
|
|
610
|
+
const readOnlyHeadResponse = await fetch(readOnlyRootDirectory, { method: 'HEAD' })
|
|
611
|
+
await checkResponse(readOnlyHeadResponse, t, 'Able to load HEAD')
|
|
612
|
+
const readOnlyHeadersAllow = readOnlyHeadResponse.headers.get('Allow')
|
|
613
|
+
t.equal(readOnlyHeadersAllow, 'HEAD,GET', 'Expected read-only Allows header')
|
|
614
|
+
|
|
615
|
+
const writableRootDirectory = new URL('/', created)
|
|
616
|
+
const writableHeadResponse = await fetch(writableRootDirectory, { method: 'HEAD' })
|
|
617
|
+
await checkResponse(writableHeadResponse, t, 'Able to load HEAD')
|
|
618
|
+
const writableHeadersAllow = writableHeadResponse.headers.get('Allow')
|
|
619
|
+
t.equal(writableHeadersAllow, 'HEAD,GET,PUT,DELETE', 'Expected writable Allows header')
|
|
620
|
+
})
|
|
621
|
+
|
|
568
622
|
async function checkResponse (response, t, successMessage = 'Response OK') {
|
|
569
623
|
if (!response.ok) {
|
|
570
624
|
const message = await response.text()
|