1 | /*
|
2 | * Copyright 2018 Adobe. All rights reserved.
|
3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
4 | * you may not use this file except in compliance with the License. You may obtain a copy
|
5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0
|
6 | *
|
7 | * Unless required by applicable law or agreed to in writing, software distributed under
|
8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
9 | * OF ANY KIND, either express or implied. See the License for the specific language
|
10 | * governing permissions and limitations under the License.
|
11 | */
|
12 |
|
13 | ;
|
14 |
|
15 | const mime = require('mime');
|
16 | const path = require('path');
|
17 | const { debug } = require('@adobe/helix-log');
|
18 | const {
|
19 | isCheckedOut, resolveBlob, createBlobReadStream, determineRefPathName,
|
20 | } = require('./git');
|
21 | const { resolveRepositoryPath } = require('./utils');
|
22 |
|
23 | /**
|
24 | * Export the raw content handler (express middleware) through a parameterizable function
|
25 | *
|
26 | * @param {object} options configuration hash
|
27 | * @returns {function(*, *, *)} handler function
|
28 | */
|
29 | function createMiddleware(options) {
|
30 | /**
|
31 | * Express middleware handling raw content requests
|
32 | *
|
33 | * @param {Request} req Request object
|
34 | * @param {Response} res Response object
|
35 | * @param {callback} next next middleware in chain
|
36 | */
|
37 | return async (req, res, next) => {
|
38 | const { owner } = req.params;
|
39 | const repoName = req.params.repo;
|
40 | let refName = req.params.ref;
|
41 | let fpath = req.params[0];
|
42 |
|
43 | let repPath = resolveRepositoryPath(options, owner, repoName);
|
44 | // temporary fix until isomorphic git can handle windows paths
|
45 | // see https://github.com/isomorphic-git/isomorphic-git/issues/783
|
46 | repPath = repPath.replace(/\\/g, '/');
|
47 |
|
48 | // issue: #53: handle branch names containing '/' (e.g. 'foo/bar')
|
49 | const parsed = await determineRefPathName(repPath, `${req.params.ref}/${req.params[0]}`);
|
50 | if (parsed) {
|
51 | refName = parsed.ref;
|
52 | fpath = parsed.pathName;
|
53 | }
|
54 |
|
55 | // issue #68: lenient handling of redundant slashes in path
|
56 | fpath = path.normalize(fpath);
|
57 | // temporary fix until isomorphic git can handle windows paths
|
58 | // see https://github.com/isomorphic-git/isomorphic-git/issues/783
|
59 | fpath = fpath.replace(/\\/g, '/');
|
60 |
|
61 | // remove leading slash
|
62 | if (fpath.length && fpath[0] === '/') {
|
63 | fpath = fpath.substr(1);
|
64 | }
|
65 |
|
66 | // project-helix/#187: serve modified content only if the requested ref is currently checked out
|
67 | isCheckedOut(repPath, refName)
|
68 | .then((serveUncommitted) => resolveBlob(repPath, refName, fpath, serveUncommitted))
|
69 | .then((oid) => {
|
70 | const mimeType = mime.getType(fpath) || 'text/plain';
|
71 | res.writeHead(200, {
|
72 | 'Content-Type': mimeType,
|
73 | ETag: oid,
|
74 | // TODO: review cache-control header
|
75 | 'Cache-Control': 'max-age=0, private, must-revalidate',
|
76 | });
|
77 | createBlobReadStream(repPath, oid)
|
78 | .then((stream) => stream.pipe(res));
|
79 | })
|
80 | .catch((err) => {
|
81 | // TODO: use generic errors
|
82 | if (err.code === 'TreeOrBlobNotFoundError' || err.code === 'ResolveRefError') {
|
83 | debug(`[rawHandler] resource not found: ${err.message}`);
|
84 | res.status(404).send('not found.');
|
85 | } else {
|
86 | debug(`[rawHandler] code: ${err.code} message: ${err.message} stack: ${err.stack}`);
|
87 | next(err);
|
88 | }
|
89 | });
|
90 | };
|
91 | }
|
92 | module.exports = createMiddleware;
|