UNPKG

50.6 kBJavaScriptView Raw
1var fs = require("fs");
2var path = require("path");
3var mkdirp = require('mkdirp');
4var request = require("request");
5var mime = require("mime");
6var uuidv4 = require("uuid/v4");
7var os = require("os");
8var async = require("async");
9var temp = require("temp");
10var onHeaders = require("on-headers");
11var sha1 = require("sha1");
12var klaw = require("klaw");
13var targz = require('targz');
14var archiver = require("archiver");
15
16var http = require("http");
17var https = require("https");
18
19var urlTool = require("url");
20
21var cloner = require("clone");
22
23var JSON5 = require("json5");
24
25var VALID_IP_ADDRESS_REGEX_STRING = "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$";
26
27exports = module.exports;
28
29var shouldIgnore = function(filePath)
30{
31 var ignore = false;
32
33 var filename = path.basename(filePath);
34
35 if ("gitana.json" === filename)
36 {
37 ignore = true;
38 }
39
40 if (".git" === filename)
41 {
42 ignore = true;
43 }
44
45 if (".DS_STORE" === filename)
46 {
47 ignore = true;
48 }
49
50 return ignore;
51};
52
53var assertSafeToDelete = function(directoryPath)
54{
55 var b = true;
56
57 if (!directoryPath || directoryPath.length < 4 || directoryPath === "" || directoryPath === "/" || directoryPath === "//") {
58 b = false;
59 throw new Error("Cannot delete null or root directory: " + directoryPath);
60 }
61
62 if (directoryPath == __dirname) {
63 b = false;
64 throw new Error("Unallowed to delete directory: " + directoryPath);
65 }
66
67 if (directoryPath.indexOf(__dirname) > -1) {
68 b = false;
69 throw new Error("Unallowed to delete directory: " + directoryPath);
70 }
71
72 return b;
73};
74
75var rmdirRecursiveSync = function(directoryOrFilePath)
76{
77 if (!assertSafeToDelete(directoryOrFilePath))
78 {
79 return false;
80 }
81
82 if (!fs.existsSync(directoryOrFilePath))
83 {
84 return false;
85 }
86
87 // get stats about the things we're about to delete
88 var isDirectory = false;
89 var isFile = false;
90 var isLink = false;
91 try
92 {
93 var stat = fs.lstatSync(directoryOrFilePath);
94
95 isDirectory = stat.isDirectory();
96 isFile = stat.isFile();
97 isLink = stat.isSymbolicLink();
98
99 }
100 catch (e)
101 {
102 console.log("Failed to get lstat for file: " + directoryOrFilePath);
103 return false;
104 }
105
106 // check if the file is a symbolic link
107 // if so, we just unlink it and save ourselves a lot of work
108 if (isLink)
109 {
110 try
111 {
112 fs.unlinkSync(directoryOrFilePath);
113 }
114 catch (e)
115 {
116 console.log("Unable to unlink: " + directoryOrFilePath + ", err: " + JSON.stringify(e));
117 }
118 }
119 else
120 {
121 // it is a physical directory or file...
122
123 // if it is a directory, dive down into children first
124 if (isDirectory)
125 {
126 var list = null;
127 try
128 {
129 list = fs.readdirSync(directoryOrFilePath);
130 }
131 catch (e)
132 {
133 console.log("Failed to read dir for: " + directoryOrFilePath + ", err: " + JSON.stringify(e));
134 list = null;
135 }
136
137 if (list)
138 {
139 for (var i = 0; i < list.length; i++)
140 {
141 if (list[i] == "." || list[i] == "..")
142 {
143 // skip these files
144 continue;
145 }
146
147 var childPath = path.join(directoryOrFilePath, list[i]);
148
149 rmdirRecursiveSync(childPath);
150 }
151 }
152 }
153
154 // now delete the actual file or directory
155 if (isFile)
156 {
157 try
158 {
159 fs.unlinkSync(directoryOrFilePath);
160 }
161 catch (e)
162 {
163 console.log("Unable to delete file: " + directoryOrFilePath + ", err: " + JSON.stringify(e));
164 }
165 }
166 else if (isDirectory)
167 {
168 try
169 {
170 fs.rmdirSync(directoryOrFilePath);
171 }
172 catch (e)
173 {
174 console.log("Unable to remove directory: " + directoryOrFilePath + ", err: " + JSON.stringify(e));
175 }
176 }
177 }
178};
179
180var executeCommands = exports.executeCommands = function(commands, logMethod, callback)
181{
182 var terminal = null;
183
184 if (isWindows())
185 {
186 terminal = require('child_process').spawn('cmd');
187 }
188 else
189 {
190 terminal = require('child_process').spawn('bash');
191 }
192
193 logMethod("Running commands: " + JSON.stringify(commands));
194
195 var text = "";
196
197 terminal.stdout.on('data', function (data) {
198 logMethod(" > " + data);
199 text = text + data;
200 });
201
202 terminal.on('error', function (err) {
203 logMethod("child process exited with error: " + err + " for commands: " + commands);
204
205 err = {
206 "commands": commands,
207 "message": err,
208 "err": err
209 };
210
211 callback(err);
212 });
213
214 terminal.on('exit', function (code) {
215
216 var err = null;
217 if (code !== 0)
218 {
219 logMethod('child process exited with code ' + code + ' for commands: ' + commands);
220
221 err = {
222 "commands": commands,
223 "message": text,
224 "code": code
225 };
226 }
227
228 callback(err, text);
229 });
230
231 setTimeout(function() {
232 //console.log('Sending stdin to terminal');
233
234 for (var i = 0; i < commands.length; i++)
235 {
236 var command = commands[i];
237 terminal.stdin.write(command + "\n");
238 }
239
240 terminal.stdin.end();
241
242 }, 1000);
243};
244
245var gitInit = function(directoryPath, logMethod, callback)
246{
247 var commands = [];
248 commands.push("cd " + directoryPath);
249 commands.push("git init");
250 executeCommands(commands, logMethod, function(err) {
251 callback(err);
252 });
253};
254
255var gitPull = function(directoryPath, gitUrl, sourceType, sourceBranch, logMethod, callback)
256{
257 var username = null;
258 var password = null;
259 if (sourceType == "github")
260 {
261 username = process.env.CLOUDCMS_NET_GITHUB_USERNAME;
262 password = process.env.CLOUDCMS_NET_GITHUB_PASSWORD;
263 }
264 else if (sourceType == "bitbucket")
265 {
266 username = process.env.CLOUDCMS_NET_BITBUCKET_USERNAME;
267 password = process.env.CLOUDCMS_NET_BITBUCKET_PASSWORD;
268 }
269
270 if (password)
271 {
272 password = escape(password).replace("@", "%40");
273 }
274
275 if (username)
276 {
277 var token = username;
278 if (password)
279 {
280 token += ":" + password;
281 }
282
283 gitUrl = gitUrl.substring(0, 8) + token + "@" + gitUrl.substring(8);
284 }
285
286 var commands = [];
287 commands.push("cd " + directoryPath);
288
289 if (!sourceBranch || sourceBranch.toLowerCase().trim() === "master")
290 {
291 commands.push("git pull " + gitUrl);
292 }
293 else
294 {
295 // if non-master branch, we do this a little differently
296 commands.push("git fetch " + gitUrl + " " + sourceBranch + ":" + sourceBranch);
297 commands.push("git checkout " + sourceBranch);
298 }
299
300 // run the commands
301 executeCommands(commands, logMethod, function(err) {
302 callback(err);
303 });
304};
305
306/**
307 * Checks out the source code for an application to be deployed and copies it into the root store for a given host.
308 *
309 * @type {*}
310 */
311exports.gitCheckout = function(host, sourceType, gitUrl, relativePath, sourceBranch, offsetPath, moveToPublic, logMethod, callback)
312{
313 // this gets a little confusing, so here is what we have:
314 //
315 // /temp-1234 (tempRootDirectoryPath)
316 // .git
317 // /website (tempWorkingDirectoryPath)
318 // /public (tempWorkingPublicDirectory)
319 // /public_build (tempWorkingPublicBuildDirectory)
320 // /config (tempWorkingConfigDirectory)
321 // /gitana.json (tempWorkingGitanaJsonFilePath)
322 //
323 // <rootStore>
324 // /web
325 // /config
326 // /content
327
328
329 // /hosts
330 // /domain.cloudcms.net (hostDirectoryPath)
331 // /public (hostPublicDirectoryPath)
332 // /public_build (hostPublicBuildDirectoryPath)
333 // /config (hostConfigDirectoryPath)
334
335 var storeService = require("../middleware/stores/stores");
336
337 // create a "root" store for the host
338 storeService.produce(host, function(err, stores) {
339
340 if (err) {
341 return callback(err, host);
342 }
343
344 var rootStore = stores.root;
345
346 // create tempRootDirectoryPath
347 logMethod("[gitCheckout] Create temp directory");
348 createTempDirectory(function (err, tempRootDirectoryPath) {
349
350 if (err) {
351 return callback(err, host);
352 }
353
354 // initialize git in temp root directory
355 logMethod("[gitCheckout] Initialize git: " + tempRootDirectoryPath);
356 gitInit(tempRootDirectoryPath, logMethod, function (err) {
357
358 if (err) {
359 return callback(err);
360 }
361
362 // perform a git pull of the repository
363 logMethod("[gitCheckout] Git pull: " + gitUrl);
364 gitPull(tempRootDirectoryPath, gitUrl, sourceType, sourceBranch, logMethod, function (err) {
365
366 if (err) {
367 return callback(err);
368 }
369
370 var tempRootDirectoryRelativePath = tempRootDirectoryPath;
371 if (relativePath && relativePath != "/")
372 {
373 tempRootDirectoryRelativePath = path.join(tempRootDirectoryRelativePath, relativePath);
374 }
375
376 // make sure the tempRootDirectoryRelativePath exists
377 var tempRootDirectoryRelativePathExists = fs.existsSync(tempRootDirectoryRelativePath);
378 if (!tempRootDirectoryRelativePathExists)
379 {
380 return callback({
381 "message": "The relative path: " + relativePath + " does not exist within the Git repository: " + gitUrl
382 });
383 }
384
385 if (moveToPublic)
386 {
387 // if there isn't a "public" and there isn't a "public_build" directory,
388 // then move files into public
389 var publicExists = fs.existsSync(path.join(tempRootDirectoryRelativePath, "public"));
390 var publicBuildExists = fs.existsSync(path.join(tempRootDirectoryRelativePath, "public_build"));
391 if (!publicExists && !publicBuildExists)
392 {
393 fs.mkdirSync(path.join(tempRootDirectoryRelativePath, "public"));
394
395 var filenames = fs.readdirSync(tempRootDirectoryRelativePath);
396 if (filenames && filenames.length > 0)
397 {
398 for (var i = 0; i < filenames.length; i++)
399 {
400 if (!shouldIgnore(path.join(tempRootDirectoryRelativePath, filenames[i])))
401 {
402 if ("config" === filenames[i])
403 {
404 // skip this
405 }
406 else if ("gitana.json" === filenames[i])
407 {
408 // skip
409 }
410 else if ("descriptor.json" === filenames[i])
411 {
412 // skip
413 }
414 else if ("public" === filenames[i])
415 {
416 // skip
417 }
418 else if ("public_build" === filenames[i])
419 {
420 // skip
421 }
422 else
423 {
424 fs.renameSync(path.join(tempRootDirectoryRelativePath, filenames[i]), path.join(tempRootDirectoryRelativePath, "public", filenames[i]));
425 }
426 }
427 }
428 }
429 }
430 }
431
432 // copy everything from temp dir into the store
433 logMethod("[gitCheckout] Copy from temp to store");
434 copyToStore(tempRootDirectoryRelativePath, rootStore, offsetPath, function(err) {
435
436 logMethod("[gitCheckout] Remove temp dir: " + tempRootDirectoryPath);
437
438 // now remove temp directory
439 rmdir(tempRootDirectoryPath);
440
441 logMethod("[gitCheckout] Done");
442
443 callback(err);
444 });
445 });
446 });
447 });
448 });
449};
450
451var installPackagedDeployment = exports.installPackagedDeployment = function(host, packagedDeploymentName, logMethod, callback)
452{
453 var storeService = require("../middleware/stores/stores");
454
455 // create a "root" store for the host
456 storeService.produce(host, function(err, stores) {
457
458 if (err) {
459 return callback(err, host);
460 }
461
462 var rootStore = stores.root;
463
464 var defaultDeploymentDir = path.join(__dirname, "..", "packaged", "deployments", packagedDeploymentName);
465 console.log("Default deployment dir: " + defaultDeploymentDir);
466
467 // copy everything from default deployment directory into the store
468 copyToStore(defaultDeploymentDir, rootStore, "/public_build", function(err) {
469
470 callback(err);
471
472 });
473 });
474};
475
476var copyToStore = exports.copyToStore = function(sourceDirectory, targetStore, offsetPath, callback)
477{
478 var f = function(filepath, fns) {
479
480 var sourceFilePath = path.join(sourceDirectory, filepath);
481 if (!shouldIgnore(sourceFilePath))
482 {
483 var sourceStats = fs.lstatSync(sourceFilePath);
484 if (sourceStats) {
485 if (sourceStats.isDirectory()) {
486
487 // list files
488 var filenames = fs.readdirSync(sourceFilePath);
489 if (filenames && filenames.length > 0) {
490 for (var i = 0; i < filenames.length; i++) {
491 f(path.join(filepath, filenames[i]), fns);
492 }
493 }
494
495 }
496 else if (sourceStats.isFile()) {
497
498 // STORE: CREATE_FILE
499 fns.push(function (sourceFilePath, filepath, targetStore) {
500 return function (done) {
501 //console.log("source: " + sourceFilePath);
502 fs.readFile(sourceFilePath, function (err, data) {
503
504 if (err) {
505 done(err);
506 return;
507 }
508
509 var targetFilePath = filepath;
510 if (offsetPath)
511 {
512 targetFilePath = path.join(offsetPath, targetFilePath);
513 }
514
515 //console.log("target: " + targetFilePath);
516
517 targetStore.writeFile(targetFilePath, data, function (err) {
518 done(err);
519 });
520 });
521 };
522 }(sourceFilePath, filepath, targetStore));
523 }
524 }
525 }
526 };
527
528 var copyFunctions = [];
529
530 var filenames = fs.readdirSync(sourceDirectory);
531 for (var i = 0; i < filenames.length; i++)
532 {
533 f(filenames[i], copyFunctions);
534 }
535
536 // run all the copy functions
537 async.series(copyFunctions, function (errors) {
538 callback();
539 });
540
541};
542
543var rmdir = exports.rmdir = function(directoryPath)
544{
545 if (!assertSafeToDelete(directoryPath)) {
546 return false;
547 }
548
549 rmdirRecursiveSync(directoryPath);
550};
551
552var mkdirs = exports.mkdirs = function(directoryPath, callback)
553{
554 mkdirp(directoryPath, function(err) {
555 callback(err);
556 });
557};
558
559var copyFile = exports.copyFile = function(srcFile, destFile)
560{
561 var contents = fs.readFileSync(srcFile);
562 fs.writeFileSync(destFile, contents);
563};
564
565var trim = exports.trim = function(text)
566{
567 return text.replace(/^\s+|\s+$/g,'');
568};
569
570var showHeaders = exports.showHeaders = function(req)
571{
572 for (var k in req.headers)
573 {
574 console.log("HEADER: " + k + " = " + req.headers[k]);
575 }
576};
577
578/**
579 * Helper function designed to automatically retry requests to a back end service over HTTP using authentication
580 * credentials acquired from an existing Gitana driver. If a request gets back an invalid_token, the Gitana
581 * driver token state is automatically refreshed.
582 *
583 * @type {Function}
584 */
585var retryGitanaRequest = exports.retryGitanaRequest = function(logMethod, gitana, config, maxAttempts, callback)
586{
587 if (!logMethod)
588 {
589 logMethod = console.log;
590 }
591
592 var _retryHandler = function(gitana, config, currentAttempts, maxAttempts, previousError, cb)
593 {
594 // try again with attempt count + 1
595 _handler(gitana, config, currentAttempts + 1, maxAttempts, previousError, cb)
596 };
597
598 var _invalidTokenRetryHandler = function(gitana, config, currentAttempts, maxAttempts, previousError, cb)
599 {
600 logMethod("Heard invalid_token, attempting retry (" + (currentAttempts + 1) + " / " + maxAttempts + ")");
601
602 // tell gitana driver to refresh access token
603 gitana.getDriver().refreshAuthentication(function(err) {
604
605 if (err)
606 {
607 logMethod("Failed to refresh access_token: " + JSON.stringify(err));
608 }
609
610 // try again with attempt count + 1
611 _handler(gitana, config, currentAttempts + 1, maxAttempts, previousError, cb)
612 });
613 };
614
615 var _handler = function(gitana, config, currentAttempts, maxAttempts, previousError, cb)
616 {
617 if (currentAttempts >= maxAttempts)
618 {
619 return cb({
620 "message": "Maximum number of connection attempts exceeded (" + maxAttempts + ")",
621 "err": previousError
622 });
623 }
624
625 // make sure agent is applied
626 if (!config.agent && config.url)
627 {
628 var agent = getAgent(config.url);
629 if (agent)
630 {
631 config.agent = agent;
632 }
633 }
634
635 // make sure we have a headers object
636 if (!config.headers)
637 {
638 config.headers = {};
639 }
640
641 // make sure we have a timeout applied
642 if (!config.timeout)
643 {
644 config.timeout = process.defaultHttpTimeoutMs;
645 }
646
647 // add "authorization" header for OAuth2 bearer token
648 var headers2 = gitana.getDriver().getHttpHeaders();
649 config.headers["Authorization"] = headers2["Authorization"];
650
651 // make the request
652 request(config, function(err, response, body) {
653
654 if (response)
655 {
656 // ok case (just callback)
657 if (response.statusCode === 200)
658 {
659 return cb(err, response, body);
660 }
661 else if (response.statusCode === 429)
662 {
663 // we heard "too many requests", so we wait a bit and then retry
664 // TODO: look at HTTP headers to determine how long to wait?
665 return setTimeout(function() {
666 logMethod("Too Many Requests heard, attempting retry (" + (currentAttempts + 1) + " / " + maxAttempts + ")");
667 _retryHandler(gitana, config, currentAttempts, maxAttempts, {
668 "message": "Heard 429 Too Many Requests",
669 "code": response.statusCode,
670 "body": body,
671 "err": err
672 }, cb);
673 }, 2000);
674 }
675 }
676
677 // look for the special "invalid_token" case
678 var isInvalidToken = false;
679 if (body)
680 {
681 try
682 {
683 var json = body;
684 if (typeof(json) === "string")
685 {
686 // convert to json
687 json = JSON.parse(json);
688 }
689 if (json.error == "invalid_token")
690 {
691 isInvalidToken = true;
692 }
693 }
694 catch (e)
695 {
696 //console.log("ERR.88 " + JSON.stringify(e));
697 }
698 }
699
700 if (isInvalidToken)
701 {
702 // refresh the access token and then retry
703 return _invalidTokenRetryHandler(gitana, config, currentAttempts, maxAttempts, {
704 "message": "Unable to refresh access token and retry",
705 "code": response.statusCode,
706 "body": body,
707 "err": err
708 }, cb);
709 }
710
711 // otherwise, we just hand back some kind of error
712 cb(err, response, body);
713 });
714 };
715
716 _handler(gitana, config, 0, maxAttempts, null, callback);
717};
718
719var isIPAddress = exports.isIPAddress = function(text)
720{
721 var rx = new RegExp(VALID_IP_ADDRESS_REGEX_STRING);
722 return rx.test(text);
723};
724
725var merge = exports.merge = function(source, target)
726{
727 for (var k in source)
728 {
729 if (typeof(source[k]) !== "undefined") {
730 if (source[k] && source[k].push) {
731 if (!target[k]) {
732 target[k] = [];
733 }
734
735 // merge array
736 for (var x = 0; x < source[k].length; x++) {
737 target[k].push(source[k][x]);
738 }
739 }
740 else if ((typeof source[k]) === "object") {
741 if (!target[k]) {
742 target[k] = {};
743 }
744
745 // merge keys/values
746 merge(source[k], target[k]);
747 }
748 else {
749 // overwrite a scalar
750 target[k] = source[k];
751 }
752 }
753 }
754};
755
756var pluck = exports.pluck = function(list, propertyPath)
757{
758 var result = [];
759 var propertyPathArray = propertyPath.split(".");
760 for(var i = 0; i < list.length; i++)
761 {
762 var prop = list[i];;
763 for(var j = 0; j < propertyPathArray.length; j++)
764 {
765 if (prop[propertyPathArray[j]])
766 {
767 prop = prop[propertyPathArray[j]];
768 }
769 else
770 {
771 prop = null;
772 break;
773 }
774 }
775
776 if (isArray(prop))
777 {
778 for(var x = 0; x < prop.length; x++)
779 {
780 result.push(prop[x]);
781 }
782 }
783 else
784 {
785 result.push(prop);
786 }
787 }
788
789 return result;
790};
791
792
793/////////////////////////////////////////////////////////////////////////////////////////////
794//
795// TIMING
796//
797/////////////////////////////////////////////////////////////////////////////////////////////
798
799// global pool of interceptor timings
800process.timings = {};
801var incrementTimings = function(req, family, id, time)
802{
803 var increment = function(map, family, id, time)
804 {
805 var key = family + ":" + id;
806
807 // interceptor timings
808 if (!map[key]) {
809 map[key] = {
810 count: 0,
811 total: 0,
812 avg: 0
813 };
814 }
815
816 map[key].count++;
817 map[key].total += time;
818 map[key].avg = map[key].total / map[key].count;
819 map[key].avg = map[key].avg.toFixed(2);
820 };
821
822 // increment global timings
823 increment(process.timings, family, id, time);
824
825 // increment request timings
826 if (!req.timings) {
827 req.timings = {};
828 }
829 increment(req.timings, family, id, time);
830};
831
832var getGlobalTimings = exports.getGlobalTimings = function()
833{
834 return process.timings;
835};
836
837
838/////////////////////////////////////////////////////////////////////////////////////////////
839//
840// CREATE INTERCEPTOR / HANDLER
841//
842/////////////////////////////////////////////////////////////////////////////////////////////
843
844
845var createHandler = exports.createHandler = function(id, configName, fn)
846{
847 if (typeof(configName) === "function") {
848 fn = configName;
849 configName = id;
850 }
851
852 return function(req, res, next) {
853
854 var startAt = process.hrtime();
855
856 // override next so that we can capture completion of interceptor
857 var _next = next;
858 next = function(err) {
859 if (startAt)
860 {
861 var diff = process.hrtime(startAt);
862 var time = diff[0] * 1e3 + diff[1] * 1e-6;
863 incrementTimings(req, "handler", id, time);
864 startAt = null;
865 }
866 return _next.call(_next, err);
867 };
868 onHeaders(res, function() {
869 if (startAt)
870 {
871 var diff = process.hrtime(startAt);
872 var time = diff[0] * 1e3 + diff[1] * 1e-6;
873 incrementTimings(req, "handler", id, time);
874 startAt = null;
875 }
876 });
877
878 req.configuration(configName, function(err, handlerConfiguration) {
879
880 if (err) {
881 return next(err);
882 }
883
884 if (handlerConfiguration && !handlerConfiguration.enabled)
885 {
886 return next();
887 }
888
889 fn(req, res, next, req.stores, req.cache, handlerConfiguration);
890 });
891 };
892};
893
894var createInterceptor = exports.createInterceptor = function(id, configName, fn)
895{
896 if (typeof(configName) === "function") {
897 fn = configName;
898 configName = id;
899 }
900
901 return function(req, res, next) {
902
903 var startAt = process.hrtime();
904
905 // override next so that we can capture completion of interceptor
906 var _next = next;
907 next = function(err) {
908 if (startAt > -1)
909 {
910 var diff = process.hrtime(startAt);
911 var time = diff[0] * 1e3 + diff[1] * 1e-6;
912 incrementTimings(req, "interceptor", id, time);
913 startAt = -1;
914 }
915 return _next.call(_next, err);
916 };
917 onHeaders(res, function() {
918 if (startAt > -1)
919 {
920 var diff = process.hrtime(startAt);
921 var time = diff[0] * 1e3 + diff[1] * 1e-6;
922 incrementTimings(req, "interceptor", id, time);
923 startAt = -1;
924 }
925 });
926
927 req.configuration(configName, function(err, interceptorConfiguration) {
928
929 if (err) {
930 return next(err);
931 }
932
933 if (interceptorConfiguration && !interceptorConfiguration.enabled)
934 {
935 return next();
936 }
937
938 fn(req, res, next, req.stores, req.cache, interceptorConfiguration);
939 });
940 };
941};
942
943var replaceAll = exports.replaceAll = function(text, find, replace)
944{
945 var i = -1;
946 do
947 {
948 i = text.indexOf(find);
949 if (i > -1)
950 {
951 text = text.substring(0, i) + replace + text.substring(i + find.length);
952 }
953 } while (i > -1);
954
955 return text;
956};
957
958var createTempDirectory = exports.createTempDirectory = function(callback)
959{
960 var tempDirectory = path.join(os.tmpdir(), "/tmp-" + guid());
961
962 mkdirs(tempDirectory, function(err) {
963 callback(err, tempDirectory);
964 });
965};
966
967var generateTempFilePath = exports.generateTempFilePath = function(basedOnFilePath)
968{
969 var tempFilePath = null;
970
971 var ext = null;
972 if (basedOnFilePath) {
973 ext = path.extname(basedOnFilePath);
974 }
975
976 if (ext)
977 {
978 tempFilePath = temp.path({suffix: ext})
979 }
980 else
981 {
982 tempFilePath = temp.path();
983 }
984
985 return tempFilePath;
986};
987
988var sendFile = exports.sendFile = function(res, stream, callback)
989{
990 res.on('finish', function() {
991
992 if (callback) {
993 callback();
994 }
995
996 });
997 res.on("error", function(err) {
998
999 if (callback) {
1000 callback(err);
1001 }
1002
1003 });
1004 stream.pipe(res);
1005 //res.end();
1006};
1007
1008var applyResponseContentType = exports.applyResponseContentType = function(response, cacheInfo, filePath)
1009{
1010 var contentType = null;
1011
1012 var filename = path.basename(filePath);
1013
1014 // do the response headers have anything to tell us
1015 if (cacheInfo)
1016 {
1017 // is there an explicit content type?
1018 contentType = cacheInfo.mimetype;
1019 }
1020 else if (filename)
1021 {
1022 var ext = path.extname(filename);
1023 if (ext) {
1024 //contentType = mime.lookup(ext);
1025 contentType = lookupMimeType(ext);
1026 }
1027 }
1028
1029 // if still nothing, what can we guess from the filename mime?
1030 if (!contentType && filename)
1031 {
1032 var ext = path.extname(filename);
1033 if (ext)
1034 {
1035 //contentType = mime.lookup(ext);
1036 contentType = lookupMimeType(ext);
1037 }
1038 }
1039
1040 // TODO: should we look for ";charset=" and strip out?
1041
1042 if (contentType)
1043 {
1044 setHeader(response, "Content-Type", contentType);
1045 }
1046
1047 return contentType;
1048};
1049
1050//var MAXAGE_ONE_YEAR = 31536000;
1051//var MAXAGE_ONE_HOUR = 3600;
1052//var MAXAGE_ONE_WEEK = 604800;
1053var MAXAGE_THIRTY_MINUTES = 1800;
1054
1055var applyDefaultContentTypeCaching = exports.applyDefaultContentTypeCaching = function(response, cacheInfo)
1056{
1057 if (!cacheInfo || !response)
1058 {
1059 return;
1060 }
1061
1062 var mimetype = cacheInfo.mimetype;
1063 if (!mimetype)
1064 {
1065 return;
1066 }
1067
1068 // assume no caching
1069 var cacheControl = "no-cache,no-store,max-age=0,s-maxage=0,must-revalidate,no-transform";
1070 var expires = "Mon, 7 Apr 2012, 16:00:00 GMT"; // some time in the past
1071
1072 // if we're in production mode, we apply caching
1073 if (process.env.CLOUDCMS_APPSERVER_MODE === "production")
1074 {
1075 var isCSS = ("text/css" == mimetype);
1076 var isImage = (mimetype.indexOf("image/") > -1);
1077 var isJS = ("text/javascript" == mimetype) || ("application/javascript" == mimetype);
1078 var isHTML = ("text/html" == mimetype);
1079
1080 var maxAge = -1;
1081
1082 // html
1083 if (isHTML)
1084 {
1085 maxAge = MAXAGE_THIRTY_MINUTES;
1086 }
1087
1088 // css, images and js get 1 year
1089 if (isCSS || isImage || isJS)
1090 {
1091 maxAge = MAXAGE_THIRTY_MINUTES;
1092 }
1093
1094 cacheControl = "public,max-age=" + maxAge + ",s-maxage=" + maxAge + ",no-transform";
1095 expires = new Date(Date.now() + (maxAge * 1000)).toUTCString();
1096 }
1097
1098 // overwrite the cache-control header
1099 setHeaderOnce(response, 'Cache-Control', cacheControl);
1100
1101 // overwrite the expires header
1102 setHeaderOnce(response, 'Expires', expires);
1103
1104 // remove pragma, this isn't used anymore
1105 removeHeader(response, "Pragma");
1106};
1107
1108var handleSendFileError = exports.handleSendFileError = function(req, res, filePath, cacheInfo, logMethod, err)
1109{
1110 if (err)
1111 {
1112 if (err.doesNotExist)
1113 {
1114 var fallback = req.query["fallback"];
1115 if (!fallback) {
1116 try { util.status(res, 404); } catch (e) { }
1117 res.end();
1118 }
1119 else
1120 {
1121 res.redirect(fallback);
1122 }
1123 }
1124 else if (err.zeroSize)
1125 {
1126 try { util.status(res, 200); } catch (e) { }
1127 res.end();
1128 }
1129 else if (err.readFailed)
1130 {
1131 logMethod(JSON.stringify(err));
1132 try { util.status(res, 503); } catch (e) { }
1133 res.end();
1134 }
1135 else if (err.sendFailed)
1136 {
1137 logMethod(JSON.stringify(err));
1138 try { util.status(res, 503); } catch (e) { }
1139 res.end();
1140 }
1141 else
1142 {
1143 try { util.status(res, 503); } catch (e) { }
1144 res.end();
1145 }
1146 }
1147};
1148
1149var createDirectory = exports.createDirectory = function(directoryPath, callback)
1150{
1151 mkdirp(directoryPath, function(err) {
1152
1153 if (err) {
1154 return callback(err);
1155 }
1156
1157 callback(null, directoryPath);
1158 });
1159};
1160
1161var setHeaderOnce = exports.setHeaderOnce = function(response, name, value)
1162{
1163 var existing = response.getHeader(name);
1164 if (typeof(existing) === "undefined")
1165 {
1166 setHeader(response, name, value);
1167 }
1168};
1169
1170var setHeader = exports.setHeader = function(response, name, value)
1171{
1172 try { response.setHeader(name, value); } catch (e) { }
1173};
1174
1175var removeHeader = exports.removeHeader = function(response, name)
1176{
1177 try { response.removeHeader(name); } catch (e) { }
1178};
1179
1180var isInvalidateTrue = exports.isInvalidateTrue = function(request)
1181{
1182 return (request.query["invalidate"] == "true");
1183};
1184
1185var hashcode = exports.hashcode = function(text)
1186{
1187 var hash = 0, i, chr, len;
1188 if (text.length == 0)
1189 {
1190 return hash;
1191 }
1192 for (i = 0, len = text.length; i < len; i++)
1193 {
1194 chr = text.charCodeAt(i);
1195 hash = ((hash << 5) - hash) + chr;
1196 hash |= 0; // Convert to 32bit integer
1197 }
1198 return hash;
1199};
1200
1201var safeReadStream = exports.safeReadStream = function(contentStore, filePath, callback)
1202{
1203 contentStore.existsFile(filePath, function(exists) {
1204
1205 if (!exists) {
1206 return callback();
1207 }
1208
1209 contentStore.fileStats(filePath, function (err, stats) {
1210
1211 if (err) {
1212 return callback();
1213 }
1214
1215 if (stats.size === 0) {
1216 return callback();
1217 }
1218
1219 contentStore.readStream(filePath, function (err, readStream) {
1220 callback(err, readStream);
1221 });
1222 });
1223 });
1224};
1225
1226var safeReadFile = exports.safeReadFile = function(contentStore, filePath, callback)
1227{
1228 contentStore.existsFile(filePath, function(exists) {
1229
1230 if (!exists) {
1231 return callback();
1232 }
1233
1234 contentStore.fileStats(filePath, function (err, stats) {
1235
1236 if (err) {
1237 return callback();
1238 }
1239
1240 if (stats.size === 0) {
1241 return callback();
1242 }
1243
1244 contentStore.readFile(filePath, function (err, data) {
1245 callback(err, data);
1246 });
1247 });
1248 });
1249};
1250
1251/**
1252 * Finds whether the type of a variable is function.
1253 * @param {Any} obj The variable being evaluated.
1254 * @returns {Boolean} True if the variable is a function, false otherwise.
1255 */
1256var isFunction = exports.isFunction = function(obj) {
1257 return Object.prototype.toString.call(obj) === "[object Function]";
1258};
1259
1260/**
1261 * Finds whether the type of a variable is string.
1262 * @param {Any} obj The variable being evaluated.
1263 * @returns {Boolean} True if the variable is a string, false otherwise.
1264 */
1265var isString = exports.isString = function(obj) {
1266 return (typeof obj === "string");
1267};
1268
1269/**
1270 * Finds whether the type of a variable is object.
1271 * @param {Any} obj The variable being evaluated.
1272 * @returns {Boolean} True if the variable is an object, false otherwise.
1273 */
1274var isObject = exports.isObject = function(obj) {
1275 return !isUndefined(obj) && Object.prototype.toString.call(obj) === '[object Object]';
1276};
1277
1278/**
1279 * Finds whether the type of a variable is number.
1280 * @param {Any} obj The variable being evaluated.
1281 * @returns {Boolean} True if the variable is a number, false otherwise.
1282 */
1283var isNumber = exports.isNumber = function(obj) {
1284 return (typeof obj === "number");
1285};
1286
1287/**
1288 * Finds whether the type of a variable is array.
1289 * @param {Any} obj The variable being evaluated.
1290 * @returns {Boolean} True if the variable is an array, false otherwise.
1291 */
1292var isArray = exports.isArray = function(obj) {
1293 return obj instanceof Array;
1294};
1295
1296/**
1297 * Finds whether the type of a variable is boolean.
1298 * @param {Any} obj The variable being evaluated.
1299 * @returns {Boolean} True if the variable is a boolean, false otherwise.
1300 */
1301var isBoolean = exports.isBoolean = function(obj) {
1302 return (typeof obj === "boolean");
1303};
1304
1305/**
1306 * Finds whether the type of a variable is undefined.
1307 * @param {Any} obj The variable being evaluated.
1308 * @returns {Boolean} True if the variable is a undefined, false otherwise.
1309 */
1310var isUndefined = exports.isUndefined = function(obj) {
1311 return (typeof obj == "undefined");
1312};
1313
1314/**
1315 * Looks up a mimetype for a given file extension.
1316 *
1317 * @type {Function}
1318 */
1319var lookupMimeType = exports.lookupMimeType = function(ext) {
1320
1321 // rely on the mimetype library for base handling
1322 var mimetype = mime.lookup(ext);
1323
1324 var extension = ext;
1325 if (extension && extension.indexOf(".") === 0)
1326 {
1327 extension = ext.substring(1);
1328 }
1329
1330 // and then make some adjustments for things that it botches
1331 if ("ttf" === extension) {
1332 mimetype = "application/x-font-truetype";
1333 }
1334 else if ("otf" === extension) {
1335 mimetype = "application/x-font-opentype";
1336 }
1337
1338 return mimetype;
1339};
1340
1341/**
1342 * Generates a page cache key for a given wcm page descriptor.
1343 */
1344var generatePageCacheKey = exports.generatePageCacheKey = function(descriptor) {
1345
1346 // sort params alphabetically
1347 var paramNames = [];
1348 for (var paramName in descriptor.params) {
1349 paramNames.push(paramName);
1350 }
1351 paramNames.sort();
1352
1353 // sort page attributes alphabetically
1354 var pageAttributeNames = [];
1355 for (var pageAttributeName in descriptor.pageAttributes) {
1356 pageAttributeNames.push(pageAttributeName);
1357 }
1358 pageAttributeNames.sort();
1359
1360 /*
1361 // sort headers alphabetically
1362 var headerNames = [];
1363 for (var headerName in descriptor.headers) {
1364 headerNames.push(headerName);
1365 }
1366 headerNames.sort();
1367 */
1368
1369 var str = descriptor.url;
1370
1371 // add in param names
1372 for (var i = 0; i < paramNames.length; i++)
1373 {
1374 var paramName = paramNames[i];
1375 var paramValue = descriptor.params[paramName];
1376
1377 if (typeof(paramValue) !== "undefined" && paramValue !== null)
1378 {
1379 str += "&param_" + paramName + "=" + paramValue;
1380 }
1381 }
1382
1383 // add in page attribute names
1384 for (var i = 0; i < pageAttributeNames.length; i++)
1385 {
1386 var pageAttributeName = pageAttributeNames[i];
1387 var pageAttributeValue = descriptor.pageAttributes[pageAttributeName];
1388
1389 if (typeof(pageAttributeValue) !== "undefined" && pageAttributeValue !== null)
1390 {
1391 str += "&attr_" + pageAttributeName + "=" + pageAttributeValue;
1392 }
1393 }
1394
1395 /*
1396 // add in header names
1397 for (var i = 0; i < headerNames.length; i++)
1398 {
1399 var headerName = headerNames[i];
1400 var headerValue = descriptor.headers[headerName];
1401 str += "&header_" + headerName + "=" + headerValue;
1402 }
1403 */
1404
1405 //console.log("KEY CONSTRUCTION STRING: " + str);
1406
1407 // calculate a signature to serve as a page cache key
1408 return hashSignature(str);
1409};
1410
1411/**
1412 * Generates a cache key for fragments.
1413 */
1414var generateFragmentCacheKey = exports.generateFragmentCacheKey = function(fragmentId, requirements) {
1415
1416 // sort params alphabetically
1417 var requirementKeys = [];
1418 for (var requirementKey in requirements) {
1419 requirementKeys.push(requirementKey);
1420 }
1421 requirementKeys.sort();
1422
1423 var str = fragmentId;
1424
1425 // add in requirement keys
1426 for (var i = 0; i < requirementKeys.length; i++)
1427 {
1428 var requirementKey = requirementKeys[i];
1429 var requirementValue = requirements[requirementKey];
1430
1431 str += "&" + requirementKey + "=" + requirementValue;
1432 }
1433
1434 // calculate a signature to serve as a fragment cache key
1435 return hashSignature(str);
1436};
1437
1438var enhanceNode = exports.enhanceNode = function(node)
1439{
1440 node._qname = node.__qname();
1441 node._type = node.__type();
1442
1443 // add in the "attachments" as a top level property
1444 // if "attachments" already exists, we'll set to "_attachments"
1445 var attachments = {};
1446 for (var id in node.getSystemMetadata()["attachments"])
1447 {
1448 var attachment = node.getSystemMetadata()["attachments"][id];
1449
1450 attachments[id] = clone(attachment, true);
1451 attachments[id]["url"] = "/static/node/" + node.getId() + "/" + id;
1452 attachments[id]["preview32"] = "/static/node/" + node.getId() + "/preview32/?attachment=" + id + "&size=32";
1453 attachments[id]["preview64"] = "/static/node/" + node.getId() + "/preview64/?attachment=" + id + "&size=64";
1454 attachments[id]["preview128"] = "/static/node/" + node.getId() + "/preview128/?attachment=" + id + "&size=128";
1455 attachments[id]["preview256/"] = "/static/node/" + node.getId() + "/preview256/?attachment=" + id + "&size=256";
1456 }
1457
1458 if (!node.attachments) {
1459 node.attachments = attachments;
1460 }
1461 else if (!node._attachments) {
1462 node._attachments = attachments;
1463 }
1464
1465 // add in the "_system" block as a top level property
1466 if (node.getSystemMetadata) {
1467 node._system = node.getSystemMetadata();
1468 }
1469};
1470
1471var status = exports.status = function(res, code)
1472{
1473 res.status(code);
1474
1475 if (code >= 200 && code <= 204)
1476 {
1477 // ok
1478 }
1479 else
1480 {
1481 // don't include cache headers
1482 setHeaderOnce(res, "Cache-Control", "no-cache,no-store,max-age=0,s-maxage=0,must-revalidate");
1483 setHeaderOnce(res, "Expires", "Mon, 7 Apr 2012, 16:00:00 GMT"); // already expired
1484 }
1485
1486 return res;
1487};
1488
1489var noCacheHeaders = exports.noCacheHeaders = function(res)
1490{
1491 setHeaderOnce(res, "Cache-Control", "no-cache,no-store,max-age=0,s-maxage=0,must-revalidate");
1492 setHeaderOnce(res, "Expires", "Mon, 7 Apr 2012, 16:00:00 GMT"); // already expired
1493};
1494
1495var allCacheHeaders = exports.allCacheHeaders = function(res)
1496{
1497 setHeaderOnce(res, "Cache-Control", "no-cache,no-store,max-age=0,s-maxage=0,must-revalidate");
1498 setHeaderOnce(res, "Expires", "Mon, 7 Apr 2012, 16:00:00 GMT"); // already expired
1499};
1500
1501var isWindows = exports.isWindows = function()
1502{
1503 return /^win/.test(process.platform);
1504};
1505
1506var maxFiles = exports.maxFiles = function(callback)
1507{
1508 // if windows, don't bother with this
1509 if (isWindows())
1510 {
1511 return callback(null, -1);
1512 }
1513
1514 var logMethod = function(txt) { };
1515
1516 var commands = [];
1517 commands.push("ulimit -n");
1518 executeCommands(commands, logMethod, function(err, text) {
1519
1520 if (err) {
1521 return callback(err);
1522 }
1523
1524 var maxFiles = -1;
1525 try
1526 {
1527 maxFiles = parseInt(text, 10);
1528 }
1529 catch (e)
1530 {
1531 // swallow
1532 }
1533
1534 callback(null, maxFiles);
1535 });
1536};
1537
1538var countOpenHandles = exports.countOpenHandles = function(callback)
1539{
1540 // if windows, don't bother with this
1541 if (isWindows())
1542 {
1543 return callback(null, -1);
1544 }
1545
1546 fs.readdir('/proc/self/fd', function(err, list) {
1547 if (err) {
1548 return callback(err);
1549 }
1550
1551 callback(null, list.length);
1552 });
1553};
1554
1555var guid = exports.guid = function()
1556{
1557 return uuidv4();
1558};
1559
1560var bytesToSize = exports.bytesToSize = function(bytes)
1561{
1562 var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
1563 if (bytes == 0) return '0 Byte';
1564 var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
1565 return Math.round(bytes / Math.pow(1024, i), 2) + ' ' + sizes[i];
1566};
1567
1568var stripQueryStringFromUrl = exports.stripQueryStringFromUrl = function(url)
1569{
1570 if (!url) {
1571 return;
1572 }
1573
1574 var z = url.indexOf("?");
1575 if (z > -1)
1576 {
1577 url = url.substring(0, z);
1578 }
1579
1580 return url;
1581};
1582
1583var getCookie = exports.getCookie = function(req, name)
1584{
1585 if (!req.cookies) {
1586 return null;
1587 }
1588
1589 return req.cookies[name];
1590};
1591
1592var setCookie = exports.setCookie = function(req, res, name, value, options)
1593{
1594 // if no options are provided, set a few standard options
1595 if (typeof(options) === "undefined") {
1596 // force all cookies to http only
1597 // this prevents browser js sniffing
1598 options = {
1599 "httpOnly": true
1600 };
1601 }
1602
1603 // any cookies requested over https should go back with secure flag set high
1604 if (isSecure(req))
1605 {
1606 options.secure = true;
1607 }
1608
1609 // set cookie on response
1610 res.cookie(name, value, options);
1611};
1612
1613var clearCookie = exports.clearCookie = function(res, name)
1614{
1615 res.clearCookie(name);
1616};
1617
1618var isSecure = exports.isSecure = function(req)
1619{
1620 if (req.protocol && req.protocol.toLowerCase() === "https")
1621 {
1622 return true;
1623 }
1624
1625 // X-FORWARDED-PROTO
1626 var xForwardedProto = null;
1627 if (req.header("CloudFront-Forwarded-Proto"))
1628 {
1629 // support special CloudFront header
1630 xForwardedProto = req.header("CloudFront-Forwarded-Proto");
1631 }
1632 else if (req.header("cloudfront-forwarded-proto"))
1633 {
1634 // support special CloudFront header
1635 xForwardedProto = req.header("cloudfront-forwarded-proto");
1636 }
1637 else if (req.header("X-CloudCMS-Forwarded-Proto"))
1638 {
1639 // support special cloudcms proto header
1640 xForwardedProto = req.header("X-CloudCMS-Forwarded-Proto");
1641 }
1642 else if (req.header("x-cloudcms-forwarded-proto"))
1643 {
1644 // support special cloudcms proto header
1645 xForwardedProto = req.header("x-cloudcms-forwarded-proto");
1646 }
1647 else if (req.header("X-Forwarded-Proto"))
1648 {
1649 xForwardedProto = req.header("X-Forwarded-Proto");
1650 }
1651 else if (req.header("x-forwarded-proto"))
1652 {
1653 xForwardedProto = req.header("x-forwarded-proto");
1654 }
1655 else if (req.header("X-FORWARDED-PROTO"))
1656 {
1657 xForwardedProto = req.header("X-FORWARDED-PROTO");
1658 }
1659
1660 if (xForwardedProto && xForwardedProto.toLowerCase() === "https")
1661 {
1662 return true;
1663 }
1664
1665 /*
1666 // if browser tells us to upgrade
1667 if (req.headers["upgrade-insecure-requests"] === "1")
1668 {
1669 return true;
1670 }
1671 */
1672
1673 return false;
1674};
1675
1676var hashSignature = exports.hashSignature = function(text)
1677{
1678 return sha1(text);
1679};
1680
1681var walkDirectory = exports.walkDirectory = function(dirPath, callback)
1682{
1683 var items = [];
1684
1685 var walker = klaw(dirPath);
1686 walker.on("data", function(item) {
1687 items.push(item);
1688 });
1689 walker.on("end", function() {
1690 callback(null, items);
1691 });
1692};
1693
1694var extractTarGz = exports.extractTarGz = function(tarGzFilePath, extractionPath, callback)
1695{
1696 targz.decompress({
1697 src: tarGzFilePath,
1698 dest: extractionPath
1699 }, function(err) {
1700 callback(err);
1701 });
1702};
1703
1704var zip = exports.zip = function(directoryPath, writableStream)
1705{
1706 var archive = archiver("zip");
1707 archive.on('error', function(err){
1708 throw err;
1709 });
1710
1711 archive.pipe(writableStream);
1712 archive.bulk([{
1713 expand: true,
1714 cwd: directoryPath,
1715 src: ['**/*'],
1716 dest: "/"
1717 }]);
1718 archive.finalize();
1719};
1720
1721/**
1722 * Converts a protocol, host and optional port into a URL.
1723 *
1724 * @param {string} protocol
1725 * @param {string} host
1726 * @param [number|string] port
1727 *
1728 * @type {Function}
1729 */
1730var asURL = exports.asURL = function(protocol, host, port)
1731{
1732 // protocol lower case
1733 protocol = protocol.toLowerCase();
1734
1735 var url = protocol + "://" + host;
1736
1737 // port is optional, so check to make sure it isn't null
1738 if (port)
1739 {
1740 // make sure port is a number
1741 if (typeof(port) === "string") {
1742 port = parseInt(port, 10);
1743 }
1744
1745 // if port and default port don't match, then append
1746 if (protocol === "https" && port !== 443)
1747 {
1748 url += ":" + port;
1749 }
1750 else if (protocol === "http" && port !== 80)
1751 {
1752 url += ":" + port;
1753 }
1754 }
1755
1756 return url;
1757};
1758
1759var cleanupURL = exports.cleanupURL = function(url)
1760{
1761 var _url = urlTool.parse(url);
1762
1763 // if protocol is "https:" (for example), strip back to "https"
1764 var protocol = _url.protocol;
1765 var a = protocol.indexOf(":");
1766 if (a > -1)
1767 {
1768 protocol = protocol.substring(0, a);
1769 }
1770
1771 // NOTE: _url.port may be null
1772 return asURL(protocol, _url.hostname, _url.port);
1773};
1774
1775var clone = exports.clone = function(thing, jsonOnly)
1776{
1777 if (jsonOnly)
1778 {
1779 return JSON.parse(JSON.stringify(thing))
1780 }
1781
1782 // use a deeper approach that copies functions
1783 return cloner(thing);
1784};
1785
1786var randomInt = exports.randomInt = function(low, high)
1787{
1788 return Math.floor(Math.random() * (high - low) + low);
1789};
1790
1791var jsonParse = exports.jsonParse = function(text)
1792{
1793 return JSON5.parse(text);
1794};
1795
1796var isHttps = exports.isHttps = function(url)
1797{
1798 return url.toLowerCase().startsWith("https://");
1799};
1800
1801var isHttp = exports.isHttp = function(url)
1802{
1803 return url.toLowerCase().startsWith("http://");
1804};
1805
1806var getAgent = exports.getAgent = function(url)
1807{
1808 var agent = http.globalAgent;
1809
1810 if (url.indexOf("https://") === 0)
1811 {
1812 agent = https.globalAgent;
1813 }
1814 else if (url.indexOf("http://") === 0)
1815 {
1816 agent = http.globalAgent;
1817 }
1818
1819 return agent;
1820};
\No newline at end of file