/* * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application * Copyright (C) 2008, Eric des Courtis * * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * Eric des Courtis * Copyright (C) Benbria. All Rights Reserved. * * Contributor(s): * * Eric des Courtis * * * http_req.c -- HTTP client implementation * */ #include "http_req.h" /* extern int dprintf(int, const char *, ...); extern int isblank(int); */ /* NOTE: v = version, header = h, status = s, newline = n, phrase = p */ static accept_state_t state_start(char c, state_machine_t * sm); static accept_state_t state_shp(char c, state_machine_t * sm); static accept_state_t state_n(char c, state_machine_t * sm); static accept_state_t state_vhp_A(char c, state_machine_t * sm); static accept_state_t state_vhp_B(char c, state_machine_t * sm); static accept_state_t state_vhp_C(char c, state_machine_t * sm); static accept_state_t state_vhp_D(char c, state_machine_t * sm); static accept_state_t state_vhp_E(char c, state_machine_t * sm); static accept_state_t state_vhp_F(char c, state_machine_t * sm); static accept_state_t state_vhp_G(char c, state_machine_t * sm); static accept_state_t state_vhp_H(char c, state_machine_t * sm); static accept_state_t state_hp_A(char c, state_machine_t * sm); static accept_state_t state_hp_B(char c, state_machine_t * sm); static accept_state_t state_p(char c, state_machine_t * sm); static accept_state_t state_error(char c, state_machine_t * sm); static int read_all(int s, char *buf, size_t len); #ifdef DEBUG int main(int argc, char *argv[]) { http_response_t response; http_request_t request; int ret; int i; if (argc != 2) return EXIT_FAILURE; request.method = GET; request.version = DEFAULT_HTTP_VERSION; request.url = argv[1]; request.header_len = 0; request.body_len = 0; ret = http_req(&request, &response); if (ret == ERROR) return EXIT_FAILURE; printf("Version : %s\n", response.version); printf("Status Code : %d\n", response.status_code); printf("Phrase : %s\n", response.phrase); for (i = 0; i < response.header_len; i++) { printf("Header : key = [%s] value = [%s]\n", response.headers[i].field_name, response.headers[i].value); } fflush(stdout); write(STDOUT_FILENO, response.body, response.body_len); printf("\n"); free_http_response(&response); return EXIT_SUCCESS; } #endif int http_req(http_request_t * req, http_response_t * res) { uint32_t addr; int s; int ret; uint16_t port = 0; size_t len; struct sockaddr_in sck; struct hostent *hst; char *hostname; char *method; char *buf; int buf_len; ssize_t i; int j; int l; int m; int p; int q = 0; char f = HTTP_FALSE; char port_s[MAX_PORT_LEN]; sighandler_t sig; struct timeval tv; tv.tv_sec = 1; tv.tv_usec = 0; (void) memset(&sck, 0, sizeof(sck)); sig = signal(SIGPIPE, SIG_IGN); if (sig == SIG_ERR) { fprintf(stderr, "Cannot ignore SIGPIPE signal\n"); return ERROR; } s = socket(PF_INET, SOCK_STREAM, 0); if (s == ERROR) { perror("Could not create socket"); return ERROR; } (void) setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); (void) setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); len = strlen(req->url); hostname = (char *) malloc(len * sizeof(char) + 1); if (hostname == NULL) { perror("Could not allocate memory for hostname"); close(s); return ERROR; } EMPTY_STRING(hostname); l = strlen(req->url) + 1; m = strlen("http://"); for (p = 0, j = m; j < l; j++) { if (req->url[j] == ':') { strncpy(hostname, req->url + m, j - m); hostname[j - m] = '\0'; f = HTTP_TRUE; p = j; continue; } if ((req->url[j] == '/' || req->url[j] == '\0') && f == HTTP_TRUE) { if ((j - p) < MAX_PORT_LEN) { strncpy(port_s, req->url + p + 1, j - p); port_s[j - p] = '\0'; port = (uint16_t) atoi(port_s); } else port = 0; q = j; break; } if ((req->url[j] == '/' || req->url[j] == '\0') && f == HTTP_FALSE) { strncpy(hostname, req->url + m, j - m); hostname[j - m] = '\0'; port = DEFAULT_HTTP_PORT; q = j; break; } } if (port == 0 || hostname[0] == '\0') { fprintf(stderr, "Invalid URL\n"); close(s); free(hostname); return ERROR; } l = strlen(hostname); for (j = 0; j < l; j++) { if (hostname[j] == '/') { hostname[j] = '\0'; break; } } hst = gethostbyname(hostname); if (hst == NULL) { herror("Could not find host"); close(s); free(hostname); return ERROR; } addr = *((uint32_t *) hst->h_addr_list[0]); sck.sin_family = AF_INET; sck.sin_port = htons(port); sck.sin_addr.s_addr = addr; ret = connect(s, (struct sockaddr *) &sck, sizeof(sck)); if (ret == ERROR) { perror("Could not connect to host"); close(s); free(hostname); return ERROR; } switch (req->method) { case GET: method = HTTP_GET_METHOD; break; case HEAD: method = HTTP_HEAD_METHOD; break; case POST: method = HTTP_POST_METHOD; break; case DELETE: method = HTTP_DELETE_METHOD; break; case PUT: method = HTTP_PUT_METHOD; break; default: method = HTTP_GET_METHOD; } if (req->url[q] == '/') { dprintf(s, "%s %s HTTP/%s\r\n", method, req->url + q, req->version); } else { dprintf(s, "%s /%s HTTP/%s\r\n", method, req->url + q, req->version); } if (port != DEFAULT_HTTP_PORT) dprintf(s, "Host: %s:%d\r\n", hostname, port); else dprintf(s, "Host: %s\r\n", hostname); dprintf(s, "Connection: close\r\n"); dprintf(s, "Content-Length: %d\r\n", (int)req->body_len); for (i = 0; i < req->header_len; i++) { dprintf(s, "%s: %s\r\n", req->headers[i].field_name, req->headers[i].value); } dprintf(s, "\r\n"); if (req->body != NULL && req->body_len != 0) { ret = write(s, req->body, req->body_len); if (ret == ERROR) { perror("Could not write body to socket"); free(hostname); return ERROR; } } buf = (char *) malloc(RECV_BUFF_SIZE * sizeof(char)); if (buf == NULL) { perror("Could not allocate memory for buffer"); close(s); free(hostname); return ERROR; } buf_len = read_all(s, buf, RECV_BUFF_SIZE - 1); if (buf_len == ERROR) { perror("Could not read into buffer"); free(hostname); return ERROR; } buf[buf_len] = '\0'; close(s); (void) signal(SIGPIPE, sig); free(hostname); return http_parse_response(buf, buf_len, res); } int http_parse_response(char *buf, ssize_t buf_len, http_response_t * response) { token_t token; state_machine_t sm; int pos; ssize_t size; char buff[STATUS_CODE_LEN]; int old_pos; int nt; int i; int j; int f; INIT_STATE_MACHINE(&sm); sm.buf = buf; sm.buf_len = buf_len; pos = sm.pos; token = get_next_token(&sm); if (token != VERSION) { fprintf(stderr, "ERROR %d-%d\n", VERSION, token); return ERROR; } size = sm.pos - pos; response->version = (char *) malloc((size + 1) * sizeof(char)); if (response->version == NULL) { perror("Cannot allocate memory for version number"); return ERROR; } strncpy(response->version, sm.buf + pos, size); response->version[size] = '\0'; pos = sm.pos; token = get_next_token(&sm); if (token != STATUS_CODE) { fprintf(stderr, "ERROR %d-%d\n", STATUS_CODE, token); return ERROR; } size = sm.pos - pos; buff[STATUS_CODE_LEN - 1] = '\0'; strncpy(buff, sm.buf + pos, STATUS_CODE_LEN - 1); response->status_code = atoi(buff); pos = sm.pos; token = get_next_token(&sm); if (token != PHRASE) { fprintf(stderr, "ERROR %d-%d\n", PHRASE, token); return ERROR; } size = sm.pos - pos - 2; response->phrase = (char *) malloc((size + 1) * sizeof(char)); if (response->phrase == NULL) { perror("Cannot allocate memory for phrase"); return ERROR; } strncpy(response->phrase, sm.buf + pos, size); response->phrase[size] = '\0'; old_pos = sm.pos; nt = 0; f = HTTP_FALSE; do { token = get_next_token(&sm); switch (token) { case PHRASE: case STATUS_CODE: f = HTTP_FALSE; case VERSION: case NEWLINE: break; case HEADER: if (f == HTTP_FALSE) { nt++; f = HTTP_TRUE; } else f = HTTP_FALSE; break; case SYNTAX_ERROR: return ERROR; } if (token != HEADER && token != PHRASE && token != STATUS_CODE) break; } while (token != SYNTAX_ERROR); if (nt != 0) { response->headers = (http_header_t *) malloc(sizeof(http_header_t) * nt); if (response->headers == NULL) { perror("Could not allocate memory for headers"); return ERROR; } } else response->headers = NULL; response->header_len = nt; sm.pos = old_pos; for (i = 0; i < nt; i++) { pos = sm.pos; sm.state = state_start; sm.stop = HTTP_FALSE; token = get_next_token(&sm); size = sm.pos - pos; size -= 2; response->headers[i].field_name = (char *) malloc((size + 1) * sizeof(char)); if (response->headers[i].field_name == NULL) { perror("Could not allocate memory for header"); return ERROR; } strncpy(response->headers[i].field_name, sm.buf + pos, size); response->headers[i].field_name[size] = '\0'; pos = sm.pos; token = get_next_token(&sm); if (token == HEADER || token == STATUS_CODE) { for (j = 0; ((sm.buf + pos)[j] == '\r' && (sm.buf + pos)[j + 1] == '\n') == 0; j++); size = j; sm.pos = j + 2; } else size = sm.pos - pos - 2; response->headers[i].value = (char *) malloc((size + 1) * sizeof(char)); if (response->headers[i].value == NULL) { perror("Could not allocate memory for header"); return ERROR; } strncpy(response->headers[i].value, sm.buf + pos, size); response->headers[i].value[size] = '\0'; } pos = sm.pos; token = get_next_token(&sm); if (token != NEWLINE) { fprintf(stderr, "ERROR %d-%d\n", NEWLINE, token); return ERROR; } response->body = (char *) malloc((buf_len - sm.pos + 1) * sizeof(char)); if (response->body == NULL) { perror("Could not allocate memory for body"); return ERROR; } response->body_len = buf_len - sm.pos; response->body[response->body_len] = '\0'; (void) memcpy(response->body, buf + sm.pos, response->body_len); return SUCCESS; } void free_http_response(http_response_t * response) { free(response->version); free(response->phrase); if (response->headers != NULL) free(response->headers); if (response->body != NULL) free(response->body); } token_t get_next_token(state_machine_t * sm) { char c; accept_state_t accept; while (sm->stop != HTTP_TRUE) { c = sm->buf[sm->pos]; accept = sm->state(c, sm); switch (accept) { case NOAS: case ASNR: sm->pos++; case ASWR: break; } switch (accept) { case ASNR: case ASWR: sm->state = state_start; return sm->token; case NOAS: break; } if (sm->pos >= sm->buf_len) { sm->stop = HTTP_TRUE; break; } } return SYNTAX_ERROR; } static accept_state_t state_start(char c, state_machine_t * sm) { if (toupper(c) == 'H') sm->state = state_vhp_A; else if (isdigit(c)) sm->state = state_shp; else if (c == '\r' || c == '\n') sm->state = state_n; else if (isprint(c)) sm->state = state_hp_A; else sm->state = state_error; return NOAS; } static accept_state_t state_shp(char c, state_machine_t * sm) { if (isdigit(c)) sm->state = state_shp; else if (isblank(c)) { sm->token = STATUS_CODE; return ASNR; } else if (c == ':') sm->state = state_hp_B; else if (c == '\r' || c == '\n') sm->state = state_p; else if (isprint(c)) sm->state = state_hp_A; else sm->state = state_error; return NOAS; } static accept_state_t state_n(char c, state_machine_t * sm) { if (c == '\r' || c == '\n') { sm->token = NEWLINE; return ASNR; } else if (c == ':') sm->state = state_hp_B; else if (isprint(c)) sm->state = state_hp_A; else sm->state = state_error; return NOAS; } static accept_state_t state_vhp_A(char c, state_machine_t * sm) { if (toupper(c) == 'T') sm->state = state_vhp_B; else if (isprint(c)) sm->state = state_hp_A; else if (c == '\n' || c == '\r') sm->state = state_p; else sm->state = state_error; return NOAS; } static accept_state_t state_vhp_B(char c, state_machine_t * sm) { if (toupper(c) == 'T') sm->state = state_vhp_C; else if (isprint(c)) sm->state = state_hp_A; else if (c == '\n' || c == '\r') sm->state = state_p; else sm->state = state_error; return NOAS; } static accept_state_t state_vhp_C(char c, state_machine_t * sm) { if (toupper(c) == 'P') sm->state = state_vhp_D; else if (isprint(c)) sm->state = state_hp_A; else if (c == '\n' || c == '\r') sm->state = state_p; else sm->state = state_error; return NOAS; } static accept_state_t state_vhp_D(char c, state_machine_t * sm) { if (toupper(c) == '/') sm->state = state_vhp_E; else if (isprint(c)) sm->state = state_hp_A; else if (c == '\n' || c == '\r') sm->state = state_p; else sm->state = state_error; return NOAS; } static accept_state_t state_vhp_E(char c, state_machine_t * sm) { if (isdigit(c)) sm->state = state_vhp_F; else if (isprint(c)) sm->state = state_hp_A; else if (c == '\n' || c == '\r') sm->state = state_p; else sm->state = state_error; return NOAS; } static accept_state_t state_vhp_F(char c, state_machine_t * sm) { if (isdigit(c)) sm->state = state_vhp_F; else if (c == '.') sm->state = state_vhp_G; else if (isprint(c)) sm->state = state_hp_A; else if (c == '\n' || c == '\r') sm->state = state_p; else sm->state = state_error; return NOAS; } static accept_state_t state_vhp_G(char c, state_machine_t * sm) { if (isdigit(c)) sm->state = state_vhp_H; else if (isprint(c)) sm->state = state_hp_A; else if (c == '\n' || c == '\r') sm->state = state_p; else sm->state = state_error; return NOAS; } static accept_state_t state_vhp_H(char c, state_machine_t * sm) { if (isdigit(c)) sm->state = state_vhp_H; else if (isblank(c)) { sm->token = VERSION; return ASNR; } else if (isprint(c)) sm->state = state_hp_A; return NOAS; } static accept_state_t state_hp_A(char c, state_machine_t * sm) { if (c == ':') sm->state = state_hp_B; else if (c == '\r' || c == '\n') sm->state = state_p; else if (isprint(c)) sm->state = state_hp_A; else sm->state = state_error; return NOAS; } static accept_state_t state_hp_B(char c, state_machine_t * sm) { if (isblank(c)) { sm->token = HEADER; return ASNR; } else if (c == '\r' || c == '\n') sm->state = state_p; else if (c == ':') sm->state = state_hp_B; else if (isprint(c)) sm->state = state_hp_A; else sm->state = state_error; return NOAS; } static accept_state_t state_p(char c, state_machine_t * sm) { if (c == '\r' || c == '\n') { sm->token = PHRASE; return ASNR; } else if (c == ':') sm->state = state_hp_B; else if (isprint(c)) sm->state = state_hp_A; else sm->state = state_error; return NOAS; } static accept_state_t state_error(char c, state_machine_t * sm) { c = 0; sm->token = SYNTAX_ERROR; return ASNR; } static int read_all(int s, char *buf, size_t len) { size_t t; size_t r = ERROR; for (t = 0; t < len && r != 0;) { r = read(s, buf + t, len - t); if ((int) r == ERROR) { return ERROR; } else { t += r; } } return (int) t; }