1 /*
2 * IPFS and IPNS protocol support through IPFS Gateway.
3 * Copyright (c) 2022 Mark Gaiser
4 *
5 * This file is part of FFmpeg.
6 *
7 * FFmpeg is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * FFmpeg is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with FFmpeg; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 */
21
27 #include <sys/stat.h>
30
31 // Define the posix PATH_MAX if not there already.
32 // This fixes a compile issue for MSVC.
33 #ifndef PATH_MAX
35 #endif
36
40 // Is filled by the -gateway argument and not changed after.
42 // If the above gateway is non null, it will be copied into this buffer.
43 // Else this buffer will contain the auto detected gateway.
44 // In either case, the gateway to use will be in this buffer.
47
48 // A best-effort way to find the IPFS gateway.
49 // Only the most appropriate gateway is set. It's not actually requested
50 // (http call) to prevent a potential slowdown in startup. A potential timeout
51 // is handled by the HTTP protocol.
53 {
55 char ipfs_full_data_folder[
PATH_MAX];
57 struct stat st;
58 int stat_ret = 0;
60 FILE *gateway_file =
NULL;
61 char *env_ipfs_gateway, *env_ipfs_path;
62
63 // Test $IPFS_GATEWAY.
65 if (env_ipfs_gateway !=
NULL) {
66 int printed =
snprintf(
c->gateway_buffer,
sizeof(
c->gateway_buffer),
67 "%s", env_ipfs_gateway);
69 if (printed >=
sizeof(
c->gateway_buffer)) {
71 "The IPFS_GATEWAY environment variable "
72 "exceeds the maximum length. "
73 "We allow a max of %zu characters\n",
74 sizeof(
c->gateway_buffer));
76 goto err;
77 }
78
80 goto err;
81 } else
83
84 // We need to know the IPFS folder to - eventually - read the contents of
85 // the "gateway" file which would tell us the gateway to use.
87 if (env_ipfs_path ==
NULL) {
88 int printed;
90
92
93 // Try via the home folder.
94 if (env_home ==
NULL) {
97 goto err;
98 }
99
100 // Verify the composed path fits.
102 ipfs_full_data_folder, sizeof(ipfs_full_data_folder),
103 "%s/.ipfs/", env_home);
105 if (printed >= sizeof(ipfs_full_data_folder)) {
107 "The IPFS data path exceeds the "
108 "max path length (%zu)\n",
109 sizeof(ipfs_full_data_folder));
111 goto err;
112 }
113
114 // Stat the folder.
115 // It should exist in a default IPFS setup when run as local user.
116 stat_ret = stat(ipfs_full_data_folder, &st);
117
118 if (stat_ret < 0) {
120 "Unable to find IPFS folder. We tried:\n"
121 "- $IPFS_PATH, which was empty.\n"
122 "- $HOME/.ipfs (full uri: %s) which doesn't exist.\n",
123 ipfs_full_data_folder);
125 goto err;
126 }
127 } else {
129 ipfs_full_data_folder, sizeof(ipfs_full_data_folder),
130 "%s", env_ipfs_path);
132 if (printed >= sizeof(ipfs_full_data_folder)) {
134 "The IPFS_PATH environment variable "
135 "exceeds the maximum length. "
136 "We allow a max of %zu characters\n",
137 sizeof(
c->gateway_buffer));
139 goto err;
140 }
141 }
142
143 // Copy the fully composed gateway path into ipfs_gateway_file.
144 if (
snprintf(ipfs_gateway_file,
sizeof(ipfs_gateway_file),
"%sgateway",
145 ipfs_full_data_folder)
146 >= sizeof(ipfs_gateway_file)) {
148 "The IPFS gateway file path exceeds "
149 "the max path length (%zu)\n",
150 sizeof(ipfs_gateway_file));
152 goto err;
153 }
154
155 // Get the contents of the gateway file.
157 if (!gateway_file) {
159 "The IPFS gateway file (full uri: %s) doesn't exist. "
160 "Is the gateway enabled?\n",
161 ipfs_gateway_file);
163 goto err;
164 }
165
166 // Read a single line (fgets stops at new line mark).
167 if (!fgets(
c->gateway_buffer,
sizeof(
c->gateway_buffer) - 1, gateway_file)) {
169 ipfs_gateway_file);
171 goto err;
172 }
173
174 // Replace first occurrence of end of line with 0円
175 c->gateway_buffer[strcspn(
c->gateway_buffer,
"\r\n")] = 0;
176
177 // If strlen finds anything longer then 0 characters then we have a
178 // potential gateway url.
179 if (*
c->gateway_buffer ==
'0円') {
181 "The IPFS gateway file (full uri: %s) appears to be empty. "
182 "Is the gateway started?\n",
183 ipfs_gateway_file);
185 goto err;
186 } else {
187 // We're done, the c->gateway_buffer has something that looks valid.
189 goto err;
190 }
191
192 err:
193 if (gateway_file)
194 fclose(gateway_file);
195
197 }
198
200 {
201 const char *ipfs_cid;
202 char *fulluri =
NULL;
205
206 // Test for ipfs://, ipfs:, ipns:// and ipns:. This prefix is stripped from
207 // the string leaving just the CID in ipfs_cid.
210
211 // We must have either ipns or ipfs.
212 if (!is_ipfs && !is_ipns) {
215 goto err;
216 }
217
218 // If the CID has a length greater then 0 then we assume we have a proper working one.
219 // It could still be wrong but in that case the gateway should save us and
220 // ruturn a 403 error. The http protocol handles this.
221 if (strlen(ipfs_cid) < 1) {
224 goto err;
225 }
226
227 // Populate c->gateway_buffer with whatever is in c->gateway
228 if (
c->gateway !=
NULL) {
229 if (
snprintf(
c->gateway_buffer,
sizeof(
c->gateway_buffer),
"%s",
231 >=
sizeof(
c->gateway_buffer)) {
233 "The -gateway parameter is too long. "
234 "We allow a max of %zu characters\n",
235 sizeof(
c->gateway_buffer));
237 goto err;
238 }
239 } else {
240 // Populate the IPFS gateway if we have any.
241 // If not, inform the user how to properly set one.
243
246 "IPFS does not appear to be running.\n\n"
247 "Installing IPFS locally is recommended to "
248 "improve performance and reliability, "
249 "and not share all your activity with a single IPFS gateway.\n"
250 "There are multiple options to define this gateway.\n"
251 "1. Call ffmpeg with a gateway param, "
252 "without a trailing slash: -gateway <url>.\n"
253 "2. Define an $IPFS_GATEWAY environment variable with the "
254 "full HTTP URL to the gateway "
255 "without trailing forward slash.\n"
256 "3. Define an $IPFS_PATH environment variable "
257 "and point it to the IPFS data path "
258 "- this is typically ~/.ipfs\n");
260 goto err;
261 }
262 }
263
264 // Test if the gateway starts with either http:// or https://
268 "The gateway URL didn't start with http:// or "
269 "https:// and is therefore invalid.\n");
271 goto err;
272 }
273
274 // Concatenate the url.
275 // This ends up with something like: http://localhost:8080/ipfs/Qm.....
276 // The format of "%s%s%s%s" is the following:
277 // 1st %s = The gateway.
278 // 2nd %s = If the gateway didn't end in a slash, add a "/". Otherwise it's an empty string
279 // 3rd %s = Either ipns/ or ipfs/.
280 // 4th %s = The IPFS CID (Qm..., bafy..., ...).
283 (
c->gateway_buffer[strlen(
c->gateway_buffer) - 1] ==
'/') ?
"" :
"/",
284 (is_ipns) ? "ipns/" : "ipfs/",
285 ipfs_cid);
286
287 if (!fulluri) {
290 goto err;
291 }
292
293 // Pass the URL back to FFmpeg's protocol handler.
296 h->protocol_whitelist,
297 h->protocol_blacklist,
h);
300 goto err;
301 }
302
303 err:
306 }
307
309 {
312 }
313
315 {
318 }
319
321 {
324 }
325
326 #define OFFSET(x) offsetof(IPFSGatewayContext, x)
327
331 };
332
338 };
339
348 };
349
358 };