-
-
Notifications
You must be signed in to change notification settings - Fork 81
Expand file tree
/
Copy pathrelative-to-absolute.ts
More file actions
107 lines (90 loc) · 3.68 KB
/
relative-to-absolute.ts
File metadata and controls
107 lines (90 loc) · 3.68 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
import type {SecureOptions, SecureOptionsInput} from './types';
import {URL} from 'url';
import urlJoin from './url-join';
export type RelativeToAbsoluteOptions = SecureOptions;
export type RelativeToAbsoluteOptionsInput = SecureOptionsInput;
// NOTE: Ghost's relative->absolute handling is a little strange when the rootUrl
// includes a subdirectory. Root-relative paths such as /content/image.jpg are
// actually treated as subdirectory-relative. This means that it's possible to
// migrate from a root config to a subdirectory config without migrating data
// in the database, _however_ that means that the database will now have a mix
// of path styles (/content/image.png and /subdir/content/image.png). To handle
// this when all root-relative paths are treated as subdir-relative we have to
// rely on subdirectory deduplication.
/**
* Convert a root-relative path to an absolute URL based on the supplied root.
* Will _only_ convert root-relative urls (/some/path not some/path)
*
* @param {string} path
* @param {string} rootUrl
* @param {string} itemPath
* @param {object} options
* @returns {string} The passed in url or an absolute URL using
*/
const relativeToAbsolute = function relativeToAbsolute(
path: string,
rootUrl: string,
itemPath: string | null | RelativeToAbsoluteOptionsInput,
_options?: RelativeToAbsoluteOptionsInput
): string {
// itemPath is optional, if it's an object it may be the options param instead
let finalItemPath: string | null = null;
let finalOptions: RelativeToAbsoluteOptionsInput = _options || {};
if (typeof itemPath === 'object' && itemPath !== null && !_options) {
finalOptions = itemPath;
finalItemPath = null;
} else if (typeof itemPath === 'string') {
finalItemPath = itemPath;
}
// itemPath could be sent as a full url in which case, extract the pathname
if (finalItemPath && finalItemPath.match(/^http/)) {
const itemUrl = new URL(finalItemPath);
finalItemPath = itemUrl.pathname;
}
const defaultOptions: RelativeToAbsoluteOptionsInput = {
assetsOnly: false,
staticImageUrlPrefix: 'content/images'
};
const options = Object.assign({}, defaultOptions, finalOptions);
// return the path as-is if it's not an asset path and we're only modifying assets
if (options.assetsOnly) {
const staticImageUrlPrefixRegex = new RegExp(options.staticImageUrlPrefix as string);
if (!path.match(staticImageUrlPrefixRegex)) {
return path;
}
}
// if URL is absolute return it as-is
try {
const parsed: URL = new URL(path, 'http://relative');
if (parsed.origin !== 'http://relative') {
return path;
}
// Do not convert protocol relative URLs
if (path.lastIndexOf('//', 0) === 0) {
return path;
}
} catch {
return path;
}
// return the path as-is if it's a pure hash param
if (path.startsWith('#')) {
return path;
}
// return the path as-is if it's not root-relative and we have no itemPath
if (!finalItemPath && !path.match(/^\//)) {
return path;
}
// force root to always have a trailing-slash for consistent behaviour
if (!rootUrl.endsWith('/')) {
rootUrl = `${rootUrl}/`;
}
const parsedRootUrl: URL = new URL(rootUrl);
const basePath = path.startsWith('/') ? '' : (finalItemPath || '');
const fullPath = urlJoin([parsedRootUrl.pathname, basePath, path], {rootUrl});
const absoluteUrl = new URL(fullPath, rootUrl);
if (options.secure) {
absoluteUrl.protocol = 'https:';
}
return absoluteUrl.toString();
};
export default relativeToAbsolute;