WebFund 2016W: Assignment 3

From Soma-notes

Please submit your answers by 2:30 PM on Tuesday, February 9th, 2016 via cuLearn.

The first lines of your assignment should be:

 COMP 2406 2016W Assignment #3
 <name>
 <student #>

Submit your answers as a text or PDF file only. MS Word .doc files, OpenOffice .odt, and other word processing files are not acceptable: convent to text or PDF!

If you used any outside resources or got help from any other students, please cite them with each answer or at the end of your assignment.


Questions

  1. [1] In simplegrep_async.js:14, what is the type of all of the symbols referenced in the function? Specifically, what is the type of lines, rawContents, and split?
  2. [1] If I create a line 24 in simplegrep_async.js that says 'console.log("finished!");', when will finished! be printed relative to the other output of the program?
  3. [1] What is a one-line change to tinywebserver.js that would make every HTTP response have a 404 response code?
  4. [1] How you could return a 403 error (access denied) to all requests from iPhones in tinywebserver.js? (Remember the User-Agent HTTP header.)
  5. [6] How would the behavior of form-demo changed when you make the following changes? Specifically, what changes would you see on the server and in the browser? Be sure to consider messages that are logged on the server and console output in the browser.
    1. Comment out form-demo/bin/www:7
    2. Change form-demo/bin/www:15 to be "var port=3000;".
    3. Change form-demo/app.js:13 to be "app.set('views', '.');"
    4. Comment out form-demo/routes/index.js:24
    5. Comment out form-demo/views/layout.jade:8
    6. Change the indentation of form-demo/views/index.jade:24 such that it is at the same indentation as form in line 8.

Solutions

  1. rawContents is a string, split is a method (function), lines is an array (of strings)
  2. finished! will be printed first, before any of the matched lines.
  3. Change line 42 to be "status=404;";
  4. Before line 92 add the following code: if (request.headers.user-agent && request.headers.user-agent.indexOf("iPhone")) { return respond(request, response, 403); } else // just before if on line 92
    1. The program would terminate at the first reference to app (line 16) because app is no longer defined.
    2. It would function identically unless the PORT environment variable were set, in which case its value would be ignored rather than being used to set the listening port.
    3. The app would now look for jade templates in the top-level directory rather than in views/. If the templates were not moved the app would terminate with an error saying it couldn't find a specified jade file the moment a page was requested that had a route that used a jade template.
    4. No routes would be defined so none of the URLs specified in routes would work (/, /add, /list).
    5. The pages /, /add, and /list would all have empty HTML <body> but the same <head>.
    6. The Submit button's position on in / will change its position slightly and the button will do nothing - it will no longer submit the form.

    Code

    Full source: simplegrep_async.js, tinywebserver.js, form-demo.zip

    simplegrep_async.js

    var fs = require('fs');
    
    if (process.argv.length < 4) {
        console.error('Not enough parameters given. Try this: ' +
                      '"node simplegrep_async term filename.txt"'); 
        process.exit(1);
    }
    
    var searchterm = process.argv[2];
    var filename = process.argv[3];
    
    var returnMatches = function(err, rawContents) {
    
        var lines = rawContents.split('\n');
    
        lines.forEach(function(theLine) {
            if (theLine.indexOf(searchterm) > -1) {
                console.log(theLine);
            }
        });
    }
    
    fs.readFile(filename, 'utf-8', returnMatches);
    

    tinywebserver.js

    var path = require('path');
    var http = require('http');
    var fs = require('fs');
    
    var MIME_TYPES = {
        'css': 'text/css',
        'gif': 'image/gif',
        'htm': 'text/html',
        'html': 'text/html',
        'ico': 'image/x-icon',
        'jpeg': 'image/jpeg',
        'jpg': 'image/jpeg',
        'js': 'text/javascript',
        'json': 'application/json',
        'png': 'image/png',
        'txt': 'text/text'
    };
    
    var options = {
        host: 'localhost',
        port: 8080,
        index: 'index.html',
        docroot: '.'
    };
    
    var get_mime = function(filename) {
        var ext, type;
        for (ext in MIME_TYPES) {
            type = MIME_TYPES[ext];
            if (filename.indexOf(ext, filename.length - ext.length) !== -1) {
                return type;
            }
        }
        return null;
    };
    
    
    var respond = function(request, response, status, content, content_type) {
        if (!status) {
            status = 200;
        }
    
        if (!content_type) {
            content_type = 'text/plain';
        }
        console.log("" + status + "\t" +
                    request.method + "\t" + request.url);
        response.writeHead(status, {
            "Content-Type": content_type
        });
        if (content) {
            response.write(content);
        }
        return response.end();
    };
    
    var serve_file = function(request, response, requestpath) {
        return fs.readFile(requestpath, function(error, content) {
            if (error != null) {
                console.error("ERROR: Encountered error while processing " +
                              request.method + " of \"" + request.url + 
                              "\".", error);
                return respond(request, response, 500);
            } else {
                return respond(request, response, 200, 
                               content, get_mime(requestpath));
            }
        });
    };
    
    
    var return_index = function(request, response, requestpath)  {
    
        var exists_callback = function(file_exists) {
            if (file_exists) {
                return serve_file(request, response, requestpath);
            } else {
                return respond(request, response, 404);
            }
        }
        
        if (requestpath.substr(-1) !== '/') {
            requestpath += "/";
        }
        requestpath += options.index;
        return fs.exists(requestpath, exists_callback);
    }
    
    var request_handler = function(request, response) {
        var requestpath;
    
        if (request.url.match(/((\.|%2E|%2e)(\.|%2E|%2e))|(~|%7E|%7e)/) != null) {
            console.warn("WARNING: " + request.method +
                         " of \"" + request.url + 
                         "\" rejected as insecure.");
            return respond(request, response, 403);
        } else {
            requestpath = path.normalize(path.join(options.docroot, request.url));
            return fs.exists(requestpath, function(file_exists) {
                if (file_exists) {
                    return fs.stat(requestpath, function(err, stat) {
                        if (err != null) {
                            console.error("ERROR: Encountered error calling" +
                                          "fs.stat on \"" + requestpath + 
                                          "\" while processing " + 
                                          request.method + " of \"" + 
                                          request.url + "\".", err);
                            return respond(request, response, 500);
                        } else {
                            if ((stat != null) && stat.isDirectory()) {
                                return return_index(request, response, requestpath);
                            } else {
                                return serve_file(request, response, requestpath);
                            }
                        }
                    });
                } else {
                    return respond(request, response, 404);
                }
            });
        }
    };
    
    var server = http.createServer(request_handler);
    
    server.listen(options.port, options.host, function() {
        return console.log("Server listening at http://" +
                           options.host + ":" + options.port + "/");
    });
    

    form-demo/bin/www

    #!/usr/bin/env node
    
    /**
     * Module dependencies.
     */
    
    var app = require('../app');
    var debug = require('debug')('form-demo:server');
    var http = require('http');
    
    /**
     * Get port from environment and store in Express.
     */
    
    var port = normalizePort(process.env.PORT || '3000');
    app.set('port', port);
    
    /**
     * Create HTTP server.
     */
    
    var server = http.createServer(app);
    
    /**
     * Listen on provided port, on all network interfaces.
     */
    
    server.listen(port);
    server.on('error', onError);
    server.on('listening', onListening);
    
    /**
     * Normalize a port into a number, string, or false.
     */
    
    function normalizePort(val) {
      var port = parseInt(val, 10);
    
      if (isNaN(port)) {
        // named pipe
        return val;
      }
    
      if (port >= 0) {
        // port number
        return port;
      }
    
      return false;
    }
    
    /**
     * Event listener for HTTP server "error" event.
     */
    
    function onError(error) {
      if (error.syscall !== 'listen') {
        throw error;
      }
    
      var bind = typeof port === 'string'
        ? 'Pipe ' + port
        : 'Port ' + port
    
      // handle specific listen errors with friendly messages
      switch (error.code) {
        case 'EACCES':
          console.error(bind + ' requires elevated privileges');
          process.exit(1);
          break;
        case 'EADDRINUSE':
          console.error(bind + ' is already in use');
          process.exit(1);
          break;
        default:
          throw error;
      }
    }
    
    /**
     * Event listener for HTTP server "listening" event.
     */
    
    function onListening() {
      var addr = server.address();
      var bind = typeof addr === 'string'
        ? 'pipe ' + addr
        : 'port ' + addr.port;
      debug('Listening on ' + bind);
    }
    


    form-demo/app.js

    var express = require('express');
    var path = require('path');
    var favicon = require('serve-favicon');
    var logger = require('morgan');
    var cookieParser = require('cookie-parser');
    var bodyParser = require('body-parser');
    
    var routes = require('./routes/index');
    
    var app = express();
    
    // view engine setup
    app.set('views', path.join(__dirname, 'views'));
    app.set('view engine', 'jade');
    
    // uncomment after placing your favicon in /public
    //app.use(favicon(__dirname + '/public/favicon.ico'));
    app.use(logger('dev'));
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({ extended: false }));
    app.use(cookieParser());
    app.use(express.static(path.join(__dirname, 'public')));
    
    app.use('/', routes);
    
    // catch 404 and forward to error handler
    app.use(function(req, res, next) {
        var err = new Error('Not Found');
        err.status = 404;
        next(err);
    });
    
    // error handlers
    
    if (app.get('env') === 'development') {
        // development error handler
        // will print stacktrace
        app.use(function(err, req, res, next) {
            res.status(err.status || 500);
            res.render('error', {
                message: err.message,
                error: err
            });
        });
        app.locals.pretty = true;
    } else {
        // production error handler
        // no stacktraces leaked to user
        app.use(function(err, req, res, next) {
    	res.status(err.status || 500);
    	res.render('error', {
                message: err.message,
                error: {}
    	});
        });
    }
    
    module.exports = app;
    


    form-demo/routes/index.js

    var express = require('express');
    var router = express.Router();
    
    var state = [];
    
    router.get('/', function(req, res, next) {
      res.render('index', { title: 'COMP 2406 Simple form demo' });
    });
    
    router.post('/add', function(req, res) {
        var obj = { name: req.body.name,
    		city: req.body.city,
                    country: req.body.country,
                    birthday: req.body.birthday,
                    email: req.body.email };
        state.push(obj);
        res.render('add', { title: 'Person just added', item: obj });
    });
    
    router.get('/list', function(req, res) {
        res.render('list', { title: 'People Listing',  items: state});
    });
    
    module.exports = router;
    


    form-demo/views/layout.jade

    doctype html
    html
      head
        title= title
        link(rel='stylesheet', href='/stylesheets/style.css')
        block header
      body
        block content
    


    form-demo/views/index.jade

    extends layout
    
    block content
      h1= title
    
      div
        p Fill out your info
        form(method="post", action="/add")
          div
            input#name(type="text", name="name")
            label Name
          div
            input#country(type="text", name="city")
            label City
          div
            input#country(type="text", name="country")
            label Country
          div
            input#birthday(type="text", name="birthday")
            label Birthday
          div
            input#email(type="text", name="email")
            label Email
          button(type="submit") Submit
    


    form-demo/views/add.jade

    extends layout
    
    block content
      h1= title
    
      p Name: #{item.name}
      p City: #{item.city}
      p Country: #{item.country}
      p Birthday: #{item.birthday}
      p Email: #{item.email}
    


    form-demo/views/list.jade

    extends layout
      
    block content
        h1= title
    
        div
          div
          table
            thead
              th Name
              th City
              th Country
              th Birthday
              th Email
            tbody
              each item in items
                tr
                  td #{item.name}
                  td #{item.city}
                  td #{item.country}
                  td #{item.birthday}
                  td #{item.email}
    
        form(method="get", action="/")
          button(type="submit") Home
    


    form-demo/views/error.jade

    extends layout
    
    block content
      h1= message
      h2= error.status
      pre #{error.stack}