diff --git a/Makefile b/Makefile index bbfb133..f2f3df6 100644 --- a/Makefile +++ b/Makefile @@ -14,6 +14,7 @@ LDLIBS = -lcurl ARFLAGS = rcs TARGET = libfortran-curl.a +DICT = dict DOWNLOAD = download GOPHER = gopher HTTP = http @@ -21,7 +22,7 @@ IMAP = imap SMTP = smtp VERSION = version -.PHONY: all clean download gopher http imap smtp version +.PHONY: all clean dict download gopher http imap smtp version all: $(TARGET) @@ -30,6 +31,9 @@ $(TARGET): $(FC) $(FFLAGS) -c src/curl.f90 $(AR) $(ARFLAGS) $(TARGET) curl.o curlv.o +dict: $(TARGET) + $(FC) $(FFLAGS) $(LDFLAGS) -o $(DICT) examples/dict/dict.f90 $(TARGET) $(LDLIBS) + download: $(TARGET) $(FC) $(FFLAGS) $(LDFLAGS) -o $(DOWNLOAD) examples/download/download.f90 $(TARGET) $(LDLIBS) @@ -52,6 +56,7 @@ clean: if [ `ls -1 *.mod 2>/dev/null | wc -l` -gt 0 ]; then rm *.mod; fi if [ `ls -1 *.o 2>/dev/null | wc -l` -gt 0 ]; then rm *.o; fi if [ -e $(TARGET) ]; then rm $(TARGET); fi + if [ -e $(DICT) ]; then rm $(DICT); fi if [ -e $(DOWNLOAD) ]; then rm $(DOWNLOAD); fi if [ -e $(GOPHER) ]; then rm $(GOPHER); fi if [ -e $(HTTP) ]; then rm $(HTTP); fi diff --git a/README.md b/README.md index c72a3da..f951034 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ Link your Fortran application with `libfortran-curl.a -lcurl`. ## Examples Examples are provided in directory `examples/`: +* **dict** queries a [DICT](https://en.wikipedia.org/wiki/DICT) server on TCP port 2628. * **download** fetches a remote file. * **gopher** prints the contents of a remote Gopher map file. * **http** makes an HTTP GET request. @@ -50,15 +51,15 @@ $ make ``` ## Coverage -| C Function Name | Fortran Interface Name | Bound | -|-----------------------|-----------------------------------------|-------| -| `curl_easy_init` | `curl_easy_init` | ✓ | -| `curl_easy_perform` | `curl_easy_perform` | ✓ | -| `curl_easy_cleanup` | `curl_easy_cleanup` | ✓ | -| `curl_easy_setopt` | `curl_easy_setopt`, `curl_easy_setopt_` | ✓ | -| `curl_slist_append` | `curl_slist_append` | ✓ | -| `curl_slist_free_all` | `curl_slist_free_all` | ✓ | -| `curl_version_info` | `curl_version_info` | ✓ | +| C Function Name | Fortran Interface Name | Bound | +|-----------------------|---------------------------------------------------------------------------|-------| +| `curl_easy_init` | `curl_easy_init` | ✓ | +| `curl_easy_perform` | `curl_easy_perform` | ✓ | +| `curl_easy_cleanup` | `curl_easy_cleanup` | ✓ | +| `curl_easy_setopt` | `curl_easy_setopt`, `curl_easy_setopt_c_ptr`, `curl_easy_setopt_c_funptr` | ✓ | +| `curl_slist_append` | `curl_slist_append` | ✓ | +| `curl_slist_free_all` | `curl_slist_free_all` | ✓ | +| `curl_version_info` | `curl_version_info` | ✓ | | C Constant Name | Fortran Interface Name | Bound | |---------------------|------------------------|-------| diff --git a/examples/dict/dict.f90 b/examples/dict/dict.f90 new file mode 100644 index 0000000..b06fa43 --- /dev/null +++ b/examples/dict/dict.f90 @@ -0,0 +1,79 @@ +! dict.f90 +! +! Basic dict protocol example, that looks up the definitions of the given words in +! the dictionaries at dict://dict.org/ on port 2628. The program is equal to +! running +! +! $ curl dict://dict.org/d:FORTRAN +! +! on the command-line. +! +! Author: John S. Urban, Philipp Engel +! Licence: ISC +module callback_dict + use :: curl, only: c_f_str_ptr + implicit none + private + public :: response_callback +contains + ! static size_t callback(char *ptr, size_t size, size_t nmemb, void *data) + function response_callback(ptr, size, nmemb, data) bind(c) + !! Callback function for `CURLOPT_WRITEFUNCTION` that prints the + !! response to standard output. + !! + !! This callback function might be called several times by libcurl, + !! passing in more chunks of the response. + use, intrinsic :: iso_c_binding, only: c_associated, c_f_pointer, c_ptr, c_size_t + type(c_ptr), intent(in), value :: ptr !! C pointer to a chunk of the response. + integer(kind=c_size_t), intent(in), value :: size !! Always 1. + integer(kind=c_size_t), intent(in), value :: nmemb !! Size of the response chunk. + type(c_ptr), intent(in), value :: data !! C pointer to argument passed by caller. + integer(kind=c_size_t) :: response_callback !! Function return value. + character(len=:), allocatable :: response + + response_callback = int(0, kind=c_size_t) + + if (.not. c_associated(ptr)) return + + allocate (character(len=nmemb) :: response) + call c_f_str_ptr(ptr, response) + write (*, '(a)', advance='no') response + deallocate (response) + + response_callback = nmemb + end function response_callback +end module callback_dict + +program main + use, intrinsic :: iso_c_binding + use :: curl + use :: callback_dict + implicit none + + character(len=*), parameter :: DEFAULT_PROTOCOL = 'dict' + character(len=*), parameter :: DEFAULT_URL = 'dict://dict.org/' + character(len=*), parameter :: DICT_QUERY = 'd:FORTRAN' + type(c_ptr) :: curl_ptr + integer :: rc + + curl_ptr = curl_easy_init() + + if (.not. c_associated(curl_ptr)) then + stop 'Error: curl_easy_init() failed' + end if + + ! Set curl options. + rc = curl_easy_setopt(curl_ptr, CURLOPT_DEFAULT_PROTOCOL, DEFAULT_PROTOCOL // c_null_char) + rc = curl_easy_setopt(curl_ptr, CURLOPT_URL, DEFAULT_URL // DICT_QUERY // c_null_char) + rc = curl_easy_setopt(curl_ptr, CURLOPT_TIMEOUT, int(10, kind=8)) + rc = curl_easy_setopt(curl_ptr, CURLOPT_NOSIGNAL, int( 1, kind=8)) + rc = curl_easy_setopt(curl_ptr, CURLOPT_CONNECTTIMEOUT, int(10, kind=8)) + rc = curl_easy_setopt(curl_ptr, CURLOPT_WRITEFUNCTION, c_funloc(response_callback)) + + ! Send request. + if (curl_easy_perform(curl_ptr) /= CURLE_OK) then + print '(a)', 'Error: curl_easy_perform() failed' + end if + + call curl_easy_cleanup(curl_ptr) +end program main diff --git a/fpm.toml b/fpm.toml index b335bd0..e737dff 100644 --- a/fpm.toml +++ b/fpm.toml @@ -1,15 +1,20 @@ name = "fortran-curl" -version = "0.1.0" +version = "0.1.1" license = "ISC" author = "Philipp Engel" maintainer = "@interkosmos" -copyright = "Copyright (c) 2020, Philipp Engel" +copyright = "Copyright (c) 2021, Philipp Engel" description = "Fortran 2008 ISO C binding interfaces to libcurl" -keywords = ["curl", "libcurl", "http", "gopher", "smtp", "imap"] +keywords = ["curl", "libcurl", "http", "gopher", "smtp", "imap", "dict"] [build] link = "curl" +[[example]] +name = "dict" +source-dir = "examples/dict" +main = "dict.f90" + [[example]] name = "download" source-dir = "examples/download" diff --git a/src/curl.f90 b/src/curl.f90 index 475d85e..216e1b9 100644 --- a/src/curl.f90 +++ b/src/curl.f90 @@ -15,7 +15,8 @@ module curl public :: curl_easy_perform public :: curl_easy_cleanup public :: curl_easy_setopt - public :: curl_easy_setopt_ + public :: curl_easy_setopt_c_ptr + public :: curl_easy_setopt_c_funptr public :: curl_slist_append public :: curl_slist_free_all public :: curl_version_info @@ -404,13 +405,22 @@ function curl_easy_perform(curl) bind(c, name='curl_easy_perform') end function curl_easy_perform ! CURLcode curl_easy_setopt(CURL *curl, CURLoption option, ...) - function curl_easy_setopt_(curl, option, parameter) bind(c, name='curl_easy_setopt') + function curl_easy_setopt_c_ptr(curl, option, parameter) bind(c, name='curl_easy_setopt') import :: c_int, c_ptr type(c_ptr), intent(in), value :: curl integer(kind=c_int), intent(in), value :: option type(c_ptr), intent(in), value :: parameter - integer(kind=c_int) :: curl_easy_setopt_ - end function curl_easy_setopt_ + integer(kind=c_int) :: curl_easy_setopt_c_ptr + end function curl_easy_setopt_c_ptr + + ! CURLcode curl_easy_setopt(CURL *curl, CURLoption option, ...) + function curl_easy_setopt_c_funptr(curl, option, parameter) bind(c, name='curl_easy_setopt') + import :: c_funptr, c_int, c_ptr + type(c_ptr), intent(in), value :: curl + integer(kind=c_int), intent(in), value :: option + type(c_funptr), intent(in), value :: parameter + integer(kind=c_int) :: curl_easy_setopt_c_funptr + end function curl_easy_setopt_c_funptr ! struct curl_slist *curl_slist_append(struct curl_slist *list, const char *string) function curl_slist_append(list, string) bind(c, name='curl_slist_append') @@ -470,7 +480,7 @@ function curl_easy_setopt_char(curl, option, parameter) character(len=*), target, intent(in) :: parameter integer :: curl_easy_setopt_char - curl_easy_setopt_char = curl_easy_setopt_(curl, option, c_loc(parameter)) + curl_easy_setopt_char = curl_easy_setopt_c_ptr(curl, option, c_loc(parameter)) end function curl_easy_setopt_char ! CURLcode curl_easy_setopt(CURL *curl, CURLoption option, ...) @@ -480,7 +490,7 @@ function curl_easy_setopt_fptr(curl, option, parameter) type(c_funptr), intent(in) :: parameter integer :: curl_easy_setopt_fptr - curl_easy_setopt_fptr = curl_easy_setopt_(curl, option, parameter) + curl_easy_setopt_fptr = curl_easy_setopt_c_funptr(curl, option, parameter) end function curl_easy_setopt_fptr ! CURLcode curl_easy_setopt(CURL *curl, CURLoption option, ...) @@ -490,7 +500,7 @@ function curl_easy_setopt_int(curl, option, parameter) integer(kind=4), target, intent(in) :: parameter integer :: curl_easy_setopt_int - curl_easy_setopt_int = curl_easy_setopt_(curl, option, c_loc(parameter)) + curl_easy_setopt_int = curl_easy_setopt_c_ptr(curl, option, c_loc(parameter)) end function curl_easy_setopt_int ! CURLcode curl_easy_setopt(CURL *curl, CURLoption option, ...) @@ -500,7 +510,7 @@ function curl_easy_setopt_long(curl, option, parameter) integer(kind=8), target, intent(in) :: parameter integer :: curl_easy_setopt_long - curl_easy_setopt_long = curl_easy_setopt_(curl, option, c_loc(parameter)) + curl_easy_setopt_long = curl_easy_setopt_c_ptr(curl, option, c_loc(parameter)) end function curl_easy_setopt_long ! CURLcode curl_easy_setopt(CURL *curl, CURLoption option, ...) @@ -510,7 +520,7 @@ function curl_easy_setopt_ptr(curl, option, parameter) type(c_ptr), intent(in) :: parameter integer :: curl_easy_setopt_ptr - curl_easy_setopt_ptr = curl_easy_setopt_(curl, option, parameter) + curl_easy_setopt_ptr = curl_easy_setopt_c_ptr(curl, option, parameter) end function curl_easy_setopt_ptr ! curl_version_info_data *curl_version_info(CURLversion age)