« index
Coverage for /Users/yunong/workspace/node-restify/lib/plugins/conditional_request.js : 94%
192 lines |
182 run |
10 missing |
1 partial |
25 blocks |
20 blocks run |
5 blocks missing
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 | // Copyright 2012 Mark Cavage, Inc. All rights reserved. var errors = require('../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'])) { /* JSSTYLED */ clientETags = ifMatch.split(/\s*,\s*/); for (var i = 0; i < clientETags.length; i++) { cur = clientETags[i]; // only strong comparison /* JSSTYLED */ cur = cur.replace(/^W\//, ''); /* JSSTYLED */ 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'])) { /* JSSTYLED */ clientETags = ifNoneMatch.split(/\s*,\s*/); for (var i = 0; i < clientETags.length; i++) { cur = clientETags[i]; // ignore weak validation /* JSSTYLED */ cur = cur.replace(/^W\//, ''); /* JSSTYLED */ 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. */ function conditionalRequest() { var chain = [ checkIfMatch, checkIfNoneMatch, checkIfModified, checkIfUnmodified ]; return (chain); } module.exports = conditionalRequest; |