mirror of
https://github.com/sogou/workflow.git
synced 2026-10-24 08:25:13 +08:00
792 lines
15 KiB
C
792 lines
15 KiB
C
/*
|
|
Copyright (c) 2019 Sogou, Inc.
|
|
|
|
Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
|
|
Author: Xie Han (xiehan@sogou-inc.com)
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include "list.h"
|
|
#include "http_parser.h"
|
|
|
|
#define MIN(x, y) ((x) <= (y) ? (x) : (y))
|
|
#define MAX(x, y) ((x) >= (y) ? (x) : (y))
|
|
|
|
#define HTTP_START_LINE_MAX 8192
|
|
#define HTTP_HEADER_VALUE_MAX 8192
|
|
#define HTTP_CHUNK_LINE_MAX 1024
|
|
#define HTTP_TRAILER_LINE_MAX 8192
|
|
#define HTTP_MSGBUF_INIT_SIZE 2048
|
|
|
|
enum
|
|
{
|
|
HPS_START_LINE,
|
|
HPS_HEADER_NAME,
|
|
HPS_HEADER_VALUE,
|
|
HPS_HEADER_COMPLETE
|
|
};
|
|
|
|
enum
|
|
{
|
|
CPS_CHUNK_DATA,
|
|
CPS_TRAILER_PART,
|
|
CPS_CHUNK_COMPLETE
|
|
};
|
|
|
|
struct __header_line
|
|
{
|
|
struct list_head list;
|
|
int name_len;
|
|
int value_len;
|
|
char *buf;
|
|
};
|
|
|
|
int http_parser_add_header(const void *name, size_t name_len,
|
|
const void *value, size_t value_len,
|
|
http_parser_t *parser)
|
|
{
|
|
size_t size = sizeof (struct __header_line) + name_len + value_len + 4;
|
|
struct __header_line *line;
|
|
|
|
line = (struct __header_line *)malloc(size);
|
|
if (line)
|
|
{
|
|
line->buf = (char *)(line + 1);
|
|
memcpy(line->buf, name, name_len);
|
|
line->buf[name_len] = ':';
|
|
line->buf[name_len + 1] = ' ';
|
|
memcpy(line->buf + name_len + 2, value, value_len);
|
|
line->buf[name_len + 2 + value_len] = '\r';
|
|
line->buf[name_len + 2 + value_len + 1] = '\n';
|
|
line->name_len = name_len;
|
|
line->value_len = value_len;
|
|
list_add_tail(&line->list, &parser->header_list);
|
|
return 0;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
int http_parser_set_header(const void *name, size_t name_len,
|
|
const void *value, size_t value_len,
|
|
http_parser_t *parser)
|
|
{
|
|
struct __header_line *line;
|
|
struct list_head *pos;
|
|
char *buf;
|
|
|
|
list_for_each(pos, &parser->header_list)
|
|
{
|
|
line = list_entry(pos, struct __header_line, list);
|
|
if (line->name_len == name_len &&
|
|
strncasecmp(line->buf, name, name_len) == 0)
|
|
{
|
|
if (value_len > line->value_len)
|
|
{
|
|
buf = (char *)malloc(name_len + value_len + 4);
|
|
if (!buf)
|
|
return -1;
|
|
|
|
if (line->buf != (char *)(line + 1))
|
|
free(line->buf);
|
|
|
|
line->buf = buf;
|
|
memcpy(buf, name, name_len);
|
|
buf[name_len] = ':';
|
|
buf[name_len + 1] = ' ';
|
|
}
|
|
|
|
memcpy(line->buf + name_len + 2, value, value_len);
|
|
line->buf[name_len + 2 + value_len] = '\r';
|
|
line->buf[name_len + 2 + value_len + 1] = '\n';
|
|
line->value_len = value_len;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return http_parser_add_header(name, name_len, value, value_len, parser);
|
|
}
|
|
|
|
static int __match_request_line(const char *method,
|
|
const char *uri,
|
|
const char *version,
|
|
http_parser_t *parser)
|
|
{
|
|
if (strcmp(version, "HTTP/1.0") == 0 || strncmp(version, "HTTP/0", 6) == 0)
|
|
parser->keep_alive = 0;
|
|
|
|
method = strdup(method);
|
|
if (method)
|
|
{
|
|
uri = strdup(uri);
|
|
if (uri)
|
|
{
|
|
version = strdup(version);
|
|
if (version)
|
|
{
|
|
free(parser->method);
|
|
free(parser->uri);
|
|
free(parser->version);
|
|
parser->method = (char *)method;
|
|
parser->uri = (char *)uri;
|
|
parser->version = (char *)version;
|
|
return 0;
|
|
}
|
|
|
|
free((char *)uri);
|
|
}
|
|
|
|
free((char *)method);
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
static int __match_status_line(const char *version,
|
|
const char *code,
|
|
const char *phrase,
|
|
http_parser_t *parser)
|
|
{
|
|
if (strcmp(version, "HTTP/1.0") == 0 || strncmp(version, "HTTP/0", 6) == 0)
|
|
parser->keep_alive = 0;
|
|
|
|
if (*code == '1' || strcmp(code, "204") == 0 || strcmp(code, "304") == 0)
|
|
parser->transfer_length = 0;
|
|
|
|
version = strdup(version);
|
|
if (version)
|
|
{
|
|
code = strdup(code);
|
|
if (code)
|
|
{
|
|
phrase = strdup(phrase);
|
|
if (phrase)
|
|
{
|
|
free(parser->version);
|
|
free(parser->code);
|
|
free(parser->phrase);
|
|
parser->version = (char *)version;
|
|
parser->code = (char *)code;
|
|
parser->phrase = (char *)phrase;
|
|
return 0;
|
|
}
|
|
|
|
free((char *)code);
|
|
}
|
|
|
|
free((char *)version);
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
static int __match_message_header(const char *name, size_t name_len,
|
|
const char *value, size_t value_len,
|
|
http_parser_t *parser)
|
|
{
|
|
switch (name_len)
|
|
{
|
|
case 6:
|
|
if (strcasecmp(name, "Expect") == 0)
|
|
{
|
|
if (strcasecmp(value, "100-continue") == 0)
|
|
parser->expect_continue = 1;
|
|
}
|
|
|
|
case 10:
|
|
if (strcasecmp(name, "Connection") == 0)
|
|
{
|
|
if (strcasecmp(value, "Keep-Alive") == 0)
|
|
parser->keep_alive = 1;
|
|
else if (strcasecmp(value, "close") == 0)
|
|
parser->keep_alive = 0;
|
|
}
|
|
|
|
break;
|
|
|
|
case 14:
|
|
if (strcasecmp(name, "Content-Length") == 0)
|
|
{
|
|
if (*value >= '0' && *value <= '9')
|
|
parser->content_length = atoi(value);
|
|
}
|
|
|
|
break;
|
|
|
|
case 17:
|
|
if (strcasecmp(name, "Transfer-Encoding") == 0)
|
|
{
|
|
if (value_len != 8 || strcasecmp(value, "identity") != 0)
|
|
parser->chunked = 1;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
return http_parser_add_header(name, name_len, value, value_len, parser);
|
|
}
|
|
|
|
static int __parse_start_line(const char *ptr, size_t len,
|
|
http_parser_t *parser)
|
|
{
|
|
char start_line[HTTP_START_LINE_MAX];
|
|
size_t min = MIN(HTTP_START_LINE_MAX, len);
|
|
char *p1, *p2, *p3;
|
|
size_t i;
|
|
int ret;
|
|
|
|
if (len >= 2 && ptr[0] == '\r' && ptr[1] == '\n')
|
|
{
|
|
parser->header_offset += 2;
|
|
return 1;
|
|
}
|
|
|
|
for (i = 0; i < min; i++)
|
|
{
|
|
start_line[i] = ptr[i];
|
|
if (start_line[i] == '\r')
|
|
{
|
|
if (i == len - 1)
|
|
return 0;
|
|
|
|
if (ptr[i + 1] != '\n')
|
|
return -2;
|
|
|
|
start_line[i] = '\0';
|
|
p1 = start_line;
|
|
p2 = strchr(p1, ' ');
|
|
if (p2)
|
|
*p2++ = '\0';
|
|
else
|
|
return -2;
|
|
|
|
p3 = strchr(p2, ' ');
|
|
if (p3)
|
|
*p3++ = '\0';
|
|
else
|
|
return -2;
|
|
|
|
if (parser->is_resp)
|
|
ret = __match_status_line(p1, p2, p3, parser);
|
|
else
|
|
ret = __match_request_line(p1, p2, p3, parser);
|
|
|
|
if (ret < 0)
|
|
return -1;
|
|
|
|
parser->header_offset += i + 2;
|
|
parser->header_state = HPS_HEADER_NAME;
|
|
return 1;
|
|
}
|
|
|
|
if (start_line[i] == '\0')
|
|
return -2;
|
|
}
|
|
|
|
if (i == HTTP_START_LINE_MAX)
|
|
return -2;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __parse_header_name(const char *ptr, size_t len,
|
|
http_parser_t *parser)
|
|
{
|
|
size_t min = MIN(HTTP_HEADER_NAME_MAX, len);
|
|
size_t i;
|
|
|
|
if (len >= 2 && ptr[0] == '\r' && ptr[1] == '\n')
|
|
{
|
|
parser->header_offset += 2;
|
|
parser->header_state = HPS_HEADER_COMPLETE;
|
|
return 1;
|
|
}
|
|
|
|
for (i = 0; i < min; i++)
|
|
{
|
|
if (ptr[i] == ':')
|
|
{
|
|
parser->namebuf[i] = '\0';
|
|
parser->header_offset += i + 1;
|
|
parser->header_state = HPS_HEADER_VALUE;
|
|
return 1;
|
|
}
|
|
|
|
if (ptr[i] == '\0')
|
|
return -2;
|
|
|
|
parser->namebuf[i] = ptr[i];
|
|
}
|
|
|
|
if (i == HTTP_HEADER_NAME_MAX)
|
|
return -2;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __parse_header_value(const char *ptr, size_t len,
|
|
http_parser_t *parser)
|
|
{
|
|
char header_value[HTTP_HEADER_VALUE_MAX];
|
|
const char *end = ptr + len;
|
|
const char *begin = ptr;
|
|
size_t i = 0;
|
|
|
|
while (1)
|
|
{
|
|
while (1)
|
|
{
|
|
if (ptr == end)
|
|
return 0;
|
|
|
|
if (*ptr == ' ' || *ptr == '\t')
|
|
ptr++;
|
|
else
|
|
break;
|
|
}
|
|
|
|
while (1)
|
|
{
|
|
if (i == HTTP_HEADER_VALUE_MAX)
|
|
return -2;
|
|
|
|
header_value[i] = *ptr++;
|
|
if (ptr == end)
|
|
return 0;
|
|
|
|
if (header_value[i] == '\r')
|
|
break;
|
|
|
|
if (header_value[i] == '\0')
|
|
return -2;
|
|
|
|
i++;
|
|
}
|
|
|
|
if (*ptr == '\n')
|
|
ptr++;
|
|
else
|
|
return -2;
|
|
|
|
if (ptr == end)
|
|
return 0;
|
|
|
|
while (i > 0)
|
|
{
|
|
if (header_value[i - 1] == ' ' || header_value[i - 1] == '\t')
|
|
i--;
|
|
else
|
|
break;
|
|
}
|
|
|
|
if (*ptr != ' ' && *ptr != '\t')
|
|
break;
|
|
|
|
ptr++;
|
|
header_value[i++] = ' ';
|
|
}
|
|
|
|
header_value[i] = '\0';
|
|
if (__match_message_header(parser->namebuf, strlen(parser->namebuf),
|
|
header_value, i, parser) < 0)
|
|
return -1;
|
|
|
|
parser->header_offset += ptr - begin;
|
|
parser->header_state = HPS_HEADER_NAME;
|
|
return 1;
|
|
}
|
|
|
|
static int __parse_message_header(const void *message, size_t size,
|
|
http_parser_t *parser)
|
|
{
|
|
const char *ptr;
|
|
size_t len;
|
|
int ret;
|
|
|
|
do
|
|
{
|
|
ptr = (const char *)message + parser->header_offset;
|
|
len = size - parser->header_offset;
|
|
if (parser->header_state == HPS_START_LINE)
|
|
ret = __parse_start_line(ptr, len, parser);
|
|
else if (parser->header_state == HPS_HEADER_VALUE)
|
|
ret = __parse_header_value(ptr, len, parser);
|
|
else /* if (parser->header_state == HPS_HEADER_NAME) */
|
|
{
|
|
ret = __parse_header_name(ptr, len, parser);
|
|
if (parser->header_state == HPS_HEADER_COMPLETE)
|
|
return 1;
|
|
}
|
|
} while (ret > 0);
|
|
|
|
return ret;
|
|
}
|
|
|
|
#define CHUNK_SIZE_MAX (2 * 1024 * 1024 * 1024U - HTTP_CHUNK_LINE_MAX - 4)
|
|
|
|
static int __parse_chunk_data(const char *ptr, size_t len,
|
|
http_parser_t *parser)
|
|
{
|
|
char chunk_line[HTTP_CHUNK_LINE_MAX];
|
|
size_t min = MIN(HTTP_CHUNK_LINE_MAX, len);
|
|
long chunk_size;
|
|
char *end;
|
|
size_t i;
|
|
|
|
for (i = 0; i < min; i++)
|
|
{
|
|
chunk_line[i] = ptr[i];
|
|
if (chunk_line[i] == '\r')
|
|
{
|
|
if (i == len - 1)
|
|
return 0;
|
|
|
|
if (ptr[i + 1] != '\n')
|
|
return -2;
|
|
|
|
chunk_line[i] = '\0';
|
|
chunk_size = strtol(chunk_line, &end, 16);
|
|
if (end == chunk_line)
|
|
return -2;
|
|
|
|
if (chunk_size == 0)
|
|
{
|
|
chunk_size = i + 2;
|
|
parser->chunk_state = CPS_TRAILER_PART;
|
|
}
|
|
else if ((unsigned long)chunk_size < CHUNK_SIZE_MAX)
|
|
{
|
|
chunk_size += i + 4;
|
|
if (len < (size_t)chunk_size)
|
|
return 0;
|
|
}
|
|
else
|
|
return -2;
|
|
|
|
parser->chunk_offset += chunk_size;
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
if (i == HTTP_CHUNK_LINE_MAX)
|
|
return -2;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __parse_trailer_part(const char *ptr, size_t len,
|
|
http_parser_t *parser)
|
|
{
|
|
size_t min = MIN(HTTP_TRAILER_LINE_MAX, len);
|
|
size_t i;
|
|
|
|
for (i = 0; i < min; i++)
|
|
{
|
|
if (ptr[i] == '\r')
|
|
{
|
|
if (i == len - 1)
|
|
return 0;
|
|
|
|
if (ptr[i + 1] != '\n')
|
|
return -2;
|
|
|
|
parser->chunk_offset += i + 2;
|
|
if (i == 0)
|
|
parser->chunk_state = CPS_CHUNK_COMPLETE;
|
|
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
if (i == HTTP_TRAILER_LINE_MAX)
|
|
return -2;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __parse_chunk(const void *message, size_t size,
|
|
http_parser_t *parser)
|
|
{
|
|
const char *ptr;
|
|
size_t len;
|
|
int ret;
|
|
|
|
do
|
|
{
|
|
ptr = (const char *)message + parser->chunk_offset;
|
|
len = size - parser->chunk_offset;
|
|
if (parser->chunk_state == CPS_CHUNK_DATA)
|
|
ret = __parse_chunk_data(ptr, len, parser);
|
|
else /* if (parser->chunk_state == CPS_TRAILER_PART) */
|
|
{
|
|
ret = __parse_trailer_part(ptr, len, parser);
|
|
if (parser->chunk_state == CPS_CHUNK_COMPLETE)
|
|
return 1;
|
|
}
|
|
} while (ret > 0);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void http_parser_init(int is_resp, http_parser_t *parser)
|
|
{
|
|
parser->header_state = HPS_START_LINE;
|
|
parser->header_offset = 0;
|
|
parser->transfer_length = (size_t)-1;
|
|
parser->content_length = is_resp ? (size_t)-1 : 0;
|
|
parser->version = NULL;
|
|
parser->method = NULL;
|
|
parser->uri = NULL;
|
|
parser->code = NULL;
|
|
parser->phrase = NULL;
|
|
INIT_LIST_HEAD(&parser->header_list);
|
|
parser->msgbuf = NULL;
|
|
parser->msgsize = 0;
|
|
parser->bufsize = 0;
|
|
parser->expect_continue = 0;
|
|
parser->keep_alive = 1;
|
|
parser->chunked = 0;
|
|
parser->complete = 0;
|
|
parser->is_resp = is_resp;
|
|
}
|
|
|
|
int http_parser_append_message(const void *buf, size_t *n,
|
|
http_parser_t *parser)
|
|
{
|
|
int ret;
|
|
|
|
if (parser->complete)
|
|
{
|
|
*n = 0;
|
|
return 1;
|
|
}
|
|
|
|
if (parser->msgsize + *n + 1 > parser->bufsize)
|
|
{
|
|
size_t new_size = MAX(HTTP_MSGBUF_INIT_SIZE, 2 * parser->bufsize);
|
|
void *new_base;
|
|
|
|
while (new_size < parser->msgsize + *n + 1)
|
|
new_size *= 2;
|
|
|
|
new_base = realloc(parser->msgbuf, new_size);
|
|
if (!new_base)
|
|
return -1;
|
|
|
|
parser->msgbuf = new_base;
|
|
parser->bufsize = new_size;
|
|
}
|
|
|
|
memcpy((char *)parser->msgbuf + parser->msgsize, buf, *n);
|
|
parser->msgsize += *n;
|
|
if (parser->header_state != HPS_HEADER_COMPLETE)
|
|
{
|
|
ret = __parse_message_header(parser->msgbuf, parser->msgsize, parser);
|
|
if (ret <= 0)
|
|
return ret;
|
|
|
|
if (parser->chunked)
|
|
{
|
|
parser->chunk_offset = parser->header_offset;
|
|
parser->chunk_state = CPS_CHUNK_DATA;
|
|
}
|
|
else if (parser->transfer_length == (size_t)-1)
|
|
parser->transfer_length = parser->content_length;
|
|
}
|
|
|
|
if (parser->transfer_length != (size_t)-1)
|
|
{
|
|
size_t total = parser->header_offset + parser->transfer_length;
|
|
|
|
if (parser->msgsize >= total)
|
|
{
|
|
*n -= parser->msgsize - total;
|
|
parser->msgsize = total;
|
|
parser->complete = 1;
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
if (!parser->chunked)
|
|
return 0;
|
|
|
|
if (parser->chunk_state != CPS_CHUNK_COMPLETE)
|
|
{
|
|
ret = __parse_chunk(parser->msgbuf, parser->msgsize, parser);
|
|
if (ret <= 0)
|
|
return ret;
|
|
}
|
|
|
|
*n -= parser->msgsize - parser->chunk_offset;
|
|
parser->msgsize = parser->chunk_offset;
|
|
parser->complete = 1;
|
|
return 1;
|
|
}
|
|
|
|
int http_parser_header_complete(http_parser_t *parser)
|
|
{
|
|
return parser->header_state == HPS_HEADER_COMPLETE;
|
|
}
|
|
|
|
int http_parser_get_body(const void **body, size_t *size,
|
|
http_parser_t *parser)
|
|
{
|
|
if (parser->complete && parser->header_state == HPS_HEADER_COMPLETE)
|
|
{
|
|
*body = (char *)parser->msgbuf + parser->header_offset;
|
|
*size = parser->msgsize - parser->header_offset;
|
|
((char *)parser->msgbuf)[parser->msgsize] = '\0';
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
int http_parser_set_method(const char *method, http_parser_t *parser)
|
|
{
|
|
method = strdup(method);
|
|
if (method)
|
|
{
|
|
free(parser->method);
|
|
parser->method = (char *)method;
|
|
return 0;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
int http_parser_set_uri(const char *uri, http_parser_t *parser)
|
|
{
|
|
uri = strdup(uri);
|
|
if (uri)
|
|
{
|
|
free(parser->uri);
|
|
parser->uri = (char *)uri;
|
|
return 0;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
int http_parser_set_version(const char *version, http_parser_t *parser)
|
|
{
|
|
version = strdup(version);
|
|
if (version)
|
|
{
|
|
free(parser->version);
|
|
parser->version = (char *)version;
|
|
return 0;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
int http_parser_set_code(const char *code, http_parser_t *parser)
|
|
{
|
|
code = strdup(code);
|
|
if (code)
|
|
{
|
|
free(parser->code);
|
|
parser->code = (char *)code;
|
|
return 0;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
int http_parser_set_phrase(const char *phrase, http_parser_t *parser)
|
|
{
|
|
phrase = strdup(phrase);
|
|
if (phrase)
|
|
{
|
|
free(parser->phrase);
|
|
parser->phrase = (char *)phrase;
|
|
return 0;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
void http_parser_deinit(http_parser_t *parser)
|
|
{
|
|
struct __header_line *line;
|
|
struct list_head *pos, *tmp;
|
|
|
|
list_for_each_safe(pos, tmp, &parser->header_list)
|
|
{
|
|
line = list_entry(pos, struct __header_line, list);
|
|
list_del(pos);
|
|
if (line->buf != (char *)(line + 1))
|
|
free(line->buf);
|
|
|
|
free(line);
|
|
}
|
|
|
|
free(parser->version);
|
|
free(parser->method);
|
|
free(parser->uri);
|
|
free(parser->code);
|
|
free(parser->phrase);
|
|
free(parser->msgbuf);
|
|
}
|
|
|
|
int http_header_cursor_next(const void **name, size_t *name_len,
|
|
const void **value, size_t *value_len,
|
|
http_header_cursor_t *cursor)
|
|
{
|
|
struct __header_line *line;
|
|
|
|
if (cursor->next->next != cursor->head)
|
|
{
|
|
cursor->next = cursor->next->next;
|
|
line = list_entry(cursor->next, struct __header_line, list);
|
|
*name = line->buf;
|
|
*name_len = line->name_len;
|
|
*value = line->buf + line->name_len + 2;
|
|
*value_len = line->value_len;
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
int http_header_cursor_find(const void *name, size_t name_len,
|
|
const void **value, size_t *value_len,
|
|
http_header_cursor_t *cursor)
|
|
{
|
|
struct __header_line *line;
|
|
|
|
while (cursor->next->next != cursor->head)
|
|
{
|
|
cursor->next = cursor->next->next;
|
|
line = list_entry(cursor->next, struct __header_line, list);
|
|
if (line->name_len == name_len)
|
|
{
|
|
if (strncasecmp(line->buf, name, name_len) == 0)
|
|
{
|
|
*value = line->buf + name_len + 2;
|
|
*value_len = line->value_len;
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|