Edit

kc3-lang/kc3/http/http_response.c

Branch :

  • http/http_response.c
  • /* kc3
     * Copyright 2022,2023,2024 kmx.io <contact@kmx.io>
     *
     * Permission is hereby granted to use this software granted the above
     * copyright notice and this permission paragraph are included in all
     * copies and substantial portions of this software.
     *
     * THIS SOFTWARE IS PROVIDED "AS-IS" WITHOUT ANY GUARANTEE OF
     * PURPOSE AND PERFORMANCE. IN NO EVENT WHATSOEVER SHALL THE
     * AUTHOR BE CONSIDERED LIABLE FOR THE USE AND PERFORMANCE OF
     * THIS SOFTWARE.
     */
    #include <string.h>
    #include <libkc3/kc3.h>
    #include "http_response.h"
    
    
    s_http_response * http_response_buf_parse (s_http_response *response,
                                               s_buf *buf, bool parse_body)
    {
      sw    content_length = -1;
      s_str content_length_str = {0};
      s_tag *key = NULL;
      s_list **l = NULL;
      sw r = 0;
      s_buf_save save;
      s_str str;
      s_http_response tmp = {0};
      s_tuple *tuple = NULL;
      s_tag *value = NULL;
      assert(response);
      assert(buf);
      buf_save_init(buf, &save);
      if (! buf_read_until_1_into_str(buf, " ", &tmp.protocol))
        goto clean;
      if (buf_parse_u16(buf, &tmp.code) <= 0)
        goto restore;
      if (tmp.code < 100 || tmp.code > 999) {
        err_puts("http_response_buf_parse: invalid response code");
        goto restore;
      }
      if ((r = buf_read_1(buf, " ")) <= 0)
        goto restore;
      if (! buf_read_until_1_into_str(buf, "\r\n", &tmp.message))
        goto restore;
      str_init_1(&content_length_str, NULL, "content-length");
      l = &tmp.headers;
      while (1) {
        if ((r = buf_read_1(buf, "\r\n")) < 0)
          goto restore;
        if (r > 0)
          break;
        *l = list_new_tuple(2, NULL);
        if (! *l)
          goto restore;
        tuple = &(*l)->tag.data.tuple;
        key = tuple->tag;
        value = tuple->tag + 1;
        key->type = TAG_STR;
        if (! buf_read_until_1_into_str(buf, ":", &key->data.str))
          goto restore;
        if ((r = buf_ignore_spaces(buf)) < 0)
          goto restore;
        value->type = TAG_STR;
        if (! buf_read_until_1_into_str(buf, "\r\n", &value->data.str))
          goto restore;
        if (! str_init_to_lower(&str, &key->data.str))
          goto restore;
        if (! compare_str(&content_length_str, &str))
          sw_init_str(&content_length, &value->data.str);
        str_clean(&str);
        l = &(*l)->next.data.list;
      }
      if (! parse_body)
        goto ok;
      if (content_length < 0)
        goto restore;
      if (! buf_read(buf, content_length, &tmp.body))
        goto restore;
     ok:
      buf_save_clean(buf, &save);
      *response = tmp;
      return response;
     restore:
      buf_save_restore_rpos(buf, &save);
     clean:
      buf_save_clean(buf, &save);
      return NULL;
    }
    
    sw http_response_buf_write (const s_http_response *response,
                                s_buf *buf, bool send_body)
    {
      sw    content_length = -1;
      s_str content_length_str = {0};
      s_tag default_messages = {0};
      s_ident ident = {0};
      s_tag *key = NULL;
      const s_list *l = NULL;
      s_str protocol = {0};
      sw r = 0;
      sw result = 0;
      s_tag tag_code = {0};
      s_tag tag_message = {0};
      s_tag *value = NULL;
      assert(response);
      assert(buf);
      if (! response->protocol.size)
        str_init_1(&protocol, NULL, "HTTP/1.1");
      else
        protocol = response->protocol;
      if ((r = buf_write_str(buf, &protocol)) < 0)
        return r;
      result += r;
      if ((r = buf_write_1(buf, " ")) < 0)
        return r;
      result += r;
      tag_code.type = TAG_U16;
      tag_code.data.u16 = response->code;
      if (! tag_code.data.u16)
        tag_code.data.u16 = 200;
      if (tag_code.data.u16 < 100 || tag_code.data.u16 > 999) {
        err_puts("http_response_buf_write: invalid response code");
        return -1;
      }
      if ((r = buf_inspect_u16_decimal(buf, &tag_code.data.u16)) < 0)
        return r;
      result += r;
      if ((r = buf_write_1(buf, " ")) < 0)
        return r;
      result += r;
      if (! response->message.size) {
        ident_init(&ident, sym_1("HTTP.Response"), sym_1("default_messages"));
        ident_get(&ident, &default_messages);
        if (! tag_is_alist(&default_messages)) {
          err_puts("http_response_buf_write: invalid default_messages:"
                   " not an AList");
          tag_clean(&default_messages);
          return -1;
        }
        if (alist_get((const s_list * const *) &default_messages.data.list,
                      &tag_code, &tag_message)) {
          if (tag_message.type != TAG_STR) {
            err_puts("http_response_buf_write: invalid default message:"
                     " not a Str");
            tag_clean(&tag_message);
            tag_clean(&default_messages);
            return -1;
          }
        }
      }
      else {
        tag_message.type = TAG_STR;
        tag_message.data.str = response->message;
        tag_message.data.str.free.p = NULL;
      }
      if ((r = buf_write_str(buf, &tag_message.data.str)) < 0) {
        tag_clean(&tag_message);
        tag_clean(&default_messages);
        return r;
      }
      result += r;
      tag_clean(&tag_message);
      tag_clean(&default_messages);
      if ((r = buf_write_1(buf, "\r\n")) < 0)
        return r;
      result += r;
      str_init_1(&content_length_str, NULL, "Content-Length");
      l = response->headers;
      while (l) {
        if (l->tag.type != TAG_TUPLE ||
            l->tag.data.tuple.count != 2) {
          err_puts("http_response_buf_write: invalid header: not a Tuple");
          return -1;
        }
        key = l->tag.data.tuple.tag;
        value = key + 1;
        if (key->type != TAG_STR || value->type != TAG_STR) {
          err_puts("http_response_buf_write: invalid header: not a Str");
          return -1;
        }
        if (! compare_str(&content_length_str, &key->data.str))
          sw_init_str(&content_length, &value->data.str);
        if ((r = buf_write_str(buf, &key->data.str)) < 0)
          return r;
        result += r;
        if ((r = buf_write_1(buf, ": ")) < 0)
          return r;
        result += r;
        if ((r = buf_write_str(buf, &value->data.str)) < 0)
          return r;
        result += r;
        if ((r = buf_write_1(buf, "\r\n")) < 0)
          return r;
        result += r;
        l = list_next(l);
      }
      if (content_length < 0) {
        if ((r = buf_write_str(buf, &content_length_str)) < 0)
          return r;
        result += r;
        if ((r = buf_write_1(buf, ": ")) < 0)
          return r;
        result += r;
        if ((r = buf_inspect_uw_decimal(buf, &response->body.size)) < 0)
          return r;
        result += r;
        if ((r = buf_write_1(buf, "\r\n")) < 0)
          return r;
        result += r;
      }
      if ((r = buf_write_1(buf, "\r\n")) < 0)
        return r;
      result += r;
      if (send_body) {
        if ((r = buf_write_str(buf, &response->body)) < 0)
          return r;
        result += r;
      }
      return result;
    }