Add. New cURL MIME API

master
Alexey Melnichuk 2017-10-12 14:22:24 +03:00
parent 14a9246abe
commit 0e1eaa525a
18 changed files with 1116 additions and 131 deletions

View File

@ -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

View File

@ -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"

View File

@ -0,0 +1,85 @@
local curl = require "lcurl"
local SMTP = {
url = "smtp://mail.example.com";
}
local FROM = "<sender@example.org>"
local TO = "<addressee@example.net>"
local CC = "<info@example.org>"
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: <dcd7cb36-11db-487a-9f3a-e652a9458efd@rfcpedant.example.org>",
"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 = ""
.. "<html><body>\r\n"
.. "<p>This is the inline <b>HTML</b> message of the e-mail.</p>"
.. "<br />\r\n"
.. "<p>It could be a lot of HTML data that would be displayed by "
.. "e-mail viewers able to handle HTML.</p>"
.. "</body></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()

View File

@ -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)" }

View File

@ -1,7 +1,7 @@
/******************************************************************************
* Author: Alexey Melnichuk <mimir@newmail.ru>
*
* Copyright (C) 2014 Alexey Melnichuk <mimir@newmail.ru>
* Copyright (C) 2014-2017 Alexey Melnichuk <mimir@newmail.ru>
*
* Licensed according to the included 'LICENSE' document
*
@ -15,6 +15,7 @@
#include "lchttppost.h"
#include "lcshare.h"
#include "lcmulti.h"
#include "lcmime.h"
#include <memory.h>
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

View File

@ -1,7 +1,7 @@
/******************************************************************************
* Author: Alexey Melnichuk <mimir@newmail.ru>
*
* Copyright (C) 2014 Alexey Melnichuk <mimir@newmail.ru>
* Copyright (C) 2014-2017 Alexey Melnichuk <mimir@newmail.ru>
*
* 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

479
src/lcmime.c Normal file
View File

@ -0,0 +1,479 @@
/******************************************************************************
* Author: Alexey Melnichuk <mimir@newmail.ru>
*
* Copyright (C) 2017 Alexey Melnichuk <mimir@newmail.ru>
*
* 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
}

66
src/lcmime.h Normal file
View File

@ -0,0 +1,66 @@
/******************************************************************************
* Author: Alexey Melnichuk <mimir@newmail.ru>
*
* Copyright (C) 2017 Alexey Melnichuk <mimir@newmail.ru>
*
* 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 <stdlib.h>
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

View File

@ -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

View File

@ -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);

View File

@ -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 */

View File

@ -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);

View File

@ -27,6 +27,7 @@ print("")
require "test_safe"
require "test_easy"
require "test_form"
require "test_mime"
require "test_curl"
RUN()

View File

@ -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

View File

@ -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

View File

@ -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)

188
test/test_mime.lua Normal file
View File

@ -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()

89
test/utils.lua Normal file
View File

@ -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