1 /* libquvi
2  * Copyright (C) 2009-2011  Toni Gundogdu <legatvs@gmail.com>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17  * 02110-1301  USA
18  */
19 
20 #include "config.h"
21 
22 #include <stdlib.h>
23 #include <memory.h>
24 #include <assert.h>
25 
26 #include "quvi/quvi.h"
27 #include "quvi/net.h"
28 
29 #include "internal.h"
30 #include "util.h"
31 #include "lua_wrap.h"
32 #include "curl_wrap.h"
33 
34 /* curl_init */
35 
curl_init(_quvi_t q)36 QUVIcode curl_init(_quvi_t q)
37 {
38   curl_global_init(CURL_GLOBAL_ALL);
39 
40   q->curl = curl_easy_init();
41   if (!q->curl)
42     return (QUVI_CURLINIT);
43 
44   /* Set quvi defaults with libcurl. */
45   curl_easy_setopt(q->curl, CURLOPT_USERAGENT, "Mozilla/5.0");
46   curl_easy_setopt(q->curl, CURLOPT_FOLLOWLOCATION, 1L);
47   curl_easy_setopt(q->curl, CURLOPT_NOBODY, 0L);
48 
49   return (QUVI_OK);
50 }
51 
52 /* curl_close */
53 
curl_close(_quvi_t q)54 void curl_close(_quvi_t q)
55 {
56   curl_easy_cleanup(q->curl);
57   q->curl = NULL;
58 
59   curl_global_cleanup();
60 }
61 
_realloc(void * p,const size_t size)62 static void *_realloc(void *p, const size_t size)
63 {
64   if (p) return realloc(p, size);
65   return malloc(size);
66 }
67 
68 struct content_s
69 {
70   size_t size;
71   char *p;
72 };
73 
74 typedef struct content_s *content_t;
75 
quvi_write_callback_default(void * p,size_t size,size_t nmemb,void * data)76 size_t quvi_write_callback_default(void *p,
77                                    size_t size,
78                                    size_t nmemb,
79                                    void *data)
80 {
81   content_t m = (content_t)data;
82   const size_t rsize = size * nmemb;
83   void *tmp = _realloc(m->p, m->size + rsize + 1);
84   if (tmp)
85     {
86       m->p = (char *)tmp;
87       memcpy(&(m->p[m->size]), p, rsize);
88       m->size += rsize;
89       m->p[m->size] = '\0';
90     }
91   return (rsize);
92 }
93 
set_feat(_quvi_t q,_quvi_net_t n,QUVInetPropertyFeatureName feature,CURLoption copt)94 static void set_feat(_quvi_t q, _quvi_net_t n,
95                      QUVInetPropertyFeatureName feature,
96                      CURLoption copt)
97 
98 {
99   char *s = quvi_net_get_one_prop_feat(n, feature);
100   if (s)
101     curl_easy_setopt(q->curl, copt, s);
102 }
103 
set_opts_from_lua_script(_quvi_t q,_quvi_net_t n)104 static void set_opts_from_lua_script(_quvi_t q, _quvi_net_t n)
105 {
106   set_feat(q, n, QUVI_NET_PROPERTY_FEATURE_ARBITRARYCOOKIE, CURLOPT_COOKIE);
107   set_feat(q, n, QUVI_NET_PROPERTY_FEATURE_USERAGENT, CURLOPT_USERAGENT);
108 }
109 
110 /* curl_fetch */
111 
curl_fetch(_quvi_t q,_quvi_net_t n)112 QUVIcode curl_fetch(_quvi_t q, _quvi_net_t n)
113 {
114   struct content_s content;
115   CURLcode curlcode;
116   long conncode;
117   QUVIcode rc;
118 
119   curl_easy_setopt(q->curl, CURLOPT_URL, n->url);
120   curl_easy_setopt(q->curl, CURLOPT_ENCODING, "");
121 
122   memset(&content, 0, sizeof(struct content_s));
123   curl_easy_setopt(q->curl, CURLOPT_WRITEDATA, &content);
124 
125   curl_easy_setopt(q->curl, CURLOPT_WRITEFUNCTION,
126                    (curl_write_callback) quvi_write_callback_default);
127 
128   set_opts_from_lua_script(q,n);
129 
130   curlcode = curl_easy_perform(q->curl);
131 
132   curl_easy_getinfo(q->curl, CURLINFO_RESPONSE_CODE, &n->resp_code);
133   curl_easy_getinfo(q->curl, CURLINFO_HTTP_CONNECTCODE, &conncode);
134 
135   if (curlcode == CURLE_OK && n->resp_code == 200)
136     rc = QUVI_OK;
137   else
138     {
139       if (curlcode == CURLE_OK)
140         {
141           freprintf(&n->errmsg, "server response code %ld (conncode=%ld)",
142                     n->resp_code, conncode);
143         }
144       else
145         {
146           freprintf(&n->errmsg, "%s (http/%ld, conn/%ld, curl/%ld)",
147                     curl_easy_strerror(curlcode), n->resp_code,
148                     conncode, curlcode);
149         }
150 
151       rc = QUVI_CALLBACK;
152     }
153 
154   n->fetch.content = content.p;
155 
156   return (rc);
157 }
158 
set_redirect_opts(_quvi_t q,content_t m,const char * url)159 static void set_redirect_opts(_quvi_t q, content_t m, const char *url)
160 {
161   curl_easy_setopt(q->curl, CURLOPT_WRITEDATA, m);
162 
163   curl_easy_setopt(q->curl, CURLOPT_WRITEFUNCTION,
164                    (curl_write_callback) quvi_write_callback_default);
165 
166   /* GET -> HEAD */
167   curl_easy_setopt(q->curl, CURLOPT_URL, url);
168   curl_easy_setopt(q->curl, CURLOPT_FOLLOWLOCATION, 0L);
169 }
170 
restore_opts(_quvi_t q)171 static void restore_opts(_quvi_t q)
172 {
173   curl_easy_setopt(q->curl, CURLOPT_FOLLOWLOCATION, 1L);
174   curl_easy_setopt(q->curl, CURLOPT_HTTPGET, 1L); /* HEAD -> GET */
175 }
176 
177 /* curl_resolve */
178 
curl_resolve(_quvi_t q,_quvi_net_t n)179 QUVIcode curl_resolve(_quvi_t q, _quvi_net_t n)
180 {
181   struct content_s tmp;
182   CURLcode curlcode;
183   long conncode;
184   QUVIcode rc;
185 
186   memset(&tmp, 0, sizeof(tmp));
187 
188   set_redirect_opts(q, &tmp, n->url);
189   set_opts_from_lua_script(q,n);
190 
191   curlcode = curl_easy_perform(q->curl);
192   rc       = QUVI_OK;
193 
194   restore_opts(q);
195 
196   curl_easy_getinfo(q->curl, CURLINFO_RESPONSE_CODE, &n->resp_code);
197   curl_easy_getinfo(q->curl, CURLINFO_HTTP_CONNECTCODE, &conncode);
198 
199   if (curlcode == CURLE_OK)
200     {
201       if (n->resp_code >= 301 && n->resp_code <= 303) /* New location */
202         {
203           char *r_url = NULL;
204 
205           curl_easy_getinfo(q->curl, CURLINFO_REDIRECT_URL, &r_url);
206           assert(r_url != NULL);
207 
208           freprintf(&n->redirect.url, "%s", r_url);
209         }
210     }
211   else
212     {
213       freprintf(&n->errmsg, "%s (http/%ld, conn/%ld, curl/%ld)",
214                 curl_easy_strerror(curlcode), n->resp_code,
215                 conncode, curlcode);
216 
217       rc = QUVI_CALLBACK;
218     }
219 
220   if (tmp.p)
221     _free(tmp.p);
222 
223   return (rc);
224 }
225 
226 /* curl_verify */
227 
curl_verify(_quvi_t q,_quvi_net_t n)228 QUVIcode curl_verify(_quvi_t q, _quvi_net_t n)
229 {
230   struct content_s tmp;
231   CURLcode curlcode;
232   long conncode;
233   QUVIcode rc;
234 
235   rc = QUVI_OK;
236 
237   curl_easy_setopt(q->curl, CURLOPT_URL, n->url);
238   curl_easy_setopt(q->curl, CURLOPT_NOBODY, 1L);     /* get -> head */
239 
240   memset(&tmp, 0, sizeof(tmp));
241   curl_easy_setopt(q->curl, CURLOPT_WRITEDATA, &tmp);
242 
243   curl_easy_setopt(q->curl, CURLOPT_WRITEFUNCTION,
244                    (curl_write_callback) quvi_write_callback_default);
245 
246   set_opts_from_lua_script(q,n);
247 
248   curlcode = curl_easy_perform(q->curl);
249 
250   curl_easy_setopt(q->curl, CURLOPT_HTTPGET, 1L);    /* reset: head -> get */
251 
252   curl_easy_getinfo(q->curl, CURLINFO_RESPONSE_CODE, &n->resp_code);
253   curl_easy_getinfo(q->curl, CURLINFO_HTTP_CONNECTCODE, &conncode);
254 
255   if (curlcode == CURLE_OK)
256     {
257       if (n->resp_code == 200 || n->resp_code == 206)
258         {
259           const char *ct;
260 
261           curl_easy_getinfo(q->curl, CURLINFO_CONTENT_TYPE, &ct);
262           assert(ct != NULL);
263 
264           freprintf(&n->verify.content_type, "%s", ct);
265 
266           curl_easy_getinfo(q->curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD,
267                             &n->verify.content_length);
268         }
269       else
270         {
271           freprintf(&n->errmsg, "server response code %ld (conncode=%ld)",
272                     n->resp_code, conncode);
273 
274           rc = QUVI_CALLBACK;
275         }
276     }
277   else
278     {
279       freprintf(&n->errmsg, "%s (curlcode=%d, conncode=%ld)",
280                 curl_easy_strerror(curlcode), curlcode, conncode);
281 
282       rc = QUVI_CALLBACK;
283     }
284 
285   if (tmp.p)
286     _free(tmp.p);
287 
288   return (rc);
289 }
290 
curl_unescape_url(_quvi_t q,char * s)291 char *curl_unescape_url(_quvi_t q, char *s)
292 {
293   char *tmp, *r;
294 
295   assert(q!= 0);
296   assert(q->curl != 0);
297 
298   tmp = curl_easy_unescape(q->curl, s, 0, NULL);
299   assert(tmp != 0);
300 
301   r = strdup(tmp);
302   curl_free(tmp);
303 
304   _free(s);
305   return (r);
306 }
307 
308 /* vim: set ts=2 sw=2 tw=72 expandtab: */
309