Пример #1
0
protocol_s* http1_alloc(intptr_t fd, http_settings_s* settings) {
  validate_mem();
  // HTTP/1.1 should send a busy response
  // if there aren't enough available file descriptors.
  if (sock_max_capacity() - sock_uuid2fd(fd) <= HTTP_BUSY_UNLESS_HAS_FDS)
    goto is_busy;
  // get an http object from the pool
  http1_protocol_s* http = pool_pop();
  // of malloc one
  if (http == NULL)
    http = malloc(HTTP1_PROTOCOL_SIZE);
  // review allocation
  if (http == NULL)
    return NULL;

  // we shouldn't update the `http` protocol as a struct, as this will waste
  // time as the whole buffer will be zeroed out when there is no need.

  // setup parsing state
  http->buffer_pos = 0;
  // setup protocol callbacks
  http->protocol = (protocol_s){
      .service = HTTP1,
      .on_data = (void (*)(intptr_t, protocol_s*))http1_on_data,
      .on_close = (void (*)(protocol_s*))http1_free,
  };
  // setup request data
  http->request = (http_request_s){
      .metadata.max_headers = HTTP1_MAX_HEADER_COUNT,
      .metadata.fd = fd,
      .metadata.owner = http,
  };
  // update settings
  http->settings = settings;
  http->on_request = settings->on_request;
  // set the timeout
  server_set_timeout(fd, settings->timeout);
  return (protocol_s*)http;

is_busy:
  if (settings->public_folder && settings->public_folder_length) {
    size_t p_len = settings->public_folder_length;
    struct stat file_data = {};
    char fname[p_len + 8 + 1];
    memcpy(fname, settings->public_folder, p_len);
    if (settings->public_folder[p_len - 1] == '/' ||
        settings->public_folder[p_len - 1] == '\\')
      p_len--;
    memcpy(fname + p_len, "/503.html", 9);
    p_len += 9;
    if (stat(fname, &file_data))
      goto busy_no_file;
    // check that we have a file and not something else
    if (!S_ISREG(file_data.st_mode) && !S_ISLNK(file_data.st_mode))
      goto busy_no_file;
    int file = open(fname, O_RDONLY);
    if (file == -1)
      goto busy_no_file;
    sock_packet_s* packet;
    packet = sock_checkout_packet();
    memcpy(packet->buffer,
           "HTTP/1.1 503 Service Unavailable\r\n"
           "Content-Type: text/html\r\n"
           "Connection: close\r\n"
           "Content-Length: ",
           94);
    p_len = 94 + http_ul2a(packet->buffer + 94, file_data.st_size);
    memcpy(packet->buffer + p_len, "\r\n\r\n", 4);
    p_len += 4;
    if (BUFFER_PACKET_SIZE - p_len > file_data.st_size) {
      if (read(file, packet->buffer + p_len, file_data.st_size) < 0) {
        close(file);
        sock_free_packet(packet);
        goto busy_no_file;
      }
      close(file);
      packet->length = p_len + file_data.st_size;
      sock_send_packet(fd, packet);
    } else {
      packet->length = p_len;
      sock_send_packet(fd, packet);
      sock_sendfile(fd, file, 0, file_data.st_size);
    }
    return NULL;
  }
busy_no_file:
  sock_write(fd,
             "HTTP/1.1 503 Service Unavailable\r\nContent-Length: "
             "13\r\n\r\nServer Busy.",
             68);
  return NULL;
}

/* *****************************************************************************
HTTP/1.1 protocol bare-bones implementation
*/

#define HTTP_BODY_CHUNK_SIZE 3072  // 4096

/* parse and call callback */
static void http1_on_data(intptr_t uuid, http1_protocol_s* protocol) {
  ssize_t len = 0;
  ssize_t result;
  char buff[HTTP_BODY_CHUNK_SIZE];
  char* buffer;
  http_request_s* request = &protocol->request;
  for (;;) {
    // handle requests with no file data
    if (request->body_file <= 0) {
      // request headers parsing
      if (len == 0) {
        buffer = protocol->buffer;
        // make sure headers don't overflow
        len = sock_read(uuid, buffer + protocol->buffer_pos,
                        HTTP1_MAX_HEADER_SIZE - protocol->buffer_pos);
        // update buffer read position.
        protocol->buffer_pos += len;
      }
      if (len <= 0) {
        return;
      }
      // parse headers
      result =
          http1_parse_request_headers(buffer, protocol->buffer_pos, request);
      // review result
      if (result >= 0) {  // headers comeplete
        // mark buffer position, for HTTP pipelining
        protocol->buffer_pos = result;
        // are we done?
        if (request->content_length == 0 || request->body_str) {
          goto handle_request;
        }
        if (request->content_length > protocol->settings->max_body_size) {
          goto body_to_big;
        }
        // initialize or submit body data
        result =
            http1_parse_request_body(buffer + result, len - result, request);
        if (result >= 0) {
          protocol->buffer_pos += result;
          goto handle_request;
        } else if (result == -1)  // parser error
          goto parser_error;
        goto parse_body;
      } else if (result == -1)  // parser error
        goto parser_error;
      // assume incomplete (result == -2), even if wrong, we're right.
      len = 0;
      continue;
    }
    if (request->body_file > 0) {
    parse_body:
      buffer = buff;
      // request body parsing
      len = sock_read(uuid, buffer, HTTP_BODY_CHUNK_SIZE);
      if (len <= 0)
        return;
      result = http1_parse_request_body(buffer, len, request);
      if (result >= 0) {
        // set buffer pos for piplining support
        protocol->buffer_pos = result;
        goto handle_request;
      } else if (result == -1)  // parser error
        goto parser_error;
      if (len < HTTP_BODY_CHUNK_SIZE)  // pause parser for more data
        return;
      goto parse_body;
    }
    continue;
  handle_request:
    // review required headers / data
    if (request->host == NULL)
      goto bad_request;
    http_settings_s* settings = protocol->settings;
    // call request callback
    if (protocol && settings &&
        (protocol->settings->public_folder == NULL ||
         http_response_sendfile2(NULL, request, settings->public_folder,
                                 settings->public_folder_length, request->path,
                                 request->path_len, settings->log_static))) {
      protocol->on_request(request);
    }
    // clear request state
    http_request_clear(request);
    // rotate buffer for HTTP pipelining
    if (result >= len) {
      len = 0;
    } else {
      memmove(protocol->buffer, buffer + protocol->buffer_pos, len - result);
      len -= result;
    }
    // restart buffer position
    protocol->buffer_pos = 0;
    buffer = protocol->buffer;
  }
  // no routes lead here.
  fprintf(stderr,
          "I am lost on a deserted island, no code can reach me here :-)\n");
  return;  // How did we get here?
parser_error:
  if (request->headers_count == request->metadata.max_headers)
    goto too_big;
bad_request:
  /* handle generally bad requests */
  {
    http_response_s response = http_response_init(request);
    response.status = 400;
    http_response_write_body(&response, "Bad Request", 11);
    http_response_finish(&response);
    sock_close(uuid);
    protocol->buffer_pos = 0;
    return;
  }
too_big:
  /* handle oversized headers */
  {
    http_response_s response = http_response_init(request);
    response.status = 431;
    http_response_write_body(&response, "Request Header Fields Too Large", 31);
    http_response_finish(&response);
    sock_close(uuid);
    protocol->buffer_pos = 0;
    return;
  body_to_big:
    /* handle oversized body */
    {
      http_response_s response = http_response_init(request);
      response.status = 413;
      http_response_write_body(&response, "Payload Too Large", 17);
      http_response_finish(&response);
      sock_close(uuid);
      protocol->buffer_pos = 0;
      return;
    }
  }
}

/* *****************************************************************************
HTTP/1.1 listenning API implementation
*/

#undef http1_listen

static void http1_on_init(http_settings_s* settings) {
  if (settings->timeout == 0)
    settings->timeout = 5;
  if (settings->max_body_size == 0)
    settings->max_body_size = HTTP_DEFAULT_BODY_LIMIT;
  if (settings->public_folder) {
    settings->public_folder_length = strlen(settings->public_folder);
    if (settings->public_folder[0] == '~' &&
        settings->public_folder[1] == '/' && getenv("HOME")) {
      char* home = getenv("HOME");
      size_t home_len = strlen(home);
      char* tmp = malloc(settings->public_folder_length + home_len + 1);
      memcpy(tmp, home, home_len);
      if (home[home_len - 1] == '/')
        --home_len;
      memcpy(tmp + home_len, settings->public_folder + 1,
             settings->public_folder_length);  // copy also the NULL
      settings->public_folder = tmp;
      settings->private_metaflags |= 1;
      settings->public_folder_length = strlen(settings->public_folder);
    }
  }
}
static void http1_on_finish(http_settings_s* settings) {
  if (settings->private_metaflags & 1)
    free((void*)settings->public_folder);
  if (settings->private_metaflags & 2)
    free(settings);
}

int http1_listen(const char* port,
                 const char* address,
                 http_settings_s settings) {
  if (settings.on_request == NULL) {
    fprintf(
        stderr,
        "ERROR: http1_listen requires the .on_request parameter to be set\n");
    exit(11);
  }
  http_settings_s* settings_copy = malloc(sizeof(*settings_copy));
  *settings_copy = settings;
  settings_copy->private_metaflags = 2;
  return server_listen(.port = port, .address = address,
                       .on_start = (void*)http1_on_init,
                       .on_finish = (void*)http1_on_finish,
                       .on_open = (void*)http1_alloc, .udata = settings_copy);
}
Пример #2
0
static void
conn_request(void *ptr, http_request_t *request, http_response_t **response)
{
    const char realm[] = "airplay";
    raop_conn_t *conn = ptr;
    raop_t *raop = conn->raop;

    http_response_t *res;
    const char *method;
    const char *cseq;
    const char *challenge;
    int require_auth = 0;

    method = http_request_get_method(request);
    cseq = http_request_get_header(request, "CSeq");
    if (!method || !cseq) {
        return;
    }

    res = http_response_init("RTSP/1.0", 200, "OK");

    /* We need authorization for everything else than OPTIONS request */
    if (strcmp(method, "OPTIONS") != 0 && strlen(raop->password)) {
        const char *authorization;

        authorization = http_request_get_header(request, "Authorization");
        if (authorization) {
            logger_log(conn->raop->logger, LOGGER_DEBUG, "Our nonce: %s", conn->nonce);
            logger_log(conn->raop->logger, LOGGER_DEBUG, "Authorization: %s", authorization);
        }
        if (!digest_is_valid(realm, raop->password, conn->nonce, method, http_request_get_url(request), authorization)) {
            char *authstr;
            int authstrlen;

            /* Allocate the authenticate string */
            authstrlen = sizeof("Digest realm=\"\", nonce=\"\"") + sizeof(realm) + sizeof(conn->nonce) + 1;
            authstr = malloc(authstrlen);

            /* Concatenate the authenticate string */
            memset(authstr, 0, authstrlen);
            strcat(authstr, "Digest realm=\"");
            strcat(authstr, realm);
            strcat(authstr, "\", nonce=\"");
            strcat(authstr, conn->nonce);
            strcat(authstr, "\"");

            /* Construct a new response */
            require_auth = 1;
            http_response_destroy(res);
            res = http_response_init("RTSP/1.0", 401, "Unauthorized");
            http_response_add_header(res, "WWW-Authenticate", authstr);
            free(authstr);
            logger_log(conn->raop->logger, LOGGER_DEBUG, "Authentication unsuccessful, sending Unauthorized");
        } else {
            logger_log(conn->raop->logger, LOGGER_DEBUG, "Authentication successful!");
        }
    }

    http_response_add_header(res, "CSeq", cseq);
    http_response_add_header(res, "Apple-Jack-Status", "connected; type=analog");

    challenge = http_request_get_header(request, "Apple-Challenge");
    if (!require_auth && challenge) {
        char signature[MAX_SIGNATURE_LEN];

        memset(signature, 0, sizeof(signature));
        rsakey_sign(raop->rsakey, signature, sizeof(signature), challenge,
                    conn->local, conn->locallen, raop->hwaddr, raop->hwaddrlen);
        http_response_add_header(res, "Apple-Response", signature);

        logger_log(conn->raop->logger, LOGGER_DEBUG, "Got challenge: %s", challenge);
        logger_log(conn->raop->logger, LOGGER_DEBUG, "Got response: %s", signature);
    }

    if (require_auth) {
        /* Do nothing in case of authentication request */
    } else if (!strcmp(method, "OPTIONS")) {
        http_response_add_header(res, "Public", "ANNOUNCE, SETUP, RECORD, PAUSE, FLUSH, TEARDOWN, OPTIONS, GET_PARAMETER, SET_PARAMETER");
    } else if (!strcmp(method, "ANNOUNCE")) {
        const char *data;
        int datalen;

        unsigned char aeskey[16];
        unsigned char aesiv[16];
        int aeskeylen, aesivlen;

        data = http_request_get_data(request, &datalen);
        if (data) {
            sdp_t *sdp;
            const char *remotestr, *rtpmapstr, *fmtpstr, *aeskeystr, *aesivstr;

            sdp = sdp_init(data, datalen);
            remotestr = sdp_get_connection(sdp);
            rtpmapstr = sdp_get_rtpmap(sdp);
            fmtpstr = sdp_get_fmtp(sdp);
            aeskeystr = sdp_get_rsaaeskey(sdp);
            aesivstr = sdp_get_aesiv(sdp);

            logger_log(conn->raop->logger, LOGGER_DEBUG, "connection: %s", remotestr);
            logger_log(conn->raop->logger, LOGGER_DEBUG, "rtpmap: %s", rtpmapstr);
            logger_log(conn->raop->logger, LOGGER_DEBUG, "fmtp: %s", fmtpstr);
            logger_log(conn->raop->logger, LOGGER_DEBUG, "rsaaeskey: %s", aeskeystr);
            logger_log(conn->raop->logger, LOGGER_DEBUG, "aesiv: %s", aesivstr);

            aeskeylen = rsakey_decrypt(raop->rsakey, aeskey, sizeof(aeskey), aeskeystr);
            aesivlen = rsakey_parseiv(raop->rsakey, aesiv, sizeof(aesiv), aesivstr);
            logger_log(conn->raop->logger, LOGGER_DEBUG, "aeskeylen: %d", aeskeylen);
            logger_log(conn->raop->logger, LOGGER_DEBUG, "aesivlen: %d", aesivlen);

            if (conn->raop_rtp) {
                /* This should never happen */
                raop_rtp_destroy(conn->raop_rtp);
                conn->raop_rtp = NULL;
            }
            conn->raop_rtp = raop_rtp_init(raop->logger, &raop->callbacks, remotestr, rtpmapstr, fmtpstr, aeskey, aesiv);
            if (!conn->raop_rtp) {
                logger_log(conn->raop->logger, LOGGER_ERR, "Error initializing the audio decoder");
                http_response_set_disconnect(res, 1);
            }
            sdp_destroy(sdp);
        }
    } else if (!strcmp(method, "SETUP")) {
        unsigned short remote_cport=0, remote_tport=0;
        unsigned short cport=0, tport=0, dport=0;
        const char *transport;
        char buffer[1024];
        int use_udp;
        const char *dacp_id;
        const char *active_remote_header;

        dacp_id = http_request_get_header(request, "DACP-ID");
        active_remote_header = http_request_get_header(request, "Active-Remote");

        if (dacp_id && active_remote_header) {
            logger_log(conn->raop->logger, LOGGER_DEBUG, "DACP-ID: %s", dacp_id);
            logger_log(conn->raop->logger, LOGGER_DEBUG, "Active-Remote: %s", active_remote_header);
            if (conn->raop_rtp) {
                raop_rtp_remote_control_id(conn->raop_rtp, dacp_id, active_remote_header);
            }
        }

        transport = http_request_get_header(request, "Transport");
        assert(transport);

        logger_log(conn->raop->logger, LOGGER_INFO, "Transport: %s", transport);
        use_udp = strncmp(transport, "RTP/AVP/TCP", 11);
        if (use_udp) {
            char *original, *current, *tmpstr;

            current = original = strdup(transport);
            if (original) {
                while ((tmpstr = utils_strsep(&current, ";")) != NULL) {
                    unsigned short value;
                    int ret;

                    ret = sscanf(tmpstr, "control_port=%hu", &value);
                    if (ret == 1) {
                        logger_log(conn->raop->logger, LOGGER_DEBUG, "Found remote control port: %hu", value);
                        remote_cport = value;
                    }
                    ret = sscanf(tmpstr, "timing_port=%hu", &value);
                    if (ret == 1) {
                        logger_log(conn->raop->logger, LOGGER_DEBUG, "Found remote timing port: %hu", value);
                        remote_tport = value;
                    }
                }
            }
            free(original);
        }
        if (conn->raop_rtp) {
            raop_rtp_start(conn->raop_rtp, use_udp, remote_cport, remote_tport, &cport, &tport, &dport);
        } else {
            logger_log(conn->raop->logger, LOGGER_ERR, "RAOP not initialized at SETUP, playing will fail!");
            http_response_set_disconnect(res, 1);
        }

        memset(buffer, 0, sizeof(buffer));
        if (use_udp) {
            snprintf(buffer, sizeof(buffer)-1,
                     "RTP/AVP/UDP;unicast;mode=record;timing_port=%hu;events;control_port=%hu;server_port=%hu",
                     tport, cport, dport);
        } else {
            snprintf(buffer, sizeof(buffer)-1,
                     "RTP/AVP/TCP;unicast;interleaved=0-1;mode=record;server_port=%u",
                     dport);
        }
        logger_log(conn->raop->logger, LOGGER_INFO, "Responding with %s", buffer);
        http_response_add_header(res, "Transport", buffer);
        http_response_add_header(res, "Session", "DEADBEEF");
    } else if (!strcmp(method, "SET_PARAMETER")) {
        const char *content_type;
        const char *data;
        int datalen;

        content_type = http_request_get_header(request, "Content-Type");
        data = http_request_get_data(request, &datalen);
        if (!strcmp(content_type, "text/parameters")) {
            char *datastr;
            datastr = calloc(1, datalen+1);
            if (data && datastr && conn->raop_rtp) {
                memcpy(datastr, data, datalen);
                if (!strncmp(datastr, "volume: ", 8)) {
                    float vol = 0.0;
                    sscanf(datastr+8, "%f", &vol);
                    raop_rtp_set_volume(conn->raop_rtp, vol);
                } else if (!strncmp(datastr, "progress: ", 10)) {
                    unsigned int start, curr, end;
                    sscanf(datastr+10, "%u/%u/%u", &start, &curr, &end);
                    raop_rtp_set_progress(conn->raop_rtp, start, curr, end);
                }
            } else if (!conn->raop_rtp) {
                logger_log(conn->raop->logger, LOGGER_WARNING, "RAOP not initialized at SET_PARAMETER");
            }
            free(datastr);
        } else if (!strcmp(content_type, "image/jpeg") || !strcmp(content_type, "image/png")) {
            logger_log(conn->raop->logger, LOGGER_INFO, "Got image data of %d bytes", datalen);
            if (conn->raop_rtp) {
                raop_rtp_set_coverart(conn->raop_rtp, data, datalen);
            } else {
                logger_log(conn->raop->logger, LOGGER_WARNING, "RAOP not initialized at SET_PARAMETER coverart");
            }
        } else if (!strcmp(content_type, "application/x-dmap-tagged")) {
            logger_log(conn->raop->logger, LOGGER_INFO, "Got metadata of %d bytes", datalen);
            if (conn->raop_rtp) {
                raop_rtp_set_metadata(conn->raop_rtp, data, datalen);
            } else {
                logger_log(conn->raop->logger, LOGGER_WARNING, "RAOP not initialized at SET_PARAMETER metadata");
            }
        }
    } else if (!strcmp(method, "FLUSH")) {
        const char *rtpinfo;
        int next_seq = -1;

        rtpinfo = http_request_get_header(request, "RTP-Info");
        if (rtpinfo) {
            logger_log(conn->raop->logger, LOGGER_INFO, "Flush with RTP-Info: %s", rtpinfo);
            if (!strncmp(rtpinfo, "seq=", 4)) {
                next_seq = strtol(rtpinfo+4, NULL, 10);
            }
        }
        if (conn->raop_rtp) {
            raop_rtp_flush(conn->raop_rtp, next_seq);
        } else {
            logger_log(conn->raop->logger, LOGGER_WARNING, "RAOP not initialized at FLUSH");
        }
    } else if (!strcmp(method, "TEARDOWN")) {
        http_response_add_header(res, "Connection", "close");
        if (conn->raop_rtp) {
            /* Destroy our RTP session */
            raop_rtp_stop(conn->raop_rtp);
            raop_rtp_destroy(conn->raop_rtp);
            conn->raop_rtp = NULL;
        }
    }
    http_response_finish(res, NULL, 0);

    logger_log(conn->raop->logger, LOGGER_DEBUG, "Handled request %s with URL %s", method, http_request_get_url(request));
    *response = res;
}