WASM: move save file encoding from JS into C.

The previous fix worked OK, but it was conceptually wrong. Puzzles
save files are better regarded as binary, not text: the length fields
are measured in bytes, so translating the file into a different
multibyte character encoding would invalidate them.

So it was wrong to fetch a C byte string containing the exactly right
binary data, then translate it into a Javascript string as if decoding
from UTF-8, then retranslate to a representation of a bytewise
encoding via encodeURIComponent, and then label the result as
application/octet-stream.

This probably wouldn't have caused any problems in practice, because I
don't remember any situation in which my save files use characters
outside printable ASCII (plus newline). But it's not actually
forbidden, so a save file might choose to do that some day, so that
UTF-8 decode/reencode hidden in the JS was a latent bug.

Now the URI-encoding is done on the C side, while we still know
exactly what the binary data ought to look like and can be sure we're
translating it byte for byte into the output encoding for the data:
URI. By the time the JS receives a string pointer from get_save_file,
it's already URI-encoded, which _guarantees_ that it's in ASCII and
won't be messed about with by Emscripten's UTF8ToString.
This commit is contained in:
Simon Tatham
2021-05-23 08:45:55 +01:00
parent 1c760b2ee8
commit f729f51e47
2 changed files with 46 additions and 6 deletions

49
emcc.c
View File

@ -787,12 +787,53 @@ struct savefile_write_ctx {
size_t pos;
};
static void savefile_write(void *vctx, const void *buf, int len)
static void savefile_write(void *vctx, const void *vbuf, int len)
{
static const unsigned char length[256] = {
/*
* Assign a length of 1 to any printable ASCII character that
* can be written literally in URI-encoding, i.e.
*
* A-Z a-z 0-9 - _ . ! ~ * ' ( )
*
* Assign length 3 (for % and two hex digits) to all other
* byte values.
*/
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 1, 3, 3, 3, 3, 3, 1, 1, 1, 1, 3, 3, 1, 1, 3,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 3, 3,
3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 1,
3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 1, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
};
static const char hexdigits[] = "0123456789ABCDEF";
struct savefile_write_ctx *ctx = (struct savefile_write_ctx *)vctx;
if (ctx->buffer)
memcpy(ctx->buffer + ctx->pos, buf, len);
ctx->pos += len;
const unsigned char *buf = (const unsigned char *)vbuf;
for (int i = 0; i < len; i++) {
unsigned char c = buf[i];
int clen = length[c];
if (ctx->buffer) {
if (clen == 1) {
ctx->buffer[ctx->pos] = c;
} else {
ctx->buffer[ctx->pos] = '%';
ctx->buffer[ctx->pos+1] = hexdigits[c >> 4];
ctx->buffer[ctx->pos+2] = hexdigits[c & 0xF];
}
}
ctx->pos += clen;
}
}
char *get_save_file(void)

View File

@ -379,8 +379,7 @@ function initPuzzle() {
"Click to download the "));
var a = document.createElement("a");
a.download = "puzzle.sav";
a.href = "data:application/octet-stream," +
encodeURIComponent(savefile_text);
a.href = "data:application/octet-stream," + savefile_text;
a.appendChild(document.createTextNode("saved-game file"));
dlg_form.appendChild(a);
dlg_form.appendChild(document.createTextNode("."));