5849898283
Unit tests functional and coverage back to 100% Add more routes to dictionary, add more credentials, add default port 5554, rename cameradar logs ENV variable, improve unit test readability, remove tmp file
528 lines
15 KiB
Go
528 lines
15 KiB
Go
package curl
|
|
|
|
/*
|
|
#include <stdlib.h>
|
|
#include <curl/curl.h>
|
|
#include "callback.h"
|
|
#include "compat.h"
|
|
|
|
static CURLcode curl_easy_setopt_long(CURL *handle, CURLoption option, long parameter) {
|
|
return curl_easy_setopt(handle, option, parameter);
|
|
}
|
|
static CURLcode curl_easy_setopt_string(CURL *handle, CURLoption option, char *parameter) {
|
|
return curl_easy_setopt(handle, option, parameter);
|
|
}
|
|
static CURLcode curl_easy_setopt_slist(CURL *handle, CURLoption option, struct curl_slist *parameter) {
|
|
return curl_easy_setopt(handle, option, parameter);
|
|
}
|
|
static CURLcode curl_easy_setopt_pointer(CURL *handle, CURLoption option, void *parameter) {
|
|
return curl_easy_setopt(handle, option, parameter);
|
|
}
|
|
static CURLcode curl_easy_setopt_off_t(CURL *handle, CURLoption option, off_t parameter) {
|
|
return curl_easy_setopt(handle, option, parameter);
|
|
}
|
|
|
|
static CURLcode curl_easy_getinfo_string(CURL *curl, CURLINFO info, char **p) {
|
|
return curl_easy_getinfo(curl, info, p);
|
|
}
|
|
static CURLcode curl_easy_getinfo_long(CURL *curl, CURLINFO info, long *p) {
|
|
return curl_easy_getinfo(curl, info, p);
|
|
}
|
|
static CURLcode curl_easy_getinfo_double(CURL *curl, CURLINFO info, double *p) {
|
|
return curl_easy_getinfo(curl, info, p);
|
|
}
|
|
static CURLcode curl_easy_getinfo_slist(CURL *curl, CURLINFO info, struct curl_slist **p) {
|
|
return curl_easy_getinfo(curl, info, p);
|
|
}
|
|
|
|
static CURLFORMcode curl_formadd_name_content_length(
|
|
struct curl_httppost **httppost, struct curl_httppost **last_post, char *name, char *content, int length) {
|
|
return curl_formadd(httppost, last_post,
|
|
CURLFORM_COPYNAME, name,
|
|
CURLFORM_COPYCONTENTS, content,
|
|
CURLFORM_CONTENTSLENGTH, length, CURLFORM_END);
|
|
}
|
|
static CURLFORMcode curl_formadd_name_content_length_type(
|
|
struct curl_httppost **httppost, struct curl_httppost **last_post, char *name, char *content, int length, char *type) {
|
|
return curl_formadd(httppost, last_post,
|
|
CURLFORM_COPYNAME, name,
|
|
CURLFORM_COPYCONTENTS, content,
|
|
CURLFORM_CONTENTSLENGTH, length,
|
|
CURLFORM_CONTENTTYPE, type, CURLFORM_END);
|
|
}
|
|
static CURLFORMcode curl_formadd_name_file_type(
|
|
struct curl_httppost **httppost, struct curl_httppost **last_post, char *name, char *filename, char *type) {
|
|
return curl_formadd(httppost, last_post,
|
|
CURLFORM_COPYNAME, name,
|
|
CURLFORM_FILE, filename,
|
|
CURLFORM_CONTENTTYPE, type, CURLFORM_END);
|
|
}
|
|
// TODO: support multi file
|
|
|
|
*/
|
|
import "C"
|
|
|
|
import (
|
|
"fmt"
|
|
"mime"
|
|
"path"
|
|
"unsafe"
|
|
"sync"
|
|
)
|
|
|
|
type CurlInfo C.CURLINFO
|
|
type CurlError C.CURLcode
|
|
|
|
type CurlString *C.char
|
|
|
|
func NewCurlString(s string) CurlString {
|
|
return CurlString(unsafe.Pointer(C.CString(s)))
|
|
}
|
|
|
|
func FreeCurlString(s CurlString) {
|
|
C.free(unsafe.Pointer(s))
|
|
}
|
|
|
|
func (e CurlError) Error() string {
|
|
// ret is const char*, no need to free
|
|
ret := C.curl_easy_strerror(C.CURLcode(e))
|
|
return fmt.Sprintf("curl: %s", C.GoString(ret))
|
|
}
|
|
|
|
func newCurlError(errno C.CURLcode) error {
|
|
if errno == C.CURLE_OK { // if nothing wrong
|
|
return nil
|
|
}
|
|
return CurlError(errno)
|
|
}
|
|
|
|
// curl_easy interface
|
|
type CURL struct {
|
|
handle unsafe.Pointer
|
|
// callback functions, bool ret means ok or not
|
|
headerFunction, writeFunction *func([]byte, interface{}) bool
|
|
readFunction *func([]byte, interface{}) int // return num of bytes writed to buf
|
|
progressFunction *func(float64, float64, float64, float64, interface{}) bool
|
|
fnmatchFunction *func(string, string, interface{}) int
|
|
// callback datas
|
|
headerData, writeData, readData, progressData, fnmatchData interface{}
|
|
// list of C allocs
|
|
mallocAllocs []*C.char
|
|
}
|
|
|
|
// concurrent safe context map
|
|
type contextMap struct {
|
|
items map[uintptr]*CURL
|
|
sync.RWMutex
|
|
}
|
|
|
|
func (c *contextMap) Set(k uintptr, v *CURL) {
|
|
c.Lock()
|
|
defer c.Unlock()
|
|
|
|
c.items[k] = v
|
|
}
|
|
|
|
func (c *contextMap) Get(k uintptr) *CURL {
|
|
c.RLock()
|
|
defer c.RUnlock()
|
|
|
|
return c.items[k]
|
|
}
|
|
|
|
func (c *contextMap) Delete(k uintptr) {
|
|
c.Lock()
|
|
defer c.Unlock()
|
|
|
|
delete(c.items, k)
|
|
}
|
|
|
|
var context_map = &contextMap {
|
|
items: make(map[uintptr]*CURL),
|
|
}
|
|
|
|
// curl_easy_init - Start a libcurl easy session
|
|
func EasyInit() *CURL {
|
|
p := C.curl_easy_init()
|
|
c := &CURL{handle: p, mallocAllocs: make([]*C.char, 0)} // other field defaults to nil
|
|
context_map.Set(uintptr(p), c)
|
|
return c
|
|
}
|
|
|
|
// curl_easy_duphandle - Clone a libcurl session handle
|
|
func (curl *CURL) Duphandle() *CURL {
|
|
p := C.curl_easy_duphandle(curl.handle)
|
|
c := &CURL{handle: p}
|
|
context_map.Set(uintptr(p), c)
|
|
return c
|
|
}
|
|
|
|
// curl_easy_cleanup - End a libcurl easy session
|
|
func (curl *CURL) Cleanup() {
|
|
p := curl.handle
|
|
C.curl_easy_cleanup(p)
|
|
curl.MallocFreeAfter(0)
|
|
context_map.Delete(uintptr(p))
|
|
}
|
|
|
|
// curl_easy_setopt - set options for a curl easy handle
|
|
// WARNING: a function pointer is &fun, but function addr is reflect.ValueOf(fun).Pointer()
|
|
func (curl *CURL) Setopt(opt int, param interface{}) error {
|
|
p := curl.handle
|
|
if param == nil {
|
|
// NOTE: some option will crash program when got a nil param
|
|
return newCurlError(C.curl_easy_setopt_pointer(p, C.CURLoption(opt), nil))
|
|
}
|
|
switch {
|
|
// not really set
|
|
case opt == OPT_READDATA: // OPT_INFILE
|
|
curl.readData = param
|
|
return nil
|
|
case opt == OPT_PROGRESSDATA:
|
|
curl.progressData = param
|
|
return nil
|
|
case opt == OPT_HEADERDATA: // also known as OPT_WRITEHEADER
|
|
curl.headerData = param
|
|
return nil
|
|
case opt == OPT_WRITEDATA: // OPT_FILE
|
|
curl.writeData = param
|
|
return nil
|
|
|
|
case opt == OPT_READFUNCTION:
|
|
fun := param.(func([]byte, interface{}) int)
|
|
curl.readFunction = &fun
|
|
|
|
ptr := C.return_read_function()
|
|
if err := newCurlError(C.curl_easy_setopt_pointer(p, C.CURLoption(opt), ptr)); err == nil {
|
|
return newCurlError(C.curl_easy_setopt_pointer(p, OPT_READDATA, unsafe.Pointer(curl.handle)))
|
|
} else {
|
|
return err
|
|
}
|
|
|
|
case opt == OPT_PROGRESSFUNCTION:
|
|
fun := param.(func(float64, float64, float64, float64, interface{}) bool)
|
|
curl.progressFunction = &fun
|
|
|
|
ptr := C.return_progress_function()
|
|
if err := newCurlError(C.curl_easy_setopt_pointer(p, C.CURLoption(opt), ptr)); err == nil {
|
|
return newCurlError(C.curl_easy_setopt_pointer(p, OPT_PROGRESSDATA, unsafe.Pointer(curl.handle)))
|
|
} else {
|
|
return err
|
|
}
|
|
|
|
case opt == OPT_HEADERFUNCTION:
|
|
fun := param.(func([]byte, interface{}) bool)
|
|
curl.headerFunction = &fun
|
|
|
|
ptr := C.return_header_function()
|
|
if err := newCurlError(C.curl_easy_setopt_pointer(p, C.CURLoption(opt), ptr)); err == nil {
|
|
return newCurlError(C.curl_easy_setopt_pointer(p, OPT_HEADERDATA, unsafe.Pointer(curl.handle)))
|
|
} else {
|
|
return err
|
|
}
|
|
|
|
case opt == OPT_WRITEFUNCTION:
|
|
fun := param.(func([]byte, interface{}) bool)
|
|
curl.writeFunction = &fun
|
|
|
|
ptr := C.return_write_function()
|
|
if err := newCurlError(C.curl_easy_setopt_pointer(p, C.CURLoption(opt), ptr)); err == nil {
|
|
return newCurlError(C.curl_easy_setopt_pointer(p, OPT_WRITEDATA, unsafe.Pointer(curl.handle)))
|
|
} else {
|
|
return err
|
|
}
|
|
|
|
// for OPT_HTTPPOST, use struct Form
|
|
case opt == OPT_HTTPPOST:
|
|
post := param.(*Form)
|
|
ptr := post.head
|
|
return newCurlError(C.curl_easy_setopt_pointer(p, C.CURLoption(opt), unsafe.Pointer(ptr)))
|
|
|
|
case opt >= C.CURLOPTTYPE_OFF_T:
|
|
val := C.off_t(0)
|
|
switch t := param.(type) {
|
|
case int:
|
|
val = C.off_t(t)
|
|
case uint64:
|
|
val = C.off_t(t)
|
|
default:
|
|
panic("OFF_T conversion not supported")
|
|
}
|
|
return newCurlError(C.curl_easy_setopt_off_t(p, C.CURLoption(opt), val))
|
|
|
|
case opt >= C.CURLOPTTYPE_FUNCTIONPOINT:
|
|
// function pointer
|
|
panic("function pointer not implemented yet!")
|
|
|
|
case opt >= C.CURLOPTTYPE_OBJECTPOINT:
|
|
switch t := param.(type) {
|
|
case string:
|
|
ptr := C.CString(t)
|
|
curl.mallocAddPtr(ptr)
|
|
return newCurlError(C.curl_easy_setopt_string(p, C.CURLoption(opt), ptr))
|
|
case CurlString:
|
|
ptr := (*C.char)(t)
|
|
return newCurlError(C.curl_easy_setopt_string(p, C.CURLoption(opt), ptr))
|
|
case []string:
|
|
if len(t) > 0 {
|
|
ptr := C.CString(t[0])
|
|
curl.mallocAddPtr(ptr)
|
|
a_slist := C.curl_slist_append(nil, ptr)
|
|
for _, s := range t[1:] {
|
|
ptr := C.CString(s)
|
|
curl.mallocAddPtr(ptr)
|
|
a_slist = C.curl_slist_append(a_slist, ptr)
|
|
}
|
|
return newCurlError(C.curl_easy_setopt_slist(p, C.CURLoption(opt), a_slist))
|
|
} else {
|
|
return newCurlError(C.curl_easy_setopt_slist(p, C.CURLoption(opt), nil))
|
|
}
|
|
case []CurlString:
|
|
if len(t) > 0 {
|
|
ptr := (*C.char)(t[0])
|
|
a_slist := C.curl_slist_append(nil, ptr)
|
|
for _, s := range t[1:] {
|
|
ptr := (*C.char)(s)
|
|
a_slist = C.curl_slist_append(a_slist, ptr)
|
|
}
|
|
return newCurlError(C.curl_easy_setopt_slist(p, C.CURLoption(opt), a_slist))
|
|
} else {
|
|
return newCurlError(C.curl_easy_setopt_slist(p, C.CURLoption(opt), nil))
|
|
}
|
|
default:
|
|
// It panics if v's Kind is not Chan, Func, Map, Ptr, Slice, or UnsafePointer.
|
|
// val := reflect.ValueOf(param)
|
|
//fmt.Printf("DEBUG(Setopt): param=%x\n", val.Pointer())
|
|
//println("DEBUG can addr =", val.Pointer(), "opt=", opt)
|
|
// pass a pointer to GoInterface
|
|
return newCurlError(C.curl_easy_setopt_pointer(p, C.CURLoption(opt),
|
|
unsafe.Pointer(¶m)))
|
|
}
|
|
case opt >= C.CURLOPTTYPE_LONG:
|
|
val := C.long(0)
|
|
switch t := param.(type) {
|
|
case int:
|
|
val = C.long(t)
|
|
case bool:
|
|
if t {
|
|
val = 1
|
|
}
|
|
case int64:
|
|
val = C.long(t)
|
|
case int32:
|
|
val = C.long(t)
|
|
default:
|
|
panic("not supported converstion to c long")
|
|
}
|
|
return newCurlError(C.curl_easy_setopt_long(p, C.CURLoption(opt), val))
|
|
}
|
|
panic("opt param error!")
|
|
}
|
|
|
|
// curl_easy_send - sends raw data over an "easy" connection
|
|
func (curl *CURL) Send(buffer []byte) (int, error) {
|
|
p := curl.handle
|
|
buflen := len(buffer)
|
|
n := C.size_t(0)
|
|
ret := C.curl_easy_send(p, unsafe.Pointer(&buffer[0]), C.size_t(buflen), &n)
|
|
return int(n), newCurlError(ret)
|
|
}
|
|
|
|
// curl_easy_recv - receives raw data on an "easy" connection
|
|
func (curl *CURL) Recv(buffer []byte) (int, error) {
|
|
p := curl.handle
|
|
buflen := len(buffer)
|
|
buf := C.CString(string(buffer))
|
|
n := C.size_t(0)
|
|
ret := C.curl_easy_recv(p, unsafe.Pointer(buf), C.size_t(buflen), &n)
|
|
return copy(buffer, C.GoStringN(buf, C.int(n))), newCurlError(ret)
|
|
}
|
|
|
|
// curl_easy_perform - Perform a file transfer
|
|
func (curl *CURL) Perform() error {
|
|
p := curl.handle
|
|
return newCurlError(C.curl_easy_perform(p))
|
|
}
|
|
|
|
// curl_easy_pause - pause and unpause a connection
|
|
func (curl *CURL) Pause(bitmask int) error {
|
|
p := curl.handle
|
|
return newCurlError(C.curl_easy_pause(p, C.int(bitmask)))
|
|
}
|
|
|
|
// curl_easy_reset - reset all options of a libcurl session handle
|
|
func (curl *CURL) Reset() {
|
|
p := curl.handle
|
|
C.curl_easy_reset(p)
|
|
}
|
|
|
|
// curl_easy_escape - URL encodes the given string
|
|
func (curl *CURL) Escape(url string) string {
|
|
p := curl.handle
|
|
oldUrl := C.CString(url)
|
|
defer C.free(unsafe.Pointer(oldUrl))
|
|
newUrl := C.curl_easy_escape(p, oldUrl, 0)
|
|
defer C.curl_free(unsafe.Pointer(newUrl))
|
|
return C.GoString(newUrl)
|
|
}
|
|
|
|
// curl_easy_unescape - URL decodes the given string
|
|
func (curl *CURL) Unescape(url string) string {
|
|
p := curl.handle
|
|
oldUrl := C.CString(url)
|
|
outlength := C.int(0)
|
|
defer C.free(unsafe.Pointer(oldUrl))
|
|
// If outlength is non-NULL, the function will write the length of the
|
|
// returned string in the integer it points to. This allows an
|
|
// escaped string containing %00 to still get used properly after unescaping.
|
|
newUrl := C.curl_easy_unescape(p, oldUrl, 0, &outlength)
|
|
defer C.curl_free(unsafe.Pointer(newUrl))
|
|
return C.GoStringN(newUrl, outlength)
|
|
}
|
|
|
|
// curl_easy_getinfo - extract information from a curl handle
|
|
func (curl *CURL) Getinfo(info CurlInfo) (ret interface{}, err error) {
|
|
p := curl.handle
|
|
cInfo := C.CURLINFO(info)
|
|
switch cInfo & C.CURLINFO_TYPEMASK {
|
|
case C.CURLINFO_STRING:
|
|
a_string := C.CString("")
|
|
defer C.free(unsafe.Pointer(a_string))
|
|
err := newCurlError(C.curl_easy_getinfo_string(p, cInfo, &a_string))
|
|
ret := C.GoString(a_string)
|
|
debugf("Getinfo %s", ret)
|
|
return ret, err
|
|
case C.CURLINFO_LONG:
|
|
a_long := C.long(-1)
|
|
err := newCurlError(C.curl_easy_getinfo_long(p, cInfo, &a_long))
|
|
ret := int(a_long)
|
|
debugf("Getinfo %s", ret)
|
|
return ret, err
|
|
case C.CURLINFO_DOUBLE:
|
|
a_double := C.double(0.0)
|
|
err := newCurlError(C.curl_easy_getinfo_double(p, cInfo, &a_double))
|
|
ret := float64(a_double)
|
|
debugf("Getinfo %s", ret)
|
|
return ret, err
|
|
case C.CURLINFO_SLIST:
|
|
a_ptr_slist := new(_Ctype_struct_curl_slist)
|
|
err := newCurlError(C.curl_easy_getinfo_slist(p, cInfo, &a_ptr_slist))
|
|
ret := []string{}
|
|
for a_ptr_slist != nil {
|
|
debugf("Getinfo %s %v", C.GoString(a_ptr_slist.data), a_ptr_slist.next)
|
|
ret = append(ret, C.GoString(a_ptr_slist.data))
|
|
a_ptr_slist = a_ptr_slist.next
|
|
}
|
|
return ret, err
|
|
default:
|
|
panic("error calling Getinfo\n")
|
|
}
|
|
panic("not implemented yet!")
|
|
return nil, nil
|
|
}
|
|
|
|
func (curl *CURL) GetHandle() unsafe.Pointer {
|
|
return curl.handle
|
|
}
|
|
|
|
func (curl *CURL) MallocGetPos() int {
|
|
return len(curl.mallocAllocs)
|
|
}
|
|
|
|
func (curl *CURL) MallocFreeAfter(from int) {
|
|
l := len(curl.mallocAllocs)
|
|
for idx := from; idx < l; idx++ {
|
|
C.free(unsafe.Pointer(curl.mallocAllocs[idx]))
|
|
curl.mallocAllocs[idx] = nil
|
|
}
|
|
curl.mallocAllocs = curl.mallocAllocs[0:from]
|
|
}
|
|
|
|
func (curl *CURL) mallocAddPtr(ptr *C.char) {
|
|
curl.mallocAllocs = append(curl.mallocAllocs, ptr)
|
|
}
|
|
|
|
// A multipart/formdata HTTP POST form
|
|
type Form struct {
|
|
head, last *C.struct_curl_httppost
|
|
}
|
|
|
|
func NewForm() *Form {
|
|
return &Form{}
|
|
}
|
|
|
|
func (form *Form) Add(name string, content interface{}) error {
|
|
head, last := form.head, form.last
|
|
namestr := C.CString(name)
|
|
defer C.free(unsafe.Pointer(namestr))
|
|
var (
|
|
buffer *C.char
|
|
length C.int
|
|
)
|
|
switch t := content.(type) {
|
|
case string:
|
|
buffer = C.CString(t)
|
|
length = C.int(len(t))
|
|
case []byte:
|
|
buffer = C.CString(string(t))
|
|
length = C.int(len(t))
|
|
default:
|
|
panic("not implemented")
|
|
}
|
|
defer C.free(unsafe.Pointer(buffer))
|
|
C.curl_formadd_name_content_length(&head, &last, namestr, buffer, length)
|
|
form.head, form.last = head, last
|
|
return nil
|
|
}
|
|
|
|
func (form *Form) AddWithType(name string, content interface{}, content_type string) error {
|
|
head, last := form.head, form.last
|
|
namestr := C.CString(name)
|
|
typestr := C.CString(content_type)
|
|
defer C.free(unsafe.Pointer(namestr))
|
|
defer C.free(unsafe.Pointer(typestr))
|
|
var (
|
|
buffer *C.char
|
|
length C.int
|
|
)
|
|
switch t := content.(type) {
|
|
case string:
|
|
buffer = C.CString(t)
|
|
length = C.int(len(t))
|
|
case []byte:
|
|
buffer = C.CString(string(t))
|
|
length = C.int(len(t))
|
|
default:
|
|
panic("not implemented")
|
|
}
|
|
defer C.free(unsafe.Pointer(buffer))
|
|
C.curl_formadd_name_content_length_type(&head, &last, namestr, buffer, length, typestr)
|
|
form.head, form.last = head, last
|
|
return nil
|
|
}
|
|
|
|
func (form *Form) AddFile(name, filename string) error {
|
|
head, last := form.head, form.last
|
|
namestr := C.CString(name)
|
|
pathstr := C.CString(filename)
|
|
typestr := C.CString(guessType(filename))
|
|
defer C.free(unsafe.Pointer(namestr))
|
|
defer C.free(unsafe.Pointer(pathstr))
|
|
defer C.free(unsafe.Pointer(typestr))
|
|
C.curl_formadd_name_file_type(&head, &last, namestr, pathstr, typestr)
|
|
form.head, form.last = head, last
|
|
return nil
|
|
}
|
|
|
|
func (form *Form) AddFromFile(name, filename string) {
|
|
}
|
|
|
|
func guessType(filename string) string {
|
|
ext := path.Ext(filename)
|
|
file_type := mime.TypeByExtension(ext)
|
|
if file_type == "" {
|
|
return "application/octet-stream"
|
|
}
|
|
return file_type
|
|
}
|