hpack.d 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. //module vibe.http.internal.hpack.hpack;
  2. module vibe.http.internal.http2.hpack.hpack;
  3. import vibe.http.internal.http2.hpack.encoder;
  4. import vibe.http.internal.http2.hpack.decoder;
  5. import vibe.http.internal.http2.hpack.tables;
  6. import std.range;
  7. import std.typecons;
  8. import std.array; // appender
  9. import std.algorithm.iteration;
  10. import vibe.container.internal.utilallocator: RegionListAllocator;
  11. /// interface for the HPACK encoder
  12. void encodeHPACK(I,R)(I src, ref R dst, ref IndexingTable table, bool huffman = true) @safe
  13. if(is(I == HTTP2HeaderTableField) || is(ElementType!I : HTTP2HeaderTableField))
  14. {
  15. static if(is(I == HTTP2HeaderTableField)) {
  16. src.encode(dst, table, huffman);
  17. } else if(is(ElementType!I : HTTP2HeaderTableField)){
  18. src.each!(h => h.encode(dst, table, huffman));
  19. }
  20. }
  21. void decodeHPACK(I,R,T)(I src, ref R dst, ref IndexingTable table, ref T alloc, uint maxTableSize = 4096) @safe
  22. if(isInputRange!I && (is(ElementType!I : immutable(ubyte)) || is(ElementType!I : immutable(char))))
  23. {
  24. while(!src.empty) src.decode(dst, table, alloc, maxTableSize);
  25. }
  26. /// ENCODER
  27. unittest {
  28. //// Following examples can be found in Appendix C of the HPACK RFC
  29. import vibe.http.status;
  30. import vibe.http.common;
  31. import std.experimental.allocator;
  32. import std.experimental.allocator.gc_allocator;
  33. auto table = IndexingTable(4096);
  34. scope alloc = new RegionListAllocator!(shared(GCAllocator), false)(1024, GCAllocator.instance);
  35. /** 1. Literal header field w. indexing (raw)
  36. * custom-key: custom-header
  37. */
  38. HTTP2HeaderTableField h1 = HTTP2HeaderTableField("custom-key", "custom-header");
  39. auto e1 = appender!(ubyte[]);
  40. auto dec1 = appender!(HTTP2HeaderTableField[]);
  41. h1.encodeHPACK(e1, table, false);
  42. decodeHPACK(cast(immutable(ubyte)[])e1.data, dec1, table, alloc);
  43. assert(dec1.data.front == h1);
  44. /** 1bis. Literal header field w. indexing (huffman encoded)
  45. * :authority: www.example.com
  46. */
  47. table.insert(HTTP2HeaderTableField(":authority", "www.example.com"));
  48. HTTP2HeaderTableField h1b = HTTP2HeaderTableField(":authority", "www.example.com");
  49. h1b.neverIndex = false;
  50. h1b.index = true;
  51. auto e1b = appender!(ubyte[]);
  52. auto dec1b = appender!(HTTP2HeaderTableField[]);
  53. h1b.encodeHPACK(e1b, table, true);
  54. decodeHPACK(cast(immutable(ubyte)[])e1b.data, dec1b, table, alloc);
  55. assert(dec1b.data.front == h1b);
  56. /** 2. Literal header field without indexing (raw)
  57. * :path: /sample/path
  58. */
  59. auto h2 = HTTP2HeaderTableField(":path", "/sample/path");
  60. h2.neverIndex = false;
  61. h2.index = false;
  62. // initialize with huffman=false (can be modified by e2.huffman)
  63. auto e2 = appender!(ubyte[]);
  64. auto dec2 = appender!(HTTP2HeaderTableField[]);
  65. h2.encodeHPACK(e2, table, false);
  66. decodeHPACK(cast(immutable(ubyte)[])e2.data, dec2, table, alloc);
  67. assert(dec2.data.front == h2);
  68. /** 3. Literal header field never indexed (raw)
  69. * password: secret
  70. */
  71. HTTP2HeaderTableField h3 = HTTP2HeaderTableField("password", "secret");
  72. h3.neverIndex = true;
  73. h3.index = false;
  74. auto e3 = appender!(ubyte[]);
  75. auto dec3 = appender!(HTTP2HeaderTableField[]);
  76. h3.encodeHPACK(e3, table, false);
  77. decodeHPACK(cast(immutable(ubyte)[])e3.data, dec3, table, alloc);
  78. assert(dec3.data.front == h3);
  79. /** 4. Indexed header field (integer)
  80. * :method: GET
  81. */
  82. HTTP2HeaderTableField h4 = HTTP2HeaderTableField(":method", HTTPMethod.GET);
  83. auto e4 = appender!(ubyte[]);
  84. auto dec4 = appender!(HTTP2HeaderTableField[]);
  85. h4.encodeHPACK(e4, table);
  86. decodeHPACK(cast(immutable(ubyte)[])e4.data, dec4, table, alloc);
  87. assert(dec4.data.front == h4);
  88. /** 5. Full request without huffman encoding
  89. * :method: GET
  90. * :scheme: http
  91. * :path: /
  92. * :authority: www.example.com
  93. * cache-control: no-cache
  94. */
  95. HTTP2HeaderTableField[] block = [
  96. HTTP2HeaderTableField(":method", HTTPMethod.GET),
  97. HTTP2HeaderTableField(":scheme", "http"),
  98. HTTP2HeaderTableField(":path", "/"),
  99. HTTP2HeaderTableField(":authority", "www.example.com"),
  100. HTTP2HeaderTableField("cache-control", "no-cache")
  101. ];
  102. ubyte[14] expected = [0x82, 0x86, 0x84, 0xbe, 0x58, 0x08, 0x6e, 0x6f, 0x2d, 0x63, 0x61, 0x63, 0x68, 0x65];
  103. auto bres = appender!(ubyte[]);
  104. block.encodeHPACK(bres, table, false);
  105. assert(bres.data == expected);
  106. /** 5. Full request with huffman encoding
  107. * :method: GET
  108. * :scheme: http
  109. * :path: /
  110. * :authority: www.example.com
  111. * cache-control: no-cache
  112. */
  113. ubyte[12] eexpected = [0x82, 0x86, 0x84, 0xbe, 0x58, 0x86, 0xa8, 0xeb, 0x10, 0x64, 0x9c, 0xbf];
  114. auto bbres = appender!(ubyte[]);
  115. block.encodeHPACK(bbres, table, true);
  116. assert(bbres.data == eexpected);
  117. }
  118. /// DECODER
  119. unittest {
  120. //// Following examples can be found in Appendix C of the HPACK RFC
  121. import std.experimental.allocator;
  122. import std.experimental.allocator.gc_allocator;
  123. auto table = IndexingTable(4096);
  124. scope alloc = new RegionListAllocator!(shared(GCAllocator), false)(1024, GCAllocator.instance);
  125. /** 1. Literal header field w. indexing (raw)
  126. * custom-key: custom-header
  127. */
  128. immutable(ubyte)[] block = [0x40, 0x0a, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x6b, 0x65, 0x79,
  129. 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72];
  130. //auto decoder = HeaderDecoder!(ubyte[])(block, table);
  131. auto dec1 = appender!(HTTP2HeaderTableField[]);
  132. block.decodeHPACK(dec1, table, alloc);
  133. assert(dec1.data.front.name == "custom-key" && dec1.data.front.value == "custom-header");
  134. // check entries to be inserted in the indexing table (dynamic)
  135. assert(dec1.data.front.index);
  136. /** 1bis. Literal header field w. indexing (huffman encoded)
  137. * :authority: www.example.com
  138. */
  139. block = [0x41, 0x8c, 0xf1, 0xe3, 0xc2, 0xe5, 0xf2, 0x3a, 0x6b, 0xa0, 0xab, 0x90, 0xf4, 0xff];
  140. auto dec1b = appender!(HTTP2HeaderTableField[]);
  141. block.decodeHPACK(dec1b, table, alloc);
  142. assert(dec1b.data.front.name == ":authority" && dec1b.data.front.value == "www.example.com");
  143. assert(dec1b.data.front.index);
  144. /** 2. Literal header field without indexing (raw)
  145. * :path: /sample/path
  146. */
  147. block = [0x04, 0x0c, 0x2f, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2f, 0x70, 0x61, 0x74, 0x68];
  148. auto dec2 = appender!(HTTP2HeaderTableField[]);
  149. block.decodeHPACK(dec2, table, alloc);
  150. assert(dec2.data.front.name == ":path" && dec2.data.front.value == "/sample/path");
  151. /** 3. Literal header field never indexed (raw)
  152. * password: secret
  153. */
  154. block = [0x10, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x06, 0x73, 0x65,
  155. 0x63, 0x72, 0x65, 0x74];
  156. auto dec3 = appender!(HTTP2HeaderTableField[]);
  157. block.decodeHPACK(dec3, table, alloc);
  158. assert(dec3.data.front.name == "password" && dec3.data.front.value == "secret");
  159. assert(dec3.data.front.neverIndex);
  160. /** 4. Indexed header field (integer)
  161. * :method: GET
  162. */
  163. import vibe.http.common;
  164. block = [0x82];
  165. auto dec4 = appender!(HTTP2HeaderTableField[]);
  166. block.decodeHPACK(dec4, table, alloc);
  167. assert(dec4.data.front.name == ":method" && dec4.data.front.value == HTTPMethod.GET);
  168. /** 5. Full request without huffman encoding
  169. * :method: GET
  170. * :scheme: http
  171. * :path: /
  172. * :authority: www.example.com
  173. * cache-control: no-cache
  174. */
  175. block = [0x82, 0x86, 0x84, 0xbe, 0x58, 0x08, 0x6e, 0x6f, 0x2d, 0x63, 0x61, 0x63, 0x68, 0x65];
  176. table.insert(HTTP2HeaderTableField(":authority", "www.example.com"));
  177. auto decR1 = appender!(HTTP2HeaderTableField[]);
  178. block.decodeHPACK(decR1, table, alloc);
  179. HTTP2HeaderTableField[] expected = [
  180. HTTP2HeaderTableField(":method", HTTPMethod.GET),
  181. HTTP2HeaderTableField(":scheme", "http"),
  182. HTTP2HeaderTableField(":path", "/"),
  183. HTTP2HeaderTableField(":authority", "www.example.com"),
  184. HTTP2HeaderTableField("cache-control", "no-cache")];
  185. foreach(i,h; decR1.data.enumerate(0)) {
  186. assert(h == expected[i]);
  187. }
  188. /** 5. Full request with huffman encoding
  189. * :method: GET
  190. * :scheme: http
  191. * :path: /
  192. * :authority: www.example.com
  193. * cache-control: no-cache
  194. */
  195. block = [0x82, 0x86, 0x84, 0xbe, 0x58, 0x86, 0xa8, 0xeb, 0x10, 0x64, 0x9c,0xbf];
  196. auto decR2 = appender!(HTTP2HeaderTableField[]);
  197. block.decodeHPACK(decR2, table, alloc);
  198. foreach(i,h; decR2.data.enumerate(0)) {
  199. assert(h == expected[i]);
  200. }
  201. /** Cookie header
  202. * cookie: filter=downloading
  203. */
  204. auto ckexp = HTTP2HeaderTableField("cookie", "filter=downloading");
  205. block = [96, 141, 148, 212, 36, 182, 65, 33, 252, 85, 65, 199, 33, 170, 155];
  206. auto ckdec = appender!(HTTP2HeaderTableField[]);
  207. block.decodeHPACK(ckdec, table, alloc);
  208. assert(ckdec.data.front == ckexp);
  209. }
  210. /// Mallocator
  211. unittest {
  212. import std.experimental.allocator;
  213. import std.experimental.allocator.mallocator;
  214. import std.experimental.allocator.gc_allocator;
  215. auto table = IndexingTable(4096);
  216. /** 1bis. Literal header field w. indexing (huffman encoded)
  217. * :authority: www.example.com
  218. */
  219. immutable(ubyte)[] block = [0x41, 0x8c, 0xf1, 0xe3, 0xc2, 0xe5, 0xf2, 0x3a, 0x6b, 0xa0, 0xab, 0x90, 0xf4, 0xff];
  220. scope alloc = new RegionListAllocator!(shared(Mallocator), false)(1024, Mallocator.instance);
  221. auto dec1b = appender!(HTTP2HeaderTableField[]);
  222. block.decodeHPACK(dec1b, table, alloc);
  223. assert(dec1b.data.front.name == ":authority" && dec1b.data.front.value == "www.example.com");
  224. assert(dec1b.data.front.index);
  225. }