1 | var CRYPTO = require('crypto'),
|
2 | FS = require('fs'),
|
3 | PATH = require('path'),
|
4 | configs,
|
5 | config;
|
6 |
|
7 | var minimatch = require('minimatch');
|
8 |
|
9 | if (!PATH.sep) PATH.sep = process.platform === 'win32'? '\\' : '/';
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 | var rePathSep = PATH.sep == '\\' ? '\\\\' : PATH.sep;
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 | const contentTypes = {
|
23 | '.css': 'text/css',
|
24 | '.cur': 'image/x-icon',
|
25 | '.eot': 'application/vnd.ms-fontobject',
|
26 | '.gif': 'image/gif',
|
27 | '.ico': 'image/x-icon',
|
28 | '.jpg': 'image/jpeg',
|
29 | '.jpeg': 'image/jpeg',
|
30 | '.js': 'application/javascript',
|
31 |
|
32 | '.otf': 'font/opentype',
|
33 | '.png': 'image/png',
|
34 | '.svg': 'image/svg+xml',
|
35 | '.swf': 'application/x-shockwave-flash',
|
36 | '.ttf': 'application/x-font-ttf',
|
37 | '.woff': 'application/x-font-woff',
|
38 | '.woff2': 'application/font-woff2'
|
39 | };
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 | var clearConfigCache = exports.clearConfigCache = function() {
|
46 | config = {
|
47 | paths: {},
|
48 | freezeNestingLevel: {},
|
49 | freezeWildcards: {},
|
50 | followSymlinks: {}
|
51 | };
|
52 |
|
53 | configs = {
|
54 | paths: {}
|
55 | };
|
56 | };
|
57 |
|
58 | clearConfigCache();
|
59 |
|
60 |
|
61 |
|
62 |
|
63 |
|
64 |
|
65 |
|
66 | exports.processPath = function(filePath) {
|
67 | return freeze(realpathSync(filePath));
|
68 | };
|
69 |
|
70 |
|
71 |
|
72 |
|
73 |
|
74 |
|
75 |
|
76 | var sha1Base64 = exports.sha1Base64 = function(content) {
|
77 | var sha1 = CRYPTO.createHash('sha1');
|
78 | sha1.update(content);
|
79 | return sha1.digest('base64');
|
80 | };
|
81 |
|
82 |
|
83 |
|
84 |
|
85 |
|
86 |
|
87 |
|
88 | var fixBase64 = exports.fixBase64 = function(base64) {
|
89 | return base64
|
90 | .replace(/\+/g, '-')
|
91 | .replace(/\//g, '_')
|
92 | .replace(/=/g, '')
|
93 | .replace(/^[+-]+/g, '');
|
94 | };
|
95 |
|
96 |
|
97 |
|
98 |
|
99 |
|
100 |
|
101 |
|
102 |
|
103 | var freeze = exports.freeze = function(filePath, content) {
|
104 | if (filePath !== realpathSync(filePath)) throw new Error();
|
105 |
|
106 | var _freezeDir = freezeDir(filePath);
|
107 |
|
108 | if (_freezeDir) {
|
109 | if (content === undefined) {
|
110 | if (!FS.existsSync(filePath)) throw new Error("No such file or directory: " + filePath);
|
111 | if (FS.statSync(filePath).isDirectory()) throw new Error("Is a directory (file needed): " + filePath);
|
112 |
|
113 | content = FS.readFileSync(filePath);
|
114 | }
|
115 |
|
116 | if (_freezeDir === ':base64:' || _freezeDir === ':encodeURIComponent:' || _freezeDir === ':encodeURI:') {
|
117 | var fileExtension = PATH.extname(filePath);
|
118 | var contentType = contentTypes[fileExtension];
|
119 | if (!contentType) {
|
120 | throw new Error('Freeze error. Unknown Content-type for ' + fileExtension);
|
121 | }
|
122 |
|
123 | var inlineDecl = 'data:' + contentType;
|
124 |
|
125 | if (_freezeDir === ':base64:') {
|
126 |
|
127 |
|
128 | return inlineDecl + ';base64,' + new Buffer(content).toString('base64');
|
129 | } else {
|
130 |
|
131 |
|
132 | var encodeFunc = _freezeDir === ':encodeURI:' ? encodeURI : encodeURIComponent;
|
133 | return inlineDecl + ',' + encodeFunc(new Buffer(content).toString('utf8'))
|
134 | .replace(/%20/g, ' ')
|
135 | .replace(/#/g, '%23');
|
136 | }
|
137 |
|
138 | } else {
|
139 |
|
140 | var hash = fixBase64(sha1Base64(content));
|
141 |
|
142 | var nestingLevel = configFreezeNestingLevel(_freezeDir);
|
143 | var nestingPath = getFreezeNestingPath(hash, nestingLevel);
|
144 |
|
145 | filePath = PATH.join(_freezeDir, nestingPath + PATH.extname(filePath));
|
146 |
|
147 | if (content && !FS.existsSync(filePath)) {
|
148 | save(filePath, content);
|
149 | }
|
150 | }
|
151 | }
|
152 |
|
153 | return filePath;
|
154 | };
|
155 |
|
156 |
|
157 |
|
158 |
|
159 |
|
160 |
|
161 |
|
162 | var freezeDir = exports.freezeDir = function(filePath) {
|
163 | filePath = PATH.normalize(filePath);
|
164 |
|
165 | if (filePath !== realpathSync(filePath)) {
|
166 | throw new Error();
|
167 | }
|
168 |
|
169 | loadConfig(filePath);
|
170 |
|
171 | for (var wildcard in config.freezeWildcards) {
|
172 | if (minimatch(filePath, wildcard)) {
|
173 | return config.freezeWildcards[wildcard];
|
174 | }
|
175 | }
|
176 |
|
177 | return null;
|
178 | };
|
179 |
|
180 |
|
181 |
|
182 |
|
183 |
|
184 |
|
185 |
|
186 |
|
187 |
|
188 |
|
189 |
|
190 |
|
191 | var getFreezeNestingPath = function(hash, nestingLevel) {
|
192 |
|
193 | nestingLevel = Math.min(hash.length - 1, nestingLevel);
|
194 |
|
195 | if (nestingLevel === 0) {
|
196 | return hash;
|
197 | }
|
198 |
|
199 | var hashArr = hash.split('');
|
200 | for (var i = 0; i < nestingLevel; i++) {
|
201 | hashArr.splice(i * 2 + 1, 0, '/');
|
202 | }
|
203 |
|
204 | return hashArr.join('');
|
205 | };
|
206 |
|
207 |
|
208 |
|
209 |
|
210 |
|
211 | exports.freezeAll = function(input) {
|
212 | |
213 |
|
214 |
|
215 |
|
216 | var result = {};
|
217 |
|
218 | var basePath = PATH.dirname(input);
|
219 | var stat = FS.statSync(input);
|
220 | if (stat.isFile()) {
|
221 | freezeAllProcessFile(input, process.cwd(), result);
|
222 |
|
223 | } else if (stat.isDirectory()) {
|
224 | freezeAllProcessDir(input, basePath, result);
|
225 | }
|
226 |
|
227 | return result;
|
228 | };
|
229 |
|
230 |
|
231 |
|
232 |
|
233 |
|
234 |
|
235 |
|
236 | function freezeAllProcessFile(absPath, basePath, result) {
|
237 | var url = absPath;
|
238 |
|
239 | if (freezableRe.test(url) || /\.(?:css|js|swf)$/.test(url)) {
|
240 | url = freeze(url);
|
241 | }
|
242 |
|
243 | var relOriginalPath = PATH.relative(basePath, absPath);
|
244 | var resolved = resolveUrl2(url);
|
245 | url = (resolved == url ? PATH.relative(basePath, url) : resolved);
|
246 |
|
247 | result[relOriginalPath] = url;
|
248 | }
|
249 |
|
250 |
|
251 |
|
252 |
|
253 |
|
254 |
|
255 |
|
256 | function freezeAllProcessDir(dir, basePath, result) {
|
257 | FS.readdirSync(dir).forEach(function(file) {
|
258 | file = PATH.resolve(dir, file);
|
259 | var stat = FS.statSync(file);
|
260 | if (stat.isFile()) {
|
261 | freezeAllProcessFile(file, basePath, result);
|
262 |
|
263 | } else if (stat.isDirectory()) {
|
264 | freezeAllProcessDir(file, basePath, result);
|
265 | }
|
266 | });
|
267 | }
|
268 |
|
269 |
|
270 |
|
271 |
|
272 |
|
273 |
|
274 |
|
275 | var path = exports.path = function(_path) {
|
276 | loadConfig(_path);
|
277 | return config.paths[_path];
|
278 | };
|
279 |
|
280 |
|
281 |
|
282 |
|
283 |
|
284 |
|
285 | var configFreezeNestingLevel = function(path) {
|
286 | return config.freezeNestingLevel[path] || 0;
|
287 | };
|
288 |
|
289 |
|
290 |
|
291 |
|
292 |
|
293 |
|
294 |
|
295 | var followSymlinks = exports.followSymlinks = function(path) {
|
296 | loadConfig(path);
|
297 | return config.followSymlinks[path];
|
298 | };
|
299 |
|
300 |
|
301 |
|
302 |
|
303 |
|
304 |
|
305 | var loadConfig = exports.loadConfig = function(path) {
|
306 | if (configs.paths[path] !== undefined) return;
|
307 |
|
308 | var config_path = PATH.join(path, '.borschik');
|
309 |
|
310 | if (FS.existsSync(config_path)) {
|
311 | configs.paths[path] = true;
|
312 |
|
313 | try {
|
314 | var _config = JSON.parse(FS.readFileSync(config_path));
|
315 | } catch (e) {
|
316 | if (e instanceof SyntaxError) {
|
317 | console.error('Invalid config: ' + config_path);
|
318 | }
|
319 | throw e;
|
320 | }
|
321 |
|
322 | var paths = _config.paths || _config.pathmap || {};
|
323 | for (var dir in paths) {
|
324 | var realpath = realpathSync(PATH.resolve(path, dir));
|
325 | if (!config.paths[realpath]) {
|
326 | var value = paths[dir];
|
327 | if (value) value = value.replace(/\*/g, PATH.sep);
|
328 | config.paths[realpath] = value;
|
329 | }
|
330 | }
|
331 |
|
332 | var freezeNestingLevels = _config['freeze_nesting_levels'] || {};
|
333 | for (var dir in freezeNestingLevels) {
|
334 | var realpath = realpathSync(PATH.resolve(path, dir));
|
335 | setNestingLevel(config.freezeNestingLevel, realpath, freezeNestingLevels[dir]);
|
336 | }
|
337 |
|
338 | var freezePaths = _config.freeze_paths || {};
|
339 | for (var freezeConfigWildcard in freezePaths) {
|
340 | var freezeRealPath = realpathSync(PATH.resolve(path, freezeConfigWildcard));
|
341 | if (!config.freezeWildcards[realpath]) {
|
342 | var freezeToPath = freezePaths[freezeConfigWildcard];
|
343 |
|
344 |
|
345 | if (freezeToPath !== ':base64:' && freezeToPath !== ':encodeURIComponent:' && freezeToPath !== ':encodeURI:') {
|
346 | freezeToPath = realpathSync(PATH.resolve(path, freezeToPath));
|
347 |
|
348 |
|
349 |
|
350 |
|
351 |
|
352 |
|
353 |
|
354 | setNestingLevel(config.freezeNestingLevel, freezeToPath, _config['freeze_nesting_level']);
|
355 | }
|
356 | config.freezeWildcards[freezeRealPath] = freezeToPath;
|
357 | }
|
358 | }
|
359 |
|
360 | var _followSymlinks = _config.follow_symlinks || {};
|
361 | for (var dir in _followSymlinks) {
|
362 | var realpath = realpathSync(PATH.resolve(path, dir));
|
363 | if (!config.followSymlinks[realpath]) {
|
364 | config.followSymlinks[realpath] = _followSymlinks[dir];
|
365 | }
|
366 | }
|
367 | } else {
|
368 | configs.paths[path] = false;
|
369 | }
|
370 |
|
371 | };
|
372 |
|
373 |
|
374 |
|
375 |
|
376 |
|
377 |
|
378 |
|
379 |
|
380 | var resolveUrl2 = exports.resolveUrl2 = function(filePath, base) {
|
381 | filePath = realpathSync(filePath);
|
382 |
|
383 | var suffix = filePath,
|
384 | prefix = '',
|
385 | host = '',
|
386 | hostpath = '',
|
387 | rePrefix = new RegExp('^(' + rePathSep + '[^' + rePathSep + ']+)', 'g'),
|
388 | matched;
|
389 |
|
390 | while (matched = suffix.match(rePrefix)) {
|
391 | prefix += matched[0];
|
392 | hostpath += matched[0];
|
393 |
|
394 | var _path = path(prefix);
|
395 | if (_path !== undefined) {
|
396 | host = _path;
|
397 | hostpath = '';
|
398 | }
|
399 | suffix = suffix.replace(rePrefix, '');
|
400 | }
|
401 |
|
402 | var result;
|
403 |
|
404 | if (host) {
|
405 | hostpath = hostpath.replace(new RegExp('^' + rePathSep + '+'), '');
|
406 | result = host + hostpath + suffix;
|
407 | } else {
|
408 | result = PATH.resolve(base, prefix + suffix);
|
409 | }
|
410 |
|
411 | return result;
|
412 | };
|
413 |
|
414 |
|
415 |
|
416 |
|
417 |
|
418 |
|
419 | function mkpath(path) {
|
420 | var dirs = path.split(PATH.sep),
|
421 | _path = '',
|
422 | winDisk = /^\w{1}:\\$/;
|
423 |
|
424 | dirs.forEach(function(dir) {
|
425 | dir = dir || PATH.sep;
|
426 | if (dir) {
|
427 | _path = PATH.join(_path, dir);
|
428 |
|
429 |
|
430 |
|
431 | if (_path === '/' || winDisk.test(PATH.resolve(dir))) {
|
432 | return;
|
433 | }
|
434 |
|
435 | try {
|
436 | FS.mkdirSync(_path);
|
437 | } catch(e) {
|
438 |
|
439 | if (e.code !== 'EEXIST') {
|
440 | throw new Error(e)
|
441 | }
|
442 | }
|
443 | }
|
444 | });
|
445 | }
|
446 |
|
447 |
|
448 |
|
449 |
|
450 |
|
451 |
|
452 |
|
453 | function save(filePath, content) {
|
454 | mkpath(PATH.dirname(filePath));
|
455 | FS.writeFileSync(filePath, content);
|
456 | }
|
457 |
|
458 |
|
459 |
|
460 |
|
461 |
|
462 |
|
463 |
|
464 |
|
465 | var realpathSync = exports.realpathSync = function(path, base) {
|
466 | path = PATH.resolve(base? base : process.cwd(), path);
|
467 |
|
468 | var folders = path.split(PATH.sep);
|
469 |
|
470 | for (var i = 0; i < folders.length; ) {
|
471 | var name = folders[i];
|
472 |
|
473 | if (name === '') {
|
474 | i++;
|
475 | continue;
|
476 | }
|
477 |
|
478 | var prefix = subArrayJoin(folders, PATH.sep, 0, i - 1),
|
479 | _followSymlinks = false;
|
480 |
|
481 | for (var j = 0; j < i; j++) {
|
482 | var followSymlinksJ = followSymlinks(subArrayJoin(folders, PATH.sep, 0, j));
|
483 | if (followSymlinksJ !== undefined) _followSymlinks = followSymlinksJ;
|
484 | }
|
485 |
|
486 | var possibleSymlink = PATH.join(prefix, name);
|
487 |
|
488 | if (_followSymlinks && isSymLink(possibleSymlink)) {
|
489 | |
490 |
|
491 |
|
492 |
|
493 |
|
494 |
|
495 |
|
496 |
|
497 | var relativeLinkPath = FS.readlinkSync(possibleSymlink);
|
498 |
|
499 |
|
500 | var absoluteLinkPath = PATH.resolve(prefix, relativeLinkPath);
|
501 |
|
502 |
|
503 | var linkParts = absoluteLinkPath.split(PATH.sep);
|
504 |
|
505 |
|
506 |
|
507 | folders = arraySplice(folders, 0, i + 1, linkParts);
|
508 |
|
509 |
|
510 | } else {
|
511 | i++;
|
512 | }
|
513 | }
|
514 |
|
515 | return folders.join(PATH.sep);
|
516 | };
|
517 |
|
518 |
|
519 | var freezableExts = process.env.BORSCHIK_FREEZABLE_EXTS ? process.env.BORSCHIK_FREEZABLE_EXTS.split(' ') :
|
520 | Object.keys(contentTypes).map(function(ext) {
|
521 | return ext.slice(1);
|
522 | }),
|
523 | freezableRe = new RegExp('\\.(' + freezableExts.join('|') + ')$');
|
524 |
|
525 |
|
526 |
|
527 |
|
528 |
|
529 |
|
530 |
|
531 | exports.isFreezableUrl = function(url) {
|
532 | return freezableRe.test(url);
|
533 | };
|
534 |
|
535 |
|
536 |
|
537 |
|
538 |
|
539 |
|
540 |
|
541 |
|
542 |
|
543 |
|
544 | function subArrayJoin(a, separator, from, to) {
|
545 | return a.slice(from, to + 1).join(separator);
|
546 | }
|
547 |
|
548 |
|
549 |
|
550 |
|
551 |
|
552 |
|
553 |
|
554 |
|
555 |
|
556 |
|
557 | function arraySplice(a1, from, to, a2) {
|
558 | var aL = a1.slice(0, from),
|
559 | aR = a1.slice(to);
|
560 |
|
561 | return aL.concat(a2).concat(aR);
|
562 | }
|
563 |
|
564 |
|
565 |
|
566 |
|
567 |
|
568 |
|
569 |
|
570 | function isSymLink(path) {
|
571 | return FS.existsSync(path) && FS.lstatSync(path).isSymbolicLink();
|
572 | }
|
573 |
|
574 |
|
575 |
|
576 |
|
577 |
|
578 |
|
579 |
|
580 | function setNestingLevel(config, dir, rawValue) {
|
581 |
|
582 | if ( !(dir in config) ) {
|
583 | config[dir] = Math.max(parseInt(rawValue, 10) || 0, 0);
|
584 | }
|
585 | }
|
586 |
|
587 | exports.getFreezeNestingPath = getFreezeNestingPath;
|