forked from ipfs/ipfs-companion
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request ipfs#323 from tableflip/feat/dir-view
Adds self served directory listings! 🚀
- Loading branch information
Showing
6 changed files
with
301 additions
and
308 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
'use strict' | ||
|
||
const filesize = require('filesize') | ||
const mainStyle = require('ipfs/src/http/gateway/dir-view/style') | ||
|
||
function buildFilesList (path, links) { | ||
const rows = links.map((link) => { | ||
let row = [ | ||
`<div class="ipfs-icon ipfs-_blank"> </div>`, | ||
`<a href="${path}${path.endsWith('/') ? '' : '/'}${link.name}">${link.name}</a>`, | ||
filesize(link.size) | ||
] | ||
|
||
row = row.map((cell) => `<td>${cell}</td>`).join('') | ||
|
||
return `<tr>${row}</tr>` | ||
}) | ||
|
||
return rows.join('') | ||
} | ||
|
||
function isRoot (path) { | ||
// Remove leading ipfs// and trailing / and split by / | ||
const parts = path.replace(/^ipfs:\/\//, '').replace(/\/$/, '').split('/') | ||
// If there's only 1 part, then it's the hash, so we are at root | ||
return parts.length === 1 | ||
} | ||
|
||
function buildTable (path, links) { | ||
return ` | ||
<table class="table table-striped"> | ||
<tbody> | ||
${isRoot(path) ? '' : (` | ||
<tr> | ||
<td class="narrow"> | ||
<div class="ipfs-icon ipfs-_blank"> </div> | ||
</td> | ||
<td class="padding"> | ||
<a href="${path.split('/').slice(0, -1).join('/')}">..</a> | ||
</td> | ||
<td></td> | ||
</tr> | ||
`)} | ||
${buildFilesList(path, links)} | ||
</tbody> | ||
</table> | ||
` | ||
} | ||
|
||
function render (path, links) { | ||
return ` | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<meta charset="utf-8"> | ||
<title>${path}</title> | ||
<style>${mainStyle}</style> | ||
</head> | ||
<body> | ||
<div id="header" class="row"> | ||
<div class="col-xs-2"> | ||
<div id="logo" class="ipfs-logo"></div> | ||
</div> | ||
</div> | ||
<br> | ||
<div class="col-xs-12"> | ||
<div class="panel panel-default"> | ||
<div class="panel-heading"> | ||
<strong>Index of ${path}</strong> | ||
</div> | ||
${buildTable(path, links)} | ||
</div> | ||
</div> | ||
</body> | ||
</html> | ||
` | ||
} | ||
|
||
exports.render = render |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,31 +1,57 @@ | ||
const { mimeSniff } = require('./mime-sniff') | ||
const dirView = require('./dir-view') | ||
const PathUtils = require('ipfs/src/http/gateway/utils/path') | ||
|
||
exports.createIpfsUrlProtocolHandler = (getIpfs) => { | ||
return async (request, reply) => { | ||
console.time('[ipfs-companion] IpfsUrlProtocolHandler') | ||
console.log(`[ipfs-companion] handling ${request.url}`) | ||
|
||
const path = request.url.split('ipfs://')[1] | ||
let path = request.url.replace('ipfs://', '/') | ||
path = path.startsWith('/ipfs') ? path : `/ipfs${path}` | ||
|
||
const ipfs = getIpfs() | ||
|
||
try { | ||
const {data, mimeType} = await getDataAndGuessMimeType(ipfs, path) | ||
console.log(`[ipfs-companion] returning ${path} as ${mimeType}`) | ||
reply({mimeType, data}) | ||
const {data, mimeType, charset} = await getDataAndGuessMimeType(ipfs, path) | ||
console.log(`[ipfs-companion] returning ${path} as mime ${mimeType} and charset ${charset}`) | ||
reply({mimeType, data, charset}) | ||
} catch (err) { | ||
console.error('[ipfs-companion] failed to get data', err) | ||
reply({mimeType: 'text/html', data: `Error ${err.message}`}) | ||
} | ||
|
||
console.timeEnd('[ipfs-companion] IpfsUrlProtocolHandler') | ||
} | ||
} | ||
|
||
function getDataAndGuessMimeType (ipfs, path) { | ||
return new Promise((resolve, reject) => { | ||
ipfs.files.cat(path, (err, res) => { | ||
if (err) return reject(err) | ||
const mimeType = mimeSniff(res, path) | ||
resolve({mimeType, data: res.toString('utf8')}) | ||
}) | ||
}) | ||
async function getDataAndGuessMimeType (ipfs, path) { | ||
let data | ||
|
||
try { | ||
data = await ipfs.files.cat(path) | ||
} catch (err) { | ||
if (err.message.toLowerCase() === 'this dag node is a directory') { | ||
return getDirectoryListingOrIndexData(ipfs, path) | ||
} | ||
throw err | ||
} | ||
|
||
const mimeType = mimeSniff(data, path) || 'text/plain' | ||
return {mimeType, data: data.toString('utf8'), charset: 'utf8'} | ||
} | ||
|
||
async function getDirectoryListingOrIndexData (ipfs, path) { | ||
const listing = await ipfs.ls(path) | ||
const index = listing.find((l) => ['index', 'index.html', 'index.htm'].includes(l.name)) | ||
|
||
if (index) { | ||
return getDataAndGuessMimeType(ipfs, PathUtils.joinURLParts(path, index.name)) | ||
} | ||
|
||
return { | ||
mimeType: 'text/html', | ||
data: dirView.render(path.replace(/^\/ipfs\//, 'ipfs://'), listing), | ||
charset: 'utf8' | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
'use strict' | ||
const { describe, it } = require('mocha') | ||
const { expect } = require('chai') | ||
const { createIpfsUrlProtocolHandler } = require('../../../add-on/src/lib/ipfs-protocol') | ||
|
||
describe('ipfs-protocol', () => { | ||
it('should serve an IPFS file', async () => { | ||
const url = 'ipfs://QmQxeMcbqW9npq5h5kyE2iPECR9jxJF4j5x4bSRQ2phLY4' | ||
const content = 'TEST' + Date.now() | ||
const ipfs = { files: { cat: () => Promise.resolve(content) } } | ||
const handler = createIpfsUrlProtocolHandler(() => ipfs) | ||
const request = { url } | ||
|
||
const res = await new Promise(async (resolve, reject) => { | ||
try { | ||
await handler(request, resolve) | ||
} catch (err) { | ||
reject(err) | ||
} | ||
}) | ||
|
||
expect(res.data).to.equal(content) | ||
}) | ||
|
||
it('should serve a directory listing', async () => { | ||
const url = 'ipfs://QmQxeMcbqW9npq5h5kyE2iPECR9jxJF4j5x4bSRQ2phLY4' | ||
const links = [ | ||
{ name: `one${Date.now()}`, size: Date.now() }, | ||
{ name: `two${Date.now()}`, size: Date.now() }, | ||
{ name: `three${Date.now()}`, size: Date.now() } | ||
] | ||
const ipfs = { | ||
files: { cat: () => Promise.reject(new Error('this dag node is a directory')) }, | ||
ls: () => Promise.resolve(links) | ||
} | ||
const handler = createIpfsUrlProtocolHandler(() => ipfs) | ||
const request = { url } | ||
|
||
const res = await new Promise(async (resolve, reject) => { | ||
try { | ||
await handler(request, resolve) | ||
} catch (err) { | ||
reject(err) | ||
} | ||
}) | ||
|
||
expect(res.mimeType).to.equal('text/html') | ||
expect(res.charset).to.equal('utf8') | ||
links.forEach((link) => expect(res.data).to.contain(`${url}/${link.name}`)) | ||
}) | ||
}) |
Oops, something went wrong.