rts-http.c

     
   1  //! @file rts-http.c
   2  //! @author J. Marcel van der Veer
   3  
   4  //! @section Copyright
   5  //!
   6  //! This file is part of Algol68G - an Algol 68 compiler-interpreter.
   7  //! Copyright 2001-2023 J. Marcel van der Veer [algol68g@xs4all.nl].
   8  
   9  //! @section License
  10  //!
  11  //! This program is free software; you can redistribute it and/or modify it 
  12  //! under the terms of the GNU General Public License as published by the 
  13  //! Free Software Foundation; either version 3 of the License, or 
  14  //! (at your option) any later version.
  15  //!
  16  //! This program is distributed in the hope that it will be useful, but 
  17  //! WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
  18  //! or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 
  19  //! more details. You should have received a copy of the GNU General Public 
  20  //! License along with this program. If not, see [http://www.gnu.org/licenses/].
  21  
  22  //! @section Synopsis
  23  //!
  24  //! HTTP client.
  25  
  26  #include "a68g.h"
  27  #include "a68g-genie.h"
  28  #include "a68g-prelude.h"
  29  #include "a68g-transput.h"
  30  
  31  #if defined (BUILD_WWW)
  32  
  33  #if defined (HAVE_NETDB_H)
  34  #  include <netdb.h>
  35  #endif
  36  
  37  #if defined (HAVE_NETINET_IN_H)
  38  #  include <netinet/in.h>
  39  #endif
  40  
  41  #if defined (HAVE_SYS_SELECT_H)
  42  #  include <sys/select.h>
  43  #endif
  44  
  45  #if defined (HAVE_SYS_SOCKET_H)
  46  #  include <sys/socket.h>
  47  #endif
  48  
  49  #define PROTOCOL "tcp"
  50  #define SERVICE "http"
  51  
  52  #define CONTENT_BUFFER_SIZE (64 * KILOBYTE)
  53  #define TIMEOUT_INTERVAL 15
  54  
  55  // Own implementation of connect with time-out. 
  56  
  57  int a68_connect (int socket_id, struct sockaddr * addr, size_t addrlen, struct timeval * timeout)
  58  {
  59    int flags = fcntl (socket_id, F_GETFL, NULL);
  60    if (flags < 0) {
  61      return -1;
  62    }
  63    if (fcntl (socket_id, F_SETFL, flags | O_NONBLOCK) < 0) {
  64      return -1;
  65    }
  66    int ret = connect (socket_id, addr, addrlen);
  67    if (ret < 0) {
  68      if (errno == EINPROGRESS) {
  69        fd_set wait_set;
  70        FD_ZERO (&wait_set);
  71        FD_SET (socket_id, &wait_set);
  72        ret = select (socket_id + 1, NULL, &wait_set, NULL, timeout);
  73      }
  74    } else {
  75      ret = 1;
  76    }
  77    if (fcntl (socket_id, F_SETFL, flags) < 0) {
  78      return -1;
  79    }
  80    if (ret < 0) {
  81      return -1;
  82    } else if (ret == 0) {
  83      errno = ETIMEDOUT;
  84      return 1;
  85    } else {
  86      socklen_t len = sizeof (flags);
  87      if (getsockopt (socket_id, SOL_SOCKET, SO_ERROR, &flags, &len) < 0) {
  88        return -1;
  89      }
  90      if (flags) {
  91        errno = flags;
  92        return -1;
  93      }
  94    }
  95    return 0;
  96  }
  97  
  98  //! @brief PROC (REF STRING, STRING, STRING, INT) INT http content 
  99  
 100  void genie_http_content (NODE_T * p)
 101  {
 102  // Send GET request to server and yield answer (TCP/HTTP only).
 103    errno = 0;
 104  // Pop arguments.
 105    A68_INT port;
 106    POP_OBJECT (p, &port, A68_INT);
 107    CHECK_INIT (p, INITIALISED (&port), M_INT);
 108    A68_REF path_string, domain_string, content_string;
 109    POP_REF (p, &path_string);
 110    CHECK_INIT (p, INITIALISED (&path_string), M_STRING);
 111    POP_REF (p, &domain_string);
 112    CHECK_INIT (p, INITIALISED (&domain_string), M_STRING);
 113    POP_REF (p, &content_string);
 114    CHECK_REF (p, content_string, M_REF_STRING);
 115    *DEREF (A68_REF, &content_string) = empty_string (p);
 116  // Reset buffers.
 117    reset_transput_buffer (DOMAIN_BUFFER);
 118    add_a_string_transput_buffer (p, DOMAIN_BUFFER, (BYTE_T *) & domain_string);
 119    reset_transput_buffer (PATH_BUFFER);
 120    add_a_string_transput_buffer (p, PATH_BUFFER, (BYTE_T *) & path_string);
 121  // Compose request. Double \r\n at the end is essential!
 122    reset_transput_buffer (REQUEST_BUFFER);
 123    add_string_transput_buffer (p, REQUEST_BUFFER, "GET ");
 124    add_string_transput_buffer (p, REQUEST_BUFFER, get_transput_buffer (PATH_BUFFER));
 125    add_string_transput_buffer (p, REQUEST_BUFFER, " HTTP/1.0\r\n");
 126    add_string_transput_buffer (p, REQUEST_BUFFER, "Host: ");
 127    add_string_transput_buffer (p, REQUEST_BUFFER, get_transput_buffer (DOMAIN_BUFFER));
 128    add_string_transput_buffer (p, REQUEST_BUFFER, "\r\nAccept: */*\r\nConnection: close\r\n\r\n");
 129  // Connect to host.
 130    struct timeval a68_timeout;
 131    TV_SEC (&a68_timeout) = TIMEOUT_INTERVAL;
 132    TV_USEC (&a68_timeout) = 0;
 133    struct sockaddr_in socket_address;
 134    FILL (&socket_address, 0, (int) sizeof (socket_address));
 135    SIN_FAMILY (&socket_address) = AF_INET;
 136    struct servent *service_address = getservbyname (SERVICE, PROTOCOL);
 137    if (service_address == NULL) {
 138      PUSH_VALUE (p, 1, A68_INT);
 139      return;
 140    }
 141    if (VALUE (&port) == 0) {
 142      SIN_PORT (&socket_address) = (uint16_t) (S_PORT (service_address));
 143    } else {
 144      SIN_PORT (&socket_address) = (uint16_t) (htons ((uint16_t) (VALUE (&port))));
 145      if (SIN_PORT (&socket_address) == 0) {
 146        PUSH_VALUE (p, (errno == 0 ? 1 : errno), A68_INT);
 147        return;
 148      }
 149    }
 150    struct hostent *host_address = gethostbyname (get_transput_buffer (DOMAIN_BUFFER));
 151    if (host_address == NULL) {
 152      PUSH_VALUE (p, (errno == 0 ? 1 : errno), A68_INT);
 153      return;
 154    }
 155    COPY (&SIN_ADDR (&socket_address), H_ADDR (host_address), H_LENGTH (host_address));
 156    struct protoent *protocol = getprotobyname (PROTOCOL);
 157    if (protocol == NULL) {
 158      PUSH_VALUE (p, (errno == 0 ? 1 : errno), A68_INT);
 159      return;
 160    }
 161    int socket_id = socket (PF_INET, SOCK_STREAM, P_PROTO (protocol));
 162    if (socket_id < 0) {
 163      PUSH_VALUE (p, (errno == 0 ? 1 : errno), A68_INT);
 164      return;
 165    }
 166    int conn = a68_connect (socket_id, (struct sockaddr *) &socket_address, (socklen_t) SIZE_ALIGNED (socket_address), &a68_timeout);
 167    if (conn < 0) {
 168      PUSH_VALUE (p, (errno == 0 ? 1 : errno), A68_INT);
 169      ASSERT (close (socket_id) == 0);
 170      return;
 171    }
 172  // Read from host.
 173    WRITE (socket_id, get_transput_buffer (REQUEST_BUFFER));
 174    if (errno != 0) {
 175      PUSH_VALUE (p, errno, A68_INT);
 176      ASSERT (close (socket_id) == 0);
 177      return;
 178    }
 179  // Initialise file descriptor set.
 180    fd_set set;
 181    FD_ZERO (&set);
 182    FD_SET (socket_id, &set);
 183  // Block until server replies or a68_timeout blows up.
 184    switch (select (FD_SETSIZE, &set, NULL, NULL, &a68_timeout)) {
 185    case 0: {
 186        errno = ETIMEDOUT;
 187        PUSH_VALUE (p, errno, A68_INT);
 188        ASSERT (close (socket_id) == 0);
 189        return;
 190      }
 191    case -1: {
 192        PUSH_VALUE (p, errno, A68_INT);
 193        ASSERT (close (socket_id) == 0);
 194        return;
 195      }
 196    case 1: {
 197        break;
 198      }
 199    default: {
 200        ABEND (A68_TRUE, ERROR_ACTION, __func__);
 201      }
 202    }
 203  // Read from the socket.
 204    char buffer[CONTENT_BUFFER_SIZE];
 205    reset_transput_buffer (CONTENT_BUFFER);
 206    int k;
 207    while ((k = (int) io_read (socket_id, &buffer, (CONTENT_BUFFER_SIZE - 1))) > 0) {
 208      add_chars_transput_buffer (p, CONTENT_BUFFER, k, buffer);
 209    }
 210    if (k < 0 || errno != 0) {
 211      PUSH_VALUE (p, (errno == 0 ? 1 : errno), A68_INT);
 212      ASSERT (close (socket_id) == 0);
 213      return;
 214    }
 215  // Convert string.
 216    *DEREF (A68_REF, &content_string) = c_to_a_string (p, get_transput_buffer (CONTENT_BUFFER), get_transput_buffer_index (CONTENT_BUFFER));
 217    ASSERT (close (socket_id) == 0);
 218    PUSH_VALUE (p, errno, A68_INT);
 219  }
 220  
 221  //! @brief PROC (REF STRING, STRING, STRING, INT) INT tcp request
 222  
 223  void genie_tcp_request (NODE_T * p)
 224  {
 225  // Send request to server and yield answer (TCP only).
 226    errno = 0;
 227  // Pop arguments.
 228    A68_INT port;
 229    POP_OBJECT (p, &port, A68_INT);
 230    CHECK_INIT (p, INITIALISED (&port), M_INT);
 231    A68_REF request_string, domain_string, content_string;
 232    POP_REF (p, &request_string);
 233    CHECK_INIT (p, INITIALISED (&request_string), M_STRING);
 234    POP_REF (p, &domain_string);
 235    CHECK_INIT (p, INITIALISED (&domain_string), M_STRING);
 236    POP_REF (p, &content_string);
 237    CHECK_REF (p, content_string, M_REF_STRING);
 238    *DEREF (A68_REF, &content_string) = empty_string (p);
 239  // Reset buffers.
 240    reset_transput_buffer (DOMAIN_BUFFER);
 241    reset_transput_buffer (REQUEST_BUFFER);
 242    reset_transput_buffer (CONTENT_BUFFER);
 243    add_a_string_transput_buffer (p, DOMAIN_BUFFER, (BYTE_T *) & domain_string);
 244    add_a_string_transput_buffer (p, REQUEST_BUFFER, (BYTE_T *) & request_string);
 245    add_string_transput_buffer (p, REQUEST_BUFFER, "\r\n\r\n");
 246  // Connect to host.
 247    struct timeval a68_timeout;
 248    TV_SEC (&a68_timeout) = TIMEOUT_INTERVAL;
 249    TV_USEC (&a68_timeout) = 0;
 250    struct sockaddr_in socket_address;
 251    FILL (&socket_address, 0, (int) sizeof (socket_address));
 252    SIN_FAMILY (&socket_address) = AF_INET;
 253    struct servent *service_address = getservbyname (SERVICE, PROTOCOL);
 254    if (service_address == NULL) {
 255      PUSH_VALUE (p, 1, A68_INT);
 256      return;
 257    }
 258    if (VALUE (&port) == 0) {
 259      SIN_PORT (&socket_address) = (uint16_t) (S_PORT (service_address));
 260    } else {
 261      SIN_PORT (&socket_address) = (uint16_t) (htons ((uint16_t) (VALUE (&port))));
 262      if (SIN_PORT (&socket_address) == 0) {
 263        PUSH_VALUE (p, (errno == 0 ? 1 : errno), A68_INT);
 264        return;
 265      }
 266    }
 267    struct hostent *host_address = gethostbyname (get_transput_buffer (DOMAIN_BUFFER));
 268    if (host_address == NULL) {
 269      PUSH_VALUE (p, (errno == 0 ? 1 : errno), A68_INT);
 270      return;
 271    }
 272    COPY (&SIN_ADDR (&socket_address), H_ADDR (host_address), H_LENGTH (host_address));
 273    struct protoent *protocol = getprotobyname (PROTOCOL);
 274    if (protocol == NULL) {
 275      PUSH_VALUE (p, (errno == 0 ? 1 : errno), A68_INT);
 276      return;
 277    }
 278    int socket_id = socket (PF_INET, SOCK_STREAM, P_PROTO (protocol));
 279    if (socket_id < 0) {
 280      PUSH_VALUE (p, (errno == 0 ? 1 : errno), A68_INT);
 281      return;
 282    }
 283    int conn = a68_connect (socket_id, (struct sockaddr *) &socket_address, (socklen_t) SIZE_ALIGNED (socket_address), &a68_timeout);
 284    if (conn < 0) {
 285      PUSH_VALUE (p, (errno == 0 ? 1 : errno), A68_INT);
 286      ASSERT (close (socket_id) == 0);
 287      return;
 288    }
 289  // Read from host.
 290    WRITE (socket_id, get_transput_buffer (REQUEST_BUFFER));
 291    if (errno != 0) {
 292      PUSH_VALUE (p, errno, A68_INT);
 293      ASSERT (close (socket_id) == 0);
 294      return;
 295    }
 296  // Initialise file descriptor set.
 297    fd_set set;
 298    FD_ZERO (&set);
 299    FD_SET (socket_id, &set);
 300  // Block until server replies or a68_timeout blows up.
 301    switch (select (FD_SETSIZE, &set, NULL, NULL, &a68_timeout)) {
 302    case 0: {
 303        errno = ETIMEDOUT;
 304        PUSH_VALUE (p, errno, A68_INT);
 305        ASSERT (close (socket_id) == 0);
 306        return;
 307      }
 308    case -1: {
 309        PUSH_VALUE (p, errno, A68_INT);
 310        ASSERT (close (socket_id) == 0);
 311        return;
 312      }
 313    case 1: {
 314        break;
 315      }
 316    default: {
 317        ABEND (A68_TRUE, ERROR_ACTION, __func__);
 318      }
 319    }
 320    char buffer[CONTENT_BUFFER_SIZE];
 321    int k;
 322    while ((k = (int) io_read (socket_id, &buffer, (CONTENT_BUFFER_SIZE - 1))) > 0) {
 323      add_chars_transput_buffer (p, CONTENT_BUFFER, k, buffer);
 324    }
 325    if (k < 0 || errno != 0) {
 326      PUSH_VALUE (p, (errno == 0 ? 1 : errno), A68_INT);
 327      ASSERT (close (socket_id) == 0);
 328      return;
 329    }
 330  // Convert string.
 331    *DEREF (A68_REF, &content_string) = c_to_a_string (p, get_transput_buffer (CONTENT_BUFFER), get_transput_buffer_index (CONTENT_BUFFER));
 332    ASSERT (close (socket_id) == 0);
 333    PUSH_VALUE (p, errno, A68_INT);
 334  }
 335  
 336  #endif