diff --git a/README.md b/README.md index fadecfd..a557efe 100755 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ you can choose CyaSSl(wolfssl). * use [libev] as its event backend * support HTTPS: alternative OpenSSL and CyaSSl(wolfssl) * flexible and you can easily extend your application to have HTTP/HTTPS services -* Lua template: embed LUA code into HTML code, like embedding PHP to HTML(soon be supported) +* Lua template: embed LUA code into HTML code, like embedding PHP to HTML * code structure is concise and understandable, also suitable for learning # Why use [libev] as its backend? diff --git a/README_ZH.md b/README_ZH.md index a3dd18f..1eb570c 100755 --- a/README_ZH.md +++ b/README_ZH.md @@ -15,7 +15,7 @@ SSL后端可以选择OpenSSL和CyaSSl(wolfssl),如果你对大小敏感,那 * 使用[libev]作为其事件后端 * 支持HTTPS: 可以选择OpenSSL和CyaSSl(wolfssl) * 可伸缩:你可以非常方便的扩展你的应用程序,使之具备HTTP/HTTPS服务 -* Lua模板:嵌入LUA代码到HTML中,就像嵌入PHP到HTML中一样(即将支持) +* Lua模板:嵌入LUA代码到HTML中,就像嵌入PHP到HTML中一样 * 代码结构简洁通俗易懂,亦适合学习 # 为什么使用[libev]作为其事件后端? diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index ae751a0..b34cfc3 100755 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -1,2 +1,6 @@ add_executable(helloworld helloworld.c) target_link_libraries(helloworld uhttp) + +add_executable(lua_template lua_template.c) +target_link_libraries(lua_template uhttp) + diff --git a/example/lua_template.c b/example/lua_template.c new file mode 100755 index 0000000..a97329c --- /dev/null +++ b/example/lua_template.c @@ -0,0 +1,56 @@ +#include +#include +#include + +static void signal_cb(struct ev_loop *loop, ev_signal *w, int revents) +{ + printf("Got signal: %d\n", w->signum); + ev_break(loop, EVBREAK_ALL); +} + +static void lua_template(struct uh_connection *con) +{ + uh_template(con); +} + +int main(int argc, char **argv) +{ + struct ev_loop *loop = EV_DEFAULT; + ev_signal *sig_watcher = NULL; + struct uh_server *srv = NULL; + + uh_log_info("libuhttp version: %s", uh_version()); + + sig_watcher = calloc(1, sizeof(ev_signal)); + if (!sig_watcher) + return -1; + + ev_signal_init(sig_watcher, signal_cb, SIGINT); + ev_signal_start(loop, sig_watcher); + + srv = uh_server_new(loop, "0.0.0.0", 8000); + if (!srv) { + uh_log_err("uh_server_new failed"); + goto err; + } + +#if (UHTTP_SSL_ENABLED) + if (uh_ssl_init(srv, "server-cert.pem", "server-key.pem") < 0) + goto err; +#endif + + uh_register_default_hook(srv, lua_template); + + uh_log_info("Listen on 8000..."); + + ev_run(loop, 0); + +err: + free(sig_watcher); + uh_server_free(srv); + + return 0; +} + + + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b1481ca..e20607b 100755 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -7,10 +7,11 @@ set(UHTTP_VERSION_PATCH 1) find_package(Libev REQUIRED) find_package(OpenSSL) find_package(CyaSSL) +find_package(Lua) -include_directories(${LIBEV_INCLUDE_DIRS}) -set(EXTRA_LIBS ${LIBEV_LIBRARIES}) -set(SOURCE_FILES uhttp.c log.c buf.c ssl.c parser.c) +include_directories(${LIBEV_INCLUDE_DIR} ${LUA_INCLUDE_DIR}) +set(EXTRA_LIBS ${LIBEV_LIBRARIES} ${LUA_LIBRARIES}) +set(SOURCE_FILES uhttp.c log.c buf.c ssl.c parser.c template.c) set(UHTTP_DEBUG_CONFIG 0) option(UHTTP_DEBUG "Turn on debug" OFF) diff --git a/src/include/internal.h b/src/include/internal.h index 59620c4..698a02a 100755 --- a/src/include/internal.h +++ b/src/include/internal.h @@ -18,6 +18,8 @@ #ifndef _UHTTP_INTERNAL_H #define _UHTTP_INTERNAL_H +#include + #include "list.h" #include "uhttp/uhttp.h" @@ -52,6 +54,8 @@ struct uh_server { #if (UHTTP_SSL_ENABLED) void *ssl_ctx; #endif + char *docroot; + lua_State *L; ev_io read_watcher; struct ev_loop *loop; uh_hookfn_t default_cb; @@ -81,6 +85,8 @@ struct uh_connection { unsigned char flags; struct uh_buf read_buf; struct uh_buf write_buf; + ev_child child_watcher; + ev_io read_watcher_lua; ev_io read_watcher; ev_io write_watcher; ev_timer timer_watcher; diff --git a/src/include/uhttp/uhttp.h b/src/include/uhttp/uhttp.h index 7a49178..966f1b1 100755 --- a/src/include/uhttp/uhttp.h +++ b/src/include/uhttp/uhttp.h @@ -38,6 +38,12 @@ struct uh_server *uh_server_new(struct ev_loop *loop, const char *ipaddr, int po /* frees a uhttp server instance. */ void uh_server_free(struct uh_server *srv); +/* Sets document root. */ +void uh_set_docroot(struct uh_server *srv, const char *path); + +/* Processing Lua templates */ +void uh_template(struct uh_connection *con); + /* Sends data to the connection. */ int uh_send(struct uh_connection *con, const void *buf, int len); @@ -111,11 +117,11 @@ struct uh_str *uh_get_query(struct uh_connection *con); struct uh_str uh_get_var(struct uh_connection *con, const char *name); struct uh_str *uh_get_header(struct uh_connection *con, const char *name); -int uh_get_con_sock(struct uh_connection *con); - /* Unescapes strings like '%7B1,%202,%203%7D' would become '{1, 2, 3}' */ int uh_unescape(const char *str, int len, char *out, int olen); +int uh_get_con_sock(struct uh_connection *con); + #if (UHTTP_SSL_ENABLED) /* Init ssl for the server */ int uh_ssl_init(struct uh_server *srv, const char *cert, const char *key); diff --git a/src/template.c b/src/template.c new file mode 100755 index 0000000..4b952f3 --- /dev/null +++ b/src/template.c @@ -0,0 +1,677 @@ +/* + * Copyright (C) 2017 Jianhui Zhao + * + * based on https://git.lede-project.org/project/luci.git modules/luci-base/src/template_parser.c + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "internal.h" +#include "uhttp/uhttp.h" + +/* code types */ +#define T_TYPE_INIT 0 +#define T_TYPE_TEXT 1 +#define T_TYPE_COMMENT 2 +#define T_TYPE_EXPR 3 +#define T_TYPE_INCLUDE 4 +#define T_TYPE_CODE 5 +#define T_TYPE_EOF 6 + +/* leading and trailing code for different types */ +static const char *gen_code[9][2] = { + { NULL, NULL }, /* T_TYPE_INIT */ + { "io.write(\"", "\")" }, /* T_TYPE_TEXT */ + { NULL, NULL }, /* T_TYPE_COMMENT */ + { "io.write(tostring(", " or \"\"))" }, /* T_TYPE_EXPR */ + { "include(\"", "\")" }, /* T_TYPE_INCLUDE */ + { NULL, " " }, /* T_TYPE_CODE */ + { NULL, NULL }, /* T_TYPE_EOF */ +}; + +/* buffer object */ +struct template_buffer { + char *data; + char *dptr; + unsigned int size; + unsigned int fill; +}; + +struct template_chunk { + const char *s; + const char *e; + int type; + int line; +}; + +/* parser state */ +struct template_parser { + int fd; + uint32_t size; + char *data; + char *off; + char *gc; + int line; + int in_expr; + int strip_before; + int strip_after; + struct template_chunk prv_chunk; + struct template_chunk cur_chunk; + const char *file; +}; + +/* initialize a buffer object */ +struct template_buffer *buf_init(int size) +{ + struct template_buffer *buf; + + if (size <= 0) + size = 1024; + + buf = (struct template_buffer *)malloc(sizeof(struct template_buffer)); + + if (buf != NULL) + { + buf->fill = 0; + buf->size = size; + buf->data = malloc(buf->size); + + if (buf->data != NULL) { + buf->dptr = buf->data; + buf->data[0] = 0; + return buf; + } + free(buf); + } + + return NULL; +} + +/* grow buffer */ +static int buf_grow(struct template_buffer *buf, int size) +{ + unsigned int off = (buf->dptr - buf->data); + char *data; + + if (size <= 0) + size = 1024; + + data = realloc(buf->data, buf->size + size); + + if (data != NULL) { + buf->data = data; + buf->dptr = data + off; + buf->size += size; + return buf->size; + } + + return 0; +} + +/* put one char into buffer object */ +static int buf_putchar(struct template_buffer *buf, char c) +{ + if( ((buf->fill + 1) >= buf->size) && !buf_grow(buf, 0) ) + return 0; + + *(buf->dptr++) = c; + *(buf->dptr) = 0; + + buf->fill++; + return 1; +} + +/* append data to buffer */ +static int buf_append(struct template_buffer *buf, const char *s, int len) +{ + if ((buf->fill + len + 1) >= buf->size) { + if (!buf_grow(buf, len + 1)) + return 0; + } + + memcpy(buf->dptr, s, len); + buf->fill += len; + buf->dptr += len; + + *(buf->dptr) = 0; + + return len; +} + +/* read buffer length */ +static int buf_length(struct template_buffer *buf) +{ + return buf->fill; +} + +/* destroy buffer object and return pointer to data */ +static char *buf_destroy(struct template_buffer *buf) +{ + char *data = buf->data; + + free(buf); + return data; +} + +static void luastr_escape(struct template_buffer *out, const char *s, unsigned int l, + int escape_xml) +{ + int esl; + char esq[8]; + char *ptr; + + for (ptr = (char *)s; ptr < (s + l); ptr++) { + switch (*ptr) { + case '\\': + buf_append(out, "\\\\", 2); + break; + + case '"': + if (escape_xml) + buf_append(out, """, 5); + else + buf_append(out, "\\\"", 2); + break; + + case '\n': + buf_append(out, "\\n", 2); + break; + + case '\'': + case '&': + case '<': + case '>': + if (escape_xml) { + esl = snprintf(esq, sizeof(esq), "&#%i;", *ptr); + buf_append(out, esq, esl); + break; + } + + default: + buf_putchar(out, *ptr); + } + } +} + +/* Simple strstr() like function that takes len arguments for both haystack and needle. */ +static char *strfind(char *haystack, int hslen, const char *needle, int ndlen) +{ + int match = 0; + int i, j; + + for( i = 0; i < hslen; i++ ) { + if( haystack[i] == needle[0] ) { + match = ((ndlen == 1) || ((i + ndlen) <= hslen)); + + for( j = 1; (j < ndlen) && ((i + j) < hslen); j++ ) { + if( haystack[i+j] != needle[j] ) { + match = 0; + break; + } + } + + if( match ) + return &haystack[i]; + } + } + + return NULL; +} + + +static int template_error(lua_State *L, struct template_parser *parser) +{ + const char *err = luaL_checkstring(L, -1); + const char *off = parser->prv_chunk.s; + const char *ptr; + char msg[1024]; + int line = 0; + int chunkline = 0; + + if ((ptr = strfind((char *)err, strlen(err), "]:", 2)) != NULL) { + chunkline = atoi(ptr + 2) - parser->prv_chunk.line; + + while (*ptr) { + if (*ptr++ == ' ') { + err = ptr; + break; + } + } + } + + if (strfind((char *)err, strlen(err), "'char(27)'", 10) != NULL) { + off = parser->data + parser->size; + err = "'%>' expected before end of file"; + chunkline = 0; + } + + for (ptr = parser->data; ptr < off; ptr++) + if (*ptr == '\n') + line++; + + snprintf(msg, sizeof(msg), "Syntax error in %s:%d: %s", + parser->file ? parser->file : "[string]", line + chunkline, err ? err : "(unknown error)"); + + lua_pushnil(L); + lua_pushinteger(L, line + chunkline); + lua_pushstring(L, msg); + + return 3; +} + + +static void template_close(struct template_parser *parser) +{ + if (!parser) + return; + + if (parser->gc != NULL) + free(parser->gc); + + /* if file is not set, we were parsing a string */ + if (parser->file) { + if ((parser->data != NULL) && (parser->data != MAP_FAILED)) + munmap(parser->data, parser->size); + + if (parser->fd >= 0) + close(parser->fd); + } + + free(parser); +} + +static void template_text(struct template_parser *parser, const char *e) +{ + const char *s = parser->off; + + if (s < (parser->data + parser->size)) { + if (parser->strip_after) { + while ((s <= e) && isspace(*s)) + s++; + } + + parser->cur_chunk.type = T_TYPE_TEXT; + } else { + parser->cur_chunk.type = T_TYPE_EOF; + } + + parser->cur_chunk.line = parser->line; + parser->cur_chunk.s = s; + parser->cur_chunk.e = e; +} + +static void template_code(struct template_parser *parser, const char *e) +{ + const char *s = parser->off; + + parser->strip_before = 0; + parser->strip_after = 0; + + if (*s == '-') { + parser->strip_before = 1; + for (s++; (s <= e) && (*s == ' ' || *s == '\t'); s++); + } + + if (*(e-1) == '-') { + parser->strip_after = 1; + for (e--; (e >= s) && (*e == ' ' || *e == '\t'); e--); + } + + switch (*s) { + /* comment */ + case '#': + s++; + parser->cur_chunk.type = T_TYPE_COMMENT; + break; + + /* include */ + case '+': + s++; + parser->cur_chunk.type = T_TYPE_INCLUDE; + break; + + /* expr */ + case '=': + s++; + parser->cur_chunk.type = T_TYPE_EXPR; + break; + + /* code */ + default: + parser->cur_chunk.type = T_TYPE_CODE; + break; + } + + parser->cur_chunk.line = parser->line; + parser->cur_chunk.s = s; + parser->cur_chunk.e = e; +} + + + +static const char *template_format_chunk(struct template_parser *parser, size_t *sz) +{ + const char *s, *p; + const char *head, *tail; + struct template_chunk *c = &parser->prv_chunk; + struct template_buffer *buf; + + *sz = 0; + s = parser->gc = NULL; + + if (parser->strip_before && c->type == T_TYPE_TEXT) { + while ((c->e > c->s) && isspace(*(c->e - 1))) + c->e--; + } + + /* empty chunk */ + if (c->s == c->e) { + if (c->type == T_TYPE_EOF) { + *sz = 0; + s = NULL; + } else { + *sz = 1; + s = " "; + } + } else if ((buf = buf_init(c->e - c->s)) != NULL) { /* format chunk */ + if ((head = gen_code[c->type][0]) != NULL) + buf_append(buf, head, strlen(head)); + + switch (c->type) { + case T_TYPE_TEXT: + luastr_escape(buf, c->s, c->e - c->s, 0); + break; + + case T_TYPE_EXPR: + buf_append(buf, c->s, c->e - c->s); + for (p = c->s; p < c->e; p++) + parser->line += (*p == '\n'); + break; + + case T_TYPE_INCLUDE: + luastr_escape(buf, c->s, c->e - c->s, 0); + break; + + case T_TYPE_CODE: + buf_append(buf, c->s, c->e - c->s); + for (p = c->s; p < c->e; p++) + parser->line += (*p == '\n'); + break; + } + + if ((tail = gen_code[c->type][1]) != NULL) + buf_append(buf, tail, strlen(tail)); + + *sz = buf_length(buf); + s = parser->gc = buf_destroy(buf); + + if (!*sz) { + *sz = 1; + s = " "; + } + } + + return s; +} + +static const char *template_reader(lua_State *L, void *ud, size_t *sz) +{ + struct template_parser *parser = ud; + int rem = parser->size - (parser->off - parser->data); + char *tag; + + parser->prv_chunk = parser->cur_chunk; + + /* free previous string */ + if (parser->gc) { + free(parser->gc); + parser->gc = NULL; + } + + /* before tag */ + if (!parser->in_expr) { + if ((tag = strfind(parser->off, rem, "<%", 2)) != NULL) { + template_text(parser, tag); + parser->off = tag + 2; + parser->in_expr = 1; + } else { + template_text(parser, parser->data + parser->size); + parser->off = parser->data + parser->size; + } + } else { /* inside tag */ + if ((tag = strfind(parser->off, rem, "%>", 2)) != NULL) { + template_code(parser, tag); + parser->off = tag + 2; + parser->in_expr = 0; + } else { + /* unexpected EOF */ + template_code(parser, parser->data + parser->size); + + *sz = 1; + return "\033"; + } + } + + return template_format_chunk(parser, sz); +} + +static int template_L_do_parse(lua_State *L, struct template_parser *parser, const char *chunkname) +{ + int lua_status, rv; + + if (!parser) { + lua_pushnil(L); + lua_pushinteger(L, errno); + lua_pushstring(L, strerror(errno)); + return 3; + } + +#if LUA_VERSION_NUM > 501 + lua_status = lua_load(L, template_reader, parser, chunkname, NULL); +#else + lua_status = lua_load(L, template_reader, parser, chunkname); +#endif + if (lua_status == 0) + rv = 1; + else + rv = template_error(L, parser); + + template_close(parser); + + return rv; +} + +struct template_parser * template_open(const char *file) +{ + struct stat s; + struct template_parser *parser; + + if (!(parser = malloc(sizeof(*parser)))) + goto err; + + memset(parser, 0, sizeof(*parser)); + parser->fd = -1; + parser->file = file; + + if (stat(file, &s)) + goto err; + + if ((parser->fd = open(file, O_RDONLY)) < 0) + goto err; + + parser->size = s.st_size; + parser->data = mmap(NULL, parser->size, PROT_READ, MAP_PRIVATE, + parser->fd, 0); + + if (parser->data != MAP_FAILED) { + parser->off = parser->data; + parser->cur_chunk.type = T_TYPE_INIT; + parser->cur_chunk.s = parser->data; + parser->cur_chunk.e = parser->data; + + return parser; + } + +err: + template_close(parser); + return NULL; +} + +static void template_read_cb(struct ev_loop *loop, ev_io *w, int revents) +{ + struct uh_connection *con = container_of(w, struct uh_connection, read_watcher_lua); + char buf[1024] = ""; + int len; + + len = read(w->fd, buf, sizeof(buf)); + if (len > 0) + uh_send_chunk(con, buf, len); + else if (len == 0) { + uh_send_chunk(con, NULL, 0); + close(w->fd); + ev_io_stop(con->srv->loop, w); + + if (!(con->flags & UH_CON_CLOSE)) + con->flags |= UH_CON_REUSE; + } +} + +static void child_cb(struct ev_loop *loop, ev_child *w, int revents) +{ + struct uh_connection *con = container_of(w, struct uh_connection, child_watcher); + ev_child_stop(con->srv->loop, w); +} + +void uh_template(struct uh_connection *con) +{ + struct template_parser *parser; + lua_State *L = con->srv->L; + pid_t pid; + int pipefd[2]; + static char path[PATH_MAX] = ""; + struct stat st; + struct sockaddr_in addr; + socklen_t addrlen = sizeof(addr); + struct uh_str *ustr; + + strcpy(path, con->srv->docroot); + strncat(path, con->req.path.at, con->req.path.len); + + if (stat(path, &st) < 0) { + uh_send_error(con, HTTP_STATUS_NOT_FOUND, NULL); + return; + } + + uh_log_debug("Path:%s", path); + + + if (!L) { + L = luaL_newstate(); + if (!L) { + uh_log_err("cannot create LUA state: not enough memory"); + goto err; + } + + con->srv->L = L; + luaL_openlibs(L); + } + + /* + * Add all variables to the global environment of LUA. + * eg. <%=_UHTTP["REMOTE_HOST"]%> + */ + + lua_newtable(L); + + lua_pushstring(L, uh_get_method_str(con)); + lua_setfield(L, -2, "HTTP_METHOD"); + + getpeername(con->sock, (struct sockaddr *)&addr, &addrlen); + lua_pushstring(L, inet_ntoa(addr.sin_addr)); + lua_setfield(L, -2, "REMOTE_HOST"); + + ustr = uh_get_url(con); + lua_pushlstring(L, ustr->at, ustr->len); + lua_setfield(L, -2, "HTTP_URL"); + + ustr = uh_get_path(con); + lua_pushlstring(L, ustr->at, ustr->len); + lua_setfield(L, -2, "HTTP_PATH"); + + lua_setglobal(L, "_UHTTP"); + + if (pipe2(pipefd, O_CLOEXEC | O_NONBLOCK) < 0) { + uh_log_err("pipe"); + goto err; + } + + uh_send_head(con, HTTP_STATUS_OK, -1, "Pragma: no-cache\r\nCache-Control: no-cache\r\n"); + + pid = fork(); + switch (pid) { + case -1: + uh_log_err("fork"); + goto err; + break; + + case 0: + close(0); + close(1); + close(pipefd[0]); + dup2(pipefd[1], 1); + + parser = template_open(path); + if (!parser) { + uh_log_err("template_open failed"); + return; + } + + + if ((template_L_do_parse(L, parser, path) != 1) || lua_pcall(L, 0, 0, 0)) { + uh_printf_chunk(con, "

Lua Error

\n%s\n", lua_tostring(L, -1)); + uh_printf_chunk(con, "\n"); + uh_send_chunk(con, NULL, 0); + lua_pop(L, -1); + } else { + uh_send_chunk(con, NULL, 0); + } + exit(0); + break; + + default: + close(pipefd[1]); + ev_io_init(&con->read_watcher_lua, template_read_cb, pipefd[0], EV_READ); + ev_io_start(con->srv->loop, &con->read_watcher_lua); + + ev_child_init(&con->child_watcher, child_cb, pid, 0); + ev_child_start(con->srv->loop, &con->child_watcher); + break; + } + + return; +err: + uh_send_error(con, HTTP_STATUS_INTERNAL_SERVER_ERROR, NULL); +} diff --git a/src/uhttp.c b/src/uhttp.c index f23d65e..3db56fd 100755 --- a/src/uhttp.c +++ b/src/uhttp.c @@ -21,6 +21,7 @@ #include #include #include +#include #include "internal.h" #include "uhttp/uhttp.h" @@ -346,9 +347,12 @@ struct uh_server *uh_server_new(struct ev_loop *loop, const char *ipaddr, int po struct sockaddr_in addr; int sock = -1, on = 1; ev_io *read_watcher; + char buf[PATH_MAX] = ""; addr.sin_family = AF_INET; addr.sin_port = htons(port); + + signal(SIGPIPE, SIG_IGN); if (inet_pton(AF_INET, ipaddr, &addr.sin_addr) <= 0) { uh_log_err("invalid ipaddr"); @@ -360,6 +364,14 @@ struct uh_server *uh_server_new(struct ev_loop *loop, const char *ipaddr, int po uh_log_err("calloc"); return NULL; } + + if (!realpath(".", buf)) { + uh_log_err("Unable to determine work dir"); + goto err; + } + + srv->docroot = strdup(buf); + uh_log_debug("docroot:%s", srv->docroot); INIT_LIST_HEAD(&srv->hooks); INIT_LIST_HEAD(&srv->connections); @@ -401,7 +413,10 @@ void uh_server_free(struct uh_server *srv) if (srv) { struct uh_connection *con, *tmp_c; struct uh_hook *h, *tmp_h; - + + if (srv->docroot) + free(srv->docroot); + if (srv->sock > 0) close(srv->sock); @@ -423,6 +438,12 @@ void uh_server_free(struct uh_server *srv) } } +void uh_set_docroot(struct uh_server *srv, const char *path) +{ + srv->docroot = strdup(path); + uh_log_debug("docroot:%s", srv->docroot); +} + int uh_send(struct uh_connection *con, const void *buf, int len) { len = uh_buf_append(&con->write_buf, buf, len);