/******************************************************************************
* Author: Alexey Melnichuk <mimir@newmail.ru>
*
* Copyright (C) 2014 Alexey Melnichuk <mimir@newmail.ru>
*
* Licensed according to the included 'LICENSE' document
*
* This file is part of lua-lcurl library.
******************************************************************************/
#include "lcurl.h"
#include "lchttppost.h"
#include "lcerror.h"
#include "lcutils.h"
#define LCURL_HTTPPOST_NAME LCURL_PREFIX" HTTPPost"
static const char *LCURL_HTTPPOST = LCURL_HTTPPOST_NAME;
#if LUA_VERSION_NUM >= 503 /* Lua 5.3 */
/*! @fixme detect real types (e.g. float/int32_t) */
# define LCURL_USE_INTEGER
#endif
#ifdef LCURL_USE_INTEGER
# ifdef LUA_32BITS
# define LCURL_INT_SIZE_16
# define LCURL_INT_SIZE_32
# else
# define LCURL_INT_SIZE_16
# define LCURL_INT_SIZE_32
# define LCURL_INT_SIZE_64
# endif
#endif
#if LCURL_CURL_VER_GE(7,46,0)
# define LCURL_FORM_CONTENTLEN CURLFORM_CONTENTLEN
# define LCURL_LEN_TYPE curl_off_t
#else
# define LCURL_FORM_CONTENTLEN CURLFORM_CONTENTSLENGTH
# define LCURL_LEN_TYPE long
#endif
/* 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)
# 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){
lcurl_hpost_stream_t *ptr = p->stream;
lcurl_hpost_stream_t *stream = (lcurl_hpost_stream_t*)malloc(sizeof(lcurl_hpost_stream_t));
if(!stream) return NULL;
stream->magic = LCURL_HPOST_STREAM_MAGIC;
stream->L = &p->L;
stream->rbuffer.ref = LUA_NOREF;
stream->rd.cb_ref = stream->rd.ud_ref = LUA_NOREF;
stream->next = NULL;
if(!p->stream) p->stream = stream;
else{
while(ptr->next) ptr = ptr->next;
ptr->next = stream;
}
return stream;
}
static void lcurl_hpost_stream_free(lua_State *L, lcurl_hpost_stream_t *ptr){
if(ptr){
luaL_unref(L, LCURL_LUA_REGISTRY, ptr->rbuffer.ref);
luaL_unref(L, LCURL_LUA_REGISTRY, ptr->rd.cb_ref);
luaL_unref(L, LCURL_LUA_REGISTRY, ptr->rd.ud_ref);
free(ptr);
}
}
static void lcurl_hpost_stream_free_last(lua_State *L, lcurl_hpost_t *p){
lcurl_hpost_stream_t *ptr = p->stream;
if(!ptr) return;
if(!ptr->next){
lcurl_hpost_stream_free(L, ptr);
p->stream = 0;
}
while(ptr->next->next) ptr = ptr->next;
lcurl_hpost_stream_free(L, ptr->next);
ptr->next = NULL;
}
static void lcurl_hpost_stream_free_all(lua_State *L, lcurl_hpost_t *p){
lcurl_hpost_stream_t *ptr = p->stream;
while(ptr){
lcurl_hpost_stream_t *next = ptr->next;
lcurl_hpost_stream_free(L, ptr);
ptr = next;
}
p->stream = 0;
}
//}
//{ HTTPPost
int lcurl_hpost_create(lua_State *L, int error_mode){
lcurl_hpost_t *p = lutil_newudatap(L, lcurl_hpost_t, LCURL_HTTPPOST);
p->post = p->last = 0;
p->storage = lcurl_storage_init(L);
p->err_mode = error_mode;
p->stream = 0;
return 1;
}
lcurl_hpost_t *lcurl_gethpost_at(lua_State *L, int i){
lcurl_hpost_t *p = (lcurl_hpost_t *)lutil_checkudatap (L, i, LCURL_HTTPPOST);
luaL_argcheck (L, p != NULL, 1, LCURL_HTTPPOST_NAME" object expected");
return p;
}
static int lcurl_hpost_to_s(lua_State *L){
lcurl_hpost_t *p = (lcurl_hpost_t *)lutil_checkudatap (L, 1, LCURL_HTTPPOST);
lua_pushfstring(L, LCURL_HTTPPOST_NAME" (%p)", (void*)p);
return 1;
}
static int lcurl_hpost_add_content(lua_State *L){
// add_buffer(name, data, [type,] [headers])
lcurl_hpost_t *p = lcurl_gethpost(L);
size_t name_len; const char *name = luaL_checklstring(L, 2, &name_len);
size_t cont_len; const char *cont = luaL_checklstring(L, 3, &cont_len);
const char *type = lua_tostring(L, 4);
struct curl_slist *list = lcurl_util_to_slist(L, type?5:4);
struct curl_forms forms[3];
CURLFORMcode code;
int i = 0;
if(type){ forms[i].option = CURLFORM_CONTENTTYPE; forms[i++].value = type; }
if(list){ forms[i].option = CURLFORM_CONTENTHEADER; forms[i++].value = (char*)list; }
forms[i].option = CURLFORM_END;
code = curl_formadd(&p->post, &p->last,
CURLFORM_PTRNAME, name, CURLFORM_NAMELENGTH, (long)name_len,
CURLFORM_PTRCONTENTS, cont, LCURL_FORM_CONTENTLEN, (LCURL_LEN_TYPE)cont_len,
CURLFORM_ARRAY, forms,
CURLFORM_END);
if(code != CURL_FORMADD_OK){
if(list) curl_slist_free_all(list);
return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_FORM, code);
}
lcurl_storage_preserve_value(L, p->storage, 2);
lcurl_storage_preserve_value(L, p->storage, 3);
if(list) lcurl_storage_preserve_slist (L, p->storage, list);
lua_settop(L, 1);
return 1;
}
static int lcurl_hpost_add_buffer(lua_State *L){
// add_buffer(name, filename, data, [type,] [headers])
lcurl_hpost_t *p = lcurl_gethpost(L);
size_t name_len; const char *name = luaL_checklstring(L, 2, &name_len);
const char *buff = luaL_checkstring(L, 3);
size_t cont_len; const char *cont = luaL_checklstring(L, 4, &cont_len);
const char *type = lua_tostring(L, 5);
struct curl_slist *list = lcurl_util_to_slist(L, ((!type)&&(lua_isnone(L,6)))?5:6);
struct curl_forms forms[3];
CURLFORMcode code;
int i = 0;
if(type){ forms[i].option = CURLFORM_CONTENTTYPE; forms[i++].value = type; }
if(list){ forms[i].option = CURLFORM_CONTENTHEADER; forms[i++].value = (char*)list; }
forms[i].option = CURLFORM_END;
code = curl_formadd(&p->post, &p->last,
CURLFORM_PTRNAME, name, CURLFORM_NAMELENGTH, (long)name_len,
CURLFORM_BUFFER, buff,
CURLFORM_BUFFERPTR, cont, CURLFORM_BUFFERLENGTH, cont_len,
CURLFORM_ARRAY, forms,
CURLFORM_END);
if(code != CURL_FORMADD_OK){
if(list) curl_slist_free_all(list);
return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_FORM, code);
}
lcurl_storage_preserve_value(L, p->storage, 2);
lcurl_storage_preserve_value(L, p->storage, 4);
if(list) lcurl_storage_preserve_slist (L, p->storage, list);
lua_settop(L, 1);
return 1;
}
static int lcurl_hpost_add_file(lua_State *L){
// add_file(name, path, [type, [fname,]] [headers])
// add_file("Picture", "c:\\image.jpg")
// add_file("Picture", "c:\\image.jpg", "image/jpeg")
// add_file("Picture", "c:\\image.jpg", "image/jpeg", {"XDescript: my image"})
// add_file("Picture", "c:\\image.jpg", "image/jpeg", "avatar.jpeg", {"XDescript: my image"})
// add_file("Picture", "c:\\image.jpg", nil, "avatar.jpeg", {"XDescript: my image"})
int top = lua_gettop(L);
lcurl_hpost_t *p = lcurl_gethpost(L);
size_t name_len; const char *name = luaL_checklstring(L, 2, &name_len);
const char *path = luaL_checkstring(L, 3);
const char *type = 0, *fname = 0;
struct curl_slist *list = NULL;
struct curl_forms forms[4];
CURLFORMcode code;
int i = 0;
if(top == 4){ /* name, path, type | headers */
if(lua_istable(L, 4))
list = lcurl_util_to_slist(L, 4);
else
type = lua_tostring(L, 4);
}
else if(top > 4){ /* name, path, type, fname | [fname, headers] */
type = lua_tostring(L, 4);
if(top == 5){ /* name, path, type, fname | headers */
if(lua_istable(L, 5))
list = lcurl_util_to_slist(L, 5);
else
fname = lua_tostring(L, 5);
}
else{ /* name, path, type, fname, headers */
fname = lua_tostring(L, 5);
list = lcurl_util_to_slist(L, 6);
}
}
if(fname){ forms[i].option = CURLFORM_FILENAME; forms[i++].value = fname; }
if(type) { forms[i].option = CURLFORM_CONTENTTYPE; forms[i++].value = type; }
if(list) { forms[i].option = CURLFORM_CONTENTHEADER; forms[i++].value = (char*)list; }
forms[i].option = CURLFORM_END;
code = curl_formadd(&p->post, &p->last,
CURLFORM_PTRNAME, name, CURLFORM_NAMELENGTH, (long)name_len,
CURLFORM_FILE, path,
CURLFORM_ARRAY, forms,
CURLFORM_END);
if(code != CURL_FORMADD_OK){
if(list) curl_slist_free_all(list);
return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_FORM, code);
}
lcurl_storage_preserve_value(L, p->storage, 2);
if(list) lcurl_storage_preserve_slist (L, p->storage, list);
lua_settop(L, 1);
return 1;
}
static int lcurl_hpost_add_stream(lua_State *L){
static const char *EMPTY = "";
// add_stream(name, [filename, [type,]] [headers,] size, reader [,context])
lcurl_hpost_t *p = lcurl_gethpost(L);
size_t name_len; const char *name = luaL_checklstring(L, 2, &name_len);
struct curl_slist *list = NULL; int ilist = 0;
const char *type = 0, *fname = 0;
size_t len;
CURLFORMcode code;
lcurl_callback_t rd = {LUA_NOREF, LUA_NOREF};
lcurl_hpost_stream_t *stream;
int n = 0, i = 3;
struct curl_forms forms[4];
while(1){ // [filename, [type,]] [headers,]
if(lua_isnone(L, i)){
lua_pushliteral(L, "stream size required");
lua_error(L);
}
if(lua_type(L, i) == LUA_TNUMBER){
break;
}
if(lua_type(L, i) == LUA_TTABLE){
ilist = i++;
break;
}
else if(!fname){
if(lua_isnil(L, i)) fname = EMPTY;
else fname = luaL_checkstring(L, i);
}
else if(!type){
if(lua_isnil(L, i)) type = EMPTY;
else type = luaL_checkstring(L, i);
}
else{
if(lua_isnil(L, i) && (!ilist)){
++i; // empty headers
break;
}
lua_pushliteral(L, "stream size required");
lua_error(L);
}
++i;
}
#if defined(LCURL_INT_SIZE_64) && LCURL_CURL_VER_GE(7,46,0)
len = luaL_checkinteger(L, i);
#else
len = luaL_checklong(L, i);
#endif
lcurl_set_callback(L, &rd, i + 1, "read");
luaL_argcheck(L, rd.cb_ref != LUA_NOREF, i + 1, "function expected");
if(ilist) list = lcurl_util_to_slist(L, ilist);
if(fname == EMPTY) fname = NULL;
if(type == EMPTY) type = NULL;
n = 0;
if(fname){ forms[n].option = CURLFORM_FILENAME; forms[n++].value = fname; }
if(type) { forms[n].option = CURLFORM_CONTENTTYPE; forms[n++].value = type; }
if(list) { forms[n].option = CURLFORM_CONTENTHEADER; forms[n++].value = (char*)list; }
forms[n].option = CURLFORM_END;
stream = lcurl_hpost_stream_add(L, p);
if(!stream){
if(list) curl_slist_free_all(list);
return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_FORM, CURL_FORMADD_MEMORY);
}
stream->rd = rd;
code = curl_formadd(&p->post, &p->last,
CURLFORM_PTRNAME, name, CURLFORM_NAMELENGTH, (long)name_len,
CURLFORM_STREAM, stream, LCURL_FORM_CONTENTLEN, (LCURL_LEN_TYPE)len,
CURLFORM_ARRAY, forms,
CURLFORM_END
);
if(code != CURL_FORMADD_OK){
lcurl_hpost_stream_free_last(L, p);
return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_FORM, code);
}
lcurl_storage_preserve_value(L, p->storage, 2);
if(list) lcurl_storage_preserve_slist (L, p->storage, list);
lua_settop(L, 1);
return 1;
}
static int lcurl_hpost_add_files(lua_State *L){
lcurl_hpost_t *p = lcurl_gethpost(L);
size_t name_len; const char *name = luaL_checklstring(L, 2, &name_len);
int i; int opt_count = 0;
int arr_count = lua_rawlen(L, 3);
struct curl_forms *forms;
CURLFORMcode code;
lua_settop(L, 3);
if(lua_type(L, -1) != LUA_TTABLE){
//! @fixme use library specific error codes
return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_FORM, CURL_FORMADD_ILLEGAL_ARRAY);
}
for(i = 1; i <= arr_count; ++i){
int n;
lua_rawgeti(L, 3, i);
if((lua_type(L, -1) != LUA_TTABLE) && (lua_type(L, -1) != LUA_TSTRING)){
//! @fixme use library specific error codes
return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_FORM, CURL_FORMADD_ILLEGAL_ARRAY);
}
n = (lua_type(L, -1) == LUA_TSTRING) ? 1: lua_rawlen(L, -1);
if(n == 1) opt_count += 1; // name
else if(n == 2) opt_count += 2; // name and type
else if(n == 3) opt_count += 3; // name, type and filename
else{
//! @fixme use library specific error codes
return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_FORM, CURL_FORMADD_ILLEGAL_ARRAY);
}
lua_pop(L, 1);
}
if(opt_count == 0){
lua_settop(L, 1);
return 1;
}
forms = (curl_forms*)calloc(opt_count + 1, sizeof(struct curl_forms));
if(!forms){
return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_FORM, CURL_FORMADD_MEMORY);
}
forms[opt_count].option = CURLFORM_END;
opt_count = 0;
for(i = 1; i <= arr_count; ++i){
int n;
lua_rawgeti(L, 3, i);
if (lua_type(L, -1) == LUA_TSTRING){
forms[opt_count].option = CURLFORM_FILE; forms[opt_count++].value = luaL_checkstring(L, -1);
}
else{
n = lua_rawlen(L, -1);
lua_rawgeti(L, -1, 1);
forms[opt_count].option = CURLFORM_FILE; forms[opt_count++].value = luaL_checkstring(L, -1);
lua_pop(L, 1);
if(n > 1){
lua_rawgeti(L, -1, 2);
forms[opt_count].option = CURLFORM_CONTENTTYPE; forms[opt_count++].value = luaL_checkstring(L, -1);
lua_pop(L, 1);
}
if(n > 2){
lua_rawgeti(L, -1, 3);
forms[opt_count].option = CURLFORM_FILENAME; forms[opt_count++].value = luaL_checkstring(L, -1);
lua_pop(L, 1);
}
}
lua_pop(L, 1);
}
code = curl_formadd(&p->post, &p->last,
CURLFORM_PTRNAME, name, CURLFORM_NAMELENGTH, (long)name_len,
CURLFORM_ARRAY, forms,
CURLFORM_END);
free(forms);
if(code != CURL_FORMADD_OK){
return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_FORM, code);
}
lua_settop(L, 1);
return 1;
}
static size_t lcurl_hpost_getter_by_buffer(void *arg, const char *buf, size_t len){
luaL_Buffer *b = (luaL_Buffer*)arg;
luaL_addlstring(b, buf, len);
return len;
}
static size_t call_writer(lua_State *L, int fn, int ctx, const char *buf, size_t len){
int top = lua_gettop(L);
int n = 1; // number of args
lua_Number ret = (lua_Number)len;
lua_pushvalue(L, fn);
if(ctx){
lua_pushvalue(L, ctx);
n += 1;
}
lua_pushlstring(L, buf, len);
if(lua_pcall(L, n, LUA_MULTRET, 0)) return 0;
if(lua_gettop(L) > top){
if(lua_isnil(L, top + 1)) return 0;
if(lua_isboolean(L, top + 1)){
if(!lua_toboolean(L, top + 1)) ret = 0;
}
else ret = lua_tonumber(L, top + 1);
}
lua_settop(L, top);
return (size_t)ret;
}
static size_t lcurl_hpost_getter_by_callback1(void *arg, const char *buf, size_t len){
lua_State *L = (lua_State*)arg;
assert(2 == lua_gettop(L));
return call_writer(L, 2, 0, buf, len);
}
static size_t lcurl_hpost_getter_by_callback2(void *arg, const char *buf, size_t len){
lua_State *L = (lua_State*)arg;
assert(3 == lua_gettop(L));
return call_writer(L, 2, 3, buf, len);
}
static int lcurl_hpost_get(lua_State *L){
// get()
// get(fn [, ctx])
// get(object)
lcurl_hpost_t *p = lcurl_gethpost(L);
CURLcode code;
int top;
if(lua_isnoneornil(L, 2)){
luaL_Buffer b;
luaL_buffinit(L, &b);
code = (CURLcode)curl_formget(p->post, &b, lcurl_hpost_getter_by_buffer);
if(code != CURLE_OK){
return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_CURL, code);
}
luaL_pushresult(&b);
return 1;
}
if(lua_isfunction(L, 2)){
if(lua_gettop(L) == 2){
top = 2;
code = (CURLcode)curl_formget(p->post, L, lcurl_hpost_getter_by_callback1);
}
else{
top = 3;
lua_settop(L, 3);
code = (CURLcode)curl_formget(p->post, L, lcurl_hpost_getter_by_callback2);
}
}
else if(lua_isuserdata(L, 2) || lua_istable(L, 2)){
lua_settop(L, 2);
lua_getfield(L, 2, "write");
luaL_argcheck(L, lua_isfunction(L, -1), 2, "write method not found in object");
assert(3 == lua_gettop(L));
lua_insert(L, -2);
top = 3;
code = (CURLcode)curl_formget(p->post, L, lcurl_hpost_getter_by_callback2);
}
else{
lua_pushliteral(L, "invalid writer type");
return lua_error(L);
}
if(LCURL_GET_CB_ERROR == code){
if(((lua_gettop(L) == top+1))&&(lua_isstring(L, -1))){
return lua_error(L);
}
return lua_gettop(L) - top;
}
if(code != CURLE_OK){
return lcurl_fail_ex(L, p->err_mode, LCURL_ERROR_CURL, code);
}
lua_settop(L, 1);
return 1;
}
static int lcurl_hpost_free(lua_State *L){
lcurl_hpost_t *p = lcurl_gethpost(L);
if(p->post){
curl_formfree(p->post);
p->post = p->last = 0;
}
if(p->storage != LUA_NOREF){
p->storage = lcurl_storage_free(L, p->storage);
}
lcurl_hpost_stream_free_all(L, p);
return 0;
}
//}
static const struct luaL_Reg lcurl_hpost_methods[] = {
{"add_content", lcurl_hpost_add_content },
{"add_buffer", lcurl_hpost_add_buffer },
{"add_file", lcurl_hpost_add_file },
{"add_stream", lcurl_hpost_add_stream },
{"add_files", lcurl_hpost_add_files },
{"get", lcurl_hpost_get },
{"free", lcurl_hpost_free },
{"__gc", lcurl_hpost_free },
{"__tostring", lcurl_hpost_to_s },
{NULL,NULL}
};
void lcurl_hpost_initlib(lua_State *L, int nup){
if(!lutil_createmetap(L, LCURL_HTTPPOST, lcurl_hpost_methods, nup))
lua_pop(L, nup);
lua_pop(L, 1);
}