1
0
mirror of https://github.com/rkd77/elinks.git synced 2024-06-15 23:35:34 +00:00
elinks/src/protocol/data.c
2022-02-21 17:52:17 +01:00

168 lines
4.7 KiB
C

/* The "data" URI protocol implementation (RFC 2397) */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "elinks.h"
#include "cache/cache.h"
#include "network/connection.h"
#include "protocol/data.h"
#include "protocol/protocol.h"
#include "protocol/uri.h"
#include "util/base64.h"
#include "util/string.h"
/* The URLs are of the form:
*
* data:[<mediatype>][;base64],<data>
*
* The <mediatype> is an Internet media type specification (with optional
* parameters.) The appearance of ";base64" means that the data is encoded as
* base64. Without ";base64", the data (as a sequence of octets) is represented
* using ASCII encoding for octets inside the range of safe URL characters and
* using the standard %xx hex encoding of URLs for octets outside that range.
* If <mediatype> is omitted, it defaults to "text/plain;charset=US-ASCII". As a
* shorthand, "text/plain" can be omitted but the charset parameter supplied.
*
* The syntax:
*
* dataurl := "data:" [ mediatype ] [ ";base64" ] "," data
* mediatype := [ type "/" subtype ] *( ";" parameter )
* data := *urlchar
* parameter := attribute "=" value
*
* where "urlchar" is imported from [RFC2396], and "type", "subtype",
* "attribute" and "value" are the corresponding tokens from [RFC2045],
* represented using URL escaped encoding of [RFC2396] as necessary.
*
* Attribute values in [RFC2045] are allowed to be either represented as tokens
* or as quoted strings. However, within a "data" URL, the "quoted-string"
* representation would be awkward, since the quote mark is itself not a valid
* urlchar. For this reason, parameter values should use the URL Escaped
* encoding instead of quoted string if the parameter values contain any
* "tspecial".
*
* The ";base64" extension is distinguishable from a content-type parameter by
* the fact that it doesn't have a following "=" sign. */
/* FIXME: Maybe some kind of redirecting to common specialized data URI could
* be useful so "data:,blah" and data:text/plain,blah" are redirected to the
* most specialized "data:text/plain;charset=US-ASCII,blah". On the other hand
* for small entries it doesn't matter. */
#define DEFAULT_DATA_MEDIATYPE "text/plain;charset=US-ASCII"
#define data_has_mediatype(header, headerlen) \
((headerlen) >= 3 && memchr(header, '/', headerlen))
#define data_has_base64_attribute(typelen, endstr) \
((typelen) >= sizeof(";base64") - 1 \
&& !memcmp(";base64", (end) - sizeof(";base64") + 1, sizeof(";base64") - 1))
static char *
init_data_protocol_header(struct cache_entry *cached,
const char *type_, int typelen)
{
char *head;
char *type;
assert(typelen);
type = memacpy(type_, typelen);
if (!type) return NULL;
/* Set fake content type */
head = straconcat("\r\nContent-Type: ", type, "\r\n",
(char *) NULL);
mem_free(type);
if (!head) return NULL;
mem_free_set(&cached->head, head);
return head;
}
static char *
parse_data_protocol_header(struct connection *conn, int *base64)
{
struct uri *uri = conn->uri;
char *end = (char *)memchr(uri->data, ',', uri->datalen);
const char *type = DEFAULT_DATA_MEDIATYPE;
int typelen = sizeof(DEFAULT_DATA_MEDIATYPE) - 1;
if (end) {
int headerlen = end - uri->data;
if (data_has_base64_attribute(headerlen, end)) {
*base64 = 1;
headerlen -= sizeof(";base64") - 1;
}
if (data_has_mediatype(uri->data, headerlen)) {
type = uri->data;
typelen = headerlen;
}
}
if (!init_data_protocol_header(conn->cached, type, typelen))
return NULL;
/* Return char after ',' or complete data part */
return end ? end + 1 : uri->data;
}
void
data_protocol_handler(struct connection *conn)
{
struct uri *uri = conn->uri;
struct cache_entry *cached = get_cache_entry(uri);
char *data_start, *data = NULL;
int base64 = 0;
int decodedlen = 0;
int datalen;
if (!cached) {
abort_connection(conn, connection_state(S_OUT_OF_MEM));
return;
}
conn->cached = cached;
data_start = parse_data_protocol_header(conn, &base64);
if (!data_start) {
abort_connection(conn, connection_state(S_OUT_OF_MEM));
return;
}
datalen = uri->datalen - (data_start - uri->data);
if (base64) {
char *decoded = base64_decode_bin(data_start, datalen, &decodedlen);
if (!decoded) {
abort_connection(conn, connection_state(S_OUT_OF_MEM));
return;
}
mem_free_set(&data, decoded);
} else {
/* Allocate the data string because URI decoding will possibly modify
* it. */
data = memacpy(data_start, datalen);
if (!data) {
abort_connection(conn, connection_state(S_OUT_OF_MEM));
return;
}
decode_uri(data);
/* Use strlen() to get the correct decoded length */
decodedlen = strlen(data);
}
add_fragment(cached, conn->from, data, decodedlen);
conn->from += decodedlen;
mem_free(data);
abort_connection(conn, connection_state(S_OK));
}