OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 class _HttpHeaders implements HttpHeaders { |
| 6 _HttpHeaders() : _headers = new Map<String, List<String>>(); |
| 7 |
| 8 List<String> operator[](String name) { |
| 9 name = name.toLowerCase(); |
| 10 return _headers[name]; |
| 11 } |
| 12 |
| 13 String value(String name) { |
| 14 name = name.toLowerCase(); |
| 15 List<String> values = _headers[name]; |
| 16 if (values == null) return null; |
| 17 if (values.length > 1) { |
| 18 throw new HttpException("More than one value for header $name"); |
| 19 } |
| 20 return values[0]; |
| 21 } |
| 22 |
| 23 void add(String name, Object value) { |
| 24 _checkMutable(); |
| 25 if (value is List) { |
| 26 for (int i = 0; i < value.length; i++) { |
| 27 _add(name, value[i]); |
| 28 } |
| 29 } else { |
| 30 _add(name, value); |
| 31 } |
| 32 } |
| 33 |
| 34 void set(String name, Object value) { |
| 35 name = name.toLowerCase(); |
| 36 _checkMutable(); |
| 37 removeAll(name); |
| 38 add(name, value); |
| 39 } |
| 40 |
| 41 void remove(String name, Object value) { |
| 42 _checkMutable(); |
| 43 name = name.toLowerCase(); |
| 44 List<String> values = _headers[name]; |
| 45 if (values != null) { |
| 46 int index = values.indexOf(value); |
| 47 if (index != -1) { |
| 48 values.removeRange(index, 1); |
| 49 } |
| 50 } |
| 51 } |
| 52 |
| 53 void removeAll(String name) { |
| 54 _checkMutable(); |
| 55 name = name.toLowerCase(); |
| 56 _headers.remove(name); |
| 57 } |
| 58 |
| 59 void forEach(void f(String name, List<String> values)) { |
| 60 _headers.forEach(f); |
| 61 } |
| 62 |
| 63 void noFolding(String name) { |
| 64 if (_noFoldingHeaders == null) _noFoldingHeaders = new List<String>(); |
| 65 _noFoldingHeaders.add(name); |
| 66 } |
| 67 |
| 68 String get host => _host; |
| 69 |
| 70 void set host(String host) { |
| 71 _checkMutable(); |
| 72 _host = host; |
| 73 _updateHostHeader(); |
| 74 } |
| 75 |
| 76 int get port => _port; |
| 77 |
| 78 void set port(int port) { |
| 79 _checkMutable(); |
| 80 _port = port; |
| 81 _updateHostHeader(); |
| 82 } |
| 83 |
| 84 Date get ifModifiedSince { |
| 85 List<String> values = _headers["if-modified-since"]; |
| 86 if (values != null) { |
| 87 try { |
| 88 return _HttpUtils.parseDate(values[0]); |
| 89 } on Exception catch (e) { |
| 90 return null; |
| 91 } |
| 92 } |
| 93 return null; |
| 94 } |
| 95 |
| 96 void set ifModifiedSince(Date ifModifiedSince) { |
| 97 _checkMutable(); |
| 98 // Format "ifModifiedSince" header with date in Greenwich Mean Time (GMT). |
| 99 String formatted = _HttpUtils.formatDate(ifModifiedSince.toUtc()); |
| 100 _set("if-modified-since", formatted); |
| 101 } |
| 102 |
| 103 Date get date { |
| 104 List<String> values = _headers["date"]; |
| 105 if (values != null) { |
| 106 try { |
| 107 return _HttpUtils.parseDate(values[0]); |
| 108 } on Exception catch (e) { |
| 109 return null; |
| 110 } |
| 111 } |
| 112 return null; |
| 113 } |
| 114 |
| 115 void set date(Date date) { |
| 116 _checkMutable(); |
| 117 // Format "Date" header with date in Greenwich Mean Time (GMT). |
| 118 String formatted = _HttpUtils.formatDate(date.toUtc()); |
| 119 _set("date", formatted); |
| 120 } |
| 121 |
| 122 Date get expires { |
| 123 List<String> values = _headers["expires"]; |
| 124 if (values != null) { |
| 125 try { |
| 126 return _HttpUtils.parseDate(values[0]); |
| 127 } on Exception catch (e) { |
| 128 return null; |
| 129 } |
| 130 } |
| 131 return null; |
| 132 } |
| 133 |
| 134 void set expires(Date expires) { |
| 135 _checkMutable(); |
| 136 // Format "Expires" header with date in Greenwich Mean Time (GMT). |
| 137 String formatted = _HttpUtils.formatDate(expires.toUtc()); |
| 138 _set("expires", formatted); |
| 139 } |
| 140 |
| 141 ContentType get contentType { |
| 142 var values = _headers["content-type"]; |
| 143 if (values != null) { |
| 144 return new ContentType.fromString(values[0]); |
| 145 } else { |
| 146 return new ContentType(); |
| 147 } |
| 148 } |
| 149 |
| 150 void set contentType(ContentType contentType) { |
| 151 _checkMutable(); |
| 152 _set("content-type", contentType.toString()); |
| 153 } |
| 154 |
| 155 void _add(String name, Object value) { |
| 156 var lowerCaseName = name.toLowerCase(); |
| 157 // TODO(sgjesse): Add immutable state throw HttpException is immutable. |
| 158 if (lowerCaseName == "date") { |
| 159 if (value is Date) { |
| 160 date = value; |
| 161 } else if (value is String) { |
| 162 _set("date", value); |
| 163 } else { |
| 164 throw new HttpException("Unexpected type for header named $name"); |
| 165 } |
| 166 } else if (lowerCaseName == "expires") { |
| 167 if (value is Date) { |
| 168 expires = value; |
| 169 } else if (value is String) { |
| 170 _set("expires", value); |
| 171 } else { |
| 172 throw new HttpException("Unexpected type for header named $name"); |
| 173 } |
| 174 } else if (lowerCaseName == "if-modified-since") { |
| 175 if (value is Date) { |
| 176 ifModifiedSince = value; |
| 177 } else if (value is String) { |
| 178 _set("if-modified-since", value); |
| 179 } else { |
| 180 throw new HttpException("Unexpected type for header named $name"); |
| 181 } |
| 182 } else if (lowerCaseName == "host") { |
| 183 int pos = value.indexOf(":"); |
| 184 if (pos == -1) { |
| 185 _host = value; |
| 186 _port = HttpClient.DEFAULT_HTTP_PORT; |
| 187 } else { |
| 188 if (pos > 0) { |
| 189 _host = value.substring(0, pos); |
| 190 } else { |
| 191 _host = null; |
| 192 } |
| 193 if (pos + 1 == value.length) { |
| 194 _port = HttpClient.DEFAULT_HTTP_PORT; |
| 195 } else { |
| 196 try { |
| 197 _port = parseInt(value.substring(pos + 1)); |
| 198 } on FormatException catch (e) { |
| 199 _port = null; |
| 200 } |
| 201 } |
| 202 } |
| 203 _set("host", value); |
| 204 } else if (lowerCaseName == "content-type") { |
| 205 _set("content-type", value); |
| 206 } else { |
| 207 name = lowerCaseName; |
| 208 List<String> values = _headers[name]; |
| 209 if (values == null) { |
| 210 values = new List<String>(); |
| 211 _headers[name] = values; |
| 212 } |
| 213 if (value is Date) { |
| 214 values.add(_HttpUtils.formatDate(value)); |
| 215 } else { |
| 216 values.add(value.toString()); |
| 217 } |
| 218 } |
| 219 } |
| 220 |
| 221 void _set(String name, String value) { |
| 222 name = name.toLowerCase(); |
| 223 List<String> values = new List<String>(); |
| 224 _headers[name] = values; |
| 225 values.add(value); |
| 226 } |
| 227 |
| 228 _checkMutable() { |
| 229 if (!_mutable) throw new HttpException("HTTP headers are not mutable"); |
| 230 } |
| 231 |
| 232 _updateHostHeader() { |
| 233 bool defaultPort = _port == null || _port == HttpClient.DEFAULT_HTTP_PORT; |
| 234 String portPart = defaultPort ? "" : ":$_port"; |
| 235 _set("host", "$host$portPart"); |
| 236 } |
| 237 |
| 238 _foldHeader(String name) { |
| 239 if (name == "set-cookie" || |
| 240 (_noFoldingHeaders != null && |
| 241 _noFoldingHeaders.indexOf(name) != -1)) { |
| 242 return false; |
| 243 } |
| 244 return true; |
| 245 } |
| 246 |
| 247 _write(_HttpConnectionBase connection) { |
| 248 final COLONSP = const [_CharCode.COLON, _CharCode.SP]; |
| 249 final COMMASP = const [_CharCode.COMMA, _CharCode.SP]; |
| 250 final CRLF = const [_CharCode.CR, _CharCode.LF]; |
| 251 |
| 252 var bufferSize = 16 * 1024; |
| 253 var buffer = new Uint8List(bufferSize); |
| 254 var bufferPos = 0; |
| 255 |
| 256 void writeBuffer() { |
| 257 connection._writeFrom(buffer, 0, bufferPos); |
| 258 bufferPos = 0; |
| 259 } |
| 260 |
| 261 // Format headers. |
| 262 _headers.forEach((String name, List<String> values) { |
| 263 bool fold = _foldHeader(name); |
| 264 List<int> nameData; |
| 265 nameData = name.charCodes; |
| 266 int nameDataLen = nameData.length; |
| 267 if (nameDataLen + 2 > bufferSize - bufferPos) writeBuffer(); |
| 268 buffer.setRange(bufferPos, nameDataLen, nameData); |
| 269 bufferPos += nameDataLen; |
| 270 buffer[bufferPos++] = _CharCode.COLON; |
| 271 buffer[bufferPos++] = _CharCode.SP; |
| 272 for (int i = 0; i < values.length; i++) { |
| 273 List<int> data = values[i].charCodes; |
| 274 int dataLen = data.length; |
| 275 // Worst case here is writing the name, value and 6 additional bytes. |
| 276 if (nameDataLen + dataLen + 6 > bufferSize - bufferPos) writeBuffer(); |
| 277 if (i > 0) { |
| 278 if (fold) { |
| 279 buffer[bufferPos++] = _CharCode.COMMA; |
| 280 buffer[bufferPos++] = _CharCode.SP; |
| 281 } else { |
| 282 buffer[bufferPos++] = _CharCode.CR; |
| 283 buffer[bufferPos++] = _CharCode.LF; |
| 284 buffer.setRange(bufferPos, nameDataLen, nameData); |
| 285 bufferPos += nameDataLen; |
| 286 buffer[bufferPos++] = _CharCode.COLON; |
| 287 buffer[bufferPos++] = _CharCode.SP; |
| 288 } |
| 289 } |
| 290 buffer.setRange(bufferPos, dataLen, data); |
| 291 bufferPos += dataLen; |
| 292 } |
| 293 buffer[bufferPos++] = _CharCode.CR; |
| 294 buffer[bufferPos++] = _CharCode.LF; |
| 295 }); |
| 296 writeBuffer(); |
| 297 } |
| 298 |
| 299 String toString() { |
| 300 StringBuffer sb = new StringBuffer(); |
| 301 _headers.forEach((String name, List<String> values) { |
| 302 sb.add(name); |
| 303 sb.add(": "); |
| 304 bool fold = _foldHeader(name); |
| 305 for (int i = 0; i < values.length; i++) { |
| 306 if (i > 0) { |
| 307 if (fold) { |
| 308 sb.add(", "); |
| 309 } else { |
| 310 sb.add("\n"); |
| 311 sb.add(name); |
| 312 sb.add(": "); |
| 313 } |
| 314 } |
| 315 sb.add(values[i]); |
| 316 } |
| 317 sb.add("\n"); |
| 318 }); |
| 319 return sb.toString(); |
| 320 } |
| 321 |
| 322 bool _mutable = true; // Are the headers currently mutable? |
| 323 Map<String, List<String>> _headers; |
| 324 List<String> _noFoldingHeaders; |
| 325 |
| 326 String _host; |
| 327 int _port; |
| 328 } |
| 329 |
| 330 |
| 331 class _HeaderValue implements HeaderValue { |
| 332 _HeaderValue([String this.value = ""]); |
| 333 |
| 334 _HeaderValue.fromString(String value, {this.parameterSeparator: ";"}) { |
| 335 // Parse the string. |
| 336 _parse(value); |
| 337 } |
| 338 |
| 339 Map<String, String> get parameters { |
| 340 if (_parameters == null) _parameters = new Map<String, String>(); |
| 341 return _parameters; |
| 342 } |
| 343 |
| 344 String toString() { |
| 345 StringBuffer sb = new StringBuffer(); |
| 346 sb.add(value); |
| 347 if (parameters != null && parameters.length > 0) { |
| 348 _parameters.forEach((String name, String value) { |
| 349 sb.add("; "); |
| 350 sb.add(name); |
| 351 sb.add("="); |
| 352 sb.add(value); |
| 353 }); |
| 354 } |
| 355 return sb.toString(); |
| 356 } |
| 357 |
| 358 void _parse(String s) { |
| 359 int index = 0; |
| 360 |
| 361 bool done() => index == s.length; |
| 362 |
| 363 void skipWS() { |
| 364 while (!done()) { |
| 365 if (s[index] != " " && s[index] != "\t") return; |
| 366 index++; |
| 367 } |
| 368 } |
| 369 |
| 370 String parseValue() { |
| 371 int start = index; |
| 372 while (!done()) { |
| 373 if (s[index] == " " || |
| 374 s[index] == "\t" || |
| 375 s[index] == parameterSeparator) break; |
| 376 index++; |
| 377 } |
| 378 return s.substring(start, index).toLowerCase(); |
| 379 } |
| 380 |
| 381 void expect(String expected) { |
| 382 if (done() || s[index] != expected) { |
| 383 throw new HttpException("Failed to parse header value"); |
| 384 } |
| 385 index++; |
| 386 } |
| 387 |
| 388 void maybeExpect(String expected) { |
| 389 if (s[index] == expected) index++; |
| 390 } |
| 391 |
| 392 void parseParameters() { |
| 393 _parameters = new Map<String, String>(); |
| 394 |
| 395 String parseParameterName() { |
| 396 int start = index; |
| 397 while (!done()) { |
| 398 if (s[index] == " " || s[index] == "\t" || s[index] == "=") break; |
| 399 index++; |
| 400 } |
| 401 return s.substring(start, index).toLowerCase(); |
| 402 } |
| 403 |
| 404 String parseParameterValue() { |
| 405 if (s[index] == "\"") { |
| 406 // Parse quoted value. |
| 407 StringBuffer sb = new StringBuffer(); |
| 408 index++; |
| 409 while (!done()) { |
| 410 if (s[index] == "\\") { |
| 411 if (index + 1 == s.length) { |
| 412 throw new HttpException("Failed to parse header value"); |
| 413 } |
| 414 index++; |
| 415 } else if (s[index] == "\"") { |
| 416 index++; |
| 417 break; |
| 418 } |
| 419 sb.add(s[index]); |
| 420 index++; |
| 421 } |
| 422 return sb.toString(); |
| 423 } else { |
| 424 // Parse non-quoted value. |
| 425 return parseValue(); |
| 426 } |
| 427 } |
| 428 |
| 429 while (!done()) { |
| 430 skipWS(); |
| 431 if (done()) return; |
| 432 String name = parseParameterName(); |
| 433 skipWS(); |
| 434 expect("="); |
| 435 skipWS(); |
| 436 String value = parseParameterValue(); |
| 437 _parameters[name] = value; |
| 438 skipWS(); |
| 439 if (done()) return; |
| 440 expect(parameterSeparator); |
| 441 } |
| 442 } |
| 443 |
| 444 skipWS(); |
| 445 value = parseValue(); |
| 446 skipWS(); |
| 447 if (done()) return; |
| 448 maybeExpect(parameterSeparator); |
| 449 parseParameters(); |
| 450 } |
| 451 |
| 452 String value; |
| 453 String parameterSeparator; |
| 454 Map<String, String> _parameters; |
| 455 } |
| 456 |
| 457 |
| 458 class _ContentType extends _HeaderValue implements ContentType { |
| 459 _ContentType(String primaryType, String subType) |
| 460 : _primaryType = primaryType, _subType = subType, super(""); |
| 461 |
| 462 _ContentType.fromString(String value) : super.fromString(value); |
| 463 |
| 464 String get value => "$_primaryType/$_subType"; |
| 465 |
| 466 void set value(String s) { |
| 467 int index = s.indexOf("/"); |
| 468 if (index == -1 || index == (s.length - 1)) { |
| 469 primaryType = s.trim().toLowerCase(); |
| 470 subType = ""; |
| 471 } else { |
| 472 primaryType = s.substring(0, index).trim().toLowerCase(); |
| 473 subType = s.substring(index + 1).trim().toLowerCase(); |
| 474 } |
| 475 } |
| 476 |
| 477 String get primaryType => _primaryType; |
| 478 |
| 479 void set primaryType(String s) { |
| 480 _primaryType = s; |
| 481 } |
| 482 |
| 483 String get subType => _subType; |
| 484 |
| 485 void set subType(String s) { |
| 486 _subType = s; |
| 487 } |
| 488 |
| 489 String get charset => parameters["charset"]; |
| 490 |
| 491 void set charset(String s) { |
| 492 parameters["charset"] = s; |
| 493 } |
| 494 |
| 495 String _primaryType = ""; |
| 496 String _subType = ""; |
| 497 } |
| 498 |
| 499 |
| 500 class _Cookie implements Cookie { |
| 501 _Cookie([String this.name, String this.value]); |
| 502 |
| 503 _Cookie.fromSetCookieValue(String value) { |
| 504 // Parse the Set-Cookie header value. |
| 505 _parseSetCookieValue(value); |
| 506 } |
| 507 |
| 508 // Parse a Set-Cookie header value according to the rules in RFC 6265. |
| 509 void _parseSetCookieValue(String s) { |
| 510 int index = 0; |
| 511 |
| 512 bool done() => index == s.length; |
| 513 |
| 514 String parseName() { |
| 515 int start = index; |
| 516 while (!done()) { |
| 517 if (s[index] == "=") break; |
| 518 index++; |
| 519 } |
| 520 return s.substring(start, index).trim().toLowerCase(); |
| 521 } |
| 522 |
| 523 String parseValue() { |
| 524 int start = index; |
| 525 while (!done()) { |
| 526 if (s[index] == ";") break; |
| 527 index++; |
| 528 } |
| 529 return s.substring(start, index).trim().toLowerCase(); |
| 530 } |
| 531 |
| 532 void expect(String expected) { |
| 533 if (done()) throw new HttpException("Failed to parse header value [$s]"); |
| 534 if (s[index] != expected) { |
| 535 throw new HttpException("Failed to parse header value [$s]"); |
| 536 } |
| 537 index++; |
| 538 } |
| 539 |
| 540 void parseAttributes() { |
| 541 String parseAttributeName() { |
| 542 int start = index; |
| 543 while (!done()) { |
| 544 if (s[index] == "=" || s[index] == ";") break; |
| 545 index++; |
| 546 } |
| 547 return s.substring(start, index).trim().toLowerCase(); |
| 548 } |
| 549 |
| 550 String parseAttributeValue() { |
| 551 int start = index; |
| 552 while (!done()) { |
| 553 if (s[index] == ";") break; |
| 554 index++; |
| 555 } |
| 556 return s.substring(start, index).trim().toLowerCase(); |
| 557 } |
| 558 |
| 559 while (!done()) { |
| 560 String name = parseAttributeName(); |
| 561 String value = ""; |
| 562 if (!done() && s[index] == "=") { |
| 563 index++; // Skip the = character. |
| 564 value = parseAttributeValue(); |
| 565 } |
| 566 if (name == "expires") { |
| 567 expires = _HttpUtils.parseCookieDate(value); |
| 568 } else if (name == "max-age") { |
| 569 maxAge = parseInt(value); |
| 570 } else if (name == "domain") { |
| 571 domain = value; |
| 572 } else if (name == "path") { |
| 573 path = value; |
| 574 } else if (name == "httponly") { |
| 575 httpOnly = true; |
| 576 } else if (name == "secure") { |
| 577 secure = true; |
| 578 } |
| 579 if (!done()) index++; // Skip the ; character |
| 580 } |
| 581 } |
| 582 |
| 583 name = parseName(); |
| 584 if (done() || name.length == 0) { |
| 585 throw new HttpException("Failed to parse header value [$s]"); |
| 586 } |
| 587 index++; // Skip the = character. |
| 588 value = parseValue(); |
| 589 if (done()) return; |
| 590 index++; // Skip the ; character. |
| 591 parseAttributes(); |
| 592 } |
| 593 |
| 594 String toString() { |
| 595 StringBuffer sb = new StringBuffer(); |
| 596 sb.add(name); |
| 597 sb.add("="); |
| 598 sb.add(value); |
| 599 if (expires != null) { |
| 600 sb.add("; Expires="); |
| 601 sb.add(_HttpUtils.formatDate(expires)); |
| 602 } |
| 603 if (maxAge != null) { |
| 604 sb.add("; Max-Age="); |
| 605 sb.add(maxAge); |
| 606 } |
| 607 if (domain != null) { |
| 608 sb.add("; Domain="); |
| 609 sb.add(domain); |
| 610 } |
| 611 if (path != null) { |
| 612 sb.add("; Path="); |
| 613 sb.add(path); |
| 614 } |
| 615 if (secure) sb.add("; Secure"); |
| 616 if (httpOnly) sb.add("; HttpOnly"); |
| 617 return sb.toString(); |
| 618 } |
| 619 |
| 620 String name; |
| 621 String value; |
| 622 Date expires; |
| 623 int maxAge; |
| 624 String domain; |
| 625 String path; |
| 626 bool httpOnly = false; |
| 627 bool secure = false; |
| 628 } |
OLD | NEW |