├── dislocate
├── templates
│ └── index.tpl
├── utils.js
├── interfaces
│ ├── generic.js
│ └── http.js
├── SConscript
├── schema.js
├── interfaces.js
├── version.js
├── health.js
├── templates.js
├── log.js
├── entry.js
├── config.js
├── pubsub.js
├── client.js
├── services.js
└── auth.js
├── NOTICE
├── extern
├── README-EXTERN
├── json-schema.js
├── stacktrace.js
├── node-router.js
└── json-template.js
├── ndislocate.js
├── test
└── add_service.js
├── TODO
├── README
├── SConstruct
└── LICENSE
/dislocate/templates/index.tpl:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | dislocate index
5 |
6 |
7 | Welcome to Dislocate.
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/NOTICE:
--------------------------------------------------------------------------------
1 | Dislocate
2 | Copyright (c) 2010, Cloudkick, Inc.
3 |
4 | This product includes software developed by
5 | Cloudkick (http://www.cloudkick.com/).
6 |
7 | For extern/node-router.js:
8 | Copyright (c) 2010 Tim Caswell
9 |
10 | for extern/json-template.js:
11 | Copyright (C) 2009 Andy Chu
--------------------------------------------------------------------------------
/extern/README-EXTERN:
--------------------------------------------------------------------------------
1 | This directory contains Third Party code and libraries used by ndislocate.
2 |
3 | node-router 304c0a56e819 (April 30, 2010)
4 | http://github.com/creationix/node-router
5 | License: MIT
6 | Library used to route URLs to handlers in HTTP server.
7 |
8 | json-template 761f60ca03e (December 5, 2009)
9 | http://github.com/andychu/json-template/
10 | License: Apache License 2.0
11 | Library used to render templates with variables.
12 |
13 | json-schema f5a14181aa1 (April 21, 2010)
14 | http://github.com/kriszyp/commonjs-utils/raw/master/lib/
15 | License: MIT
16 | Library used to validate input JSON objects.
17 |
18 | stacktrace fe9d84729c5 (May 29, 2010)
19 | http://github.com/emwendelin/javascript-stacktrace
20 | License: BSD
21 | Utility function to make a stack trace. Slightly modified to use CommonJS style exports.
--------------------------------------------------------------------------------
/ndislocate.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | /*
3 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more
4 | * contributor license agreements. See the NOTICE file distributed with
5 | * this work for additional information regarding copyright ownership.
6 | * Cloudkick licenses this file to You under the Apache License, Version 2.0
7 | * (the "License"); you may not use this file except in compliance with
8 | * the License. You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 |
19 | /**
20 | * Wrapper around dislocate entry point.
21 | */
22 |
23 | var dislocate = require('./dislocate/entry');
24 |
25 | dislocate.run();
26 |
--------------------------------------------------------------------------------
/dislocate/utils.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | /**
19 | * Short utility functions that otherwise wouldn't have a home.
20 | */
21 |
22 | /*
23 | * very simple object merging
24 | * TODO: import mixin from jquery / dojo for real objects
25 | */
26 | exports.merge = function (a, b)
27 | {
28 | for (var attrname in a) {
29 | if (a.hasOwnProperty(attrname)) {
30 | b[attrname] = a[attrname];
31 | }
32 | }
33 | return b;
34 | };
35 |
--------------------------------------------------------------------------------
/dislocate/interfaces/generic.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | /*
19 | * Generic 'interface' utility code. Creates a standard object model
20 | * between the different interfaces.
21 | */
22 |
23 | var log = require('../log');
24 | var services = require('../services');
25 |
26 | exports.list = function()
27 | {
28 | var rv = {'services': services.all()};
29 | return rv;
30 | };
31 |
32 | exports.register = function(obj)
33 | {
34 | services.register(obj.name, obj);
35 | };
36 |
--------------------------------------------------------------------------------
/dislocate/SConscript:
--------------------------------------------------------------------------------
1 | #
2 | # Licensed to Cloudkick, Inc ('Cloudkick') under one or more
3 | # contributor license agreements. See the NOTICE file distributed with
4 | # this work for additional information regarding copyright ownership.
5 | # Cloudkick licenses this file to You under the Apache License, Version 2.0
6 | # (the "License"); you may not use this file except in compliance with
7 | # the License. You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | #
17 |
18 | src = [ 'auth.js',
19 | 'config.js',
20 | 'client.js',
21 | 'entry.js',
22 | 'health.js',
23 | 'interfaces.js',
24 | 'log.js',
25 | 'pubsub.js',
26 | 'schema.js',
27 | 'services.js',
28 | 'templates.js',
29 | 'utils.js',
30 | 'version.js',
31 | 'interfaces/generic.js',
32 | 'interfaces/http.js']
33 |
34 | source = [File(x) for x in src]
35 |
36 | Return("source")
37 |
--------------------------------------------------------------------------------
/dislocate/schema.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | /**
19 | * Validates a JSON object against a prepared set of types.
20 | */
21 |
22 | var jsch = require('../extern/json-schema');
23 | var log = require('./log');
24 | var sys = require('sys');
25 |
26 | var schemas = {
27 | 'service': {
28 | type:"object",
29 | properties:{
30 | name: {tyoe: 'string'},
31 | type: {type:"string"},
32 | address: {type:"string"},
33 | port: {type:'number'}
34 | }
35 | }
36 | };
37 |
38 | exports.validate = function(name, inst)
39 | {
40 | /* TODO: provide better message on errors ?*/
41 | return jsch.validate(inst, schemas[name]);
42 | };
43 |
44 |
--------------------------------------------------------------------------------
/test/add_service.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 |
19 | var client = require('../dislocate/client');
20 | var log = require('../dislocate/log');
21 | var ps = require('../dislocate/pubsub')
22 | var config = require('../dislocate/config');
23 |
24 | (function() {
25 |
26 | config.init();
27 |
28 | ps.sub(ps.CONFIG_DONE, function() {
29 |
30 | client.request({'host': '127.0.0.1', 'port': 1099},
31 | 'PUT', '/d/service',
32 | function(res) {
33 | log.info('all done!', res.res.statusCode, res.body);
34 | },
35 | JSON.stringify({'name':'test.sshd',
36 | 'type': 'static',
37 | 'address': '127.0.0.1',
38 | 'port': 22}));
39 | });
40 | })();
--------------------------------------------------------------------------------
/TODO:
--------------------------------------------------------------------------------
1 | just do it:
2 |
3 | - http server
4 | - service registration (semi done, validation side is done)
5 | - Need to spec out API design soonish
6 | - JSON-Schema auto-generated docs helpful?
7 |
8 | - services heartbeating/checkup (see if tcp port is alive, modules for this)
9 | - static / ttl
10 | - tcp alive
11 | - custom every X seconds
12 | - thrift alive
13 | - http & body match
14 | - databases (mysql, pgsql)
15 | - nosql (cassandra, redis, mongo)
16 | - http & https
17 | - ajp (?)
18 | - amqp
19 |
20 | - on disk services json file (atomic replacement)
21 | - on disk state storage for internal use
22 |
23 | - client clients:
24 | - node.js
25 | - python
26 | - Djnago 1.2 example for database routing:
27 |
28 | - Cassandra Clusters
29 | - php
30 | - Java (?) (ck/esper needs this)
31 |
32 | - dislocate console
33 | - Base on REPLServer
34 | - status, peers, local services, all services, globs, subscribe, disable, etc
35 |
36 | other:
37 |
38 | - documentation!
39 |
40 | - test cases / testing framework
41 | - maybe just assert's for now?
42 |
43 | design:
44 |
45 | - design peer to peer network protocol
46 | - http (?)
47 | - sha512 HMAC on data
48 | - think about SSL crap / hate CAs / etc
49 |
50 | - think about gossip protocol more
51 | - do we need it initially?
52 | - network partitions
53 | - partial tree updates
54 | - how do you update efficiently without transmitting the entire tree. (tree diffs between timestamps?) (there have to be some academic papers about this part)
55 |
56 | - Convince Dan
--------------------------------------------------------------------------------
/README:
--------------------------------------------------------------------------------
1 | Dislocate - A distributed service locator
2 | ------
3 |
4 | Dislocate aims to eliminate the need to configure 'where' a service is
5 | available on a network, and enable auto discovery of new services, and high
6 | performance location of these services by clients.
7 |
8 | Dislocate is a combination of what monitoring, DNS and load balancers are meant
9 | to do -- but maintaining those services on a large scale distributed system
10 | is a significant toll on sysadmins. Dislocate aims to reduce the amount of
11 | work that sysadmins need to do to maintain properly scaling architectures on
12 | distributed, disperse and dynamic networks.
13 |
14 | Clients on the localhost ask Dislocate for a service, via either HTTP or a file
15 | on disk. Dislocate provides information about both the service, and the machine
16 | hosting it, so that the clients can make load balancing decisions, like
17 | using the least loaded slave SQL server.
18 |
19 | Dislocate itself bootstraps based upon a few seed nodes, and then forms a
20 | peer to peer network over TCP with other Dislocate nodes. Communication
21 | between each node is authenticated by a shared secret.
22 |
23 | Services register themselves with their local Dislcoate instance on startup, and
24 | then Dislocate will advertise them to other peers. The service can register
25 | how Dislocate should see if its still alive, including TCP health checks
26 | or other custom functions.
27 |
28 | Status
29 | ------
30 | Pre-Alpha. Lots of work to do, I've been trying to hack down the TODO file
31 | whenever I get a chance.
32 |
33 | I welcome help:
34 | IRC: pquerna on Freenode (#bloglines or other channels).
35 | E-Mail:
36 |
37 |
--------------------------------------------------------------------------------
/dislocate/interfaces.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | /**
19 | * Manages the external interfaces used to communicate with dislocate.
20 | *
21 | * This includes the http server and local json file storage.
22 | *
23 | * No implementations are in this file, just management of them.
24 | */
25 |
26 | var ps = require('./pubsub');
27 | var interfaces = {
28 | 'http': require('./interfaces/http')
29 | };
30 | var running = [];
31 |
32 | exports.start = function()
33 | {
34 | for (var key in interfaces) {
35 | if (interfaces.hasOwnProperty(key)) {
36 | mod = interfaces[key];
37 | mod.start();
38 | running.push(mod);
39 | }
40 | }
41 |
42 | ps.sub(ps.STATE_STOP, function() {
43 | exports.stop();
44 | });
45 | };
46 |
47 | exports.stop = function()
48 | {
49 | running.forEach(function(entry) {
50 | entry.stop();
51 | });
52 | running = [];
53 | };
54 |
--------------------------------------------------------------------------------
/dislocate/version.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | /**
19 | * Provides the current version of dislocate.
20 | *
21 | * These are written in an easily parsable format for use by
22 | * other (non-js) scripts to easily grep out the version number.
23 | */
24 |
25 | var DISLOCATE_VERSION_MAJOR = 0;
26 | var DISLOCATE_VERSION_MINOR = 1;
27 | var DISLOCATE_VERSION_PATCH = 0;
28 |
29 | /* change this on release tags */
30 | var DISLCOATE_IS_DEV = true;
31 |
32 | /* Used in the network protocol for compatibility testing. */
33 | var DISLOCATE_MAGIC = 2010052200;
34 |
35 | exports.MAJOR = DISLOCATE_VERSION_MAJOR;
36 | exports.MINOR = DISLOCATE_VERSION_MINOR;
37 | exports.PATCH = DISLOCATE_VERSION_PATCH;
38 | exports.IS_DEV = DISLCOATE_IS_DEV;
39 | exports.MAGIC = DISLOCATE_MAGIC;
40 |
41 | exports.toString = function()
42 | {
43 | dstr = '-dev';
44 |
45 | if (DISLCOATE_IS_DEV === false) {
46 | dstr = '-release';
47 | }
48 |
49 | return 'dislcoate-'+ DISLOCATE_VERSION_MAJOR +'.'+ DISLOCATE_VERSION_MINOR +'.'+ DISLOCATE_VERSION_PATCH +''+dstr;
50 | };
51 |
--------------------------------------------------------------------------------
/dislocate/health.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | /**
19 | * This module keeps track of the health and status of the local machine.
20 | *
21 | * This state information is used by other nodes to determine how much load
22 | * to place on it, but this module itself only cares about local state.
23 | */
24 |
25 | var ps = require('./pubsub');
26 | var log = require('./log');
27 | var update_frequency = 5;
28 | var status = null;
29 | var timerId = null;
30 |
31 | function getloadaverage()
32 | {
33 | /* TODO: getloadaverage */
34 | return 0.1;
35 | }
36 |
37 | function get_status()
38 | {
39 | var r = {'load': getloadaverage()};
40 |
41 | status = r;
42 |
43 | setTimeout(get_status, update_frequency * 1000);
44 | }
45 |
46 |
47 | exports.status = function()
48 | {
49 | return status;
50 | };
51 |
52 | (function(){
53 | ps.sub(ps.CONFIG_DONE, function(){
54 | get_status();
55 | timerId = setTimeout(get_status, update_frequency * 1000);
56 | ps.sub(ps.STATE_STOP, function() {
57 | clearTimeout(timerId);
58 | });
59 | });
60 | })();
61 |
--------------------------------------------------------------------------------
/dislocate/templates.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | /**
19 | * Provides JSON based templates for use by other modules.
20 | */
21 |
22 | var Template = require("../extern/json-template").Template;
23 | var sys = require('sys');
24 | var fs = require('fs');
25 | var path = require('path');
26 | var utils = require("./utils");
27 | var template_dir = null;
28 |
29 | function load(name, cb)
30 | {
31 | p = path.join(template_dir, name + ".tpl");
32 | fs.readFile(p, function(err, data) {
33 | if (err) {
34 | msg = "Loading "+ p +" got: "+ err + "\n";
35 | cb(msg);
36 | return;
37 | }
38 | /* TODO: Cache parsed template files? */
39 | t = Template(data.toString());
40 | cb(null, t);
41 | });
42 | }
43 |
44 | exports.render = function(name, ctx, cb)
45 | {
46 | default_vars = {};
47 | ctx = utils.merge(ctx, default_vars);
48 | load(name, function(err, tpl) {
49 | if (err) {
50 | cb(err);
51 | return;
52 | }
53 | tpl.render(ctx, function(result) {
54 | cb(null, result);
55 | });
56 | });
57 | };
58 |
59 | (function() {
60 | template_dir = path.join(__dirname, "templates");
61 | })();
62 |
--------------------------------------------------------------------------------
/dislocate/log.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 |
19 | /**
20 | * Logging subsytem, providing log filtering and a simple set of log message
21 | * types.
22 | */
23 |
24 | var sys = require("sys");
25 | var stacktrace = require('../extern/stacktrace');
26 | /**
27 | * TODO: format strings
28 | */
29 |
30 | var loglevels = {
31 | 'nothing': 0,
32 | 'crit': 1,
33 | 'err': 2,
34 | 'warn': 3,
35 | 'info': 4,
36 | 'debug': 5
37 | };
38 |
39 | var loglevel_strs = [];
40 | var loglevel = loglevels.debug;
41 |
42 | function logit (level, inargs) {
43 | if (level <= loglevel) {
44 | sys.log(loglevel_strs[level]+ ": "+ Array.apply({}, inargs).join(" "));
45 | }
46 | }
47 |
48 | exports.set_loglevel = function(level) {
49 | loglevel = level;
50 | };
51 |
52 | exports.trace = function() {
53 | return stacktrace.trace();
54 | };
55 |
56 | (function() {
57 | for (var attrname in loglevels) {
58 | if (loglevels.hasOwnProperty(attrname)) {
59 | loglevel_strs[loglevels[attrname]] = attrname;
60 | exports[attrname] = (function (level) {
61 | return function () {
62 | return logit(level, arguments);
63 | };
64 | })(loglevels[attrname]);
65 | }
66 | }
67 | })();
68 |
--------------------------------------------------------------------------------
/SConstruct:
--------------------------------------------------------------------------------
1 | #
2 | # Licensed to Cloudkick, Inc ('Cloudkick') under one or more
3 | # contributor license agreements. See the NOTICE file distributed with
4 | # this work for additional information regarding copyright ownership.
5 | # Cloudkick licenses this file to You under the Apache License, Version 2.0
6 | # (the "License"); you may not use this file except in compliance with
7 | # the License. You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | #
17 |
18 | EnsureSConsVersion(1, 1, 0)
19 |
20 | import os
21 | import re
22 | from os.path import join as pjoin
23 |
24 | opts = Variables('build.py')
25 |
26 | env = Environment(options=opts,
27 | ENV = os.environ.copy(),
28 | tools=['default'])
29 |
30 | #TODO: convert this to a configure builder, so it gets cached
31 | def read_version(prefix, path):
32 | version_re = re.compile("(.*)%s_VERSION_(?PMAJOR|MINOR|PATCH)(\s+)=(\s+)(?P\d)(.*)" % prefix)
33 | versions = {}
34 | fp = open(path, 'rb')
35 | for line in fp.readlines():
36 | m = version_re.match(line)
37 | if m:
38 | versions[m.group('id')] = int(m.group('num'))
39 | fp.close()
40 | return (versions['MAJOR'], versions['MINOR'], versions['PATCH'])
41 |
42 | env['version_major'], env['version_minor'], env['version_patch'] = read_version('DISLOCATE', 'dislocate/version.js')
43 | env['version_string'] = "%d.%d.%d" % (env['version_major'], env['version_minor'], env['version_patch'])
44 |
45 | conf = Configure(env, custom_tests = {})
46 |
47 | conf.env.AppendUnique(RPATH = conf.env.get('LIBPATH'))
48 | env = conf.Finish()
49 |
50 | Export("env")
51 |
52 | source = SConscript("dislocate/SConscript")
53 |
54 | jslint = [env.Command(str(x)+".jslint", x, ["jslint $SOURCE || exit 0"]) for x in source]
55 | #env.AlwaysBuild(jslint)
56 |
57 | Alias('jslint', jslint)
58 |
59 | targets = []
60 |
61 | env.Default(targets)
62 |
--------------------------------------------------------------------------------
/dislocate/entry.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | /**
19 | * This module provides a top level entry point and provides flow control to
20 | * startup, run, and shutdown the dislocate service.
21 | */
22 |
23 | var sys = require("sys");
24 | var log = require("./log");
25 | var config = require("./config");
26 | var ps = require('./pubsub');
27 | var services = require('./services');
28 | var auth = require('./auth');
29 | var interfaces = require('./interfaces');
30 | var version = require('./version');
31 |
32 | exports.run = function() {
33 | var called_stop = false;
34 | log.info(version.toString());
35 | var rv = config.init();
36 |
37 | if (!rv) {
38 | return;
39 | }
40 |
41 | ps.sub(ps.STATE_STOP, function() {
42 | called_stop = true;
43 | }, true);
44 |
45 | ps.sub(ps.STATE_EXIT, function() {
46 | if (called_stop === false) {
47 | ps.pub(ps.STATE_STOP);
48 | called_stop = true;
49 | }
50 | }, true);
51 |
52 | ps.sub(ps.CONFIG_DONE, function() {
53 | process.addListener('SIGINT', function () {
54 | log.debug("Caught SIGINT, exiting....");
55 | ps.pub(ps.STATE_EXIT, {'why': 'signal', 'value': "SIGINT"});
56 | process.exit();
57 | });
58 | services.start();
59 | interfaces.start();
60 |
61 | services.register('test.sshd', {'type': 'static', 'address': '127.0.0.1', 'port': 22});
62 | services.register('test.webservers.mine', {'type': 'http', 'address': '127.0.0.1', 'port': 80});
63 | services.register('test.webservers.mine', {'type': 'http', 'address': '127.0.0.1', 'port': 8080});
64 | }, true);
65 | };
66 |
--------------------------------------------------------------------------------
/dislocate/config.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | /**
19 | * Configuration subsytem, providing a set of defaults, and merging of a configuration
20 | * JSON file.
21 | */
22 |
23 | var fs = require('fs');
24 | var sys = require('sys');
25 | var log = require('./log');
26 | var ps = require('./pubsub');
27 | var utils = require('./utils');
28 |
29 | var config = {
30 | 'user': 'nobody',
31 | 'port': 49700,
32 | 'address': '0.0.0.0',
33 | 'secret': null,
34 | /* TODO: pick a better default / prefix directory */
35 | 'state_directory': '/var/tmp/dislocate',
36 | 'authentication_format': {
37 | 'algorithm': 'sha256',
38 | 'encoding': 'base64'
39 | }
40 | };
41 |
42 | function drop_uid(uid)
43 | {
44 | /* TOOD: username string -> UID */
45 | cuid = process.getuid();
46 | /* TODO: setgid? */
47 | if (cuid === 0) {
48 | process.setuid(uid);
49 | }
50 | else {
51 | log.warn('Currently running as', ''+cuid, 'not changing to', ''+uid);
52 | }
53 | }
54 |
55 | function parse_config(path) {
56 | fs.readFile('test.json', function(err, data) {
57 | parsed = JSON.parse(data.toString());
58 | log.err(parsed);
59 | config = utils.merge(parsed, config);
60 | ps.pub(ps.CONFIG_DONE);
61 | });
62 | }
63 |
64 | exports.init = function() {
65 | if (process.argv.length != 3) {
66 | log.err('3rd argument must be configuration file.');
67 | return false;
68 | }
69 | cfile = process.argv[2];
70 | log.debug('Reading configuration from', cfile);
71 | parse_config(cfile);
72 | return true;
73 | };
74 |
75 | exports.get = function() {
76 | return config;
77 | };
78 |
--------------------------------------------------------------------------------
/dislocate/pubsub.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | var log = require('./log');
19 | var pending = {};
20 | /*
21 |
22 | TODO: consider rewriting pubsub to use EventEmitter internally, I'm not sure
23 | it can do 'single' event subs in an easy way though, nor i'm not sure
24 | about the performance difference.
25 |
26 | var sys = require('sys');
27 | var events = require('events').EventEmitter;
28 | var emitter = null;
29 | sys.inherits(PubSubEmitter, events.EventEmitter);
30 |
31 | (function(){
32 | emitter = new PubSubEmitter();
33 | })();
34 |
35 | */
36 |
37 | exports.CONFIG_DONE = "dislocate.config.done";
38 | exports.STATE_START ='dislocate.state.start';
39 | exports.STATE_STOP ='dislocate.state.stop';
40 | exports.STATE_EXIT ='dislocate.state.exit';
41 | exports.LOCAL_SERVICE_REGISTER = "dislocate.local.service.register";
42 |
43 |
44 | exports.pub = function(path, data)
45 | {
46 | if (path === undefined) {
47 | throw "pubsub: path must be defined, did you forget to add a new event type?";
48 | }
49 |
50 | //log.debug("pub:", path, data);
51 | if (pending[path] !== undefined) {
52 | var arr = pending[path];
53 | pending[path] = arr.filter(function(i) {
54 | i.cb(data);
55 | if (i.once === true) {
56 | return false;
57 | }
58 | return true;
59 | });
60 | }
61 | };
62 |
63 | exports.sub = function(path, cb, once)
64 | {
65 | if (path === undefined) {
66 | throw "pubsub: path must be defined, did you forget to add a new event type?";
67 | }
68 |
69 | if (once === undefined) {
70 | once = false;
71 | }
72 |
73 | if (pending[path] === undefined) {
74 | pending[path] = [];
75 | }
76 |
77 | //log.debug("sub:", path, cb);
78 | pending[path].push({'cb': cb, 'once': once});
79 | };
80 |
--------------------------------------------------------------------------------
/dislocate/client.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | /**
19 | * 'low level' Peer Client interface. Only provides thin wrapper around
20 | * http + the tokens used for authentication.
21 | */
22 | var auth = require('./auth');
23 | var http = require('http');
24 | var log = require('./log');
25 |
26 | exports.request = function(peer, method, path, cb, body)
27 | {
28 | var h = http.createClient(peer.port, peer.host);
29 | var preq = {
30 | 'method': method,
31 | 'url': path,
32 | 'headers': {'Host': peer.host,
33 | 'Transfer-Encoding': 'chunked'}
34 | };
35 |
36 | if (preq.headers.Date === undefined) {
37 | preq.headers.Date = (new Date()).toUTCString();
38 | }
39 |
40 | /* TODO: auth header here */
41 | var sig = auth.generateFromRequest(preq, body);
42 |
43 | if (sig.err !== false) {
44 | throw sig.err;
45 | }
46 |
47 | preq.headers['X-Dislocate-Signature'] = sig.hmac;
48 |
49 | var req = h.request(method, path, preq.headers);
50 |
51 | req.addListener('response', function (res) {
52 | var buf = "";
53 | res.setEncoding('utf8');
54 | res.addListener('data', function (chunk) {
55 | if (chunk !== undefined) {
56 | buf += chunk;
57 | }
58 | });
59 | res.addListener('end', function() {
60 | var sig = auth.generateFromResponse(res, res.statusCode, res.headers, buf);
61 | var rv = {'err': false, 'body': buf, 'res': res};
62 | var hmac = res.headers['x-dislocate-signature'];
63 | if (!auth.validateRaw(sig.hmac, hmac)) {
64 | rv.err = "Invalid HMAC";
65 | log.err('Invalid response HMAC. Expected ', sig.hmac, ' got ', hmac);
66 | }
67 | cb(rv);
68 | });
69 | });
70 | if (body !== undefined) {
71 | req.write(body);
72 | }
73 | req.end();
74 | };
75 |
76 |
--------------------------------------------------------------------------------
/dislocate/services.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | /**
19 | * Manages the state of services.
20 | *
21 | * Dislocate is built around a hierachical list of objects.
22 | *
23 | * Each object has a set of metadata.
24 | *
25 | * the ^ prefix is used for internal metadata
26 | * and should not be used or set by clients
27 | *
28 | * names are seperated by a period ('.'), so this
29 | * lets clients subscribe to a part of the tree.
30 | *
31 | **/
32 |
33 | var log = require('./log');
34 | var utils = require('./utils');
35 | var ps = require('./pubsub');
36 |
37 |
38 | var services = {
39 | '^': {
40 | 'dislocate': {
41 | 'services': []
42 | }
43 | }
44 | };
45 |
46 | // metdata type implies how you check if the service is alive
47 | /*
48 | var native_types = {
49 | 'tcp': require('./services/tcp')
50 | }
51 | */
52 |
53 | function preptree(name)
54 | {
55 | var last = null;
56 | var parts = name.split('.');
57 | parts.forEach(function(p) {
58 | if (services[p] === undefined) {
59 | services[p] = {};
60 | }
61 | last = services[p];
62 | });
63 | if (last.services === undefined) {
64 | last.services = [];
65 | }
66 | return last;
67 | }
68 |
69 | var checkup_interval = 10000;
70 | var runchecks_timer = null;
71 |
72 | function runchecks()
73 | {
74 | runchecks_timer = setTimeout(runchecks, checkup_interval);
75 | }
76 |
77 | exports.start = function()
78 | {
79 | runchecks_timer = setTimeout(runchecks, checkup_interval);
80 | ps.sub(ps.STATE_STOP, function() {
81 | exports.stop();
82 | });
83 | };
84 |
85 | exports.stop = function()
86 | {
87 | clearTimeout(runchecks_timer);
88 | };
89 |
90 | exports.register = function(name, metadata) {
91 | var last = preptree(name);
92 |
93 | last.services.push(metadata);
94 |
95 | log.debug("Registered new service: ", name);
96 | };
97 |
98 | exports.find = function(name) {
99 | var last = preptree(name);
100 | return last.services;
101 | };
102 |
103 | exports.all = function()
104 | {
105 | var r = {};
106 | return utils.merge(services, r);
107 | };
108 |
--------------------------------------------------------------------------------
/dislocate/auth.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | /**
19 | * Provides a HMAC authentcation of a message, using the configured secret,
20 | * and authentication formats. Currently defaults to a SHA256 HAMC formated
21 | * in base64.
22 | */
23 |
24 | var crypto = require('crypto');
25 | var log = require('./log');
26 | var config = require('./config');
27 |
28 | exports.validate = function(input, hmac)
29 | {
30 | var correct = exports.generate(input);
31 | return exports.validateRaw(correct, hmac);
32 | };
33 |
34 | exports.validateRaw = function(correct, hmac)
35 | {
36 | if (correct.length != hmac.length) {
37 | return false;
38 | }
39 |
40 | var rv = 0;
41 |
42 | for (var i = 0; i < correct.length; i++) {
43 | rv = rv | (correct[i] ^ hmac[i]);
44 | }
45 |
46 | if (rv === 0) {
47 | return true;
48 | }
49 |
50 | return false;
51 | };
52 |
53 | exports.generate = function(input)
54 | {
55 | /* TODO: better handle inputs of objects / non-string types in a consistent way */
56 | var c = config.get();
57 | var h = crypto.createHmac(c.authentication_format.algorithm, c.secret);
58 | h.update(input);
59 | return h.digest(c.authentication_format.encoding);
60 | };
61 |
62 | exports.generateFromRequest = function(req, body)
63 | {
64 | /**
65 | * TODO: Add other HTTP headers
66 | * TODO: talk over this scheme with someone else
67 | *
68 | * Generates HMAC of the following:
69 | * HTTP Method
70 | * URL of the request
71 | * HTTP Date Header
72 | * Entire Request Body
73 | */
74 |
75 | var rv = {'err': false, 'hmac': null};
76 | var inputs = [req.method];
77 | inputs.push(req.url);
78 |
79 | var d = req.headers.date;
80 | if (d === undefined) {
81 | d = req.headers.Date;
82 | }
83 |
84 | if (d === undefined) {
85 | rv.err = 'Date http header must be sent';
86 | return rv;
87 | }
88 |
89 | inputs.push(d);
90 | inputs.push(body);
91 |
92 | var input = inputs.join("");
93 | rv.hmac = exports.generate(input);
94 | return rv;
95 | };
96 |
97 | exports.generateFromResponse = function(res, code, headers, body)
98 | {
99 | var rv = {'err': false, 'hmac': null};
100 | var inputs = [code];
101 | var d = headers.date;
102 | if (d === undefined) {
103 | d = headers.Date;
104 | }
105 |
106 | if (d === undefined) {
107 | rv.err = 'Date http header must be sent';
108 | return rv;
109 | }
110 |
111 | inputs.push(d);
112 | inputs.push(body);
113 |
114 | var input = inputs.join("");
115 |
116 | rv.hmac = exports.generate(input);
117 |
118 | return rv;
119 | };
120 |
--------------------------------------------------------------------------------
/dislocate/interfaces/http.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to Cloudkick, Inc ('Cloudkick') under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * Cloudkick licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 |
19 | /**
20 | * HTTP Interface to Dislocate.
21 | *
22 | * Provides read and write access to the state of the dislocate node.
23 | *
24 | * Only requests from 127.0.0.1 or peers with the correct secret are
25 | * allowed to modify any settings.
26 | */
27 |
28 | var log = require('../log');
29 | var config = require('../config');
30 | var nr = require('../../extern/node-router');
31 | var templates = require("../templates");
32 | var generic = require('./generic');
33 | var schema = require('../schema');
34 | var auth = require('../auth');
35 | var sys = require('sys');
36 | var server = null;
37 |
38 | function addResponseAuth(res)
39 | {
40 | res.writeAll = function (code, headers, body) {
41 | if (headers.Date === undefined) {
42 | /* TODO: not correct */
43 | headers.Date = (new Date()).toUTCString();
44 | }
45 | var sig = auth.generateFromResponse(res, code, headers, body);
46 | headers['X-Dislocate-Signature'] = sig.hmac;
47 | res.writeHead(code, headers);
48 | res.write(body);
49 | res.end();
50 | };
51 | }
52 |
53 | function shortResponse(res, status, message)
54 | {
55 | message = message + "";
56 | res.writeAll(status, {
57 | "Content-Type": "text/plain; charset=utf-8",
58 | "Content-Length": message.length
59 | }, message);
60 | }
61 |
62 | function renderResponse(res, name, context)
63 | {
64 | templates.render(name, context,
65 | function (err, result) {
66 | if (err) {
67 | shortResponse(res, 500, "Exception rendering template "+ name +": "+ err);
68 | return;
69 | }
70 | shortResponse(res, 200, result);
71 | });
72 | }
73 |
74 | function renderJSON(res, context)
75 | {
76 | data = JSON.stringify(context);
77 | res.writeAll(200, {
78 | 'Content-Type': 'application/json; charset=utf-8',
79 | "Content-Length": data.length
80 | }, data);
81 | }
82 |
83 | function renderSuccess(res)
84 | {
85 | /* TODO: make this a consistent JSON */
86 | shortResponse(res, 200, "great success!\n");
87 | }
88 |
89 | function checkAuth(req, res, body, success, failure)
90 | {
91 | addResponseAuth(res);
92 |
93 | if (failure === undefined) {
94 | failure = function(reason) {
95 | shortResponse(res, 401, reason + '\n');
96 | };
97 | }
98 |
99 | var proposed = req.headers["x-dislocate-signature"];
100 | if (proposed !== undefined) {
101 | /* TODO: this is bogus. Should NEVER reconvert the body to JSON again. */
102 | var good = auth.generateFromRequest(req, JSON.stringify(body));
103 |
104 | if (good.err !== false) {
105 | log.info('sending failure');
106 | return failure("request failed to authenticate: "+ good.err);
107 | }
108 |
109 | if (auth.validateRaw(good.hmac, proposed) === true) {
110 | return success();
111 | }
112 |
113 | return failure("request failed to authenticate");
114 | }
115 |
116 | /* TODO: this is just wrong for IPb6 enabled machines */
117 | if (req.connection.remoteAddress == "127.0.0.1") {
118 | return success();
119 | }
120 |
121 | return failure("must be from localhost");
122 | }
123 |
124 | function checkBody(req, res, object_name, object_to_check, success, failure)
125 | {
126 | if (failure === undefined) {
127 | failure = function(reason) {
128 | shortResponse(res, 400, reason + '\n');
129 | };
130 | }
131 |
132 | /* TODO: Figure out how much this hurts performance */
133 | var v = schema.validate(object_name, object_to_check);
134 | if (!v.valid) {
135 | var msg = "";
136 | for (var i = 0; i < v.errors.length; i++) {
137 | msg += v.errors[i].property +": "+ v.errors[i].message + "\n";
138 | }
139 | failure('Invalid JSON Object: '+ msg);
140 | return;
141 | }
142 |
143 | return success();
144 | }
145 |
146 | exports.start = function()
147 | {
148 | var c = config.get();
149 | server = nr.getServer();
150 |
151 | server.get("/", function (req, res, match) {
152 | return renderResponse(res, 'index', {});
153 | });
154 |
155 | server.get("/d/services", function (req, res, match) {
156 | var services = generic.list();
157 | return renderJSON(res, services);
158 | });
159 |
160 | server.put("/d/service", function (req, res, body) {
161 | checkAuth(req, res, body, function() {
162 | checkBody(req, res, 'service', body, function(){
163 | var rv = generic.register(body);
164 | if (rv === true) {
165 | return renderSuccess(res);
166 | }
167 | else {
168 | return shortResponse(res, 500, rv + '\n');
169 | }
170 | });
171 | });
172 | }, "json");
173 |
174 | log.info("Starting HTTP server on", c.port);
175 | server.listen(c.port, "0.0.0.0");
176 | };
177 |
178 | exports.stop = function()
179 | {
180 | if (server !== null) {
181 | server.close();
182 | server = null;
183 | }
184 | };
185 |
--------------------------------------------------------------------------------
/extern/json-schema.js:
--------------------------------------------------------------------------------
1 | /**
2 | * JSONSchema Validator - Validates JavaScript objects using JSON Schemas
3 | * (http://www.json.com/json-schema-proposal/)
4 | *
5 | * Copyright (c) 2007 Kris Zyp SitePen (www.sitepen.com)
6 | * Licensed under the MIT (MIT-LICENSE.txt) license.
7 | To use the validator call JSONSchema.validate with an instance object and an optional schema object.
8 | If a schema is provided, it will be used to validate. If the instance object refers to a schema (self-validating),
9 | that schema will be used to validate and the schema parameter is not necessary (if both exist,
10 | both validations will occur).
11 | The validate method will return an array of validation errors. If there are no errors, then an
12 | empty list will be returned. A validation error will have two properties:
13 | "property" which indicates which property had the error
14 | "message" which indicates what the error was
15 | */
16 |
17 | // setup primitive classes to be JSON Schema types
18 | String.type = "string";
19 | Boolean.type = "boolean";
20 | Number.type = "number";
21 | exports.Integer = {type:"integer"};
22 | Object.type = "object";
23 | Array.type = "array";
24 | Date.type = "string";
25 | Date.format = "date-time";
26 |
27 | exports.validate = function(/*Any*/instance,/*Object*/schema) {
28 | // Summary:
29 | // To use the validator call JSONSchema.validate with an instance object and an optional schema object.
30 | // If a schema is provided, it will be used to validate. If the instance object refers to a schema (self-validating),
31 | // that schema will be used to validate and the schema parameter is not necessary (if both exist,
32 | // both validations will occur).
33 | // The validate method will return an object with two properties:
34 | // valid: A boolean indicating if the instance is valid by the schema
35 | // errors: An array of validation errors. If there are no errors, then an
36 | // empty list will be returned. A validation error will have two properties:
37 | // property: which indicates which property had the error
38 | // message: which indicates what the error was
39 | //
40 | return validate(instance,schema,false);
41 | };
42 | exports.checkPropertyChange = function(/*Any*/value,/*Object*/schema, /*String*/ property) {
43 | // Summary:
44 | // The checkPropertyChange method will check to see if an value can legally be in property with the given schema
45 | // This is slightly different than the validate method in that it will fail if the schema is readonly and it will
46 | // not check for self-validation, it is assumed that the passed in value is already internally valid.
47 | // The checkPropertyChange method will return the same object type as validate, see JSONSchema.validate for
48 | // information.
49 | //
50 | return validate(value,schema, property || "property");
51 | };
52 | var validate = exports._validate = function(/*Any*/instance,/*Object*/schema,/*Boolean*/ _changing) {
53 |
54 | var errors = [];
55 | // validate a value against a property definition
56 | function checkProp(value, schema, path,i){
57 | var l;
58 | path += path ? typeof i == 'number' ? '[' + i + ']' : typeof i == 'undefined' ? '' : '.' + i : i;
59 | function addError(message){
60 | errors.push({property:path,message:message});
61 | }
62 |
63 | if((typeof schema != 'object' || schema instanceof Array) && (path || typeof schema != 'function') && !(schema && schema.type)){
64 | if(typeof schema == 'function'){
65 | if(!(value instanceof schema)){
66 | addError("is not an instance of the class/constructor " + schema.name);
67 | }
68 | }else if(schema){
69 | addError("Invalid schema/property definition " + schema);
70 | }
71 | return null;
72 | }
73 | if(_changing && schema.readonly){
74 | addError("is a readonly field, it can not be changed");
75 | }
76 | if(schema['extends']){ // if it extends another schema, it must pass that schema as well
77 | checkProp(value,schema['extends'],path,i);
78 | }
79 | // validate a value against a type definition
80 | function checkType(type,value){
81 | if(type){
82 | if(typeof type == 'string' && type != 'any' &&
83 | (type == 'null' ? value !== null : typeof value != type) &&
84 | !(value instanceof Array && type == 'array') &&
85 | !(type == 'integer' && value%1===0)){
86 | return [{property:path,message:(typeof value) + " value found, but a " + type + " is required"}];
87 | }
88 | if(type instanceof Array){
89 | var unionErrors=[];
90 | for(var j = 0; j < type.length; j++){ // a union type
91 | if(!(unionErrors=checkType(type[j],value)).length){
92 | break;
93 | }
94 | }
95 | if(unionErrors.length){
96 | return unionErrors;
97 | }
98 | }else if(typeof type == 'object'){
99 | var priorErrors = errors;
100 | errors = [];
101 | checkProp(value,type,path);
102 | var theseErrors = errors;
103 | errors = priorErrors;
104 | return theseErrors;
105 | }
106 | }
107 | return [];
108 | }
109 | if(value === undefined){
110 | if(!schema.optional && !schema.get){
111 | addError("is missing and it is not optional");
112 | }
113 | }else{
114 | errors = errors.concat(checkType(schema.type,value));
115 | if(schema.disallow && !checkType(schema.disallow,value).length){
116 | addError(" disallowed value was matched");
117 | }
118 | if(value !== null){
119 | if(value instanceof Array){
120 | if(schema.items){
121 | if(schema.items instanceof Array){
122 | for(i=0,l=value.length; i schema.maxItems){
135 | addError("There must be a maximum of " + schema.maxItems + " in the array");
136 | }
137 | }else if(schema.properties || schema.additionalProperties){
138 | errors.concat(checkObj(value, schema.properties, path, schema.additionalProperties));
139 | }
140 | if(schema.pattern && typeof value == 'string' && !value.match(schema.pattern)){
141 | addError("does not match the regex pattern " + schema.pattern);
142 | }
143 | if(schema.maxLength && typeof value == 'string' && value.length > schema.maxLength){
144 | addError("may only be " + schema.maxLength + " characters long");
145 | }
146 | if(schema.minLength && typeof value == 'string' && value.length < schema.minLength){
147 | addError("must be at least " + schema.minLength + " characters long");
148 | }
149 | if(typeof schema.minimum !== undefined && typeof value == typeof schema.minimum &&
150 | schema.minimum > value){
151 | addError("must have a minimum value of " + schema.minimum);
152 | }
153 | if(typeof schema.maximum !== undefined && typeof value == typeof schema.maximum &&
154 | schema.maximum < value){
155 | addError("must have a maximum value of " + schema.maximum);
156 | }
157 | if(schema['enum']){
158 | var enumer = schema['enum'];
159 | l = enumer.length;
160 | var found;
161 | for(var j = 0; j < l; j++){
162 | if(enumer[j]===value){
163 | found=1;
164 | break;
165 | }
166 | }
167 | if(!found){
168 | addError("does not have a value in the enumeration " + enumer.join(", "));
169 | }
170 | }
171 | if(typeof schema.maxDecimal == 'number' &&
172 | (value.toString().match(new RegExp("\\.[0-9]{" + (schema.maxDecimal + 1) + ",}")))){
173 | addError("may only have " + schema.maxDecimal + " digits of decimal places");
174 | }
175 | }
176 | }
177 | return null;
178 | }
179 | // validate an object against a schema
180 | function checkObj(instance,objTypeDef,path,additionalProp){
181 |
182 | if(typeof objTypeDef =='object'){
183 | if(typeof instance != 'object' || instance instanceof Array){
184 | errors.push({property:path,message:"an object is required"});
185 | }
186 |
187 | for(var i in objTypeDef){
188 | if(objTypeDef.hasOwnProperty(i) && !(i.charAt(0) == '_' && i.charAt(1) == '_')){
189 | var value = instance[i];
190 | var propDef = objTypeDef[i];
191 | checkProp(value,propDef,path,i);
192 | }
193 | }
194 | }
195 | for(i in instance){
196 | if(instance.hasOwnProperty(i) && !(i.charAt(0) == '_' && i.charAt(1) == '_') && objTypeDef && !objTypeDef[i] && additionalProp===false){
197 | errors.push({property:path,message:(typeof value) + "The property " + i +
198 | " is not defined in the schema and the schema does not allow additional properties"});
199 | }
200 | var requires = objTypeDef && objTypeDef[i] && objTypeDef[i].requires;
201 | if(requires && !(requires in instance)){
202 | errors.push({property:path,message:"the presence of the property " + i + " requires that " + requires + " also be present"});
203 | }
204 | value = instance[i];
205 | if(additionalProp && (!(objTypeDef && typeof objTypeDef == 'object') || !(i in objTypeDef))){
206 | checkProp(value,additionalProp,path,i);
207 | }
208 | if(!_changing && value && value.$schema){
209 | errors = errors.concat(checkProp(value,value.$schema,path,i));
210 | }
211 | }
212 | return errors;
213 | }
214 | if(schema){
215 | checkProp(instance,schema,'',_changing || '');
216 | }
217 | if(!_changing && instance && instance.$schema){
218 | checkProp(instance,instance.$schema,'','');
219 | }
220 | return {valid:!errors.length,errors:errors};
221 | };
222 | exports.mustBeValid = function(result){
223 | // summary:
224 | // This checks to ensure that the result is valid and will throw an appropriate error message if it is not
225 | // result: the result returned from checkPropertyChange or validate
226 | if(!result.valid){
227 | throw new TypeError(result.errors.map(function(error){return "for property " + error.property + ': ' + error.message;}).join(", \n"));
228 | }
229 | }
230 | /* will add this later
231 | newFromSchema : function() {
232 | }
233 | */
234 |
235 |
--------------------------------------------------------------------------------
/extern/stacktrace.js:
--------------------------------------------------------------------------------
1 | // Domain Public by Eric Wendelin http://eriwen.com/ (2008)
2 | // Luke Smith http://lucassmith.name/ (2008)
3 | // Loic Dachary (2008)
4 | // Johan Euphrosine (2008)
5 | // Øyvind Sean Kinsey http://kinsey.no/blog (2010)
6 | //
7 | // Information and discussions
8 | // http://jspoker.pokersource.info/skin/test-printstacktrace.html
9 | // http://eriwen.com/javascript/js-stack-trace/
10 | // http://eriwen.com/javascript/stacktrace-update/
11 | // http://pastie.org/253058
12 | // http://browsershots.org/http://jspoker.pokersource.info/skin/test-printstacktrace.html
13 | //
14 |
15 | //
16 | // guessFunctionNameFromLines comes from firebug
17 | //
18 | // Software License Agreement (BSD License)
19 | //
20 | // Copyright (c) 2007, Parakey Inc.
21 | // All rights reserved.
22 | //
23 | // Redistribution and use of this software in source and binary forms, with or without modification,
24 | // are permitted provided that the following conditions are met:
25 | //
26 | // * Redistributions of source code must retain the above
27 | // copyright notice, this list of conditions and the
28 | // following disclaimer.
29 | //
30 | // * Redistributions in binary form must reproduce the above
31 | // copyright notice, this list of conditions and the
32 | // following disclaimer in the documentation and/or other
33 | // materials provided with the distribution.
34 | //
35 | // * Neither the name of Parakey Inc. nor the names of its
36 | // contributors may be used to endorse or promote products
37 | // derived from this software without specific prior
38 | // written permission of Parakey Inc.
39 | //
40 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
41 | // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
42 | // FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
43 | // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
44 | // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
45 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
46 | // IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
47 | // OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
48 |
49 | /**
50 | *
51 | * @cfg {Error} e The error to create a stacktrace from (optional)
52 | * @cfg {Boolean} guess If we should try to resolve the names of anonymous functions
53 | */
54 | function printStackTrace(options) {
55 | var ex = (options && options.e) ? options.e : null;
56 | var guess = options ? !!options.guess : true;
57 |
58 | var p = new printStackTrace.implementation();
59 | var result = p.run(ex);
60 | return (guess) ? p.guessFunctions(result) : result;
61 | }
62 |
63 | printStackTrace.implementation = function() {};
64 |
65 | printStackTrace.implementation.prototype = {
66 | run: function(ex) {
67 | // Use either the stored mode, or resolve it
68 | var mode = this._mode || this.mode();
69 | if (mode === 'other') {
70 | return this.other(arguments.callee);
71 | } else {
72 | ex = ex ||
73 | (function() {
74 | try {
75 | (0)();
76 | } catch (e) {
77 | return e;
78 | }
79 | })();
80 | return this[mode](ex);
81 | }
82 | },
83 |
84 | mode: function() {
85 | try {
86 | (0)();
87 | } catch (e) {
88 | if (e.arguments) {
89 | return (this._mode = 'chrome');
90 | } else if (e.stack) {
91 | return (this._mode = 'firefox');
92 | } else if (window.opera && !('stacktrace' in e)) { //Opera 9-
93 | return (this._mode = 'opera');
94 | }
95 | }
96 | return (this._mode = 'other');
97 | },
98 |
99 | chrome: function(e) {
100 | return e.stack.replace(/^.*?\n/, '').
101 | replace(/^.*?\n/, '').
102 | replace(/^.*?\n/, '').
103 | replace(/^[^\(]+?[\n$]/gm, '').
104 | replace(/^\s+at\s+/gm, '').
105 | replace(/^Object.\s*\(/gm, '{anonymous}()@').
106 | split('\n');
107 | },
108 |
109 | firefox: function(e) {
110 | return e.stack.replace(/^.*?\n/, '').
111 | replace(/(?:\n@:0)?\s+$/m, '').
112 | replace(/^\(/gm, '{anonymous}(').
113 | split('\n');
114 | },
115 |
116 | // Opera 7.x and 8.x only!
117 | opera: function(e) {
118 | var lines = e.message.split('\n'), ANON = '{anonymous}',
119 | lineRE = /Line\s+(\d+).*?script\s+(http\S+)(?:.*?in\s+function\s+(\S+))?/i, i, j, len;
120 |
121 | for (i = 4, j = 0, len = lines.length; i < len; i += 2) {
122 | if (lineRE.test(lines[i])) {
123 | lines[j++] = (RegExp.$3 ? RegExp.$3 + '()@' + RegExp.$2 + RegExp.$1 : ANON + '()@' + RegExp.$2 + ':' + RegExp.$1) +
124 | ' -- ' +
125 | lines[i + 1].replace(/^\s+/, '');
126 | }
127 | }
128 |
129 | lines.splice(j, lines.length - j);
130 | return lines;
131 | },
132 |
133 | // Safari, Opera 9+, IE, and others
134 | other: function(curr) {
135 | var ANON = '{anonymous}', fnRE = /function\s*([\w\-$]+)?\s*\(/i, stack = [], j = 0, fn, args;
136 |
137 | var maxStackSize = 10;
138 | while (curr && stack.length < maxStackSize) {
139 | fn = fnRE.test(curr.toString()) ? RegExp.$1 || ANON : ANON;
140 | args = Array.prototype.slice.call(curr['arguments']);
141 | stack[j++] = fn + '(' + printStackTrace.implementation.prototype.stringifyArguments(args) + ')';
142 |
143 | //Opera bug: if curr.caller does not exist, Opera returns curr (WTF)
144 | if (curr === curr.caller && window.opera) {
145 | //TODO: check for same arguments if possible
146 | break;
147 | }
148 | curr = curr.caller;
149 | }
150 | return stack;
151 | },
152 |
153 | /**
154 | * @return given arguments array as a String, subsituting type names for non-string types.
155 | */
156 | stringifyArguments: function(args) {
157 | for (var i = 0; i < args.length; ++i) {
158 | var argument = args[i];
159 | if (typeof argument == 'object') {
160 | args[i] = '#object';
161 | } else if (typeof argument == 'function') {
162 | args[i] = '#function';
163 | } else if (typeof argument == 'string') {
164 | args[i] = '"' + argument + '"';
165 | }
166 | }
167 | return args.join(',');
168 | },
169 |
170 | sourceCache: {},
171 |
172 | /**
173 | * @return the text from a given URL.
174 | */
175 | ajax: function(url) {
176 | var req = this.createXMLHTTPObject();
177 | if (!req) {
178 | return;
179 | }
180 | req.open('GET', url, false);
181 | req.setRequestHeader('User-Agent', 'XMLHTTP/1.0');
182 | req.send('');
183 | return req.responseText;
184 | },
185 |
186 | createXMLHTTPObject: function() {
187 | // Try XHR methods in order and store XHR factory
188 | var xmlhttp, XMLHttpFactories = [
189 | function() {
190 | return new XMLHttpRequest();
191 | }, function() {
192 | return new ActiveXObject('Msxml2.XMLHTTP');
193 | }, function() {
194 | return new ActiveXObject('Msxml3.XMLHTTP');
195 | }, function() {
196 | return new ActiveXObject('Microsoft.XMLHTTP');
197 | }
198 | ];
199 | for (var i = 0; i < XMLHttpFactories.length; i++) {
200 | try {
201 | xmlhttp = XMLHttpFactories[i]();
202 | // Use memoization to cache the factory
203 | this.createXMLHTTPObject = XMLHttpFactories[i];
204 | return xmlhttp;
205 | } catch (e) {}
206 | }
207 | },
208 |
209 | getSource: function(url) {
210 | if (!(url in this.sourceCache)) {
211 | this.sourceCache[url] = this.ajax(url).split('\n');
212 | }
213 | return this.sourceCache[url];
214 | },
215 |
216 | guessFunctions: function(stack) {
217 | for (var i = 0; i < stack.length; ++i) {
218 | var reStack = /{anonymous}\(.*\)@(\w+:\/\/([-\w\.]+)+(:\d+)?[^:]+):(\d+):?(\d+)?/;
219 | var frame = stack[i], m = reStack.exec(frame);
220 | if (m) {
221 | var file = m[1], lineno = m[4]; //m[7] is character position in Chrome
222 | if (file && lineno) {
223 | var functionName = this.guessFunctionName(file, lineno);
224 | stack[i] = frame.replace('{anonymous}', functionName);
225 | }
226 | }
227 | }
228 | return stack;
229 | },
230 |
231 | guessFunctionName: function(url, lineNo) {
232 | try {
233 | return this.guessFunctionNameFromLines(lineNo, this.getSource(url));
234 | } catch (e) {
235 | return 'getSource failed with url: ' + url + ', exception: ' + e.toString();
236 | }
237 | },
238 |
239 | guessFunctionNameFromLines: function(lineNo, source) {
240 | var reFunctionArgNames = /function ([^(]*)\(([^)]*)\)/;
241 | var reGuessFunction = /['"]?([0-9A-Za-z_]+)['"]?\s*[:=]\s*(function|eval|new Function)/;
242 | // Walk backwards from the first line in the function until we find the line which
243 | // matches the pattern above, which is the function definition
244 | var line = "", maxLines = 10;
245 | for (var i = 0; i < maxLines; ++i) {
246 | line = source[lineNo - i] + line;
247 | if (line !== undefined) {
248 | var m = reGuessFunction.exec(line);
249 | if (m && m[1]) {
250 | return m[1];
251 | } else {
252 | m = reFunctionArgNames.exec(line);
253 | if (m && m[1]) {
254 | return m[1];
255 | }
256 | }
257 | }
258 | }
259 | return '(?)';
260 | }
261 | };
262 |
263 | exports.trace = printStackTrace;
264 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/extern/node-router.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2010 Tim Caswell
3 |
4 | Permission is hereby granted, free of charge, to any person
5 | obtaining a copy of this software and associated documentation
6 | files (the "Software"), to deal in the Software without
7 | restriction, including without limitation the rights to use,
8 | copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the
10 | Software is furnished to do so, subject to the following
11 | conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23 | OTHER DEALINGS IN THE SOFTWARE.
24 | */
25 |
26 | var sys = require('sys');
27 | var fs = require('fs');
28 | var path = require('path');
29 | var http = require('http');
30 | var url_parse = require("url").parse;
31 |
32 | // Used as a simple, convient 404 handler.
33 | function notFound(req, res, message) {
34 | message = (message || "Not Found\n") + "";
35 | res.writeHead(404, {
36 | "Content-Type": "text/plain",
37 | "Content-Length": message.length
38 | });
39 | res.write(message);
40 | res.end();
41 | }
42 |
43 | function badRequest(req, res, message) {
44 | message = (message || "Bad Request\n") + "";
45 | res.writeHead(400, {
46 | "Content-Type": "text/plain",
47 | "Content-Length": message.length
48 | });
49 | res.write(message);
50 | res.end();
51 | }
52 |
53 | // Modifies req and res to call logger with a log line on each res.end
54 | // Think of it as "middleware"
55 | function logify(req, res, logger) {
56 | var end = res.end;
57 | res.end = function () {
58 | // Common Log Format (mostly)
59 | logger((req.socket && req.socket.remoteAddress) + " - - [" + (new Date()).toUTCString() + "]"
60 | + " \"" + req.method + " " + req.url
61 | + " HTTP/" + req.httpVersionMajor + "." + req.httpVersionMinor + "\" "
62 | + res.statusCode + " - \""
63 | + (req.headers['referer'] || "") + "\" \"" + (req.headers["user-agent"] ? req.headers["user-agent"].split(' ')[0] : '') + "\"");
64 | return end.apply(this, arguments);
65 | }
66 | var writeHead = res.writeHead;
67 | res.writeHead = function (code) {
68 | res.statusCode = code;
69 | return writeHead.apply(this, arguments);
70 | }
71 | }
72 |
73 | exports.getServer = function getServer(logger) {
74 |
75 | logger = logger || sys.puts;
76 |
77 | var routes = [];
78 |
79 | // Adds a route the the current server
80 | function addRoute(method, pattern, handler, format) {
81 | if (typeof pattern === 'string') {
82 | pattern = new RegExp("^" + pattern + "$");
83 | }
84 | var route = {
85 | method: method,
86 | pattern: pattern,
87 | handler: handler
88 | };
89 | if (format !== undefined) {
90 | route.format = format;
91 | }
92 | routes.push(route);
93 | }
94 |
95 | // The four verbs are wrappers around addRoute
96 | function get(pattern, handler) {
97 | return addRoute("GET", pattern, handler);
98 | }
99 | function post(pattern, handler, format) {
100 | return addRoute("POST", pattern, handler, format);
101 | }
102 | function put(pattern, handler, format) {
103 | return addRoute("PUT", pattern, handler, format);
104 | }
105 | function del(pattern, handler) {
106 | return addRoute("DELETE", pattern, handler);
107 | }
108 |
109 | // This is a meta pattern that expands to a common RESTful mapping
110 | function resource(name, controller, format) {
111 | get(new RegExp('^/' + name + '$'), controller.index);
112 | get(new RegExp('^/' + name + '/([^/]+)$'), controller.show);
113 | post(new RegExp('^/' + name + '$'), controller.create, format);
114 | put(new RegExp('^/' + name + '/([^/]+)$'), controller.update, format);
115 | del(new RegExp('^/' + name + '/([^/]+)$'), controller.destroy);
116 | };
117 |
118 | function resourceController(name, data, on_change) {
119 | data = data || [];
120 | on_change = on_change || function () {};
121 | return {
122 | index: function (req, res) {
123 | res.simpleJson(200, {content: data, self: '/' + name});
124 | },
125 | show: function (req, res, id) {
126 | var item = data[id];
127 | if (item) {
128 | res.simpleJson(200, {content: item, self: '/' + name + '/' + id});
129 | } else {
130 | res.notFound();
131 | }
132 | },
133 | create: function (req, res) {
134 | req.jsonBody(function (json) {
135 | var item, id, url;
136 | item = json && json.content;
137 | if (!item) {
138 | res.notFound();
139 | } else {
140 | data.push(item);
141 | id = data.length - 1;
142 | on_change(id);
143 | url = "/" + name + "/" + id;
144 | res.simpleJson(201, {content: item, self: url}, [["Location", url]]);
145 | }
146 | });
147 | },
148 | update: function (req, res, id) {
149 | req.jsonBody(function (json) {
150 | var item = json && json.content;
151 | if (!item) {
152 | res.notFound();
153 | } else {
154 | data[id] = item;
155 | on_change(id);
156 | res.simpleJson(200, {content: item, self: "/" + name + "/" + id});
157 | }
158 | });
159 | },
160 | destroy: function (req, res, id) {
161 | delete(data[id]);
162 | on_change(id);
163 | res.simpleJson(200, "200 Destroyed");
164 | }
165 | };
166 | };
167 |
168 | // Create the http server object
169 | var server = http.createServer(function (req, res) {
170 |
171 | // Enable logging on all requests using common-logger style
172 | logify(req, res, logger);
173 |
174 | var uri, path;
175 |
176 | // Performs an HTTP 302 redirect
177 | res.redirect = function redirect(location) {
178 | res.writeHead(302, {"Location": location});
179 | res.end();
180 | }
181 |
182 | // Performs an internal redirect
183 | res.innerRedirect = function innerRedirect(location) {
184 | logger("Internal Redirect: " + req.url + " -> " + location);
185 | req.url = location;
186 | doRoute();
187 | }
188 |
189 | function simpleResponse(code, body, content_type, extra_headers) {
190 | res.writeHead(code, (extra_headers || []).concat(
191 | [ ["Content-Type", content_type],
192 | ["Content-Length", body.length]
193 | ]));
194 | res.write(body);
195 | res.end();
196 | }
197 |
198 | res.simpleText = function (code, body, extra_headers) {
199 | simpleResponse(code, body, "text/plain", extra_headers);
200 | };
201 |
202 | res.simpleHtml = function (code, body, extra_headers) {
203 | simpleResponse(code, body, "text/html", extra_headers);
204 | };
205 |
206 | res.simpleJson = function (code, json, extra_headers) {
207 | simpleResponse(code, JSON.stringify(json), "application/json", extra_headers);
208 | };
209 |
210 | res.notFound = function (message) {
211 | notFound(req, res, message);
212 | };
213 |
214 | function doRoute() {
215 | uri = url_parse(req.url);
216 | path = uri.pathname;
217 |
218 | for (var i = 0, l = routes.length; i < l; i += 1) {
219 | var route = routes[i];
220 | if (req.method === route.method) {
221 | var match = path.match(route.pattern);
222 | if (match && match[0].length > 0) {
223 | match.shift();
224 | match = match.map(function (part) {
225 | return part ? unescape(part) : part;
226 | });
227 | match.unshift(res);
228 | match.unshift(req);
229 | if (route.format !== undefined) {
230 | var body = "";
231 | req.setEncoding('utf8');
232 | req.addListener('data', function (chunk) {
233 | body += chunk;
234 | });
235 | req.addListener('end', function () {
236 | if (route.format === 'json') {
237 | try {
238 | body = JSON.parse(unescape(body));
239 | }
240 | catch (err) {
241 | badRequest(req, res, "JSON.parse failed: "+err);
242 | return;
243 | }
244 | }
245 | match.push(body);
246 | route.handler.apply(null, match);
247 | });
248 | return;
249 | }
250 | var result = route.handler.apply(null, match);
251 | switch (typeof result) {
252 | case "string":
253 | res.simpleHtml(200, result);
254 | break;
255 | case "object":
256 | res.simpleJson(200, result);
257 | break;
258 | }
259 |
260 | return;
261 | }
262 | }
263 | }
264 |
265 | notFound(req, res);
266 | }
267 | doRoute();
268 |
269 | });
270 |
271 | function listen(port, host) {
272 | port = port || 8080;
273 | if (typeof port === 'number') {
274 | host = host || "127.0.0.1";
275 | }
276 | server.listen(port, host);
277 | if (typeof port === 'number') {
278 | logger("node-router server instance at http://" + host + ":" + port + "/");
279 | } else {
280 | logger("node-router server instance at unix:" + port);
281 | }
282 | }
283 |
284 | function close() {
285 | return server.close();
286 | }
287 |
288 | // Return a handle to the public facing functions from this closure as the
289 | // server object.
290 | return {
291 | get: get,
292 | post: post,
293 | put: put,
294 | del: del,
295 | resource: resource,
296 | resourceController: resourceController,
297 | listen: listen,
298 | close: close,
299 | };
300 | }
301 |
302 |
303 |
304 |
305 | exports.staticHandler = function (filename) {
306 | var body, headers;
307 | var content_type = mime.getMime(filename)
308 | var encoding = (content_type.slice(0,4) === "text" ? "utf8" : "binary");
309 |
310 | function loadResponseData(req, res, callback) {
311 | if (body && headers) {
312 | callback();
313 | return;
314 | }
315 |
316 | fs.readFile(filename, encoding, function (err, data) {
317 | if (err) {
318 | notFound(req, res, "Cannot find file: " + filename);
319 | return;
320 | }
321 | body = data;
322 | headers = [ [ "Content-Type" , content_type ],
323 | [ "Content-Length" , body.length ]
324 | ];
325 | headers.push(["Cache-Control", "public"]);
326 |
327 | callback();
328 | });
329 | }
330 |
331 | return function (req, res) {
332 | loadResponseData(req, res, function () {
333 | res.writeHead(200, headers);
334 | res.write(body, encoding);
335 | res.end();
336 | });
337 | };
338 | };
339 |
340 | exports.staticDirHandler = function(root, prefix) {
341 | function loadResponseData(req, res, filename, callback) {
342 | var content_type = mime.getMime(filename);
343 | var encoding = (content_type.slice(0,4) === "text" ? "utf8" : "binary");
344 |
345 | fs.readFile(filename, encoding, function(err, data) {
346 | if(err) {
347 | notFound(req, res, "Cannot find file: " + filename);
348 | return;
349 | }
350 | var headers = [ [ "Content-Type" , content_type ],
351 | [ "Content-Length" , data.length ],
352 | [ "Cache-Control" , "public" ]
353 | ];
354 | callback(headers, data, encoding);
355 | });
356 | }
357 |
358 | return function (req, res) {
359 | // trim off any query/anchor stuff
360 | var filename = req.url.replace(/[\?|#].*$/, '');
361 | if (prefix) filename = filename.replace(new RegExp('^'+prefix), '');
362 | // make sure nobody can explore our local filesystem
363 | filename = path.join(root, filename.replace(/\.\.+/g, '.'));
364 | if (filename == root) filename = path.join(root, 'index.html');
365 | loadResponseData(req, res, filename, function(headers, body, encoding) {
366 | res.writeHead(200, headers);
367 | res.write(body, encoding);
368 | res.end();
369 | });
370 | };
371 | };
372 |
373 |
374 | // Mini mime module for static file serving
375 | var DEFAULT_MIME = 'application/octet-stream';
376 | var mime = exports.mime = {
377 |
378 | getMime: function getMime(path) {
379 | var index = path.lastIndexOf(".");
380 | if (index < 0) {
381 | return DEFAULT_MIME;
382 | }
383 | return mime.TYPES[path.substring(index).toLowerCase()] || DEFAULT_MIME;
384 | },
385 |
386 | TYPES : { ".3gp" : "video/3gpp",
387 | ".a" : "application/octet-stream",
388 | ".ai" : "application/postscript",
389 | ".aif" : "audio/x-aiff",
390 | ".aiff" : "audio/x-aiff",
391 | ".asc" : "application/pgp-signature",
392 | ".asf" : "video/x-ms-asf",
393 | ".asm" : "text/x-asm",
394 | ".asx" : "video/x-ms-asf",
395 | ".atom" : "application/atom+xml",
396 | ".au" : "audio/basic",
397 | ".avi" : "video/x-msvideo",
398 | ".bat" : "application/x-msdownload",
399 | ".bin" : "application/octet-stream",
400 | ".bmp" : "image/bmp",
401 | ".bz2" : "application/x-bzip2",
402 | ".c" : "text/x-c",
403 | ".cab" : "application/vnd.ms-cab-compressed",
404 | ".cc" : "text/x-c",
405 | ".chm" : "application/vnd.ms-htmlhelp",
406 | ".class" : "application/octet-stream",
407 | ".com" : "application/x-msdownload",
408 | ".conf" : "text/plain",
409 | ".cpp" : "text/x-c",
410 | ".crt" : "application/x-x509-ca-cert",
411 | ".css" : "text/css",
412 | ".csv" : "text/csv",
413 | ".cxx" : "text/x-c",
414 | ".deb" : "application/x-debian-package",
415 | ".der" : "application/x-x509-ca-cert",
416 | ".diff" : "text/x-diff",
417 | ".djv" : "image/vnd.djvu",
418 | ".djvu" : "image/vnd.djvu",
419 | ".dll" : "application/x-msdownload",
420 | ".dmg" : "application/octet-stream",
421 | ".doc" : "application/msword",
422 | ".dot" : "application/msword",
423 | ".dtd" : "application/xml-dtd",
424 | ".dvi" : "application/x-dvi",
425 | ".ear" : "application/java-archive",
426 | ".eml" : "message/rfc822",
427 | ".eps" : "application/postscript",
428 | ".exe" : "application/x-msdownload",
429 | ".f" : "text/x-fortran",
430 | ".f77" : "text/x-fortran",
431 | ".f90" : "text/x-fortran",
432 | ".flv" : "video/x-flv",
433 | ".for" : "text/x-fortran",
434 | ".gem" : "application/octet-stream",
435 | ".gemspec" : "text/x-script.ruby",
436 | ".gif" : "image/gif",
437 | ".gz" : "application/x-gzip",
438 | ".h" : "text/x-c",
439 | ".hh" : "text/x-c",
440 | ".htm" : "text/html",
441 | ".html" : "text/html",
442 | ".ico" : "image/vnd.microsoft.icon",
443 | ".ics" : "text/calendar",
444 | ".ifb" : "text/calendar",
445 | ".iso" : "application/octet-stream",
446 | ".jar" : "application/java-archive",
447 | ".java" : "text/x-java-source",
448 | ".jnlp" : "application/x-java-jnlp-file",
449 | ".jpeg" : "image/jpeg",
450 | ".jpg" : "image/jpeg",
451 | ".js" : "application/javascript",
452 | ".json" : "application/json",
453 | ".log" : "text/plain",
454 | ".m3u" : "audio/x-mpegurl",
455 | ".m4v" : "video/mp4",
456 | ".man" : "text/troff",
457 | ".mathml" : "application/mathml+xml",
458 | ".mbox" : "application/mbox",
459 | ".mdoc" : "text/troff",
460 | ".me" : "text/troff",
461 | ".mid" : "audio/midi",
462 | ".midi" : "audio/midi",
463 | ".mime" : "message/rfc822",
464 | ".mml" : "application/mathml+xml",
465 | ".mng" : "video/x-mng",
466 | ".mov" : "video/quicktime",
467 | ".mp3" : "audio/mpeg",
468 | ".mp4" : "video/mp4",
469 | ".mp4v" : "video/mp4",
470 | ".mpeg" : "video/mpeg",
471 | ".mpg" : "video/mpeg",
472 | ".ms" : "text/troff",
473 | ".msi" : "application/x-msdownload",
474 | ".odp" : "application/vnd.oasis.opendocument.presentation",
475 | ".ods" : "application/vnd.oasis.opendocument.spreadsheet",
476 | ".odt" : "application/vnd.oasis.opendocument.text",
477 | ".ogg" : "application/ogg",
478 | ".p" : "text/x-pascal",
479 | ".pas" : "text/x-pascal",
480 | ".pbm" : "image/x-portable-bitmap",
481 | ".pdf" : "application/pdf",
482 | ".pem" : "application/x-x509-ca-cert",
483 | ".pgm" : "image/x-portable-graymap",
484 | ".pgp" : "application/pgp-encrypted",
485 | ".pkg" : "application/octet-stream",
486 | ".pl" : "text/x-script.perl",
487 | ".pm" : "text/x-script.perl-module",
488 | ".png" : "image/png",
489 | ".pnm" : "image/x-portable-anymap",
490 | ".ppm" : "image/x-portable-pixmap",
491 | ".pps" : "application/vnd.ms-powerpoint",
492 | ".ppt" : "application/vnd.ms-powerpoint",
493 | ".ps" : "application/postscript",
494 | ".psd" : "image/vnd.adobe.photoshop",
495 | ".py" : "text/x-script.python",
496 | ".qt" : "video/quicktime",
497 | ".ra" : "audio/x-pn-realaudio",
498 | ".rake" : "text/x-script.ruby",
499 | ".ram" : "audio/x-pn-realaudio",
500 | ".rar" : "application/x-rar-compressed",
501 | ".rb" : "text/x-script.ruby",
502 | ".rdf" : "application/rdf+xml",
503 | ".roff" : "text/troff",
504 | ".rpm" : "application/x-redhat-package-manager",
505 | ".rss" : "application/rss+xml",
506 | ".rtf" : "application/rtf",
507 | ".ru" : "text/x-script.ruby",
508 | ".s" : "text/x-asm",
509 | ".sgm" : "text/sgml",
510 | ".sgml" : "text/sgml",
511 | ".sh" : "application/x-sh",
512 | ".sig" : "application/pgp-signature",
513 | ".snd" : "audio/basic",
514 | ".so" : "application/octet-stream",
515 | ".svg" : "image/svg+xml",
516 | ".svgz" : "image/svg+xml",
517 | ".swf" : "application/x-shockwave-flash",
518 | ".t" : "text/troff",
519 | ".tar" : "application/x-tar",
520 | ".tbz" : "application/x-bzip-compressed-tar",
521 | ".tci" : "application/x-topcloud",
522 | ".tcl" : "application/x-tcl",
523 | ".tex" : "application/x-tex",
524 | ".texi" : "application/x-texinfo",
525 | ".texinfo" : "application/x-texinfo",
526 | ".text" : "text/plain",
527 | ".tif" : "image/tiff",
528 | ".tiff" : "image/tiff",
529 | ".torrent" : "application/x-bittorrent",
530 | ".tr" : "text/troff",
531 | ".ttf" : "application/x-font-ttf",
532 | ".txt" : "text/plain",
533 | ".vcf" : "text/x-vcard",
534 | ".vcs" : "text/x-vcalendar",
535 | ".vrml" : "model/vrml",
536 | ".war" : "application/java-archive",
537 | ".wav" : "audio/x-wav",
538 | ".wma" : "audio/x-ms-wma",
539 | ".wmv" : "video/x-ms-wmv",
540 | ".wmx" : "video/x-ms-wmx",
541 | ".wrl" : "model/vrml",
542 | ".wsdl" : "application/wsdl+xml",
543 | ".xbm" : "image/x-xbitmap",
544 | ".xhtml" : "application/xhtml+xml",
545 | ".xls" : "application/vnd.ms-excel",
546 | ".xml" : "application/xml",
547 | ".xpm" : "image/x-xpixmap",
548 | ".xsl" : "application/xml",
549 | ".xslt" : "application/xslt+xml",
550 | ".yaml" : "text/yaml",
551 | ".yml" : "text/yaml",
552 | ".zip" : "application/zip"
553 | }
554 | };
555 |
--------------------------------------------------------------------------------
/extern/json-template.js:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2009 Andy Chu
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | //
16 | // JavaScript implementation of json-template.
17 | //
18 |
19 | // This is predefined in tests, shouldn't be defined anywhere else. TODO: Do
20 | // something nicer.
21 | var log = log || function() {};
22 | var repr = repr || function() {};
23 |
24 |
25 | // The "module" exported by this script is called "jsontemplate":
26 |
27 | var jsontemplate = function() {
28 |
29 |
30 | // Regex escaping for metacharacters
31 | function EscapeMeta(meta) {
32 | return meta.replace(/([\{\}\(\)\[\]\|\^\$\-\+\?])/g, '\\$1');
33 | }
34 |
35 | var token_re_cache = {};
36 |
37 | function _MakeTokenRegex(meta_left, meta_right) {
38 | var key = meta_left + meta_right;
39 | var regex = token_re_cache[key];
40 | if (regex === undefined) {
41 | var str = '(' + EscapeMeta(meta_left) + '.*?' + EscapeMeta(meta_right) +
42 | '\n?)';
43 | regex = new RegExp(str, 'g');
44 | }
45 | return regex;
46 | }
47 |
48 | //
49 | // Formatters
50 | //
51 |
52 | function HtmlEscape(s) {
53 | return s.replace(/&/g,'&').
54 | replace(/>/g,'>').
55 | replace(//g,'>').
61 | replace(/ 1) ? p : s;
93 | }
94 |
95 | function _Cycle(value, unused_context, args) {
96 | // Cycle between various values on consecutive integers.
97 | // @index starts from 1, so use 1-based indexing.
98 | return args[(value - 1) % args.length];
99 | }
100 |
101 | var DEFAULT_FORMATTERS = {
102 | 'html': HtmlEscape,
103 | 'htmltag': HtmlTagEscape,
104 | 'html-attr-value': HtmlTagEscape,
105 | 'str': ToString,
106 | 'raw': function(x) { return x; },
107 | 'AbsUrl': function(value, context) {
108 | // TODO: Normalize leading/trailing slashes
109 | return context.get('base-url') + '/' + value;
110 | },
111 | 'plain-url': function(x) {
112 | return '' + HtmlEscape(x) + '' ;
113 | }
114 | };
115 |
116 | var DEFAULT_PREDICATES = {
117 | 'singular?': function(x) { return x == 1; },
118 | 'plural?': function(x) { return x > 1; },
119 | 'Debug?': function(unused, context) {
120 | try {
121 | return context.get('debug');
122 | } catch(err) {
123 | if (err.name == 'UndefinedVariable') {
124 | return false;
125 | } else {
126 | throw err;
127 | }
128 | }
129 | }
130 | };
131 |
132 | var FunctionRegistry = function() {
133 | return {
134 | lookup: function(user_str) {
135 | return [null, null];
136 | }
137 | };
138 | };
139 |
140 | var SimpleRegistry = function(obj) {
141 | return {
142 | lookup: function(user_str) {
143 | var func = obj[user_str] || null;
144 | return [func, null];
145 | }
146 | };
147 | };
148 |
149 | var CallableRegistry = function(callable) {
150 | return {
151 | lookup: function(user_str) {
152 | var func = callable(user_str);
153 | return [func, null];
154 | }
155 | };
156 | };
157 |
158 | // Default formatters which can't be expressed in DEFAULT_FORMATTERS
159 | var PrefixRegistry = function(functions) {
160 | return {
161 | lookup: function(user_str) {
162 | for (var i = 0; i < functions.length; i++) {
163 | var name = functions[i].name, func = functions[i].func;
164 | if (user_str.slice(0, name.length) == name) {
165 | // Delimiter is usually a space, but could be something else
166 | var args;
167 | var splitchar = user_str.charAt(name.length);
168 | if (splitchar === '') {
169 | args = []; // No arguments
170 | } else {
171 | args = user_str.split(splitchar).slice(1);
172 | }
173 | return [func, args];
174 | }
175 | }
176 | return [null, null]; // No formatter
177 | }
178 | };
179 | };
180 |
181 | var ChainedRegistry = function(registries) {
182 | return {
183 | lookup: function(user_str) {
184 | for (var i=0; i 0) {
460 | // TODO: check that items is an array; apparently this is hard in JavaScript
461 | //if type(items) is not list:
462 | // raise EvaluationError('Expected a list; got %s' % type(items))
463 |
464 | // Execute the statements in the block for every item in the list.
465 | // Execute the alternate block on every iteration except the last. Each
466 | // item could be an atom (string, integer, etc.) or a dictionary.
467 |
468 | var last_index = items.length - 1;
469 | var statements = block.Statements();
470 | var alt_statements = block.Statements('alternate');
471 |
472 | for (var i=0; context.next() !== undefined; i++) {
473 | _Execute(statements, context, callback);
474 | if (i != last_index) {
475 | _Execute(alt_statements, context, callback);
476 | }
477 | }
478 | } else {
479 | _Execute(block.Statements('or'), context, callback);
480 | }
481 |
482 | context.pop();
483 | }
484 |
485 |
486 | var _SECTION_RE = /(repeated)?\s*(section)\s+(\S+)?/;
487 | var _OR_RE = /or(?:\s+(.+))?/;
488 | var _IF_RE = /if(?:\s+(.+))?/;
489 |
490 |
491 | // Turn a object literal, function, or Registry into a Registry
492 | function MakeRegistry(obj) {
493 | if (!obj) {
494 | // if null/undefined, use a totally empty FunctionRegistry
495 | return new FunctionRegistry();
496 | } else if (typeof obj === 'function') {
497 | return new CallableRegistry(obj);
498 | } else if (obj.lookup !== undefined) {
499 | // TODO: Is this a good pattern? There is a namespace conflict where get
500 | // could be either a formatter or a method on a FunctionRegistry.
501 | // instanceof might be more robust.
502 | return obj;
503 | } else if (typeof obj === 'object') {
504 | return new SimpleRegistry(obj);
505 | }
506 | }
507 |
508 | // TODO: The compile function could be in a different module, in case we want to
509 | // compile on the server side.
510 | function _Compile(template_str, options) {
511 | var more_formatters = MakeRegistry(options.more_formatters);
512 |
513 | // default formatters with arguments
514 | var default_formatters = PrefixRegistry([
515 | {name: 'pluralize', func: _Pluralize},
516 | {name: 'cycle', func: _Cycle}
517 | ]);
518 | var all_formatters = new ChainedRegistry([
519 | more_formatters,
520 | SimpleRegistry(DEFAULT_FORMATTERS),
521 | default_formatters
522 | ]);
523 |
524 | var more_predicates = MakeRegistry(options.more_predicates);
525 |
526 | // TODO: Add defaults
527 | var all_predicates = new ChainedRegistry([
528 | more_predicates, SimpleRegistry(DEFAULT_PREDICATES)
529 | ]);
530 |
531 | // We want to allow an explicit null value for default_formatter, which means
532 | // that an error is raised if no formatter is specified.
533 | var default_formatter;
534 | if (options.default_formatter === undefined) {
535 | default_formatter = 'str';
536 | } else {
537 | default_formatter = options.default_formatter;
538 | }
539 |
540 | function GetFormatter(format_str) {
541 | var pair = all_formatters.lookup(format_str);
542 | if (!pair[0]) {
543 | throw {
544 | name: 'BadFormatter',
545 | message: format_str + ' is not a valid formatter'
546 | };
547 | }
548 | return pair;
549 | }
550 |
551 | function GetPredicate(pred_str) {
552 | var pair = all_predicates.lookup(pred_str);
553 | if (!pair[0]) {
554 | throw {
555 | name: 'BadPredicate',
556 | message: pred_str + ' is not a valid predicate'
557 | };
558 | }
559 | return pair;
560 | }
561 |
562 | var format_char = options.format_char || '|';
563 | if (format_char != ':' && format_char != '|') {
564 | throw {
565 | name: 'ConfigurationError',
566 | message: 'Only format characters : and | are accepted'
567 | };
568 | }
569 |
570 | var meta = options.meta || '{}';
571 | var n = meta.length;
572 | if (n % 2 == 1) {
573 | throw {
574 | name: 'ConfigurationError',
575 | message: meta + ' has an odd number of metacharacters'
576 | };
577 | }
578 | var meta_left = meta.substring(0, n/2);
579 | var meta_right = meta.substring(n/2, n);
580 |
581 | var token_re = _MakeTokenRegex(meta_left, meta_right);
582 | var current_block = _Section({});
583 | var stack = [current_block];
584 |
585 | var strip_num = meta_left.length; // assume they're the same length
586 |
587 | var token_match;
588 | var last_index = 0;
589 |
590 | while (true) {
591 | token_match = token_re.exec(template_str);
592 | if (token_match === null) {
593 | break;
594 | } else {
595 | var token = token_match[0];
596 | }
597 |
598 | // Add the previous literal to the program
599 | if (token_match.index > last_index) {
600 | var tok = template_str.slice(last_index, token_match.index);
601 | current_block.Append(tok);
602 | }
603 | last_index = token_re.lastIndex;
604 |
605 | var had_newline = false;
606 | if (token.slice(-1) == '\n') {
607 | token = token.slice(null, -1);
608 | had_newline = true;
609 | }
610 |
611 | token = token.slice(strip_num, -strip_num);
612 |
613 | if (token.charAt(0) == '#') {
614 | continue; // comment
615 | }
616 |
617 | if (token.charAt(0) == '.') { // Keyword
618 | token = token.substring(1, token.length);
619 |
620 | var literal = {
621 | 'meta-left': meta_left,
622 | 'meta-right': meta_right,
623 | 'space': ' ',
624 | 'tab': '\t',
625 | 'newline': '\n'
626 | }[token];
627 |
628 | if (literal !== undefined) {
629 | current_block.Append(literal);
630 | continue;
631 | }
632 |
633 | var new_block, func;
634 |
635 | var section_match = token.match(_SECTION_RE);
636 | if (section_match) {
637 | var repeated = section_match[1];
638 | var section_name = section_match[3];
639 |
640 | if (repeated) {
641 | func = _DoRepeatedSection;
642 | new_block = _RepeatedSection({section_name: section_name});
643 | } else {
644 | func = _DoSection;
645 | new_block = _Section({section_name: section_name});
646 | }
647 | current_block.Append([func, new_block]);
648 | stack.push(new_block);
649 | current_block = new_block;
650 | continue;
651 | }
652 |
653 | var pred_str, pred;
654 |
655 | // Check {.or pred?} before {.pred?}
656 | var or_match = token.match(_OR_RE);
657 | if (or_match) {
658 | pred_str = or_match[1];
659 | pred = pred_str ? GetPredicate(pred_str) : null;
660 | current_block.NewOrClause(pred);
661 | continue;
662 | }
663 |
664 | // Match either {.pred?} or {.if pred?}
665 | var matched = false;
666 |
667 | var if_match = token.match(_IF_RE);
668 | if (if_match) {
669 | pred_str = if_match[1];
670 | matched = true;
671 | } else if (token.charAt(token.length-1) == '?') {
672 | pred_str = token;
673 | matched = true;
674 | }
675 | if (matched) {
676 | pred = pred_str ? GetPredicate(pred_str) : null;
677 | new_block = _PredicateSection();
678 | new_block.NewOrClause(pred);
679 | current_block.Append([_DoPredicates, new_block]);
680 | stack.push(new_block);
681 | current_block = new_block;
682 | continue;
683 | }
684 |
685 | if (token == 'alternates with') {
686 | current_block.AlternatesWith();
687 | continue;
688 | }
689 |
690 | if (token == 'end') {
691 | // End the block
692 | stack.pop();
693 | if (stack.length > 0) {
694 | current_block = stack[stack.length-1];
695 | } else {
696 | throw {
697 | name: 'TemplateSyntaxError',
698 | message: 'Got too many {end} statements'
699 | };
700 | }
701 | continue;
702 | }
703 | }
704 |
705 | // A variable substitution
706 | var parts = token.split(format_char);
707 | var formatters;
708 | var name;
709 | if (parts.length == 1) {
710 | if (default_formatter === null) {
711 | throw {
712 | name: 'MissingFormatter',
713 | message: 'This template requires explicit formatters.'
714 | };
715 | }
716 | // If no formatter is specified, use the default.
717 | formatters = [GetFormatter(default_formatter)];
718 | name = token;
719 | } else {
720 | formatters = [];
721 | for (var j=1; j