| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644 | 
							- /*
 
- GET /note
 
- GET /note/:id
 
- POST /note
 
- PUT /note/:id
 
- DELETE /note/:id
 
- */
 
- HTTP = Package.http && Package.http.HTTP || {};
 
- // Primary local test scope
 
- _methodHTTP = {};
 
- _methodHTTP.methodHandlers = {};
 
- _methodHTTP.methodTree = {};
 
- // This could be changed eg. could allow larger data chunks than 1.000.000
 
- // 5mb = 5 * 1024 * 1024 = 5242880;
 
- HTTP.methodsMaxDataLength = 5242880; //1e6;
 
- _methodHTTP.nameFollowsConventions = function(name) {
 
-   // Check that name is string, not a falsy or empty
 
-   return name && name === '' + name && name !== '';
 
- };
 
- _methodHTTP.getNameList = function(name) {
 
-   // Remove leading and trailing slashes and make command array
 
-   name = name && name.replace(/^\//g, '') || ''; // /^\/|\/$/g
 
-   // TODO: Get the format from the url - eg.: "/list/45.json" format should be
 
-   // set in this function by splitting the last list item by . and have format
 
-   // as the last item. How should we toggle:
 
-   // "/list/45/item.name.json" and "/list/45/item.name"?
 
-   // We would either have to check all known formats or allways determin the "."
 
-   // as an extension. Resolving in "json" and "name" as handed format - the user
 
-   // Could simply just add the format as a parametre? or be explicit about
 
-   // naming
 
-   return name && name.split('/') || [];
 
- };
 
- // Merge two arrays one containing keys and one values
 
- _methodHTTP.createObject = function(keys, values) {
 
-   var result = {};
 
-   if (keys && values) {
 
-     for (var i = 0; i < keys.length; i++) {
 
-       result[keys[i]] = values[i] && decodeURIComponent(values[i]) || '';
 
-     }
 
-   }
 
-   return result;
 
- };
 
- _methodHTTP.addToMethodTree = function(methodName) {
 
-   var list = _methodHTTP.getNameList(methodName);
 
-   var name = '/';
 
-   // Contains the list of params names
 
-   var params = [];
 
-   var currentMethodTree = _methodHTTP.methodTree;
 
-   for (var i = 0; i < list.length; i++) {
 
-     // get the key name
 
-     var key = list[i];
 
-     // Check if it expects a value
 
-     if (key[0] === ':') {
 
-       // This is a value
 
-       params.push(key.slice(1));
 
-       key = ':value';
 
-     }
 
-     name += key + '/';
 
-     // Set the key into the method tree
 
-     if (typeof currentMethodTree[key] === 'undefined') {
 
-       currentMethodTree[key] = {};
 
-     }
 
-     // Dig deeper
 
-     currentMethodTree = currentMethodTree[key];
 
-   }
 
-   if (_.isEmpty(currentMethodTree[':ref'])) {
 
-     currentMethodTree[':ref'] = {
 
-       name: name,
 
-       params: params
 
-     };
 
-   }
 
-   return currentMethodTree[':ref'];
 
- };
 
- // This method should be optimized for speed since its called on allmost every
 
- // http call to the server so we return null as soon as we know its not a method
 
- _methodHTTP.getMethod = function(name) {
 
-   // Check if the
 
-   if (!_methodHTTP.nameFollowsConventions(name)) {
 
-     return null;
 
-   }
 
-   var list = _methodHTTP.getNameList(name);
 
-   // Check if we got a correct list
 
-   if (!list || !list.length) {
 
-     return null;
 
-   }
 
-   // Set current refernce in the _methodHTTP.methodTree
 
-   var currentMethodTree = _methodHTTP.methodTree;
 
-   // Buffer for values to hand on later
 
-   var values = [];
 
-   // Iterate over the method name and check if its found in the method tree
 
-   for (var i = 0; i < list.length; i++) {
 
-     // get the key name
 
-     var key = list[i];
 
-     // We expect to find the key or :value if not we break
 
-     if (typeof currentMethodTree[key] !== 'undefined' ||
 
-             typeof currentMethodTree[':value'] !== 'undefined') {
 
-       // We got a result now check if its a value
 
-       if (typeof currentMethodTree[key] === 'undefined') {
 
-         // Push the value
 
-         values.push(key);
 
-         // Set the key to :value to dig deeper
 
-         key = ':value';
 
-       }
 
-     } else {
 
-       // Break - method call not found
 
-       return null;
 
-     }
 
-     // Dig deeper
 
-     currentMethodTree = currentMethodTree[key];
 
-   }
 
-   // Extract reference pointer
 
-   var reference = currentMethodTree && currentMethodTree[':ref'];
 
-   if (typeof reference !== 'undefined') {
 
-     return {
 
-       name: reference.name,
 
-       params: _methodHTTP.createObject(reference.params, values),
 
-       handle: _methodHTTP.methodHandlers[reference.name]
 
-     };
 
-   } else {
 
-     // Did not get any reference to the method
 
-     return null;
 
-   }
 
- };
 
- // This method retrieves the userId from the token and makes sure that the token
 
- // is valid and not expired
 
- _methodHTTP.getUserId = function() {
 
-   var self = this;
 
-   // // Get ip, x-forwarded-for can be comma seperated ips where the first is the
 
-   // // client ip
 
-   // var ip = self.req.headers['x-forwarded-for'] &&
 
-   //         // Return the first item in ip list
 
-   //         self.req.headers['x-forwarded-for'].split(',')[0] ||
 
-   //         // or return the remoteAddress
 
-   //         self.req.connection.remoteAddress;
 
-   // Check authentication
 
-   var userToken = self.query.token;
 
-   // Check if we are handed strings
 
-   try {
 
-     userToken && check(userToken, String);
 
-   } catch(err) {
 
-     throw new Meteor.Error(404, 'Error user token and id not of type strings, Error: ' + (err.stack || err.message));
 
-   }
 
-   // Set the this.userId
 
-   if (userToken) {
 
-     // Look up user to check if user exists and is loggedin via token
 
-     var user = Meteor.users.findOne({
 
-         $or: [
 
-           {'services.resume.loginTokens.hashedToken': Accounts._hashLoginToken(userToken)},
 
-           {'services.resume.loginTokens.token': userToken}
 
-         ]
 
-       });
 
-     // TODO: check 'services.resume.loginTokens.when' to have the token expire
 
-     // Set the userId in the scope
 
-     return user && user._id;
 
-   }
 
-   return null;
 
- };
 
- // Expose the default auth for calling from custom authentication handlers.
 
- HTTP.defaultAuth = _methodHTTP.getUserId;
 
- /*
 
- Add default support for options
 
- */
 
- _methodHTTP.defaultOptionsHandler = function(methodObject) {
 
-   // List of supported methods
 
-   var allowMethods = [];
 
-   // The final result object
 
-   var result = {};
 
-   // Iterate over the methods
 
-   // XXX: We should have a way to extend this - We should have some schema model
 
-   // for our methods...
 
-   _.each(methodObject, function(f, methodName) {
 
-     // Skip the stream and auth functions - they are not public / accessible
 
-     if (methodName !== 'stream' && methodName !== 'auth') {
 
-       // Create an empty description
 
-       result[methodName] = { description: '', parameters: {} };
 
-       // Add method name to headers
 
-       allowMethods.push(methodName);
 
-     }
 
-   });
 
-   // Lets play nice
 
-   this.setStatusCode(200);
 
-   // We have to set some allow headers here
 
-   this.addHeader('Allow', allowMethods.join(','));
 
-   // Return json result - Pretty print
 
-   return JSON.stringify(result, null, '\t');
 
- };
 
- // Public interface for adding server-side http methods - if setting a method to
 
- // 'false' it would actually remove the method (can be used to unpublish a method)
 
- HTTP.methods = function(newMethods) {
 
-   _.each(newMethods, function(func, name) {
 
-     if (_methodHTTP.nameFollowsConventions(name)) {
 
-       // Check if we got a function
 
-       //if (typeof func === 'function') {
 
-         var method = _methodHTTP.addToMethodTree(name);
 
-         // The func is good
 
-         if (typeof _methodHTTP.methodHandlers[method.name] !== 'undefined') {
 
-           if (func === false) {
 
-             // If the method is set to false then unpublish
 
-             delete _methodHTTP.methodHandlers[method.name];
 
-             // Delete the reference in the _methodHTTP.methodTree
 
-             delete method.name;
 
-             delete method.params;
 
-           } else {
 
-             // We should not allow overwriting - following Meteor.methods
 
-             throw new Error('HTTP method "' + name + '" is already registered');
 
-           }
 
-         } else {
 
-           // We could have a function or a object
 
-           // The object could have:
 
-           // '/test/': {
 
-           //   auth: function() ... returning the userId using over default
 
-           //
 
-           //   method: function() ...
 
-           //   or
 
-           //   post: function() ...
 
-           //   put:
 
-           //   get:
 
-           //   delete:
 
-           //   head:
 
-           // }
 
-           /*
 
-           We conform to the object format:
 
-           {
 
-             auth:
 
-             post:
 
-             put:
 
-             get:
 
-             delete:
 
-             head:
 
-           }
 
-           This way we have a uniform reference
 
-           */
 
-           var uniObj = {};
 
-           if (typeof func === 'function') {
 
-             uniObj = {
 
-               'auth': _methodHTTP.getUserId,
 
-               'stream': false,
 
-               'POST': func,
 
-               'PUT': func,
 
-               'GET': func,
 
-               'DELETE': func,
 
-               'HEAD': func,
 
-               'OPTIONS': _methodHTTP.defaultOptionsHandler
 
-             };
 
-           } else {
 
-             uniObj = {
 
-               'stream': func.stream || false,
 
-               'auth': func.auth || _methodHTTP.getUserId,
 
-               'POST': func.post || func.method,
 
-               'PUT': func.put || func.method,
 
-               'GET': func.get || func.method,
 
-               'DELETE': func.delete || func.method,
 
-               'HEAD': func.head || func.get || func.method,
 
-               'OPTIONS': func.options || _methodHTTP.defaultOptionsHandler
 
-             };
 
-           }
 
-           // Registre the method
 
-           _methodHTTP.methodHandlers[method.name] = uniObj; // func;
 
-         }
 
-       // } else {
 
-       //   // We do require a function as a function to execute later
 
-       //   throw new Error('HTTP.methods failed: ' + name + ' is not a function');
 
-       // }
 
-     } else {
 
-       // We have to follow the naming spec defined in nameFollowsConventions
 
-       throw new Error('HTTP.method "' + name + '" invalid naming of method');
 
-     }
 
-   });
 
- };
 
- var sendError = function(res, code, message) {
 
-   if (code) {
 
-     res.writeHead(code);
 
-   } else {
 
-     res.writeHead(500);
 
-   }
 
-   res.end(message);
 
- };
 
- // This handler collects the header data into either an object (if json) or the
 
- // raw data. The data is passed to the callback
 
- var requestHandler = function(req, res, callback) {
 
-   if (typeof callback !== 'function') {
 
-     return null;
 
-   }
 
-   // Container for buffers and a sum of the length
 
-   var bufferData = [], dataLen = 0;
 
-   // Extract the body
 
-   req.on('data', function(data) {
 
-     bufferData.push(data);
 
-     dataLen += data.length;
 
-     // We have to check the data length in order to spare the server
 
-     if (dataLen > HTTP.methodsMaxDataLength) {
 
-       dataLen = 0;
 
-       bufferData = [];
 
-       // Flood attack or faulty client
 
-       sendError(res, 413, 'Flood attack or faulty client');
 
-       req.connection.destroy();
 
-     }
 
-   });
 
-   // When message is ready to be passed on
 
-   req.on('end', function() {
 
-     if (res.finished) {
 
-       return;
 
-     }
 
-     // Allow the result to be undefined if so
 
-     var result;
 
-     // If data found the work it - either buffer or json
 
-     if (dataLen > 0) {
 
-       result = new Buffer(dataLen);
 
-       // Merge the chunks into one buffer
 
-       for (var i = 0, ln = bufferData.length, pos = 0; i < ln; i++) {
 
-         bufferData[i].copy(result, pos);
 
-         pos += bufferData[i].length;
 
-         delete bufferData[i];
 
-       }
 
-       // Check if we could be dealing with json
 
-       if (result[0] == 0x7b && result[1] === 0x22) {
 
-         try {
 
-           // Convert the body into json and extract the data object
 
-           result = EJSON.parse(result.toString());
 
-         } catch(err) {
 
-           // Could not parse so we return the raw data
 
-         }
 
-       }
 
-     } // Else result will be undefined
 
-     try {
 
-       callback(result);
 
-     } catch(err) {
 
-       sendError(res, 500, 'Error in requestHandler callback, Error: ' + (err.stack || err.message) );
 
-     }
 
-   });
 
- };
 
- // This is the simplest handler - it simply passes req stream as data to the
 
- // method
 
- var streamHandler = function(req, res, callback) {
 
-   try {
 
-     callback();
 
-   } catch(err) {
 
-     sendError(res, 500, 'Error in requestHandler callback, Error: ' + (err.stack || err.message) );
 
-   }
 
- };
 
- /*
 
-   Allow file uploads in cordova cfs
 
- */
 
- var setCordovaHeaders = function(request, response) {
 
-   var origin = request.headers.origin;
 
-   // Match http://localhost:<port> for Cordova clients in Meteor 1.3
 
-   // and http://meteor.local for earlier versions
 
-   if (origin && (origin === 'http://meteor.local' || /^http:\/\/localhost/.test(origin))) {
 
-     // We need to echo the origin provided in the request
 
-     response.setHeader("Access-Control-Allow-Origin", origin);
 
-     response.setHeader("Access-Control-Allow-Methods", "PUT");
 
-     response.setHeader("Access-Control-Allow-Headers", "Content-Type");
 
-   }
 
- };
 
- // Handle the actual connection
 
- WebApp.connectHandlers.use(function(req, res, next) {
 
-   // Check to se if this is a http method call
 
-   var method = _methodHTTP.getMethod(req._parsedUrl.pathname);
 
-   // If method is null then it wasn't and we pass the request along
 
-   if (method === null) {
 
-     return next();
 
-   }
 
-   var dataHandle = (method.handle && method.handle.stream)?streamHandler:requestHandler;
 
-   dataHandle(req, res, function(data) {
 
-     // If methodsHandler not found or somehow the methodshandler is not a
 
-     // function then return a 404
 
-     if (typeof method.handle === 'undefined') {
 
-       sendError(res, 404, 'Error HTTP method handler "' + method.name + '" is not found');
 
-       return;
 
-     }
 
-     // Set CORS headers for Meteor Cordova clients
 
-     setCordovaHeaders(req, res);
 
-     // Set fiber scope
 
-     var fiberScope = {
 
-       // Pointers to Request / Response
 
-       req: req,
 
-       res: res,
 
-       // Request / Response helpers
 
-       statusCode: 200,
 
-       method: req.method,
 
-       // Headers for response
 
-       headers: {
 
-         'Content-Type': 'text/html'  // Set default type
 
-       },
 
-       // Arguments
 
-       data: data,
 
-       query: req.query,
 
-       params: method.params,
 
-       // Method reference
 
-       reference: method.name,
 
-       methodObject: method.handle,
 
-       _streamsWaiting: 0
 
-     };
 
-     // Helper functions this scope
 
-     Fiber = Npm.require('fibers');
 
-     runServerMethod = Fiber(function(self) {
 
-       var result, resultBuffer;
 
-       // We fetch methods data from methodsHandler, the handler uses the this.addItem()
 
-       // function to populate the methods, this way we have better check control and
 
-       // better error handling + messages
 
-       // The scope for the user methodObject callbacks
 
-       var thisScope = {
 
-         // The user whos id and token was used to run this method, if set/found
 
-         userId: null,
 
-         // The id of the data
 
-         _id: null,
 
-         // Set the query params ?token=1&id=2 -> { token: 1, id: 2 }
 
-         query: self.query,
 
-         // Set params /foo/:name/test/:id -> { name: '', id: '' }
 
-         params: self.params,
 
-         // Method GET, PUT, POST, DELETE, HEAD
 
-         method: self.method,
 
-         // User agent
 
-         userAgent: req.headers['user-agent'],
 
-         // All request headers
 
-         requestHeaders: req.headers,
 
-         // Add the request object it self
 
-         request: req,
 
-         // Set the userId
 
-         setUserId: function(id) {
 
-           this.userId = id;
 
-         },
 
-         // We dont simulate / run this on the client at the moment
 
-         isSimulation: false,
 
-         // Run the next method in a new fiber - This is default at the moment
 
-         unblock: function() {},
 
-         // Set the content type in header, defaults to text/html?
 
-         setContentType: function(type) {
 
-           self.headers['Content-Type'] = type;
 
-         },
 
-         setStatusCode: function(code) {
 
-           self.statusCode = code;
 
-         },
 
-         addHeader: function(key, value) {
 
-           self.headers[key] = value;
 
-         },
 
-         createReadStream: function() {
 
-           self._streamsWaiting++;
 
-           return req;
 
-         },
 
-         createWriteStream: function() {
 
-           self._streamsWaiting++;
 
-           return res;
 
-         },
 
-         Error: function(err) {
 
-           if (err instanceof Meteor.Error) {
 
-             // Return controlled error
 
-             sendError(res, err.error, err.message);
 
-           } else if (err instanceof Error) {
 
-             // Return error trace - this is not intented
 
-             sendError(res, 503, 'Error in method "' + self.reference + '", Error: ' + (err.stack || err.message) );
 
-           } else {
 
-             sendError(res, 503, 'Error in method "' + self.reference + '"' );
 
-           }
 
-         },
 
-         // getData: function() {
 
-         //   // XXX: TODO if we could run the request handler stuff eg.
 
-         //   // in here in a fiber sync it could be cool - and the user did
 
-         //   // not have to specify the stream=true flag?
 
-         // }
 
-       };
 
-       // This function sends the final response. Depending on the
 
-       // timing of the streaming, we might have to wait for all
 
-       // streaming to end, or we might have to wait for this function
 
-       // to finish after streaming ends. The checks in this function
 
-       // and the fact that we call it twice ensure that we will always
 
-       // send the response if we haven't sent an error response, but
 
-       // we will not send it too early.
 
-       function sendResponseIfDone() {
 
-         res.statusCode = self.statusCode;
 
-         // If no streams are waiting
 
-         if (self._streamsWaiting === 0 &&
 
-             (self.statusCode === 200 || self.statusCode === 206) &&
 
-             self.done &&
 
-             !self._responseSent &&
 
-             !res.finished) {
 
-           self._responseSent = true;
 
-           res.end(resultBuffer);
 
-         }
 
-       }
 
-       var methodCall = self.methodObject[self.method];
 
-       // If the method call is set for the POST/PUT/GET or DELETE then run the
 
-       // respective methodCall if its a function
 
-       if (typeof methodCall === 'function') {
 
-         // Get the userId - This is either set as a method specific handler and
 
-         // will allways default back to the builtin getUserId handler
 
-         try {
 
-           // Try to set the userId
 
-           thisScope.userId = self.methodObject.auth.apply(self);
 
-         } catch(err) {
 
-           sendError(res, err.error, (err.message || err.stack));
 
-           return;
 
-         }
 
-         // This must be attached before there's any chance of `createReadStream`
 
-         // or `createWriteStream` being called, which means before we do
 
-         // `methodCall.apply` below.
 
-         req.on('end', function() {
 
-           self._streamsWaiting--;
 
-           sendResponseIfDone();
 
-         });
 
-         // Get the result of the methodCall
 
-         try {
 
-           if (self.method === 'OPTIONS') {
 
-             result = methodCall.apply(thisScope, [self.methodObject]) || '';
 
-           } else {
 
-             result = methodCall.apply(thisScope, [self.data]) || '';
 
-           }
 
-         } catch(err) {
 
-           if (err instanceof Meteor.Error) {
 
-             // Return controlled error
 
-             sendError(res, err.error, err.message);
 
-           } else {
 
-             // Return error trace - this is not intented
 
-             sendError(res, 503, 'Error in method "' + self.reference + '", Error: ' + (err.stack || err.message) );
 
-           }
 
-           return;
 
-         }
 
-         // Set headers
 
-         _.each(self.headers, function(value, key) {
 
-           // If value is defined then set the header, this allows for unsetting
 
-           // the default content-type
 
-           if (typeof value !== 'undefined')
 
-             res.setHeader(key, value);
 
-         });
 
-         // If OK / 200 then Return the result
 
-         if (self.statusCode === 200 || self.statusCode === 206) {
 
-           if (self.method !== "HEAD") {
 
-             // Return result
 
-             if (typeof result === 'string') {
 
-               resultBuffer = new Buffer(result);
 
-             } else {
 
-               resultBuffer = new Buffer(JSON.stringify(result));
 
-             }
 
-             // Check if user wants to overwrite content length for some reason?
 
-             if (typeof self.headers['Content-Length'] === 'undefined') {
 
-               self.headers['Content-Length'] = resultBuffer.length;
 
-             }
 
-           }
 
-           self.done = true;
 
-           sendResponseIfDone();
 
-         } else {
 
-           // Allow user to alter the status code and send a message
 
-           sendError(res, self.statusCode, result);
 
-         }
 
-       } else {
 
-         sendError(res, 404, 'Service not found');
 
-       }
 
-     });
 
-     // Run http methods handler
 
-     try {
 
-       runServerMethod.run(fiberScope);
 
-     } catch(err) {
 
-       sendError(res, 500, 'Error running the server http method handler, Error: ' + (err.stack || err.message));
 
-     }
 
-   }); // EO Request handler
 
- });
 
 
  |