1
0
mirror of https://git.sr.ht/~sircmpwn/gmni synced 2024-11-23 00:42:15 +01:00

Implement input

This commit is contained in:
Drew DeVault 2020-09-20 14:09:45 -04:00
parent 9b1a618b42
commit 78eb57cad4
3 changed files with 164 additions and 39 deletions

@ -68,4 +68,8 @@ void gemini_response_finish(struct gemini_response *resp);
// 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

@ -176,7 +176,7 @@ gemini_request(const char *url, struct gemini_options *options,
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 @@ gemini_response_finish(struct gemini_response *resp)
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 @@ gemini_strerr(enum gemini_result r, struct gemini_response *resp)
}
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;
}

@ -26,10 +26,17 @@ main(int argc, char *argv[])
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 @@ main(int argc, char *argv[])
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 @@ main(int argc, char *argv[])
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 @@ main(int argc, char *argv[])
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]);
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);
if (n == -1) {
fprintf(stderr, "Error: read\n");
return 1;
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;
}
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));
if (fileno(input_source) != -1 &&
isatty(fileno(input_source))) {
fprintf(stderr, "%s: ", resp.meta);
}
size_t s = 0;
ssize_t n = getline(&input, &s, input_source);
if (n == -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);
}
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;
}