curl: add support for the IPFS protocols:

- ipfs://<cid>
- ipns://<cid>

This allows you tu use ipfs in curl like:
curl ipfs://<cid>
and
curl ipns://<cid>

For more information consult the readme at:
https://curl.se/docs/ipfs.html

Closes #8805
This commit is contained in:
Mark Gaiser 2022-03-27 01:31:58 +01:00 committed by Daniel Stenberg
parent 5595e33617
commit 65b563a96a
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
21 changed files with 678 additions and 31 deletions

View file

@ -109,6 +109,7 @@ static void free_config_fields(struct OperationConfig *config)
config->url_get = NULL;
config->url_out = NULL;
Curl_safefree(config->ipfs_gateway);
Curl_safefree(config->doh_url);
Curl_safefree(config->cipher_list);
Curl_safefree(config->proxy_cipher_list);

View file

@ -128,6 +128,7 @@ struct OperationConfig {
struct getout *url_get; /* point to the node to fill in URL */
struct getout *url_out; /* point to the node to fill in outfile */
struct getout *url_ul; /* point to the node to fill in upload */
char *ipfs_gateway;
char *doh_url;
char *cipher_list;
char *proxy_cipher_list;

View file

@ -116,6 +116,7 @@ static const struct LongShort aliases[]= {
{"*r", "create-dirs", ARG_BOOL},
{"*R", "create-file-mode", ARG_STRING},
{"*s", "max-redirs", ARG_STRING},
{"*S", "ipfs-gateway", ARG_STRING},
{"*t", "proxy-ntlm", ARG_BOOL},
{"*u", "crlf", ARG_BOOL},
{"*v", "stderr", ARG_FILENAME},
@ -1137,6 +1138,10 @@ ParameterError getparameter(const char *flag, /* f or -long-flag */
err = PARAM_BAD_NUMERIC;
break;
case 'S': /* ipfs gateway url */
GetStr(&config->ipfs_gateway, nextarg);
break;
case 't': /* --proxy-ntlm */
if(!feature_ntlm) {
err = PARAM_LIBCURL_DOESNT_SUPPORT;

View file

@ -54,20 +54,21 @@ struct helptxt {
#define CURLHELP_HTTP 1u << 7u
#define CURLHELP_IMAP 1u << 8u
#define CURLHELP_IMPORTANT 1u << 9u
#define CURLHELP_MISC 1u << 10u
#define CURLHELP_OUTPUT 1u << 11u
#define CURLHELP_POP3 1u << 12u
#define CURLHELP_POST 1u << 13u
#define CURLHELP_PROXY 1u << 14u
#define CURLHELP_SCP 1u << 15u
#define CURLHELP_SFTP 1u << 16u
#define CURLHELP_SMTP 1u << 17u
#define CURLHELP_SSH 1u << 18u
#define CURLHELP_TELNET 1u << 19u
#define CURLHELP_TFTP 1u << 20u
#define CURLHELP_TLS 1u << 21u
#define CURLHELP_UPLOAD 1u << 22u
#define CURLHELP_VERBOSE 1u << 23u
#define CURLHELP_IPFS 1u << 10u
#define CURLHELP_MISC 1u << 11u
#define CURLHELP_OUTPUT 1u << 12u
#define CURLHELP_POP3 1u << 13u
#define CURLHELP_POST 1u << 14u
#define CURLHELP_PROXY 1u << 15u
#define CURLHELP_SCP 1u << 16u
#define CURLHELP_SFTP 1u << 17u
#define CURLHELP_SMTP 1u << 18u
#define CURLHELP_SSH 1u << 19u
#define CURLHELP_TELNET 1u << 20u
#define CURLHELP_TFTP 1u << 21u
#define CURLHELP_TLS 1u << 22u
#define CURLHELP_UPLOAD 1u << 23u
#define CURLHELP_VERBOSE 1u << 24u
extern const struct helptxt helptext[];

View file

@ -51,6 +51,8 @@ const char *proto_rtsp = NULL;
const char *proto_scp = NULL;
const char *proto_sftp = NULL;
const char *proto_tftp = NULL;
const char *proto_ipfs = "ipfs";
const char *proto_ipns = "ipns";
static struct proto_name_tokenp {
const char *proto_name;

View file

@ -44,6 +44,8 @@ extern const char *proto_rtsp;
extern const char *proto_scp;
extern const char *proto_sftp;
extern const char *proto_tftp;
extern const char *proto_ipfs;
extern const char *proto_ipns;
extern bool feature_altsvc;
extern bool feature_brotli;

View file

@ -308,6 +308,9 @@ const struct helptxt helptext[] = {
CURLHELP_CONNECTION | CURLHELP_DNS},
{"-6, --ipv6",
"Resolve names to IPv6 addresses",
CURLHELP_IPFS},
{"--ipfs-gateway",
"Defines the gateway to handle IPFS protocol requests",
CURLHELP_CONNECTION | CURLHELP_DNS},
{" --json <data>",
"HTTP POST JSON",

View file

@ -45,8 +45,6 @@
# include <proto/dos.h>
#endif
#include "strcase.h"
#define ENABLE_CURLX_PRINTF
/* use our own printf() functions */
#include "curlx.h"
@ -699,30 +697,245 @@ noretry:
return result;
}
static char *ipfs_gateway(void)
{
char *gateway = NULL;
char *ipfs_path = NULL;
char *gateway_composed_file_path = NULL;
FILE *gateway_file = NULL;
gateway = curlx_getenv("IPFS_GATEWAY");
/* Gateway is found from environment variable. */
if(gateway && strlen(gateway)) {
char *composed_gateway = NULL;
bool add_slash = (gateway[strlen(gateway) - 1] == '/') ? FALSE : TRUE;
composed_gateway = aprintf("%s%s", gateway, (add_slash) ? "/" : "");
Curl_safefree(gateway);
gateway = aprintf("%s", composed_gateway);
Curl_safefree(composed_gateway);
return gateway;
}
/* Try to find the gateway in the IPFS data folder. */
ipfs_path = curlx_getenv("IPFS_PATH");
if(!ipfs_path) {
char *home = NULL;
home = curlx_getenv("HOME");
/* Empty path, fallback to "~/.ipfs", as that's the default location. */
ipfs_path = aprintf("%s/.ipfs/", home);
Curl_safefree(home);
}
if(!ipfs_path) {
Curl_safefree(gateway);
Curl_safefree(ipfs_path);
return NULL;
}
gateway_composed_file_path = aprintf("%sgateway", ipfs_path);
if(!gateway_composed_file_path) {
Curl_safefree(gateway);
Curl_safefree(ipfs_path);
return NULL;
}
gateway_file = fopen(gateway_composed_file_path, FOPEN_READTEXT);
Curl_safefree(gateway_composed_file_path);
if(gateway_file) {
char *gateway_buffer = NULL;
if((PARAM_OK == file2string(&gateway_buffer, gateway_file)) &&
gateway_buffer) {
bool add_slash = (gateway_buffer[strlen(gateway_buffer) - 1] == '/')
? FALSE
: TRUE;
gateway = aprintf("%s%s", gateway_buffer, (add_slash) ? "/" : "");
Curl_safefree(gateway_buffer);
}
if(gateway_file)
fclose(gateway_file);
if(!gateway) {
Curl_safefree(gateway);
Curl_safefree(ipfs_path);
return NULL;
}
Curl_safefree(ipfs_path);
return gateway;
}
Curl_safefree(gateway);
Curl_safefree(ipfs_path);
return NULL;
}
/*
* Rewrite ipfs://<cid> and ipns://<cid> to a HTTP(S)
* URL that can be handled by an IPFS gateway.
*/
static CURLcode ipfs_url_rewrite(CURLU *uh, const char *protocol, char **url,
struct OperationConfig *config)
{
CURLcode result = CURLE_URL_MALFORMAT;
CURLUcode urlGetResult;
char *gateway = NULL;
char *cid = NULL;
char *pathbuffer = NULL;
CURLU *ipfsurl = curl_url();
if(!ipfsurl) {
result = CURLE_FAILED_INIT;
goto clean;
}
urlGetResult = curl_url_get(uh, CURLUPART_HOST, &cid, CURLU_URLDECODE);
if(urlGetResult) {
goto clean;
}
if(!cid) {
goto clean;
}
/* We might have a --ipfs-gateway argument. Check it first and use it. Error
* if we do have something but if it's an invalid url.
*/
if(config->ipfs_gateway) {
if(curl_url_set(ipfsurl, CURLUPART_URL, config->ipfs_gateway,
CURLU_GUESS_SCHEME)
== CURLUE_OK) {
gateway = strdup(config->ipfs_gateway);
if(!gateway) {
result = CURLE_URL_MALFORMAT;
goto clean;
}
}
else {
result = CURLE_BAD_FUNCTION_ARGUMENT;
goto clean;
}
}
else {
gateway = ipfs_gateway();
if(!gateway) {
result = CURLE_FILE_COULDNT_READ_FILE;
goto clean;
}
if(curl_url_set(ipfsurl, CURLUPART_URL, gateway, CURLU_GUESS_SCHEME
| CURLU_NON_SUPPORT_SCHEME) != CURLUE_OK) {
goto clean;
}
}
pathbuffer = aprintf("%s/%s", protocol, cid);
if(!pathbuffer) {
goto clean;
}
if(curl_url_set(ipfsurl, CURLUPART_PATH, pathbuffer, CURLU_URLENCODE)
!= CURLUE_OK) {
goto clean;
}
/* Free whatever it has now, rewriting is next */
Curl_safefree(*url);
if(curl_url_get(ipfsurl, CURLUPART_URL, url, CURLU_URLENCODE)
!= CURLUE_OK) {
goto clean;
}
result = CURLE_OK;
clean:
curl_free(gateway);
curl_free(cid);
curl_free(pathbuffer);
if(ipfsurl) {
curl_url_cleanup(ipfsurl);
}
switch(result) {
case CURLE_URL_MALFORMAT:
helpf(stderr, "malformed URL. Visit https://curl.se/"
"docs/ipfs.html#Gateway-file-and-"
"environment-variable for more "
"information for more information");
break;
case CURLE_FILE_COULDNT_READ_FILE:
helpf(stderr, "IPFS automatic gateway detection "
"failure. Visit https://curl.se/docs/"
"ipfs.html#Malformed-gateway-URL for "
"more information for more "
"information");
break;
case CURLE_BAD_FUNCTION_ARGUMENT:
helpf(stderr, "--ipfs-gateway argument results in "
"malformed URL. Visit https://curl.se/"
"docs/ipfs.html#Malformed-gateway-URL "
"for more information for more "
"information");
break;
default:
break;
}
return result;
}
/*
* Return the protocol token for the scheme used in the given URL
*/
static const char *url_proto(char *url)
static CURLcode url_proto(char **url,
struct OperationConfig *config,
char **scheme)
{
CURLcode result = CURLE_OK;
CURLU *uh = curl_url();
const char *proto = NULL;
*scheme = NULL;
if(uh) {
if(url) {
if(!curl_url_set(uh, CURLUPART_URL, url,
CURLU_GUESS_SCHEME | CURLU_NON_SUPPORT_SCHEME)) {
char *schemep = NULL;
if(!curl_url_get(uh, CURLUPART_SCHEME, &schemep,
CURLU_DEFAULT_SCHEME) &&
schemep) {
proto = proto_token(schemep);
curl_free(schemep);
if(*url) {
char *schemep = NULL;
if(!curl_url_set(uh, CURLUPART_URL, *url,
CURLU_GUESS_SCHEME | CURLU_NON_SUPPORT_SCHEME) &&
!curl_url_get(uh, CURLUPART_SCHEME, &schemep,
CURLU_DEFAULT_SCHEME)) {
if(curl_strequal(schemep, proto_ipfs) ||
curl_strequal(schemep, proto_ipns)) {
result = ipfs_url_rewrite(uh, schemep, url, config);
/* short-circuit proto_token, we know it's ipfs or ipns */
if(curl_strequal(schemep, proto_ipfs))
proto = proto_ipfs;
else if(curl_strequal(schemep, proto_ipns))
proto = proto_ipns;
}
else
proto = proto_token(schemep);
curl_free(schemep);
}
}
curl_url_cleanup(uh);
}
return proto? proto: "???"; /* Never match if not found. */
*scheme = (char *) (proto? proto: "???"); /* Never match if not found. */
return result;
}
/* create the next (singular) transfer */
@ -738,6 +951,7 @@ static CURLcode single_transfer(struct GlobalConfig *global,
bool orig_isatty = global->isatty;
struct State *state = &config->state;
char *httpgetfields = state->httpgetfields;
*added = FALSE; /* not yet */
if(config->postfields) {
@ -866,7 +1080,7 @@ static CURLcode single_transfer(struct GlobalConfig *global,
struct OutStruct *etag_save;
struct HdrCbData *hdrcbdata = NULL;
struct OutStruct etag_first;
const char *use_proto;
char *use_proto;
CURL *curl;
/* --etag-save */
@ -1265,7 +1479,10 @@ static CURLcode single_transfer(struct GlobalConfig *global,
if(result)
break;
use_proto = url_proto(per->this_url);
/* result is only used when for ipfs and ipns, ignored otherwise */
result = url_proto(&per->this_url, config, &use_proto);
if(result && (use_proto == proto_ipfs || use_proto == proto_ipns))
break;
/* On most modern OSes, exiting works thoroughly,
we'll clean everything up via exit(), so don't bother with
@ -1522,7 +1739,6 @@ static CURLcode single_transfer(struct GlobalConfig *global,
my_setopt_str(curl, CURLOPT_PROXY_KEYPASSWD, config->proxy_key_passwd);
if(use_proto == proto_scp || use_proto == proto_sftp) {
/* SSH and SSL private key uses same command-line option */
/* new in libcurl 7.16.1 */
my_setopt_str(curl, CURLOPT_SSH_PRIVATE_KEYFILE, config->key);