mirror of
https://github.com/signalwire/freeswitch.git
synced 2025-04-14 16:15:04 +00:00
git-svn-id: http://svn.freeswitch.org/svn/freeswitch/trunk@12069 d0543943-73ff-0310-b7d9-9358b9ac24b2
729 lines
15 KiB
C
729 lines
15 KiB
C
/*
|
|
* FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
|
|
* Copyright (C) 2008, Eric des Courtis <eric.des.courtis@benbria.com>
|
|
*
|
|
* 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 <eric.des.courtis@benbria.com>
|
|
* Copyright (C) Benbria. All Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
*
|
|
* Eric des Courtis <eric.des.courtis@benbria.com>
|
|
*
|
|
*
|
|
* 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;
|
|
}
|