Coverage Report

Created: 2026-04-29 06:53

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/mhd2/src/mhd2/h2/h2_req_fields.c
Line
Count
Source
1
/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */
2
/*
3
  This file is part of GNU libmicrohttpd.
4
  Copyright (C) 2025 Evgeny Grin (Karlson2k)
5
6
  GNU libmicrohttpd is free software; you can redistribute it and/or
7
  modify it under the terms of the GNU Lesser General Public
8
  License as published by the Free Software Foundation; either
9
  version 2.1 of the License, or (at your option) any later version.
10
11
  GNU libmicrohttpd is distributed in the hope that it will be useful,
12
  but WITHOUT ANY WARRANTY; without even the implied warranty of
13
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
  Lesser General Public License for more details.
15
16
  Alternatively, you can redistribute GNU libmicrohttpd and/or
17
  modify it under the terms of the GNU General Public License as
18
  published by the Free Software Foundation; either version 2 of
19
  the License, or (at your option) any later version, together
20
  with the eCos exception, as follows:
21
22
    As a special exception, if other files instantiate templates or
23
    use macros or inline functions from this file, or you compile this
24
    file and link it with other works to produce a work based on this
25
    file, this file does not by itself cause the resulting work to be
26
    covered by the GNU General Public License. However the source code
27
    for this file must still be made available in accordance with
28
    section (3) of the GNU General Public License v2.
29
30
    This exception does not invalidate any other reasons why a work
31
    based on this file might be covered by the GNU General Public
32
    License.
33
34
  You should have received copies of the GNU Lesser General Public
35
  License and the GNU General Public License along with this library;
36
  if not, see <https://www.gnu.org/licenses/>.
37
*/
38
39
/**
40
 * @file src/mhd2/h2/h2_dec_fields.c
41
 * @brief  Implementation of HTTP/2 request fields functions
42
 * @author Karlson2k (Evgeny Grin)
43
 */
44
45
#include "mhd_sys_options.h"
46
47
#include "sys_bool_type.h"
48
#include "sys_base_types.h"
49
50
#include <string.h>
51
52
#include "mhd_assert.h"
53
#include "mhd_unreachable.h"
54
#include "mhd_assume.h"
55
56
#include "mhd_constexpr.h"
57
#include "mhd_buffer.h"
58
#include "mhd_str_types.h"
59
#include "mhd_str_macros.h"
60
61
#include "mhd_connection.h"
62
#include "h2_conn_data.h"
63
64
#include "h2_stream_data.h"
65
66
#include "mhd_str.h"
67
68
#include "stream_process_request.h"
69
70
#include "h2_req_item_kinds.h"
71
#include "h2_req_item_struct.h"
72
#include "h2_req_items_funcs.h"
73
74
#include "hpack/mhd_hpack_codec.h"
75
76
#include "h2_proc_conn.h"
77
#include "h2_conn_streams.h"
78
79
#include "h2_req_fields.h"
80
81
MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_
82
MHD_FN_PAR_INOUT_ (1) MHD_FN_PAR_IN_ (2)
83
MHD_FN_PAR_INOUT_ (4) MHD_FN_PAR_OUT_ (5) enum mhd_H2DecFieldsResult
84
mhd_h2_req_fields_decode (struct mhd_HpackDecContext *restrict hk_dec,
85
                          const struct mhd_Buffer *restrict enc_data,
86
                          bool are_trailers,
87
                          struct mhd_H2ReqItemsBlock *restrict ib,
88
                          size_t *restrict left_unprocessed)
89
0
{
90
0
  mhd_constexpr unsigned int max_no_fields = 8u;
91
0
  const enum mhd_H2RequestItemKind field_kind =
92
0
    are_trailers ? mhd_H2_RIK_TRAILER : mhd_H2_RIK_HEADER;
93
0
  size_t pos;
94
0
  unsigned int no_fields;
95
0
  enum mhd_H2DecFieldsResult ret;
96
97
0
  pos = 0u;
98
0
  no_fields = 0u;
99
0
  ret = mhd_H2_DEC_FIELDS_OK;
100
101
0
  while (pos < enc_data->size)
102
0
  {
103
0
    size_t name_len;
104
0
    size_t val_len;
105
0
    size_t pos_incr;
106
0
    enum mhd_HpackDecResult res;
107
0
    struct mhd_Buffer buff;
108
109
0
    mhd_assert (mhd_H2_DEC_FIELDS_OK == ret);
110
111
0
    if (! mhd_h2_items_get_buff_new_item (ib,
112
0
                                          &buff))
113
0
      return mhd_H2_DEC_FIELDS_NO_SPACE;
114
115
0
    res = mhd_hpack_dec_data (hk_dec,
116
0
                              enc_data->size - pos,
117
0
                              (const uint8_t *) enc_data->data + pos,
118
0
                              buff.size,
119
0
                              buff.data,
120
0
                              &name_len,
121
0
                              &val_len,
122
0
                              &pos_incr);
123
124
0
    if (! mhd_HPACK_DEC_RES_IS_ERR (res))
125
0
    {
126
0
      mhd_assert (0u != pos_incr);
127
0
      pos += pos_incr;
128
0
    }
129
130
0
    switch (res)
131
0
    {
132
0
    case mhd_HPACK_DEC_RES_NO_NEW_FIELD:
133
0
      if (max_no_fields < ++no_fields)
134
0
      {
135
0
        ret = mhd_H2_DEC_FIELDS_PROT_ABUSE;
136
0
        break;
137
0
      }
138
0
      mhd_h2_items_cancel_new_item_buff (ib);
139
0
      continue;
140
0
    case mhd_HPACK_DEC_RES_NEW_FIELD:
141
0
      mhd_assert (buff.size >= (name_len + val_len + 2u));
142
0
      mhd_assert (0 == buff.data[name_len]);
143
0
      mhd_assert (0 == buff.data[name_len + 1 + val_len]);
144
0
      mhd_h2_items_add_new_item_buff (ib,
145
0
                                      name_len,
146
0
                                      val_len,
147
0
                                      (':' == buff.data[0]) ?
148
0
                                      mhd_H2_RIK_PSEUDOHEADER : field_kind);
149
0
      continue;
150
0
    case mhd_HPACK_DEC_RES_INCOMPLETE:
151
0
      mhd_assert (mhd_H2_DEC_FIELDS_OK == ret);
152
0
      break;
153
0
    case mhd_HPACK_DEC_RES_ALLOC_ERR:
154
0
      ret = mhd_H2_DEC_FIELDS_INT_ERR;
155
0
      break;
156
0
    case mhd_HPACK_DEC_RES_BUFFER_TOO_SMALL: // TODO: support "minimal" decoding for decoder state updates
157
0
    case mhd_HPACK_DEC_RES_STRING_TOO_LONG:
158
0
      ret = mhd_H2_DEC_FIELDS_NO_SPACE;
159
0
      break;
160
0
    case mhd_HPACK_DEC_RES_NUMBER_TOO_LONG:
161
0
    case mhd_HPACK_DEC_RES_DYN_SIZE_UPD_TOO_LARGE:
162
0
      ret = mhd_H2_DEC_FIELDS_PROT_ABUSE;
163
0
      break;
164
0
    case mhd_HPACK_DEC_RES_DYN_SIZE_UPD_MISSING:
165
0
    case mhd_HPACK_DEC_RES_HUFFMAN_ERR:
166
0
    case mhd_HPACK_DEC_RES_HPACK_BAD_IDX:
167
0
    case mhd_HPACK_DEC_RES_HPACK_ERR:
168
0
      ret = mhd_H2_DEC_FIELDS_BROKEN_DATA;
169
0
      break;
170
0
    case mhd_HPACK_DEC_RES_INTERNAL_ERR:
171
0
    default:
172
0
      mhd_UNREACHABLE_D ("Impossible value");
173
0
      ret = mhd_H2_DEC_FIELDS_INT_ERR;
174
0
      break;
175
0
    }
176
0
    mhd_h2_items_cancel_new_item_buff (ib);
177
0
    break; /* Break the loop */
178
0
  }
179
180
0
  mhd_assert (pos <= enc_data->size);
181
0
  *left_unprocessed = enc_data->size - pos;
182
183
0
  return ret;
184
0
}
185
186
187
static inline MHD_FN_PAR_NONNULL_ALL_
188
MHD_FN_PAR_INOUT_ (1) bool
189
req_validate_fields_chars (struct mhd_H2Stream *restrict s)
190
0
{
191
  // TODO: implement checking all field chars for validity, RFC 9113 Section 8.2.1
192
0
  (void) s;
193
0
  return true;
194
0
}
195
196
197
static inline MHD_FN_PAR_NONNULL_ALL_
198
MHD_FN_PAR_INOUT_ (1)
199
MHD_FN_PAR_OUT_ (2) bool
200
req_pseudoheaders_preprocess (struct mhd_H2Stream *restrict s,
201
                              size_t *pos)
202
0
{
203
0
  static const struct MHD_String ph_method = mhd_MSTR_INIT (":method");
204
0
  static const struct MHD_String ph_scheme = mhd_MSTR_INIT (":scheme");
205
0
  static const struct MHD_String ph_authority = mhd_MSTR_INIT (":authority");
206
0
  static const struct MHD_String ph_path = mhd_MSTR_INIT (":path");
207
0
  struct mhd_H2ReqItemsBlock *const ib = s->c->h2.mem.req_ib;
208
0
  const char *buff = mhd_h2_items_get_strings_buffc (ib);
209
0
  bool have_method = false;
210
0
  bool have_scheme = false;
211
0
  bool have_authority = false;
212
0
  bool have_path = false;
213
214
0
  *pos = 0u;
215
0
  while (1)
216
0
  {
217
0
    const struct mhd_H2ReqItem *item;
218
0
    item = mhd_h2_items_get_item_nc (ib,
219
0
                                     *pos);
220
221
0
    if (NULL == item)
222
0
      break;
223
0
    else if (mhd_H2_RIK_PSEUDOHEADER != item->kind)
224
0
      break;
225
0
    else if ((item->name_len == ph_method.len) &&
226
0
             (0 == memcmp (buff + item->offset,
227
0
                           ph_method.cstr,
228
0
                           ph_method.len)))
229
0
    {
230
0
      if (have_method)
231
0
        return mhd_h2_stream_req_problem (s,
232
0
                                          mhd_H2_REQ_PRBLM_PSEUDOHDR_DUP);
233
0
      have_method = true;
234
0
      s->req.pos_method = *pos;
235
0
      s->req.method =
236
0
        mhd_parse_http_method (item->val_len,
237
0
                               buff + item->offset + ph_method.len + 1u);
238
0
    }
239
0
    else if ((item->name_len == ph_path.len) &&
240
0
             (0 == memcmp (buff + item->offset,
241
0
                           ph_path.cstr,
242
0
                           ph_path.len)))
243
0
    {
244
0
      if (have_path)
245
0
        return mhd_h2_stream_req_problem (s,
246
0
                                          mhd_H2_REQ_PRBLM_PSEUDOHDR_DUP);
247
0
      have_path = true;
248
0
      s->req.pos_path = *pos;
249
0
    }
250
0
    else if ((item->name_len == ph_authority.len) &&
251
0
             (0 == memcmp (buff + item->offset,
252
0
                           ph_authority.cstr,
253
0
                           ph_authority.len)))
254
0
    {
255
0
      if (have_authority)
256
0
        return mhd_h2_stream_req_problem (s,
257
0
                                          mhd_H2_REQ_PRBLM_PSEUDOHDR_DUP);
258
0
      have_authority = true;
259
0
      s->req.pos_authority = *pos;
260
0
    }
261
0
    else if ((item->name_len == ph_scheme.len) &&
262
0
             (0 == memcmp (buff + item->offset,
263
0
                           ph_scheme.cstr,
264
0
                           ph_scheme.len)))
265
0
    {
266
0
      if (have_scheme)
267
0
        return mhd_h2_stream_req_problem (s,
268
0
                                          mhd_H2_REQ_PRBLM_PSEUDOHDR_DUP);
269
0
      have_scheme = true;
270
0
    }
271
0
    mhd_assert (':' == buff[item->offset]);
272
0
    ++(*pos);
273
0
  }
274
275
0
  if (! have_method)
276
0
    return mhd_h2_stream_req_problem (s,
277
0
                                      mhd_H2_REQ_PRBLM_PSEUDOHDR_MISSING);
278
279
0
  if (mhd_HTTP_METHOD_CONNECT != s->req.method)
280
0
  {
281
0
    if (! have_path || ! have_scheme)
282
0
      return mhd_h2_stream_req_problem (s,
283
0
                                        mhd_H2_REQ_PRBLM_PSEUDOHDR_EXTRA);
284
0
  }
285
0
  else
286
0
  {
287
0
    if (have_path || have_scheme)
288
0
      return mhd_h2_stream_req_problem (s,
289
0
                                        mhd_H2_REQ_PRBLM_PSEUDOHDR_MISSING);
290
0
  }
291
292
0
  return true;
293
0
}
294
295
296
static inline MHD_FN_PAR_NONNULL_ALL_
297
MHD_FN_PAR_INOUT_ (1)
298
MHD_FN_PAR_OUT_ (2) bool
299
req_headers_preprocess (struct mhd_H2Stream *restrict s,
300
                        size_t *pos)
301
0
{
302
0
  static const struct MHD_String h_host = mhd_MSTR_INIT ("host");
303
0
  static const struct MHD_String h_cntn_len = mhd_MSTR_INIT ("content-length");
304
0
  struct mhd_H2ReqItemsBlock *const ib = s->c->h2.mem.req_ib;
305
0
  const char *buff = mhd_h2_items_get_strings_buffc (ib);
306
307
0
  while (1)
308
0
  {
309
0
    const struct mhd_H2ReqItem *item;
310
0
    item = mhd_h2_items_get_item_nc (ib,
311
0
                                     *pos);
312
313
0
    if (NULL == item)
314
0
      break;
315
0
    else if (mhd_H2_RIK_PSEUDOHEADER == item->kind)
316
0
      return mhd_h2_stream_req_problem (s,
317
0
                                        mhd_H2_REQ_PRBLM_PSEUDOHDR_AFTER_HDR);
318
0
    else if (mhd_H2_RIK_HEADER != item->kind)
319
0
      (void) 0; /* skip */
320
0
    else if ((item->name_len == h_cntn_len.len) &&
321
0
             (0 == memcmp (buff + item->offset,
322
0
                           h_cntn_len.cstr,
323
0
                           h_cntn_len.len)))
324
0
    {
325
0
      uint_fast64_t cntnt_len;
326
327
0
      if ((0u == item->val_len) ||
328
0
          (item->val_len !=
329
0
           mhd_str_to_uint64 (buff + item->offset + h_cntn_len.len + 1u,
330
0
                              &cntnt_len)))
331
0
        return mhd_h2_stream_req_problem (s,
332
0
                                          mhd_H2_REQ_PRBLM_CNTNT_LEN_WRONG);
333
334
0
      if ((MHD_SIZE_UNKNOWN != s->req.cntn_size) &&
335
0
          (s->req.cntn_size != cntnt_len))
336
0
        return mhd_h2_stream_req_problem (s,
337
0
                                          mhd_H2_REQ_PRBLM_CNTNT_LEN_WRONG);
338
0
      else
339
0
        s->req.cntn_size = cntnt_len;
340
0
    }
341
0
    else if ((item->name_len == h_host.len) &&
342
0
             (0 == memcmp (buff + item->offset,
343
0
                           h_host.cstr,
344
0
                           h_host.len)))
345
0
    {
346
0
      if (mhd_H2_REQ_ITEM_POS_INVALID == s->req.pos_authority)
347
0
        s->req.pos_authority = *pos;
348
0
      else
349
0
      {
350
0
        const struct mhd_H2ReqItem *item_auth =
351
0
          mhd_h2_items_get_item_nc (ib,
352
0
                                    s->req.pos_authority);
353
0
        mhd_assert (NULL != item_auth);
354
0
        if ((item_auth->val_len != item->val_len) ||
355
0
            (! mhd_str_equal_caseless_bin_n (buff + item->offset
356
0
                                             + item->name_len + 1u,
357
0
                                             buff + item_auth->offset
358
0
                                             + item_auth->name_len + 1u,
359
0
                                             item->val_len)))
360
0
          return
361
0
            mhd_h2_stream_req_problem (s,
362
0
                                       mhd_H2_REQ_PRBLM_HOST_HDR_WRONG_EXTRA);
363
0
      }
364
0
    }
365
366
0
    ++(*pos);
367
0
    mhd_assert ((mhd_H2_RIK_HEADER != item->kind) ||
368
0
                (':' != buff[item->offset]));
369
0
  }
370
371
0
  return true;
372
0
}
373
374
375
MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_
376
MHD_FN_PAR_INOUT_ (1) bool
377
mhd_h2_req_headers_preprocess (struct mhd_H2Stream *restrict s)
378
0
{
379
0
  size_t pos;
380
381
0
  mhd_assert (mhd_h2_items_debug_get_streamid (s->c->h2.mem.req_ib)
382
0
              == s->stream_id);
383
384
0
  if (! req_validate_fields_chars (s))
385
0
    return false;
386
387
0
  if (! req_pseudoheaders_preprocess (s,
388
0
                                      &pos))
389
0
    return false;
390
391
0
  mhd_assert (0u != pos);
392
0
  mhd_assert (mhd_HTTP_METHOD_NO_METHOD != s->req.method);
393
394
0
  if (! req_headers_preprocess (s,
395
0
                                &pos))
396
0
    return false;
397
398
0
  return true;
399
0
}
400
401
402
MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_
403
MHD_FN_PAR_INOUT_ (1) bool
404
mhd_h2_req_uri_parse (struct mhd_H2Stream *restrict s)
405
0
{
406
0
  struct mhd_H2ReqItemsBlock *const restrict ib = s->c->h2.mem.req_ib;
407
0
  char *const restrict buff = mhd_h2_items_get_strings_buff (ib);
408
0
  struct mhd_H2ReqItem *restrict item =
409
0
    mhd_h2_items_get_item_n (ib,
410
0
                             s->req.pos_path);
411
0
  const size_t path_start = (size_t) (item->offset + item->name_len + 1u);
412
0
  char *questn_mark;
413
414
0
  questn_mark = (char*) memchr (buff + path_start,
415
0
                                '?',
416
0
                                (size_t) item->val_len);
417
0
  if (NULL == questn_mark)
418
0
  {
419
0
    item->val_len =
420
0
      (uint_least32_t)
421
0
      mhd_str_dec_norm_uri_path ((size_t) item->val_len,
422
0
                                 buff + path_start);
423
0
  }
424
0
  else
425
0
  {
426
0
    const size_t path_len = (size_t) (questn_mark - (buff + path_start));
427
0
    const size_t uri_end = (size_t) (path_start + item->val_len);
428
0
    size_t i = path_start + path_len + 1u;
429
430
0
    mhd_assert (path_len < item->val_len);
431
432
0
    buff[path_start + path_len] = '\0'; /* Zero-terminate the path */
433
0
    item->val_len =
434
0
      (uint_least32_t)
435
0
      mhd_str_dec_norm_uri_path (path_len,
436
0
                                 buff + path_start);
437
438
0
    do
439
0
    {
440
0
      size_t name_start;
441
0
      size_t name_len;
442
0
      size_t value_start;
443
0
      size_t value_len;
444
445
0
      value_start = 0u;
446
0
      for (name_start = i; i < uri_end; ++i) /* Processing parameter */
447
0
      {
448
0
        if ('+' == buff[i])
449
0
          buff[i] = ' ';
450
0
        else if ('=' == buff[i])
451
0
        {
452
          /* Found start of the value */
453
0
          for (value_start = ++i; i < uri_end; ++i) /* Processing parameter value */
454
0
          {
455
0
            if ('+' == buff[i])
456
0
              buff[i] = ' ';
457
0
            else if ('&' == buff[i]) /* delimiter for the next parameter */
458
0
              break; /* Next parameter */
459
0
          }
460
0
          break; /* End of the current parameter */
461
0
        }
462
0
        else if ('&' == buff[i])
463
0
          break; /* End of the name of the parameter without a value */
464
0
      }
465
466
      /* PCT-decode, zero-terminate and store the found parameter */
467
468
0
      if (0u != value_start) /* Value cannot start at zero position */
469
0
      { /* Name with value */
470
0
        mhd_assert (name_start + 1u <= value_start);
471
0
        name_len = value_start - name_start - 1u;
472
0
        value_len = i - value_start;
473
0
      }
474
0
      else
475
0
      { /* Name without value */
476
0
        name_len = i - name_start;
477
0
        value_len = 0u;
478
0
      }
479
0
      name_len = mhd_str_pct_decode_lenient_n (buff + name_start,
480
0
                                               name_len,
481
0
                                               buff + name_start,
482
0
                                               name_len,
483
0
                                               NULL); // TODO: add support for broken encoding detection
484
0
      buff[name_start + name_len] = 0;
485
486
0
      if (0u != value_start)
487
0
      {
488
0
        value_len =
489
0
          mhd_str_pct_decode_lenient_n (buff + name_start + name_len + 1u,
490
0
                                        value_len,
491
0
                                        buff + value_start,
492
0
                                        value_len,
493
0
                                        NULL); // TODO: add support for broken encoding detection
494
0
        buff[value_start + value_len] = 0;
495
0
      }
496
497
0
      if (! mhd_h2_items_reserve_new_item (ib))
498
0
        break; // TODO: support reporting no space errors
499
500
0
      mhd_h2_items_add_new_item_reserved (ib,
501
0
                                          name_start,
502
0
                                          name_len,
503
0
                                          value_len,
504
0
                                          (0u != value_start)
505
0
                                          ? mhd_H2_RIK_URI_PARAM :
506
0
                                          mhd_H2_RIK_URI_PARAM_NV);
507
508
0
    } while (uri_end > ++i);
509
510
0
  }
511
512
0
  return true;
513
0
}
514
515
516
MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_
517
MHD_FN_PAR_INOUT_ (1) bool
518
mhd_h2_req_cookie_parse (struct mhd_H2Stream *restrict s)
519
0
{
520
  // TODO: handle cookie combining
521
  // TODO: Implement cookie parsing
522
0
  return true;
523
0
}