From 0e1eaa525a8f4e7d5aa99a615217437e73c3c9b7 Mon Sep 17 00:00:00 2001 From: Alexey Melnichuk Date: Thu, 12 Oct 2017 14:22:24 +0300 Subject: [PATCH 01/16] Add. New cURL MIME API --- .travis.yml | 28 +- appveyor.yml | 2 +- examples/lcurl/smtp-mime.lua | 85 ++++++ rockspecs/lua-curl-scm-0.rockspec | 2 +- src/lceasy.c | 95 +++++- src/lceasy.h | 21 +- src/lcmime.c | 479 ++++++++++++++++++++++++++++++ src/lcmime.h | 66 ++++ src/lcopteasy.h | 4 + src/lcurl.c | 8 +- src/lcutils.c | 12 +- src/lcutils.h | 2 + test/run.lua | 1 + test/test_curl.lua | 30 +- test/test_easy.lua | 65 +--- test/test_form.lua | 70 +++-- test/test_mime.lua | 188 ++++++++++++ test/utils.lua | 89 ++++++ 18 files changed, 1116 insertions(+), 131 deletions(-) create mode 100644 examples/lcurl/smtp-mime.lua create mode 100644 src/lcmime.c create mode 100644 src/lcmime.h create mode 100644 test/test_mime.lua create mode 100644 test/utils.lua diff --git a/.travis.yml b/.travis.yml index 098c160..7e0facd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,23 +9,17 @@ env: matrix: include: - - compiler: ": Lua51-osx" - env: LUA="lua 5.1" + - env: LUA="lua 5.1" os: osx - - compiler: ": Lua51" - env: LUA="lua 5.1" + - env: LUA="lua 5.1" os: linux - - compiler: ": Lua52" - env: LUA="lua 5.2" + - env: LUA="lua 5.2" os: linux - - compiler: ": Lua53" - env: LUA="lua 5.3" + - env: LUA="lua 5.3" os: linux - - compiler: ": LuaJIT20" - env: LUA="luajit 2.0" + - env: LUA="luajit 2.0" os: linux - - compiler: ": LuaJIT21" - env: LUA="luajit 2.1" + - env: LUA="luajit 2.1" os: linux cache: @@ -36,10 +30,9 @@ cache: branches: only: - master + - curl_mime before_install: - - export CC=gcc - - gcc --version - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then export PATH=$PATH:~/Library/Python/2.7/bin/; fi - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then export LCURL_LD_FLAGS="-bundle -undefined dynamic_lookup -all_load --coverage"; fi - pip install --user cpp-coveralls @@ -68,6 +61,13 @@ script: # - lunit.sh test_form.lua # - lunit.sh test_curl.lua +before_cache: + # - cd $TRAVIS_BUILD_DIR/test + # - coveralls -b .. -r .. --dump c.report.json + # - luacov-coveralls -j c.report.json -v + # - luarocks remove lluv-gsmmodem + # - rm -f /home/travis/.cache/pip/log/debug.log + after_success: - coveralls -b .. -r .. --dump c.report.json - luacov-coveralls -j c.report.json -v diff --git a/appveyor.yml b/appveyor.yml index f3446e4..08190a5 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -7,7 +7,7 @@ shallow_clone: true environment: LR_EXTERNAL: c:\external - CURL_VER: 7.55.1 + CURL_VER: 7.56.0 matrix: - LUA: "lua 5.1" diff --git a/examples/lcurl/smtp-mime.lua b/examples/lcurl/smtp-mime.lua new file mode 100644 index 0000000..f829726 --- /dev/null +++ b/examples/lcurl/smtp-mime.lua @@ -0,0 +1,85 @@ +local curl = require "lcurl" + +local SMTP = { + url = "smtp://mail.example.com"; +} + +local FROM = "" +local TO = "" +local CC = "" +local FILE = "smtp-mime.lua" -- if you send this file do not forget it may have mail password +local CT_FILE = "application/lua" + +local DUMP_MIME = false + +local headers = { + "Date: Tue, 22 Aug 2017 14:08:43 +0100", + "To: " .. TO, + "From: " .. FROM .. " (Example User)", + "Cc: " .. CC .. " (Another example User)", + "Message-ID: ", + "Subject: example sending a MIME-formatted message", +} + +local inline_text = "" + .. "This is the inline text message of the e-mail.\r\n" + .. "\r\n" + .. " It could be a lot of lines that would be displayed in an e-mail\r\n" + .. "viewer that is not able to handle HTML.\r\n" + +local inline_html = "" + .. "\r\n" + .. "

This is the inline HTML message of the e-mail.

" + .. "
\r\n" + .. "

It could be a lot of HTML data that would be displayed by " + .. "e-mail viewers able to handle HTML.

" + .. "\r\n" + +local function dump_mime(type, data) + if type == curl.INFO_DATA_OUT then io.write(data) end +end + +local easy = curl.easy() + +local mime = easy:mime() do + local alt = easy:mime() + alt + :addpart() + :data(inline_html, "text/html") + alt + :addpart() + :data(inline_text) + mime:addpart() + :subparts(alt, "multipart/alternative", { + "Content-Disposition: inline" + }) + mime + :addpart() + :filedata(FILE, CT_FILE) +end + +easy:setopt{ + url = SMTP.url, + mail_from = FROM, + mail_rcpt = {TO, CC}, + httpheader = headers; + mimepost = mime; + ssl_verifyhost = false; + ssl_verifypeer = false; + username = SMTP.user; + password = SMTP.password; + upload = true; +} + +if DUMP_MIME then + easy:setopt{ + verbose = true; + debugfunction = dump_mime; + } +end + +easy:perform() + +easy:close() + +mime:free() diff --git a/rockspecs/lua-curl-scm-0.rockspec b/rockspecs/lua-curl-scm-0.rockspec index 22f5a00..2f284a8 100644 --- a/rockspecs/lua-curl-scm-0.rockspec +++ b/rockspecs/lua-curl-scm-0.rockspec @@ -63,7 +63,7 @@ build = { sources = { "src/l52util.c", "src/lceasy.c", "src/lcerror.c", "src/lchttppost.c", "src/lcurl.c", "src/lcutils.c", - "src/lcmulti.c", "src/lcshare.c", + "src/lcmulti.c", "src/lcshare.c","src/lcmime.c", }, incdirs = { "$(CURL_INCDIR)" }, libdirs = { "$(CURL_LIBDIR)" } diff --git a/src/lceasy.c b/src/lceasy.c index f3a7cac..936a286 100644 --- a/src/lceasy.c +++ b/src/lceasy.c @@ -1,7 +1,7 @@ /****************************************************************************** * Author: Alexey Melnichuk * -* Copyright (C) 2014 Alexey Melnichuk +* Copyright (C) 2014-2017 Alexey Melnichuk * * Licensed according to the included 'LICENSE' document * @@ -15,6 +15,7 @@ #include "lchttppost.h" #include "lcshare.h" #include "lcmulti.h" +#include "lcmime.h" #include static const char *LCURL_ERROR_TAG = "LCURL_ERROR_TAG"; @@ -29,7 +30,7 @@ static const char *LCURL_EASY = LCURL_EASY_NAME; #endif /* Before call curl_XXX function which can call any callback - * need set Curren Lua thread pointer in easy/multi contexts. + * need set Current Lua thread pointer in easy/multi contexts. * But it also possible that we already in callback call. * E.g. `curl_easy_pause` function may be called from write callback. * and it even may be called in different thread. @@ -41,7 +42,7 @@ static const char *LCURL_EASY = LCURL_EASY_NAME; * ``` * So we have to restore previews Lua state in callback contexts. * But if previews Lua state is NULL then we can just do not set it back. - * But set it to NULL make esier debug code. + * But set it to NULL make easier to debug code. */ void lcurl__easy_assign_lua(lua_State *L, lcurl_easy_t *p, lua_State *value, int assign_multi){ if(p->multi && assign_multi){ @@ -52,6 +53,11 @@ void lcurl__easy_assign_lua(lua_State *L, lcurl_easy_t *p, lua_State *value, int if(p->post){ p->post->L = value; } +#if LCURL_CURL_VER_GE(7,56,0) + if(p->mime){ + lcurl_mime_set_lua(L, p->mime, value); + } +#endif } } @@ -74,6 +80,9 @@ int lcurl_easy_create(lua_State *L, int error_mode){ p->L = NULL; p->post = NULL; p->multi = NULL; +#if LCURL_CURL_VER_GE(7,56,0) + p->mime = NULL; +#endif p->storage = lcurl_storage_init(L); p->wr.cb_ref = p->wr.ud_ref = LUA_NOREF; p->rd.cb_ref = p->rd.ud_ref = LUA_NOREF; @@ -125,6 +134,8 @@ static int lcurl_easy_cleanup(lua_State *L){ // In my tests when I cleanup some easy handle. // timerfunction called only for single multi handle. + // Also may be this function may call `close` callback + // for `curl_mimepart` structure. curL = p->L; lcurl__easy_assign_lua(L, p, L, 1); curl_easy_cleanup(p->curl); #ifndef LCURL_RESET_NULL_LUA @@ -135,6 +146,11 @@ static int lcurl_easy_cleanup(lua_State *L){ p->curl = NULL; } + p->post = NULL; +#if LCURL_CURL_VER_GE(7,56,0) + p->mime = NULL; +#endif + if(p->storage != LUA_NOREF){ p->storage = lcurl_storage_free(L, p->storage); } @@ -266,6 +282,15 @@ static int lcurl_easy_reset(lua_State *L){ return 1; } +#if LCURL_CURL_VER_GE(7,56,0) + +static int lcurl_easy_mime(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + return lcurl_mime_create(L, p->err_mode); +} + +#endif + //{ OPTIONS //{ set @@ -447,6 +472,26 @@ static int lcurl_easy_set_STREAM_DEPENDS_E(lua_State *L){ #endif +#if LCURL_CURL_VER_GE(7,56,0) + +static int lcurl_easy_set_MIMEPOST(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + lcurl_mime_t *mime = lcurl_getmime_at(L, 2); + CURLcode code = curl_easy_setopt(p->curl, CURLOPT_MIMEPOST, mime->mime); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + + lcurl_storage_preserve_iv(L, p->storage, CURLOPT_MIMEPOST, 2); + + p->mime = mime; + + lua_settop(L, 1); + return 1; +} + +#endif + //} //{ unset @@ -775,6 +820,25 @@ static int lcurl_easy_unset_STREAM_DEPENDS_E(lua_State *L){ #endif +#if LCURL_CURL_VER_GE(7,56,0) + +static int lcurl_easy_unset_MIMEPOST(lua_State *L){ + lcurl_easy_t *p = lcurl_geteasy(L); + CURLcode code = curl_easy_setopt(p->curl, CURLOPT_MIMEPOST, NULL); + if(code != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, code); + } + + lcurl_storage_remove_i(L, p->storage, CURLOPT_MIMEPOST); + + p->mime = NULL; + + lua_settop(L, 1); + return 1; +} + +#endif + //} //} @@ -1004,7 +1068,7 @@ static int lcurl_easy_set_WRITEFUNCTION(lua_State *L){ //{ Reader -static size_t lcurl_read_callback(lua_State *L, +size_t lcurl_read_callback(lua_State *L, lcurl_callback_t *rd, lcurl_read_buffer_t *rbuffer, char *buffer, size_t size, size_t nitems ){ @@ -1477,6 +1541,9 @@ static int lcurl_easy_setopt(lua_State *L){ #if LCURL_CURL_VER_GE(7,46,0) OPT_ENTRY(stream_depends, STREAM_DEPENDS, TTT, 0, 0) OPT_ENTRY(stream_depends_e, STREAM_DEPENDS_E, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,56,0) + OPT_ENTRY(mimepost, MIMEPOST, TTT, 0, 0) #endif } #undef OPT_ENTRY @@ -1511,6 +1578,9 @@ static int lcurl_easy_unsetopt(lua_State *L){ #if LCURL_CURL_VER_GE(7,46,0) OPT_ENTRY(stream_depends, STREAM_DEPENDS, TTT, 0, 0) OPT_ENTRY(stream_depends_e, STREAM_DEPENDS_E, TTT, 0, 0) +#endif +#if LCURL_CURL_VER_GE(7,56,0) + OPT_ENTRY(mimepost, MIMEPOST, TTT, 0, 0) #endif } #undef OPT_ENTRY @@ -1582,7 +1652,7 @@ static const struct luaL_Reg lcurl_easy_methods[] = { OPT_ENTRY(seekfunction, SEEKFUNCTION, TTT, 0, 0) OPT_ENTRY(debugfunction, DEBUGFUNCTION, TTT, 0, 0) #if LCURL_CURL_VER_GE(7,21,0) - OPT_ENTRY(fnmatch_function, FNMATCH_FUNCTION, TTT, 0, 0) + OPT_ENTRY(fnmatch_function, FNMATCH_FUNCTION, TTT, 0, 0) OPT_ENTRY(chunk_bgn_function, CHUNK_BGN_FUNCTION, TTT, 0, 0) OPT_ENTRY(chunk_end_function, CHUNK_END_FUNCTION, TTT, 0, 0) #endif @@ -1590,6 +1660,9 @@ static const struct luaL_Reg lcurl_easy_methods[] = { OPT_ENTRY(stream_depends, STREAM_DEPENDS, TTT, 0, 0) OPT_ENTRY(stream_depends_e, STREAM_DEPENDS_E, TTT, 0, 0) #endif +#if LCURL_CURL_VER_GE(7,56,0) + OPT_ENTRY(mimepost, MIMEPOST, TTT, 0, 0) +#endif #undef OPT_ENTRY #define OPT_ENTRY(L, N, T, S, D) { "unsetopt_"#L, lcurl_easy_unset_##N }, @@ -1604,7 +1677,7 @@ static const struct luaL_Reg lcurl_easy_methods[] = { OPT_ENTRY(seekfunction, SEEKFUNCTION, TTT, 0, 0) OPT_ENTRY(debugfunction, DEBUGFUNCTION, TTT, 0, 0) #if LCURL_CURL_VER_GE(7,21,0) - OPT_ENTRY(fnmatch_function, FNMATCH_FUNCTION, TTT, 0, 0) + OPT_ENTRY(fnmatch_function, FNMATCH_FUNCTION, TTT, 0, 0) OPT_ENTRY(chunk_bgn_function, CHUNK_BGN_FUNCTION, TTT, 0, 0) OPT_ENTRY(chunk_end_function, CHUNK_END_FUNCTION, TTT, 0, 0) #endif @@ -1612,12 +1685,19 @@ static const struct luaL_Reg lcurl_easy_methods[] = { OPT_ENTRY(stream_depends, STREAM_DEPENDS, TTT, 0, 0) OPT_ENTRY(stream_depends_e, STREAM_DEPENDS_E, TTT, 0, 0) #endif +#if LCURL_CURL_VER_GE(7,56,0) + OPT_ENTRY(mimepost, MIMEPOST, TTT, 0, 0) +#endif #undef OPT_ENTRY #define OPT_ENTRY(L, N, T, S) { "getinfo_"#L, lcurl_easy_get_##N }, #include "lcinfoeasy.h" #undef OPT_ENTRY +#if LCURL_CURL_VER_GE(7,56,0) + { "mime", lcurl_easy_mime }, +#endif + { "pause", lcurl_easy_pause }, { "reset", lcurl_easy_reset }, { "setopt", lcurl_easy_setopt }, @@ -1659,6 +1739,9 @@ static const lcurl_const_t lcurl_easy_opt[] = { OPT_ENTRY(stream_depends, STREAM_DEPENDS, TTT, 0, 0) OPT_ENTRY(stream_depends_e, STREAM_DEPENDS_E, TTT, 0, 0) #endif +#if LCURL_CURL_VER_GE(7,56,0) + OPT_ENTRY(mimepost, MIMEPOST, TTT, 0, 0) +#endif #undef OPT_ENTRY #undef FLG_ENTRY diff --git a/src/lceasy.h b/src/lceasy.h index e3275f1..916f53d 100644 --- a/src/lceasy.h +++ b/src/lceasy.h @@ -1,7 +1,7 @@ /****************************************************************************** * Author: Alexey Melnichuk * -* Copyright (C) 2014 Alexey Melnichuk +* Copyright (C) 2014-2017 Alexey Melnichuk * * Licensed according to the included 'LICENSE' document * @@ -37,9 +37,16 @@ enum { #if LCURL_CC_SUPPORT_FORWARD_TYPEDEF typedef struct lcurl_multi_tag lcurl_multi_t; +#if LCURL_CURL_VER_GE(7,56,0) +typedef struct lcurl_mime_tag lcurl_mime_t; +#endif #else struct lcurl_multi_tag; #define lcurl_multi_t struct lcurl_multi_tag +#if LCURL_CURL_VER_GE(7,56,0) +struct lcurl_mime_tag; +#define lcurl_mime_t struct lcurl_mime_tag; +#endif #endif typedef struct lcurl_easy_tag{ @@ -53,6 +60,10 @@ typedef struct lcurl_easy_tag{ lcurl_multi_t *multi; +#if LCURL_CURL_VER_GE(7,56,0) + lcurl_mime_t *mime; +#endif + CURL *curl; int storage; int lists[LCURL_LIST_COUNT]; @@ -77,8 +88,16 @@ void lcurl_easy_initlib(lua_State *L, int nup); void lcurl__easy_assign_lua(lua_State *L, lcurl_easy_t *p, lua_State *value, int assign_multi); +size_t lcurl_read_callback(lua_State *L, + lcurl_callback_t *rd, lcurl_read_buffer_t *rbuffer, + char *buffer, size_t size, size_t nitems +); + #if !LCURL_CC_SUPPORT_FORWARD_TYPEDEF #undef lcurl_multi_t +#ifdef lcurl_mime_t +#undef lcurl_mime_t +#endif #endif #endif diff --git a/src/lcmime.c b/src/lcmime.c new file mode 100644 index 0000000..43d221e --- /dev/null +++ b/src/lcmime.c @@ -0,0 +1,479 @@ +/****************************************************************************** +* Author: Alexey Melnichuk +* +* Copyright (C) 2017 Alexey Melnichuk +* +* Licensed according to the included 'LICENSE' document +* +* This file is part of lua-lcurl library. +******************************************************************************/ + +#include "lcurl.h" +#include "lcmime.h" +#include "lceasy.h" +#include "lcerror.h" +#include "lcutils.h" + +#if LCURL_CURL_VER_GE(7,56,0) + +#define LCURL_MIME_NAME LCURL_PREFIX" MIME" +static const char *LCURL_MIME = LCURL_MIME_NAME; + +#define LCURL_MIME_PART_NAME LCURL_PREFIX" MIME Part" +static const char *LCURL_MIME_PART = LCURL_MIME_PART_NAME; + +static void lcurl_mime_part_remove_subparts(lua_State *L, lcurl_mime_part_t *p, int free_it); + +static lcurl_mime_t* lcurl_mime_part_get_subparts(lua_State *L, lcurl_mime_part_t *part){ + lcurl_mime_t *sub = NULL; + + if(LUA_NOREF != part->subpart_ref){ + lua_rawgeti(L, LCURL_LUA_REGISTRY, part->subpart_ref); + sub = lcurl_getmime_at(L, -1); + lua_pop(L, 1); + } + + return sub; +} + +static int lcurl_mime_part_reset(lua_State *L, lcurl_mime_part_t *p){ + p->part = NULL; + + luaL_unref(L, LCURL_LUA_REGISTRY, p->rd.cb_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->rd.ud_ref); + luaL_unref(L, LCURL_LUA_REGISTRY, p->rbuffer.ref); + + p->headers_ref = p->rbuffer.ref = p->rd.cb_ref = p->rd.ud_ref = LUA_NOREF; + + /*free only if we have no parents*/ + lcurl_mime_part_remove_subparts(L, p, 0); + + return 0; +} + +static int lcurl_mime_reset(lua_State *L, lcurl_mime_t *p){ + lcurl_mime_part_t *ptr; + + /* reset all parts*/ + for(ptr = p->parts; ptr; ptr=ptr->next){ + lcurl_mime_part_reset(L, ptr); + } + + if(LUA_NOREF != p->storage){ + p->storage = lcurl_storage_free(L, p->storage); + } + + p->parts = p->parent = NULL; + p->mime = NULL; + + return 0; +} + +int lcurl_mime_set_lua(lua_State *L, lcurl_mime_t *p, lua_State *v){ + lcurl_mime_part_t *part; + for(part = p->parts; part; part=part->next){ + lcurl_mime_t *sub = lcurl_mime_part_get_subparts(L, part); + if(sub) lcurl_mime_set_lua(L, sub, v); + part->L = v; + } + return 0; +} + +//{ MIME + +static lcurl_mime_part_t* lcurl_mime_parts_append(lcurl_mime_t *m, lcurl_mime_part_t *p){ + if(!m->parts) m->parts = p; + else{ + lcurl_mime_part_t *ptr = m->parts; + while(ptr->next)ptr = ptr->next; + ptr->next = p; + } + return p; +} + +static lcurl_mime_part_t* lcurl_mime_parts_find(lcurl_mime_t *m, lcurl_mime_part_t *p){ + lcurl_mime_part_t *ptr; + + for(ptr = m->parts; ptr; ptr = ptr->next){ + if(ptr == p) return p; + } + + return NULL; +} + +int lcurl_mime_create(lua_State *L, int error_mode){ + //! @todo make this function as method of easy handle + lcurl_easy_t *e = lcurl_geteasy(L); + + lcurl_mime_t *p = lutil_newudatap(L, lcurl_mime_t, LCURL_MIME); + + p->mime = curl_mime_init(e->curl); + + //! @todo return more accurate error category/code + if(!p->mime) return lcurl_fail_ex(L, error_mode, LCURL_ERROR_EASY, CURLE_FAILED_INIT); + + p->storage = lcurl_storage_init(L); + p->err_mode = error_mode; + p->parts = p->parent = NULL; + + return 1; +} + +lcurl_mime_t *lcurl_getmime_at(lua_State *L, int i){ + lcurl_mime_t *p = (lcurl_mime_t *)lutil_checkudatap (L, i, LCURL_MIME); + luaL_argcheck (L, p != NULL, i, LCURL_MIME_NAME" object expected"); + luaL_argcheck (L, p->mime != NULL, i, LCURL_MIME_NAME" object freed"); + return p; +} + +static int lcurl_mime_to_s(lua_State *L){ + lcurl_mime_t *p = (lcurl_mime_t *)lutil_checkudatap (L, 1, LCURL_MIME); + luaL_argcheck (L, p != NULL, 1, LCURL_MIME_NAME" object expected"); + + lua_pushfstring(L, LCURL_MIME_NAME" (%p)%s", (void*)p, + p->mime ? (p->parent ? " (subpart)" : "") : " (freed)" + ); + return 1; +} + +static int lcurl_mime_free(lua_State *L){ + lcurl_mime_t *p = (lcurl_mime_t *)lutil_checkudatap (L, 1, LCURL_MIME); + luaL_argcheck (L, p != NULL, 1, LCURL_MIME_NAME" object expected"); + + if((p->mime) && (NULL == p->parent)){ + curl_mime_free(p->mime); + } + + return lcurl_mime_reset(L, p); +} + +static int lcurl_mime_addpart(lua_State *L){ + lcurl_mime_t *p = lcurl_getmime(L); + int ret = lcurl_mime_part_create(L, p->err_mode); + if(ret != 1) return ret; + + /* store mime part in storage */ + lcurl_storage_preserve_value(L, p->storage, lua_absindex(L, -1)); + lcurl_mime_parts_append(p, lcurl_getmimepart_at(L, -1)); + return 1; +} + +//} + +//{ MIME Part + +int lcurl_mime_part_create(lua_State *L, int error_mode){ + //! @todo make this function as method of mime handle + lcurl_mime_t *m = lcurl_getmime(L); + + lcurl_mime_part_t *p = lutil_newudatap(L, lcurl_mime_part_t, LCURL_MIME_PART); + + p->part = curl_mime_addpart(m->mime); + + //! @todo return more accurate error category/code + if(!p->part) return lcurl_fail_ex(L, error_mode, LCURL_ERROR_EASY, CURLE_FAILED_INIT); + + p->rbuffer.ref = p->rd.cb_ref = p->rd.ud_ref = LUA_NOREF; + p->err_mode = error_mode; + p->subpart_ref = p->headers_ref = LUA_NOREF; + p->parent = m; + + return 1; +} + +lcurl_mime_part_t *lcurl_getmimepart_at(lua_State *L, int i){ + lcurl_mime_part_t *p = (lcurl_mime_part_t *)lutil_checkudatap (L, i, LCURL_MIME_PART); + luaL_argcheck (L, p != NULL, i, LCURL_MIME_PART_NAME" object expected"); + luaL_argcheck (L, p->part != NULL, i, LCURL_MIME_PART_NAME" object freed"); + return p; +} + +static int lcurl_mime_part_to_s(lua_State *L){ + lcurl_mime_part_t *p = (lcurl_mime_part_t *)lutil_checkudatap (L, 1, LCURL_MIME_PART); + luaL_argcheck (L, p != NULL, 1, LCURL_MIME_PART_NAME" object expected"); + + lua_pushfstring(L, LCURL_MIME_PART_NAME" (%p)%s", (void*)p, p->part ? "" : " (freed)"); + return 1; +} + +static int lcurl_mime_part_free(lua_State *L){ + lcurl_mime_part_t *p = (lcurl_mime_part_t *)lutil_checkudatap (L, 1, LCURL_MIME_PART); + luaL_argcheck (L, p != NULL, 1, LCURL_MIME_PART_NAME" object expected"); + + lcurl_mime_part_reset(L, p); + + return 0; +} + +static void lcurl_mime_part_remove_subparts(lua_State *L, lcurl_mime_part_t *p, int free_it){ + lcurl_mime_t *sub = lcurl_mime_part_get_subparts(L, p); + if(sub){ + assert(LUA_NOREF != p->subpart_ref); + /* detach `subpart` mime from current mime part */ + + /* if set `sub->parent = NULL` then gc for mime will try free curl_mime_free. */ + /* So do not set it unless `curl_mime_subparts(p->part, NULL)` does not free mime */ + + luaL_unref(L, LCURL_LUA_REGISTRY, p->subpart_ref); + p->subpart_ref = LUA_NOREF; + + if(p->part && free_it){ + curl_mime_subparts(p->part, NULL); + } + + /* issues #1961 */ + + /* seems curl_mime_subparts(h, NULL) free asubparts. + so we have to invalidate all reference to all nested objects (part/mime). + NOTE. All resources already feed. So just need set all pointers to NULL + and free all Lua resources (like references and storages) + */ + { + lcurl_mime_part_t *ptr; + /* reset all parts*/ + for(ptr = sub->parts; ptr; ptr=ptr->next){ + lcurl_mime_part_remove_subparts(L, p, 0); + } + lcurl_mime_reset(L, sub); + } + } +} + +#define IS_NILORSTR(L, i) (lua_type(L, i) == LUA_TSTRING) || (lua_type(L, i) == LUA_TNIL) +#define IS_TABLE(L, i) lua_type(L, i) == LUA_TTABLE + +static int lcurl_mime_part_assing_ext(lua_State *L, lcurl_mime_part_t *p, int i){ + const char *mime_type = NULL, *mime_name = NULL; + int headers = 0; + CURLcode ret; + + if(IS_TABLE(L, i)) headers = i; + else if(IS_NILORSTR(L, i)){ + mime_type = lua_tostring(L, i); + if(IS_TABLE(L, i+1)) headers = i+1; + else if(IS_NILORSTR(L, i+1)){ + mime_name = lua_tostring(L, i+1); + if(IS_TABLE(L, i+2)) headers = i+2; + } + } + + if(mime_type){ + ret = curl_mime_type(p->part, mime_type); + if(ret != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); + } + } + + if(mime_name){ + ret = curl_mime_name(p->part, mime_name); + if(ret != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); + } + } + + if(headers){ + struct curl_slist *list = lcurl_util_to_slist(L, headers); + ret = curl_mime_headers(p->part, list, 1); + if(ret != CURLE_OK){ + curl_slist_free_all(list); + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); + } + } + + return 0; +} + +// part:data(str[, type[, name]][, headers]) +static int lcurl_mime_part_data(lua_State *L){ + lcurl_mime_part_t *p = lcurl_getmimepart(L); + size_t len; const char *data = luaL_checklstring(L, 2, &len); + CURLcode ret; + + /*string too long*/ + if(len == CURL_ZERO_TERMINATED){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, CURLE_BAD_FUNCTION_ARGUMENT); + } + + /* curl_mime_data copies data */ + ret = curl_mime_data(p->part, data, len); + if(ret != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); + } + + if (lua_gettop(L) > 2){ + int res = lcurl_mime_part_assing_ext(L, p, 3); + if (res) return res; + } + + lua_settop(L, 1); + return 1; +} + +// part:subparts(mime[, type[, name]][, headers]) +static int lcurl_mime_part_subparts(lua_State *L){ + lcurl_mime_part_t *p = lcurl_getmimepart(L); + lcurl_mime_t *mime = lcurl_getmime_at(L, 2); + CURLcode ret; + + /* we can attach mime to only one part */ + if(mime->parent){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, CURLE_BAD_FUNCTION_ARGUMENT); + } + + /* if we already have one subpart then libcurl free it so we can not use any references to it */ + lcurl_mime_part_remove_subparts(L, p, 1); + + ret = curl_mime_subparts(p->part, mime->mime); + if(ret != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); + } + + lua_pushvalue(L, 2); + p->subpart_ref = luaL_ref(L, LCURL_LUA_REGISTRY); + mime->parent = p; + + if (lua_gettop(L) > 2){ + int res = lcurl_mime_part_assing_ext(L, p, 3); + if (res) return res; + } + + lua_settop(L, 1); + return 1; +} + +// part:filedata(path[, type[, name]][, headers]) +static int lcurl_mime_part_filedata(lua_State *L){ + lcurl_mime_part_t *p = lcurl_getmimepart(L); + const char *data = luaL_checkstring(L, 2); + CURLcode ret; + + ret = curl_mime_filedata(p->part, data); + if(ret != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); + } + + if (lua_gettop(L) > 2){ + int res = lcurl_mime_part_assing_ext(L, p, 3); + if (res) return res; + } + + lua_settop(L, 1); + return 1; +} + +// part:headers(t) +static int lcurl_mime_part_headers(lua_State *L){ + lcurl_mime_part_t *p = lcurl_getmimepart(L); + struct curl_slist *list = lcurl_util_to_slist(L, 2); + CURLcode ret; + + luaL_argcheck(L, list, 2, "array expected"); + + ret = curl_mime_headers(p->part, list, 1); + + if(ret != CURLE_OK){ + curl_slist_free_all(list); + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); + } + + lua_settop(L, 1); + return 1; +} + +// part:type(t) +static int lcurl_mime_part_type(lua_State *L){ + lcurl_mime_part_t *p = lcurl_getmimepart(L); + const char *mime_type = luaL_checkstring(L, 2); + CURLcode ret; + + ret = curl_mime_type(p->part, mime_type); + + if(ret != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); + } + + lua_settop(L, 1); + return 1; +} + +// part:name(t) +static int lcurl_mime_part_name(lua_State *L){ + lcurl_mime_part_t *p = lcurl_getmimepart(L); + const char *mime_name = luaL_checkstring(L, 2); + CURLcode ret; + + ret = curl_mime_name(p->part, mime_name); + + if(ret != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); + } + + lua_settop(L, 1); + return 1; +} + + +//} + +static const struct luaL_Reg lcurl_mime_methods[] = { + + {"addpart", lcurl_mime_addpart }, + + {"free", lcurl_mime_free }, + {"__gc", lcurl_mime_free }, + {"__tostring", lcurl_mime_to_s }, + + {NULL,NULL} +}; + +static const struct luaL_Reg lcurl_mime_part_methods[] = { + + {"subparts", lcurl_mime_part_subparts }, + {"data", lcurl_mime_part_data }, + {"filedata", lcurl_mime_part_filedata }, + {"headers", lcurl_mime_part_headers }, + {"name", lcurl_mime_part_name }, + {"type", lcurl_mime_part_type }, + + {"free", lcurl_mime_part_free }, + {"__gc", lcurl_mime_part_free }, + {"__tostring", lcurl_mime_part_to_s }, + + {NULL,NULL} +}; + +static int lcurl_pushvalues(lua_State *L, int nup) { + assert(lua_gettop(L) >= nup); + + if (nup > 0) { + int b = lua_absindex(L, -nup); + int e = lua_absindex(L, -1); + int i; + + lua_checkstack(L, nup); + + for(i = b; i <= e; ++i) + lua_pushvalue(L, i); + } + + return nup; +} + +#endif + +void lcurl_mime_initlib(lua_State *L, int nup){ +#if LCURL_CURL_VER_GE(7,56,0) + lcurl_pushvalues(L, nup); + + if(!lutil_createmetap(L, LCURL_MIME, lcurl_mime_methods, nup)) + lua_pop(L, nup); + lua_pop(L, 1); + + if(!lutil_createmetap(L, LCURL_MIME_PART, lcurl_mime_part_methods, nup)) + lua_pop(L, nup); + lua_pop(L, 1); +#else + lua_pop(L, nup); +#endif +} + diff --git a/src/lcmime.h b/src/lcmime.h new file mode 100644 index 0000000..02ebf55 --- /dev/null +++ b/src/lcmime.h @@ -0,0 +1,66 @@ +/****************************************************************************** +* Author: Alexey Melnichuk +* +* Copyright (C) 2017 Alexey Melnichuk +* +* Licensed according to the included 'LICENSE' document +* +* This file is part of lua-lcurl library. +******************************************************************************/ + +#ifndef _LCMIME_H_ +#define _LCMIME_H_ + +#include "lcurl.h" +#include "lcutils.h" +#include + +void lcurl_mime_initlib(lua_State *L, int nup); + +#if LCURL_CURL_VER_GE(7,56,0) + +typedef struct lcurl_mime_part_tag{ + lua_State *L; + + lcurl_callback_t rd; + lcurl_read_buffer_t rbuffer; + + curl_mimepart *part; + + struct lcurl_mime_tag *parent; /*always set and can not be changed*/ + + int subpart_ref; + int headers_ref; + + int err_mode; + + struct lcurl_mime_part_tag *next; +}lcurl_mime_part_t; + +typedef struct lcurl_mime_tag{ + curl_mime *mime; + + int storage; + int err_mode; + + lcurl_mime_part_t *parts; + lcurl_mime_part_t *parent; /*after set there no way change it*/ +}lcurl_mime_t; + +int lcurl_mime_create(lua_State *L, int error_mode); + +lcurl_mime_t *lcurl_getmime_at(lua_State *L, int i); + +#define lcurl_getmime(L) lcurl_getmime_at((L), 1) + +int lcurl_mime_part_create(lua_State *L, int error_mode); + +lcurl_mime_part_t *lcurl_getmimepart_at(lua_State *L, int i); + +#define lcurl_getmimepart(L) lcurl_getmimepart_at((L), 1) + +int lcurl_mime_set_lua(lua_State *L, lcurl_mime_t *p, lua_State *v); + +#endif + +#endif \ No newline at end of file diff --git a/src/lcopteasy.h b/src/lcopteasy.h index 35725d1..d683d84 100644 --- a/src/lcopteasy.h +++ b/src/lcopteasy.h @@ -401,6 +401,10 @@ OPT_ENTRY( request_target, REQUEST_TARGET, STR, 0, LCURL_DEF OPT_ENTRY( socks5_auth, SOCKS5_AUTH, LNG, 0, LCURL_DEFAULT_VALUE) #endif +#if LCURL_CURL_VER_GE(7,56,0) +OPT_ENTRY( ssh_compression, SSH_COMPRESSION, LNG, 0, LCURL_DEFAULT_VALUE) +#endif + #ifdef LCURL__TCP_FASTOPEN # define TCP_FASTOPEN LCURL__TCP_FASTOPEN # undef LCURL__TCP_FASTOPEN diff --git a/src/lcurl.c b/src/lcurl.c index 512fe0c..c3d2c8f 100644 --- a/src/lcurl.c +++ b/src/lcurl.c @@ -14,6 +14,7 @@ #include "lcshare.h" #include "lcerror.h" #include "lchttppost.h" +#include "lcmime.h" #include "lcutils.h" /*export*/ @@ -113,6 +114,10 @@ static int lcurl_version_info(lua_State *L){ #ifdef CURL_VERSION_HTTPS_PROXY lua_pushliteral(L, "HTTPS_PROXY"); lua_pushboolean(L, data->features & CURL_VERSION_HTTPS_PROXY ); lua_rawset(L, -3); #endif +#ifdef CURL_VERSION_MULTI_SSL + lua_pushliteral(L, "MULTI_SSL"); lua_pushboolean(L, data->features & CURL_VERSION_MULTI_SSL ); lua_rawset(L, -3); +#endif + lua_setfield(L, -2, "features"); /* bitmask, see defines below */ if(data->ssl_version){lua_pushstring(L, data->ssl_version); lua_setfield(L, -2, "ssl_version");} /* human readable string */ @@ -155,7 +160,7 @@ static const struct luaL_Reg lcurl_functions[] = { {"share", lcurl_share_new }, {"version", lcurl_version }, {"version_info", lcurl_version_info }, - + {NULL,NULL} }; @@ -215,6 +220,7 @@ static int luaopen_lcurl_(lua_State *L, const struct luaL_Reg *func){ lua_pushvalue(L, -3); lua_pushvalue(L, -3); lcurl_error_initlib(L, 2); lua_pushvalue(L, -3); lua_pushvalue(L, -3); lcurl_hpost_initlib(L, 2); lua_pushvalue(L, -3); lua_pushvalue(L, -3); lcurl_easy_initlib (L, 2); + lua_pushvalue(L, -3); lua_pushvalue(L, -3); lcurl_mime_initlib (L, 2); lua_pushvalue(L, -3); lua_pushvalue(L, -3); lcurl_multi_initlib(L, 2); lua_pushvalue(L, -3); lua_pushvalue(L, -3); lcurl_share_initlib(L, 2); diff --git a/src/lcutils.c b/src/lcutils.c index c6baa6f..e4428fb 100644 --- a/src/lcutils.c +++ b/src/lcutils.c @@ -21,13 +21,21 @@ int lcurl_storage_init(lua_State *L){ } void lcurl_storage_preserve_value(lua_State *L, int storage, int i){ - assert(i > 0); + assert(i > 0 && i <= lua_gettop(L)); luaL_checkany(L, i); lua_rawgeti(L, LCURL_LUA_REGISTRY, storage); lua_pushvalue(L, i); lua_pushboolean(L, 1); lua_rawset(L, -3); lua_pop(L, 1); } +void lcurl_storage_remove_value(lua_State *L, int storage, int i){ + assert(i > 0 && i <= lua_gettop(L)); + luaL_checkany(L, i); + lua_rawgeti(L, LCURL_LUA_REGISTRY, storage); + lua_pushvalue(L, i); lua_pushnil(L); lua_rawset(L, -3); + lua_pop(L, 1); +} + static void lcurl_storage_ensure_t(lua_State *L, int t){ lua_rawgeti(L, -1, t); if(!lua_istable(L, -1)){ @@ -256,7 +264,7 @@ int lcurl_utils_apply_options(lua_State *L, int opt, int obj, int do_close, while(lua_next(L, opt) != 0){ int n; assert(lua_gettop(L) == (top + 2)); - + if(lua_type(L, -2) == LUA_TNUMBER){ /* [curl.OPT_URL] = "http://localhost" */ lua_pushvalue(L, -2); lua_insert(L, -2); /*Stack : opt, obj, k, k, v */ diff --git a/src/lcutils.h b/src/lcutils.h index 25a822d..4a3e280 100644 --- a/src/lcutils.h +++ b/src/lcutils.h @@ -55,6 +55,8 @@ int lcurl_storage_init(lua_State *L); void lcurl_storage_preserve_value(lua_State *L, int storage, int i); +void lcurl_storage_remove_value(lua_State *L, int storage, int i); + int lcurl_storage_preserve_slist(lua_State *L, int storage, struct curl_slist * list); struct curl_slist* lcurl_storage_remove_slist(lua_State *L, int storage, int idx); diff --git a/test/run.lua b/test/run.lua index 44a694b..e5feee2 100644 --- a/test/run.lua +++ b/test/run.lua @@ -27,6 +27,7 @@ print("") require "test_safe" require "test_easy" require "test_form" +require "test_mime" require "test_curl" RUN() diff --git a/test/test_curl.lua b/test/test_curl.lua index dbf4b25..3e65497 100644 --- a/test/test_curl.lua +++ b/test/test_curl.lua @@ -18,6 +18,12 @@ local scurl = require "cURL.safe" local json = require "dkjson" local fname = "./test.download" +local utils = require "utils" + +-- libcurl 7.56.0 does not add `Content-Type: text/plain` +-- not sure is it bug or not +local text_plain = utils.is_curl_eq(7,56,0) and 'test/plain' or 'text/plain' + local ENABLE = true local _ENV = TEST_CASE'version' if ENABLE then @@ -222,11 +228,11 @@ function test_content_01() end function test_content_02() - post = assert(scurl.form{name02 = {'value02', type = "text/plain"}}) + post = assert(scurl.form{name02 = {'value02', type = text_plain}}) local data = assert_string(post:get()) assert_match("\r\n\r\nvalue02\r\n", data) assert_match('name="name02"', data) - assert_match('Content%-Type: text/plain\r\n', data) + assert_match('Content%-Type: ' .. text_plain .. '\r\n', data) end function test_content_03() @@ -238,12 +244,12 @@ function test_content_03() end function test_content_04() - post = assert(scurl.form{name04 = {'value04', type = "text/plain", headers = {"Content-Encoding: gzip"}}}) + post = assert(scurl.form{name04 = {'value04', type = text_plain, headers = {"Content-Encoding: gzip"}}}) local data = assert_string(post:get()) assert_match("\r\n\r\nvalue04\r\n", data) assert_match('name="name04"', data) assert_match('Content%-Encoding: gzip\r\n', data) - assert_match('Content%-Type: text/plain\r\n', data) + assert_match('Content%-Type: ' .. text_plain .. '\r\n', data) end function test_buffer_01() @@ -263,14 +269,14 @@ function test_buffer_02() post = assert(scurl.form{name02 = { name = 'file02', data = 'value02', - type = "text/plain", + type = text_plain, }}) local data = assert_string(post:get()) assert_match("\r\n\r\nvalue02\r\n", data) assert_match('name="name02"', data) assert_match('filename="file02"', data) - assert_match('Content%-Type: text/plain\r\n', data) + assert_match('Content%-Type: ' .. text_plain .. '\r\n', data) assert_not_match('Content%-Type: application/octet%-stream\r\n', data) end @@ -292,7 +298,7 @@ function test_buffer_04() post = assert(scurl.form{name04 = { name = 'file04', data = 'value04', - type = "text/plain", + type = text_plain, headers = {"Content-Encoding: gzip"}, }}) @@ -300,7 +306,7 @@ function test_buffer_04() assert_match("\r\n\r\nvalue04\r\n", data) assert_match('name="name04"', data) assert_match('filename="file04"', data) - assert_match('Content%-Type: text/plain\r\n', data) + assert_match('Content%-Type: ' .. text_plain .. '\r\n', data) assert_not_match('Content%-Type: application/octet%-stream\r\n', data) assert_match('Content%-Encoding: gzip\r\n', data) end @@ -331,13 +337,13 @@ function test_stream_03() name = 'file03', stream = function() end, length = 128, - type = 'text/plain', + type = text_plain, }}) local data = assert_string(post:get()) assert_match('name="name03"', data) assert_match('filename="file03"', data) - assert_match('Content%-Type: text/plain\r\n', data) + assert_match('Content%-Type: ' .. text_plain .. '\r\n', data) end function test_stream_04() @@ -345,13 +351,13 @@ function test_stream_04() name = 'file04', stream = function() end, length = 128, - type = 'text/plain', + type = text_plain, headers = {"Content-Encoding: gzip"}, }}) local data = assert_string(post:get()) assert_match('name="name04"', data) assert_match('filename="file04"', data) - assert_match('Content%-Type: text/plain\r\n', data) + assert_match('Content%-Type: ' .. text_plain .. '\r\n', data) assert_match('Content%-Encoding: gzip\r\n', data) end diff --git a/test/test_easy.lua b/test/test_easy.lua index 1b2c02e..d61d335 100644 --- a/test/test_easy.lua +++ b/test/test_easy.lua @@ -17,6 +17,7 @@ local scurl = require "lcurl.safe" local json = require "dkjson" local path = require "path" local upath = require "path".new('/') +local utils = require "utils" local url = "http://example.com" local fname = "./test.download" @@ -26,66 +27,8 @@ local fname = "./test.download" -- print("------------------------------------") -- print("") -local function weak_ptr(val) - return setmetatable({value = val},{__mode = 'v'}) -end - -local function gc_collect() - collectgarbage("collect") - collectgarbage("collect") -end - -local function cver(min, maj, pat) - return min * 2^16 + maj * 2^8 + pat -end - -local function is_curl_ge(min, maj, pat) - assert(0x070908 == cver(7,9,8)) - return curl.version_info('version_num') >= cver(min, maj, pat) -end - -local function read_file(n) - local f, e = io.open(n, "r") - if not f then return nil, e end - local d, e = f:read("*all") - f:close() - return d, e -end - -local function get_bin_by(str,n) - local pos = 1 - n - return function() - pos = pos + n - return (str:sub(pos,pos+n-1)) - end -end - -local function strem(ch, n, m) - return n, get_bin_by( (ch):rep(n), m) -end - -local function Stream(ch, n, m) - local size, reader - - local _stream = {} - - function _stream:read(...) - _stream.called_ctx = self - _stream.called_co = coroutine.running() - return reader(...) - end - - function _stream:size() - return size - end - - function _stream:reset() - size, reader = strem(ch, n, m) - return self - end - - return _stream:reset() -end +local weak_ptr, gc_collect, is_curl_ge, read_file, stream, Stream = + utils.import('weak_ptr', 'gc_collect', 'is_curl_ge', 'read_file', 'stream', 'Stream') local ENABLE = true @@ -499,7 +442,7 @@ function teardown() end function test() - assert_equal(f, f:add_stream('SSSSS', strem('X', 128, 13))) + assert_equal(f, f:add_stream('SSSSS', stream('X', 128, 13))) assert_equal(c, c:setopt_httppost(f)) -- should be called only stream callback diff --git a/test/test_form.lua b/test/test_form.lua index 96da473..11ac8e8 100644 --- a/test/test_form.lua +++ b/test/test_form.lua @@ -12,6 +12,12 @@ end local TEST_CASE = assert(lunit.TEST_CASE) local skip = lunit.skip or function() end +local utils = require "utils" + +-- libcurl 7.56.0 does not add `Content-Type: text/plain` +-- not sure is it bug or not +local text_plain = utils.is_curl_eq(7,56,0) and 'test/plain' or 'text/plain' + local curl = require "lcurl" local _ENV = TEST_CASE'add_content' do @@ -42,11 +48,11 @@ function test_01() end function test_02() - assert_equal(post, post:add_content('name02', 'value02', "text/plain")) + assert_equal(post, post:add_content('name02', 'value02', text_plain)) local data = assert_string(post:get()) assert_match("\r\n\r\nvalue02\r\n", data) assert_match('name="name02"', data) - assert_match('Content%-Type: text/plain\r\n', data) + assert_match('Content%-Type: ' .. text_plain .. '\r\n', data) end function test_03() @@ -58,12 +64,12 @@ function test_03() end function test_04() - assert_equal(post, post:add_content('name04', 'value04', "text/plain", {"Content-Encoding: gzip"})) + assert_equal(post, post:add_content('name04', 'value04', text_plain, {"Content-Encoding: gzip"})) local data = assert_string(post:get()) assert_match("\r\n\r\nvalue04\r\n", data) assert_match('name="name04"', data) assert_match('Content%-Encoding: gzip\r\n', data) - assert_match('Content%-Type: text/plain\r\n', data) + assert_match('Content%-Type: ' .. text_plain .. '\r\n', data) end function test_error() @@ -97,12 +103,12 @@ function test_01() end function test_02() - assert_equal(post, post:add_buffer('name02', 'file02', 'value02', "text/plain")) + assert_equal(post, post:add_buffer('name02', 'file02', 'value02', text_plain)) local data = assert_string(post:get()) assert_match("\r\n\r\nvalue02\r\n", data) assert_match('name="name02"', data) assert_match('filename="file02"', data) - assert_match('Content%-Type: text/plain\r\n', data) + assert_match('Content%-Type: ' .. text_plain .. '\r\n', data) assert_not_match('Content%-Type: application/octet%-stream\r\n', data) end @@ -117,12 +123,12 @@ function test_03() end function test_04() - assert_equal(post, post:add_buffer('name04', 'file04', 'value04', "text/plain", {"Content-Encoding: gzip"})) + assert_equal(post, post:add_buffer('name04', 'file04', 'value04', text_plain, {"Content-Encoding: gzip"})) local data = assert_string(post:get()) assert_match("\r\n\r\nvalue04\r\n", data) assert_match('name="name04"', data) assert_match('filename="file04"', data) - assert_match('Content%-Type: text/plain\r\n', data) + assert_match('Content%-Type: ' .. text_plain .. '\r\n', data) assert_not_match('Content%-Type: application/octet%-stream\r\n', data) assert_match('Content%-Encoding: gzip\r\n', data) end @@ -180,19 +186,19 @@ function test_02() end function test_03() - assert_equal(post, post:add_stream('name03', 'file03', 'text/plain', stream())) + assert_equal(post, post:add_stream('name03', 'file03', text_plain, stream())) local data = assert_string(post:get()) assert_match('name="name03"', data) assert_match('filename="file03"', data) - assert_match('Content%-Type: text/plain\r\n', data) + assert_match('Content%-Type: ' .. text_plain .. '\r\n', data) end function test_04() - assert_equal(post, post:add_stream('name04', 'file04', 'text/plain', {"Content-Encoding: gzip"}, stream())) + assert_equal(post, post:add_stream('name04', 'file04', text_plain, {"Content-Encoding: gzip"}, stream())) local data = assert_string(post:get()) assert_match('name="name04"', data) assert_match('filename="file04"', data) - assert_match('Content%-Type: text/plain\r\n', data) + assert_match('Content%-Type: ' .. text_plain .. '\r\n', data) assert_match('Content%-Encoding: gzip\r\n', data) end @@ -290,21 +296,21 @@ function test_01() end function test_02() - assert_equal(post, post:add_file('nameXX', form_path, 'text/plain')) + assert_equal(post, post:add_file('nameXX', form_path, text_plain)) local data = assert_string(post:get()) check_form(data) assert_match('filename="' .. form_file .. '"', data) assert_not_match('Content%-Type: application/octet%-stream\r\n', data) - assert_match('Content%-Type: text/plain\r\n', data) + assert_match('Content%-Type: ' .. text_plain .. '\r\n', data) end function test_03() - assert_equal(post, post:add_file('nameXX', form_path, 'text/plain', 'renamedfile')) + assert_equal(post, post:add_file('nameXX', form_path, text_plain, 'renamedfile')) local data = assert_string(post:get()) check_form(data) assert_match('filename="' .. 'renamedfile' .. '"', data) assert_not_match('Content%-Type: application/octet%-stream\r\n', data) - assert_match('Content%-Type: text/plain\r\n', data) + assert_match('Content%-Type: ' .. text_plain .. '\r\n', data) end function test_04() @@ -316,22 +322,22 @@ function test_04() end function test_05() - assert_equal(post, post:add_file('nameXX', form_path, 'text/plain', 'renamedfile', {"Content-Encoding: gzip"})) + assert_equal(post, post:add_file('nameXX', form_path, text_plain, 'renamedfile', {"Content-Encoding: gzip"})) local data = assert_string(post:get()) check_form(data) assert_match('filename="' .. 'renamedfile' .. '"', data) assert_not_match('Content%-Type: application/octet%-stream\r\n', data) - assert_match('Content%-Type: text/plain\r\n', data) + assert_match('Content%-Type: ' .. text_plain .. '\r\n', data) assert_match('Content%-Encoding: gzip\r\n', data) end function test_05() - assert_equal(post, post:add_file('nameXX', form_path, 'text/plain', {"Content-Encoding: gzip"})) + assert_equal(post, post:add_file('nameXX', form_path, text_plain, {"Content-Encoding: gzip"})) local data = assert_string(post:get()) check_form(data) assert_match('filename="' .. form_file .. '"', data) assert_not_match('Content%-Type: application/octet%-stream\r\n', data) - assert_match('Content%-Type: text/plain\r\n', data) + assert_match('Content%-Type: ' .. text_plain .. '\r\n', data) assert_match('Content%-Encoding: gzip\r\n', data) end @@ -364,7 +370,7 @@ end function test_error() assert_error(function() - assert_equal(post, post:add_file('nameXX', 'text/plain', form_path)) + assert_equal(post, post:add_file('nameXX', text_plain, form_path)) post:get() end) end @@ -388,7 +394,7 @@ local function check_form(data) assert_match("\r\n\r\nvalueXX\r\n", data) assert_match('name="nameXX"', data) assert_match('Content%-Encoding: gzip\r\n', data) - assert_match('Content%-Type: text/plain\r\n', data) + assert_match('Content%-Type: ' .. text_plain .. '\r\n', data) end function test_writer_01() @@ -397,7 +403,7 @@ function test_writer_01() assert_equal(0, select("#", ...)) t[#t+1] = str end - assert_equal(post, post:add_content('nameXX', 'valueXX', "text/plain", {"Content-Encoding: gzip"})) + assert_equal(post, post:add_content('nameXX', 'valueXX', text_plain, {"Content-Encoding: gzip"})) assert_equal(post, post:get(writer)) check_form(table.concat(t)) end @@ -409,7 +415,7 @@ function test_writer_02() t[#t+1] = str return true end - assert_equal(post, post:add_content('nameXX', 'valueXX', "text/plain", {"Content-Encoding: gzip"})) + assert_equal(post, post:add_content('nameXX', 'valueXX', text_plain, {"Content-Encoding: gzip"})) assert_equal(post, post:get(writer)) check_form(table.concat(t)) end @@ -421,7 +427,7 @@ function test_writer_03() t[#t+1] = str return #str end - assert_equal(post, post:add_content('nameXX', 'valueXX', "text/plain", {"Content-Encoding: gzip"})) + assert_equal(post, post:add_content('nameXX', 'valueXX', text_plain, {"Content-Encoding: gzip"})) assert_equal(post, post:get(writer)) check_form(table.concat(t)) end @@ -433,40 +439,40 @@ function test_writer_context() assert_equal(0, select("#", ...)) T[#T+1] = str end - assert_equal(post, post:add_content('nameXX', 'valueXX', "text/plain", {"Content-Encoding: gzip"})) + assert_equal(post, post:add_content('nameXX', 'valueXX', text_plain, {"Content-Encoding: gzip"})) assert_equal(post, post:get(writer, t)) local data = table.concat(t) assert_match("\r\n\r\nvalueXX\r\n", data) assert_match('name="nameXX"', data) assert_match('Content%-Encoding: gzip\r\n', data) - assert_match('Content%-Type: text/plain\r\n', data) + assert_match('Content%-Type: ' .. text_plain .. '\r\n', data) end function test_abort_01() local err = {} local function writer() return nil, err end - assert_equal(post, post:add_content('nameXX', 'valueXX', "text/plain", {"Content-Encoding: gzip"})) + assert_equal(post, post:add_content('nameXX', 'valueXX', text_plain, {"Content-Encoding: gzip"})) local _, e = assert_nil(post:get(writer)) assert_equal(err, e) end function test_abort_02() local function writer() return 0 end - assert_equal(post, post:add_content('nameXX', 'valueXX', "text/plain", {"Content-Encoding: gzip"})) + assert_equal(post, post:add_content('nameXX', 'valueXX', text_plain, {"Content-Encoding: gzip"})) local _, e = assert_nil(post:get(writer)) assert_nil(e) end function test_abort_03() local function writer() return nil end - assert_equal(post, post:add_content('nameXX', 'valueXX', "text/plain", {"Content-Encoding: gzip"})) + assert_equal(post, post:add_content('nameXX', 'valueXX', text_plain, {"Content-Encoding: gzip"})) local _, e = assert_nil(post:get(writer)) assert_nil(e) end function test_abort_04() local function writer() return false end - assert_equal(post, post:add_content('nameXX', 'valueXX', "text/plain", {"Content-Encoding: gzip"})) + assert_equal(post, post:add_content('nameXX', 'valueXX', text_plain, {"Content-Encoding: gzip"})) local _, e = assert_nil(post:get(writer)) assert_nil(e) end @@ -474,7 +480,7 @@ end function test_error() local err = {} local function writer() error("WRITEERROR") end - assert_equal(post, post:add_content('nameXX', 'valueXX', "text/plain", {"Content-Encoding: gzip"})) + assert_equal(post, post:add_content('nameXX', 'valueXX', text_plain, {"Content-Encoding: gzip"})) assert_error_match("WRITEERROR", function() post:get(writer) end) diff --git a/test/test_mime.lua b/test/test_mime.lua new file mode 100644 index 0000000..602297c --- /dev/null +++ b/test/test_mime.lua @@ -0,0 +1,188 @@ +local lunit, RUN = lunit do +RUN = lunit and function()end or function () + local res = lunit.run() + if res.errors + res.failed > 0 then + os.exit(-1) + end + return os.exit(0) +end +lunit = require "lunit" +end + +local TEST_CASE = assert(lunit.TEST_CASE) +local skip = lunit.skip or function() end + +local curl = require "lcurl" + +local function weak_ptr(val) + return setmetatable({value = val}, {__mode = 'v'}) +end + +local function gc_collect(n) + for i = 1, (n or 10) do + collectgarbage("collect") + end +end + +local function is_freed(c) + return not not string.find(tostring(c), '%(freed%)') +end + +local _ENV = TEST_CASE'mime lifetime' if not curl.OPT_MIMEPOST then +function test() skip("MIMI API supports since cURL 7.56.0") end +else + +local easy, mime + +function setup() + easy = curl.easy() +end + +function teardown() + if easy then easy:close() end + if mime then mime:free() end + easy, mime = nil +end + +function test_preserve_mime_part_reference() + -- mime part stores references to all parts + + local mime, part = easy:mime() do + part = weak_ptr(mime:addpart()) + end + gc_collect() + + assert_not_nil(part.value) + + mime = nil + gc_collect() + + assert_nil(part.value) + + easy:close() +end + +function test_free_mime_subparts() + -- when free root free all nodes + + -- mime + -- +- part3 + -- +- alt + -- +- part1 + -- +- part2 + + local mime, a, p1, p2, p3 = easy:mime() do + + local alt = easy:mime() + + local part1 = alt:addpart() + + local part2 = alt:addpart() + + local part3 = mime:addpart() + part3:subparts(alt, "multipart/alternative") + + a = weak_ptr(alt) + p1 = weak_ptr(part1) + p2 = weak_ptr(part2) + p3 = weak_ptr(part3) + end + + gc_collect() + + assert_not_nil(a.value) + assert_not_nil(p1.value) + assert_not_nil(p2.value) + assert_not_nil(p3.value) + + -- reamove reference to root node + mime = nil + gc_collect(4) + + assert_nil(a.value) + assert_nil(p1.value) + assert_nil(p2.value) + assert_nil(p3.value) + + easy:close() +end + +function test_preserve_mime_subparts() + -- if we have references to subnode but we free root + -- then all references have to become to invalid + + -- mime + -- +- part3 + -- +- alt + -- +- part1 + -- +- part2 + + local easy = curl.easy() + + local mime, a, p1, p2, p3 = easy:mime() do + + local alt = easy:mime() + + local part1 = alt:addpart() + + local part2 = alt:addpart() + + local part3 = mime:addpart() + part3:subparts(alt, "multipart/alternative") + + a = weak_ptr(alt) + p1 = weak_ptr(part1) + p2 = weak_ptr(part2) + p3 = weak_ptr(part3) + end + + gc_collect() + + assert_not_nil(a.value) + assert_not_nil(p1.value) + assert_not_nil(p2.value) + assert_not_nil(p3.value) + + -- save reference to subnode + local subnode = a.value + + mime = nil + + -- in this case call `free` to root node. + -- there no way to get reference to this node from child + -- so there no way to use it. + gc_collect() + + -- libcurl still close all childs + -- so all reference are invalid + + assert_not_nil(a.value) + assert_not_nil(is_freed(a.value)) + assert_nil(p1.value) + assert_nil(p2.value) + assert_nil(p3.value) + + easy:close() +end + +function test_preserve_mime_by_easy() + + local mime do + mime = weak_ptr(easy:mime()) + easy:setopt_mimepost(mime.value) + end + + gc_collect() + + assert_not_nil(mime.value) + + easy:unsetopt_mimepost() + + gc_collect() + + assert_nil(mime.value) +end + +end + +RUN() diff --git a/test/utils.lua b/test/utils.lua new file mode 100644 index 0000000..798a105 --- /dev/null +++ b/test/utils.lua @@ -0,0 +1,89 @@ +local curl = require "lcurl" + +local function weak_ptr(val) + return setmetatable({value = val},{__mode = 'v'}) +end + +local function gc_collect() + collectgarbage("collect") + collectgarbage("collect") +end + +local function cver(min, maj, pat) + return min * 2^16 + maj * 2^8 + pat +end + +local function is_curl_ge(min, maj, pat) + assert(0x070908 == cver(7,9,8)) + return curl.version_info('version_num') >= cver(min, maj, pat) +end + +local function is_curl_eq(min, maj, pat) + assert(0x070908 == cver(7,9,8)) + return curl.version_info('version_num') == cver(min, maj, pat) +end + +local function read_file(n) + local f, e = io.open(n, "r") + if not f then return nil, e end + local d, e = f:read("*all") + f:close() + return d, e +end + +local function get_bin_by(str,n) + local pos = 1 - n + return function() + pos = pos + n + return (str:sub(pos,pos+n-1)) + end +end + +local function stream(ch, n, m) + return n, get_bin_by( (ch):rep(n), m) +end + +local function Stream(ch, n, m) + local size, reader + + local _stream = {} + + function _stream:read(...) + _stream.called_ctx = self + _stream.called_co = coroutine.running() + return reader(...) + end + + function _stream:size() + return size + end + + function _stream:reset() + size, reader = stream(ch, n, m) + return self + end + + return _stream:reset() +end + +local utils = { + weak_ptr = weak_ptr; + gc_collect = gc_collect; + is_curl_ge = is_curl_ge; + is_curl_eq = is_curl_eq; + get_bin_by = get_bin_by; + read_file = read_file; + stream = stream; + Stream = Stream; +} + +utils.import = function(...) + local n, t = select('#', ...), {} + for i = 1, n do + local name = select(i, ...) + t[#t + 1] = assert(utils[name], 'unknown field: ' .. tostring(name)) + end + return (unpack or table.unpack)(t, 1, n) +end + +return utils \ No newline at end of file From 8916f541d9f2d2cdce6db368d4579392dfbe0125 Mon Sep 17 00:00:00 2001 From: Alexey Melnichuk Date: Thu, 12 Oct 2017 15:39:57 +0300 Subject: [PATCH 02/16] Fix. Handle new return code for curl_formget in libcurl 7.56.0 --- src/lchttppost.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/lchttppost.c b/src/lchttppost.c index 0200339..e3b315a 100644 --- a/src/lchttppost.c +++ b/src/lchttppost.c @@ -44,6 +44,15 @@ static const char *LCURL_HTTPPOST = LCURL_HTTPPOST_NAME; # define LCURL_LEN_TYPE long #endif +/* 7.56.0 changed code for `curl_formget` if callback abort write + * not sure is it bug or not so set only for single version + **/ +#if LCURL_CURL_VER_GE(7,56,0) && !LCURL_CURL_VER_GE(7,56,1) +# define LCURL_GET_CB_ERROR CURLE_READ_ERROR +#else +# define LCURL_GET_CB_ERROR (CURLcode)-1 +#endif + //{ stream static lcurl_hpost_stream_t *lcurl_hpost_stream_add(lua_State *L, lcurl_hpost_t *p){ @@ -525,7 +534,7 @@ static int lcurl_hpost_get(lua_State *L){ return lua_error(L); } - if((CURLcode)-1 == code){ + if(LCURL_GET_CB_ERROR == code){ if(((lua_gettop(L) == top+1))&&(lua_isstring(L, -1))){ return lua_error(L); } From 5239176fc01aeac35e7e676e29d491c22e2e3038 Mon Sep 17 00:00:00 2001 From: Alexey Melnichuk Date: Wed, 18 Oct 2017 14:11:04 +0300 Subject: [PATCH 03/16] Test. Use pegasus server to dump mime content. --- appveyor.yml | 8 +++++ test/server.lua | 18 ++++++++++ test/test_mime.lua | 88 +++++++++++++++++++++++++++++++++++++++++----- test/utils.lua | 28 +++++++++++++++ 4 files changed, 134 insertions(+), 8 deletions(-) create mode 100644 test/server.lua diff --git a/appveyor.yml b/appveyor.yml index 08190a5..e4e867b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -39,6 +39,7 @@ install: hererocks c:\hererocks --%LUA% --target %HR_TARGET% -rlatest ) - call c:\hererocks\bin\activate + - luarocks show luarocks-fetch-gitrec >nul 2>&1 || luarocks install luarocks-fetch-gitrec before_build: # external deps @@ -57,6 +58,10 @@ before_test: - luarocks show dkjson >nul 2>&1 || luarocks install dkjson - luarocks show luafilesystem >nul 2>&1 || luarocks install luafilesystem - luarocks show lua-path >nul 2>&1 || luarocks install lua-path + - luarocks show pegasus >nul 2>&1 || luarocks install pegasus http.parser \ + --server=http://luarocks.org/manifests/moteus + - ps: $TestServer = Start-Process lua -ArgumentList test/server.lua -PassThru + - curl -s http://127.0.0.1:7090 test_script: - echo "Testing..." @@ -69,3 +74,6 @@ test_script: after_test: - cd %APPVEYOR_BUILD_FOLDER% - .appveyor\pack_artifact.bat lua-curl bin-rock + +on_finish: + - ps: Stop-Process -Id $TestServer.Id \ No newline at end of file diff --git a/test/server.lua b/test/server.lua new file mode 100644 index 0000000..e29bd6c --- /dev/null +++ b/test/server.lua @@ -0,0 +1,18 @@ +local function prequire(m) + local ok, err = pcall(require, m) + if not ok then return nil, err end + return err +end + +local uv = prequire "lluv" +local Pegasus = require (uv and "lluv.pegasus" or "pegasus") + +local server = Pegasus:new{host = '127.0.0.1', port = 7090} + +server:start(function(request, response) + response:statusCode(200) + response:addHeader('Content-Type', 'text/plain') + response:write('Hello from Pegasus') +end) + +if uv then uv.run() end diff --git a/test/test_mime.lua b/test/test_mime.lua index 602297c..1a535a4 100644 --- a/test/test_mime.lua +++ b/test/test_mime.lua @@ -13,16 +13,11 @@ local TEST_CASE = assert(lunit.TEST_CASE) local skip = lunit.skip or function() end local curl = require "lcurl" +local utils = require "utils" -local function weak_ptr(val) - return setmetatable({value = val}, {__mode = 'v'}) -end +local weak_ptr, gc_collect, dump_mime_ = utils.import('weak_ptr', 'gc_collect', 'dump_mime') -local function gc_collect(n) - for i = 1, (n or 10) do - collectgarbage("collect") - end -end +local dump_mime_url = 'http://127.0.0.1:7090' local function is_freed(c) return not not string.find(tostring(c), '%(freed%)') @@ -185,4 +180,81 @@ end end +local _ENV = TEST_CASE'mime basic' if not curl.OPT_MIMEPOST then +function test() skip("MIMI API supports since cURL 7.56.0") end +else + +local easy, mime + +local function dump_mime(mime) + return dump_mime_(easy, mime, dump_mime_url) +end + +function setup() + easy = curl.easy() + mime = easy:mime() +end + +function teardown() + if easy then easy:close() end + if mime then mime:free() end + easy, mime = nil +end + +function test_data() + mime:addpart():data('hello') + local info = assert_string(dump_mime(mime)) + assert_match('\r\n\r\nhello', info) +end + +function test_data_type() + mime:addpart():data('hello', 'text/html') + local info = assert_string(dump_mime(mime)) + assert_match('\r\n\r\nhello', info) + assert_match('Content%-Type:%s+text/html', info) +end + +function test_data_type_name() + mime:addpart():data('hello', 'text/html', 'test') + local info = assert_string(dump_mime(mime)) + assert_match('\r\n\r\nhello', info) + assert_match('Content%-Type:%s+text/html', info) + assert_match('Content%-Disposition:.-name="test"', info) +end + +function test_data_name() + mime:addpart():data('hello', nil, 'test') + local info = assert_string(dump_mime(mime)) + assert_match('\r\n\r\nhello', info) + assert_match('Content%-Disposition:.-name="test"', info) +end + +function test_data_headers() + mime:addpart():data('hello', { + 'X-Custom-Header: hello' + }) + local info = assert_string(dump_mime(mime)) + assert_match('\r\n\r\nhello', info) + assert_match('X%-Custom%-Header:%s*hello', info) +end + +function test_unset_name() + mime:addpart():data('hello', 'text/html', 'test'):name(false) + + local info = assert_string(dump_mime(mime)) + assert_match('\r\n\r\nhello', info) + assert_match('Content%-Type:%s+text/html', info) + assert_not_match('Content%-Disposition:.-name="test"', info) +end + +function test_unset_type() + mime:addpart():data('hello', 'text/html'):type(false) + + local info = assert_string(dump_mime(mime)) + assert_match('\r\n\r\nhello', info) + assert_not_match('Content%-Type:%s+text/html', info) +end + +end + RUN() diff --git a/test/utils.lua b/test/utils.lua index 798a105..6dce917 100644 --- a/test/utils.lua +++ b/test/utils.lua @@ -66,6 +66,33 @@ local function Stream(ch, n, m) return _stream:reset() end +local function easy_dump_mime(easy, mime, url) + local buffer = {} + + local function dump_mime(type, data) + if type == curl.INFO_DATA_OUT then + buffer[#buffer + 1] = data + end + end + + local ok, err = easy:setopt{ + url = url or "http://127.0.0.1:7090"; + customrequest = "GET"; + mimepost = mime; + verbose = true; + debugfunction = dump_mime; + writefunction = function()end; + } + + if not ok then return nil, err end + + ok, err = easy:perform() + + if not ok then return nil, err end + + return table.concat(buffer) +end + local utils = { weak_ptr = weak_ptr; gc_collect = gc_collect; @@ -73,6 +100,7 @@ local utils = { is_curl_eq = is_curl_eq; get_bin_by = get_bin_by; read_file = read_file; + dump_mime = easy_dump_mime; stream = stream; Stream = Stream; } From b723615c053fd132260a1ec023e0a309a6c0f1a0 Mon Sep 17 00:00:00 2001 From: Alexey Melnichuk Date: Wed, 18 Oct 2017 14:53:18 +0300 Subject: [PATCH 04/16] Add. Ability unset info by passing `false` --- src/lcmime.c | 70 ++++++++++++++++++++----- test/test_mime.lua | 127 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 185 insertions(+), 12 deletions(-) diff --git a/src/lcmime.c b/src/lcmime.c index 43d221e..0208a4e 100644 --- a/src/lcmime.c +++ b/src/lcmime.c @@ -241,6 +241,8 @@ static void lcurl_mime_part_remove_subparts(lua_State *L, lcurl_mime_part_t *p, #define IS_NILORSTR(L, i) (lua_type(L, i) == LUA_TSTRING) || (lua_type(L, i) == LUA_TNIL) #define IS_TABLE(L, i) lua_type(L, i) == LUA_TTABLE +#define IS_FALSE(L, i) (lua_type(L, i) == LUA_TBOOLEAN) && (!lua_toboolean(L, i)) +#define IS_OPTSTR(L, i) (IS_FALSE(L, i)) || (IS_NILORSTR(L, i)) static int lcurl_mime_part_assing_ext(lua_State *L, lcurl_mime_part_t *p, int i){ const char *mime_type = NULL, *mime_name = NULL; @@ -248,12 +250,31 @@ static int lcurl_mime_part_assing_ext(lua_State *L, lcurl_mime_part_t *p, int i) CURLcode ret; if(IS_TABLE(L, i)) headers = i; - else if(IS_NILORSTR(L, i)){ - mime_type = lua_tostring(L, i); + else if (IS_OPTSTR(L, i)) { + if (IS_FALSE(L, i)) { + ret = curl_mime_type(p->part, NULL); + if(ret != CURLE_OK) + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); + } + else{ + mime_type = lua_tostring(L, i); + } if(IS_TABLE(L, i+1)) headers = i+1; - else if(IS_NILORSTR(L, i+1)){ - mime_name = lua_tostring(L, i+1); + else if(IS_OPTSTR(L, i+1)){ + if (IS_FALSE(L, i+1)) { + ret = curl_mime_name(p->part, NULL); + if(ret != CURLE_OK) + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); + } + else{ + mime_name = lua_tostring(L, i+1); + } if(IS_TABLE(L, i+2)) headers = i+2; + else if(IS_FALSE(L, i + 2)){ + ret = curl_mime_headers(p->part, NULL, 0); + if(ret != CURLE_OK) + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); + } } } @@ -286,12 +307,18 @@ static int lcurl_mime_part_assing_ext(lua_State *L, lcurl_mime_part_t *p, int i) // part:data(str[, type[, name]][, headers]) static int lcurl_mime_part_data(lua_State *L){ lcurl_mime_part_t *p = lcurl_getmimepart(L); - size_t len; const char *data = luaL_checklstring(L, 2, &len); + size_t len; const char *data; CURLcode ret; - /*string too long*/ - if(len == CURL_ZERO_TERMINATED){ - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, CURLE_BAD_FUNCTION_ARGUMENT); + if(IS_FALSE(L, 2)){ + data = NULL; + } + else{ + data = luaL_checklstring(L, 2, &len); + /*string too long*/ + if(len == CURL_ZERO_TERMINATED){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, CURLE_BAD_FUNCTION_ARGUMENT); + } } /* curl_mime_data copies data */ @@ -364,10 +391,16 @@ static int lcurl_mime_part_filedata(lua_State *L){ // part:headers(t) static int lcurl_mime_part_headers(lua_State *L){ lcurl_mime_part_t *p = lcurl_getmimepart(L); - struct curl_slist *list = lcurl_util_to_slist(L, 2); + struct curl_slist *list; CURLcode ret; - luaL_argcheck(L, list, 2, "array expected"); + if(IS_FALSE(L, 2)){ + list = NULL; + } + else{ + list = lcurl_util_to_slist(L, 2); + luaL_argcheck(L, list, 2, "array or nil expected"); + } ret = curl_mime_headers(p->part, list, 1); @@ -383,9 +416,16 @@ static int lcurl_mime_part_headers(lua_State *L){ // part:type(t) static int lcurl_mime_part_type(lua_State *L){ lcurl_mime_part_t *p = lcurl_getmimepart(L); - const char *mime_type = luaL_checkstring(L, 2); + const char *mime_type; CURLcode ret; + if(IS_FALSE(L, 2)){ + mime_type = NULL; + } + else{ + mime_type = luaL_checkstring(L, 2); + } + ret = curl_mime_type(p->part, mime_type); if(ret != CURLE_OK){ @@ -399,9 +439,15 @@ static int lcurl_mime_part_type(lua_State *L){ // part:name(t) static int lcurl_mime_part_name(lua_State *L){ lcurl_mime_part_t *p = lcurl_getmimepart(L); - const char *mime_name = luaL_checkstring(L, 2); + const char *mime_name; CURLcode ret; + if(IS_FALSE(L, 2)){ + mime_name = NULL; + } + else{ + mime_name = luaL_checkstring(L, 2); + } ret = curl_mime_name(p->part, mime_name); if(ret != CURLE_OK){ diff --git a/test/test_mime.lua b/test/test_mime.lua index 1a535a4..80964d8 100644 --- a/test/test_mime.lua +++ b/test/test_mime.lua @@ -229,6 +229,38 @@ function test_data_name() assert_match('Content%-Disposition:.-name="test"', info) end +function test_data_should_not_unset_on_nil() + local part = mime:addpart():data('hello', 'text/html', 'test', { + 'X-Custom-Header: hello' + }) + local info = assert_string(dump_mime(mime)) + assert_match('\r\n\r\nhello', info) + assert_match('Content%-Type:%s+text/html', info) + assert_match('Content%-Disposition:.-name="test"', info) + assert_match('X%-Custom%-Header:%s*hello', info) + + part:data('world', nil, 'test2') + info = assert_string(dump_mime(mime)) + assert_match('\r\n\r\nworld', info) + assert_match('Content%-Type:%s+text/html', info) + assert_match('Content%-Disposition:.-name="test2"', info) + assert_match('X%-Custom%-Header:%s*hello', info) + + part:data('!!!', 'text/xml', nil) + info = assert_string(dump_mime(mime)) + assert_match('\r\n\r\n!!!', info) + assert_match('Content%-Type:%s+text/xml', info) + assert_match('Content%-Disposition:.-name="test2"', info) + assert_match('X%-Custom%-Header:%s*hello', info) + + part:data('!!!', 'text/xml', nil, nil) + info = assert_string(dump_mime(mime)) + assert_match('\r\n\r\n!!!', info) + assert_match('Content%-Type:%s+text/xml', info) + assert_match('Content%-Disposition:.-name="test2"', info) + assert_match('X%-Custom%-Header:%s*hello', info) +end + function test_data_headers() mime:addpart():data('hello', { 'X-Custom-Header: hello' @@ -255,6 +287,101 @@ function test_unset_type() assert_not_match('Content%-Type:%s+text/html', info) end +function test_unset_headers() + mime:addpart():data('hello', 'text/html',{ + 'X-Custom-Header: hello' + }):headers(false) + + local info = assert_string(dump_mime(mime)) + assert_match('\r\n\r\nhello', info) + assert_not_match('X%-Custom%-Header:%s*hello', info) +end + +function test_unset_data() + mime:addpart():data('hello', 'text/html', 'test'):data(false) + + local info = assert_string(dump_mime(mime)) + assert_not_match('\r\n\r\nhello', info) + assert_match('Content%-Type:%s+text/html', info) + assert_match('Content%-Disposition:.-name="test"', info) +end + +function test_unset_data_type_1() + local part = mime:addpart():data('hello', 'text/html', 'test', { + 'X-Custom-Header: hello' + }):data('hello', false) + + local info = assert_string(dump_mime(mime)) + assert_match('\r\n\r\nhello', info) + assert_not_match('Content%-Type:%s+text/html', info) + assert_match('Content%-Disposition:.-name="test"', info) + assert_match('X%-Custom%-Header:%s*hello', info) +end + +function test_unset_data_type_2() + local part = mime:addpart():data('hello', 'text/html', 'test', { + 'X-Custom-Header: hello' + }):data('hello', false, nil, nil) + + local info = assert_string(dump_mime(mime)) + assert_match('\r\n\r\nhello', info) + assert_not_match('Content%-Type:%s+text/html', info) + assert_match('Content%-Disposition:.-name="test"', info) + assert_match('X%-Custom%-Header:%s*hello', info) +end + +function test_unset_data_name_1() + local part = mime:addpart():data('hello', 'text/html', 'test', { + 'X-Custom-Header: hello' + }):data('hello', nil, false) + + local info = assert_string(dump_mime(mime)) + assert_match('\r\n\r\nhello', info) + assert_match('Content%-Type:%s+text/html', info) + assert_not_match('Content%-Disposition:.-name="test"', info) + assert_match('X%-Custom%-Header:%s*hello', info) +end + +function test_unset_data_name_2() + local part = mime:addpart():data('hello', 'text/html', 'test', { + 'X-Custom-Header: hello' + }):data('hello', nil, false, nil) + + local info = assert_string(dump_mime(mime)) + assert_match('\r\n\r\nhello', info) + assert_match('Content%-Type:%s+text/html', info) + assert_not_match('Content%-Disposition:.-name="test"', info) + assert_match('X%-Custom%-Header:%s*hello', info) +end + +function test_unset_data_header() + local part = mime:addpart():data('hello', 'text/html', 'test', { + 'X-Custom-Header: hello' + }):data('hello', nil, nil, false) + + local info = assert_string(dump_mime(mime)) + assert_match('\r\n\r\nhello', info) + assert_match('Content%-Type:%s+text/html', info) + assert_match('Content%-Disposition:.-name="test"', info) + assert_not_match('X%-Custom%-Header:%s*hello', info) +end + +function test_fail_pass_nil_as_first_arg() + local part = mime:addpart() + assert_error(function() part:data() end) + assert_error(function() part:data(nil) end) + + assert_error(function() part:name() end) + assert_error(function() part:name(nil) end) + + assert_error(function() part:type() end) + assert_error(function() part:type(nil) end) + + assert_error(function() part:headers() end) + assert_error(function() part:headers(nil) end) +end + + end RUN() From 4f75627e5cf1d0d9a0470ba2d0144ae1b0e53651 Mon Sep 17 00:00:00 2001 From: Alexey Melnichuk Date: Thu, 19 Oct 2017 14:39:07 +0300 Subject: [PATCH 05/16] Replace `httpbin.org` to local pegasus server --- .travis.yml | 17 ++++++-- .travis/platform.sh | 15 +++++++ .travis/setup_uv.sh | 23 ++++++++++ appveyor.yml | 15 +++++-- msvc/lcurl.vcproj | 14 ++++-- test/server.lua | 94 ++++++++++++++++++++++++++++++++++++++++- test/test_curl.lua | 13 +++--- test/test_easy.lua | 14 +++--- test/test_mime.lua | 2 +- test/test_pause02.c.lua | 3 +- 10 files changed, 187 insertions(+), 23 deletions(-) create mode 100644 .travis/platform.sh create mode 100644 .travis/setup_uv.sh diff --git a/.travis.yml b/.travis.yml index 7e0facd..01c85f6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,15 +39,26 @@ before_install: - pip install --user hererocks - hererocks here -r^ --$LUA - source here/bin/activate + - luarocks show lluv > /dev/null 2>&1 || bash .travis/setup_uv.sh install: - luarocks make rockspecs/lua-curl-scm-0.rockspec CFLAGS="$LCURL_CC_FLAGS" LIBFLAG="$LCURL_LD_FLAGS" before_script: - luarocks show luacov-coveralls > /dev/null 2>&1 || luarocks install luacov-coveralls - - luarocks show lunitx > /dev/null 2>&1 || luarocks install lunitx - - luarocks show luafilesystem > /dev/null 2>&1 || luarocks install luafilesystem - - luarocks show dkjson > /dev/null 2>&1 || luarocks install dkjson --deps-mode=none + - luarocks show lunitx > /dev/null 2>&1 || luarocks install lunitx + - luarocks show luafilesystem > /dev/null 2>&1 || luarocks install luafilesystem + - luarocks show dkjson > /dev/null 2>&1 || luarocks install dkjson --deps-mode=none + - luarocks show pegasus > /dev/null 2>&1 || luarocks install pegasus http.parser + --server=http://luarocks.org/manifests/moteus + - luarocks show pegasus-router > /dev/null 2>&1 || luarocks install pegasus-router + --server=http://luarocks.org/dev + - luarocks show lluv > /dev/null 2>&1 || luarocks install lluv UV_DIR=$TRAVIS_BUILD_DIR/libuv + --server=http://luarocks.org/dev + - luarocks show lluv-pegasus > /dev/null 2>&1 || luarocks install lluv-pegasus + --server=http://luarocks.org/dev --deps-mode=none + - lua test/server.lua & + - curl -s http://127.0.0.1:7090/get script: - cd test diff --git a/.travis/platform.sh b/.travis/platform.sh new file mode 100644 index 0000000..7259a7d --- /dev/null +++ b/.travis/platform.sh @@ -0,0 +1,15 @@ +if [ -z "${PLATFORM:-}" ]; then + PLATFORM=$TRAVIS_OS_NAME; +fi + +if [ "$PLATFORM" == "osx" ]; then + PLATFORM="macosx"; +fi + +if [ -z "$PLATFORM" ]; then + if [ "$(uname)" == "Linux" ]; then + PLATFORM="linux"; + else + PLATFORM="macosx"; + fi; +fi diff --git a/.travis/setup_uv.sh b/.travis/setup_uv.sh new file mode 100644 index 0000000..69aa72e --- /dev/null +++ b/.travis/setup_uv.sh @@ -0,0 +1,23 @@ +#! /bin/bash + +source .travis/platform.sh + +cd $TRAVIS_BUILD_DIR + +git clone https://github.com/libuv/libuv.git -b v1.x + +cd libuv + +mkdir -p lib +mkdir -p build +git clone https://chromium.googlesource.com/external/gyp build/gyp + +if [ "$PLATFORM" == "macosx" ]; then + ./gyp_uv.py -f xcode && xcodebuild -ARCHS="x86_64" -project uv.xcodeproj -configuration Release -target All + cp ./build/Release/libuv.a ./lib; +else + ./gyp_uv.py -f make && BUILDTYPE=Release CFLAGS=-fPIC make -C out + cp ./out/Release/libuv.a ./lib; +fi + +cd $TRAVIS_BUILD_DIR diff --git a/appveyor.yml b/appveyor.yml index e4e867b..6004fb2 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -58,10 +58,12 @@ before_test: - luarocks show dkjson >nul 2>&1 || luarocks install dkjson - luarocks show luafilesystem >nul 2>&1 || luarocks install luafilesystem - luarocks show lua-path >nul 2>&1 || luarocks install lua-path - - luarocks show pegasus >nul 2>&1 || luarocks install pegasus http.parser \ + - luarocks show pegasus >nul 2>&1 || luarocks install pegasus http.parser --server=http://luarocks.org/manifests/moteus - - ps: $TestServer = Start-Process lua -ArgumentList test/server.lua -PassThru - - curl -s http://127.0.0.1:7090 + - luarocks show pegasus-router >nul 2>&1 || luarocks install pegasus-router + --server=http://luarocks.org/dev + - ps: $TestServer = Start-Process lua -ArgumentList test/server.lua -RedirectStandardOutput "$env:APPVEYOR_BUILD_FOLDER\server.stdout.txt" -RedirectStandardError "$env:APPVEYOR_BUILD_FOLDER\server.stderr.txt" -PassThru + - curl -s http://127.0.0.1:7090/get test_script: - echo "Testing..." @@ -75,5 +77,10 @@ after_test: - cd %APPVEYOR_BUILD_FOLDER% - .appveyor\pack_artifact.bat lua-curl bin-rock +on_failure: + - ps: $path = "$env:APPVEYOR_BUILD_FOLDER\server.stderr.txt"; if (Test-Path $path -PathType Leaf) { Push-AppveyorArtifact $path; } else { echo "File $path does not exist"; } + - ps: $path = "$env:APPVEYOR_BUILD_FOLDER\server.stdout.txt"; if (Test-Path $path -PathType Leaf) { Push-AppveyorArtifact $path; } else { echo "File $path does not exist"; } + on_finish: - - ps: Stop-Process -Id $TestServer.Id \ No newline at end of file + - curl -s http://127.0.0.1:7090/get + - ps: Stop-Process -Id $TestServer.Id diff --git a/msvc/lcurl.vcproj b/msvc/lcurl.vcproj index 6d58985..7c22f2e 100644 --- a/msvc/lcurl.vcproj +++ b/msvc/lcurl.vcproj @@ -1,7 +1,7 @@ + + @@ -251,6 +255,10 @@ RelativePath="..\src\lcinfoeasy.h" > + + diff --git a/test/server.lua b/test/server.lua index e29bd6c..7e71ad6 100644 --- a/test/server.lua +++ b/test/server.lua @@ -6,10 +6,102 @@ end local uv = prequire "lluv" local Pegasus = require (uv and "lluv.pegasus" or "pegasus") +local Router = require "pegasus.plugins.router" +local json = require "dkjson" +-- local pp = require "pp" -local server = Pegasus:new{host = '127.0.0.1', port = 7090} +local function decode_form(form) + return string.match(form, '\r\nContent%-Disposition:%s*form%-data;%s*name="(.-)".-\r\n\r\n(.-)\r\n') +end + +local function decode_params(str) + local params = {} + for k, v in string.gmatch(str, '([^=]+)=([^&]+)&?') do + params[k] = v + end + return params +end + +local function rand_bytes(n) + local res = {} + for i = 1, n do + res[#res + 1] = string.char(math.random(254)) + end + return table.concat(res) +end + +local r = Router:new() + +local server = Pegasus:new{ + plugins = { r }; + host = '127.0.0.1', port = 7090, timout = 10 +} + +r:get('/get', function(request, response) + local headers = request:headers() + local params = request:params() + local path = request:path() + local ip = request.ip + + local result = json.encode({ + args = params; + headers = headers; + origin = ip; + url = 'http://127.0.0.1' .. path; + }, {indent = true}) + + response:statusCode(200) + response:contentType('application/json') + response:write(result) +end) + +r:get('/bytes/:size', function(request, response, params) + local headers = request:headers() + local size = tonumber(params.size) or 1024 + local result = rand_bytes(size) + + response:statusCode(200) + response:contentType('application/octet-stream') + response:write(result) +end) + +r:post('/post', function(request, response, params) + local headers = request:headers() + local params = request:params() + local path = request:path() + local ip = request.ip + + local body = {} + while true do + local result, status = request:receiveBody() + if result then body[#body + 1] = result + elseif status ~= 'timeout' then break end + end + body = table.concat(body) + + local name, data, form = decode_form(body) + if name then + form = {[name] = data} + else + form = decode_params(body) + end + + local result = json.encode({ + args = params; + headers = headers; + origin = ip; + form = form; + url = 'http://127.0.0.1' .. path; + }, {indent = true}) + + response:statusCode(200) + response:contentType('application/json') + response:write(result) +end) server:start(function(request, response) + local headers = request:headers() + response:statusCode(200) response:addHeader('Content-Type', 'text/plain') response:write('Hello from Pegasus') diff --git a/test/test_curl.lua b/test/test_curl.lua index 3e65497..8047e15 100644 --- a/test/test_curl.lua +++ b/test/test_curl.lua @@ -24,6 +24,8 @@ local utils = require "utils" -- not sure is it bug or not local text_plain = utils.is_curl_eq(7,56,0) and 'test/plain' or 'text/plain' +local GET_URL = "http://127.0.0.1:7090/get" + local ENABLE = true local _ENV = TEST_CASE'version' if ENABLE then @@ -35,7 +37,7 @@ end end -local _ENV = TEST_CASE'easy' if ENABLE then +local _ENV = TEST_CASE'easy' if ENABLE then local e1, e2 function teardown() @@ -124,7 +126,7 @@ end local _ENV = TEST_CASE'multi_iterator' if ENABLE then -local url = "http://httpbin.org/get" +local url = GET_URL local c, t, m @@ -144,8 +146,7 @@ function teardown() end function test_add_handle() - - local base_url = 'http://httpbin.org/get?key=' + local base_url = url .. '?key=' local urls = { base_url .. "1", base_url .. "2", @@ -196,7 +197,9 @@ function test_add_handle() end function test_info_read() - local url = 'http://httpbin.org/get?key=1' + + local url = GET_URL .. '?key=1' + c = assert(curl.easy{url=url, writefunction=function() end}) assert_equal(m, m:add_handle(c)) diff --git a/test/test_easy.lua b/test/test_easy.lua index d61d335..68b54cb 100644 --- a/test/test_easy.lua +++ b/test/test_easy.lua @@ -30,6 +30,10 @@ local fname = "./test.download" local weak_ptr, gc_collect, is_curl_ge, read_file, stream, Stream = utils.import('weak_ptr', 'gc_collect', 'is_curl_ge', 'read_file', 'stream', 'Stream') +-- local POST_URL = "http://httpbin.org/post" + +local POST_URL = "http://127.0.0.1:7090/post" + local ENABLE = true local _ENV = TEST_CASE'curl error' if ENABLE then @@ -416,7 +420,7 @@ local _ENV = TEST_CASE'read_stream_callback' if ENABLE and is_curl_ge(7,30,0) th -- tested on WinXP(x32)/Win8(x64) libcurl/7.37.1 / libcurl/7.30.0 -local url = "http://httpbin.org/post" +local url = POST_URL local m, c, f, t @@ -865,10 +869,10 @@ end function test() do local fields = {} - for i = 1, 100 do fields[#fields + 1] = "key" .. i .. "=value"..i end + for i = 1, 100 do fields[#fields + 1] = string.format('key%d=value%d', i, i) end fields = table.concat(fields, '&') c = assert(curl.easy{ - url = "http://httpbin.org/post", + url = POST_URL, postfields = fields, writefunction = function()end, }) @@ -884,10 +888,10 @@ function test_unset() local pfields do local fields = {} - for i = 1, 100 do fields[#fields + 1] = "key" .. i .. "=value"..i end + for i = 1, 100 do fields[#fields + 1] = string.format('key%d=value%d', i, i) end fields = table.concat(fields, '&') c = assert(curl.easy{ - url = "http://httpbin.org/post", + url = POST_URL, postfields = fields, writefunction = function()end, }) diff --git a/test/test_mime.lua b/test/test_mime.lua index 80964d8..8dc458b 100644 --- a/test/test_mime.lua +++ b/test/test_mime.lua @@ -17,7 +17,7 @@ local utils = require "utils" local weak_ptr, gc_collect, dump_mime_ = utils.import('weak_ptr', 'gc_collect', 'dump_mime') -local dump_mime_url = 'http://127.0.0.1:7090' +local dump_mime_url = 'http://127.0.0.1:7090/post' local function is_freed(c) return not not string.find(tostring(c), '%(freed%)') diff --git a/test/test_pause02.c.lua b/test/test_pause02.c.lua index 383c652..3a1fb4f 100644 --- a/test/test_pause02.c.lua +++ b/test/test_pause02.c.lua @@ -6,7 +6,8 @@ local curl = require "lcurl" local WAIT_COUNT = 15 local SIZE = 10 * 1024 -local RESOURCE_URL = "http://httpbin.org/bytes/" .. SIZE +-- local RESOURCE_URL = "http://httpbin.org/bytes/" .. SIZE +local RESOURCE_URL = "http://127.0.0.1:7090/bytes/" .. SIZE local State = { PAUSE = 0, -- write function should return CURL_WRITEFUNC_PAUSE From c8a0f7ea8ecfe3479650de9f25708ddfe74fc5d1 Mon Sep 17 00:00:00 2001 From: Alexey Melnichuk Date: Thu, 19 Oct 2017 17:36:54 +0300 Subject: [PATCH 06/16] Update test server. --- appveyor.yml | 1 + lakefile | 1 + test/server.lua | 35 +++++++++++++++++++++++++++-------- test/test_mime.lua | 10 +++++----- 4 files changed, 34 insertions(+), 13 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 6004fb2..65e21e3 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -78,6 +78,7 @@ after_test: - .appveyor\pack_artifact.bat lua-curl bin-rock on_failure: + - ps: Stop-Process -Id $TestServer.Id - ps: $path = "$env:APPVEYOR_BUILD_FOLDER\server.stderr.txt"; if (Test-Path $path -PathType Leaf) { Push-AppveyorArtifact $path; } else { echo "File $path does not exist"; } - ps: $path = "$env:APPVEYOR_BUILD_FOLDER\server.stdout.txt"; if (Test-Path $path -PathType Leaf) { Push-AppveyorArtifact $path; } else { echo "File $path does not exist"; } diff --git a/lakefile b/lakefile index 2c4a123..55c77a0 100644 --- a/lakefile +++ b/lakefile @@ -32,6 +32,7 @@ target('test', install, function() run_test('test_form.lua') run_test('test_pause02.c.lua') run_test('test_curl.lua') + run_test('test_mime.lua') run_test('test_multi_callback.lua') run_test('test_multi_nested_callback.lua') diff --git a/test/server.lua b/test/server.lua index 7e71ad6..594b12e 100644 --- a/test/server.lua +++ b/test/server.lua @@ -4,7 +4,7 @@ local function prequire(m) return err end -local uv = prequire "lluv" +local uv = prequire "lluv-" local Pegasus = require (uv and "lluv.pegasus" or "pegasus") local Router = require "pegasus.plugins.router" local json = require "dkjson" @@ -37,20 +37,44 @@ local server = Pegasus:new{ host = '127.0.0.1', port = 7090, timout = 10 } +local function recvFullBody(request, T1) + local body, counter = {}, 0 + + local result, status + while true do + result, status = request:receiveBody() + if result then + counter = 0 + body[#body + 1] = result + elseif status ~= 'timeout' then + break + else + counter = counter + 1 + if counter > T1 then break end + end + end + + return table.concat(body), status +end + r:get('/get', function(request, response) local headers = request:headers() local params = request:params() local path = request:path() local ip = request.ip + local body, status = recvFullBody(request, 15) + local result = json.encode({ args = params; headers = headers; origin = ip; + content = body; url = 'http://127.0.0.1' .. path; }, {indent = true}) response:statusCode(200) + response:addHeader('Connection', 'close') response:contentType('application/json') response:write(result) end) @@ -71,13 +95,7 @@ r:post('/post', function(request, response, params) local path = request:path() local ip = request.ip - local body = {} - while true do - local result, status = request:receiveBody() - if result then body[#body + 1] = result - elseif status ~= 'timeout' then break end - end - body = table.concat(body) + local body, status = recvFullBody(request, 15) local name, data, form = decode_form(body) if name then @@ -95,6 +113,7 @@ r:post('/post', function(request, response, params) }, {indent = true}) response:statusCode(200) + response:addHeader('Connection', 'close') response:contentType('application/json') response:write(result) end) diff --git a/test/test_mime.lua b/test/test_mime.lua index 8dc458b..fa2456f 100644 --- a/test/test_mime.lua +++ b/test/test_mime.lua @@ -17,7 +17,7 @@ local utils = require "utils" local weak_ptr, gc_collect, dump_mime_ = utils.import('weak_ptr', 'gc_collect', 'dump_mime') -local dump_mime_url = 'http://127.0.0.1:7090/post' +local dump_mime_url = 'http://127.0.0.1:7090/get' local function is_freed(c) return not not string.find(tostring(c), '%(freed%)') @@ -246,16 +246,16 @@ function test_data_should_not_unset_on_nil() assert_match('Content%-Disposition:.-name="test2"', info) assert_match('X%-Custom%-Header:%s*hello', info) - part:data('!!!', 'text/xml', nil) + part:data('!!!!!', 'text/xml', nil) info = assert_string(dump_mime(mime)) - assert_match('\r\n\r\n!!!', info) + assert_match('\r\n\r\n!!!!!', info) assert_match('Content%-Type:%s+text/xml', info) assert_match('Content%-Disposition:.-name="test2"', info) assert_match('X%-Custom%-Header:%s*hello', info) - part:data('!!!', 'text/xml', nil, nil) + part:data('!!!!!!!', 'text/xml', nil, nil) info = assert_string(dump_mime(mime)) - assert_match('\r\n\r\n!!!', info) + assert_match('\r\n\r\n!!!!!', info) assert_match('Content%-Type:%s+text/xml', info) assert_match('Content%-Disposition:.-name="test2"', info) assert_match('X%-Custom%-Header:%s*hello', info) From c0f7a3f18a8e002cc662382f4cbdca12ee7cd57c Mon Sep 17 00:00:00 2001 From: Alexey Melnichuk Date: Thu, 19 Oct 2017 18:06:32 +0300 Subject: [PATCH 07/16] Replace `http://example.com` to local pegasus server. --- test/test_easy.lua | 59 +++++++++++++++++++++++----------------------- test/test_mime.lua | 4 ++-- 2 files changed, 32 insertions(+), 31 deletions(-) diff --git a/test/test_easy.lua b/test/test_easy.lua index 68b54cb..3ffb408 100644 --- a/test/test_easy.lua +++ b/test/test_easy.lua @@ -18,9 +18,14 @@ local json = require "dkjson" local path = require "path" local upath = require "path".new('/') local utils = require "utils" -local url = "http://example.com" +-- local url = "http://127.0.0.1:7090/get" local fname = "./test.download" +-- local GET_URL = "http://example.com" +-- local POST_URL = "http://httpbin.org/post" +local GET_URL = "http://127.0.0.1:7090/get" +local POST_URL = "http://127.0.0.1:7090/post" + -- print("------------------------------------") -- print("Lua version: " .. (_G.jit and _G.jit.version or _G._VERSION)) -- print("cURL version: " .. curl.version()) @@ -30,10 +35,6 @@ local fname = "./test.download" local weak_ptr, gc_collect, is_curl_ge, read_file, stream, Stream = utils.import('weak_ptr', 'gc_collect', 'is_curl_ge', 'read_file', 'stream', 'Stream') --- local POST_URL = "http://httpbin.org/post" - -local POST_URL = "http://127.0.0.1:7090/post" - local ENABLE = true local _ENV = TEST_CASE'curl error' if ENABLE then @@ -97,7 +98,7 @@ end function test_write_to_file() f = assert(io.open(fname, "w+b")) c = assert(curl.easy{ - url = url; + url = GET_URL; writefunction = f; }) @@ -106,7 +107,7 @@ end function test_write_abort_01() c = assert(scurl.easy{ - url = url; + url = GET_URL; writefunction = function(str) return #str - 1 end; }) @@ -116,7 +117,7 @@ end function test_write_abort_02() c = assert(scurl.easy{ - url = url; + url = GET_URL; writefunction = function(str) return false end; }) @@ -126,7 +127,7 @@ end function test_write_abort_03() c = assert(scurl.easy{ - url = url; + url = GET_URL; writefunction = function(str) return nil, "WRITEERROR" end; }) @@ -136,7 +137,7 @@ end function test_write_abort_04() c = assert(scurl.easy{ - url = url; + url = GET_URL; writefunction = function(str) return nil end; }) @@ -157,7 +158,7 @@ end function test_write_pass_01() c = assert(curl.easy{ - url = url; + url = GET_URL; writefunction = function(s) return #s end }) @@ -166,7 +167,7 @@ end function test_write_pass_02() c = assert(curl.easy{ - url = url; + url = GET_URL; writefunction = function() return end }) @@ -175,7 +176,7 @@ end function test_write_pass_03() c = assert(curl.easy{ - url = url; + url = GET_URL; writefunction = function() return true end }) @@ -188,7 +189,7 @@ function test_write_coro() co1 = coroutine.create(function() c = assert(curl.easy{ - url = url; + url = GET_URL; writefunction = function() called = coroutine.running() return true @@ -224,7 +225,7 @@ end function test_abort_01() c = assert(scurl.easy{ - url = url, + url = GET_URL, writefunction = pass, noprogress = false, progressfunction = function() return false end @@ -236,7 +237,7 @@ end function test_abort_02() c = assert(scurl.easy{ - url = url, + url = GET_URL, writefunction = pass, noprogress = false, progressfunction = function() return 0 end @@ -248,7 +249,7 @@ end function test_abort_03() c = assert(scurl.easy{ - url = url, + url = GET_URL, writefunction = pass, noprogress = false, progressfunction = function() return nil end @@ -260,7 +261,7 @@ end function test_abort_04() c = assert(scurl.easy{ - url = url, + url = GET_URL, writefunction = pass, noprogress = false, progressfunction = function() return nil, "PROGRESSERROR" end @@ -272,7 +273,7 @@ end function test_abort_05() c = assert(scurl.easy{ - url = url, + url = GET_URL, writefunction = pass, noprogress = false, progressfunction = function() error( "PROGRESSERROR" )end @@ -285,7 +286,7 @@ end function test_pass_01() c = assert(scurl.easy{ - url = url, + url = GET_URL, writefunction = pass, noprogress = false, progressfunction = function() end @@ -296,7 +297,7 @@ end function test_pass_02() c = assert(scurl.easy{ - url = url, + url = GET_URL, writefunction = pass, noprogress = false, progressfunction = function() return true end @@ -307,7 +308,7 @@ end function test_pass_03() c = assert(scurl.easy{ - url = url, + url = GET_URL, writefunction = pass, noprogress = false, progressfunction = function() return 1 end @@ -330,7 +331,7 @@ end function test_header_abort_01() c = assert(scurl.easy{ - url = url; + url = GET_URL; writefunction = dummy, headerfunction = function(str) return #str - 1 end; }) @@ -341,7 +342,7 @@ end function test_header_abort_02() c = assert(scurl.easy{ - url = url; + url = GET_URL; writefunction = dummy, headerfunction = function(str) return false end; }) @@ -352,7 +353,7 @@ end function test_header_abort_03() c = assert(scurl.easy{ - url = url; + url = GET_URL; writefunction = dummy, headerfunction = function(str) return nil, "WRITEERROR" end; }) @@ -363,7 +364,7 @@ end function test_header_abort_04() c = assert(scurl.easy{ - url = url; + url = GET_URL; writefunction = dummy, headerfunction = function(str) return nil end; }) @@ -385,7 +386,7 @@ end function test_header_pass_01() c = assert(curl.easy{ - url = url; + url = GET_URL; writefunction = dummy, headerfunction = function(s) return #s end }) @@ -395,7 +396,7 @@ end function test_header_pass_02() c = assert(curl.easy{ - url = url; + url = GET_URL; writefunction = dummy, headerfunction = function() return end }) @@ -405,7 +406,7 @@ end function test_header_pass_03() c = assert(curl.easy{ - url = url; + url = GET_URL; writefunction = dummy, headerfunction = function() return true end }) diff --git a/test/test_mime.lua b/test/test_mime.lua index fa2456f..ea7f6ad 100644 --- a/test/test_mime.lua +++ b/test/test_mime.lua @@ -17,7 +17,7 @@ local utils = require "utils" local weak_ptr, gc_collect, dump_mime_ = utils.import('weak_ptr', 'gc_collect', 'dump_mime') -local dump_mime_url = 'http://127.0.0.1:7090/get' +local GET_URL = 'http://127.0.0.1:7090/get' local function is_freed(c) return not not string.find(tostring(c), '%(freed%)') @@ -187,7 +187,7 @@ else local easy, mime local function dump_mime(mime) - return dump_mime_(easy, mime, dump_mime_url) + return dump_mime_(easy, mime, GET_URL) end function setup() From 37b880fef4f6a0923575ba98feb7ca4cb9bc52da Mon Sep 17 00:00:00 2001 From: Alexey Melnichuk Date: Thu, 19 Oct 2017 19:19:40 +0300 Subject: [PATCH 08/16] Update test server [ci skip] --- test/server.lua | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/test/server.lua b/test/server.lua index 594b12e..65aa9de 100644 --- a/test/server.lua +++ b/test/server.lua @@ -4,7 +4,7 @@ local function prequire(m) return err end -local uv = prequire "lluv-" +local uv = prequire "lluv" local Pegasus = require (uv and "lluv.pegasus" or "pegasus") local Router = require "pegasus.plugins.router" local json = require "dkjson" @@ -79,16 +79,6 @@ r:get('/get', function(request, response) response:write(result) end) -r:get('/bytes/:size', function(request, response, params) - local headers = request:headers() - local size = tonumber(params.size) or 1024 - local result = rand_bytes(size) - - response:statusCode(200) - response:contentType('application/octet-stream') - response:write(result) -end) - r:post('/post', function(request, response, params) local headers = request:headers() local params = request:params() @@ -118,6 +108,17 @@ r:post('/post', function(request, response, params) response:write(result) end) +r:get('/bytes/:size', function(request, response, params) + local headers = request:headers() + local size = tonumber(params.size) or 1024 + local result = rand_bytes(size) + + response:statusCode(200) + response:addHeader('Connection', 'close') + response:contentType('application/octet-stream') + response:write(result) +end) + server:start(function(request, response) local headers = request:headers() From 435a0be37b3bd4c36234986e3d2a6206113f8184 Mon Sep 17 00:00:00 2001 From: Alexey Melnichuk Date: Mon, 23 Oct 2017 12:47:27 +0300 Subject: [PATCH 09/16] Update curl version on AppVeyor --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 65e21e3..e6bc3db 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -7,7 +7,7 @@ shallow_clone: true environment: LR_EXTERNAL: c:\external - CURL_VER: 7.56.0 + CURL_VER: 7.56.1 matrix: - LUA: "lua 5.1" From e523cb45357ff946bc595c97bfe7b7cdc0ce6111 Mon Sep 17 00:00:00 2001 From: Alexey Melnichuk Date: Mon, 23 Oct 2017 13:10:09 +0300 Subject: [PATCH 10/16] Handle hpost stream abort CURLE_READ_ERROR error code. --- src/lchttppost.c | 10 +++++++--- test/test_curl.lua | 3 +-- test/test_form.lua | 3 +-- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/lchttppost.c b/src/lchttppost.c index e3b315a..1f5b3bf 100644 --- a/src/lchttppost.c +++ b/src/lchttppost.c @@ -44,10 +44,14 @@ static const char *LCURL_HTTPPOST = LCURL_HTTPPOST_NAME; # define LCURL_LEN_TYPE long #endif -/* 7.56.0 changed code for `curl_formget` if callback abort write - * not sure is it bug or not so set only for single version +/* 7.56.0 changed code for `curl_formget` if callback abort write. + * + * https://github.com/curl/curl/issues/1987#issuecomment-336139060 + * ... not sure its worth the effort to document its return codes to + * any further extent then it currently is. This function is very + * rarely used, and the new mime API doesn't even have a version of it. **/ -#if LCURL_CURL_VER_GE(7,56,0) && !LCURL_CURL_VER_GE(7,56,1) +#if LCURL_CURL_VER_GE(7,56,0) # define LCURL_GET_CB_ERROR CURLE_READ_ERROR #else # define LCURL_GET_CB_ERROR (CURLcode)-1 diff --git a/test/test_curl.lua b/test/test_curl.lua index 8047e15..2c279b4 100644 --- a/test/test_curl.lua +++ b/test/test_curl.lua @@ -20,8 +20,7 @@ local fname = "./test.download" local utils = require "utils" --- libcurl 7.56.0 does not add `Content-Type: text/plain` --- not sure is it bug or not +-- Bug. libcurl 7.56.0 does not add `Content-Type: text/plain` local text_plain = utils.is_curl_eq(7,56,0) and 'test/plain' or 'text/plain' local GET_URL = "http://127.0.0.1:7090/get" diff --git a/test/test_form.lua b/test/test_form.lua index 11ac8e8..08836cc 100644 --- a/test/test_form.lua +++ b/test/test_form.lua @@ -14,8 +14,7 @@ local skip = lunit.skip or function() end local utils = require "utils" --- libcurl 7.56.0 does not add `Content-Type: text/plain` --- not sure is it bug or not +-- Bug. libcurl 7.56.0 does not add `Content-Type: text/plain` local text_plain = utils.is_curl_eq(7,56,0) and 'test/plain' or 'text/plain' local curl = require "lcurl" From 6734219197825f7ec9289dd8dd250a2577372ac9 Mon Sep 17 00:00:00 2001 From: Alexey Melnichuk Date: Mon, 23 Oct 2017 16:16:51 +0300 Subject: [PATCH 11/16] Add. `filename` parameter to mime data/filedata Add. `addpart` accept table as argument Add. `encoder` method to mime part --- src/lcmime.c | 206 ++++++++++++++++++++++++++++++++++++--------- test/test_mime.lua | 100 +++++++++++++++++++++- 2 files changed, 264 insertions(+), 42 deletions(-) diff --git a/src/lcmime.c b/src/lcmime.c index 0208a4e..9610186 100644 --- a/src/lcmime.c +++ b/src/lcmime.c @@ -79,6 +79,80 @@ int lcurl_mime_set_lua(lua_State *L, lcurl_mime_t *p, lua_State *v){ return 0; } +#define IS_NILORSTR(L, i) (lua_type(L, i) == LUA_TSTRING) || (lua_type(L, i) == LUA_TNIL) +#define IS_TABLE(L, i) lua_type(L, i) == LUA_TTABLE +#define IS_FALSE(L, i) (lua_type(L, i) == LUA_TBOOLEAN) && (!lua_toboolean(L, i)) +#define IS_OPTSTR(L, i) (IS_FALSE(L, i)) || (IS_NILORSTR(L, i)) + +static int lutil_isarray(lua_State *L, int i){ + int ret = 0; + lua_pushnil(L); + if(lua_next(L, i)){ + ret = lua_isnumber(L, -2); + lua_pop(L, 2); + } + return ret; +} + +static int lcurl_mime_part_assig(lua_State *L, int part, const char *method){ + int top = lua_gettop(L); + + lua_pushvalue(L, part); + lua_insert(L, -2); + lua_getfield(L, -2, method); + lua_insert(L, -3); + lua_call(L, 2, LUA_MULTRET); + + return lua_gettop(L) - top + 1; +} + +static const char *lcurl_mime_part_fields[] = { + "data", "filedata", "name", "filename", "headers", "encoder", NULL +}; + +static int lcurl_mime_part_assing_table(lua_State *L, int part, int t){ + int top = lua_gettop(L); + const char *method; int i; + + part = lua_absindex(L, part); + t = lua_absindex(L, t); + + if(lutil_isarray(L, t)){ + int ret; + lua_pushvalue(L, t); + ret = lcurl_mime_part_assig(L, part, "headers"); + if(ret != 1) return ret; + + lua_pop(L, 1); + + assert(top == lua_gettop(L)); + } + else{ + for(i=0;method = lcurl_mime_part_fields[i]; ++i){ + lua_getfield(L, t, method); + if(!lua_isnil(L, -1)){ + int ret = lcurl_mime_part_assig(L, part, method); + if(ret != 1) return ret; + } + lua_pop(L, 1); + + assert(top == lua_gettop(L)); + } + + lua_getfield(L, t, "subparts"); + if(!lua_isnil(L, -1)){ + if(IS_FALSE(L, -1) || lcurl_getmimepart_at(L, -1)){ + int ret = lcurl_mime_part_assig(L, part, "subparts"); + if(ret != 1) return ret; + } + } + lua_pop(L, 1); + assert(top == lua_gettop(L)); + } + + return 0; +} + //{ MIME static lcurl_mime_part_t* lcurl_mime_parts_append(lcurl_mime_t *m, lcurl_mime_part_t *p){ @@ -149,12 +223,22 @@ static int lcurl_mime_free(lua_State *L){ static int lcurl_mime_addpart(lua_State *L){ lcurl_mime_t *p = lcurl_getmime(L); - int ret = lcurl_mime_part_create(L, p->err_mode); + int ret; + + lua_settop(L, 2); + + ret = lcurl_mime_part_create(L, p->err_mode); if(ret != 1) return ret; /* store mime part in storage */ lcurl_storage_preserve_value(L, p->storage, lua_absindex(L, -1)); lcurl_mime_parts_append(p, lcurl_getmimepart_at(L, -1)); + + if(lua_istable(L, 2)){ + ret = lcurl_mime_part_assing_table(L, 3, 2); + if(ret) return ret; + } + return 1; } @@ -239,72 +323,69 @@ static void lcurl_mime_part_remove_subparts(lua_State *L, lcurl_mime_part_t *p, } } -#define IS_NILORSTR(L, i) (lua_type(L, i) == LUA_TSTRING) || (lua_type(L, i) == LUA_TNIL) -#define IS_TABLE(L, i) lua_type(L, i) == LUA_TTABLE -#define IS_FALSE(L, i) (lua_type(L, i) == LUA_TBOOLEAN) && (!lua_toboolean(L, i)) -#define IS_OPTSTR(L, i) (IS_FALSE(L, i)) || (IS_NILORSTR(L, i)) +static int lcurl_mime_part_assing_ext(lua_State *L, int part, int i){ +#define UNSET_VALUE (const char*)-1 -static int lcurl_mime_part_assing_ext(lua_State *L, lcurl_mime_part_t *p, int i){ - const char *mime_type = NULL, *mime_name = NULL; + const char *mime_type = NULL, *mime_name = NULL, *mime_fname = NULL; int headers = 0; CURLcode ret; + lcurl_mime_part_t *p = lcurl_getmimepart_at(L, part); if(IS_TABLE(L, i)) headers = i; else if (IS_OPTSTR(L, i)) { - if (IS_FALSE(L, i)) { - ret = curl_mime_type(p->part, NULL); - if(ret != CURLE_OK) - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); - } - else{ - mime_type = lua_tostring(L, i); - } + mime_type = IS_FALSE(L, i) ? UNSET_VALUE : lua_tostring(L, i); if(IS_TABLE(L, i+1)) headers = i+1; else if(IS_OPTSTR(L, i+1)){ - if (IS_FALSE(L, i+1)) { - ret = curl_mime_name(p->part, NULL); - if(ret != CURLE_OK) - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); - } - else{ - mime_name = lua_tostring(L, i+1); - } + mime_name = IS_FALSE(L, i+1) ? UNSET_VALUE : lua_tostring(L, i+1); if(IS_TABLE(L, i+2)) headers = i+2; - else if(IS_FALSE(L, i + 2)){ - ret = curl_mime_headers(p->part, NULL, 0); - if(ret != CURLE_OK) - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); + else if(IS_OPTSTR(L, i+2)){ + mime_fname = IS_FALSE(L, i+2) ? UNSET_VALUE : lua_tostring(L, i+2); + if(IS_TABLE(L, i+3)) headers = i+3; + else if(IS_FALSE(L, i+3)){ + headers = -1; + } } } } if(mime_type){ - ret = curl_mime_type(p->part, mime_type); + ret = curl_mime_type(p->part, mime_type == UNSET_VALUE ? NULL : mime_type); if(ret != CURLE_OK){ return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); } } if(mime_name){ - ret = curl_mime_name(p->part, mime_name); + ret = curl_mime_name(p->part, mime_name == UNSET_VALUE ? NULL : mime_name); + if(ret != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); + } + } + + if(mime_fname){ + ret = curl_mime_filename(p->part, mime_fname == UNSET_VALUE ? NULL : mime_fname); if(ret != CURLE_OK){ return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); } } if(headers){ - struct curl_slist *list = lcurl_util_to_slist(L, headers); - ret = curl_mime_headers(p->part, list, 1); - if(ret != CURLE_OK){ - curl_slist_free_all(list); - return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); + if(-1 == headers){ + ret = curl_mime_headers(p->part, NULL, 0); + if(ret != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); + } } + else + return lcurl_mime_part_assing_table(L, part, headers); } return 0; + +#undef UNSET_VALUE } -// part:data(str[, type[, name]][, headers]) +// part:data(str[, type[, name[, filename]]][, headers]) static int lcurl_mime_part_data(lua_State *L){ lcurl_mime_part_t *p = lcurl_getmimepart(L); size_t len; const char *data; @@ -312,6 +393,7 @@ static int lcurl_mime_part_data(lua_State *L){ if(IS_FALSE(L, 2)){ data = NULL; + len = 0; } else{ data = luaL_checklstring(L, 2, &len); @@ -328,7 +410,7 @@ static int lcurl_mime_part_data(lua_State *L){ } if (lua_gettop(L) > 2){ - int res = lcurl_mime_part_assing_ext(L, p, 3); + int res = lcurl_mime_part_assing_ext(L, 1, 3); if (res) return res; } @@ -360,7 +442,7 @@ static int lcurl_mime_part_subparts(lua_State *L){ mime->parent = p; if (lua_gettop(L) > 2){ - int res = lcurl_mime_part_assing_ext(L, p, 3); + int res = lcurl_mime_part_assing_ext(L, 1, 3); if (res) return res; } @@ -368,7 +450,7 @@ static int lcurl_mime_part_subparts(lua_State *L){ return 1; } -// part:filedata(path[, type[, name]][, headers]) +// part:filedata(path[, type[, name[, filename]]][, headers]) static int lcurl_mime_part_filedata(lua_State *L){ lcurl_mime_part_t *p = lcurl_getmimepart(L); const char *data = luaL_checkstring(L, 2); @@ -380,7 +462,7 @@ static int lcurl_mime_part_filedata(lua_State *L){ } if (lua_gettop(L) > 2){ - int res = lcurl_mime_part_assing_ext(L, p, 3); + int res = lcurl_mime_part_assing_ext(L, 1, 3); if (res) return res; } @@ -458,6 +540,49 @@ static int lcurl_mime_part_name(lua_State *L){ return 1; } +// part:filename(t) +static int lcurl_mime_part_filename(lua_State *L){ + lcurl_mime_part_t *p = lcurl_getmimepart(L); + const char *mime_name; + CURLcode ret; + + if(IS_FALSE(L, 2)){ + mime_name = NULL; + } + else{ + mime_name = luaL_checkstring(L, 2); + } + ret = curl_mime_filename(p->part, mime_name); + + if(ret != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); + } + + lua_settop(L, 1); + return 1; +} + +// part:encoder(t) +static int lcurl_mime_part_encoder(lua_State *L){ + lcurl_mime_part_t *p = lcurl_getmimepart(L); + const char *mime_encode; + CURLcode ret; + + if(IS_FALSE(L, 2)){ + mime_encode = NULL; + } + else{ + mime_encode = luaL_checkstring(L, 2); + } + ret = curl_mime_encoder(p->part, mime_encode); + + if(ret != CURLE_OK){ + return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret); + } + + lua_settop(L, 1); + return 1; +} //} @@ -479,7 +604,10 @@ static const struct luaL_Reg lcurl_mime_part_methods[] = { {"filedata", lcurl_mime_part_filedata }, {"headers", lcurl_mime_part_headers }, {"name", lcurl_mime_part_name }, + {"filename", lcurl_mime_part_filename }, {"type", lcurl_mime_part_type }, + {"encoder", lcurl_mime_part_encoder }, + {"free", lcurl_mime_part_free }, {"__gc", lcurl_mime_part_free }, diff --git a/test/test_mime.lua b/test/test_mime.lua index ea7f6ad..0517f97 100644 --- a/test/test_mime.lua +++ b/test/test_mime.lua @@ -178,6 +178,33 @@ function test_preserve_mime_by_easy() assert_nil(mime.value) end +function test_mime_does_not_ref_to_easy() + -- exists of mime object does not prevent easy from GC + + local mime = easy:mime() + local peasy = weak_ptr(easy) + easy = nil + + gc_collect() + + assert_nil(peasy.value) +end + +function test_mimepost_does_not_ref_to_easy() + -- exists of mime object does not prevent easy from GC + + -- create mime and set it as mimepost option + local mime = easy:mime() + easy:setopt_mimepost(mime) + + local peasy = weak_ptr(easy) + easy = nil + + gc_collect() + + assert_nil(peasy.value) +end + end local _ENV = TEST_CASE'mime basic' if not curl.OPT_MIMEPOST then @@ -219,14 +246,21 @@ function test_data_type_name() local info = assert_string(dump_mime(mime)) assert_match('\r\n\r\nhello', info) assert_match('Content%-Type:%s+text/html', info) - assert_match('Content%-Disposition:.-name="test"', info) + assert_match('Content%-Disposition:.-%sname="test"', info) end function test_data_name() mime:addpart():data('hello', nil, 'test') local info = assert_string(dump_mime(mime)) assert_match('\r\n\r\nhello', info) - assert_match('Content%-Disposition:.-name="test"', info) + assert_match('Content%-Disposition:.-%sname="test"', info) +end + +function test_data_filename() + mime:addpart():data('hello', nil, nil, 'test.html') + local info = assert_string(dump_mime(mime)) + assert_match('\r\n\r\nhello', info) + assert_match('Content%-Disposition:.-%sfilename="test%.html"', info) end function test_data_should_not_unset_on_nil() @@ -357,7 +391,7 @@ end function test_unset_data_header() local part = mime:addpart():data('hello', 'text/html', 'test', { 'X-Custom-Header: hello' - }):data('hello', nil, nil, false) + }):data('hello', nil, nil, nil, false) local info = assert_string(dump_mime(mime)) assert_match('\r\n\r\nhello', info) @@ -366,6 +400,19 @@ function test_unset_data_header() assert_not_match('X%-Custom%-Header:%s*hello', info) end +function test_unset_data_filename_1() + local part = mime:addpart():data('hello', 'text/html', 'test', 'test.html', { + 'X-Custom-Header: hello' + }):data('hello', nil, nil, false) + + local info = assert_string(dump_mime(mime)) + assert_match('\r\n\r\nhello', info) + assert_match('Content%-Type:%s+text/html', info) + assert_match('Content%-Disposition:.-%sname="test"', info) + assert_not_match('Content%-Disposition:.-%sname="test%.html"', info) + assert_match('X%-Custom%-Header:%s*hello', info) +end + function test_fail_pass_nil_as_first_arg() local part = mime:addpart() assert_error(function() part:data() end) @@ -381,6 +428,53 @@ function test_fail_pass_nil_as_first_arg() assert_error(function() part:headers(nil) end) end +end + +local _ENV = TEST_CASE'mime addpart' if not curl.OPT_MIMEPOST then +function test() skip("MIMI API supports since cURL 7.56.0") end +else + +local easy, mime + +local function dump_mime(mime) + return dump_mime_(easy, mime, GET_URL) +end + +function setup() + easy = curl.easy() + mime = easy:mime() +end + +function teardown() + if easy then easy:close() end + if mime then mime:free() end + easy, mime = nil +end + +function test_empty_table() + assert(mime:addpart{}) +end + +function test_pass_args() + assert(mime:addpart{ + data = 'hello'; + encoder = 'base64'; + name = 'test'; + filename = 'test.html'; + type = 'text/html'; + headers = { + 'X-Custom-Header: hello'; + } + }) + + local info = assert_string(dump_mime(mime)) + assert_match('\r\n\r\naGVsbG8=', info) + assert_match('Content%-Type:%s+text/html', info) + assert_match('Content%-Disposition:.-%sname="test"', info) + assert_not_match('Content%-Disposition:.-%sname="test%.html"', info) + assert_match('X%-Custom%-Header:%s*hello', info) + assert_match('Content%-Transfer%-Encoding:%s*base64', info) +end end From d86d77b582d5e064d9c8e58cdc545562fe099063 Mon Sep 17 00:00:00 2001 From: Alexey Melnichuk Date: Fri, 3 Nov 2017 11:39:20 +0300 Subject: [PATCH 12/16] Update test server --- test/server.lua | 43 +++++++++++++++++-------------------------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/test/server.lua b/test/server.lua index 65aa9de..a3053da 100644 --- a/test/server.lua +++ b/test/server.lua @@ -57,55 +57,46 @@ local function recvFullBody(request, T1) return table.concat(body), status end -r:get('/get', function(request, response) +local function buildResponse(request) local headers = request:headers() local params = request:params() local path = request:path() local ip = request.ip + local host = headers and headers.Host or '127.0.0.1' + local url = string.format('http://%s%s', host, path) - local body, status = recvFullBody(request, 15) - - local result = json.encode({ + return { args = params; headers = headers; origin = ip; - content = body; - url = 'http://127.0.0.1' .. path; - }, {indent = true}) + url = url; + } +end + +r:get('/get', function(request, response) + local result = buildResponse(request) + result.body = recvFullBody(request, 15) response:statusCode(200) - response:addHeader('Connection', 'close') response:contentType('application/json') - response:write(result) + response:write(json.encode(result, {indent = true})) end) r:post('/post', function(request, response, params) - local headers = request:headers() - local params = request:params() - local path = request:path() - local ip = request.ip + local result = buildResponse(request) local body, status = recvFullBody(request, 15) - local name, data, form = decode_form(body) + local name, data = decode_form(body) if name then - form = {[name] = data} + result.form = {[name] = data} else - form = decode_params(body) + result.form = decode_params(body) end - local result = json.encode({ - args = params; - headers = headers; - origin = ip; - form = form; - url = 'http://127.0.0.1' .. path; - }, {indent = true}) - response:statusCode(200) - response:addHeader('Connection', 'close') response:contentType('application/json') - response:write(result) + response:write(json.encode(result, {indent = true})) end) r:get('/bytes/:size', function(request, response, params) From 58847b11addb495b3f193982df3bbe4241baab94 Mon Sep 17 00:00:00 2001 From: Alexey Melnichuk Date: Mon, 30 Apr 2018 13:24:43 +0300 Subject: [PATCH 13/16] Add. `mime:easy()` method. --- appveyor.yml | 2 +- src/lcmime.c | 113 +++++++++++++++++++++++++++++---------------- src/lcurl.c | 47 +++++++++++++++---- src/lcurl.h | 5 +- test/test_mime.lua | 56 +++++++++++----------- 5 files changed, 143 insertions(+), 80 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index e6bc3db..1fdfb3d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -7,7 +7,7 @@ shallow_clone: true environment: LR_EXTERNAL: c:\external - CURL_VER: 7.56.1 + CURL_VER: 7.59.0 matrix: - LUA: "lua 5.1" diff --git a/src/lcmime.c b/src/lcmime.c index 9610186..457d37b 100644 --- a/src/lcmime.c +++ b/src/lcmime.c @@ -14,6 +14,22 @@ #include "lcerror.h" #include "lcutils.h" +/* API Notes. + * 1. Each mime can be root or child. If mime is a child (subpart) then curl free it + * when parent mime is freed or when remove this part from parent. There no way reuse same mime. + * Its not clear is it possible use mime created by one easy handle when do preform in another. + * `m=e1:mime() e2:setopt_httpmime(m) e1:close() e2:perform()` + * + * // Attach child to root (root also can have parent) + * curl_mime_subparts(root, child); + * + * // curl free `child` and all its childs + * curl_mime_subparts(root, other_child_or_null); + * + * // forbidden + * curl_mime_free(child); + */ + #if LCURL_CURL_VER_GE(7,56,0) #define LCURL_MIME_NAME LCURL_PREFIX" MIME" @@ -22,6 +38,8 @@ static const char *LCURL_MIME = LCURL_MIME_NAME; #define LCURL_MIME_PART_NAME LCURL_PREFIX" MIME Part" static const char *LCURL_MIME_PART = LCURL_MIME_PART_NAME; +//{ Free mime and subparts + static void lcurl_mime_part_remove_subparts(lua_State *L, lcurl_mime_part_t *p, int free_it); static lcurl_mime_t* lcurl_mime_part_get_subparts(lua_State *L, lcurl_mime_part_t *part){ @@ -66,9 +84,45 @@ static int lcurl_mime_reset(lua_State *L, lcurl_mime_t *p){ p->parts = p->parent = NULL; p->mime = NULL; + /* remove weak reference to easy */ + lua_pushnil(L); + lua_rawsetp(L, LCURL_MIME_EASY, p); + return 0; } +static void lcurl_mime_part_remove_subparts(lua_State *L, lcurl_mime_part_t *p, int free_it){ + lcurl_mime_t *sub = lcurl_mime_part_get_subparts(L, p); + if(sub){ + assert(LUA_NOREF != p->subpart_ref); + /* detach `subpart` mime from current mime part */ + /* if set `sub->parent = NULL` then gc for mime will try free curl_mime_free. */ + + luaL_unref(L, LCURL_LUA_REGISTRY, p->subpart_ref); + p->subpart_ref = LUA_NOREF; + + if(p->part && free_it){ + curl_mime_subparts(p->part, NULL); + } + + /* seems curl_mime_subparts(h, NULL) free asubparts. + so we have to invalidate all reference to all nested objects (part/mime). + NOTE. All resources already feed. So just need set all pointers to NULL + and free all Lua resources (like references and storages) + */ + { + lcurl_mime_part_t *ptr; + /* reset all parts*/ + for(ptr = sub->parts; ptr; ptr=ptr->next){ + lcurl_mime_part_remove_subparts(L, p, 0); + } + lcurl_mime_reset(L, sub); + } + } +} + +//} + int lcurl_mime_set_lua(lua_State *L, lcurl_mime_t *p, lua_State *v){ lcurl_mime_part_t *part; for(part = p->parts; part; part=part->next){ @@ -86,6 +140,7 @@ int lcurl_mime_set_lua(lua_State *L, lcurl_mime_t *p, lua_State *v){ static int lutil_isarray(lua_State *L, int i){ int ret = 0; + i = lua_absindex(L, i); lua_pushnil(L); if(lua_next(L, i)){ ret = lua_isnumber(L, -2); @@ -94,7 +149,7 @@ static int lutil_isarray(lua_State *L, int i){ return ret; } -static int lcurl_mime_part_assig(lua_State *L, int part, const char *method){ +static int lcurl_mime_part_assign(lua_State *L, int part, const char *method){ int top = lua_gettop(L); lua_pushvalue(L, part); @@ -107,7 +162,7 @@ static int lcurl_mime_part_assig(lua_State *L, int part, const char *method){ } static const char *lcurl_mime_part_fields[] = { - "data", "filedata", "name", "filename", "headers", "encoder", NULL + "data", "filedata", "name", "filename", "headers", "encoder", "type", NULL }; static int lcurl_mime_part_assing_table(lua_State *L, int part, int t){ @@ -120,7 +175,7 @@ static int lcurl_mime_part_assing_table(lua_State *L, int part, int t){ if(lutil_isarray(L, t)){ int ret; lua_pushvalue(L, t); - ret = lcurl_mime_part_assig(L, part, "headers"); + ret = lcurl_mime_part_assign(L, part, "headers"); if(ret != 1) return ret; lua_pop(L, 1); @@ -131,7 +186,7 @@ static int lcurl_mime_part_assing_table(lua_State *L, int part, int t){ for(i=0;method = lcurl_mime_part_fields[i]; ++i){ lua_getfield(L, t, method); if(!lua_isnil(L, -1)){ - int ret = lcurl_mime_part_assig(L, part, method); + int ret = lcurl_mime_part_assign(L, part, method); if(ret != 1) return ret; } lua_pop(L, 1); @@ -141,8 +196,8 @@ static int lcurl_mime_part_assing_table(lua_State *L, int part, int t){ lua_getfield(L, t, "subparts"); if(!lua_isnil(L, -1)){ - if(IS_FALSE(L, -1) || lcurl_getmimepart_at(L, -1)){ - int ret = lcurl_mime_part_assig(L, part, "subparts"); + if(IS_FALSE(L, -1) || lcurl_getmime_at(L, -1)){ + int ret = lcurl_mime_part_assign(L, part, "subparts"); if(ret != 1) return ret; } } @@ -190,6 +245,10 @@ int lcurl_mime_create(lua_State *L, int error_mode){ p->err_mode = error_mode; p->parts = p->parent = NULL; + /* weak reference from mime to easy handle */ + lua_pushvalue(L, 1); + lua_rawsetp(L, LCURL_MIME_EASY, (void*)p); + return 1; } @@ -242,6 +301,12 @@ static int lcurl_mime_addpart(lua_State *L){ return 1; } +static int lcurl_mime_easy(lua_State *L){ + lcurl_mime_t *p = lcurl_getmime(L); + lua_rawgetp(L, LCURL_MIME_EASY, p); + return 1; +} + //} //{ MIME Part @@ -289,40 +354,6 @@ static int lcurl_mime_part_free(lua_State *L){ return 0; } -static void lcurl_mime_part_remove_subparts(lua_State *L, lcurl_mime_part_t *p, int free_it){ - lcurl_mime_t *sub = lcurl_mime_part_get_subparts(L, p); - if(sub){ - assert(LUA_NOREF != p->subpart_ref); - /* detach `subpart` mime from current mime part */ - - /* if set `sub->parent = NULL` then gc for mime will try free curl_mime_free. */ - /* So do not set it unless `curl_mime_subparts(p->part, NULL)` does not free mime */ - - luaL_unref(L, LCURL_LUA_REGISTRY, p->subpart_ref); - p->subpart_ref = LUA_NOREF; - - if(p->part && free_it){ - curl_mime_subparts(p->part, NULL); - } - - /* issues #1961 */ - - /* seems curl_mime_subparts(h, NULL) free asubparts. - so we have to invalidate all reference to all nested objects (part/mime). - NOTE. All resources already feed. So just need set all pointers to NULL - and free all Lua resources (like references and storages) - */ - { - lcurl_mime_part_t *ptr; - /* reset all parts*/ - for(ptr = sub->parts; ptr; ptr=ptr->next){ - lcurl_mime_part_remove_subparts(L, p, 0); - } - lcurl_mime_reset(L, sub); - } - } -} - static int lcurl_mime_part_assing_ext(lua_State *L, int part, int i){ #define UNSET_VALUE (const char*)-1 @@ -589,6 +620,7 @@ static int lcurl_mime_part_encoder(lua_State *L){ static const struct luaL_Reg lcurl_mime_methods[] = { {"addpart", lcurl_mime_addpart }, + {"easy", lcurl_mime_easy }, {"free", lcurl_mime_free }, {"__gc", lcurl_mime_free }, @@ -646,6 +678,7 @@ void lcurl_mime_initlib(lua_State *L, int nup){ if(!lutil_createmetap(L, LCURL_MIME_PART, lcurl_mime_part_methods, nup)) lua_pop(L, nup); lua_pop(L, 1); + #else lua_pop(L, nup); #endif diff --git a/src/lcurl.c b/src/lcurl.c index c3d2c8f..1ebc544 100644 --- a/src/lcurl.c +++ b/src/lcurl.c @@ -189,6 +189,21 @@ static volatile int LCURL_INIT = 0; static const char* LCURL_REGISTRY = "LCURL Registry"; static const char* LCURL_USERVAL = "LCURL Uservalues"; +#if LCURL_CURL_VER_GE(7,56,0) +static const char* LCURL_MIME_EASY_MAP = "LCURL Mime easy"; +#endif + +#if LCURL_CURL_VER_GE(7,56,0) +#define NUP 3 +#else +#define NUP 2 +#endif + +#if LCURL_CURL_VER_GE(7,56,0) +#define LCURL_PUSH_NUP(L) lua_pushvalue(L, -NUP-1);lua_pushvalue(L, -NUP-1);lua_pushvalue(L, -NUP-1); +#else +#define LCURL_PUSH_NUP(L) lua_pushvalue(L, -NUP-1);lua_pushvalue(L, -NUP-1); +#endif static int luaopen_lcurl_(lua_State *L, const struct luaL_Reg *func){ if(!LCURL_INIT){ @@ -214,20 +229,32 @@ static int luaopen_lcurl_(lua_State *L, const struct luaL_Reg *func){ lcurl_util_new_weak_table(L, "k"); } +#if LCURL_CURL_VER_GE(7,56,0) + lua_rawgetp(L, LUA_REGISTRYINDEX, LCURL_MIME_EASY_MAP); + if(!lua_istable(L, -1)){ /* Mime->Easy */ + lua_pop(L, 1); + lcurl_util_new_weak_table(L, "v"); + } +#endif + lua_newtable(L); /* library */ - lua_pushvalue(L, -3); lua_pushvalue(L, -3); luaL_setfuncs(L, func, 2); - lua_pushvalue(L, -3); lua_pushvalue(L, -3); lcurl_error_initlib(L, 2); - lua_pushvalue(L, -3); lua_pushvalue(L, -3); lcurl_hpost_initlib(L, 2); - lua_pushvalue(L, -3); lua_pushvalue(L, -3); lcurl_easy_initlib (L, 2); - lua_pushvalue(L, -3); lua_pushvalue(L, -3); lcurl_mime_initlib (L, 2); - lua_pushvalue(L, -3); lua_pushvalue(L, -3); lcurl_multi_initlib(L, 2); - lua_pushvalue(L, -3); lua_pushvalue(L, -3); lcurl_share_initlib(L, 2); + LCURL_PUSH_NUP(L); luaL_setfuncs(L, func, NUP); + LCURL_PUSH_NUP(L); lcurl_error_initlib(L, NUP); + LCURL_PUSH_NUP(L); lcurl_hpost_initlib(L, NUP); + LCURL_PUSH_NUP(L); lcurl_easy_initlib (L, NUP); + LCURL_PUSH_NUP(L); lcurl_mime_initlib (L, NUP); + LCURL_PUSH_NUP(L); lcurl_multi_initlib(L, NUP); + LCURL_PUSH_NUP(L); lcurl_share_initlib(L, NUP); - lua_pushvalue(L, -3); lua_rawsetp(L, LUA_REGISTRYINDEX, LCURL_REGISTRY); - lua_pushvalue(L, -2); lua_rawsetp(L, LUA_REGISTRYINDEX, LCURL_USERVAL); + LCURL_PUSH_NUP(L); - lua_remove(L, -2); /* registry */ +#if LCURL_CURL_VER_GE(7,56,0) + lua_rawsetp(L, LUA_REGISTRYINDEX, LCURL_MIME_EASY_MAP); +#endif + + lua_rawsetp(L, LUA_REGISTRYINDEX, LCURL_USERVAL); + lua_rawsetp(L, LUA_REGISTRYINDEX, LCURL_REGISTRY); lcurl_util_set_const(L, lcurl_flags); diff --git a/src/lcurl.h b/src/lcurl.h index b9bd48c..d7a4570 100644 --- a/src/lcurl.h +++ b/src/lcurl.h @@ -1,7 +1,7 @@ /****************************************************************************** * Author: Alexey Melnichuk * -* Copyright (C) 2014 Alexey Melnichuk +* Copyright (C) 2014-2017 Alexey Melnichuk * * Licensed according to the included 'LICENSE' document * @@ -25,4 +25,7 @@ #define LCURL_USERVALUES lua_upvalueindex(2) +/* only for `mime` API */ +#define LCURL_MIME_EASY lua_upvalueindex(3) + #endif diff --git a/test/test_mime.lua b/test/test_mime.lua index 0517f97..e7239f4 100644 --- a/test/test_mime.lua +++ b/test/test_mime.lua @@ -235,17 +235,17 @@ function test_data() end function test_data_type() - mime:addpart():data('hello', 'text/html') + mime:addpart():data('hello', 'test/html') local info = assert_string(dump_mime(mime)) assert_match('\r\n\r\nhello', info) - assert_match('Content%-Type:%s+text/html', info) + assert_match('Content%-Type:%s+test/html', info) end function test_data_type_name() - mime:addpart():data('hello', 'text/html', 'test') + mime:addpart():data('hello', 'test/html', 'test') local info = assert_string(dump_mime(mime)) assert_match('\r\n\r\nhello', info) - assert_match('Content%-Type:%s+text/html', info) + assert_match('Content%-Type:%s+test/html', info) assert_match('Content%-Disposition:.-%sname="test"', info) end @@ -264,19 +264,19 @@ function test_data_filename() end function test_data_should_not_unset_on_nil() - local part = mime:addpart():data('hello', 'text/html', 'test', { + local part = mime:addpart():data('hello', 'test/html', 'test', { 'X-Custom-Header: hello' }) local info = assert_string(dump_mime(mime)) assert_match('\r\n\r\nhello', info) - assert_match('Content%-Type:%s+text/html', info) + assert_match('Content%-Type:%s+test/html', info) assert_match('Content%-Disposition:.-name="test"', info) assert_match('X%-Custom%-Header:%s*hello', info) part:data('world', nil, 'test2') info = assert_string(dump_mime(mime)) assert_match('\r\n\r\nworld', info) - assert_match('Content%-Type:%s+text/html', info) + assert_match('Content%-Type:%s+test/html', info) assert_match('Content%-Disposition:.-name="test2"', info) assert_match('X%-Custom%-Header:%s*hello', info) @@ -305,24 +305,24 @@ function test_data_headers() end function test_unset_name() - mime:addpart():data('hello', 'text/html', 'test'):name(false) + mime:addpart():data('hello', 'test/html', 'test'):name(false) local info = assert_string(dump_mime(mime)) assert_match('\r\n\r\nhello', info) - assert_match('Content%-Type:%s+text/html', info) + assert_match('Content%-Type:%s+test/html', info) assert_not_match('Content%-Disposition:.-name="test"', info) end function test_unset_type() - mime:addpart():data('hello', 'text/html'):type(false) + mime:addpart():data('hello', 'test/html'):type(false) local info = assert_string(dump_mime(mime)) assert_match('\r\n\r\nhello', info) - assert_not_match('Content%-Type:%s+text/html', info) + assert_not_match('Content%-Type:%s+test/html', info) end function test_unset_headers() - mime:addpart():data('hello', 'text/html',{ + mime:addpart():data('hello', 'test/html',{ 'X-Custom-Header: hello' }):headers(false) @@ -332,82 +332,82 @@ function test_unset_headers() end function test_unset_data() - mime:addpart():data('hello', 'text/html', 'test'):data(false) + mime:addpart():data('hello', 'test/html', 'test'):data(false) local info = assert_string(dump_mime(mime)) assert_not_match('\r\n\r\nhello', info) - assert_match('Content%-Type:%s+text/html', info) + assert_match('Content%-Type:%s+test/html', info) assert_match('Content%-Disposition:.-name="test"', info) end function test_unset_data_type_1() - local part = mime:addpart():data('hello', 'text/html', 'test', { + local part = mime:addpart():data('hello', 'test/html', 'test', { 'X-Custom-Header: hello' }):data('hello', false) local info = assert_string(dump_mime(mime)) assert_match('\r\n\r\nhello', info) - assert_not_match('Content%-Type:%s+text/html', info) + assert_not_match('Content%-Type:%s+test/html', info) assert_match('Content%-Disposition:.-name="test"', info) assert_match('X%-Custom%-Header:%s*hello', info) end function test_unset_data_type_2() - local part = mime:addpart():data('hello', 'text/html', 'test', { + local part = mime:addpart():data('hello', 'test/html', 'test', { 'X-Custom-Header: hello' }):data('hello', false, nil, nil) local info = assert_string(dump_mime(mime)) assert_match('\r\n\r\nhello', info) - assert_not_match('Content%-Type:%s+text/html', info) + assert_not_match('Content%-Type:%s+test/html', info) assert_match('Content%-Disposition:.-name="test"', info) assert_match('X%-Custom%-Header:%s*hello', info) end function test_unset_data_name_1() - local part = mime:addpart():data('hello', 'text/html', 'test', { + local part = mime:addpart():data('hello', 'test/html', 'test', { 'X-Custom-Header: hello' }):data('hello', nil, false) local info = assert_string(dump_mime(mime)) assert_match('\r\n\r\nhello', info) - assert_match('Content%-Type:%s+text/html', info) + assert_match('Content%-Type:%s+test/html', info) assert_not_match('Content%-Disposition:.-name="test"', info) assert_match('X%-Custom%-Header:%s*hello', info) end function test_unset_data_name_2() - local part = mime:addpart():data('hello', 'text/html', 'test', { + local part = mime:addpart():data('hello', 'test/html', 'test', { 'X-Custom-Header: hello' }):data('hello', nil, false, nil) local info = assert_string(dump_mime(mime)) assert_match('\r\n\r\nhello', info) - assert_match('Content%-Type:%s+text/html', info) + assert_match('Content%-Type:%s+test/html', info) assert_not_match('Content%-Disposition:.-name="test"', info) assert_match('X%-Custom%-Header:%s*hello', info) end function test_unset_data_header() - local part = mime:addpart():data('hello', 'text/html', 'test', { + local part = mime:addpart():data('hello', 'test/html', 'test', { 'X-Custom-Header: hello' }):data('hello', nil, nil, nil, false) local info = assert_string(dump_mime(mime)) assert_match('\r\n\r\nhello', info) - assert_match('Content%-Type:%s+text/html', info) + assert_match('Content%-Type:%s+test/html', info) assert_match('Content%-Disposition:.-name="test"', info) assert_not_match('X%-Custom%-Header:%s*hello', info) end function test_unset_data_filename_1() - local part = mime:addpart():data('hello', 'text/html', 'test', 'test.html', { + local part = mime:addpart():data('hello', 'test/html', 'test', 'test.html', { 'X-Custom-Header: hello' }):data('hello', nil, nil, false) local info = assert_string(dump_mime(mime)) assert_match('\r\n\r\nhello', info) - assert_match('Content%-Type:%s+text/html', info) + assert_match('Content%-Type:%s+test/html', info) assert_match('Content%-Disposition:.-%sname="test"', info) assert_not_match('Content%-Disposition:.-%sname="test%.html"', info) assert_match('X%-Custom%-Header:%s*hello', info) @@ -461,7 +461,7 @@ function test_pass_args() encoder = 'base64'; name = 'test'; filename = 'test.html'; - type = 'text/html'; + type = 'test/html'; headers = { 'X-Custom-Header: hello'; } @@ -469,7 +469,7 @@ function test_pass_args() local info = assert_string(dump_mime(mime)) assert_match('\r\n\r\naGVsbG8=', info) - assert_match('Content%-Type:%s+text/html', info) + assert_match('Content%-Type:%s+test/html', info) assert_match('Content%-Disposition:.-%sname="test"', info) assert_not_match('Content%-Disposition:.-%sname="test%.html"', info) assert_match('X%-Custom%-Header:%s*hello', info) From 52963a1e08887a17fad21692ce3dd06a32385d19 Mon Sep 17 00:00:00 2001 From: Alexey Melnichuk Date: Mon, 30 Apr 2018 15:31:06 +0300 Subject: [PATCH 14/16] Use libuv 1.19.0 --- .travis/setup_uv.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis/setup_uv.sh b/.travis/setup_uv.sh index 69aa72e..34eb88e 100644 --- a/.travis/setup_uv.sh +++ b/.travis/setup_uv.sh @@ -8,6 +8,8 @@ git clone https://github.com/libuv/libuv.git -b v1.x cd libuv +git checkout v1.19.0 + mkdir -p lib mkdir -p build git clone https://chromium.googlesource.com/external/gyp build/gyp From a8288a6217f17207bb101e1d71ec68ec3ffc356b Mon Sep 17 00:00:00 2001 From: Alexey Melnichuk Date: Mon, 30 Apr 2018 17:03:09 +0300 Subject: [PATCH 15/16] Update Travis file --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 01c85f6..53acbae 100644 --- a/.travis.yml +++ b/.travis.yml @@ -35,8 +35,8 @@ branches: before_install: - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then export PATH=$PATH:~/Library/Python/2.7/bin/; fi - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then export LCURL_LD_FLAGS="-bundle -undefined dynamic_lookup -all_load --coverage"; fi - - pip install --user cpp-coveralls - - pip install --user hererocks + - pip2 install --user cpp-coveralls + - pip2 install --user hererocks - hererocks here -r^ --$LUA - source here/bin/activate - luarocks show lluv > /dev/null 2>&1 || bash .travis/setup_uv.sh From 814c2e6a0ba0b3c0aa76ed7f17ea04a385885741 Mon Sep 17 00:00:00 2001 From: Alexey Melnichuk Date: Tue, 1 May 2018 12:27:54 +0300 Subject: [PATCH 16/16] Update Travis file --- .travis.yml | 13 ++----------- src/lceasy.c | 2 +- src/lcmime.c | 2 +- 3 files changed, 4 insertions(+), 13 deletions(-) diff --git a/.travis.yml b/.travis.yml index 53acbae..878383e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -67,21 +67,12 @@ script: - lua test_pause02.c.lua - lua test_multi_callback.lua - lua test_multi_nested_callback.lua - # - lunit.sh test_easy.lua - # - lunit.sh test_safe.lua - # - lunit.sh test_form.lua - # - lunit.sh test_curl.lua before_cache: - # - cd $TRAVIS_BUILD_DIR/test - # - coveralls -b .. -r .. --dump c.report.json - # - luacov-coveralls -j c.report.json -v - # - luarocks remove lluv-gsmmodem - # - rm -f /home/travis/.cache/pip/log/debug.log - -after_success: - coveralls -b .. -r .. --dump c.report.json - luacov-coveralls -j c.report.json -v + - luarocks remove lua-curl + - rm -f /home/travis/.cache/pip/log/debug.log notifications: email: diff --git a/src/lceasy.c b/src/lceasy.c index 936a286..683c041 100644 --- a/src/lceasy.c +++ b/src/lceasy.c @@ -1,7 +1,7 @@ /****************************************************************************** * Author: Alexey Melnichuk * -* Copyright (C) 2014-2017 Alexey Melnichuk +* Copyright (C) 2014-2018 Alexey Melnichuk * * Licensed according to the included 'LICENSE' document * diff --git a/src/lcmime.c b/src/lcmime.c index 457d37b..06c3550 100644 --- a/src/lcmime.c +++ b/src/lcmime.c @@ -1,7 +1,7 @@ /****************************************************************************** * Author: Alexey Melnichuk * -* Copyright (C) 2017 Alexey Melnichuk +* Copyright (C) 2017-2018 Alexey Melnichuk * * Licensed according to the included 'LICENSE' document *