1 /***********************************************************************
2  Freeciv - Copyright (C) 1996 - A Kjeldberg, L Gregersen, P Unold
3    This program is free software; you can redistribute it and/or modify
4    it under the terms of the GNU General Public License as published by
5    the Free Software Foundation; either version 2, or (at your option)
6    any later version.
7 
8    This program is distributed in the hope that it will be useful,
9    but WITHOUT ANY WARRANTY; without even the implied warranty of
10    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11    GNU General Public License for more details.
12 ***********************************************************************/
13 
14 #ifdef HAVE_CONFIG_H
15 #include <fc_config.h>
16 #endif
17 
18 #include <curl/curl.h>
19 
20 #ifdef WIN32_NATIVE
21 #include <windows.h>
22 #endif
23 
24 /* utility */
25 #include "fcintl.h"
26 #include "ioz.h"
27 #include "mem.h"
28 #include "rand.h"
29 #include "registry.h"
30 
31 #include "netfile.h"
32 
33 struct netfile_post {
34   struct curl_httppost *first;
35   struct curl_httppost *last;
36 };
37 
38 typedef size_t (*netfile_write_cb)(char *ptr, size_t size, size_t nmemb, void *userdata);
39 
40 static char error_buf_curl[CURL_ERROR_SIZE];
41 
42 /**********************************************************************
43   Set handle to usable state.
44 ***********************************************************************/
netfile_init_handle(void)45 static CURL *netfile_init_handle(void)
46 {
47   /* Consecutive transfers can use same handle for better performance */
48   static CURL *handle = NULL;
49 
50   if (handle == NULL) {
51     handle = curl_easy_init();
52   } else {
53     curl_easy_reset(handle);
54   }
55 
56   error_buf_curl[0] = '\0';
57   curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, error_buf_curl);
58 
59 #ifdef CUSTOM_CACERT_PATH
60   curl_easy_setopt(handle, CURLOPT_CAINFO, CUSTOM_CACERT_PATH);
61 #endif /* CUSTOM_CERT_PATH */
62 
63   return handle;
64 }
65 
66 /**********************************************************************
67   curl write callback to store received file to memory.
68 ***********************************************************************/
netfile_memwrite_cb(char * ptr,size_t size,size_t nmemb,void * userdata)69 static size_t netfile_memwrite_cb(char *ptr, size_t size, size_t nmemb, void *userdata)
70 {
71   struct netfile_write_cb_data *data = (struct netfile_write_cb_data *)userdata;
72 
73   if (size > 0) {
74     data->mem = fc_realloc(data->mem, data->size + size * nmemb);
75     memcpy(data->mem + data->size, ptr, size * nmemb);
76     data->size += size * nmemb;
77   }
78 
79   return size * nmemb;
80 }
81 
82 /**********************************************************************
83   Fetch file from given URL to given file stream. This is core
84   function of netfile module.
85 ***********************************************************************/
netfile_download_file_core(const char * URL,FILE * fp,struct netfile_write_cb_data * mem_data,nf_errmsg cb,void * data)86 static bool netfile_download_file_core(const char *URL, FILE *fp,
87                                        struct netfile_write_cb_data *mem_data,
88                                        nf_errmsg cb, void *data)
89 {
90   CURLcode curlret;
91   struct curl_slist *headers = NULL;
92   static CURL *handle;
93   bool ret = TRUE;
94 
95   handle = netfile_init_handle();
96 
97   headers = curl_slist_append(headers,"User-Agent: Freeciv/" VERSION_STRING);
98 
99   curl_easy_setopt(handle, CURLOPT_URL, URL);
100   if (mem_data != NULL) {
101     mem_data->mem = NULL;
102     mem_data->size = 0;
103     curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, netfile_memwrite_cb);
104     curl_easy_setopt(handle, CURLOPT_WRITEDATA, mem_data);
105   } else {
106     curl_easy_setopt(handle, CURLOPT_WRITEDATA, fp);
107   }
108   curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
109   curl_easy_setopt(handle, CURLOPT_FAILONERROR, 1);
110 
111   curlret = curl_easy_perform(handle);
112 
113   curl_slist_free_all(headers);
114 
115   if (curlret != CURLE_OK) {
116     if (cb != NULL) {
117       char buf[2048 + CURL_ERROR_SIZE];
118 
119       fc_snprintf(buf, sizeof(buf),
120                   /* TRANS: first %s is URL, second is Curl error message
121                    * (not in Freeciv translation domain) */
122                   _("Failed to fetch %s: %s"), URL,
123                   error_buf_curl[0] != '\0' ? error_buf_curl : curl_easy_strerror(curlret));
124       cb(buf, data);
125     }
126 
127     ret = FALSE;
128   }
129 
130   return ret;
131 }
132 
133 /**********************************************************************
134   Fetch section file from net
135 ***********************************************************************/
netfile_get_section_file(const char * URL,nf_errmsg cb,void * data)136 struct section_file *netfile_get_section_file(const char *URL,
137                                               nf_errmsg cb, void *data)
138 {
139   bool success;
140   struct section_file *out = NULL;
141   struct netfile_write_cb_data mem_data;
142   fz_FILE *file;
143 
144   success = netfile_download_file_core(URL, NULL, &mem_data, cb, data);
145 
146   if (success) {
147     file = fz_from_memory(mem_data.mem, mem_data.size, TRUE);
148 
149     out = secfile_from_stream(file, TRUE);
150   }
151 
152   return out;
153 }
154 
155 /**********************************************************************
156   Fetch file from given URL and save as given filename.
157 ***********************************************************************/
netfile_download_file(const char * URL,const char * filename,nf_errmsg cb,void * data)158 bool netfile_download_file(const char *URL, const char *filename,
159                            nf_errmsg cb, void *data)
160 {
161   bool success;
162   FILE *fp;
163 
164   fp = fc_fopen(filename, "w+b");
165 
166   if (fp == NULL) {
167     if (cb != NULL) {
168       char buf[2048];
169 
170       fc_snprintf(buf, sizeof(buf),
171                   _("Could not open %s for writing"), filename);
172       cb(buf, data);
173     }
174     return FALSE;
175   }
176 
177   success = netfile_download_file_core(URL, fp, NULL, cb, data);
178 
179   fclose(fp);
180 
181   return success;
182 }
183 
184 /**********************************************************************
185   Allocate netfile_post
186 ***********************************************************************/
netfile_start_post(void)187 struct netfile_post *netfile_start_post(void)
188 {
189   return fc_calloc(1, sizeof(struct netfile_post));
190 }
191 
192 /**********************************************************************
193   Add one entry to netfile post form
194 ***********************************************************************/
netfile_add_form_str(struct netfile_post * post,const char * name,const char * val)195 void netfile_add_form_str(struct netfile_post *post,
196                           const char *name, const char *val)
197 {
198   curl_formadd(&post->first, &post->last,
199                CURLFORM_COPYNAME, name,
200                CURLFORM_COPYCONTENTS, val,
201                CURLFORM_END);
202 }
203 
204 /**********************************************************************
205   Add one integer entry to netfile post form
206 ***********************************************************************/
netfile_add_form_int(struct netfile_post * post,const char * name,const int val)207 void netfile_add_form_int(struct netfile_post *post,
208                           const char *name, const int val)
209 {
210   char buf[50];
211 
212   fc_snprintf(buf, sizeof(buf), "%d", val);
213   netfile_add_form_str(post, name, buf);
214 }
215 
216 /**********************************************************************
217   Free netfile_post resources
218 ***********************************************************************/
netfile_close_post(struct netfile_post * post)219 void netfile_close_post(struct netfile_post *post)
220 {
221   curl_formfree(post->first);
222   FC_FREE(post);
223 }
224 
225 /**********************************************************************
226   Dummy write callback used only to make sure curl's default write
227   function does not get used as we don't want reply to stdout
228 ***********************************************************************/
dummy_write(void * buffer,size_t size,size_t nmemb,void * userp)229 static size_t dummy_write(void *buffer, size_t size, size_t nmemb, void *userp)
230 {
231   return size * nmemb;
232 }
233 
234 /**********************************************************************
235   Send HTTP POST
236 ***********************************************************************/
netfile_send_post(const char * URL,struct netfile_post * post,FILE * reply_fp,struct netfile_write_cb_data * mem_data,const char * addr)237 bool netfile_send_post(const char *URL, struct netfile_post *post,
238                        FILE *reply_fp, struct netfile_write_cb_data *mem_data,
239                        const char *addr)
240 {
241   CURLcode curlret;
242   long http_resp;
243   struct curl_slist *headers = NULL;
244   static CURL *handle;
245 
246   handle = netfile_init_handle();
247 
248   headers = curl_slist_append(headers,"User-Agent: Freeciv/" VERSION_STRING);
249 
250   curl_easy_setopt(handle, CURLOPT_URL, URL);
251   curl_easy_setopt(handle, CURLOPT_HTTPPOST, post->first);
252   if (mem_data != NULL) {
253     mem_data->mem = NULL;
254     mem_data->size = 0;
255     curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, netfile_memwrite_cb);
256     curl_easy_setopt(handle, CURLOPT_WRITEDATA, mem_data);
257   } else if (reply_fp == NULL) {
258     curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, dummy_write);
259   } else {
260     curl_easy_setopt(handle, CURLOPT_WRITEDATA, reply_fp);
261   }
262   if (addr != NULL) {
263     curl_easy_setopt(handle, CURLOPT_INTERFACE, addr);
264   }
265   curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
266 
267   curlret = curl_easy_perform(handle);
268 
269   curl_slist_free_all(headers);
270 
271   if (curlret != CURLE_OK) {
272     return FALSE;
273   }
274 
275   curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &http_resp);
276 
277   if (http_resp != 200) {
278     return FALSE;
279   }
280 
281   return TRUE;
282 }
283