Skip to content

Commit af0468f

Browse files
committed
Add file extension to generated paths on POST.
This commit appends an extension to generated paths for POST requests, reflecting the Content-Type of the request. This enables the server to serve that file back with the correct content type. For example, if a text/html request is posted, it will receive the .html extension and will hence be served back as text/html. Addresses the POST case of #275.
1 parent d771b94 commit af0468f

File tree

3 files changed

+102
-41
lines changed

3 files changed

+102
-41
lines changed

lib/handlers/post.js

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ var path = require('path')
66
var header = require('../header')
77
var patch = require('./patch')
88
var error = require('../http-error')
9+
var { extensions } = require('mime-types')
910

1011
function handler (req, res, next) {
1112
var ldp = req.app.locals.ldp
@@ -80,20 +81,19 @@ function handler (req, res, next) {
8081

8182
function one () {
8283
debug('Receving one file')
83-
var linkHeader = header.parseMetadataFromHeader(req.get('Link'))
84-
var slug = req.get('Slug')
85-
ldp.post(
86-
req.hostname,
87-
containerPath,
88-
slug,
89-
req,
90-
linkHeader.isBasicContainer,
84+
const { slug, link, 'content-type': contentType } = req.headers
85+
const links = header.parseMetadataFromHeader(link)
86+
const mimeType = contentType ? contentType.replace(/\s*;.*/, '') : ''
87+
const extension = mimeType in extensions ? `.${extensions[mimeType][0]}` : ''
88+
89+
ldp.post(req.hostname, containerPath, req,
90+
{ slug, extension, container: links.isBasicContainer },
9191
function (err, resourcePath) {
9292
if (err) {
9393
return next(err)
9494
}
9595
debug('File stored in ' + resourcePath)
96-
header.addLinks(res, linkHeader)
96+
header.addLinks(res, links)
9797
res.set('Location', resourcePath)
9898
res.sendStatus(201)
9999
next()

lib/ldp.js

Lines changed: 25 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ var ldpContainer = require('./ldp-container')
1717
var parse = require('./utils').parse
1818

1919
const DEFAULT_CONTENT_TYPE = 'text/turtle'
20+
const DEFAULT_EXTENSION = '.ttl'
2021

2122
const RDF_MIME_TYPES = [
2223
'text/turtle', // .ttl
@@ -184,7 +185,7 @@ class LDP {
184185
.then(result => callback(null, result), callback)
185186
}
186187

187-
post (hostname, containerPath, slug, stream, container, callback) {
188+
post (host, containerPath, stream, { container, slug, extension }, callback) {
188189
var ldp = this
189190
debug.handlers('POST -- On parent: ' + containerPath)
190191
// prepare slug
@@ -195,8 +196,10 @@ class LDP {
195196
return
196197
}
197198
}
199+
// No need to use the default extension, as we know its MIME type already
200+
if (extension === DEFAULT_EXTENSION) extension = ''
198201
// TODO: possibly package this in ldp.post
199-
ldp.getAvailablePath(hostname, containerPath, slug, function (resourcePath) {
202+
ldp.getAvailablePath(host, containerPath, { slug, extension }).then(resourcePath => {
200203
debug.handlers('POST -- Will create at: ' + resourcePath)
201204
let originalPath = resourcePath
202205
if (container) {
@@ -206,7 +209,7 @@ class LDP {
206209
originalPath += '/'
207210
}
208211
}
209-
ldp.put(hostname, resourcePath, stream, function (err) {
212+
ldp.put(host, resourcePath, stream, function (err) {
210213
if (err) callback(err)
211214
callback(null, originalPath)
212215
})
@@ -281,15 +284,15 @@ class LDP {
281284
})
282285
}
283286

284-
exists (host, reqPath, callback) {
285-
var options = {
286-
'hostname': host,
287-
'path': reqPath,
288-
'baseUri': undefined,
289-
'includeBody': false,
290-
'possibleRDFType': undefined
287+
exists (hostname, path, callback) {
288+
const options = { hostname, path, includeBody: false }
289+
if (callback) {
290+
return this.get(options, callback)
291+
} else {
292+
return new Promise((resolve, reject) => {
293+
this.get(options, err => err ? reject(err) : resolve(true))
294+
})
291295
}
292-
this.get(options, callback)
293296
}
294297

295298
/**
@@ -478,27 +481,18 @@ class LDP {
478481
})
479482
}
480483

481-
getAvailablePath (host, containerURI, slug, callback) {
482-
var self = this
483-
slug = slug || uuid.v1()
484-
485-
function ensureNotExists (newPath) {
486-
return new Promise(resolve => {
487-
self.exists(host, newPath, function (err) {
488-
// If an error occurred, the resource does not exist yet
489-
if (err) {
490-
resolve(newPath)
491-
// Otherwise, generate a new path
492-
} else {
493-
const id = uuid.v1().split('-')[ 0 ] + '-'
494-
newPath = path.join(containerURI, id + slug)
495-
resolve(ensureNotExists(newPath))
496-
}
497-
})
498-
})
484+
getAvailablePath (host, containerURI, { slug = uuid.v1(), extension }) {
485+
function ensureNotExists (self, newPath) {
486+
// Verify whether the new path already exists
487+
return self.exists(host, newPath).then(
488+
// If it does, generate another one
489+
() => ensureNotExists(self, path.join(containerURI,
490+
`${uuid.v1().split('-')[0]}-${slug}${extension}`)),
491+
// If not, we found an appropriate path
492+
() => newPath
493+
)
499494
}
500-
501-
return ensureNotExists(path.join(containerURI, slug)).then(callback)
495+
return ensureNotExists(this, path.join(containerURI, slug + extension))
502496
}
503497
}
504498
module.exports = LDP

test/integration/http-test.js

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ var ldpServer = ldnode.createServer({
1616
webid: false
1717
})
1818
var server = supertest(ldpServer)
19-
var assert = require('chai').assert
19+
var { assert, expect } = require('chai')
2020

2121
/**
2222
* Creates a new test basic container via an LDP POST
@@ -608,6 +608,73 @@ describe('HTTP APIs', function () {
608608
})
609609
})
610610

611+
describe('content-type-based file extensions', () => {
612+
// ensure the container exists
613+
before(() =>
614+
server.post('/post-tests/')
615+
.send(postRequest1Body)
616+
.set('content-type', 'text/turtle')
617+
)
618+
619+
describe('a new text/turtle document posted without slug', () => {
620+
let response
621+
before(() =>
622+
server.post('/post-tests/')
623+
.set('content-type', 'text/turtle; charset=utf-8')
624+
.then(res => { response = res })
625+
)
626+
627+
it('is assigned an extensionless URL', () => {
628+
expect(response.headers).to.have.property('location')
629+
expect(response.headers.location).to.match(/^\/post-tests\/[^./]+$/)
630+
})
631+
})
632+
633+
describe('a new text/turtle document posted with a slug', () => {
634+
let response
635+
before(() =>
636+
server.post('/post-tests/')
637+
.set('slug', 'slug1')
638+
.set('content-type', 'text/turtle; charset=utf-8')
639+
.then(res => { response = res })
640+
)
641+
642+
it('is assigned an extensionless URL', () => {
643+
expect(response.headers).to.have.property('location')
644+
expect(response.headers.location).to.match(/^\/post-tests\/slug1+$/)
645+
})
646+
})
647+
648+
describe('a new text/html document posted without slug', () => {
649+
let response
650+
before(() =>
651+
server.post('/post-tests/')
652+
.set('content-type', 'text/html; charset=utf-8')
653+
.then(res => { response = res })
654+
)
655+
656+
it('is assigned an URL with the .html extension', () => {
657+
expect(response.headers).to.have.property('location')
658+
expect(response.headers.location).to.match(/^\/post-tests\/[^./]+\.html$/)
659+
})
660+
})
661+
662+
describe('a new text/html document posted with a slug', () => {
663+
let response
664+
before(() =>
665+
server.post('/post-tests/')
666+
.set('slug', 'slug2')
667+
.set('content-type', 'text/html; charset=utf-8')
668+
.then(res => { response = res })
669+
)
670+
671+
it('is assigned an URL with the .html extension', () => {
672+
expect(response.headers).to.have.property('location')
673+
expect(response.headers.location).to.match(/^\/post-tests\/slug2+\.html$/)
674+
})
675+
})
676+
})
677+
611678
/* No, URLs are NOT ex-encoded to make filenames -- the other way around.
612679
it('should create a container with a url name', (done) => {
613680
let containerName = 'https://example.com/page'

0 commit comments

Comments
 (0)