text: // Copyright 2012 Mark Cavage, Inc.  All rights reserved.
  
  'use strict';
  
  var errors = require('restify-errors');
  
  
  ///--- Globals
  
  var BadRequestError = errors.BadRequestError;
  var PreconditionFailedError = errors.PreconditionFailedError;
  
  var IF_MATCH_FAIL = 'if-match \'%s\' didn\'t match etag \'%s\'';
  var IF_NO_MATCH_FAIL = 'if-none-match \'%s\' matched etag \'%s\'';
  var IF_MOD_FAIL = 'object was modified at \'%s\'; if-modified-since \'%s\'';
  var IF_UNMOD_FAIL = 'object was modified at \'%s\'; if-unmodified-since \'%s\'';
  
  
  ///--- API
  // Reference RFC2616 section 14 for an explanation of what this all does.
  
  function checkIfMatch(req, res, next) {
      var clientETags;
      var cur;
      var etag = res.etag || res.getHeader('etag') || '';
      var ifMatch;
      var matched = false;
  
      if ((ifMatch = req.headers['if-match'])) {
  
          clientETags = ifMatch.split(/\s*,\s*/);
  
          for (var i = 0; i < clientETags.length; i++) {
              cur = clientETags[i];
  
              // only strong comparison
  
              cur = cur.replace(/^W\//, '');
              cur = cur.replace(/^"(\w*)"$/, '$1');
  
              if (cur === '*' || cur === etag) {
                  matched = true;
                  break;
              }
          }
  
          if (!matched) {
              var err = new PreconditionFailedError(IF_MATCH_FAIL,
                  ifMatch,
                  etag);
              return (next(err));
          }
      }
  
      return (next());
  }
  
  
  function checkIfNoneMatch(req, res, next) {
      var clientETags;
      var cur;
      var etag = res.etag || res.getHeader('etag') || '';
      var ifNoneMatch;
      var matched = false;
  
      if ((ifNoneMatch = req.headers['if-none-match'])) {
  
          clientETags = ifNoneMatch.split(/\s*,\s*/);
  
          for (var i = 0; i < clientETags.length; i++) {
              cur = clientETags[i];
  
              // ignore weak validation
              cur = cur.replace(/^W\//, '');
              cur = cur.replace(/^"(\w*)"$/, '$1');
  
              if (cur === '*' || cur === etag) {
                  matched = true;
                  break;
              }
          }
  
          if (!matched) {
              return (next());
          }
  
          if (req.method !== 'GET' && req.method !== 'HEAD') {
              var err = new PreconditionFailedError(IF_NO_MATCH_FAIL,
                  ifNoneMatch,
                  etag);
              return (next(err));
          }
  
          res.send(304);
          return (next(false));
      }
  
      return (next());
  }
  
  
  function checkIfModified(req, res, next) {
      var code;
      var err;
      var ctime = req.header('if-modified-since');
      var mtime = res.mtime || res.header('Last-Modified') || '';
  
      if (!mtime || !ctime) {
          next();
          return;
      }
  
      try {
          //
          // TODO handle Range header modifications
          //
          // Note: this is not technically correct as per 2616 -
          // 2616 only specifies semantics for GET requests, not
          // any other method - but using if-modified-since with a
          // PUT or DELETE seems like returning 412 is sane
          //
          if (Date.parse(mtime) <= Date.parse(ctime)) {
              switch (req.method) {
                  case 'GET':
                  case 'HEAD':
                      code = 304;
                      break;
  
                  default:
                      err = new PreconditionFailedError(IF_MOD_FAIL,
                          mtime,
                          ctime);
                      break;
              }
          }
      } catch (e) {
          next(new BadRequestError(e.message));
          return;
      }
  
      if (code !== undefined) {
          res.send(code);
          next(false);
          return;
      }
  
      next(err);
  }
  
  
  function checkIfUnmodified(req, res, next) {
      var err;
      var ctime = req.headers['if-unmodified-since'];
      var mtime = res.mtime || res.header('Last-Modified') || '';
  
      if (!mtime || !ctime) {
          next();
          return;
      }
  
      try {
          if (Date.parse(mtime) > Date.parse(ctime)) {
              err = new PreconditionFailedError(IF_UNMOD_FAIL,
                  mtime,
                  ctime);
          }
      } catch (e) {
          next(new BadRequestError(e.message));
          return;
      }
  
      next(err);
  }
  
  
  ///--- Exports
  
  /**
   * Returns a set of plugins that will compare an already set ETag header with
   * the client's If-Match and If-None-Match header, and an already set
   * Last-Modified header with the client's If-Modified-Since and
   * If-Unmodified-Since header.
   * @public
   * @throws {BadRequestError | PreconditionFailedError}
   * @function conditionalRequest
   * @returns  {Array}
   */
  function conditionalRequest() {
      var chain = [
          checkIfMatch,
          checkIfNoneMatch,
          checkIfModified,
          checkIfUnmodified
      ];
      return (chain);
  }
  
  module.exports = conditionalRequest;
  