xref: /curl/src/tool_ipfs.c (revision aba80430)
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9  *
10  * This software is licensed as described in the file COPYING, which
11  * you should have received as part of this distribution. The terms
12  * are also available at https://curl.se/docs/copyright.html.
13  *
14  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15  * copies of the Software, and permit persons to whom the Software is
16  * furnished to do so, under the terms of the COPYING file.
17  *
18  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19  * KIND, either express or implied.
20  *
21  * SPDX-License-Identifier: curl
22  *
23  ***************************************************************************/
24 #include "tool_setup.h"
25 
26 #define ENABLE_CURLX_PRINTF
27 /* use our own printf() functions */
28 #include "curlx.h"
29 #include "dynbuf.h"
30 
31 #include "tool_cfgable.h"
32 #include "tool_msgs.h"
33 #include "tool_ipfs.h"
34 
35 #include "memdebug.h" /* keep this as LAST include */
36 
37 /* ensure input ends in slash */
ensure_trailing_slash(char ** input)38 static CURLcode ensure_trailing_slash(char **input)
39 {
40   if(*input && **input) {
41     size_t len = strlen(*input);
42     if(((*input)[len - 1] != '/')) {
43       struct curlx_dynbuf dyn;
44       curlx_dyn_init(&dyn, len + 2);
45 
46       if(curlx_dyn_addn(&dyn, *input, len)) {
47         Curl_safefree(*input);
48         return CURLE_OUT_OF_MEMORY;
49       }
50 
51       Curl_safefree(*input);
52 
53       if(curlx_dyn_addn(&dyn, "/", 1))
54         return CURLE_OUT_OF_MEMORY;
55 
56       *input = curlx_dyn_ptr(&dyn);
57     }
58   }
59 
60   return CURLE_OK;
61 }
62 
ipfs_gateway(void)63 static char *ipfs_gateway(void)
64 {
65   char *ipfs_path = NULL;
66   char *gateway_composed_file_path = NULL;
67   FILE *gateway_file = NULL;
68   char *gateway = curl_getenv("IPFS_GATEWAY");
69 
70   /* Gateway is found from environment variable. */
71   if(gateway) {
72     if(ensure_trailing_slash(&gateway))
73       goto fail;
74     return gateway;
75   }
76 
77   /* Try to find the gateway in the IPFS data folder. */
78   ipfs_path = curl_getenv("IPFS_PATH");
79 
80   if(!ipfs_path) {
81     char *home = getenv("HOME");
82     if(home && *home)
83       ipfs_path = aprintf("%s/.ipfs/", home);
84     /* fallback to "~/.ipfs", as that's the default location. */
85   }
86 
87   if(!ipfs_path || ensure_trailing_slash(&ipfs_path))
88     goto fail;
89 
90   gateway_composed_file_path = aprintf("%sgateway", ipfs_path);
91 
92   if(!gateway_composed_file_path)
93     goto fail;
94 
95   gateway_file = fopen(gateway_composed_file_path, FOPEN_READTEXT);
96   Curl_safefree(gateway_composed_file_path);
97 
98   if(gateway_file) {
99     int c;
100     struct curlx_dynbuf dyn;
101     curlx_dyn_init(&dyn, MAX_GATEWAY_URL_LEN);
102 
103     /* get the first line of the gateway file, ignore the rest */
104     while((c = getc(gateway_file)) != EOF && c != '\n' && c != '\r') {
105       char c_char = (char)c;
106       if(curlx_dyn_addn(&dyn, &c_char, 1))
107         goto fail;
108     }
109 
110     fclose(gateway_file);
111     gateway_file = NULL;
112 
113     if(curlx_dyn_len(&dyn))
114       gateway = curlx_dyn_ptr(&dyn);
115 
116     if(gateway)
117       ensure_trailing_slash(&gateway);
118 
119     if(!gateway)
120       goto fail;
121 
122     Curl_safefree(ipfs_path);
123 
124     return gateway;
125   }
126 fail:
127   if(gateway_file)
128     fclose(gateway_file);
129   Curl_safefree(gateway);
130   Curl_safefree(ipfs_path);
131   return NULL;
132 }
133 
134 /*
135  * Rewrite ipfs://<cid> and ipns://<cid> to a HTTP(S)
136  * URL that can be handled by an IPFS gateway.
137  */
ipfs_url_rewrite(CURLU * uh,const char * protocol,char ** url,struct OperationConfig * config)138 CURLcode ipfs_url_rewrite(CURLU *uh, const char *protocol, char **url,
139                           struct OperationConfig *config)
140 {
141   CURLcode result = CURLE_URL_MALFORMAT;
142   CURLUcode getResult;
143   char *gateway = NULL;
144   char *gwhost = NULL;
145   char *gwpath = NULL;
146   char *gwquery = NULL;
147   char *gwscheme = NULL;
148   char *gwport = NULL;
149   char *inputpath = NULL;
150   char *cid = NULL;
151   char *pathbuffer = NULL;
152   char *cloneurl;
153   CURLU *gatewayurl = curl_url();
154 
155   if(!gatewayurl) {
156     result = CURLE_FAILED_INIT;
157     goto clean;
158   }
159 
160   getResult = curl_url_get(uh, CURLUPART_HOST, &cid, CURLU_URLDECODE);
161   if(getResult || !cid)
162     goto clean;
163 
164   /* We might have a --ipfs-gateway argument. Check it first and use it. Error
165    * if we do have something but if it's an invalid url.
166    */
167   if(config->ipfs_gateway) {
168     /* ensure the gateway ends in a trailing / */
169     if(ensure_trailing_slash(&config->ipfs_gateway) != CURLE_OK) {
170       result = CURLE_OUT_OF_MEMORY;
171       goto clean;
172     }
173 
174     if(!curl_url_set(gatewayurl, CURLUPART_URL, config->ipfs_gateway,
175                     CURLU_GUESS_SCHEME)) {
176       gateway = strdup(config->ipfs_gateway);
177       if(!gateway) {
178         result = CURLE_URL_MALFORMAT;
179         goto clean;
180       }
181 
182     }
183     else {
184       result = CURLE_BAD_FUNCTION_ARGUMENT;
185       goto clean;
186     }
187   }
188   else {
189     /* this is ensured to end in a trailing / within ipfs_gateway() */
190     gateway = ipfs_gateway();
191     if(!gateway) {
192       result = CURLE_FILE_COULDNT_READ_FILE;
193       goto clean;
194     }
195 
196     if(curl_url_set(gatewayurl, CURLUPART_URL, gateway, 0)) {
197       result = CURLE_URL_MALFORMAT;
198       goto clean;
199     }
200   }
201 
202   /* check for unsupported gateway parts */
203   if(curl_url_get(gatewayurl, CURLUPART_QUERY, &gwquery, 0)
204                   != CURLUE_NO_QUERY) {
205     result = CURLE_URL_MALFORMAT;
206     goto clean;
207   }
208 
209   /* get gateway parts */
210   if(curl_url_get(gatewayurl, CURLUPART_HOST,
211                   &gwhost, CURLU_URLDECODE)) {
212     goto clean;
213   }
214 
215   if(curl_url_get(gatewayurl, CURLUPART_SCHEME,
216                   &gwscheme, CURLU_URLDECODE)) {
217     goto clean;
218   }
219 
220   curl_url_get(gatewayurl, CURLUPART_PORT, &gwport, CURLU_URLDECODE);
221   curl_url_get(gatewayurl, CURLUPART_PATH, &gwpath, CURLU_URLDECODE);
222 
223   /* get the path from user input */
224   curl_url_get(uh, CURLUPART_PATH, &inputpath, CURLU_URLDECODE);
225   /* inputpath might be NULL or a valid pointer now */
226 
227   /* set gateway parts in input url */
228   if(curl_url_set(uh, CURLUPART_SCHEME, gwscheme, CURLU_URLENCODE) ||
229      curl_url_set(uh, CURLUPART_HOST, gwhost, CURLU_URLENCODE) ||
230      curl_url_set(uh, CURLUPART_PORT, gwport, CURLU_URLENCODE))
231     goto clean;
232 
233   /* if the input path is just a slash, clear it */
234   if(inputpath && (inputpath[0] == '/') && !inputpath[1])
235     *inputpath = '\0';
236 
237   /* ensure the gateway path ends with a trailing slash */
238   ensure_trailing_slash(&gwpath);
239 
240   pathbuffer = aprintf("%s%s/%s%s", gwpath, protocol, cid,
241                        inputpath ? inputpath : "");
242   if(!pathbuffer) {
243     goto clean;
244   }
245 
246   if(curl_url_set(uh, CURLUPART_PATH, pathbuffer, CURLU_URLENCODE)) {
247     goto clean;
248   }
249 
250   /* Free whatever it has now, rewriting is next */
251   Curl_safefree(*url);
252 
253   if(curl_url_get(uh, CURLUPART_URL, &cloneurl, CURLU_URLENCODE)) {
254     goto clean;
255   }
256   /* we need to strdup the URL so that we can call free() on it later */
257   *url = strdup(cloneurl);
258   curl_free(cloneurl);
259   if(!*url)
260     goto clean;
261 
262   result = CURLE_OK;
263 
264 clean:
265   free(gateway);
266   curl_free(gwhost);
267   curl_free(gwpath);
268   curl_free(gwquery);
269   curl_free(inputpath);
270   curl_free(gwscheme);
271   curl_free(gwport);
272   curl_free(cid);
273   curl_free(pathbuffer);
274   curl_url_cleanup(gatewayurl);
275   {
276     switch(result) {
277     case CURLE_URL_MALFORMAT:
278       helpf(tool_stderr, "malformed target URL");
279       break;
280     case CURLE_FILE_COULDNT_READ_FILE:
281       helpf(tool_stderr, "IPFS automatic gateway detection failed");
282       break;
283     case CURLE_BAD_FUNCTION_ARGUMENT:
284       helpf(tool_stderr, "--ipfs-gateway was given a malformed URL");
285       break;
286     default:
287       break;
288     }
289   }
290   return result;
291 }
292