@solid/community-server
Version:
Community Solid Server: an open and modular implementation of the Solid specifications
131 lines • 6.05 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.ExtensionBasedMapperFactory = exports.ExtensionBasedMapper = void 0;
const node_fs_1 = require("node:fs");
const mime = __importStar(require("mime-types"));
const ContentTypes_1 = require("../../util/ContentTypes");
const NotImplementedHttpError_1 = require("../../util/errors/NotImplementedHttpError");
const PathUtil_1 = require("../../util/PathUtil");
const BaseFileIdentifierMapper_1 = require("./BaseFileIdentifierMapper");
/**
* Supports the behaviour described in https://www.w3.org/DesignIssues/HTTPFilenameMapping.html
* Determines content-type based on the file extension.
* In case an identifier does not end on an extension matching its content-type,
* the corresponding file will be appended with the correct extension, preceded by $.
*/
class ExtensionBasedMapper extends BaseFileIdentifierMapper_1.BaseFileIdentifierMapper {
customTypes;
customExtensions;
constructor(base, rootFilepath, customTypes) {
super(base, rootFilepath);
// Workaround for https://github.com/LinkedSoftwareDependencies/Components.js/issues/20
if (!customTypes || Object.keys(customTypes).length === 0) {
this.customTypes = ContentTypes_1.DEFAULT_CUSTOM_TYPES;
}
else {
this.customTypes = customTypes;
}
this.customExtensions = {};
for (const [extension, contentType] of Object.entries(this.customTypes)) {
this.customExtensions[contentType] = extension;
}
}
async mapUrlToDocumentPath(identifier, filePath, contentType) {
// Would conflict with how new extensions are stored
if (/\$\.\w+$/u.test(filePath)) {
this.logger.warn(`Identifier ${identifier.path} contains a dollar sign before its extension`);
throw new NotImplementedHttpError_1.NotImplementedHttpError('Identifiers cannot contain a dollar sign before their extension');
}
// Existing file
if (!contentType) {
// Find a matching file
const [, folder, documentName] = /^(.*\/)([^/]*)$/u.exec(filePath);
let fileName;
try {
const files = await node_fs_1.promises.readdir(folder);
fileName = files.find((file) => file.startsWith(documentName) && /^(?:\$\..+)?$/u.test(file.slice(documentName.length)));
}
catch {
// Parent folder does not exist (or is not a folder)
}
if (fileName) {
filePath = (0, PathUtil_1.joinFilePath)(folder, fileName);
}
contentType = await this.getContentTypeFromPath(filePath);
// If the extension of the identifier matches a different content-type than the one that is given,
// we need to add a new extension to match the correct type.
}
else if (contentType !== await this.getContentTypeFromPath(filePath)) {
let extension = mime.extension(contentType) || this.customExtensions[contentType];
if (!extension) {
// When no extension is found for the provided content-type, use a fallback extension.
extension = this.unknownMediaTypeExtension;
// Signal the fallback by setting the content-type to undefined in the output link.
contentType = undefined;
}
filePath += `$.${extension}`;
}
return super.mapUrlToDocumentPath(identifier, filePath, contentType);
}
async getDocumentUrl(relative) {
return super.getDocumentUrl(this.stripExtension(relative));
}
async getContentTypeFromPath(filePath) {
const extension = (0, PathUtil_1.getExtension)(filePath).toLowerCase();
return mime.lookup(extension) ||
this.customTypes[extension] ||
await super.getContentTypeFromPath(filePath);
}
/**
* Helper function that removes the internal extension, one starting with $., from the given path.
* Nothing happens if no such extension is present.
*/
stripExtension(path) {
const extension = (0, PathUtil_1.getExtension)(path);
if (extension && path.endsWith(`$.${extension}`)) {
path = path.slice(0, -(extension.length + 2));
}
return path;
}
}
exports.ExtensionBasedMapper = ExtensionBasedMapper;
class ExtensionBasedMapperFactory {
async create(base, rootFilePath) {
return new ExtensionBasedMapper(base, rootFilePath);
}
}
exports.ExtensionBasedMapperFactory = ExtensionBasedMapperFactory;
//# sourceMappingURL=ExtensionBasedMapper.js.map