FFmpeg
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"
27 #include "libavutil/getenv_utf8.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 
40 typedef 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 
195 err:
196  if (gateway_file)
197  fclose(gateway_file);
198 
199  return ret;
200 }
201 
202 static 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 
306 err:
307  av_free(fulluri);
308  return ret;
309 }
310 
311 static 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 
317 static int64_t ipfs_seek(URLContext *h, int64_t pos, int whence)
318 {
319  IPFSGatewayContext *c = h->priv_data;
320  return ffurl_seek(c->inner, pos, whence);
321 }
322 
323 static int ipfs_close(URLContext *h)
324 {
325  IPFSGatewayContext *c = h->priv_data;
326  return ffurl_closep(&c->inner);
327 }
328 
329 #define OFFSET(x) offsetof(IPFSGatewayContext, x)
330 
331 static 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 };
flags
const SwsFlags flags[]
Definition: swscale.c:85
ffurl_seek
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:222
AV_LOG_WARNING
#define AV_LOG_WARNING
Something somehow does not look correct.
Definition: log.h:216
AVERROR
Filter the word “frame” indicates either a video frame or a group of audio as stored in an AVFrame structure Format for each input and each output the list of supported formats For video that means pixel format For audio that means channel sample they are references to shared objects When the negotiation mechanism computes the intersection of the formats supported at each end of a all references to both lists are replaced with a reference to the intersection And when a single format is eventually chosen for a link amongst the remaining all references to the list are updated That means that if a filter requires that its input and output have the same format amongst a supported all it has to do is use a reference to the same list of formats query_formats can leave some formats unset and return AVERROR(EAGAIN) to cause the negotiation mechanism toagain later. That can be used by filters with complex requirements to use the format negotiated on one link to set the formats supported on another. Frame references ownership and permissions
opt.h
int64_t
long long int64_t
Definition: coverity.c:34
av_asprintf
char * av_asprintf(const char *fmt,...)
Definition: avstring.c:115
IPFSGatewayContext
Definition: ipfsgateway.c:40
AVOption
AVOption.
Definition: opt.h:428
freeenv_utf8
static void freeenv_utf8(char *var)
Definition: getenv_utf8.h:72
AVDictionary
Definition: dict.c:32
URLProtocol
Definition: url.h:51
os_support.h
AV_LOG_ERROR
#define AV_LOG_ERROR
Something went wrong and cannot losslessly be recovered.
Definition: log.h:210
ffurl_open_whitelist
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:368
ff_ipns_gateway_protocol
const URLProtocol ff_ipns_gateway_protocol
Definition: ipfsgateway.c:353
IPFSGatewayContext::gateway_buffer
char gateway_buffer[PATH_MAX]
Definition: ipfsgateway.c:48
AV_LOG_DEBUG
#define AV_LOG_DEBUG
Stuff which is only useful for libav* developers.
Definition: log.h:231
av_stristart
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
file_open.h
ipfs_seek
static int64_t ipfs_seek(URLContext *h, int64_t pos, int whence)
Definition: ipfsgateway.c:317
LIBAVUTIL_VERSION_INT
#define LIBAVUTIL_VERSION_INT
Definition: version.h:85
AVClass
Describe the class of an AVClass context structure.
Definition: log.h:76
NULL
#define NULL
Definition: coverity.c:32
OFFSET
#define OFFSET(x)
Definition: ipfsgateway.c:329
PATH_MAX
#define PATH_MAX
Definition: ipfsgateway.c:37
IPFSGatewayContext::gateway
char * gateway
Definition: ipfsgateway.c:44
av_default_item_name
const char * av_default_item_name(void *ptr)
Return the context name.
Definition: log.c:242
getenv_utf8
static char * getenv_utf8(const char *varname)
Definition: getenv_utf8.h:67
options
Definition: swscale.c:50
c
Undefined Behavior In the C some operations are like signed integer dereferencing freed accessing outside allocated Undefined Behavior must not occur in a C it is not safe even if the output of undefined operations is unused The unsafety may seem nit picking but Optimizing compilers have in fact optimized code on the assumption that no undefined Behavior occurs Optimizing code based on wrong assumptions can and has in some cases lead to effects beyond the output of computations The signed integer overflow problem in speed critical code Code which is highly optimized and works with signed integers sometimes has the problem that often the output of the computation does not c
Definition: undefined.txt:32
ipfs_gateway_context_class
static const AVClass ipfs_gateway_context_class
Definition: ipfsgateway.c:336
error.h
size
int size
Definition: twinvq_data.h:10344
URLProtocol::name
const char * name
Definition: url.h:52
getenv_utf8.h
AV_LOG_INFO
#define AV_LOG_INFO
Standard information.
Definition: log.h:221
URLContext
Definition: url.h:35
ff_ipfs_gateway_protocol
const URLProtocol ff_ipfs_gateway_protocol
Definition: ipfsgateway.c:343
url.h
avpriv_fopen_utf8
FILE * avpriv_fopen_utf8(const char *path, const char *mode)
Open a file using a UTF-8 filename.
Definition: file_open.c:160
translate_ipfs_to_http
static int translate_ipfs_to_http(URLContext *h, const char *uri, int flags, AVDictionary **options)
Definition: ipfsgateway.c:202
ffurl_closep
int ffurl_closep(URLContext **hh)
Close the resource accessed by the URLContext h, and free the memory used by it.
Definition: avio.c:594
ret
ret
Definition: filter_design.txt:187
options
static const AVOption options[]
Definition: ipfsgateway.c:331
AVClass::class_name
const char * class_name
The name of the class; usually it is the same name as the context structure type to which the AVClass...
Definition: log.h:81
pos
unsigned int pos
Definition: spdifenc.c:414
IPFSGatewayContext::inner
URLContext * inner
Definition: ipfsgateway.c:42
AV_OPT_FLAG_DECODING_PARAM
#define AV_OPT_FLAG_DECODING_PARAM
A generic parameter which can be set by the user for demuxing or decoding.
Definition: opt.h:355
mem.h
av_free
#define av_free(p)
Definition: tableprint_vlc.h:34
av_log
#define av_log(a,...)
Definition: tableprint_vlc.h:27
h
h
Definition: vp9dsp_template.c:2070
avstring.h
ipfs_read
static int ipfs_read(URLContext *h, unsigned char *buf, int size)
Definition: ipfsgateway.c:311
AV_OPT_TYPE_STRING
@ 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
ipfs_close
static int ipfs_close(URLContext *h)
Definition: ipfsgateway.c:323
snprintf
#define snprintf
Definition: snprintf.h:34
populate_ipfs_gateway
static int populate_ipfs_gateway(URLContext *h)
Definition: ipfsgateway.c:55
ffurl_read
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:181