LCOV - code coverage report
Current view: top level - waltz/h2 - fd_hpack.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 181 0.0 %
Date: 2026-10-22 18:10:43 Functions: 0 6 0.0 %

          Line data    Source code
       1             : #include "fd_hpack.h"
       2             : #include "fd_h2_base.h"
       3             : #include "fd_hpack_private.h"
       4             : #include "nghttp2_hd_huffman.h"
       5             : #include "../../util/log/fd_log.h"
       6             : 
       7             : fd_hpack_static_entry_t const
       8             : fd_hpack_static_table[ 62 ] = {
       9             :   [  1 ] = { ":authority",                       10,  0 },
      10             :   [  2 ] = { ":method"         "GET",             7,  3 },
      11             :   [  3 ] = { ":method"         "POST",            7,  4 },
      12             :   [  4 ] = { ":path"           "/",               5,  1 },
      13             :   [  5 ] = { ":path"           "/index.html",     5, 11 },
      14             :   [  6 ] = { ":scheme"         "http",            7,  4 },
      15             :   [  7 ] = { ":scheme"         "https",           7,  5 },
      16             :   [  8 ] = { ":status"         "200",             7,  3 },
      17             :   [  9 ] = { ":status"         "204",             7,  3 },
      18             :   [ 10 ] = { ":status"         "206",             7,  3 },
      19             :   [ 11 ] = { ":status"         "304",             7,  3 },
      20             :   [ 12 ] = { ":status"         "400",             7,  3 },
      21             :   [ 13 ] = { ":status"         "404",             7,  3 },
      22             :   [ 14 ] = { ":status"         "500",             7,  3 },
      23             :   [ 15 ] = { "accept-charset",                   14,  0 },
      24             :   [ 16 ] = { "accept-encoding" "gzip, deflate",  15, 13 },
      25             :   [ 17 ] = { "accept-language",                  15,  0 },
      26             :   [ 18 ] = { "accept-ranges",                    13,  0 },
      27             :   [ 19 ] = { "accept",                            6,  0 },
      28             :   [ 20 ] = { "access-control-allow-origin",      27,  0 },
      29             :   [ 21 ] = { "age",                               3,  0 },
      30             :   [ 22 ] = { "allow",                             5,  0 },
      31             :   [ 23 ] = { "authorization",                    13,  0 },
      32             :   [ 24 ] = { "cache-control",                    13,  0 },
      33             :   [ 25 ] = { "content-disposition",              19,  0 },
      34             :   [ 26 ] = { "content-encoding",                 16,  0 },
      35             :   [ 27 ] = { "content-language",                 16,  0 },
      36             :   [ 28 ] = { "content-length",                   14,  0 },
      37             :   [ 29 ] = { "content-location",                 16,  0 },
      38             :   [ 30 ] = { "content-range",                    13,  0 },
      39             :   [ 31 ] = { "content-type",                     12,  0 },
      40             :   [ 32 ] = { "cookie",                            6,  0 },
      41             :   [ 33 ] = { "date",                              4,  0 },
      42             :   [ 34 ] = { "etag",                              4,  0 },
      43             :   [ 35 ] = { "expect",                            6,  0 },
      44             :   [ 36 ] = { "expires",                           7,  0 },
      45             :   [ 37 ] = { "from",                              4,  0 },
      46             :   [ 38 ] = { "host",                              4,  0 },
      47             :   [ 39 ] = { "if-match",                          8,  0 },
      48             :   [ 40 ] = { "if-modified-since",                17,  0 },
      49             :   [ 41 ] = { "if-none-match",                    13,  0 },
      50             :   [ 42 ] = { "if-range",                          8,  0 },
      51             :   [ 43 ] = { "if-unmodified-since",              19,  0 },
      52             :   [ 44 ] = { "last-modified",                    13,  0 },
      53             :   [ 45 ] = { "link",                              4,  0 },
      54             :   [ 46 ] = { "location",                          8,  0 },
      55             :   [ 47 ] = { "max-forwards",                     12,  0 },
      56             :   [ 48 ] = { "proxy-authenticate",               18,  0 },
      57             :   [ 49 ] = { "proxy-authorization",              19,  0 },
      58             :   [ 50 ] = { "range",                             5,  0 },
      59             :   [ 51 ] = { "referer",                           7,  0 },
      60             :   [ 52 ] = { "refresh",                           7,  0 },
      61             :   [ 53 ] = { "retry-after",                      11,  0 },
      62             :   [ 54 ] = { "server",                            6,  0 },
      63             :   [ 55 ] = { "set-cookie",                       10,  0 },
      64             :   [ 56 ] = { "strict-transport-security",        25,  0 },
      65             :   [ 57 ] = { "transfer-encoding",                17,  0 },
      66             :   [ 58 ] = { "user-agent",                       10,  0 },
      67             :   [ 59 ] = { "vary",                              4,  0 },
      68             :   [ 60 ] = { "via",                               3,  0 },
      69             :   [ 61 ] = { "www-authenticate",                 16,  0 }
      70             : };
      71             : 
      72             : fd_hpack_rd_t *
      73             : fd_hpack_rd_init( fd_hpack_rd_t * rd,
      74             :                   uchar const *   src,
      75           0 :                   ulong           srcsz ) {
      76           0 :   *rd = (fd_hpack_rd_t) {
      77           0 :     .src     = src,
      78           0 :     .src_end = src+srcsz
      79           0 :   };
      80             :   /* Skip Dynamic Table Size Updates to zero.  Others fail in rd_next. */
      81           0 :   while( rd->src<rd->src_end && rd->src[0]==0x20 ) rd->src++;
      82           0 :   return rd;
      83           0 : }
      84             : 
      85             : /* fd_hpack_rd_indexed selects a header from HPACK dictionaries.
      86             :    Currently, only supports the static table.  (Pretends that the
      87             :    dynamic table size is 0). */
      88             : 
      89             : static uint
      90             : fd_hpack_rd_indexed( fd_h2_hdr_t * hdr,
      91           0 :                      ulong         idx ) {
      92           0 :   if( FD_UNLIKELY( idx==0 || idx>61 ) ) return FD_H2_ERR_COMPRESSION;
      93           0 :   fd_hpack_static_entry_t const * entry = &fd_hpack_static_table[ idx ];
      94           0 :   *hdr = (fd_h2_hdr_t) {
      95           0 :     .name      = entry->entry,
      96           0 :     .name_len  = entry->name_len,
      97           0 :     .value     = entry->entry + entry->name_len,
      98           0 :     .value_len = entry->value_len,
      99           0 :     .hint      = (ushort)idx | FD_H2_HDR_HINT_NAME_INDEXED,
     100           0 :   };
     101           0 :   return FD_H2_SUCCESS;
     102           0 : }
     103             : 
     104             : static uint
     105             : fd_hpack_rd_next_raw( fd_hpack_rd_t * rd,
     106           0 :                       fd_h2_hdr_t *   hdr ) {
     107           0 :   uchar const * end = rd->src_end;
     108           0 :   if( FD_UNLIKELY( rd->src >= end ) ) FD_LOG_CRIT(( "fd_hpack_rd_next called out of bounds" ));
     109             : 
     110           0 :   uint b0 = *(rd->src++);
     111             : 
     112           0 :   if( (b0&0xc0)==0x80 ) {
     113             :     /* name indexed, value indexed, index in [0,63], varint sz 0 */
     114           0 :     uint err = fd_hpack_rd_indexed( hdr, b0&0x7f );
     115           0 :     hdr->hint |= FD_H2_HDR_HINT_VALUE_INDEXED;
     116           0 :     return err;
     117           0 :   }
     118             : 
     119           0 :   if( b0==0x40 || b0==0x00 || b0==0x10 ) {
     120             :     /* name literal, value literal */
     121           0 :     if( FD_UNLIKELY( rd->src+2 > end ) ) return FD_H2_ERR_COMPRESSION;
     122             : 
     123           0 :     uint  name_word = *(rd->src++);
     124           0 :     ulong name_len  = fd_hpack_rd_varint( rd, name_word, 0x7f );
     125           0 :     if( FD_UNLIKELY( name_len==ULONG_MAX     ) ) return FD_H2_ERR_COMPRESSION;
     126           0 :     if( FD_UNLIKELY( rd->src+name_len >= end ) ) return FD_H2_ERR_COMPRESSION;
     127           0 :     uchar const * name_p = rd->src;
     128           0 :     rd->src += name_len;
     129             : 
     130           0 :     uint  value_word = *(rd->src++);
     131           0 :     ulong value_len  = fd_hpack_rd_varint( rd, value_word, 0x7f );
     132           0 :     if( FD_UNLIKELY( value_len==ULONG_MAX    ) ) return FD_H2_ERR_COMPRESSION;
     133           0 :     if( FD_UNLIKELY( rd->src+value_len > end ) ) return FD_H2_ERR_COMPRESSION;
     134           0 :     uchar const * value_p = rd->src;
     135           0 :     rd->src += value_len;
     136             : 
     137           0 :     hdr->name      = (char const *)name_p;
     138           0 :     hdr->name_len  = (ushort)name_len;
     139           0 :     hdr->value     = (char const *)value_p;
     140           0 :     hdr->value_len = (uint)value_len;
     141           0 :     hdr->hint      = fd_ushort_if( name_word&0x80,  FD_H2_HDR_HINT_NAME_HUFFMAN,  0 ) |
     142           0 :                      fd_ushort_if( value_word&0x80, FD_H2_HDR_HINT_VALUE_HUFFMAN, 0 );
     143           0 :     return FD_H2_SUCCESS;
     144           0 :   }
     145             : 
     146           0 :   if( (b0&0xc0)==0x40 || (b0&0xf0)==0x00 || (b0&0xf0)==0x10 ) {
     147             :     /* name indexed, value literal */
     148           0 :     uint  name_mask = (b0&0xc0)==0x40 ? 0x3f : 0x0f;
     149           0 :     ulong name_idx  = fd_hpack_rd_varint( rd, b0, name_mask );
     150             : 
     151           0 :     if( FD_UNLIKELY( rd->src >= end ) ) return FD_H2_ERR_COMPRESSION;
     152           0 :     uint  value_word = *(rd->src++);
     153           0 :     ulong value_len  = fd_hpack_rd_varint( rd, value_word, 0x7f );
     154           0 :     if( FD_UNLIKELY( value_len==ULONG_MAX    ) ) return FD_H2_ERR_COMPRESSION;
     155           0 :     if( FD_UNLIKELY( rd->src+value_len > end ) ) return FD_H2_ERR_COMPRESSION;
     156           0 :     uchar const * value_p = rd->src;
     157           0 :     rd->src += value_len;
     158             : 
     159           0 :     uint err = fd_hpack_rd_indexed( hdr, name_idx );
     160           0 :     if( FD_UNLIKELY( err ) ) return FD_H2_ERR_COMPRESSION;
     161           0 :     hdr->value     = (char const *)value_p;
     162           0 :     hdr->value_len = (uint)value_len;
     163           0 :     hdr->hint     |= fd_ushort_if( value_word&0x80, FD_H2_HDR_HINT_VALUE_HUFFMAN, 0 );
     164           0 :     return FD_H2_SUCCESS;
     165           0 :   }
     166             : 
     167           0 :   if( FD_UNLIKELY( (b0&0xc0)==0xc0 ) ) {
     168             :     /* name indexed, value indexed, index >=128 */
     169           0 :     ulong idx = fd_hpack_rd_varint( rd, b0, 0x7f ); /* may fail */
     170           0 :     return fd_hpack_rd_indexed( hdr, idx );
     171           0 :   }
     172             : 
     173             :   /* FIXME slow */
     174             :   /* Skip over Dynamic Table Size Updates */
     175           0 :   while( FD_LIKELY( rd->src < end ) ) {
     176           0 :     b0 = rd->src[0];
     177           0 :     if( FD_UNLIKELY( (b0&0xe0)==0x20 ) ) {
     178           0 :       ulong max_sz = fd_hpack_rd_varint( rd, b0, 0x1f );
     179           0 :       if( FD_UNLIKELY( max_sz!=0UL ) ) return FD_H2_ERR_COMPRESSION;
     180           0 :       rd->src++;
     181           0 :     } else {
     182           0 :       break;
     183           0 :     }
     184           0 :   }
     185             : 
     186             :   /* Unknown HPACK instruction */
     187           0 :   return FD_H2_ERR_COMPRESSION;
     188           0 : }
     189             : 
     190             : /* fd_hpack_decoded_sz_max returns an upper bound for the number of
     191             :    decoded bytes given an arbitrary HPACK Huffman coding of enc_sz
     192             :    bytes.  The smallest HPACK symbol is 5 bits large.  Therefore, the
     193             :    true bound is closer to (enc_sz*8)/5.  To defend against possible
     194             :    bugs in huff_decode_table, we use a more conservative estimate,
     195             :    namely the greatest amount of bytes that nghttp2_hd_huff_decode can
     196             :    produce regardless of the content of huff_decode_table. */
     197             : 
     198             : static inline ulong
     199           0 : fd_hpack_decoded_sz_max( ulong enc_sz ) {
     200           0 :   return enc_sz*2UL;
     201           0 : }
     202             : 
     203             : uint
     204             : fd_hpack_rd_next( fd_hpack_rd_t * hpack_rd,
     205             :                   fd_h2_hdr_t *   hdr,
     206             :                   uchar **        scratch,
     207           0 :                   uchar *         scratch_end ) {
     208           0 :   uint err = fd_hpack_rd_next_raw( hpack_rd, hdr );
     209           0 :   if( FD_UNLIKELY( err ) ) return err;
     210             : 
     211           0 :   uchar * scratch_ = *scratch;
     212             : 
     213           0 :   if( hdr->hint & FD_H2_HDR_HINT_NAME_HUFFMAN ) {
     214           0 :     if( FD_UNLIKELY( scratch_+fd_hpack_decoded_sz_max( hdr->name_len )>scratch_end ) ) return FD_H2_ERR_COMPRESSION;
     215           0 :     nghttp2_hd_huff_decode_context ctx[1];
     216           0 :     nghttp2_hd_huff_decode_context_init( ctx );
     217           0 :     nghttp2_buf buf = { .last = scratch_ };
     218           0 :     if( FD_UNLIKELY( nghttp2_hd_huff_decode( ctx, &buf, (uchar const *)hdr->name, hdr->name_len, 1 )<0 ) ) return FD_H2_ERR_COMPRESSION;
     219           0 :     hdr->name     = (char const *)scratch_;
     220           0 :     hdr->name_len = (ushort)( buf.last-scratch_ );
     221           0 :     scratch_      = buf.last;
     222           0 :   }
     223             : 
     224           0 :   if( hdr->hint & FD_H2_HDR_HINT_VALUE_HUFFMAN ) {
     225           0 :     if( FD_UNLIKELY( scratch_+fd_hpack_decoded_sz_max( hdr->value_len )>scratch_end ) ) return FD_H2_ERR_COMPRESSION;
     226           0 :     nghttp2_hd_huff_decode_context ctx[1];
     227           0 :     nghttp2_hd_huff_decode_context_init( ctx );
     228           0 :     nghttp2_buf buf = { .last = scratch_ };
     229           0 :     if( FD_UNLIKELY( nghttp2_hd_huff_decode( ctx, &buf, (uchar const *)hdr->value, hdr->value_len, 1 )<0 ) ) return FD_H2_ERR_COMPRESSION;
     230           0 :     hdr->value     = (char const *)scratch_;
     231           0 :     hdr->value_len = (uint)( buf.last-scratch_ );
     232           0 :     scratch_       = buf.last;
     233           0 :   }
     234             : 
     235           0 :   *scratch = scratch_;
     236           0 :   hdr->hint &= (ushort)~FD_H2_HDR_HINT_HUFFMAN;
     237           0 :   return FD_H2_SUCCESS;
     238           0 : }
     239             : 
     240             : /* Incremental decoding for fields that need not be retained.  Only one
     241             :    integer or one encoded string is pending at a time.  FIELD, NAME_LEN
     242             :    and VALUE_LEN each read one integer (name index or string length). */
     243             : 
     244           0 : #define FD_HPACK_SKIP_FIELD     0U
     245             : #define FD_HPACK_SKIP_NAME_LEN  1U
     246           0 : #define FD_HPACK_SKIP_VALUE_LEN 2U
     247           0 : #define FD_HPACK_SKIP_NAME      3U
     248             : #define FD_HPACK_SKIP_VALUE     4U
     249           0 : #define FD_HPACK_SKIP_ERROR     5U
     250             : 
     251             : /* fd_hpack_skip_next[ phase ][ integer==0 ] follows a completed integer */
     252             : static uchar const fd_hpack_skip_next[ 3 ][ 2 ] = {
     253             :   { FD_HPACK_SKIP_VALUE_LEN, FD_HPACK_SKIP_NAME_LEN  },
     254             :   { FD_HPACK_SKIP_NAME,      FD_HPACK_SKIP_VALUE_LEN },
     255             :   { FD_HPACK_SKIP_VALUE,     FD_HPACK_SKIP_FIELD     }
     256             : };
     257             : 
     258             : uint
     259             : fd_hpack_skip_feed( fd_hpack_skip_t * skip,
     260             :                     uchar const *     src,
     261             :                     ulong             srcsz,
     262           0 :                     int               fin ) {
     263           0 :   if( FD_UNLIKELY( skip->phase==FD_HPACK_SKIP_ERROR ) ) return FD_H2_ERR_COMPRESSION;
     264           0 :   while( srcsz ) {
     265           0 :     if( skip->phase>=FD_HPACK_SKIP_NAME ) {
     266           0 :       ulong chunk_sz = fd_ulong_min( srcsz, skip->integer );
     267           0 :       if( skip->huff_state ) {
     268             :         /* The decoder emits at most two symbols per encoded octet.
     269             :            Reuse a small sink while retaining only its decoding state. */
     270           0 :         uchar sink[ 256 ];
     271           0 :         nghttp2_hd_huff_decode_context ctx = { .fstate = skip->huff_state };
     272           0 :         chunk_sz = fd_ulong_min( chunk_sz, sizeof(sink)/2UL );
     273           0 :         nghttp2_buf buf = { .last = sink };
     274           0 :         if( FD_UNLIKELY( nghttp2_hd_huff_decode( &ctx, &buf, src, chunk_sz, chunk_sz==skip->integer )<0 ) ) goto fail;
     275             :         /* A decoded EOS reaches the terminal failure state even before
     276             :            the announced encoded string length has been consumed. */
     277           0 :         if( FD_UNLIKELY( (ctx.fstate&0x1ffU)==256U ) ) goto fail;
     278           0 :         skip->huff_state = ctx.fstate;
     279           0 :       }
     280           0 :       src           += chunk_sz;
     281           0 :       srcsz         -= chunk_sz;
     282           0 :       skip->integer -= chunk_sz;
     283           0 :       if( !skip->integer ) skip->phase = skip->phase==FD_HPACK_SKIP_NAME ? FD_HPACK_SKIP_VALUE_LEN : FD_HPACK_SKIP_FIELD;
     284           0 :       continue;
     285           0 :     }
     286             : 
     287           0 :     uint byte = *src++;
     288           0 :     srcsz--;
     289           0 :     if( skip->int_shift ) {
     290             :       /* Continuation octet.  At most eight, so the shift is at most 49. */
     291           0 :       skip->integer += (ulong)(byte&0x7fU) << (skip->int_shift-7U);
     292           0 :       if( FD_UNLIKELY( skip->phase==FD_HPACK_SKIP_FIELD && skip->integer>61UL ) ) goto fail;
     293           0 :       if( byte&0x80U ) {
     294           0 :         if( FD_UNLIKELY( skip->int_shift==56U ) ) goto fail;
     295           0 :         skip->int_shift = (uchar)( skip->int_shift+7U );
     296           0 :         continue;
     297           0 :       }
     298           0 :     } else {
     299           0 :       uint mask = 0x7fU;
     300           0 :       if( skip->phase==FD_HPACK_SKIP_FIELD ) {
     301           0 :         if( (byte&0xe0U)==0x20U ) {
     302           0 :           if( FD_UNLIKELY( byte!=0x20U || skip->field_seen ) ) goto fail;
     303           0 :           continue;
     304           0 :         }
     305           0 :         skip->field_seen = 1U;
     306             :         /* Every supported indexed field fits in its seven-bit prefix. */
     307           0 :         if( byte&0x80U ) {
     308           0 :           if( FD_UNLIKELY( !(byte&0x7fU) || (byte&0x7fU)>61U ) ) goto fail;
     309           0 :           continue;
     310           0 :         }
     311           0 :         mask = byte&0x40U ? 0x3fU : 0x0fU;
     312           0 :         if( FD_UNLIKELY( (byte&mask)>61U ) ) goto fail;
     313           0 :       } else {
     314             :         /* All Huffman DFA states are nonzero, leaving zero for raw data. */
     315           0 :         skip->huff_state = byte&0x80U ? NGHTTP2_HUFF_ACCEPTED : 0U;
     316           0 :       }
     317           0 :       skip->integer = byte&mask;
     318           0 :       if( skip->integer==mask ) {
     319           0 :         skip->int_shift = 7U;
     320           0 :         continue;
     321           0 :       }
     322           0 :     }
     323           0 :     skip->int_shift = 0U;
     324           0 :     skip->phase     = fd_hpack_skip_next[ skip->phase ][ !skip->integer ];
     325           0 :   }
     326           0 :   if( FD_UNLIKELY( fin && ( skip->phase || skip->int_shift ) ) ) goto fail;
     327           0 :   return FD_H2_SUCCESS;
     328             : 
     329           0 : fail:
     330           0 :   skip->phase = FD_HPACK_SKIP_ERROR;
     331           0 :   return FD_H2_ERR_COMPRESSION;
     332           0 : }

Generated by: LCOV version 1.14