FFmpeg
Loading...
Searching...
No Matches
ipfsgateway.c
Go to the documentation of this file.
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
22#include <string.h>
23
24#include "libavutil/avstring.h"
25#include "libavutil/error.h"
26#include "libavutil/file_open.h"
28#include "libavutil/mem.h"
29#include "libavutil/opt.h"
30#include <sys/stat.h>
31#include "os_support.h"
32#include "url.h"
33
34// Define the posix PATH_MAX if not there already.
35// This fixes a compile issue for MSVC.
36#ifndef PATH_MAX
37#define PATH_MAX 4096
38#endif
39
40typedef struct IPFSGatewayContext {
41 AVClass *class;
43 // Is filled by the -gateway argument and not changed after.
44 char *gateway;
45 // If the above gateway is non null, it will be copied into this buffer.
46 // Else this buffer will contain the auto detected gateway.
47 // In either case, the gateway to use will be in this buffer.
50
51// A best-effort way to find the IPFS gateway.
52// Only the most appropriate gateway is set. It's not actually requested
53// (http call) to prevent a potential slowdown in startup. A potential timeout
54// is handled by the HTTP protocol.
56{
57 IPFSGatewayContext *c = h->priv_data;
58 char ipfs_full_data_folder[PATH_MAX];
59 char ipfs_gateway_file[PATH_MAX];
60 struct stat st;
61 int stat_ret = 0;
62 int ret = AVERROR(EINVAL);
63 FILE *gateway_file = NULL;
64 char *env_ipfs_gateway, *env_ipfs_path;
65
66 // Test $IPFS_GATEWAY.
67 env_ipfs_gateway = getenv_utf8("IPFS_GATEWAY");
68 if (env_ipfs_gateway != NULL) {
69 int printed = snprintf(c->gateway_buffer, sizeof(c->gateway_buffer),
70 "%s", env_ipfs_gateway);
71 freeenv_utf8(env_ipfs_gateway);
72 if (printed >= sizeof(c->gateway_buffer)) {
74 "The IPFS_GATEWAY environment variable "
75 "exceeds the maximum length. "
76 "We allow a max of %zu characters\n",
77 sizeof(c->gateway_buffer));
78 ret = AVERROR(EINVAL);
79 goto err;
80 }
81
82 ret = 1;
83 goto err;
84 } else
85 av_log(h, AV_LOG_DEBUG, "$IPFS_GATEWAY is empty.\n");
86
87 // We need to know the IPFS folder to - eventually - read the contents of
88 // the "gateway" file which would tell us the gateway to use.
89 env_ipfs_path = getenv_utf8("IPFS_PATH");
90 if (env_ipfs_path == NULL) {
91 int printed;
92 char *env_home = getenv_utf8("HOME");
93
94 av_log(h, AV_LOG_DEBUG, "$IPFS_PATH is empty.\n");
95
96 // Try via the home folder.
97 if (env_home == NULL) {
98 av_log(h, AV_LOG_WARNING, "$HOME appears to be empty.\n");
99 ret = AVERROR(EINVAL);
100 goto err;
101 }
102
103 // Verify the composed path fits.
104 printed = snprintf(
105 ipfs_full_data_folder, sizeof(ipfs_full_data_folder),
106 "%s/.ipfs/", env_home);
107 freeenv_utf8(env_home);
108 if (printed >= sizeof(ipfs_full_data_folder)) {
110 "The IPFS data path exceeds the "
111 "max path length (%zu)\n",
112 sizeof(ipfs_full_data_folder));
113 ret = AVERROR(EINVAL);
114 goto err;
115 }
116
117 // Stat the folder.
118 // It should exist in a default IPFS setup when run as local user.
119 stat_ret = stat(ipfs_full_data_folder, &st);
120
121 if (stat_ret < 0) {
123 "Unable to find IPFS folder. We tried:\n"
124 "- $IPFS_PATH, which was empty.\n"
125 "- $HOME/.ipfs (full uri: %s) which doesn't exist.\n",
126 ipfs_full_data_folder);
127 ret = AVERROR(ENOENT);
128 goto err;
129 }
130 } else {
131 int printed = snprintf(
132 ipfs_full_data_folder, sizeof(ipfs_full_data_folder),
133 "%s", env_ipfs_path);
134 freeenv_utf8(env_ipfs_path);
135 if (printed >= sizeof(ipfs_full_data_folder)) {
137 "The IPFS_PATH environment variable "
138 "exceeds the maximum length. "
139 "We allow a max of %zu characters\n",
140 sizeof(c->gateway_buffer));
141 ret = AVERROR(EINVAL);
142 goto err;
143 }
144 }
145
146 // Copy the fully composed gateway path into ipfs_gateway_file.
147 if (snprintf(ipfs_gateway_file, sizeof(ipfs_gateway_file), "%sgateway",
148 ipfs_full_data_folder)
149 >= sizeof(ipfs_gateway_file)) {
151 "The IPFS gateway file path exceeds "
152 "the max path length (%zu)\n",
153 sizeof(ipfs_gateway_file));
154 ret = AVERROR(ENOENT);
155 goto err;
156 }
157
158 // Get the contents of the gateway file.
159 gateway_file = avpriv_fopen_utf8(ipfs_gateway_file, "r");
160 if (!gateway_file) {
162 "The IPFS gateway file (full uri: %s) doesn't exist. "
163 "Is the gateway enabled?\n",
164 ipfs_gateway_file);
165 ret = AVERROR(ENOENT);
166 goto err;
167 }
168
169 // Read a single line (fgets stops at new line mark).
170 if (!fgets(c->gateway_buffer, sizeof(c->gateway_buffer) - 1, gateway_file)) {
171 av_log(h, AV_LOG_WARNING, "Unable to read from file (full uri: %s).\n",
172 ipfs_gateway_file);
173 ret = AVERROR(ENOENT);
174 goto err;
175 }
176
177 // Replace first occurrence of end of line with \0
178 c->gateway_buffer[strcspn(c->gateway_buffer, "\r\n")] = 0;
179
180 // If strlen finds anything longer then 0 characters then we have a
181 // potential gateway url.
182 if (*c->gateway_buffer == '\0') {
184 "The IPFS gateway file (full uri: %s) appears to be empty. "
185 "Is the gateway started?\n",
186 ipfs_gateway_file);
187 ret = AVERROR(EILSEQ);
188 goto err;
189 } else {
190 // We're done, the c->gateway_buffer has something that looks valid.
191 ret = 1;
192 goto err;
193 }
194
195err:
196 if (gateway_file)
197 fclose(gateway_file);
198
199 return ret;
200}
201
202static int translate_ipfs_to_http(URLContext *h, const char *uri, int flags, AVDictionary **options)
203{
204 const char *ipfs_cid;
205 char *fulluri = NULL;
206 int ret;
207 IPFSGatewayContext *c = h->priv_data;
208
209 // Test for ipfs://, ipfs:, ipns:// and ipns:. This prefix is stripped from
210 // the string leaving just the CID in ipfs_cid.
211 int is_ipfs = av_stristart(uri, "ipfs://", &ipfs_cid);
212 int is_ipns = av_stristart(uri, "ipns://", &ipfs_cid);
213
214 // We must have either ipns or ipfs.
215 if (!is_ipfs && !is_ipns) {
216 ret = AVERROR(EINVAL);
217 av_log(h, AV_LOG_WARNING, "Unsupported url %s\n", uri);
218 goto err;
219 }
220
221 // If the CID has a length greater then 0 then we assume we have a proper working one.
222 // It could still be wrong but in that case the gateway should save us and
223 // ruturn a 403 error. The http protocol handles this.
224 if (strlen(ipfs_cid) < 1) {
225 av_log(h, AV_LOG_WARNING, "A CID must be provided.\n");
226 ret = AVERROR(EILSEQ);
227 goto err;
228 }
229
230 // Populate c->gateway_buffer with whatever is in c->gateway
231 if (c->gateway != NULL) {
232 if (snprintf(c->gateway_buffer, sizeof(c->gateway_buffer), "%s",
233 c->gateway)
234 >= sizeof(c->gateway_buffer)) {
236 "The -gateway parameter is too long. "
237 "We allow a max of %zu characters\n",
238 sizeof(c->gateway_buffer));
239 ret = AVERROR(EINVAL);
240 goto err;
241 }
242 } else {
243 // Populate the IPFS gateway if we have any.
244 // If not, inform the user how to properly set one.
246
247 if (ret < 1) {
249 "IPFS does not appear to be running.\n\n"
250 "Installing IPFS locally is recommended to "
251 "improve performance and reliability, "
252 "and not share all your activity with a single IPFS gateway.\n"
253 "There are multiple options to define this gateway.\n"
254 "1. Call ffmpeg with a gateway param, "
255 "without a trailing slash: -gateway <url>.\n"
256 "2. Define an $IPFS_GATEWAY environment variable with the "
257 "full HTTP URL to the gateway "
258 "without trailing forward slash.\n"
259 "3. Define an $IPFS_PATH environment variable "
260 "and point it to the IPFS data path "
261 "- this is typically ~/.ipfs\n");
262 ret = AVERROR(EINVAL);
263 goto err;
264 }
265 }
266
267 // Test if the gateway starts with either http:// or https://
268 if (av_stristart(c->gateway_buffer, "http://", NULL) == 0
269 && av_stristart(c->gateway_buffer, "https://", NULL) == 0) {
271 "The gateway URL didn't start with http:// or "
272 "https:// and is therefore invalid.\n");
273 ret = AVERROR(EILSEQ);
274 goto err;
275 }
276
277 // Concatenate the url.
278 // This ends up with something like: http://localhost:8080/ipfs/Qm.....
279 // The format of "%s%s%s%s" is the following:
280 // 1st %s = The gateway.
281 // 2nd %s = If the gateway didn't end in a slash, add a "/". Otherwise it's an empty string
282 // 3rd %s = Either ipns/ or ipfs/.
283 // 4th %s = The IPFS CID (Qm..., bafy..., ...).
284 fulluri = av_asprintf("%s%s%s%s",
285 c->gateway_buffer,
286 (c->gateway_buffer[strlen(c->gateway_buffer) - 1] == '/') ? "" : "/",
287 (is_ipns) ? "ipns/" : "ipfs/",
288 ipfs_cid);
289
290 if (!fulluri) {
291 av_log(h, AV_LOG_ERROR, "Failed to compose the URL\n");
292 ret = AVERROR(ENOMEM);
293 goto err;
294 }
295
296 // Pass the URL back to FFmpeg's protocol handler.
297 ret = ffurl_open_whitelist(&c->inner, fulluri, flags,
298 &h->interrupt_callback, options,
299 h->protocol_whitelist,
300 h->protocol_blacklist, h);
301 if (ret < 0) {
302 av_log(h, AV_LOG_WARNING, "Unable to open resource: %s\n", fulluri);
303 goto err;
304 }
305
306err:
307 av_free(fulluri);
308 return ret;
309}
310
311static int ipfs_read(URLContext *h, unsigned char *buf, int size)
312{
313 IPFSGatewayContext *c = h->priv_data;
314 return ffurl_read(c->inner, buf, size);
315}
316
318{
319 IPFSGatewayContext *c = h->priv_data;
320 return ffurl_seek(c->inner, pos, whence);
321}
322
324{
325 IPFSGatewayContext *c = h->priv_data;
326 return ffurl_closep(&c->inner);
327}
328
329#define OFFSET(x) offsetof(IPFSGatewayContext, x)
330
331static const AVOption options[] = {
332 {"gateway", "The gateway to ask for IPFS data.", OFFSET(gateway), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, AV_OPT_FLAG_DECODING_PARAM},
333 {NULL},
334};
335
337 .class_name = "IPFS Gateway",
338 .item_name = av_default_item_name,
339 .option = options,
340 .version = LIBAVUTIL_VERSION_INT,
341};
342
344 .name = "ipfs",
345 .url_open2 = translate_ipfs_to_http,
346 .url_read = ipfs_read,
347 .url_seek = ipfs_seek,
348 .url_close = ipfs_close,
349 .priv_data_size = sizeof(IPFSGatewayContext),
350 .priv_data_class = &ipfs_gateway_context_class,
351};
352
354 .name = "ipns",
355 .url_open2 = translate_ipfs_to_http,
356 .url_read = ipfs_read,
357 .url_seek = ipfs_seek,
358 .url_close = ipfs_close,
359 .priv_data_size = sizeof(IPFSGatewayContext),
360 .priv_data_class = &ipfs_gateway_context_class,
361};
int ffurl_open_whitelist(URLContext **puc, const char *filename, int flags, const AVIOInterruptCB *int_cb, AVDictionary **options, const char *whitelist, const char *blacklist, URLContext *parent)
Create an URLContext for accessing to the resource indicated by url, and open it.
Definition avio.c:461
int ffurl_closep(URLContext **hh)
Close the resource accessed by the URLContext h, and free the memory used by it.
Definition avio.c:656
char * av_asprintf(const char *fmt,...)
Definition avstring.c:115
#define flags(name, subs,...)
Definition cbs_h264.c:74
#define NULL
Definition coverity.c:32
long long int64_t
Definition coverity.c:34
error code definitions
static char * getenv_utf8(const char *varname)
Definition getenv_utf8.h:67
static void freeenv_utf8(char *var)
Definition getenv_utf8.h:72
#define AV_OPT_FLAG_DECODING_PARAM
A generic parameter which can be set by the user for demuxing or decoding.
Definition opt.h:355
@ AV_OPT_TYPE_STRING
Underlying C type is a uint8_t* that is either NULL or points to a C string allocated with the av_mal...
Definition opt.h:275
#define AVERROR(e)
Definition error.h:45
#define AV_LOG_DEBUG
Stuff which is only useful for libav* developers.
Definition log.h:231
#define AV_LOG_WARNING
Something somehow does not look correct.
Definition log.h:216
#define AV_LOG_INFO
Standard information.
Definition log.h:221
#define AV_LOG_ERROR
Something went wrong and cannot losslessly be recovered.
Definition log.h:210
const char * av_default_item_name(void *ptr)
Return the context name.
Definition log.c:241
int av_stristart(const char *str, const char *pfx, const char **ptr)
Return non-zero if pfx is a prefix of str independent of case.
Definition avstring.c:47
#define LIBAVUTIL_VERSION_INT
Definition version.h:85
const URLProtocol ff_ipfs_gateway_protocol
const URLProtocol ff_ipns_gateway_protocol
static int ipfs_read(URLContext *h, unsigned char *buf, int size)
static int64_t ipfs_seek(URLContext *h, int64_t pos, int whence)
static int ipfs_close(URLContext *h)
static int translate_ipfs_to_http(URLContext *h, const char *uri, int flags, AVDictionary **options)
static const AVClass ipfs_gateway_context_class
#define OFFSET(x)
#define PATH_MAX
Definition ipfsgateway.c:37
static int populate_ipfs_gateway(URLContext *h)
Definition ipfsgateway.c:55
FILE * avpriv_fopen_utf8(const char *path, const char *mode)
Open a file using a UTF-8 filename.
Definition file_open.c:160
Memory handling functions.
AVOptions.
miscellaneous OS support macros and functions.
#define snprintf
Definition snprintf.h:34
unsigned int pos
Definition spdifenc.c:431
Describe the class of an AVClass context structure.
Definition log.h:76
AVOption.
Definition opt.h:428
URLContext * inner
Definition ipfsgateway.c:42
char gateway_buffer[PATH_MAX]
Definition ipfsgateway.c:48
#define av_free(p)
#define av_log(a,...)
int size
unbuffered private I/O API
static int64_t ffurl_seek(URLContext *h, int64_t pos, int whence)
Change the position that will be used by the next read/write operation on the resource accessed by h.
Definition url.h:224
static int ffurl_read(URLContext *h, uint8_t *buf, int size)
Read up to size bytes from the resource accessed by h, and store the read bytes in buf.
Definition url.h:183
static double c[64]