encoder.d 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. module vibe.http.internal.http2.hpack.encoder;
  2. import vibe.http.internal.http2.hpack.tables;
  3. import vibe.http.internal.http2.hpack.huffman;
  4. import vibe.http.internal.http2.hpack.util;
  5. import std.range;
  6. import std.typecons;
  7. import std.conv;
  8. import std.array;
  9. void encode(R)(HTTP2HeaderTableField header, ref R dst, ref IndexingTable table, bool huffman = true)
  10. @safe
  11. {
  12. // try to encode as integer
  13. bool indexed = encodeInteger(header, dst, table, huffman);
  14. // if fail, encode as literal
  15. if(!indexed) encodeLiteral(header, dst, huffman);
  16. }
  17. /// encode a pure integer (present in table) or integer name + literal value
  18. private bool encodeInteger(R)(const HTTP2HeaderTableField header, ref R dst, ref IndexingTable table, bool huffman = true)
  19. @trusted
  20. {
  21. // check table for indexed headers
  22. size_t idx = 1;
  23. bool found = false;
  24. size_t partialFound = false;
  25. while(idx < table.size) {
  26. // encode both name / value as index
  27. auto h = table[idx];
  28. if(h.name == header.name && h.value == header.value) {
  29. found = true;
  30. partialFound = false;
  31. break;
  32. // encode name as index, value as literal
  33. } else if(h.name == header.name && h.value != header.value) {
  34. found = false;
  35. partialFound = idx;
  36. }
  37. idx++;
  38. }
  39. if(found) {
  40. if(idx < 127) { // can be fit in one octet
  41. dst.put(cast(ubyte)(idx ^ 128));
  42. } else { // must be split in multiple octets
  43. dst.put(cast(ubyte)255);
  44. idx -= 127;
  45. while (idx > 127) {
  46. dst.put(cast(ubyte)((idx % 128) ^ 128));
  47. idx = idx / 128;
  48. }
  49. dst.put(cast(ubyte)(idx & 127));
  50. }
  51. return true;
  52. } else if(partialFound) {
  53. // encode name as index ( always smaller than 64 )
  54. if(header.index) dst.put(cast(ubyte)((partialFound + 64) & 127));
  55. else if (header.neverIndex) dst.put(cast(ubyte)((partialFound + 16) & 31));
  56. else dst.put(cast(ubyte)(partialFound & 15));
  57. // encode value as literal
  58. encodeLiteralField(to!string(header.value), dst, huffman);
  59. return true;
  60. }
  61. return false;
  62. }
  63. /// encode a literal field depending on its indexing requirements
  64. private void encodeLiteral(R)(const HTTP2HeaderTableField header, ref R dst, bool huffman = true)
  65. @safe
  66. {
  67. if(header.index) dst.put(cast(ubyte)(64));
  68. else if(header.neverIndex) dst.put(cast(ubyte)(16));
  69. else dst.put(cast(ubyte)(0));
  70. encodeLiteralField(to!string(header.name), dst, huffman);
  71. encodeLiteralField(to!string(header.value), dst, huffman);
  72. }
  73. /// encode a field (name / value) using huffman or raw encoding
  74. private void encodeLiteralField(R)(string src, ref R dst, bool huffman = true) @safe
  75. {
  76. if(huffman) {
  77. encodeHuffman(src, dst);
  78. } else {
  79. auto blen = (src.length) & 127;
  80. dst.put(cast(ubyte)blen);
  81. dst.put(cast(ubyte[])(to!string(src).dup));
  82. }
  83. }
  84. unittest {
  85. // encode integer
  86. import vibe.internal.array : BatchBuffer;
  87. import vibe.http.common;
  88. auto table = IndexingTable(4096);
  89. BatchBuffer!(ubyte, 1) bres;
  90. bres.putN(1);
  91. ubyte[1] expected = [0x82];
  92. auto hint = HTTP2HeaderTableField(":method", HTTPMethod.GET);
  93. assert(encodeInteger(hint, bres, table));
  94. assert(bres.peekDst == expected);
  95. }
  96. unittest {
  97. // encode literal
  98. // custom-key: custom-header
  99. import vibe.internal.array : BatchBuffer;
  100. ubyte[26] lexpected = [0x40, 0x0a, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x6b,
  101. 0x65, 0x79, 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x68, 0x65, 0x61, 0x64,
  102. 0x65, 0x72];
  103. BatchBuffer!(ubyte, 26) lres;
  104. lres.putN(26);
  105. auto hlit = HTTP2HeaderTableField("custom-key", "custom-header");
  106. encodeLiteral(hlit, lres, false);
  107. assert(lres.peekDst == lexpected);
  108. }