mirror of
https://git.sr.ht/~sircmpwn/gmni
synced 2024-11-23 00:42:15 +01:00
Implement input
This commit is contained in:
parent
9b1a618b42
commit
78eb57cad4
@ -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
|
||||
|
40
src/client.c
40
src/client.c
@ -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;
|
||||
}
|
||||
|
159
src/gmnic.c
159
src/gmnic.c
@ -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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user