diff --git a/include/client.h b/include/client.h
index f711eea91c3842ac29a884b38e0d7a0ab99cb7fb..40729073bcaa2c0a172d3375662ac91cef0ea550 100644
--- a/include/client.h
+++ b/include/client.h
@@ -68,4 +68,8 @@
// Returns a user-friendly string describing an error.
const char *gemini_strerr(enum gemini_result r, struct gemini_response *resp);
+// Returns the given URL with the input response set to the specified value.
+// The caller must free the string.
+char *gemini_input_url(const char *url, const char *input);
+
#endif
diff --git a/src/client.c b/src/client.c
index 67671ccca2a25a460681bdffacb981c7f26c1407..c252c9d9ab1783dd462343ce346cc74611e9ae85 100644
--- a/src/client.c
+++ b/src/client.c
@@ -176,7 +176,7 @@ }
char *endptr;
resp->status = (int)strtol(buf, &endptr, 10);
- if (*endptr != ' ' || resp->status <= 10 || resp->status >= 70) {
+ if (*endptr != ' ' || resp->status < 10 || resp->status >= 70) {
res = GEMINI_ERR_PROTOCOL;
goto cleanup;
}
@@ -195,14 +195,25 @@ {
if (!resp) {
return;
}
+
if (resp->fd != -1) {
close(resp->fd);
+ resp->fd = -1;
}
- BIO_free(BIO_pop(resp->bio)); // ssl bio
- BIO_free(resp->bio); // buffered bio
+
+ if (resp->bio) {
+ BIO_free(BIO_pop(resp->bio)); // ssl bio
+ BIO_free(resp->bio); // buffered bio
+ resp->bio = NULL;
+ }
+
SSL_free(resp->ssl);
SSL_CTX_free(resp->ssl_ctx);
free(resp->meta);
+
+ resp->ssl = NULL;
+ resp->ssl_ctx = NULL;
+ resp->meta = NULL;
}
const char *
@@ -230,3 +241,26 @@ return "Protocol error";
}
assert(0);
}
+
+char *
+gemini_input_url(const char *url, const char *input)
+{
+ char *new_url = NULL;
+ struct Curl_URL *uri = curl_url();
+ if (!uri) {
+ return NULL;
+ }
+ if (curl_url_set(uri, CURLUPART_URL, url, 0) != CURLUE_OK) {
+ goto cleanup;
+ }
+ if (curl_url_set(uri, CURLUPART_QUERY, input, CURLU_URLENCODE) != CURLUE_OK) {
+ goto cleanup;
+ }
+ if (curl_url_get(uri, CURLUPART_URL, &new_url, 0) != CURLUE_OK) {
+ new_url = NULL;
+ goto cleanup;
+ }
+cleanup:
+ curl_url_cleanup(uri);
+ return new_url;
+}
diff --git a/src/gmnic.c b/src/gmnic.c
index 014211dc3784ba3eadcf1885ef7bcf6fe84d8462..794b94ba2235757864acb81194b8de88220cc6da 100644
--- a/src/gmnic.c
+++ b/src/gmnic.c
@@ -26,10 +26,17 @@ OMIT_HEADERS,
SHOW_HEADERS,
ONLY_HEADERS,
};
- enum header_mode headers = OMIT_HEADERS;
+ enum header_mode header_mode = OMIT_HEADERS;
+
+ enum input_mode {
+ INPUT_READ,
+ INPUT_SUPPRESS,
+ };
+ enum input_mode input_mode = INPUT_READ;
+ FILE *input_source = stdin;
int c;
- while ((c = getopt(argc, argv, "46C:d:hLiI")) != -1) {
+ while ((c = getopt(argc, argv, "46C:d:D:hLiIN")) != -1) {
switch (c) {
case '4':
assert(0); // TODO
@@ -41,7 +48,21 @@ case 'C':
assert(0); // TODO: Client certificates
break;
case 'd':
- assert(0); // TODO: Input
+ input_mode = INPUT_READ;
+ input_source = fmemopen(optarg, strlen(optarg), "r");
+ break;
+ case 'D':
+ input_mode = INPUT_READ;
+ if (strcmp(optarg, "-") == 0) {
+ input_source = stdin;
+ } else {
+ input_source = fopen(optarg, "r");
+ if (!input_source) {
+ fprintf(stderr, "Error: open %s: %s",
+ optarg, strerror(errno));
+ return 1;
+ }
+ }
break;
case 'h':
usage(argv[0]);
@@ -50,10 +71,14 @@ case 'L':
assert(0); // TODO: Follow redirects
break;
case 'i':
- headers = SHOW_HEADERS;
+ header_mode = SHOW_HEADERS;
break;
case 'I':
- headers = ONLY_HEADERS;
+ header_mode = ONLY_HEADERS;
+ input_mode = INPUT_SUPPRESS;
+ break;
+ case 'N':
+ input_mode = INPUT_SUPPRESS;
break;
default:
fprintf(stderr, "fatal: unknown flag %c", c);
@@ -69,43 +94,105 @@
SSL_load_error_strings();
ERR_load_crypto_strings();
- struct gemini_response resp;
- enum gemini_result r = gemini_request(argv[optind], NULL, &resp);
- if (r != GEMINI_OK) {
- fprintf(stderr, "Error: %s\n", gemini_strerr(r, &resp));
- gemini_response_finish(&resp);
- return (int)r;
- }
+ bool exit = false;
+ char *url = strdup(argv[optind]);
+
+ int ret = 0;
+ while (!exit) {
+ struct gemini_response resp;
+ enum gemini_result r = gemini_request(url, NULL, &resp);
+ if (r != GEMINI_OK) {
+ fprintf(stderr, "Error: %s\n", gemini_strerr(r, &resp));
+ ret = (int)r;
+ exit = true;
+ goto next;
+ }
+
+ char *new_url, *input = NULL;
+ switch (resp.status / 10) {
+ case 1: // INPUT
+ if (input_mode == INPUT_SUPPRESS) {
+ exit = true;
+ break;
+ }
+
+ if (fileno(input_source) != -1 &&
+ isatty(fileno(input_source))) {
+ fprintf(stderr, "%s: ", resp.meta);
+ }
- switch (headers) {
- case ONLY_HEADERS:
- printf("%d %s\n", resp.status, resp.meta);
- break;
- case SHOW_HEADERS:
- printf("%d %s\n", resp.status, resp.meta);
- /* fallthrough */
- case OMIT_HEADERS:
- for (int n = 1; n > 0;) {
- char buf[BUFSIZ];
- n = BIO_read(resp.bio, buf, BUFSIZ);
+ size_t s = 0;
+ ssize_t n = getline(&input, &s, input_source);
if (n == -1) {
- fprintf(stderr, "Error: read\n");
- return 1;
+ fprintf(stderr, "Error reading input: %s\n",
+ feof(input_source) ? "EOF" :
+ strerror(ferror(input_source)));
+ r = 1;
+ exit = true;
+ break;
+ }
+ input[n - 1] = '\0'; // Drop LF
+
+ new_url = gemini_input_url(url, input);
+ free(url);
+ url = new_url;
+ goto next;
+ case 3: // REDIRECT
+ assert(0); // TODO
+ case 6: // CLIENT CERTIFICATE REQUIRED
+ assert(0); // TODO
+ case 4: // TEMPORARY FAILURE
+ case 5: // PERMANENT FAILURE
+ if (header_mode == OMIT_HEADERS) {
+ fprintf(stderr, "%s: %d %s\n",
+ resp.status / 10 == 4 ?
+ "TEMPORARY FAILURE" : "PERMANENT FALIURE",
+ resp.status, resp.meta);
}
- ssize_t w = 0;
- while (w < (ssize_t)n) {
- ssize_t x = write(STDOUT_FILENO, &buf[w], n - w);
- if (x == -1) {
- fprintf(stderr, "Error: write: %s\n",
- strerror(errno));
+ exit = true;
+ break;
+ case 2: // SUCCESS
+ exit = true;
+ break;
+ }
+
+ switch (header_mode) {
+ case ONLY_HEADERS:
+ printf("%d %s\n", resp.status, resp.meta);
+ break;
+ case SHOW_HEADERS:
+ printf("%d %s\n", resp.status, resp.meta);
+ /* fallthrough */
+ case OMIT_HEADERS:
+ if (resp.status / 10 != 2) {
+ break;
+ }
+ for (int n = 1; n > 0;) {
+ char buf[BUFSIZ];
+ n = BIO_read(resp.bio, buf, BUFSIZ);
+ if (n == -1) {
+ fprintf(stderr, "Error: read\n");
return 1;
}
- w += x;
+ ssize_t w = 0;
+ while (w < (ssize_t)n) {
+ ssize_t x = write(STDOUT_FILENO, &buf[w], n - w);
+ if (x == -1) {
+ fprintf(stderr, "Error: write: %s\n",
+ strerror(errno));
+ return 1;
+ }
+ w += x;
+ }
}
+ break;
}
- break;
+
+next:
+ gemini_response_finish(&resp);
}
- gemini_response_finish(&resp);
- return 0;
+ (void)input_mode;
+ free(url);
+ return ret;
}