/******************************************************************************
* Author: Alexey Melnichuk <mimir@newmail.ru>
*
* Copyright (C) 2017-2018 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"
/* 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"
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){
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;
/* 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){
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;
}
#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))) || lutil_is_null(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;
i = lua_absindex(L, i);
lua_pushnil(L);
if(lua_next(L, i)){
ret = lua_isnumber(L, -2);
lua_pop(L, 2);
}
return ret;
}
static int lcurl_mime_part_assign(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", "type", 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_assign(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_assign(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_getmime_at(L, -1)){
int ret = lcurl_mime_part_assign(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){
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;
/* weak reference from mime to easy handle */
lua_pushvalue(L, 1);
lua_rawsetp(L, LCURL_MIME_EASY, (void*)p);
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;
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;
}
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
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 int lcurl_mime_part_assing_ext(lua_State *L, int part, int i){
#define UNSET_VALUE (const char*)-1
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)) {
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)){
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_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 == 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 == 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){
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[, 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;
CURLcode ret;
if(IS_FALSE(L, 2)){
data = NULL;
len = 0;
}
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 */
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, 1, 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, 1, 3);
if (res) return res;
}
lua_settop(L, 1);
return 1;
}
// 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);
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, 1, 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;
CURLcode ret;
if(IS_FALSE(L, 2)){
list = NULL;
}
else{
list = lcurl_util_to_slist(L, 2);
luaL_argcheck(L, list || IS_TABLE(L, 2), 2, "array or null expected");
}
ret = curl_mime_headers(p->part, list, 1);
if(ret != CURLE_OK){
if(list) 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;
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){
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;
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){
return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_EASY, ret);
}
lua_settop(L, 1);
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;
}
//}
static const struct luaL_Reg lcurl_mime_methods[] = {
{"addpart", lcurl_mime_addpart },
{"easy", lcurl_mime_easy },
{"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 },
{"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 },
{"__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
}