diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fc060ad2..2ecffc8f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,7 +18,6 @@ jobs: - { platform: "x64", archive: "-x64" } - { platform: "Win32", archive: "" } - { platform: "ARM64", archive: "-arm64" } - - { platform: "ARM", archive: "-arm" } steps: - name: Checkout diff --git a/Buildscr b/Buildscr index 5049e761..497430db 100644 --- a/Buildscr +++ b/Buildscr @@ -35,7 +35,7 @@ module putty ifeq "$(RELEASE)" "" set Ndate $(!builddate) ifneq "$(Ndate)" "" in . do echo $(Ndate) | perl -pe 's/(....)(..)(..)/$$1-$$2-$$3/' > date ifneq "$(Ndate)" "" read Date date -set Epoch 18819 # update this at every release +set Epoch 19052 # update this at every release ifneq "$(Ndate)" "" in . do echo $(Ndate) | perl -ne 'use Time::Local; /(....)(..)(..)/ and print timegm(0,0,0,$$3,$$2-1,$$1) / 86400 - $(Epoch)' > days ifneq "$(Ndate)" "" read Days days @@ -140,6 +140,9 @@ delegate - in putty do cmake . -DCMAKE_C_COMPILER=clang -DCMAKE_C_FLAGS="-fsanitize=address -fsanitize=leak" -DSTRICT=ON in putty do make -j$(nproc) VERBOSE=1 in putty do python3 test/cryptsuite.py +in putty do ./test_lineedit +in putty do ./test_terminal +in putty do ./test_conf enddelegate delegate - diff --git a/CHECKLST.txt b/CHECKLST.txt index 3d7093a7..92ef67e5 100644 --- a/CHECKLST.txt +++ b/CHECKLST.txt @@ -112,7 +112,7 @@ Making a release candidate build - Make a preliminary gpg signature, but don't run the full release- signing procedure. (We use the presence of a full set of GPG signatures to distinguish _abandoned_ release candidates from the - one that ended up being the release.) In the 'build.X.YZ-rcN.out' + one that ended up being the release.) In the 'build-X.YZ-rcN.out' directory, run sh sign.sh -r -p putty which will generate a clearsigned file called @@ -164,13 +164,24 @@ Preparing to make the release - Prepare some 'what's new in this release' blurb for the Windows Store. This should be very brief - even briefer than the website - news item. Keep it to a couple of sentences in a single paragraph, - templated along the lines of 'X.YZ adds support for this, that and - the other, and fixes bugs including this and that', or 'X.YZ is a - bug-fix release, mostly in the area of Foo, with one important fix - to Bar'. + news item. + * Keep it to a couple of sentences in a single paragraph, + templated along the lines of + X.YZ adds support for this, that and the other, and fixes bugs + including this and that. + or + X.YZ is a bug-fix release, mostly in the area of Foo, with one + important fix to Bar. * Might as well check this into putty-aux too. + - Prepare a toot! I'm on Mastodon, so I should announce the release + there. This means writing a cut-down 500-char announcement, maybe + more like the website news item than like the email. + * Include any relevant hashtags. Refer to the software as #PuTTY; + if you mention SSH then write it as #SSH; similarly if we're + fixing a #vulnerability. That's how people will find the toot. + * Again, commit to putty-aux for team review. + - Update the wishlist, in a local checkout: * If there are any last-minute wishlist entries (e.g. security vulnerabilities fixed in the new release), write entries for @@ -196,6 +207,21 @@ Preparing to make the release sh sign.sh -r putty # and enter the release key passphrase chmod -R a-w putty + - If the release is on a branch (which I expect it generally will + be), prepare a merge of that branch to main. Even if the branch + consists of nothing but cherry-picks _from_ main, this will mean + that the 'update version number' change appears on main and the + snapshots start announcing themselves as post-X.YZ. But also, if + there's anything new on the branch, this is how it gets on to main + as well. + + - Log in to the MS Partner Center and make sure everything is in + order. If the UI has completely changed, make sure you can find + your way around the new one; if it wants you to read an enormous + document of revised T&Cs, get that out of the way in advance, so it + doesn't suddenly become a delay in the middle of the actual + release. + The actual release procedure ---------------------------- @@ -219,18 +245,12 @@ locally, this is the procedure for putting it up on the web. correctly and work: ../putty/release.pl --version=X.YZ --postcheck - - If the release is on a branch (which I expect it generally will - be), merge that branch to main, so that the 'update version number' - change appears on main and the snapshots start announcing - themselves as post-X.YZ. - - Push all the git repositories: * run 'git push' in the website checkout * run 'git push' in the wishlist checkout * push from the main PuTTY checkout. Typically this one will be - pushing both the release tag and the merge we just made to the - main branch, plus removing the pre-release branch, so you'll - want some + pushing both the release tag and the merge to the main branch, + plus removing the pre-release branch, so you'll want some commands along these lines: git push origin main # update the main branch git push origin --tags # should push the new release tag @@ -276,6 +296,7 @@ locally, this is the procedure for putting it up on the web. + Mail that release announcement to . + Post it to comp.security.ssh. + + Post the prepared toot to Mastodon. + Mention it in on mono. - Edit the master ~/adm/puttysnap.sh to disable pre-release builds, diff --git a/CMakeLists.txt b/CMakeLists.txt index e6d884e3..314b7f6a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,7 +20,7 @@ add_subdirectory(utils) add_subdirectory(stubs) add_library(logging OBJECT - logging.c) + logging.c utils/logeventf.c) add_library(eventloop STATIC callback.c timing.c) @@ -36,7 +36,8 @@ add_library(crypto STATIC add_subdirectory(crypto) add_library(network STATIC - errsock.c logging.c x11disp.c + errsock.c x11disp.c + $ proxy/proxy.c proxy/http.c proxy/socks4.c @@ -54,7 +55,7 @@ add_library(agent STATIC add_library(guiterminal STATIC terminal/terminal.c terminal/bidi.c - ldisc.c config.c dialog.c + ldisc.c terminal/lineedit.c config.c dialog.c $) add_library(noterminal STATIC @@ -87,6 +88,11 @@ add_executable(test_decode_utf8 target_compile_definitions(test_decode_utf8 PRIVATE TEST) target_link_libraries(test_decode_utf8 utils ${platform_libraries}) +add_executable(test_unicode_norm + utils/unicode-norm.c) +target_compile_definitions(test_unicode_norm PRIVATE TEST) +target_link_libraries(test_unicode_norm utils ${platform_libraries}) + add_executable(test_tree234 utils/tree234.c) target_compile_definitions(test_tree234 PRIVATE TEST) @@ -111,7 +117,8 @@ add_executable(bidi_test target_link_libraries(bidi_test guiterminal utils ${platform_libraries}) add_executable(plink - ${platform}/plink.c) + ${platform}/plink.c + stubs/no-lineedit.c) # Note: if we ever port Plink to a platform where we can't implement a # serial backend, this be_list command will need to become platform- # dependent, so that it only sets the SERIAL option on platforms where @@ -151,8 +158,30 @@ target_link_libraries(psocks eventloop console network utils ${platform_libraries}) +add_executable(test_conf + test/test_conf.c + stubs/no-agent.c + stubs/no-callback.c + stubs/no-gss.c + stubs/no-ldisc.c + stubs/no-network.c + stubs/no-timing.c + proxy/noproxy.c # FIXME: move this to stubs +) +be_list(test_conf TestConf SSH SERIAL OTHERBACKENDS) +target_link_libraries(test_conf sshclient otherbackends settings network crypto utils ${platform_libraries}) + foreach(subdir ${platform} ${extra_dirs}) add_subdirectory(${subdir}) endforeach() +# Nasty bodge: we'd like to run this command inside unix/CMakeLists, +# adding the 'charset' library to everything that links with utils. +# But that wasn't allowed until cmake 3.13 (see cmake policy CMP0073), +# and we still have a min cmake version less than that. So we do it +# here instead. +if(platform STREQUAL unix) + target_link_libraries(utils charset) +endif() + configure_file(cmake/cmake.h.in ${GENERATED_SOURCES_DIR}/cmake.h) diff --git a/LATEST.VER b/LATEST.VER index 453a698e..e6e9cf41 100644 --- a/LATEST.VER +++ b/LATEST.VER @@ -1 +1 @@ -0.81 +0.82 diff --git a/README b/README index 979c02ba..ad25bc75 100644 --- a/README +++ b/README @@ -1,14 +1,23 @@ -This is the README for PuTTY, a free Windows and Unix Telnet and SSH -client. +PuTTY source code README +======================== + +This is the README for the source code of PuTTY, a free Windows and +Unix Telnet and SSH client. PuTTY is built using CMake . To compile in the -simplest way (on any of Linux, Windows or Mac), run these commands in -the source directory: +simplest way (on any of Linux, Windows or Mac), the general method is +to run these commands in the source directory: cmake . cmake --build . -Then, to install in the simplest way on Linux or Mac: +These commands will expect to find a usable compile toolchain on your +path. So if you're building on Windows with MSVC, you'll need to make +sure that the MSVC compiler (cl.exe) is on your path, by running one +of the 'vcvars32.bat' setup scripts provided with the tools. Then the +cmake commands above should work. + +To install in the simplest way on Linux or Mac: cmake --build . --target install diff --git a/cmake/cmake.h.in b/cmake/cmake.h.in index 3882148b..f8fcbc58 100644 --- a/cmake/cmake.h.in +++ b/cmake/cmake.h.in @@ -48,6 +48,7 @@ #cmakedefine01 HAVE_BINARY_SETPGRP #cmakedefine01 HAVE_PANGO_FONT_FAMILY_IS_MONOSPACE #cmakedefine01 HAVE_PANGO_FONT_MAP_LIST_FAMILIES +#cmakedefine01 HAVE_G_APPLICATION_DEFAULT_FLAGS #cmakedefine01 HAVE_AES_NI #cmakedefine01 HAVE_SHA_NI diff --git a/cmake/gtk.cmake b/cmake/gtk.cmake index 13ff7705..9c70c685 100644 --- a/cmake/gtk.cmake +++ b/cmake/gtk.cmake @@ -84,6 +84,10 @@ if(GTK_FOUND) ${HAVE_PANGO_FONT_FAMILY_IS_MONOSPACE} PARENT_SCOPE) set(HAVE_PANGO_FONT_MAP_LIST_FAMILIES ${HAVE_PANGO_FONT_MAP_LIST_FAMILIES} PARENT_SCOPE) + check_c_source_compiles(" + #include + int f = G_APPLICATION_DEFAULT_FLAGS; + int main(void) {}" HAVE_G_APPLICATION_DEFAULT_FLAGS) endfunction() pango_check_subscope() endif() diff --git a/cmake/winegcc b/cmake/winegcc index fb298ad1..afa17de6 100755 --- a/cmake/winegcc +++ b/cmake/winegcc @@ -5,6 +5,7 @@ # options that CMake gets wrong. init=true +link=true for arg in init "$@"; do if $init; then set -- @@ -21,9 +22,18 @@ for arg in init "$@"; do # suffix. -l*.lib) set -- "$@" "${arg%.lib}";; + # Options that mean we're not linking. + -E | -S | -c) link=false set -- "$@" "$arg";; + # Anything else, we leave unchanged. *) set -- "$@" "$arg";; esac done +if $link; then + # winegcc requires this library for _wfopen + set -- "$@" -lucrtbase +fi + +echo "$@" >&2 exec winegcc "$@" diff --git a/cmdgen.c b/cmdgen.c index a333b09e..40ea710b 100644 --- a/cmdgen.c +++ b/cmdgen.c @@ -717,10 +717,11 @@ int main(int argc, char **argv) /* * If run with at least one argument _but_ not the required - * ones, print the usage message and return failure. + * ones, fail with an error. */ if (!infile && keytype == NOKEYGEN) { - usage(true); + fprintf(stderr, "puttygen: expected an input key file name, " + "or -t for a type of key to generate\n"); RETURN(1); } diff --git a/cmdline.c b/cmdline.c index 812b71e7..5de30f7f 100644 --- a/cmdline.c +++ b/cmdline.c @@ -31,7 +31,7 @@ #define NPRIORITIES 2 struct cmdline_saved_param { - char *p, *value; + CmdlineArg *p, *value; }; struct cmdline_saved_param_set { struct cmdline_saved_param *params; @@ -44,11 +44,11 @@ struct cmdline_saved_param_set { */ static struct cmdline_saved_param_set saves[NPRIORITIES]; -static void cmdline_save_param(const char *p, const char *value, int pri) +static void cmdline_save_param(CmdlineArg *p, CmdlineArg *value, int pri) { sgrowarray(saves[pri].params, saves[pri].savesize, saves[pri].nsaved); - saves[pri].params[saves[pri].nsaved].p = dupstr(p); - saves[pri].params[saves[pri].nsaved].value = dupstr(value); + saves[pri].params[saves[pri].nsaved].p = p; + saves[pri].params[saves[pri].nsaved].value = value; saves[pri].nsaved++; } @@ -73,7 +73,7 @@ void cmdline_cleanup(void) } #define SAVEABLE(pri) do { \ - if (need_save) { cmdline_save_param(p, value, pri); return ret; } \ + if (need_save) { cmdline_save_param(arg, nextarg, pri); return ret; } \ } while (0) /* @@ -130,10 +130,15 @@ SeatPromptResult cmdline_get_passwd_input( return SPR_OK; } +static void cmdline_report_unavailable(const char *p) +{ + cmdline_error("option \"%s\" not available in this tool", p); +} + static bool cmdline_check_unavailable(int flag, const char *p) { if (cmdline_tooltype & flag) { - cmdline_error("option \"%s\" not available in this tool", p); + cmdline_report_unavailable(p); return true; } return false; @@ -185,10 +190,13 @@ static void set_port(Conf *conf, int port) conf_set_int(conf, CONF_port, port); } -int cmdline_process_param(const char *p, char *value, +int cmdline_process_param(CmdlineArg *arg, CmdlineArg *nextarg, int need_save, Conf *conf) { int ret = 0; + const char *p = cmdline_arg_to_str(arg); + const char *value_utf8 = cmdline_arg_to_utf8(nextarg); + const char *value = cmdline_arg_to_str(nextarg); if (p[0] != '-') { if (need_save < 0) @@ -403,9 +411,8 @@ int cmdline_process_param(const char *p, char *value, * pretend we received a -P argument, so that it will be * deferred until it's a good moment to run it. */ - char *dup = dupstr(p); /* 'value' is not a const char * */ - int retd = cmdline_process_param("-P", dup, 1, conf); - sfree(dup); + int retd = cmdline_process_param( + cmdline_arg_from_str(arg->list, "-P"), arg, 1, conf); assert(retd == 2); seen_port_argument = true; return 1; @@ -453,7 +460,10 @@ int cmdline_process_param(const char *p, char *value, RETURN(2); UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); SAVEABLE(0); - conf_set_str(conf, CONF_username, value); + if (value_utf8) + conf_set_utf8(conf, CONF_username, value_utf8); + else + conf_set_str(conf, CONF_username, value); } if (!strcmp(p, "-loghost")) { RETURN(2); @@ -553,20 +563,23 @@ int cmdline_process_param(const char *p, char *value, sfree(host); } if (!strcmp(p, "-m")) { - const char *filename; + Filename *filename; FILE *fp; RETURN(2); UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); SAVEABLE(0); - filename = value; + filename = cmdline_arg_to_filename(nextarg); - fp = fopen(filename, "r"); + fp = f_open(filename, "r", false); if (!fp) { - cmdline_error("unable to open command file \"%s\"", filename); + cmdline_error("unable to open command file \"%s\"", + filename_to_str(filename)); + filename_free(filename); return ret; } + filename_free(filename); strbuf *command = strbuf_new(); char readbuf[4096]; while (1) { @@ -603,11 +616,9 @@ int cmdline_process_param(const char *p, char *value, } cmdline_password = dupstr(value); - /* Assuming that `value' is directly from argv, make a good faith - * attempt to trample it, to stop it showing up in `ps' output - * on Unix-like systems. Not guaranteed, of course. */ - smemclr(value, strlen(value)); } + + cmdline_arg_wipe(nextarg); } if (!strcmp(p, "-pwfile")) { @@ -620,7 +631,7 @@ int cmdline_process_param(const char *p, char *value, cmdline_error("the -pwfile option can only be used with the " "SSH protocol"); else { - Filename *fn = filename_from_str(value); + Filename *fn = cmdline_arg_to_filename(nextarg); FILE *fp = f_open(fn, "r", false); if (!fp) { cmdline_error("unable to open password file '%s'", value); @@ -742,21 +753,19 @@ int cmdline_process_param(const char *p, char *value, } if (!strcmp(p, "-i")) { - Filename *fn; RETURN(2); UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); SAVEABLE(0); - fn = filename_from_str(value); + Filename *fn = cmdline_arg_to_filename(nextarg); conf_set_filename(conf, CONF_keyfile, fn); filename_free(fn); } if (!strcmp(p, "-cert")) { - Filename *fn; RETURN(2); UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); SAVEABLE(0); - fn = filename_from_str(value); + Filename *fn = cmdline_arg_to_filename(nextarg); conf_set_filename(conf, CONF_detached_cert, fn); filename_free(fn); } @@ -774,7 +783,7 @@ int cmdline_process_param(const char *p, char *value, conf_set_int(conf, CONF_addressfamily, ADDRTYPE_IPV6); } if (!strcmp(p, "-sercfg")) { - char* nextitem; + const char *nextitem; RETURN(2); UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); SAVEABLE(1); @@ -791,7 +800,6 @@ int cmdline_process_param(const char *p, char *value, skip = 0; } else { length = end - nextitem; - nextitem[length] = '\0'; skip = 1; } if (length == 1) { @@ -846,7 +854,9 @@ int cmdline_process_param(const char *p, char *value, /* Messy special case */ conf_set_int(conf, CONF_serstopbits, 3); } else { - int serspeed = atoi(nextitem); + char *speedstr = dupprintf("%.*s", length, nextitem); + int serspeed = atoi(speedstr); + sfree(speedstr); if (serspeed != 0) { conf_set_int(conf, CONF_serspeed, serspeed); } else { @@ -859,12 +869,11 @@ int cmdline_process_param(const char *p, char *value, } if (!strcmp(p, "-sessionlog")) { - Filename *fn; RETURN(2); UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER); /* but available even in TOOLTYPE_NONNETWORK, cf pterm "-log" */ SAVEABLE(0); - fn = filename_from_str(value); + Filename *fn = cmdline_arg_to_filename(nextarg); conf_set_filename(conf, CONF_logfilename, fn); conf_set_int(conf, CONF_logtype, LGTYP_DEBUG); filename_free(fn); @@ -872,11 +881,10 @@ int cmdline_process_param(const char *p, char *value, if (!strcmp(p, "-sshlog") || !strcmp(p, "-sshrawlog")) { - Filename *fn; RETURN(2); UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); SAVEABLE(0); - fn = filename_from_str(value); + Filename *fn = cmdline_arg_to_filename(nextarg); conf_set_filename(conf, CONF_logfilename, fn); conf_set_int(conf, CONF_logtype, !strcmp(p, "-sshlog") ? LGTYP_PACKETS : @@ -906,6 +914,33 @@ int cmdline_process_param(const char *p, char *value, conf_set_str(conf, CONF_proxy_telnet_command, value); } + if (!strcmp(p, "-batch")) { + RETURN(1); + SAVEABLE(0); + if (!console_set_batch_mode(true)) { + cmdline_report_unavailable(p); + return ret; + } + } + + if (!strcmp(p, "-legacy-stdio-prompts") || + !strcmp(p, "-legacy_stdio_prompts")) { + RETURN(1); + if (!console_set_stdio_prompts(true)) { + cmdline_report_unavailable(p); + return ret; + } + } + + if (!strcmp(p, "-legacy-charset-handling") || + !strcmp(p, "-legacy_charset_handling")) { + RETURN(1); + if (!set_legacy_charset_handling(true)) { + cmdline_report_unavailable(p); + return ret; + } + } + #ifdef _WINDOWS /* * Cross-tool options only available on Windows. @@ -923,12 +958,9 @@ int cmdline_process_param(const char *p, char *value, void cmdline_run_saved(Conf *conf) { for (size_t pri = 0; pri < NPRIORITIES; pri++) { - for (size_t i = 0; i < saves[pri].nsaved; i++) { + for (size_t i = 0; i < saves[pri].nsaved; i++) cmdline_process_param(saves[pri].params[i].p, saves[pri].params[i].value, 0, conf); - sfree(saves[pri].params[i].p); - sfree(saves[pri].params[i].value); - } saves[pri].nsaved = 0; } } diff --git a/conf-enums.h b/conf-enums.h new file mode 100644 index 00000000..02341686 --- /dev/null +++ b/conf-enums.h @@ -0,0 +1,197 @@ +/* + * Define various enumerations used in the storage format of PuTTY's + * settings. + * + * Each CONF_ENUM here defines an identifier that config options can + * cite using their STORAGE_ENUM property. Within each enum, the + * VALUE(x,y) records indicate that if the internal value of the + * integer Conf property in question is x, then it's stored in the + * saved settings as y. + * + * This allows the internal enumerations to be rearranged without + * breaking storage compatibility, and also allows for the fact that + * (for embarrassing historical reasons) the tri-state FORCE_OFF / + * FORCE_ON / AUTO enumeration has several different storage + * representations depending on the Conf option. + * + * VALUE_OBSOLETE(x,y) records only work one way: they indicate how a + * legacy value in saved session data should be interpreted, but also + * say that that value should no longer be generated in newly saved + * data. That is, they indicate that y maps to x, but not that x maps + * to y. + */ + +CONF_ENUM(addressfamily, + VALUE(ADDRTYPE_UNSPEC, 0), + VALUE(ADDRTYPE_IPV4, 1), + VALUE(ADDRTYPE_IPV6, 2), +) + +CONF_ENUM(on_off_auto, + VALUE(FORCE_ON, 0), + VALUE(FORCE_OFF, 1), + VALUE(AUTO, 2), +) + +CONF_ENUM(off_auto_on, + VALUE(FORCE_OFF, 0), + VALUE(AUTO, 1), + VALUE(FORCE_ON, 2), +) + +CONF_ENUM(auto_off_on, + VALUE(AUTO, 0), + VALUE(FORCE_OFF, 1), + VALUE(FORCE_ON, 2), +) + +CONF_ENUM(off1_on2, + VALUE(FORCE_OFF, 1), + VALUE(FORCE_ON, 2), +) + +CONF_ENUM(ssh_protocol, + VALUE(0, 0), + VALUE(3, 3), + /* Save value 1 used to mean 'prefer SSH-1 but allow 2'; 2 vice versa. + * We no longer support SSH-1 vs SSH-2 agnosticism, so we translate each + * to the preferred version only. */ + VALUE_OBSOLETE(0, 1), + VALUE_OBSOLETE(3, 2), +) + +CONF_ENUM(serparity, + VALUE(SER_PAR_NONE, 0), + VALUE(SER_PAR_ODD, 1), + VALUE(SER_PAR_EVEN, 2), + VALUE(SER_PAR_MARK, 3), + VALUE(SER_PAR_SPACE, 4), +) + +CONF_ENUM(serflow, + VALUE(SER_FLOW_NONE, 0), + VALUE(SER_FLOW_XONXOFF, 1), + VALUE(SER_FLOW_RTSCTS, 2), + VALUE(SER_FLOW_DSRDTR, 3), +) + +CONF_ENUM(supdup_charset, + VALUE(SUPDUP_CHARSET_ASCII, 0), + VALUE(SUPDUP_CHARSET_ITS, 1), + VALUE(SUPDUP_CHARSET_WAITS, 2), +) + +CONF_ENUM(proxy_type, + VALUE(PROXY_NONE, 0), + VALUE(PROXY_SOCKS4, 1), + VALUE(PROXY_SOCKS5, 2), + VALUE(PROXY_HTTP, 3), + VALUE(PROXY_TELNET, 4), + VALUE(PROXY_CMD, 5), + VALUE(PROXY_SSH_TCPIP, 6), + VALUE(PROXY_SSH_EXEC, 7), + VALUE(PROXY_SSH_SUBSYSTEM, 8), +) + +CONF_ENUM(old_proxy_type, + VALUE(PROXY_NONE, 0), + VALUE(PROXY_HTTP, 1), + VALUE(PROXY_SOCKS5, 2), /* really, both SOCKS 4 and 5 */ + VALUE(PROXY_TELNET, 3), + VALUE(PROXY_CMD, 4), +) + +CONF_ENUM(funky_type, + VALUE(FUNKY_TILDE, 0), + VALUE(FUNKY_LINUX, 1), + VALUE(FUNKY_XTERM, 2), + VALUE(FUNKY_VT400, 3), + VALUE(FUNKY_VT100P, 4), + VALUE(FUNKY_SCO, 5), + VALUE(FUNKY_XTERM_216, 6), +) + +CONF_ENUM(sharrow_type, + VALUE(SHARROW_APPLICATION, 0), + VALUE(SHARROW_BITMAP, 1), +) + +CONF_ENUM(remote_qtitle_action, + VALUE(TITLE_NONE, 0), + VALUE(TITLE_EMPTY, 1), + VALUE(TITLE_REAL, 2), +) + +CONF_ENUM(cursor_type, + VALUE(CURSOR_BLOCK, 0), + VALUE(CURSOR_UNDERLINE, 1), + VALUE(CURSOR_VERTICAL_LINE, 2), +) + +CONF_ENUM(beep, + VALUE(BELL_DISABLED, 0), + VALUE(BELL_DEFAULT, 1), + VALUE(BELL_VISUAL, 2), + VALUE(BELL_WAVEFILE, 3), + VALUE(BELL_PCSPEAKER, 4), +) + +CONF_ENUM(beep_indication, + VALUE(B_IND_DISABLED, 0), + VALUE(B_IND_FLASH, 1), + VALUE(B_IND_STEADY, 2), +) + +CONF_ENUM(resize_effect, + VALUE(RESIZE_TERM, 0), + VALUE(RESIZE_DISABLED, 1), + VALUE(RESIZE_FONT, 2), + VALUE(RESIZE_EITHER, 3), +) + +CONF_ENUM(font_quality, + VALUE(FQ_DEFAULT, 0), + VALUE(FQ_ANTIALIASED, 1), + VALUE(FQ_NONANTIALIASED, 2), + VALUE(FQ_CLEARTYPE, 3), +) + +CONF_ENUM(log_type, + VALUE(LGTYP_NONE, 0), + VALUE(LGTYP_ASCII, 1), + VALUE(LGTYP_DEBUG, 2), + VALUE(LGTYP_PACKETS, 3), + VALUE(LGTYP_SSHRAW, 4), +) + +CONF_ENUM(log_to_existing_file, + VALUE(LGXF_OVR, 1), + VALUE(LGXF_APN, 0), + VALUE(LGXF_ASK, -1), +) + +CONF_ENUM(bold_style, + VALUE(BOLD_STYLE_FONT, 0), + VALUE(BOLD_STYLE_COLOUR, 1), + VALUE(BOLD_STYLE_FONT | BOLD_STYLE_COLOUR, 2), +) + +CONF_ENUM(mouse_buttons, + VALUE(MOUSE_COMPROMISE, 0), + VALUE(MOUSE_XTERM, 1), + VALUE(MOUSE_WINDOWS, 2), +) + +CONF_ENUM(line_drawing, + VALUE(VT_XWINDOWS, 0), + VALUE(VT_OEMANSI, 1), + VALUE(VT_OEMONLY, 2), + VALUE(VT_POORMAN, 3), + VALUE(VT_UNICODE, 4), +) + +CONF_ENUM(x11_auth, + VALUE(X11_NO_AUTH, 0), + VALUE(X11_MIT, 1), + VALUE(X11_XDM, 2), +) diff --git a/conf.h b/conf.h new file mode 100644 index 00000000..20875f2b --- /dev/null +++ b/conf.h @@ -0,0 +1,1288 @@ +/* + * Master list of configuration options living in the Conf data + * structure. + * + * Each CONF_OPTION directive defines a single CONF_foo primary key in + * Conf, and can be equipped with the following properties: + * + * - VALUE_TYPE: the type of data associated with that key + * - SUBKEY_TYPE: if the primary key goes with a subkey (that is, the + * primary key identifies some mapping from subkeys to values), the + * data type of the subkey + * - DEFAULT_INT, DEFAULT_STR, DEFAULT_BOOL: the default value for + * the key, if no save data is available. Must match VALUE_TYPE, if + * the key has no subkey. Otherwise, no default is permitted, and + * the default value of the mapping is assumed to be empty (and if + * not, then LOAD_CUSTOM code must override that). + * - SAVE_KEYWORD: the keyword used for the option in the Windows + * registry or ~/.putty/sessions save files. + * - STORAGE_ENUM: for int-typed settings with no subkeys, this + * identifies an enumeration in conf-enums.h which maps internal + * values of the setting in the Conf to values in the saved data. + * - LOAD_CUSTOM, SAVE_CUSTOM: suppress automated loading or saving + * (respectively) of this setting, in favour of manual code in + * settings.c load_open_settings() or save_open_settings() + * respectively. + * - NOT_SAVED: indicate that this setting is not part of saved + * session data at all. + */ + +CONF_OPTION(host, + VALUE_TYPE(STR), + DEFAULT_STR(""), + SAVE_KEYWORD("HostName"), +) +CONF_OPTION(port, + VALUE_TYPE(INT), + SAVE_KEYWORD("PortNumber"), + LOAD_CUSTOM, /* default value depends on the value of CONF_protocol */ +) +CONF_OPTION(protocol, + VALUE_TYPE(INT), /* PROT_SSH, PROT_TELNET etc */ + LOAD_CUSTOM, SAVE_CUSTOM, + /* + * Notionally SAVE_KEYWORD("Protocol"), but saving/loading is handled by + * custom code because the stored value is a string representation + * of the protocol name. + */ +) +CONF_OPTION(addressfamily, + VALUE_TYPE(INT), + DEFAULT_INT(ADDRTYPE_UNSPEC), + SAVE_KEYWORD("AddressFamily"), + STORAGE_ENUM(addressfamily), +) +CONF_OPTION(close_on_exit, + VALUE_TYPE(INT), + DEFAULT_INT(AUTO), + SAVE_KEYWORD("CloseOnExit"), + STORAGE_ENUM(off_auto_on), +) +CONF_OPTION(warn_on_close, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(true), + SAVE_KEYWORD("WarnOnClose"), +) +CONF_OPTION(ping_interval, + VALUE_TYPE(INT), /* in seconds */ + LOAD_CUSTOM, SAVE_CUSTOM, + /* + * Saving/loading is handled by custom code because for historical + * reasons this value corresponds to two save keywords, + * "PingInterval" (measured in minutes) and "PingIntervalSecs" + * (measured in seconds), which are added together on loading. + * Rationale: the value was once measured in minutes, and the + * seconds field was added later. + */ +) +CONF_OPTION(tcp_nodelay, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(true), + SAVE_KEYWORD("TCPNoDelay"), +) +CONF_OPTION(tcp_keepalives, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("TCPKeepalives"), +) +CONF_OPTION(loghost, /* logical host being contacted, for host key check */ + VALUE_TYPE(STR), + DEFAULT_STR(""), + SAVE_KEYWORD("LogHost"), +) + +/* Proxy options */ +CONF_OPTION(proxy_exclude_list, + VALUE_TYPE(STR), + DEFAULT_STR(""), + SAVE_KEYWORD("ProxyExcludeList"), +) +CONF_OPTION(proxy_dns, + VALUE_TYPE(INT), + DEFAULT_INT(AUTO), + SAVE_KEYWORD("ProxyDNS"), + STORAGE_ENUM(off_auto_on), +) +CONF_OPTION(even_proxy_localhost, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("ProxyLocalhost"), +) +CONF_OPTION(proxy_type, + VALUE_TYPE(INT), /* PROXY_NONE, PROXY_SOCKS4, ... */ + STORAGE_ENUM(proxy_type), + SAVE_KEYWORD("ProxyMethod"), + LOAD_CUSTOM, + /* + * Custom load code: there was an earlier keyword "ProxyType" + * using a different enumeration, in which SOCKS4 and SOCKS5 + * shared a value, and a second keyword "ProxySOCKSVersion" + * disambiguated. + */ +) +CONF_OPTION(proxy_host, + VALUE_TYPE(STR), + DEFAULT_STR("proxy"), + SAVE_KEYWORD("ProxyHost"), +) +CONF_OPTION(proxy_port, + VALUE_TYPE(INT), + DEFAULT_INT(80), + SAVE_KEYWORD("ProxyPort"), +) +CONF_OPTION(proxy_username, + VALUE_TYPE(STR), + DEFAULT_STR(""), + SAVE_KEYWORD("ProxyUsername"), +) +CONF_OPTION(proxy_password, + VALUE_TYPE(STR), + DEFAULT_STR(""), + SAVE_KEYWORD("ProxyPassword"), +) +CONF_OPTION(proxy_telnet_command, + VALUE_TYPE(STR), + DEFAULT_STR("connect %host %port\\n"), + SAVE_KEYWORD("ProxyTelnetCommand"), +) +CONF_OPTION(proxy_log_to_term, + VALUE_TYPE(INT), + DEFAULT_INT(FORCE_OFF), + SAVE_KEYWORD("ProxyLogToTerm"), + STORAGE_ENUM(on_off_auto), +) + +/* SSH options */ +CONF_OPTION(remote_cmd, + VALUE_TYPE(STR_AMBI), + DEFAULT_STR(""), + SAVE_KEYWORD("RemoteCommand"), +) +CONF_OPTION(remote_cmd2, + /* + * Fallback command to try to run if remote_cmd fails. Only set + * internally by PSCP and PSFTP (so that they can try multiple + * methods of running an SFTP server at the remote end); never set + * by user configuration, or loaded or saved. + */ + VALUE_TYPE(STR_AMBI), + DEFAULT_STR(""), + NOT_SAVED, +) +CONF_OPTION(nopty, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("NoPTY"), +) +CONF_OPTION(compression, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("Compression"), +) +CONF_OPTION(ssh_kexlist, + SUBKEY_TYPE(INT), /* indices in preference order: 0,...,KEX_MAX-1 + * (lower is more preferred) */ + VALUE_TYPE(INT), /* KEX_* enum values */ + LOAD_CUSTOM, SAVE_CUSTOM, /* necessary for preference lists */ +) +CONF_OPTION(ssh_hklist, + SUBKEY_TYPE(INT), /* indices in preference order: 0,...,HK_MAX-1 + * (lower is more preferred) */ + VALUE_TYPE(INT), /* HK_* enum values */ + LOAD_CUSTOM, SAVE_CUSTOM, /* necessary for preference lists */ +) +CONF_OPTION(ssh_prefer_known_hostkeys, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(true), + SAVE_KEYWORD("PreferKnownHostKeys"), +) +CONF_OPTION(ssh_rekey_time, + VALUE_TYPE(INT), /* in minutes */ + DEFAULT_INT(60), + SAVE_KEYWORD("RekeyTime"), +) +CONF_OPTION(ssh_rekey_data, + VALUE_TYPE(STR), /* string encoding e.g. "100K", "2M", "1G" */ + DEFAULT_STR("1G"), + SAVE_KEYWORD("RekeyBytes"), +) +CONF_OPTION(tryagent, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(true), + SAVE_KEYWORD("TryAgent"), +) +CONF_OPTION(agentfwd, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("AgentFwd"), +) +CONF_OPTION(change_username, /* allow username switching in SSH-2 */ + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("ChangeUsername"), +) +CONF_OPTION(ssh_cipherlist, + SUBKEY_TYPE(INT), /* indices in preference order: 0,...,CIPHER_MAX-1 + * (lower is more preferred) */ + VALUE_TYPE(INT), /* CIPHER_* enum values */ + LOAD_CUSTOM, SAVE_CUSTOM, /* necessary for preference lists */ +) +CONF_OPTION(keyfile, + VALUE_TYPE(FILENAME), + SAVE_KEYWORD("PublicKeyFile"), +) +CONF_OPTION(detached_cert, + VALUE_TYPE(FILENAME), + SAVE_KEYWORD("DetachedCertificate"), +) +CONF_OPTION(auth_plugin, + VALUE_TYPE(STR), + DEFAULT_STR(""), + SAVE_KEYWORD("AuthPlugin"), +) +CONF_OPTION(sshprot, + /* + * Which SSH protocol to use. + * + * For historical reasons, the current legal values for CONF_sshprot + * are: + * 0 = SSH-1 only + * 3 = SSH-2 only + * + * We used to also support + * 1 = SSH-1 with fallback to SSH-2 + * 2 = SSH-2 with fallback to SSH-1 + * + * and we continue to use 0/3 in storage formats rather than the more + * obvious 1/2 to avoid surprises if someone saves a session and later + * downgrades PuTTY. So it's easier to use these numbers internally too. + */ + VALUE_TYPE(INT), + DEFAULT_INT(3), + SAVE_KEYWORD("SshProt"), + STORAGE_ENUM(ssh_protocol), +) +CONF_OPTION(ssh_simple, + /* + * This means that we promise never to open any channel other + * than the main one, which means it can safely use a very large + * window in SSH-2. + * + * Only ever set internally by file transfer tools; never set by + * user configuration, or loaded or saved. + */ + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + NOT_SAVED, +) +CONF_OPTION(ssh_connection_sharing, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("ConnectionSharing"), +) +CONF_OPTION(ssh_connection_sharing_upstream, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(true), + SAVE_KEYWORD("ConnectionSharingUpstream"), +) +CONF_OPTION(ssh_connection_sharing_downstream, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(true), + SAVE_KEYWORD("ConnectionSharingDownstream"), +) +CONF_OPTION(ssh_manual_hostkeys, + /* + * Manually configured host keys to accept regardless of the state + * of the host key cache. + * + * This is conceptually a set rather than a dictionary: every + * value in this map is the empty string, and the set of subkeys + * that exist is the important data. + */ + SUBKEY_TYPE(STR), + VALUE_TYPE(STR), + LOAD_CUSTOM, SAVE_CUSTOM, /* necessary for mappings */ +) +CONF_OPTION(ssh2_des_cbc, /* "des-cbc" unrecommended SSH-2 cipher */ + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("SSH2DES"), +) +CONF_OPTION(ssh_no_userauth, /* bypass "ssh-userauth" (SSH-2 only) */ + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("SshNoAuth"), +) +CONF_OPTION(ssh_no_trivial_userauth, /* disable trivial types of auth */ + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("SshNoTrivialAuth"), +) +CONF_OPTION(ssh_show_banner, /* show USERAUTH_BANNERs (SSH-2 only) */ + VALUE_TYPE(BOOL), + DEFAULT_BOOL(true), + SAVE_KEYWORD("SshBanner"), +) +CONF_OPTION(try_tis_auth, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("AuthTIS"), +) +CONF_OPTION(try_ki_auth, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(true), + SAVE_KEYWORD("AuthKI"), +) +CONF_OPTION(try_gssapi_auth, /* attempt gssapi via ssh userauth */ + VALUE_TYPE(BOOL), + LOAD_CUSTOM, SAVE_CUSTOM, /* under #ifndef NO_GSSAPI */ +) +CONF_OPTION(try_gssapi_kex, /* attempt gssapi via ssh kex */ + VALUE_TYPE(BOOL), + LOAD_CUSTOM, SAVE_CUSTOM, /* under #ifndef NO_GSSAPI */ +) +CONF_OPTION(gssapifwd, /* forward tgt via gss */ + VALUE_TYPE(BOOL), + LOAD_CUSTOM, SAVE_CUSTOM, /* under #ifndef NO_GSSAPI */ +) +CONF_OPTION(gssapirekey, /* KEXGSS refresh interval (mins) */ + VALUE_TYPE(INT), + LOAD_CUSTOM, SAVE_CUSTOM, /* under #ifndef NO_GSSAPI */ +) +CONF_OPTION(ssh_gsslist, + SUBKEY_TYPE(INT), /* indices in preference order: 0,...,ngsslibs + * (lower is more preferred; ngsslibs is a platform- + * dependent value) */ + VALUE_TYPE(INT), /* indices of GSSAPI lib types (platform-dependent) */ + LOAD_CUSTOM, SAVE_CUSTOM, /* necessary for preference lists, also this + * setting is under #ifndef NO_GSSAPI */ +) +CONF_OPTION(ssh_gss_custom, + VALUE_TYPE(FILENAME), + LOAD_CUSTOM, SAVE_CUSTOM, /* under #ifndef NO_GSSAPI */ +) +CONF_OPTION(ssh_subsys, /* run a subsystem rather than a command */ + /* + * Only set internally by PSCP and PSFTP; never set by user + * configuration, or loaded or saved. + */ + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + NOT_SAVED, +) +CONF_OPTION(ssh_subsys2, /* fallback to go with remote_cmd2 */ + /* + * Only set internally by PSCP and PSFTP; never set by user + * configuration, or loaded or saved. + */ + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + NOT_SAVED, +) +CONF_OPTION(ssh_no_shell, /* avoid running a shell */ + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("SshNoShell"), +) +CONF_OPTION(ssh_nc_host, /* host to connect to in `nc' mode */ + /* + * Only set by the '-nc' command-line option and by the SSH proxy + * code. There's no GUI config option for this, and therefore it's + * also never loaded or saved. + */ + VALUE_TYPE(STR), + DEFAULT_STR(""), + NOT_SAVED, +) +CONF_OPTION(ssh_nc_port, /* port to connect to in `nc' mode */ + /* + * Only set by the '-nc' command-line option and by the SSH proxy + * code. There's no GUI config option for this, and therefore it's + * also never loaded or saved. + */ + VALUE_TYPE(INT), + DEFAULT_INT(0), + NOT_SAVED, +) + +/* Telnet options */ +CONF_OPTION(termtype, + VALUE_TYPE(STR), + DEFAULT_STR("xterm"), + SAVE_KEYWORD("TerminalType"), +) +CONF_OPTION(termspeed, + VALUE_TYPE(STR), + DEFAULT_STR("38400,38400"), + SAVE_KEYWORD("TerminalSpeed"), +) +CONF_OPTION(ttymodes, + /* + * The full set of permitted subkeys is listed in + * ssh/ttymode-list.h, as the first parameter of each TTYMODE_CHAR + * or TTYMODE_FLAG macro. + * + * The permitted value strings are: + * + * - "N" means do not include a record for this mode at all in + * the terminal mode data in the "pty-req" channel request. + * Corresponds to setting the mode to 'Nothing' in the GUI. + * - "A" means use PuTTY's automatic default, matching the + * settings for GUI PuTTY's terminal window or Unix Plink's + * controlling tty. Corresponds to setting 'Auto' in the GUI. + * - "V" followed by further string data means send a custom + * value to the SSH server. Values are as documented in the + * manual. + */ + SUBKEY_TYPE(STR), + VALUE_TYPE(STR), + LOAD_CUSTOM, SAVE_CUSTOM, /* necessary for mappings */ +) +CONF_OPTION(environmt, + SUBKEY_TYPE(STR), /* environment variable name */ + VALUE_TYPE(STR), /* environment variable value */ + LOAD_CUSTOM, SAVE_CUSTOM, /* necessary for mappings */ +) +CONF_OPTION(username, + VALUE_TYPE(STR_AMBI), + DEFAULT_STR(""), + SAVE_KEYWORD("UserName"), +) +CONF_OPTION(username_from_env, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("UserNameFromEnvironment"), +) +CONF_OPTION(localusername, + VALUE_TYPE(STR), + DEFAULT_STR(""), + SAVE_KEYWORD("LocalUserName"), +) +CONF_OPTION(rfc_environ, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("RFCEnviron"), +) +CONF_OPTION(passive_telnet, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("PassiveTelnet"), +) + +/* Serial port options */ +CONF_OPTION(serline, + VALUE_TYPE(STR), + DEFAULT_STR(""), + SAVE_KEYWORD("SerialLine"), +) +CONF_OPTION(serspeed, + VALUE_TYPE(INT), + DEFAULT_INT(115200), + SAVE_KEYWORD("SerialSpeed"), +) +CONF_OPTION(serdatabits, + VALUE_TYPE(INT), + DEFAULT_INT(8), + SAVE_KEYWORD("SerialDataBits"), +) +CONF_OPTION(serstopbits, + VALUE_TYPE(INT), + DEFAULT_INT(2), + SAVE_KEYWORD("SerialStopHalfbits"), +) +CONF_OPTION(serparity, + VALUE_TYPE(INT), + DEFAULT_INT(SER_PAR_NONE), + SAVE_KEYWORD("SerialParity"), + STORAGE_ENUM(serparity), +) +CONF_OPTION(serflow, + VALUE_TYPE(INT), + DEFAULT_INT(SER_FLOW_NONE), + SAVE_KEYWORD("SerialFlowControl"), + STORAGE_ENUM(serflow), +) + +/* SUPDUP options */ +CONF_OPTION(supdup_location, + VALUE_TYPE(STR), + DEFAULT_STR("The Internet"), + SAVE_KEYWORD("SUPDUPLocation"), +) +CONF_OPTION(supdup_ascii_set, + VALUE_TYPE(INT), + DEFAULT_INT(SUPDUP_CHARSET_ASCII), + SAVE_KEYWORD("SUPDUPCharset"), + STORAGE_ENUM(supdup_charset), +) +CONF_OPTION(supdup_more, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("SUPDUPMoreProcessing"), +) +CONF_OPTION(supdup_scroll, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("SUPDUPScrolling"), +) + +/* Keyboard options */ +CONF_OPTION(bksp_is_delete, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(true), + SAVE_KEYWORD("BackspaceIsDelete"), +) +CONF_OPTION(rxvt_homeend, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("RXVTHomeEnd"), +) +CONF_OPTION(funky_type, + VALUE_TYPE(INT), + DEFAULT_INT(FUNKY_XTERM), + SAVE_KEYWORD("LinuxFunctionKeys"), + STORAGE_ENUM(funky_type), +) +CONF_OPTION(sharrow_type, + VALUE_TYPE(INT), + DEFAULT_INT(SHARROW_APPLICATION), + SAVE_KEYWORD("ShiftedArrowKeys"), + STORAGE_ENUM(sharrow_type), +) +CONF_OPTION(no_applic_c, /* totally disable app cursor keys */ + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("NoApplicationCursors"), +) +CONF_OPTION(no_applic_k, /* totally disable app keypad */ + VALUE_TYPE(BOOL), + DEFAULT_BOOL(true), + SAVE_KEYWORD("NoApplicationKeys"), +) +CONF_OPTION(no_mouse_rep, /* totally disable mouse reporting */ + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("NoMouseReporting"), +) +CONF_OPTION(no_remote_resize, /* disable remote resizing */ + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("NoRemoteResize"), +) +CONF_OPTION(no_alt_screen, /* disable alternate screen */ + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("NoAltScreen"), +) +CONF_OPTION(no_remote_wintitle, /* disable remote retitling */ + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("NoRemoteWinTitle"), +) +CONF_OPTION(no_remote_clearscroll, /* disable ESC[3J */ + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("NoRemoteClearScroll"), +) +CONF_OPTION(no_dbackspace, /* disable destructive backspace */ + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("NoDBackspace"), +) +CONF_OPTION(no_remote_charset, /* disable remote charset config */ + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("NoRemoteCharset"), +) +CONF_OPTION(remote_qtitle_action, /* handling of remote window title queries */ + VALUE_TYPE(INT), + STORAGE_ENUM(remote_qtitle_action), + SAVE_KEYWORD("RemoteQTitleAction"), + LOAD_CUSTOM, /* older versions had a boolean "NoRemoteQTitle" + * before we ended up with three options */ +) +CONF_OPTION(app_cursor, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("ApplicationCursorKeys"), +) +CONF_OPTION(app_keypad, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("ApplicationKeypad"), +) +CONF_OPTION(nethack_keypad, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("NetHackKeypad"), +) +CONF_OPTION(telnet_keyboard, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("TelnetKey"), +) +CONF_OPTION(telnet_newline, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(true), + SAVE_KEYWORD("TelnetRet"), +) +CONF_OPTION(alt_f4, /* is it special? */ + VALUE_TYPE(BOOL), + DEFAULT_BOOL(true), + SAVE_KEYWORD("AltF4"), +) +CONF_OPTION(alt_space, /* is it special? */ + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("AltSpace"), +) +CONF_OPTION(alt_only, /* is it special? */ + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("AltOnly"), +) +CONF_OPTION(localecho, + VALUE_TYPE(INT), + DEFAULT_INT(AUTO), + SAVE_KEYWORD("LocalEcho"), + STORAGE_ENUM(on_off_auto), +) +CONF_OPTION(localedit, + VALUE_TYPE(INT), + DEFAULT_INT(AUTO), + SAVE_KEYWORD("LocalEdit"), + STORAGE_ENUM(on_off_auto), +) +CONF_OPTION(alwaysontop, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("AlwaysOnTop"), +) +CONF_OPTION(fullscreenonaltenter, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("FullScreenOnAltEnter"), +) +CONF_OPTION(scroll_on_key, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("ScrollOnKey"), +) +CONF_OPTION(scroll_on_disp, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(true), + SAVE_KEYWORD("ScrollOnDisp"), +) +CONF_OPTION(erase_to_scrollback, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(true), + SAVE_KEYWORD("EraseToScrollback"), +) +CONF_OPTION(compose_key, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("ComposeKey"), +) +CONF_OPTION(ctrlaltkeys, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(true), + SAVE_KEYWORD("CtrlAltKeys"), +) +CONF_OPTION(osx_option_meta, + VALUE_TYPE(BOOL), + LOAD_CUSTOM, SAVE_CUSTOM, /* under #ifdef OSX_META_KEY_CONFIG */ +) +CONF_OPTION(osx_command_meta, + VALUE_TYPE(BOOL), + LOAD_CUSTOM, SAVE_CUSTOM, /* under #ifdef OSX_META_KEY_CONFIG */ +) +CONF_OPTION(wintitle, /* initial window title */ + VALUE_TYPE(STR), + DEFAULT_STR(""), + SAVE_KEYWORD("WinTitle"), +) +/* Terminal options */ +CONF_OPTION(savelines, + VALUE_TYPE(INT), + DEFAULT_INT(9999), + SAVE_KEYWORD("ScrollbackLines"), +) +CONF_OPTION(dec_om, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("DECOriginMode"), +) +CONF_OPTION(wrap_mode, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(true), + SAVE_KEYWORD("AutoWrapMode"), +) +CONF_OPTION(lfhascr, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("LFImpliesCR"), +) +CONF_OPTION(cursor_type, + VALUE_TYPE(INT), + DEFAULT_INT(0), + SAVE_KEYWORD("CurType"), + STORAGE_ENUM(cursor_type), +) +CONF_OPTION(blink_cur, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("BlinkCur"), +) +CONF_OPTION(beep, + VALUE_TYPE(INT), + DEFAULT_INT(BELL_DEFAULT), + SAVE_KEYWORD("Beep"), + STORAGE_ENUM(beep), +) +CONF_OPTION(beep_ind, + VALUE_TYPE(INT), + DEFAULT_INT(B_IND_DISABLED), + SAVE_KEYWORD("BeepInd"), + STORAGE_ENUM(beep_indication), +) +CONF_OPTION(bellovl, /* bell overload protection active? */ + VALUE_TYPE(BOOL), + DEFAULT_BOOL(true), + SAVE_KEYWORD("BellOverload"), +) +CONF_OPTION(bellovl_n, /* number of bells to cause overload */ + VALUE_TYPE(INT), + DEFAULT_INT(5), + SAVE_KEYWORD("BellOverloadN"), +) +CONF_OPTION(bellovl_t, /* time interval for overload (ticks) */ + VALUE_TYPE(INT), + LOAD_CUSTOM, SAVE_CUSTOM, + /* + * Loading and saving is done in custom code because the format is + * platform-dependent for historical reasons: on Unix, the stored + * value is multiplied by 1000. (And since TICKSPERSEC=1000 on + * that platform, it means the stored value is interpreted in + * microseconds.) + */ +) +CONF_OPTION(bellovl_s, /* period of silence to re-enable bell (s) */ + VALUE_TYPE(INT), + LOAD_CUSTOM, SAVE_CUSTOM, + /* + * Loading and saving is done in custom code because the format is + * platform-dependent for historical reasons: on Unix, the stored + * value is multiplied by 1000. (And since TICKSPERSEC=1000 on + * that platform, it means the stored value is interpreted in + * microseconds.) + */ +) +CONF_OPTION(bell_wavefile, + VALUE_TYPE(FILENAME), + SAVE_KEYWORD("BellWaveFile"), +) +CONF_OPTION(scrollbar, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(true), + SAVE_KEYWORD("ScrollBar"), +) +CONF_OPTION(scrollbar_in_fullscreen, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("ScrollBarFullScreen"), +) +CONF_OPTION(resize_action, + VALUE_TYPE(INT), + DEFAULT_INT(RESIZE_TERM), + SAVE_KEYWORD("LockSize"), + STORAGE_ENUM(resize_effect), +) +CONF_OPTION(bce, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(true), + SAVE_KEYWORD("BCE"), +) +CONF_OPTION(blinktext, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("BlinkText"), +) +CONF_OPTION(win_name_always, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(true), + SAVE_KEYWORD("WinNameAlways"), +) +CONF_OPTION(width, + VALUE_TYPE(INT), + DEFAULT_INT(80), + SAVE_KEYWORD("TermWidth"), +) +CONF_OPTION(height, + VALUE_TYPE(INT), + DEFAULT_INT(24), + SAVE_KEYWORD("TermHeight"), +) +CONF_OPTION(font, + VALUE_TYPE(FONT), + SAVE_KEYWORD("Font"), +) +CONF_OPTION(font_quality, + VALUE_TYPE(INT), + DEFAULT_INT(FQ_DEFAULT), + SAVE_KEYWORD("FontQuality"), + STORAGE_ENUM(font_quality), +) +CONF_OPTION(logfilename, + VALUE_TYPE(FILENAME), + SAVE_KEYWORD("LogFileName"), +) +CONF_OPTION(logtype, + VALUE_TYPE(INT), + DEFAULT_INT(LGTYP_NONE), + SAVE_KEYWORD("LogType"), + STORAGE_ENUM(log_type), +) +CONF_OPTION(logxfovr, + VALUE_TYPE(INT), + DEFAULT_INT(LGXF_ASK), + SAVE_KEYWORD("LogFileClash"), + STORAGE_ENUM(log_to_existing_file), +) +CONF_OPTION(logflush, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(true), + SAVE_KEYWORD("LogFlush"), +) +CONF_OPTION(logheader, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(true), + SAVE_KEYWORD("LogHeader"), +) +CONF_OPTION(logomitpass, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(true), + SAVE_KEYWORD("SSHLogOmitPasswords"), +) +CONF_OPTION(logomitdata, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("SSHLogOmitData"), +) +CONF_OPTION(hide_mouseptr, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("HideMousePtr"), +) +CONF_OPTION(sunken_edge, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("SunkenEdge"), +) +CONF_OPTION(window_border, + VALUE_TYPE(INT), /* in pixels */ + DEFAULT_INT(1), + SAVE_KEYWORD("WindowBorder"), +) +CONF_OPTION(answerback, + VALUE_TYPE(STR), + DEFAULT_STR("PuTTY"), + SAVE_KEYWORD("Answerback"), +) +CONF_OPTION(printer, + VALUE_TYPE(STR), + DEFAULT_STR(""), + SAVE_KEYWORD("Printer"), +) +CONF_OPTION(no_arabicshaping, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("DisableArabicShaping"), +) +CONF_OPTION(no_bidi, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("DisableBidi"), +) +CONF_OPTION(no_bracketed_paste, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("DisableBracketedPaste"), +) + +/* Colour options */ +CONF_OPTION(ansi_colour, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(true), + SAVE_KEYWORD("ANSIColour"), +) +CONF_OPTION(xterm_256_colour, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(true), + SAVE_KEYWORD("Xterm256Colour"), +) +CONF_OPTION(true_colour, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(true), + SAVE_KEYWORD("TrueColour"), +) +CONF_OPTION(system_colour, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("UseSystemColours"), +) +CONF_OPTION(try_palette, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("TryPalette"), +) +CONF_OPTION(bold_style, + VALUE_TYPE(INT), + DEFAULT_INT(2), + SAVE_KEYWORD("BoldAsColour"), + STORAGE_ENUM(bold_style), +) +CONF_OPTION(colours, + /* + * Subkeys in this setting are indexed based on the CONF_COLOUR_* + * enum values in putty.h. But each subkey identifies just one + * component of the RGB value. Subkey 3*a+b identifies colour #a, + * channel #b, where channels 0,1,2 mean R,G,B respectively. + * + * Values are 8-bit integers. + */ + SUBKEY_TYPE(INT), + VALUE_TYPE(INT), + LOAD_CUSTOM, SAVE_CUSTOM, /* necessary for mappings */ +) + +/* Selection options */ +CONF_OPTION(mouse_is_xterm, + VALUE_TYPE(INT), + DEFAULT_INT(0), + SAVE_KEYWORD("MouseIsXterm"), + STORAGE_ENUM(mouse_buttons), +) +CONF_OPTION(rect_select, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("RectSelect"), +) +CONF_OPTION(paste_controls, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("PasteControls"), +) +CONF_OPTION(rawcnp, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("RawCNP"), +) +CONF_OPTION(utf8linedraw, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("UTF8linedraw"), +) +CONF_OPTION(rtf_paste, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("PasteRTF"), +) +CONF_OPTION(mouse_override, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(true), + SAVE_KEYWORD("MouseOverride"), +) +CONF_OPTION(wordness, + SUBKEY_TYPE(INT), /* ASCII character codes (literally, just 00-7F) */ + VALUE_TYPE(INT), /* arbitrary equivalence-class value for that char */ + LOAD_CUSTOM, SAVE_CUSTOM, /* necessary for mappings */ +) +CONF_OPTION(mouseautocopy, + /* + * What clipboard (if any) to copy text to as soon as it's + * selected with the mouse. + */ + VALUE_TYPE(BOOL), + DEFAULT_BOOL(CLIPUI_DEFAULT_AUTOCOPY), /* platform-dependent bool-valued + * macro */ + SAVE_KEYWORD("MouseAutocopy"), +) +CONF_OPTION(mousepaste, /* clipboard used by one-mouse-click paste actions */ + VALUE_TYPE(INT), + LOAD_CUSTOM, SAVE_CUSTOM, + /* + * SAVE_KEYWORD("MousePaste"), but loading and saving is done by + * custom code, because the saved value is a string, and also sets + * CONF_mousepaste_custom + */ +) +CONF_OPTION(ctrlshiftins, /* clipboard used by Ctrl+Ins and Shift+Ins */ + VALUE_TYPE(INT), + LOAD_CUSTOM, SAVE_CUSTOM, + /* + * SAVE_KEYWORD("CtrlShiftIns"), but loading and saving is done by + * custom code, because the saved value is a string, and also sets + * CONF_ctrlshiftins_custom + */ +) +CONF_OPTION(ctrlshiftcv, /* clipboard used by Ctrl+Shift+C and Ctrl+Shift+V */ + VALUE_TYPE(INT), + LOAD_CUSTOM, SAVE_CUSTOM, + /* + * SAVE_KEYWORD("CtrlShiftCV"), but loading and saving is done by + * custom code, because the saved value is a string, and also sets + * CONF_ctrlshiftcv_custom + */ +) +CONF_OPTION(mousepaste_custom, + /* Custom clipboard name if CONF_mousepaste is set to CLIPUI_CUSTOM */ + VALUE_TYPE(STR), + LOAD_CUSTOM, SAVE_CUSTOM, + /* + * Loading and saving is handled by custom code in conjunction + * with CONF_mousepaste + */ +) +CONF_OPTION(ctrlshiftins_custom, + /* Custom clipboard name if CONF_ctrlshiftins is set to CLIPUI_CUSTOM */ + VALUE_TYPE(STR), + LOAD_CUSTOM, SAVE_CUSTOM, + /* + * Loading and saving is handled by custom code in conjunction + * with CONF_ctrlshiftins + */ +) +CONF_OPTION(ctrlshiftcv_custom, + /* Custom clipboard name if CONF_ctrlshiftcv is set to CLIPUI_CUSTOM */ + VALUE_TYPE(STR), + LOAD_CUSTOM, SAVE_CUSTOM, + /* + * Loading and saving is handled by custom code in conjunction + * with CONF_ctrlshiftcv + */ +) + +/* Character-set translation */ +CONF_OPTION(vtmode, + VALUE_TYPE(INT), + DEFAULT_INT(VT_UNICODE), + SAVE_KEYWORD("FontVTMode"), + STORAGE_ENUM(line_drawing), +) +CONF_OPTION(line_codepage, + VALUE_TYPE(STR), + DEFAULT_STR(""), + SAVE_KEYWORD("LineCodePage"), +) +CONF_OPTION(cjk_ambig_wide, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("CJKAmbigWide"), +) +CONF_OPTION(utf8_override, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(true), + SAVE_KEYWORD("UTF8Override"), +) +CONF_OPTION(xlat_capslockcyr, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("CapsLockCyr"), +) + +/* X11 forwarding */ +CONF_OPTION(x11_forward, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("X11Forward"), +) +CONF_OPTION(x11_display, + VALUE_TYPE(STR), + DEFAULT_STR(""), + SAVE_KEYWORD("X11Display"), +) +CONF_OPTION(x11_auth, + VALUE_TYPE(INT), + DEFAULT_INT(X11_MIT), + SAVE_KEYWORD("X11AuthType"), + STORAGE_ENUM(x11_auth), +) +CONF_OPTION(xauthfile, + VALUE_TYPE(FILENAME), + SAVE_KEYWORD("X11AuthFile"), +) + +/* Port forwarding */ +CONF_OPTION(lport_acceptall, /* accept conns from hosts other than localhost */ + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("LocalPortAcceptAll"), +) +CONF_OPTION(rport_acceptall, /* same for remote forwarded ports */ + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("RemotePortAcceptAll"), +) +CONF_OPTION(portfwd, + /* + * Subkeys for 'portfwd' can have the following forms: + * + * [LR]localport + * [LR]localaddr:localport + * + * Dynamic forwardings are indicated by an 'L' key, and the + * special value "D". For all other forwardings, the value should + * be of the form 'host:port'. + */ + SUBKEY_TYPE(STR), + VALUE_TYPE(STR), + LOAD_CUSTOM, SAVE_CUSTOM, /* necessary for mappings */ +) + +/* SSH bug compatibility modes. All FORCE_ON/FORCE_OFF/AUTO */ +CONF_OPTION(sshbug_ignore1, + VALUE_TYPE(INT), + DEFAULT_INT(AUTO), + SAVE_KEYWORD("BugIgnore1"), + STORAGE_ENUM(auto_off_on), +) +CONF_OPTION(sshbug_plainpw1, + VALUE_TYPE(INT), + DEFAULT_INT(AUTO), + SAVE_KEYWORD("BugPlainPW1"), + STORAGE_ENUM(auto_off_on), +) +CONF_OPTION(sshbug_rsa1, + VALUE_TYPE(INT), + DEFAULT_INT(AUTO), + SAVE_KEYWORD("BugRSA1"), + STORAGE_ENUM(auto_off_on), +) +CONF_OPTION(sshbug_ignore2, + VALUE_TYPE(INT), + DEFAULT_INT(AUTO), + SAVE_KEYWORD("BugIgnore2"), + STORAGE_ENUM(auto_off_on), +) +CONF_OPTION(sshbug_derivekey2, + VALUE_TYPE(INT), + DEFAULT_INT(AUTO), + SAVE_KEYWORD("BugDeriveKey2"), + STORAGE_ENUM(auto_off_on), +) +CONF_OPTION(sshbug_rsapad2, + VALUE_TYPE(INT), + DEFAULT_INT(AUTO), + SAVE_KEYWORD("BugRSAPad2"), + STORAGE_ENUM(auto_off_on), +) +CONF_OPTION(sshbug_pksessid2, + VALUE_TYPE(INT), + DEFAULT_INT(AUTO), + SAVE_KEYWORD("BugPKSessID2"), + STORAGE_ENUM(auto_off_on), +) +CONF_OPTION(sshbug_rekey2, + VALUE_TYPE(INT), + DEFAULT_INT(AUTO), + SAVE_KEYWORD("BugRekey2"), + STORAGE_ENUM(auto_off_on), +) +CONF_OPTION(sshbug_maxpkt2, + VALUE_TYPE(INT), + DEFAULT_INT(AUTO), + SAVE_KEYWORD("BugMaxPkt2"), + STORAGE_ENUM(auto_off_on), +) +CONF_OPTION(sshbug_oldgex2, + VALUE_TYPE(INT), + DEFAULT_INT(AUTO), + SAVE_KEYWORD("BugOldGex2"), + STORAGE_ENUM(auto_off_on), +) +CONF_OPTION(sshbug_winadj, + VALUE_TYPE(INT), + DEFAULT_INT(AUTO), + SAVE_KEYWORD("BugWinadj"), + STORAGE_ENUM(auto_off_on), +) +CONF_OPTION(sshbug_chanreq, + VALUE_TYPE(INT), + DEFAULT_INT(AUTO), + SAVE_KEYWORD("BugChanReq"), + STORAGE_ENUM(auto_off_on), +) +CONF_OPTION(sshbug_dropstart, + VALUE_TYPE(INT), + DEFAULT_INT(FORCE_OFF), + SAVE_KEYWORD("BugDropStart"), + STORAGE_ENUM(off1_on2), +) +CONF_OPTION(sshbug_filter_kexinit, + VALUE_TYPE(INT), + DEFAULT_INT(FORCE_OFF), + SAVE_KEYWORD("BugFilterKexinit"), + STORAGE_ENUM(off1_on2), +) +CONF_OPTION(sshbug_rsa_sha2_cert_userauth, + VALUE_TYPE(INT), + DEFAULT_INT(AUTO), + SAVE_KEYWORD("BugRSASHA2CertUserauth"), + STORAGE_ENUM(auto_off_on), +) +CONF_OPTION(sshbug_hmac2, + VALUE_TYPE(INT), + DEFAULT_INT(AUTO), + SAVE_KEYWORD("BugHMAC2"), + STORAGE_ENUM(auto_off_on), + LOAD_CUSTOM, /* there was an earlier keyword called "BuggyMAC" */ +) + +/* Options for Unix. Should split out into platform-dependent part. */ +CONF_OPTION(stamp_utmp, /* used by Unix pterm */ + VALUE_TYPE(BOOL), + DEFAULT_BOOL(true), + SAVE_KEYWORD("StampUtmp"), +) +CONF_OPTION(login_shell, /* used by Unix pterm */ + VALUE_TYPE(BOOL), + DEFAULT_BOOL(true), + SAVE_KEYWORD("LoginShell"), +) +CONF_OPTION(scrollbar_on_left, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("ScrollbarOnLeft"), +) +CONF_OPTION(shadowbold, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("ShadowBold"), +) +CONF_OPTION(boldfont, + VALUE_TYPE(FONT), + SAVE_KEYWORD("BoldFont"), +) +CONF_OPTION(widefont, + VALUE_TYPE(FONT), + SAVE_KEYWORD("WideFont"), +) +CONF_OPTION(wideboldfont, + VALUE_TYPE(FONT), + SAVE_KEYWORD("WideBoldFont"), +) +CONF_OPTION(shadowboldoffset, + VALUE_TYPE(INT), /* in pixels */ + DEFAULT_INT(1), + SAVE_KEYWORD("ShadowBoldOffset"), +) +CONF_OPTION(crhaslf, + VALUE_TYPE(BOOL), + DEFAULT_BOOL(false), + SAVE_KEYWORD("CRImpliesLF"), +) +CONF_OPTION(winclass, + VALUE_TYPE(STR), + DEFAULT_STR(""), + SAVE_KEYWORD("WindowClass"), +) diff --git a/config.c b/config.c index b3d68daa..66e0f990 100644 --- a/config.c +++ b/config.c @@ -122,11 +122,19 @@ void conf_editbox_handler(dlgcontrol *ctrl, dlgparam *dlg, if (type->type == EDIT_STR) { if (event == EVENT_REFRESH) { - char *field = conf_get_str(conf, key); - dlg_editbox_set(ctrl, dlg, field); + bool utf8; + char *field = conf_get_str_ambi(conf, key, &utf8); + if (utf8) + dlg_editbox_set_utf8(ctrl, dlg, field); + else + dlg_editbox_set(ctrl, dlg, field); } else if (event == EVENT_VALCHANGE) { - char *field = dlg_editbox_get(ctrl, dlg); - conf_set_str(conf, key, field); + char *field = dlg_editbox_get_utf8(ctrl, dlg); + if (!conf_try_set_utf8(conf, key, field)) { + sfree(field); + field = dlg_editbox_get(ctrl, dlg); + conf_set_str(conf, key, field); + } sfree(field); } } else { @@ -2190,6 +2198,9 @@ void setup_config_box(struct controlbox *b, bool midsession, ctrl_checkbox(s, "禁用双向文本显示(D)", 'd', HELPCTX(features_bidi), conf_checkbox_handler, I(CONF_no_bidi)); + ctrl_checkbox(s, "Disable bracketed paste mode", + 'p', HELPCTX(features_bracketed_paste), conf_checkbox_handler, + I(CONF_no_bracketed_paste)); /* * The Window panel. @@ -2248,9 +2259,9 @@ void setup_config_box(struct controlbox *b, bool midsession, HELPCTX(appearance_cursor), conf_radiobutton_handler, I(CONF_cursor_type), - "显示块(L)", 'l', I(0), - "下划线(U)", 'u', I(1), - "垂直线(V)", 'v', I(2)); + "显示块(L)", 'l', I(CURSOR_BLOCK), + "下划线(U)", 'u', I(CURSOR_UNDERLINE), + "垂直线(V)", 'v', I(CURSOR_VERTICAL_LINE)); ctrl_checkbox(s, "光标闪烁(B)", 'b', HELPCTX(appearance_cursor), conf_checkbox_handler, I(CONF_blink_cur)); @@ -2420,9 +2431,9 @@ void setup_config_box(struct controlbox *b, bool midsession, ctrl_radiobuttons(s, "粗体文字表现(B):", 'b', 3, HELPCTX(colours_bold), conf_radiobutton_handler, I(CONF_bold_style), - "字体", I(1), - "颜色", I(2), - "两者", I(3)); + "字体", I(BOLD_STYLE_FONT), + "颜色", I(BOLD_STYLE_COLOUR), + "两者", I(BOLD_STYLE_FONT | BOLD_STYLE_COLOUR)); str = dupprintf("调整 %s 显示的精确颜色", appname); s = ctrl_getset(b, "窗口/颜色", "adjust", str); diff --git a/console.c b/console.c index 81e43d5b..f63c91f4 100644 --- a/console.c +++ b/console.c @@ -26,6 +26,12 @@ const SeatDialogPromptDescriptions *console_prompt_descriptions(Seat *seat) bool console_batch_mode = false; +bool console_set_batch_mode(bool newvalue) +{ + console_batch_mode = newvalue; + return true; +} + /* * Error message and/or fatal exit functions, all based on * console_print_error_msg which the platform front end provides. @@ -69,6 +75,11 @@ void console_connection_fatal(Seat *seat, const char *msg) cleanup_exit(1); } +void console_nonfatal(Seat *seat, const char *msg) +{ + console_print_error_msg("ERROR", msg); +} + /* * Console front ends redo their select() or equivalent every time, so * they don't need separate timer handling. diff --git a/contrib/kh2reg.py b/contrib/kh2reg.py index cff06c8f..81e79a43 100755 --- a/contrib/kh2reg.py +++ b/contrib/kh2reg.py @@ -27,7 +27,7 @@ def winmungestr(s): candot = 0 r = "" for c in s: - if c in ' \*?%~' or ord(c) is a field, so the only - * non-invertible value is 0. Even so, there _is_ one, so check - * the return value! + * Invert 3*f over Z_q. This is guaranteed to succeed, since + * Z_q/ is a field, so the only non-invertible value is + * 0. And f is nonzero because it came from ntru_gen_short (hence, + * w of its components are nonzero), hence so is 3*f. */ uint16_t *f3inv = snewn(p, uint16_t); - if (!ntru_ring_invert(f3inv, f3, p, q)) { - ring_free(f, p); - ring_free(f3, p); - ring_free(f3inv, p); - ring_free(g, p); - ring_free(ginv, p); - return NULL; - } + bool expect_always_success = ntru_ring_invert(f3inv, f3, p, q); + assert(expect_always_success); /* * Make the public key, by converting g to a polynomial over q and @@ -1875,15 +1869,25 @@ static const ecdh_keyalg ssh_ntru_selector_vt = { .description = ssh_ntru_description, }; -static const ssh_kex ssh_ntru_curve25519 = { +static const ssh_kex ssh_ntru_curve25519_openssh = { .name = "sntrup761x25519-sha512@openssh.com", .main_type = KEXTYPE_ECDH, .hash = &ssh_sha512, .ecdh_vt = &ssh_ntru_selector_vt, }; +static const ssh_kex ssh_ntru_curve25519 = { + /* Same as sntrup761x25519-sha512@openssh.com but with an + * IANA-assigned name */ + .name = "sntrup761x25519-sha512", + .main_type = KEXTYPE_ECDH, + .hash = &ssh_sha512, + .ecdh_vt = &ssh_ntru_selector_vt, +}; + static const ssh_kex *const hybrid_list[] = { &ssh_ntru_curve25519, + &ssh_ntru_curve25519_openssh, }; const ssh_kexes ssh_ntru_hybrid_kex = { lenof(hybrid_list), hybrid_list }; diff --git a/crypto/rsa.c b/crypto/rsa.c index aa0e08a6..6246eda1 100644 --- a/crypto/rsa.c +++ b/crypto/rsa.c @@ -340,11 +340,11 @@ char *rsa_ssh1_fingerprint(RSAKey *key) */ char **rsa_ssh1_fake_all_fingerprints(RSAKey *key) { - char **ret = snewn(SSH_N_FPTYPES, char *); + char **fingerprints = snewn(SSH_N_FPTYPES, char *); for (unsigned i = 0; i < SSH_N_FPTYPES; i++) - ret[i] = NULL; - ret[SSH_FPTYPE_MD5] = rsa_ssh1_fingerprint(key); - return ret; + fingerprints[i] = NULL; + fingerprints[SSH_FPTYPE_MD5] = rsa_ssh1_fingerprint(key); + return fingerprints; } /* @@ -837,7 +837,36 @@ static void rsa2_sign(ssh_key *key, ptrlen data, mp_free(in); put_stringz(bs, sign_alg_name); - nbytes = (mp_get_nbits(out) + 7) / 8; + if (flags == 0) { + /* + * Original "ssh-rsa", per RFC 4253 section 6.6, stores the + * signature integer in a string without padding - not even + * the leading zero byte that an ordinary SSH-2 mpint would + * require to avoid looking like two's complement. + * + * "The value for 'rsa_signature_blob' is encoded as a string + * containing s (which is an integer, without lengths or + * padding, unsigned, and in network byte order)." + */ + nbytes = (mp_get_nbits(out) + 7) / 8; + } else { + /* + * The SHA-256 and SHA-512 signature systems, per RFC 8332 + * section 3, should be padded to the length of the key + * modulus. + * + * "The value for 'rsa_signature_blob' is encoded as a string + * that contains an octet string S (which is the output of + * RSASSA-PKCS1-v1_5) and that has the same length (in octets) + * as the RSA modulus." + * + * Awkwardly, RFC 8332 doesn't say whether that means the + * 'raw' length of the RSA modulus (that is, ceil(n/8) for an + * n-bit key) or the length it would occupy as an SSH-2 mpint. + * My interpretation is the former. + */ + nbytes = (mp_get_nbits(rsa->modulus) + 7) / 8; + } put_uint32(bs, nbytes); for (size_t i = 0; i < nbytes; i++) put_byte(bs, mp_get_byte(out, nbytes - 1 - i)); diff --git a/defs.h b/defs.h index 8b1f2712..d8bfe02a 100644 --- a/defs.h +++ b/defs.h @@ -74,6 +74,12 @@ uintmax_t strtoumax(const char *nptr, char **endptr, int base); #endif /* __GNUC__ */ typedef struct conf_tag Conf; +typedef struct ConfKeyInfo ConfKeyInfo; +typedef struct ConfSaveEnumValue ConfSaveEnumValue; +typedef struct ConfSaveEnumType ConfSaveEnumType; +typedef struct CmdlineArgList CmdlineArgList; +typedef struct CmdlineArg CmdlineArg; + typedef struct terminal_tag Terminal; typedef struct term_utf8_decode term_utf8_decode; @@ -91,6 +97,7 @@ typedef struct BinarySink BinarySink; typedef struct BinarySource BinarySource; typedef struct stdio_sink stdio_sink; typedef struct bufchain_sink bufchain_sink; +typedef struct buffer_sink buffer_sink; typedef struct handle_sink handle_sink; typedef struct IdempotentCallback IdempotentCallback; @@ -99,7 +106,7 @@ typedef struct SockAddr SockAddr; typedef struct Socket Socket; typedef struct Plug Plug; -typedef struct SocketPeerInfo SocketPeerInfo; +typedef struct SocketEndpointInfo SocketEndpointInfo; typedef struct DeferredSocketOpener DeferredSocketOpener; typedef struct DeferredSocketOpenerVtable DeferredSocketOpenerVtable; @@ -114,6 +121,11 @@ typedef struct LogContext LogContext; typedef struct LogPolicy LogPolicy; typedef struct LogPolicyVtable LogPolicyVtable; +typedef struct TermLineEditor TermLineEditor; +typedef struct TermLineEditorCallbackReceiver TermLineEditorCallbackReceiver; +typedef struct TermLineEditorCallbackReceiverVtable + TermLineEditorCallbackReceiverVtable; + typedef struct Seat Seat; typedef struct SeatVtable SeatVtable; typedef struct SeatDialogText SeatDialogText; diff --git a/dialog.c b/dialog.c index b9306982..511331d3 100644 --- a/dialog.c +++ b/dialog.c @@ -41,15 +41,15 @@ int ctrl_path_compare(const char *p1, const char *p2) struct controlbox *ctrl_new_box(void) { - struct controlbox *ret = snew(struct controlbox); + struct controlbox *b = snew(struct controlbox); - ret->nctrlsets = ret->ctrlsetsize = 0; - ret->ctrlsets = NULL; - ret->nfrees = ret->freesize = 0; - ret->frees = NULL; - ret->freefuncs = NULL; + b->nctrlsets = b->ctrlsetsize = 0; + b->ctrlsets = NULL; + b->nfrees = b->freesize = 0; + b->frees = NULL; + b->freefuncs = NULL; - return ret; + return b; } void ctrl_free_box(struct controlbox *b) @@ -392,8 +392,8 @@ dlgcontrol *ctrl_draglist(struct controlset *s, const char *label, } dlgcontrol *ctrl_filesel(struct controlset *s, const char *label, - char shortcut, const char *filter, bool write, - const char *title, HelpCtx helpctx, + char shortcut, FILESELECT_FILTER_TYPE filter, + bool write, const char *title, HelpCtx helpctx, handler_fn handler, intorptr context) { dlgcontrol *c = ctrl_new(s, CTRL_FILESELECT, helpctx, handler, context); diff --git a/dialog.h b/dialog.h index ef9a9dfe..19e0695d 100644 --- a/dialog.h +++ b/dialog.h @@ -362,7 +362,7 @@ struct dlgcontrol { * particular platform might choose to cast integers into * this pointer type... */ - char const *filter; + FILESELECT_FILTER_TYPE filter; /* * Some systems like to know whether a file selector is * choosing a file to read or one to write (and possibly @@ -552,8 +552,8 @@ dlgcontrol *ctrl_draglist(struct controlset *, const char *label, char shortcut, HelpCtx helpctx, handler_fn handler, intorptr context); dlgcontrol *ctrl_filesel(struct controlset *, const char *label, - char shortcut, const char *filter, bool write, - const char *title, HelpCtx helpctx, + char shortcut, FILESELECT_FILTER_TYPE filter, + bool write, const char *title, HelpCtx helpctx, handler_fn handler, intorptr context); dlgcontrol *ctrl_fontsel(struct controlset *, const char *label, char shortcut, HelpCtx helpctx, @@ -574,7 +574,9 @@ int dlg_radiobutton_get(dlgcontrol *ctrl, dlgparam *dp); void dlg_checkbox_set(dlgcontrol *ctrl, dlgparam *dp, bool checked); bool dlg_checkbox_get(dlgcontrol *ctrl, dlgparam *dp); void dlg_editbox_set(dlgcontrol *ctrl, dlgparam *dp, char const *text); +void dlg_editbox_set_utf8(dlgcontrol *ctrl, dlgparam *dp, char const *text); char *dlg_editbox_get(dlgcontrol *ctrl, dlgparam *dp); /* result must be freed by caller */ +char *dlg_editbox_get_utf8(dlgcontrol *ctrl, dlgparam *dp); /* result must be freed by caller */ void dlg_editbox_select_range(dlgcontrol *ctrl, dlgparam *dp, size_t start, size_t len); /* The `listbox' functions can also apply to combo boxes. */ diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index 293cd6f1..ef77eb12 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -82,6 +82,7 @@ if(HALIBUT AND PERL_EXECUTABLE) ${CMAKE_CURRENT_SOURCE_DIR}/pgpkeys.but ${CMAKE_CURRENT_SOURCE_DIR}/sshnames.but ${CMAKE_CURRENT_SOURCE_DIR}/authplugin.but + ${CMAKE_CURRENT_SOURCE_DIR}/privacy.but ${CMAKE_CURRENT_SOURCE_DIR}/index.but ${VERSION_BUT}) diff --git a/doc/config.but b/doc/config.but index 8b3191fa..a28e866c 100644 --- a/doc/config.but +++ b/doc/config.but @@ -984,6 +984,34 @@ right in all situations. You may also find you need to disable Arabic text shaping; see \k{config-features-shaping}. +\S{config-features-bracketed paste} Disabling \i{bracketed paste} mode + +By default, when you paste text into the terminal window, it's sent to +the server as terminal input, exactly as if you'd typed the same text +into the terminal window using the keyboard (except that it's all sent +at once, much faster than you could type it). + +However, a terminal application can change that, by asking the +terminal to enable \q{bracketed paste mode}. In this mode, pasted data +is marked in the input stream, by sending a special control sequence +before the paste, and another one at the end. + +A terminal application can use this information to treat pasted data +differently from keyboard input. For example, a terminal-based text +editor can treat the input as literal data, even if some of its +characters would normally trigger special editor functions. A shell +can treat pasted input as less trusted, in case another application +somehow sneaked a malicious shell command into your clipboard: modern +versions of \cw{bash} will highlight pasted data on the command line, +and not run it until you've confirmed it by pressing Return, even if +the pasted data contained a newline character. + +In edge cases, it's possible that bracketed paste mode introduces +bigger problems than the ones it solves. So you can use this checkbox +to turn it off completely. If you do that, then PuTTY will always send +your paste data exactly as if it had been typed at the keyboard, +whether or not the server asked for bracketed paste mode. + \H{config-window} The Window panel The Window configuration panel allows you to control aspects of the diff --git a/doc/faq.but b/doc/faq.but index 61d2a5a2..35db12e4 100644 --- a/doc/faq.but +++ b/doc/faq.but @@ -1198,6 +1198,17 @@ don't advise anyone to use it in preference to our own site. The real PuTTY web site, run by the PuTTY team, has always been at \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/}\cw{https://www.chiark.greenend.org.uk/~sgtatham/putty/}. +\S{faq-the}{Question} Why do the download links point to +\cw{the.earth.li} and not chiark? Has your website been hacked? + +We haven't been hacked: links to \cw{the.earth.li} are legit. The +files for released versions of PuTTY are hosted on a different server +from the web pages, for bandwidth reasons. + +The download site \cw{the.earth.li} is hosted by +\W{https://www.mythic-beasts.com/}{Mythic Beasts}, and we're very +grateful to them! + \S{faq-domain}{Question} Would you like me to register you a nicer domain name? @@ -1297,8 +1308,7 @@ There isn't one, that we know of. If someone else wants to set up a mailing list or other forum for PuTTY users to help each other with common problems, that would be fine with us, though the PuTTY team would almost certainly not have the -time to read it. It's probably better to use one of the established -newsgroups for this purpose (see \k{feedback-other-fora}). +time to read it. \S{faq-donations}{Question} How can I donate to PuTTY development? diff --git a/doc/feedback.but b/doc/feedback.but index d4f58c09..c459e49d 100644 --- a/doc/feedback.but +++ b/doc/feedback.but @@ -13,16 +13,15 @@ reports and feature requests. The PuTTY development team gets a \e{lot} of mail. If you can possibly solve your own problem by reading the manual, reading the -FAQ, reading the web site, asking a fellow user, perhaps posting to a -newsgroup (see \k{feedback-other-fora}), or some other means, then it -would make our lives much easier. +FAQ, reading the web site, asking a fellow user, or some other +means, then it would make our lives much easier. We get so much e-mail that we literally do not have time to answer it all. We regret this, but there's nothing we can do about it. So if you can \e{possibly} avoid sending mail to the PuTTY team, we recommend you do so. In particular, support requests -(\k{feedback-support}) are probably better sent to newsgroups, or -passed to a local expert if possible. +(\k{feedback-support}) are probably better sent to some public +forum, or passed to a local expert if possible. The PuTTY contact email address is a private \i{mailing list} containing four or five core developers. Don't be put off by it being a mailing @@ -90,23 +89,7 @@ modification. If you've only changed 10 lines, we'd prefer to receive a mail that's 30 lines long than one containing multiple megabytes of data we already have. -\S{feedback-other-fora} Other places to ask for help - -There are two Usenet newsgroups that are particularly relevant to the -PuTTY tools: - -\b \W{news:comp.security.ssh}\c{comp.security.ssh}, for questions -specific to using the SSH protocol; - -\b \W{news:comp.terminals}\c{comp.terminals}, for issues relating to -terminal emulation (for instance, keyboard problems). - -Please use the newsgroup most appropriate to your query, and remember -that these are general newsgroups, not specifically about PuTTY. - -If you don't have direct access to Usenet, you can access these -newsgroups through Google Groups -(\W{http://groups.google.com/}\cw{groups.google.com}). +\# \S{feedback-other-fora} Other places to ask for help \H{feedback-bugs} Reporting bugs @@ -200,8 +183,7 @@ think the documentation is unclear or unhelpful. But we do need to be given exact details of \e{what} you think the documentation has failed to tell you, or \e{how} you think it could be made clearer. If your problem is simply that you don't \e{understand} the -documentation, we suggest posting to a newsgroup (see -\k{feedback-other-fora}) and seeing if someone +documentation, we suggest asking around and seeing if someone will explain what you need to know. \e{Then}, if you think the documentation could usefully have told you that, send us a bug report and explain how you think we should change it. @@ -356,17 +338,15 @@ include: \b If you want to do something with PuTTY but have no idea where to start, and reading the manual hasn't helped, try posting to a -newsgroup (see \k{feedback-other-fora}) and see if someone can explain -it to you. +public forum and see if someone can explain it to you. \b If you have tried to do something with PuTTY but it hasn't worked, and you aren't sure whether it's a bug in PuTTY or a bug in your SSH server or simply that you're not doing it right, then try -posting to a newsgroup (see \k{feedback-other-fora}) and see -if someone can solve your problem. Or try doing the same thing with -a different SSH client and see if it works with that. Please do not -report it as a PuTTY bug unless you are really sure it \e{is} a bug -in PuTTY. +posting to some public forum and see if someone can solve your +problem. Or try doing the same thing with a different SSH client +and see if it works with that. Please do not report it as a PuTTY +bug unless you are really sure it \e{is} a bug in PuTTY. \b If someone else installed PuTTY for you, or you're using PuTTY on someone else's computer, try asking them for help first. They're more diff --git a/doc/index.but b/doc/index.but index 86c6f931..35c479ff 100644 --- a/doc/index.but +++ b/doc/index.but @@ -77,6 +77,9 @@ from other protocols \IM{copy and paste} cut and paste \IM{copy and paste} paste, copy and +\IM{bracketed paste} bracketed paste +\IM{bracketed paste} paste, bracketed + \IM{three-button mouse} three-button mouse \IM{three-button mouse} mouse, three-button diff --git a/doc/man-pageant.but b/doc/man-pageant.but index d202f166..3b8cdcec 100644 --- a/doc/man-pageant.but +++ b/doc/man-pageant.but @@ -8,8 +8,8 @@ \S{pageant-manpage-synopsis} SYNOPSIS -\c pageant ( -X | -T | --permanent | --debug ) [ [ --encrypted ] key-file... ] -\e bbbbbbb bb bb bbbbbbbbbbb bbbbbbb bbbbbbbbbbb iiiiiiii +\c pageant ( -X | -T | --permanent | --debug | --foreground ) [ [ --encrypted ] key-file... ] +\e bbbbbbb bb bb bbbbbbbbbbb bbbbbbb bbbbbbbbbbbb bbbbbbbbbbb iiiiiiii \c pageant [ [ --encrypted ] key-file... ] --exec command [ args... ] \e bbbbbbb bbbbbbbbb iiiiiiii bbbbbb iiiiiii iiii \c pageant -a [ --encrypted ] key-file... @@ -183,6 +183,26 @@ prompts will need to be answered on standard input. This is useful for debugging what Pageant itself is doing, or what another process is doing to it. +\dt \cw{--foreground} + +\dd Like \cw{--debug}, Pageant will run in the foreground, without +forking. It will print its environment variable setup commands on +standard output. Unlike \cw{--debug}, Pageant will not automatically log +agent activity to standard output, nor will it force passphrase prompts +to standard input. This is useful if Pageant is spawned by a parent +process that controls or otherwise programmatically interfaces with +Pageant. + +\lcont{ + +After Pageant prints its environment setup commands, it closes its +standard output. So if the parent process has run it in a pipe to +retrieve the environment setup commands, it can simply read until it +receives EOF, instead of having to know how many lines of output to +expect. + +} + \S{pageant-manpage-client} CLIENT OPTIONS The following options tell Pageant to operate in client mode, diff --git a/doc/pgpkeys.but b/doc/pgpkeys.but index 9e25e208..9fcd8657 100644 --- a/doc/pgpkeys.but +++ b/doc/pgpkeys.but @@ -19,6 +19,9 @@ built-in signatures that are automatically verified by Windows' own mechanism (\q{\i{Authenticode}}). The keys used for that are different, and are not covered here. +See \k{faq-checksums} in the FAQ for some gotchas when verifying +checksums and signatures. + (Note that none of the keys, signatures, etc mentioned here have anything to do with keys used with SSH - they are purely for verifying the origin of files distributed by the PuTTY team.) diff --git a/doc/plink.but b/doc/plink.but index 2b460404..8d2849b2 100644 --- a/doc/plink.but +++ b/doc/plink.but @@ -41,7 +41,7 @@ use Plink: \c C:\>plink \c Plink: command-line connection utility -\c Release 0.81 +\c Release 0.82 \c Usage: plink [options] [user@]host [command] \c ("host" can also be a PuTTY saved session name) \c Options: diff --git a/doc/privacy.but b/doc/privacy.but new file mode 100644 index 00000000..0f488bf6 --- /dev/null +++ b/doc/privacy.but @@ -0,0 +1,246 @@ +\A{privacy} PuTTY privacy considerations + +This appendix lists the implications of using PuTTY for your privacy +and personal data. + +The short summary: PuTTY never \q{phones home} to us, the developers. +It does store data on your own computer, and it does transmit data +over the network, but in both cases, only as necessary to do its job. +In particular, data is only transmitted over the network to the server +you told PuTTY to connect to. + +But if you're concerned about exactly \e{what} information is stored +or transmitted, then here's a more detailed description. + +\H{privacy-local}Information that PuTTY stores locally + +When you use PuTTY, it stores a small amount of information on your +computer, necessary for doing its own job. This information is stored +in the user account of the user who runs PuTTY, so it is under your +control: you can view it, change it, or delete it. + +If you need to delete all of this data, you can use the \c{-cleanup} +command-line option, as described in \k{using-cleanup}. + +PuTTY does not transmit your saved session data to any other site. +However, you may need to be aware of the fact that it is stored on +\e{your} computer. (For example, somebody else accessing your computer +might be able to find a list of sites you have connected to, if you +have saved details of them.) + +\S{privacy-hostkeys} Host key cache + +If you use the SSH protocol, then PuTTY stores a list of the SSH +servers you have connected to, together with their host keys. + +This is known as the \q{host key cache}. It is used to detect network +attacks, by notifying you if a server you've connected to before +doesn't look like the same one you thought it was. (See \k{gs-hostkey} +for a basic introduction to host keys.) + +The host key cache is optional. An entry is only saved in the host key +cache if you select the \q{Accept} action at one of the PuTTY suite's +host key verification prompts. So if you want to make an SSH +connection without PuTTY saving any trace of where you connected to, +you can press \q{Connect Once} instead of \q{Accept}, which does not +store the host key in the cache. + +However, if you do this, PuTTY can't automatically detect the host key +changing in the future, so you should check the key fingerprint +yourself every time you connect. \s{This is vitally important.} If you +don't let PuTTY cache host keys \e{and} don't check them yourself, +then it becomes easy for an attacker to interpose a listener between +you and the server you're connecting to. The entire cryptographic +system of SSH depends on making sure the host key is right. + +The host key cache is only used by SSH. No other protocol supported +by PuTTY has any analogue of it. + +\S{privacy-savedsessions} Saved sessions + +After you set up PuTTY's configuration for a particular network +connection, you can choose to save it as a \q{saved session}, so that +you can make the same connection again later without having to +re-enter all the details. + +PuTTY will not do this unless you use the \q{Save} button in its +configuration box. It never saves session configuration automatically. + +So if you want to make an SSH connection without leaving any trace of +where you connected to, you should not make a saved session for that +connection. Instead, re-enter the details by hand every time you do +it. + +\S{privacy-jumplist} Jump list + +On Windows, the operating system provides a feature called a \q{jump +list}. This is a menu that pops up from an application's icon in the +Windows taskbar, and the application can configure entries that appear +in it. Applications typically include menu items to re-launch recently +used documents or configurations. + +PuTTY updates its jump list whenever a saved session is loaded, either +to launch it immediately or to load it within the configuration dialog +box. So if you have a collection of saved sessions, the jump list will +contain a record of which ones you have recently used. + +An exception is that saved sessions are not included in the jump list +if they are not \q{launchable}, meaning that they actually specify a +host name or serial port to connect to. A non-launchable session can +specify all the other configuration details (such as fonts, window +size, keyboard setup, SSH features, etc), but leave out the hostname. + +If you want to avoid leaving any evidence of having made a particular +connection, then make the connection without creating a launchable +saved session for it: either make no saved session at all, or create a +non-launchable one which sets up every detail \e{except} the +destination host name. Then it won't appear in the jump list. + +(The saved session itself would also be evidence, of course, as +discussed in the previous section.) + +\S{privacy-logfiles} Log files + +PuTTY can be configured to save a log file of your entire session to +the computer you run it on. By default it does not do so: the content +of your session is not saved. + +See \k{config-logging} for details of the logging features. Some +logging modes store only output sent by the server and printed in +PuTTY's terminal window. Other more thorough modes also store your +input that PuTTY sends \e{to} the server. + +If the logging feature is enabled, then by default, PuTTY will avoid +saving data in the log file that it knows to be sensitive, such as +passwords. However, it cannot reliably identify \e{all} passwords. If +you use a password for your initial login to an SSH server, PuTTY +knows that is a password, and will omit it from the log file. But if +after login you type a password into an application on the server, +then PuTTY will not know that \e{that} is a password, so it will +appear in the log file, if PuTTY is writing a type that includes +keyboard input. + +PuTTY can also be configured to include all passwords in its log +files, even the ones it would normally leave out. This is intended for +debugging purposes, for example if a server is refusing your password +and you need to check whether the password is being sent correctly. We +do not recommend enabling this option routinely. + +\S{privacy-randomseed} Random seed file + +PuTTY stores a small file of random bytes under the name +\cq{putty.rnd}, which is reloaded the next time it is run and used to +seed its random number generator. These bytes are meaningless and +random, and do not contain an encrypted version of anything. + +\H{privacy-network} Sending information over the network + +PuTTY is a communications tool. Its \e{purpose} is to connect to +another computer, over a network or a serial port, and send +information. However it only makes the network connections that its +configuration instructs it to. + +\S{privacy-nophonehome} PuTTY only connects to the specified destination host + +No PuTTY tool will \q{phone home} to any site under the control of us +(the development team), or to any other site apart from the +destination host or proxy host in its configuration, and any DNS +server that is needed to look up the IP addresses corresponding to +those host names. + +No information about your network sessions, and no information from +the computer you run PuTTY on, is collected or recorded by the PuTTY +developers. + +Information you provide to PuTTY (via keyboard input, the command +line, or files loaded by the file transfer tools) is sent to the +server that PuTTY's configuration tells it to connect to. It is not +sent anywhere else. + +\S{privacy-whatdata} What data is sent to the destination host + +When you log in to a server, PuTTY will send your username. If you use +a password to authenticate to the server, PuTTY will send it that +password as well. + +(Therefore, the server is told what your password is during login. +This means that if you use the same password on two servers, the +administrator of one could find out your password and log in to your +account on the other.) + +If you use an SSH private key to authenticate, PuTTY will send the +\e{public} key, but not the private key. If you typed a passphrase to +decrypt the private key, PuTTY will not send the passphrase either. + +(Therefore, it is safer to use the same \e{public key} to authenticate +to two SSH servers. Neither server gains the ability to impersonate +you to the other server. However, if the server maintainers talked to +each other, they would at least be able to find out that your accounts +on the two machines were owned by the same person, if they didn't +already know.) + +When PuTTY prompts for a private key passphrase, a small copy of the +PuTTY icon appears to the left of the prompt, to indicate that the +prompt was genuinely from PuTTY. (We call this a \q{trust sigil}.) +That icon never appears next to text sent from the server. So if a +server tries to mimic that prompt to trick you into telling it your +private key passphrase, it won't be able to fake that trust sigil, and +you can tell the difference. + +If you're running Pageant, and you haven't configured a specific +public key to authenticate to this server, then PuTTY will try all the +keys in Pageant one after the other, sending each public key to the +server to see if it's acceptable. This can lead to the server finding +out about other public keys you own. However, if you configure PuTTY +to use a specific public key, then it will ignore all the other keys +in Pageant. + +Once you have logged in, keystrokes you type in the PuTTY terminal +window, and data you paste in with the mouse, are sent to the +destination host. That is PuTTY's primary job. + +The server can request PuTTY to send details of mouse movements in the +terminal window, in order to implement mouse-controlled user +interfaces on the server. If you consider this to be a privacy +intrusion, you can turn off that terminal feature in the Features +configuration panel (\q{Disable xterm-style mouse reporting}, as +described in \k{config-features-mouse}). + +\H{privacy-config} Configuration + +The operation of a PuTTY network tool is controlled by its +configuration. This configuration is obtained from: + +\b the command line used to run the tool + +\b settings configured in the GUI before opening a network session + +\b optionally, the contents of a saved session, if the command line +or a GUI action instructed PuTTY to load one + +\b the special saved session called \q{Default Settings}, which +applies if no other saved session is loaded + +\b defaults built in to PuTTY itself. + +The defaults built in to PuTTY do not tell it to save log files, or +specify the name of any network site to connect to. + +However, if PuTTY has been installed for you by somebody else, such as +an organisation, then that organisation may have provided their own +default configuration. In that situation you may wish to check that +the defaults they have set are compatible with your privacy needs. For +example, an organisation providing your PuTTY configuration might +configure PuTTY to save log files of your sessions, even though +PuTTY's own default is not to do so. + +\H{privacy-modified} Modified versions of PuTTY + +PuTTY is free software. Its source code is available, so anyone can +make a modified version of it. The modified version can behave +differently from the original in any way it likes. + +This list of privacy considerations only applies to the original +version of PuTTY, as distributed by its development team. We cannot +make any promises about the behaviour of modified versions distributed +by other people. diff --git a/doc/pscp.but b/doc/pscp.but index 1a27e7d9..dbc04c85 100644 --- a/doc/pscp.but +++ b/doc/pscp.but @@ -39,7 +39,7 @@ use PSCP: \c C:\>pscp \c PuTTY Secure Copy client -\c Release 0.81 +\c Release 0.82 \c Usage: pscp [options] [user@]host:source target \c pscp [options] source [source...] [user@]host:target \c pscp [options] -ls [user@]host:filespec diff --git a/doc/using.but b/doc/using.but index 5865ac95..c595c153 100644 --- a/doc/using.but +++ b/doc/using.but @@ -1184,3 +1184,88 @@ session at all. Instead, it will just display the configuration dialog box for host certification authorities, as described in \k{config-ssh-kex-cert}. When you dismiss that dialog box, PuTTY will terminate. + +\S2{using-cmdline-legacy-stdio-prompts} \i{\c{-legacy-stdio-prompts}}: +handle Windows console prompts like older versions of PuTTY + +This option applies to all of PSCP, PSFTP and Plink on Windows: all +the tools in the PuTTY suite that run in a Windows console and make +SSH connections. + +These tools use the Windows console to prompt for various information: +usernames, passwords, answers to questions about host keys, and so on. + +In current versions of PuTTY, these prompts work by direct access to +the Windows console. This means that even if you redirect the standard +input or output of the tool, prompts will \e{still} be sent to the +console (and not where you've redirected your output), and the user's +responses will be read from the console (and not from where you've +redirected your input). + +Another advantage of reading directly from the Windows console is that +the tools can read input as \i{Unicode}. So this also allows you to +enter usernames and passwords that contain characters not in the +Windows system's default character set. + +In versions of the PuTTY tools up to and including 0.81, the prompts +used the tool's ordinary I/O handles, so prompt output and user +responses could be redirected. + +We think the new behaviour is more likely to be useful. For example, +if you have a local command that generates output, and you want to +pipe that output into a command running remotely via Plink, you can +run a command line such as + +\c local_command | plink hostname remote_command + +and the data piped into the remote command will be the same whether or +not Plink has to stop to ask for a password. With the old behaviour +you would have had to include the password in Plink's input, which is +more awkward. + +However, we recognise that people may have customised complicated +workflows around the old behaviour. So if you need to switch back to +it, you can do so by specifying \c{-legacy-stdio-prompts} on the +command-line. + +To fully revert to the previous behaviour, you'd also need to specify +\c{-legacy-charset-handling} (see the next section). (Even without +that option, prompt handling with \c{-legacy-stdio-prompts} may not be +fully Unicode-clean.) + +\S2{using-cmdline-legacy-charset-handling} \i{\c{-legacy-charset-handling}}: +handle character set in prompts like older versions of PuTTY + +This option applies to PuTTY (on all platforms), and also to all of +PSCP, PSFTP and Plink on Windows. + +In current versions of PuTTY, when you are prompted in the terminal +window for things like SSH usernames and passwords, the responses you +type are interpreted as \i{Unicode}, and transmitted to the server as +such, even if the terminal is otherwise configured to use a different +character encoding (see \k{config-charset}). Similarly, the same +prompts from the Windows console tools will unconditionally interpret +their input as Unicode. + +This behaviour is in line with the SSH standards; it allows things +like usernames to use the full character set of the user's native +language, and ensures that different keystrokes you type for your +password are actually treated distinctly. + +However, if you are used to the behaviour of the PuTTY tools up to +version 0.81, this could cause a previously working username and/or +password not to work as you expected. For instance, if you had set a +password including some \i{accented characters}, this change in +behaviour could cause the same keystrokes you've always entered to +start sending a different sequence of bytes to the server, denying you +access (and you wouldn't even be able to see the difference, since the +password is not shown when you type it). + +\c{-legacy-charset-handling} reverts the PuTTY tools' behaviour to how +it was previously: what you type at these prompts will be interpreted +according to the \q{Remote character set} (for PuTTY) or Windows' +default character set (for the Windows console tools). + +(For example, this could allow you to log in to change your password +to make using this option unnecessary in future. But if you're doing +that, make sure the terminal is configured as UTF-8!) diff --git a/errsock.c b/errsock.c index 6f26629d..4f640a66 100644 --- a/errsock.c +++ b/errsock.c @@ -39,16 +39,11 @@ static const char *sk_error_socket_error(Socket *s) return es->error; } -static SocketPeerInfo *sk_error_peer_info(Socket *s) -{ - return NULL; -} - static const SocketVtable ErrorSocket_sockvt = { .plug = sk_error_plug, .close = sk_error_close, .socket_error = sk_error_socket_error, - .peer_info = sk_error_peer_info, + .endpoint_info = nullsock_endpoint_info, /* other methods are NULL */ }; diff --git a/import.c b/import.c index 918de50e..8e901bb5 100644 --- a/import.c +++ b/import.c @@ -339,7 +339,7 @@ static void BinarySink_put_mp_ssh2_from_string(BinarySink *bs, ptrlen str) static struct openssh_pem_key *load_openssh_pem_key(BinarySource *src, const char **errmsg_p) { - struct openssh_pem_key *ret; + struct openssh_pem_key *key; char *line = NULL; const char *errmsg; char *p; @@ -347,8 +347,8 @@ static struct openssh_pem_key *load_openssh_pem_key(BinarySource *src, char base64_bit[4]; int base64_chars = 0; - ret = snew(struct openssh_pem_key); - ret->keyblob = strbuf_new_nm(); + key = snew(struct openssh_pem_key); + key->keyblob = strbuf_new_nm(); if (!(line = bsgetline(src))) { errmsg = "unexpected end of file"; @@ -366,11 +366,11 @@ static struct openssh_pem_key *load_openssh_pem_key(BinarySource *src, * base64. */ if (!strcmp(line, "-----BEGIN RSA PRIVATE KEY-----")) { - ret->keytype = OP_RSA; + key->keytype = OP_RSA; } else if (!strcmp(line, "-----BEGIN DSA PRIVATE KEY-----")) { - ret->keytype = OP_DSA; + key->keytype = OP_DSA; } else if (!strcmp(line, "-----BEGIN EC PRIVATE KEY-----")) { - ret->keytype = OP_ECDSA; + key->keytype = OP_ECDSA; } else if (!strcmp(line, "-----BEGIN OPENSSH PRIVATE KEY-----")) { errmsg = "this is a new-style OpenSSH key"; goto error; @@ -382,8 +382,8 @@ static struct openssh_pem_key *load_openssh_pem_key(BinarySource *src, sfree(line); line = NULL; - ret->encrypted = false; - memset(ret->iv, 0, sizeof(ret->iv)); + key->encrypted = false; + memset(key->iv, 0, sizeof(key->iv)); headers_done = false; while (1) { @@ -411,15 +411,15 @@ static struct openssh_pem_key *load_openssh_pem_key(BinarySource *src, } p += 2; if (!strcmp(p, "ENCRYPTED")) - ret->encrypted = true; + key->encrypted = true; } else if (!strcmp(line, "DEK-Info")) { int i, ivlen; if (!strncmp(p, "DES-EDE3-CBC,", 13)) { - ret->encryption = OP_E_3DES; + key->encryption = OP_E_3DES; ivlen = 8; } else if (!strncmp(p, "AES-128-CBC,", 12)) { - ret->encryption = OP_E_AES; + key->encryption = OP_E_AES; ivlen = 16; } else { errmsg = "unsupported cipher"; @@ -432,7 +432,7 @@ static struct openssh_pem_key *load_openssh_pem_key(BinarySource *src, errmsg = "expected more iv data in DEK-Info"; goto error; } - ret->iv[i] = j; + key->iv[i] = j; p += 2; } if (*p) { @@ -459,7 +459,7 @@ static struct openssh_pem_key *load_openssh_pem_key(BinarySource *src, goto error; } - put_data(ret->keyblob, out, len); + put_data(key->keyblob, out, len); smemclr(out, sizeof(out)); } @@ -472,12 +472,12 @@ static struct openssh_pem_key *load_openssh_pem_key(BinarySource *src, line = NULL; } - if (!ret->keyblob || ret->keyblob->len == 0) { + if (!key->keyblob || key->keyblob->len == 0) { errmsg = "key body not present"; goto error; } - if (ret->encrypted && ret->keyblob->len % 8 != 0) { + if (key->encrypted && key->keyblob->len % 8 != 0) { errmsg = "encrypted key blob is not a multiple of " "cipher block size"; goto error; @@ -485,7 +485,7 @@ static struct openssh_pem_key *load_openssh_pem_key(BinarySource *src, smemclr(base64_bit, sizeof(base64_bit)); if (errmsg_p) *errmsg_p = NULL; - return ret; + return key; error: if (line) { @@ -494,11 +494,11 @@ static struct openssh_pem_key *load_openssh_pem_key(BinarySource *src, line = NULL; } smemclr(base64_bit, sizeof(base64_bit)); - if (ret) { - if (ret->keyblob) - strbuf_free(ret->keyblob); - smemclr(ret, sizeof(*ret)); - sfree(ret); + if (key) { + if (key->keyblob) + strbuf_free(key->keyblob); + smemclr(key, sizeof(*key)); + sfree(key); } if (errmsg_p) *errmsg_p = errmsg; return NULL; @@ -1119,7 +1119,7 @@ struct openssh_new_key { static struct openssh_new_key *load_openssh_new_key(BinarySource *filesrc, const char **errmsg_p) { - struct openssh_new_key *ret; + struct openssh_new_key *key; char *line = NULL; const char *errmsg; char *p; @@ -1129,8 +1129,8 @@ static struct openssh_new_key *load_openssh_new_key(BinarySource *filesrc, ptrlen str; unsigned key_index; - ret = snew(struct openssh_new_key); - ret->keyblob = strbuf_new_nm(); + key = snew(struct openssh_new_key); + key->keyblob = strbuf_new_nm(); if (!(line = bsgetline(filesrc))) { errmsg = "unexpected end of file"; @@ -1171,7 +1171,7 @@ static struct openssh_new_key *load_openssh_new_key(BinarySource *filesrc, goto error; } - put_data(ret->keyblob, out, len); + put_data(key->keyblob, out, len); smemclr(out, sizeof(out)); } @@ -1183,12 +1183,12 @@ static struct openssh_new_key *load_openssh_new_key(BinarySource *filesrc, line = NULL; } - if (ret->keyblob->len == 0) { + if (key->keyblob->len == 0) { errmsg = "key body not present"; goto error; } - BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(ret->keyblob)); + BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(key->keyblob)); if (strcmp(get_asciz(src), "openssh-key-v1") != 0) { errmsg = "new-style OpenSSH magic number missing\n"; @@ -1198,11 +1198,11 @@ static struct openssh_new_key *load_openssh_new_key(BinarySource *filesrc, /* Cipher name */ str = get_string(src); if (ptrlen_eq_string(str, "none")) { - ret->cipher = ON_E_NONE; + key->cipher = ON_E_NONE; } else if (ptrlen_eq_string(str, "aes256-cbc")) { - ret->cipher = ON_E_AES256CBC; + key->cipher = ON_E_AES256CBC; } else if (ptrlen_eq_string(str, "aes256-ctr")) { - ret->cipher = ON_E_AES256CTR; + key->cipher = ON_E_AES256CTR; } else { errmsg = get_err(src) ? "no cipher name found" : "unrecognised cipher name\n"; @@ -1212,9 +1212,9 @@ static struct openssh_new_key *load_openssh_new_key(BinarySource *filesrc, /* Key derivation function name */ str = get_string(src); if (ptrlen_eq_string(str, "none")) { - ret->kdf = ON_K_NONE; + key->kdf = ON_K_NONE; } else if (ptrlen_eq_string(str, "bcrypt")) { - ret->kdf = ON_K_BCRYPT; + key->kdf = ON_K_BCRYPT; } else { errmsg = get_err(src) ? "no kdf name found" : "unrecognised kdf name\n"; @@ -1223,7 +1223,7 @@ static struct openssh_new_key *load_openssh_new_key(BinarySource *filesrc, /* KDF extra options */ str = get_string(src); - switch (ret->kdf) { + switch (key->kdf) { case ON_K_NONE: if (str.len != 0) { errmsg = "expected empty options string for 'none' kdf"; @@ -1234,8 +1234,8 @@ static struct openssh_new_key *load_openssh_new_key(BinarySource *filesrc, BinarySource opts[1]; BinarySource_BARE_INIT_PL(opts, str); - ret->kdfopts.bcrypt.salt = get_string(opts); - ret->kdfopts.bcrypt.rounds = get_uint32(opts); + key->kdfopts.bcrypt.salt = get_string(opts); + key->kdfopts.bcrypt.rounds = get_uint32(opts); if (get_err(opts)) { errmsg = "failed to parse bcrypt options string"; @@ -1257,23 +1257,23 @@ static struct openssh_new_key *load_openssh_new_key(BinarySource *filesrc, * 'key_wanted' field is set to a value in the range [0, * nkeys) by some mechanism. */ - ret->nkeys = toint(get_uint32(src)); - if (ret->nkeys != 1) { + key->nkeys = toint(get_uint32(src)); + if (key->nkeys != 1) { errmsg = get_err(src) ? "no key count found" : "multiple keys in new-style OpenSSH key file not supported\n"; goto error; } - ret->key_wanted = 0; + key->key_wanted = 0; /* Read and ignore a string per public key. */ - for (key_index = 0; key_index < ret->nkeys; key_index++) + for (key_index = 0; key_index < key->nkeys; key_index++) str = get_string(src); /* * Now we expect a string containing the encrypted part of the * key file. */ - ret->private = get_string(src); + key->private = get_string(src); if (get_err(src)) { errmsg = "no private key container string found\n"; goto error; @@ -1285,7 +1285,7 @@ static struct openssh_new_key *load_openssh_new_key(BinarySource *filesrc, smemclr(base64_bit, sizeof(base64_bit)); if (errmsg_p) *errmsg_p = NULL; - return ret; + return key; error: if (line) { @@ -1294,10 +1294,10 @@ static struct openssh_new_key *load_openssh_new_key(BinarySource *filesrc, line = NULL; } smemclr(base64_bit, sizeof(base64_bit)); - if (ret) { - strbuf_free(ret->keyblob); - smemclr(ret, sizeof(*ret)); - sfree(ret); + if (key) { + strbuf_free(key->keyblob); + smemclr(key, sizeof(*key)); + sfree(key); } if (errmsg_p) *errmsg_p = errmsg; return NULL; @@ -1725,7 +1725,7 @@ struct sshcom_key { static struct sshcom_key *load_sshcom_key(BinarySource *src, const char **errmsg_p) { - struct sshcom_key *ret; + struct sshcom_key *key; char *line = NULL; int hdrstart, len; const char *errmsg; @@ -1734,9 +1734,9 @@ static struct sshcom_key *load_sshcom_key(BinarySource *src, char base64_bit[4]; int base64_chars = 0; - ret = snew(struct sshcom_key); - ret->comment[0] = '\0'; - ret->keyblob = strbuf_new_nm(); + key = snew(struct sshcom_key); + key->comment[0] = '\0'; + key->keyblob = strbuf_new_nm(); if (!(line = bsgetline(src))) { errmsg = "unexpected end of file"; @@ -1803,8 +1803,8 @@ static struct sshcom_key *load_sshcom_key(BinarySource *src, p++; p[strlen(p)-1] = '\0'; } - strncpy(ret->comment, p, sizeof(ret->comment)); - ret->comment[sizeof(ret->comment)-1] = '\0'; + strncpy(key->comment, p, sizeof(key->comment)); + key->comment[sizeof(key->comment)-1] = '\0'; } } else { headers_done = true; @@ -1824,7 +1824,7 @@ static struct sshcom_key *load_sshcom_key(BinarySource *src, goto error; } - put_data(ret->keyblob, out, len); + put_data(key->keyblob, out, len); } p++; @@ -1835,13 +1835,13 @@ static struct sshcom_key *load_sshcom_key(BinarySource *src, line = NULL; } - if (ret->keyblob->len == 0) { + if (key->keyblob->len == 0) { errmsg = "key body not present"; goto error; } if (errmsg_p) *errmsg_p = NULL; - return ret; + return key; error: if (line) { @@ -1849,10 +1849,10 @@ static struct sshcom_key *load_sshcom_key(BinarySource *src, sfree(line); line = NULL; } - if (ret) { - strbuf_free(ret->keyblob); - smemclr(ret, sizeof(*ret)); - sfree(ret); + if (key) { + strbuf_free(key->keyblob); + smemclr(key, sizeof(*key)); + sfree(key); } if (errmsg_p) *errmsg_p = errmsg; return NULL; diff --git a/ldisc.c b/ldisc.c index 27f41a6f..899f5c82 100644 --- a/ldisc.c +++ b/ldisc.c @@ -12,50 +12,36 @@ #include "putty.h" #include "terminal.h" +typedef enum InputType { NORMAL, DEDICATED, NONINTERACTIVE } InputType; + +struct input_chunk { + struct input_chunk *next; + InputType type; + size_t size; +}; + struct Ldisc_tag { Terminal *term; Backend *backend; Seat *seat; /* - * When the backend is not reporting true from sendok(), terminal - * input that comes here is stored in this bufchain instead. When - * the backend later decides it wants session input, we empty the - * queue in ldisc_check_sendok_callback(), passing its contents on - * to the backend. Before then, we also provide data from this - * queue to term_get_userpass_input() via ldisc_get_input_token(), - * to be interpreted as user responses to username and password - * prompts during authentication. - * - * Unfortunately, the data stored in this queue is not all of the - * same type: our output to the backend consists of both raw bytes - * sent to backend_send(), and also session specials such as - * SS_EOL and SS_EC. So we have to encode our queued data in a way - * that can represent both. + * When the backend is not reporting true from sendok(), we must + * buffer the input received by ldisc_send(). It's stored in the + * bufchain below, together with a linked list of input_chunk + * blocks storing the extra metadata about special keys and + * interactivity that ldisc_send() receives. * - * The encoding is private to this source file, so we can change - * it if necessary and only have to worry about the encode and - * decode functions here. Currently, it is: - * - * - Bytes other than 0xFF are stored literally. - * - The byte 0xFF itself is stored as 0xFF 0xFF. - * - A session special (code, arg) is stored as 0xFF, followed by - * a big-endian 4-byte integer containing code, followed by - * another big-endian 4-byte integer containing arg. - * - * (This representation relies on session special codes being at - * most 0xFEFFFFFF when represented in 32 bits, so that the first - * byte of the 'code' integer can't be confused with the 0xFF - * followup byte indicating a literal 0xFF, But since session - * special codes are defined by an enum counting up from zero, and - * there are only a couple of dozen of them, that shouldn't be a - * problem! Even so, just in case, an assertion checks that at - * encode time.) + * All input is added to this buffer initially, but we then + * process as much of it as possible immediately and hand it off + * to the backend or a TermLineEditor. Anything left stays in this + * buffer until ldisc_check_sendok() is next called, triggering a + * run of the callback that tries again to process the queue. */ bufchain input_queue; + struct input_chunk *inchunk_head, *inchunk_tail; IdempotentCallback input_queue_callback; - prompts_t *prompts; /* * Values cached out of conf. @@ -63,9 +49,13 @@ struct Ldisc_tag { bool telnet_keyboard, telnet_newline; int protocol, localecho, localedit; - char *buf; - size_t buflen, bufsiz; - bool quotenext; + TermLineEditor *le; + TermLineEditorCallbackReceiver le_rcv; + + /* We get one of these communicated to us by + * term_get_userpass_input while it's reading a prompt, so that we + * can push data straight into it */ + TermLineEditor *userpass_le; }; #define ECHOING (ldisc->localecho == FORCE_ON || \ @@ -75,71 +65,16 @@ struct Ldisc_tag { (ldisc->localedit == AUTO && \ (backend_ldisc_option_state(ldisc->backend, LD_EDIT)))) -static void c_write(Ldisc *ldisc, const void *buf, int len) -{ - seat_stdout(ldisc->seat, buf, len); -} - -static int plen(Ldisc *ldisc, unsigned char c) -{ - if ((c >= 32 && c <= 126) || (c >= 160 && !in_utf(ldisc->term))) - return 1; - else if (c < 128) - return 2; /* ^x for some x */ - else if (in_utf(ldisc->term) && c >= 0xC0) - return 1; /* UTF-8 introducer character - * (FIXME: combining / wide chars) */ - else if (in_utf(ldisc->term) && c >= 0x80 && c < 0xC0) - return 0; /* UTF-8 followup character */ - else - return 4; /* hex representation */ -} - -static void pwrite(Ldisc *ldisc, unsigned char c) -{ - if ((c >= 32 && c <= 126) || - (!in_utf(ldisc->term) && c >= 0xA0) || - (in_utf(ldisc->term) && c >= 0x80)) { - c_write(ldisc, &c, 1); - } else if (c < 128) { - char cc[2]; - cc[1] = (c == 127 ? '?' : c + 0x40); - cc[0] = '^'; - c_write(ldisc, cc, 2); - } else { - char cc[5]; - sprintf(cc, "<%02X>", c); - c_write(ldisc, cc, 4); - } -} - -static bool char_start(Ldisc *ldisc, unsigned char c) -{ - if (in_utf(ldisc->term)) - return (c < 0x80 || c >= 0xC0); - else - return true; -} - -static void bsb(Ldisc *ldisc, int n) -{ - while (n--) - c_write(ldisc, "\010 \010", 3); -} - static void ldisc_input_queue_callback(void *ctx); +static const TermLineEditorCallbackReceiverVtable ldisc_lineedit_receiver_vt; + #define CTRL(x) (x^'@') -#define KCTRL(x) ((x^'@') | 0x100) Ldisc *ldisc_create(Conf *conf, Terminal *term, Backend *backend, Seat *seat) { Ldisc *ldisc = snew(Ldisc); - - ldisc->buf = NULL; - ldisc->buflen = 0; - ldisc->bufsiz = 0; - ldisc->quotenext = false; + memset(ldisc, 0, sizeof(Ldisc)); ldisc->backend = backend; ldisc->term = term; @@ -147,12 +82,15 @@ Ldisc *ldisc_create(Conf *conf, Terminal *term, Backend *backend, Seat *seat) bufchain_init(&ldisc->input_queue); - ldisc->prompts = NULL; ldisc->input_queue_callback.fn = ldisc_input_queue_callback; ldisc->input_queue_callback.ctx = ldisc; - ldisc->input_queue_callback.queued = false; bufchain_set_callback(&ldisc->input_queue, &ldisc->input_queue_callback); + if (ldisc->term) { + ldisc->le_rcv.vt = &ldisc_lineedit_receiver_vt; + ldisc->le = lineedit_new(ldisc->term, 0, &ldisc->le_rcv); + } + ldisc_configure(ldisc, conf); /* Link ourselves into the backend and the terminal */ @@ -171,19 +109,28 @@ void ldisc_configure(Ldisc *ldisc, Conf *conf) ldisc->protocol = conf_get_int(conf, CONF_protocol); ldisc->localecho = conf_get_int(conf, CONF_localecho); ldisc->localedit = conf_get_int(conf, CONF_localedit); + + unsigned flags = 0; + if (ldisc->protocol == PROT_RAW) + flags |= LE_CRLF_NEWLINE; + if (ldisc->telnet_keyboard) + flags |= LE_INTERRUPT | LE_SUSPEND | LE_ABORT; + lineedit_modify_flags(ldisc->le, ~0U, flags); } void ldisc_free(Ldisc *ldisc) { bufchain_clear(&ldisc->input_queue); + while (ldisc->inchunk_head) { + struct input_chunk *oldhead = ldisc->inchunk_head; + ldisc->inchunk_head = ldisc->inchunk_head->next; + sfree(oldhead); + } + lineedit_free(ldisc->le); if (ldisc->term) ldisc->term->ldisc = NULL; if (ldisc->backend) backend_provide_ldisc(ldisc->backend, NULL); - if (ldisc->buf) - sfree(ldisc->buf); - if (ldisc->prompts && ldisc->prompts->ldisc_ptr_to_us == &ldisc->prompts) - ldisc->prompts->ldisc_ptr_to_us = NULL; delete_callbacks_for_context(ldisc); sfree(ldisc); } @@ -191,180 +138,119 @@ void ldisc_free(Ldisc *ldisc) void ldisc_echoedit_update(Ldisc *ldisc) { seat_echoedit_update(ldisc->seat, ECHOING, EDITING); -} -void ldisc_enable_prompt_callback(Ldisc *ldisc, prompts_t *prompts) -{ /* - * Called by the terminal to indicate that there's a prompts_t - * currently in flight, or to indicate that one has just finished - * (by passing NULL). When ldisc->prompts is not null, we notify - * the terminal whenever new data arrives in our input queue, so - * that it can continue the interactive prompting process. + * If we've just turned off local line editing mode, and our + * TermLineEditor had a partial buffer, then send the contents of + * the buffer. Rationale: (a) otherwise you lose data; (b) the + * user quite likely typed the buffer contents _anticipating_ that + * local editing would be turned off shortly, and the event was + * slow arriving. */ - ldisc->prompts = prompts; - if (prompts) - ldisc->prompts->ldisc_ptr_to_us = &ldisc->prompts; + if (!EDITING) + lineedit_send_line(ldisc->le); } -static void ldisc_input_queue_callback(void *ctx) +void ldisc_provide_userpass_le(Ldisc *ldisc, TermLineEditor *le) { /* - * Toplevel callback that is triggered whenever the input queue - * lengthens. If we're currently processing an interactive prompt, - * we call back the Terminal to tell it to do some more stuff with - * that prompt based on the new input. + * Called by term_get_userpass_input to tell us when it has its + * own TermLineEditor processing a password prompt, so that we can + * inject our input into that instead of putting it into our own + * TermLineEditor or sending it straight to the backend. */ - Ldisc *ldisc = (Ldisc *)ctx; - if (ldisc->term && ldisc->prompts) { + ldisc->userpass_le = le; +} + +static inline bool is_dedicated_byte(char c, InputType type) +{ + switch (type) { + case DEDICATED: + return true; + case NORMAL: + return false; + case NONINTERACTIVE: /* - * The integer return value from this call is discarded, - * because we have no channel to pass it on to the backend - * that originally wanted it. But that's OK, because if the - * return value is >= 0 (that is, the prompts are either - * completely filled in, or aborted by the user), then the - * terminal will notify the callback in the prompts_t, and - * when that calls term_get_userpass_input again, it will - * return the same answer again. + * Non-interactive input (e.g. from a paste) doesn't come with + * the ability to distinguish dedicated keypresses like Return + * from generic ones like Ctrl+M. So we just have to make up + * an answer to this question. In particular, we _must_ treat + * Ctrl+M as the Return key, because that's the only way a + * newline can be pasted at all. */ - term_get_userpass_input(ldisc->term, ldisc->prompts); + return c == '\r'; + default: + unreachable("those values should be exhaustive"); } } -static void ldisc_to_backend_raw( - Ldisc *ldisc, const void *vbuf, size_t len) +static void ldisc_input_queue_consume(Ldisc *ldisc, size_t size) { - if (backend_sendok(ldisc->backend)) { - backend_send(ldisc->backend, vbuf, len); - } else { - const char *buf = (const char *)vbuf; - while (len > 0) { - /* - * Encode raw data in input_queue, by storing large chunks - * as long as they don't include 0xFF, and pausing every - * time they do to escape it. - */ - const char *ff = memchr(buf, '\xFF', len); - size_t this_len = ff ? ff - buf : len; - if (this_len > 0) { - bufchain_add(&ldisc->input_queue, buf, len); - } else { - bufchain_add(&ldisc->input_queue, "\xFF\xFF", 2); - this_len = 1; - } - buf += this_len; - len -= this_len; + bufchain_consume(&ldisc->input_queue, size); + while (size > 0) { + size_t thissize = (size < ldisc->inchunk_head->size ? + size : ldisc->inchunk_head->size); + ldisc->inchunk_head->size -= thissize; + size -= thissize; + + if (!ldisc->inchunk_head->size) { + struct input_chunk *oldhead = ldisc->inchunk_head; + ldisc->inchunk_head = ldisc->inchunk_head->next; + if (!ldisc->inchunk_head) + ldisc->inchunk_tail = NULL; + sfree(oldhead); } } } -static void ldisc_to_backend_special( - Ldisc *ldisc, SessionSpecialCode code, int arg) +static void ldisc_lineedit_to_terminal( + TermLineEditorCallbackReceiver *rcv, ptrlen data) { - if (backend_sendok(ldisc->backend)) { - backend_special(ldisc->backend, code, arg); - } else { - /* - * Encode a session special in input_queue. - */ - unsigned char data[9]; - data[0] = 0xFF; - PUT_32BIT_MSB_FIRST(data+1, code); - PUT_32BIT_MSB_FIRST(data+5, arg); - assert(data[1] != 0xFF && - "SessionSpecialCode encoding collides with FF FF escape"); - bufchain_add(&ldisc->input_queue, data, 9); - } + Ldisc *ldisc = container_of(rcv, Ldisc, le_rcv); + if (ECHOING) + seat_stdout(ldisc->seat, data.ptr, data.len); } -bool ldisc_has_input_buffered(Ldisc *ldisc) +static void ldisc_lineedit_to_backend( + TermLineEditorCallbackReceiver *rcv, ptrlen data) { - return bufchain_size(&ldisc->input_queue) > 0; + Ldisc *ldisc = container_of(rcv, Ldisc, le_rcv); + backend_send(ldisc->backend, data.ptr, data.len); } -LdiscInputToken ldisc_get_input_token(Ldisc *ldisc) +static void ldisc_lineedit_special( + TermLineEditorCallbackReceiver *rcv, SessionSpecialCode code, int arg) { - assert(bufchain_size(&ldisc->input_queue) > 0 && - "You're not supposed to call this unless there is buffered input!"); - - LdiscInputToken tok; - - char c; - bufchain_fetch_consume(&ldisc->input_queue, &c, 1); - if (c != '\xFF') { - /* A literal non-FF byte */ - tok.is_special = false; - tok.chr = c; - return tok; - } else { - char data[8]; - - /* See if the byte after the FF is also FF, indicating a literal FF */ - bufchain_fetch_consume(&ldisc->input_queue, data, 1); - if (data[0] == '\xFF') { - tok.is_special = false; - tok.chr = '\xFF'; - return tok; - } - - /* If not, get the rest of an 8-byte chunk and decode a special */ - bufchain_fetch_consume(&ldisc->input_queue, data+1, 7); - tok.is_special = true; - tok.code = GET_32BIT_MSB_FIRST(data); - tok.arg = toint(GET_32BIT_MSB_FIRST(data+4)); - return tok; - } + Ldisc *ldisc = container_of(rcv, Ldisc, le_rcv); + backend_special(ldisc->backend, code, arg); } -static void ldisc_check_sendok_callback(void *ctx) +static void ldisc_lineedit_newline(TermLineEditorCallbackReceiver *rcv) { - Ldisc *ldisc = (Ldisc *)ctx; - - if (!(ldisc->backend && backend_sendok(ldisc->backend))) - return; - - /* - * Flush the ldisc input queue into the backend, which is now - * willing to receive the data. - */ - while (bufchain_size(&ldisc->input_queue) > 0) { - /* - * Process either a chunk of non-special data, or an FF - * escape, depending on whether the first thing we see is an - * FF byte. - */ - ptrlen data = bufchain_prefix(&ldisc->input_queue); - const char *ff = memchr(data.ptr, '\xFF', data.len); - if (ff != data.ptr) { - /* Send a maximal block of data not containing any - * difficult bytes. */ - if (ff) - data.len = ff - (const char *)data.ptr; - backend_send(ldisc->backend, data.ptr, data.len); - bufchain_consume(&ldisc->input_queue, data.len); - } else { - /* Decode either a special or an escaped FF byte. The - * easiest way to do this is to reuse the decoding code - * already in ldisc_get_input_token. */ - LdiscInputToken tok = ldisc_get_input_token(ldisc); - if (tok.is_special) - backend_special(ldisc->backend, tok.code, tok.arg); - else - backend_send(ldisc->backend, &tok.chr, 1); - } - } + Ldisc *ldisc = container_of(rcv, Ldisc, le_rcv); + if (ldisc->protocol == PROT_RAW) + backend_send(ldisc->backend, "\r\n", 2); + else if (ldisc->protocol == PROT_TELNET && ldisc->telnet_newline) + backend_special(ldisc->backend, SS_EOL, 0); + else + backend_send(ldisc->backend, "\r", 1); } +static const TermLineEditorCallbackReceiverVtable +ldisc_lineedit_receiver_vt = { + .to_terminal = ldisc_lineedit_to_terminal, + .to_backend = ldisc_lineedit_to_backend, + .special = ldisc_lineedit_special, + .newline = ldisc_lineedit_newline, +}; + void ldisc_check_sendok(Ldisc *ldisc) { - queue_toplevel_callback(ldisc_check_sendok_callback, ldisc); + queue_idempotent_callback(&ldisc->input_queue_callback); } void ldisc_send(Ldisc *ldisc, const void *vbuf, int len, bool interactive) { - const char *buf = (const char *)vbuf; - int keyflag = 0; - assert(ldisc->term); if (interactive) { @@ -379,202 +265,124 @@ void ldisc_send(Ldisc *ldisc, const void *vbuf, int len, bool interactive) term_nopaste(ldisc->term); } + InputType type; + if (len < 0) { + /* + * Less than zero means null terminated special string. + */ + len = strlen(vbuf); + type = DEDICATED; + } else { + type = interactive ? NORMAL : NONINTERACTIVE; + } + /* - * Less than zero means null terminated special string. + * Append our data to input_queue, and ensure it's marked with the + * right type. */ - if (len < 0) { - len = strlen(buf); - keyflag = KCTRL('@'); + bufchain_add(&ldisc->input_queue, vbuf, len); + if (!(ldisc->inchunk_tail && ldisc->inchunk_tail->type == type)) { + struct input_chunk *new_chunk = snew(struct input_chunk); + + new_chunk->type = type; + new_chunk->size = 0; + + new_chunk->next = NULL; + if (ldisc->inchunk_tail) + ldisc->inchunk_tail->next = new_chunk; + else + ldisc->inchunk_head = new_chunk; + ldisc->inchunk_tail = new_chunk; } + ldisc->inchunk_tail->size += len; + /* - * Either perform local editing, or just send characters. + * And process as much of the data immediately as we can. */ - if (EDITING) { - while (len--) { - int c; - c = (unsigned char)(*buf++) + keyflag; - if (!interactive && c == '\r') - c += KCTRL('@'); - switch (ldisc->quotenext ? ' ' : c) { - /* - * ^h/^?: delete, and output BSBs, to return to - * last character boundary (in UTF-8 mode this may - * be more than one byte) - * ^w: delete, and output BSBs, to return to last - * space/nonspace boundary - * ^u: delete, and output BSBs, to return to BOL - * ^c: Do a ^u then send a telnet IP - * ^z: Do a ^u then send a telnet SUSP - * ^\: Do a ^u then send a telnet ABORT - * ^r: echo "^R\n" and redraw line - * ^v: quote next char - * ^d: if at BOL, end of file and close connection, - * else send line and reset to BOL - * ^m: send line-plus-\r\n and reset to BOL - */ - case KCTRL('H'): - case KCTRL('?'): /* backspace/delete */ - if (ldisc->buflen > 0) { - do { - if (ECHOING) - bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1])); - ldisc->buflen--; - } while (!char_start(ldisc, ldisc->buf[ldisc->buflen])); - } - break; - case CTRL('W'): /* delete word */ - while (ldisc->buflen > 0) { - if (ECHOING) - bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1])); - ldisc->buflen--; - if (ldisc->buflen > 0 && - isspace((unsigned char)ldisc->buf[ldisc->buflen-1]) && - !isspace((unsigned char)ldisc->buf[ldisc->buflen])) - break; - } - break; - case CTRL('U'): /* delete line */ - case CTRL('C'): /* Send IP */ - case CTRL('\\'): /* Quit */ - case CTRL('Z'): /* Suspend */ - while (ldisc->buflen > 0) { - if (ECHOING) - bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1])); - ldisc->buflen--; - } - if (c == CTRL('U')) - break; /* ^U *just* erases a line */ - ldisc_to_backend_special(ldisc, SS_EL, 0); - /* - * We don't send IP, SUSP or ABORT if the user has - * configured telnet specials off! This breaks - * talkers otherwise. - */ - if (!ldisc->telnet_keyboard) - goto default_case; - if (c == CTRL('C')) - ldisc_to_backend_special(ldisc, SS_IP, 0); - if (c == CTRL('Z')) - ldisc_to_backend_special(ldisc, SS_SUSP, 0); - if (c == CTRL('\\')) - ldisc_to_backend_special(ldisc, SS_ABORT, 0); - break; - case CTRL('R'): /* redraw line */ - if (ECHOING) { - int i; - c_write(ldisc, "^R\r\n", 4); - for (i = 0; i < ldisc->buflen; i++) - pwrite(ldisc, ldisc->buf[i]); - } - break; - case CTRL('V'): /* quote next char */ - ldisc->quotenext = true; - break; - case CTRL('D'): /* logout or send */ - if (ldisc->buflen == 0) { - ldisc_to_backend_special(ldisc, SS_EOF, 0); - } else { - ldisc_to_backend_raw(ldisc, ldisc->buf, ldisc->buflen); - ldisc->buflen = 0; - } - break; - /* - * This particularly hideous bit of code from RDB - * allows ordinary ^M^J to do the same thing as - * magic-^M when in Raw protocol. The line `case - * KCTRL('M'):' is _inside_ the if block. Thus: - * - * - receiving regular ^M goes straight to the - * default clause and inserts as a literal ^M. - * - receiving regular ^J _not_ directly after a - * literal ^M (or not in Raw protocol) fails the - * if condition, leaps to the bottom of the if, - * and falls through into the default clause - * again. - * - receiving regular ^J just after a literal ^M - * in Raw protocol passes the if condition, - * deletes the literal ^M, and falls through - * into the magic-^M code - * - receiving a magic-^M empties the line buffer, - * signals end-of-line in one of the various - * entertaining ways, and _doesn't_ fall out of - * the bottom of the if and through to the - * default clause because of the break. - */ - case CTRL('J'): - if (ldisc->protocol == PROT_RAW && - ldisc->buflen > 0 && ldisc->buf[ldisc->buflen - 1] == '\r') { - if (ECHOING) - bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1])); - ldisc->buflen--; - /* FALLTHROUGH */ - case KCTRL('M'): /* send with newline */ - if (ldisc->buflen > 0) - ldisc_to_backend_raw(ldisc, ldisc->buf, ldisc->buflen); - if (ldisc->protocol == PROT_RAW) - ldisc_to_backend_raw(ldisc, "\r\n", 2); - else if (ldisc->protocol == PROT_TELNET && ldisc->telnet_newline) - ldisc_to_backend_special(ldisc, SS_EOL, 0); - else - ldisc_to_backend_raw(ldisc, "\r", 1); - if (ECHOING) - c_write(ldisc, "\r\n", 2); - ldisc->buflen = 0; - break; - } - /* FALLTHROUGH */ - default: /* get to this label from ^V handler */ - default_case: - sgrowarray(ldisc->buf, ldisc->bufsiz, ldisc->buflen); - ldisc->buf[ldisc->buflen++] = c; - if (ECHOING) - pwrite(ldisc, (unsigned char) c); - ldisc->quotenext = false; - break; - } + ldisc_input_queue_callback(ldisc); +} + +static void ldisc_input_queue_callback(void *ctx) +{ + Ldisc *ldisc = (Ldisc *)ctx; + + /* + * Toplevel callback that is triggered whenever the input queue + * lengthens. + */ + while (bufchain_size(&ldisc->input_queue)) { + ptrlen pl = bufchain_prefix(&ldisc->input_queue); + const char *start = pl.ptr, *buf = pl.ptr; + size_t len = (pl.len < ldisc->inchunk_head->size ? + pl.len : ldisc->inchunk_head->size); + InputType type = ldisc->inchunk_head->type; + + while (len > 0 && ldisc->userpass_le) { + char c = *buf++; + len--; + + bool dedicated = is_dedicated_byte(c, type); + lineedit_input(ldisc->userpass_le, c, dedicated); } - } else { - if (ldisc->buflen != 0) { - ldisc_to_backend_raw(ldisc, ldisc->buf, ldisc->buflen); - while (ldisc->buflen > 0) { - bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1])); - ldisc->buflen--; - } + + if (!backend_sendok(ldisc->backend)) { + ldisc_input_queue_consume(ldisc, buf - start); + break; } - if (len > 0) { + + /* + * Either perform local editing, or just send characters. + */ + if (EDITING) { + while (len > 0) { + char c = *buf++; + len--; + + bool dedicated = is_dedicated_byte(c, type); + lineedit_input(ldisc->le, c, dedicated); + } + ldisc_input_queue_consume(ldisc, buf - start); + } else { if (ECHOING) - c_write(ldisc, buf, len); - if (keyflag && ldisc->protocol == PROT_TELNET && len == 1) { - switch (buf[0]) { - case CTRL('M'): - if (ldisc->protocol == PROT_TELNET && ldisc->telnet_newline) - ldisc_to_backend_special(ldisc, SS_EOL, 0); - else - ldisc_to_backend_raw(ldisc, "\r", 1); - break; - case CTRL('?'): - case CTRL('H'): - if (ldisc->telnet_keyboard) { - ldisc_to_backend_special(ldisc, SS_EC, 0); + seat_stdout(ldisc->seat, buf, len); + if (type == DEDICATED && ldisc->protocol == PROT_TELNET) { + while (len > 0) { + char c = *buf++; + len--; + switch (c) { + case CTRL('M'): + if (ldisc->telnet_newline) + backend_special(ldisc->backend, SS_EOL, 0); + else + backend_send(ldisc->backend, "\r", 1); break; - } - case CTRL('C'): - if (ldisc->telnet_keyboard) { - ldisc_to_backend_special(ldisc, SS_IP, 0); - break; - } - case CTRL('Z'): - if (ldisc->telnet_keyboard) { - ldisc_to_backend_special(ldisc, SS_SUSP, 0); + case CTRL('?'): + case CTRL('H'): + if (ldisc->telnet_keyboard) { + backend_special(ldisc->backend, SS_EC, 0); + break; + } + case CTRL('C'): + if (ldisc->telnet_keyboard) { + backend_special(ldisc->backend, SS_IP, 0); + break; + } + case CTRL('Z'): + if (ldisc->telnet_keyboard) { + backend_special(ldisc->backend, SS_SUSP, 0); + break; + } + + default: + backend_send(ldisc->backend, &c, 1); break; } - - default: - ldisc_to_backend_raw(ldisc, buf, len); - break; } - } else - ldisc_to_backend_raw(ldisc, buf, len); + ldisc_input_queue_consume(ldisc, buf - start); + } else { + backend_send(ldisc->backend, buf, len); + ldisc_input_queue_consume(ldisc, len); + } } } } diff --git a/logging.c b/logging.c index 031aae4a..2c3ab3f6 100644 --- a/logging.c +++ b/logging.c @@ -254,26 +254,6 @@ void logevent(LogContext *ctx, const char *event) } } -void logevent_and_free(LogContext *ctx, char *event) -{ - logevent(ctx, event); - sfree(event); -} - -void logeventvf(LogContext *ctx, const char *fmt, va_list ap) -{ - logevent_and_free(ctx, dupvprintf(fmt, ap)); -} - -void logeventf(LogContext *ctx, const char *fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); - logeventvf(ctx, fmt, ap); - va_end(ap); -} - /* * Log an SSH packet. * If n_blanks != 0, blank or omit some parts. diff --git a/marshal.h b/marshal.h index b9136292..59b9ed2a 100644 --- a/marshal.h +++ b/marshal.h @@ -156,6 +156,16 @@ struct BinarySink { #define put_c_string_literal(bs, str) \ BinarySink_put_c_string_literal(BinarySink_UPCAST(bs), str) +/* More complicated function implemented in encode_utf8.c */ +#define put_utf8_char(bs, c) \ + BinarySink_put_utf8_char(BinarySink_UPCAST(bs), c) + +/* More complicated functions still implemented in /unicode.c */ +#define put_mb_to_wc(bs, codepage, mbstr, mblen) \ + BinarySink_put_mb_to_wc(BinarySink_UPCAST(bs), codepage, mbstr, mblen) +#define put_wc_to_mb(bs, codepage, wcstr, wclen, def) \ + BinarySink_put_wc_to_mb(BinarySink_UPCAST(bs), codepage, wcstr, wclen, def) + /* * The underlying real C functions that implement most of those * macros. Generally you won't want to call these directly, because @@ -185,6 +195,13 @@ void BinarySink_put_mp_ssh2(BinarySink *bs, mp_int *x); void BinarySink_put_fmt(BinarySink *, const char *fmt, ...) PRINTF_LIKE(2, 3); void BinarySink_put_fmtv(BinarySink *, const char *fmt, va_list ap); void BinarySink_put_c_string_literal(BinarySink *, ptrlen); +void BinarySink_put_utf8_char(BinarySink *, unsigned); +/* put_mb_to_wc / put_wc_to_mb return false if the codepage is invalid */ +bool BinarySink_put_mb_to_wc( + BinarySink *bs, int codepage, const char *mbstr, int mblen); +bool BinarySink_put_wc_to_mb( + BinarySink *bs, int codepage, const wchar_t *wcstr, int wclen, + const char *defchr); /* ---------------------------------------------------------------------- */ @@ -353,7 +370,14 @@ struct bufchain_sink { bufchain *ch; BinarySink_IMPLEMENTATION; }; +struct buffer_sink { + char *out; + size_t space; + bool overflowed; + BinarySink_IMPLEMENTATION; +}; void stdio_sink_init(stdio_sink *sink, FILE *fp); void bufchain_sink_init(bufchain_sink *sink, bufchain *ch); +void buffer_sink_init(buffer_sink *sink, void *buffer, size_t len); #endif /* PUTTY_MARSHAL_H */ diff --git a/misc.h b/misc.h index 1b3d324a..7d94e7e6 100644 --- a/misc.h +++ b/misc.h @@ -26,11 +26,13 @@ char *host_strrchr(const char *s, int c); char *host_strduptrim(const char *s); char *dupstr(const char *s); +wchar_t *dupwcs(const wchar_t *s); char *dupcat_fn(const char *s1, ...); #define dupcat(...) dupcat_fn(__VA_ARGS__, (const char *)NULL) char *dupprintf(const char *fmt, ...) PRINTF_LIKE(1, 2); char *dupvprintf(const char *fmt, va_list ap); void burnstr(char *string); +void burnwcs(wchar_t *string); /* * The visible part of a strbuf structure. There's a surrounding @@ -69,11 +71,12 @@ void strbuf_finalise_agent_query(strbuf *buf); /* String-to-Unicode converters that auto-allocate the destination and * work around the rather deficient interface of mb_to_wc. */ -wchar_t *dup_mb_to_wc_c(int codepage, int flags, const char *string, int len); -wchar_t *dup_mb_to_wc(int codepage, int flags, const char *string); -char *dup_wc_to_mb_c(int codepage, int flags, const wchar_t *string, int len, - const char *defchr); -char *dup_wc_to_mb(int codepage, int flags, const wchar_t *string, +wchar_t *dup_mb_to_wc_c(int codepage, const char *string, + size_t len, size_t *outlen_p); +wchar_t *dup_mb_to_wc(int codepage, const char *string); +char *dup_wc_to_mb_c(int codepage, const wchar_t *string, + size_t len, const char *defchr, size_t *outlen_p); +char *dup_wc_to_mb(int codepage, const wchar_t *string, const char *defchr); static inline int toint(unsigned u) @@ -248,26 +251,53 @@ void smemclr(void *b, size_t len); * by the 'eq' in the name. */ unsigned smemeq(const void *av, const void *bv, size_t len); -/* Encode a single UTF-8 character. Assumes that illegal characters - * (such as things in the surrogate range, or > 0x10FFFF) have already - * been removed. */ -size_t encode_utf8(void *output, unsigned long ch); - /* Encode a wide-character string into UTF-8. Tolerates surrogates if * sizeof(wchar_t) == 2, assuming that in that case the wide string is * encoded in UTF-16. */ char *encode_wide_string_as_utf8(const wchar_t *wstr); +/* Decode UTF-8 to a wide-character string, emitting UTF-16 surrogates + * if sizeof(wchar_t) == 2. */ +wchar_t *decode_utf8_to_wide_string(const char *ustr); + /* Decode a single UTF-8 character. Returns U+FFFD for any of the - * illegal cases. */ -unsigned long decode_utf8(const char **utf8); + * illegal cases. If the source is empty, returns L'\0' (and sets the + * error indicator on the source, of course). */ +#define DECODE_UTF8_FAILURE_LIST(X) \ + X(DUTF8_SUCCESS, "success") \ + X(DUTF8_SPURIOUS_CONTINUATION, "spurious continuation byte") \ + X(DUTF8_ILLEGAL_BYTE, "illegal UTF-8 byte value") \ + X(DUTF8_E_OUT_OF_DATA, "unfinished multibyte encoding at end of string") \ + X(DUTF8_TRUNCATED_SEQUENCE, "multibyte encoding interrupted by " \ + "non-continuation byte") \ + X(DUTF8_OVERLONG_ENCODING, "overlong encoding") \ + X(DUTF8_ENCODED_SURROGATE, "Unicode surrogate character encoded in " \ + "UTF-8") \ + X(DUTF8_CODE_POINT_TOO_BIG, "code point outside the Unicode range") \ + /* end of list */ +typedef enum DecodeUTF8Failure { + #define ENUM_DECL(sym, string) sym, + DECODE_UTF8_FAILURE_LIST(ENUM_DECL) + #undef ENUM_DECL + DUTF8_N_FAILURE_CODES +} DecodeUTF8Failure; +unsigned decode_utf8(BinarySource *src, DecodeUTF8Failure *err); +extern const char *const decode_utf8_error_strings[DUTF8_N_FAILURE_CODES]; /* Decode a single UTF-8 character to an output buffer of the * platform's wchar_t. May write a pair of surrogates if * sizeof(wchar_t) == 2, assuming that in that case the wide string is * encoded in UTF-16. Otherwise, writes one character. Returns the * number written. */ -size_t decode_utf8_to_wchar(const char **utf8, wchar_t *out); +size_t decode_utf8_to_wchar(BinarySource *src, wchar_t *out, + DecodeUTF8Failure *err); + +/* Normalise a UTF-8 string into Normalisation Form C. */ +strbuf *utf8_to_nfc(ptrlen input); + +/* Determine if a UTF-8 string contains any characters unknown to our + * supported version of Unicode. */ +char *utf8_unknown_char(ptrlen input); /* Write a string out in C string-literal format. */ void write_c_string_literal(FILE *fp, ptrlen str); @@ -309,6 +339,11 @@ void debug_memdump(const void *buf, int len, bool L); #define debug(...) (debug_printf(__VA_ARGS__)) #define dmemdump(buf,len) (debug_memdump(buf, len, false)) #define dmemdumpl(buf,len) (debug_memdump(buf, len, true)) + +/* Functions used only for debugging, not declared unless + * defined(DEBUG) to avoid accidentally linking them in production */ +const char *conf_id(int key); + #else #define debug(...) ((void)0) #define dmemdump(buf,len) ((void)0) diff --git a/network.h b/network.h index 57e6662d..ae56ba4a 100644 --- a/network.h +++ b/network.h @@ -34,7 +34,7 @@ struct SocketVtable { void (*set_frozen) (Socket *s, bool is_frozen); /* ignored by tcp, but vital for ssl */ const char *(*socket_error) (Socket *s); - SocketPeerInfo *(*peer_info) (Socket *s); + SocketEndpointInfo *(*endpoint_info) (Socket *s, bool peer); }; typedef union { void *p; int i; } accept_ctx_t; @@ -91,7 +91,7 @@ struct PlugVtable { * all Plugs must implement this method, even if only to ignore * the logged events. */ - void (*log)(Plug *p, PlugLogType type, SockAddr *addr, int port, + void (*log)(Plug *p, Socket *s, PlugLogType type, SockAddr *addr, int port, const char *error_msg, int error_code); /* @@ -245,8 +245,9 @@ static inline void sk_write_eof(Socket *s) { s->vt->write_eof(s); } static inline void plug_log( - Plug *p, int type, SockAddr *addr, int port, const char *msg, int code) -{ p->vt->log(p, type, addr, port, msg, code); } + Plug *p, Socket *s, int type, SockAddr *addr, int port, + const char *msg, int code) +{ p->vt->log(p, s, type, addr, port, msg, code); } static inline void plug_closing(Plug *p, PlugCloseType type, const char *msg) { p->vt->closing(p, type, msg); } static inline void plug_closing_normal(Plug *p) @@ -292,23 +293,25 @@ static inline void sk_set_frozen(Socket *s, bool is_frozen) { s->vt->set_frozen(s, is_frozen); } /* - * Return a structure giving some information about the other end of + * Return a structure giving some information about one end of * the socket. May be NULL, if nothing is available at all. If it is * not NULL, then it is dynamically allocated, and should be freed by - * a call to sk_free_peer_info(). See below for the definition. + * a call to sk_free_endpoint_info(). See below for the definition. */ -static inline SocketPeerInfo *sk_peer_info(Socket *s) -{ return s->vt->peer_info(s); } +static inline SocketEndpointInfo *sk_endpoint_info(Socket *s, bool peer) +{ return s->vt->endpoint_info(s, peer); } +static inline SocketEndpointInfo *sk_peer_info(Socket *s) +{ return sk_endpoint_info(s, true); } /* - * The structure returned from sk_peer_info, and a function to free + * The structure returned from sk_endpoint_info, and a function to free * one (in utils). */ -struct SocketPeerInfo { +struct SocketEndpointInfo { int addressfamily; /* - * Text form of the IPv4 or IPv6 address of the other end of the + * Text form of the IPv4 or IPv6 address of the specified end of the * socket, if available, in the standard text representation. */ const char *addr_text; @@ -337,7 +340,7 @@ struct SocketPeerInfo { */ const char *log_text; }; -void sk_free_peer_info(SocketPeerInfo *pi); +void sk_free_endpoint_info(SocketEndpointInfo *ei); /* * Simple wrapper on getservbyname(), needed by portfwd.c. Returns the @@ -377,19 +380,24 @@ extern Plug *const nullplug; * In particular, nullplug_log is useful to Plugs that don't need to * worry about logging. */ -void nullplug_log(Plug *plug, PlugLogType type, SockAddr *addr, +void nullplug_log(Plug *plug, Socket *s, PlugLogType type, SockAddr *addr, int port, const char *err_msg, int err_code); void nullplug_closing(Plug *plug, PlugCloseType type, const char *error_msg); void nullplug_receive(Plug *plug, int urgent, const char *data, size_t len); void nullplug_sent(Plug *plug, size_t bufsize); +/* + * Similar no-op socket function. + */ +SocketEndpointInfo *nullsock_endpoint_info(Socket *s, bool peer); + /* ---------------------------------------------------------------------- * Functions defined outside the network code, which have to be * declared in this header file rather than the main putty.h because * they use types defined here. */ -void backend_socket_log(Seat *seat, LogContext *logctx, +void backend_socket_log(Seat *seat, LogContext *logctx, Socket *sock, PlugLogType type, SockAddr *addr, int port, const char *error_msg, int error_code, Conf *conf, bool session_started); @@ -401,8 +409,8 @@ typedef struct ProxyStderrBuf { } ProxyStderrBuf; void psb_init(ProxyStderrBuf *psb); void psb_set_prefix(ProxyStderrBuf *psb, const char *prefix); -void log_proxy_stderr( - Plug *plug, ProxyStderrBuf *psb, const void *vdata, size_t len); +void log_proxy_stderr(Plug *plug, Socket *sock, ProxyStderrBuf *psb, + const void *vdata, size_t len); /* ---------------------------------------------------------------------- * The DeferredSocketOpener trait. This is a thing that some Socket diff --git a/otherbackends/raw.c b/otherbackends/raw.c index c95db970..9fc5e4c8 100644 --- a/otherbackends/raw.c +++ b/otherbackends/raw.c @@ -36,12 +36,13 @@ static void c_write(Raw *raw, const void *buf, size_t len) sk_set_frozen(raw->s, backlog > RAW_MAX_BACKLOG); } -static void raw_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, - const char *error_msg, int error_code) +static void raw_log(Plug *plug, Socket *s, PlugLogType type, SockAddr *addr, + int port, const char *error_msg, int error_code) { Raw *raw = container_of(plug, Raw, plug); - backend_socket_log(raw->seat, raw->logctx, type, addr, port, error_msg, - error_code, raw->conf, raw->socket_connected); + backend_socket_log(raw->seat, raw->logctx, s, type, addr, port, + error_msg, error_code, raw->conf, + raw->socket_connected); if (type == PLUGLOG_CONNECT_SUCCESS) { raw->socket_connected = true; if (raw->ldisc) @@ -91,7 +92,7 @@ static void raw_closing(Plug *plug, PlugCloseType type, const char *error_msg) if (!raw->sent_socket_eof) { if (raw->s) sk_write_eof(raw->s); - raw->sent_socket_eof= true; + raw->sent_socket_eof = true; } } raw->sent_console_eof = true; @@ -287,7 +288,7 @@ static void raw_special(Backend *be, SessionSpecialCode code, int arg) if (code == SS_EOF && raw->s) { if (!raw->sent_socket_eof) sk_write_eof(raw->s); - raw->sent_socket_eof= true; + raw->sent_socket_eof = true; raw_check_close(raw); } diff --git a/otherbackends/rlogin.c b/otherbackends/rlogin.c index 37087257..6efe036f 100644 --- a/otherbackends/rlogin.c +++ b/otherbackends/rlogin.c @@ -45,11 +45,11 @@ static void c_write(Rlogin *rlogin, const void *buf, size_t len) sk_set_frozen(rlogin->s, backlog > RLOGIN_MAX_BACKLOG); } -static void rlogin_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, - const char *error_msg, int error_code) +static void rlogin_log(Plug *plug, Socket *s, PlugLogType type, SockAddr *addr, + int port, const char *error_msg, int error_code) { Rlogin *rlogin = container_of(plug, Rlogin, plug); - backend_socket_log(rlogin->seat, rlogin->logctx, type, addr, port, + backend_socket_log(rlogin->seat, rlogin->logctx, s, type, addr, port, error_msg, error_code, rlogin->conf, rlogin->socket_connected); if (type == PLUGLOG_CONNECT_SUCCESS) { diff --git a/otherbackends/supdup.c b/otherbackends/supdup.c index 9aaf1d4f..6f574c9f 100644 --- a/otherbackends/supdup.c +++ b/otherbackends/supdup.c @@ -559,11 +559,11 @@ static void do_supdup_read(Supdup *supdup, const char *buf, size_t len) strbuf_free(outbuf); } -static void supdup_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, - const char *error_msg, int error_code) +static void supdup_log(Plug *plug, Socket *s, PlugLogType type, SockAddr *addr, + int port, const char *error_msg, int error_code) { Supdup *supdup = container_of(plug, Supdup, plug); - backend_socket_log(supdup->seat, supdup->logctx, type, addr, port, + backend_socket_log(supdup->seat, supdup->logctx, s, type, addr, port, error_msg, error_code, supdup->conf, supdup->socket_connected); if (type == PLUGLOG_CONNECT_SUCCESS) { diff --git a/otherbackends/telnet.c b/otherbackends/telnet.c index 23b7fc9e..2f12722c 100644 --- a/otherbackends/telnet.c +++ b/otherbackends/telnet.c @@ -358,28 +358,26 @@ static void proc_rec_opt(Telnet *telnet, int cmd, int option) static void process_subneg(Telnet *telnet) { - unsigned char *b, *p, *q; - int var, value, n, bsize; - char *e, *eval, *ekey, *user; + unsigned char *p, *q; + int var, value; switch (telnet->sb_opt) { case TELOPT_TSPEED: if (telnet->sb_buf->len == 1 && telnet->sb_buf->u[0] == TELQUAL_SEND) { char *termspeed = conf_get_str(telnet->conf, CONF_termspeed); - b = snewn(20 + strlen(termspeed), unsigned char); - b[0] = IAC; - b[1] = SB; - b[2] = TELOPT_TSPEED; - b[3] = TELQUAL_IS; - strcpy((char *)(b + 4), termspeed); - n = 4 + strlen(termspeed); - b[n] = IAC; - b[n + 1] = SE; - telnet->bufsize = sk_write(telnet->s, b, n + 2); + strbuf *sb = strbuf_new(); + put_byte(sb, IAC); + put_byte(sb, SB); + put_byte(sb, TELOPT_TSPEED); + put_byte(sb, TELQUAL_IS); + put_datapl(sb, ptrlen_from_asciz(termspeed)); + put_byte(sb, IAC); + put_byte(sb, SE); + telnet->bufsize = sk_write(telnet->s, sb->s, sb->len); logevent(telnet->logctx, "server subnegotiation: SB TSPEED SEND"); logeventf(telnet->logctx, "client subnegotiation: SB TSPEED IS %s", termspeed); - sfree(b); + strbuf_free(sb); } else logevent(telnet->logctx, "server subnegotiation: SB TSPEED "); @@ -387,24 +385,24 @@ static void process_subneg(Telnet *telnet) case TELOPT_TTYPE: if (telnet->sb_buf->len == 1 && telnet->sb_buf->u[0] == TELQUAL_SEND) { char *termtype = conf_get_str(telnet->conf, CONF_termtype); - b = snewn(20 + strlen(termtype), unsigned char); - b[0] = IAC; - b[1] = SB; - b[2] = TELOPT_TTYPE; - b[3] = TELQUAL_IS; - for (n = 0; termtype[n]; n++) - b[n + 4] = (termtype[n] >= 'a' && termtype[n] <= 'z' ? - termtype[n] + 'A' - 'a' : - termtype[n]); - b[n + 4] = IAC; - b[n + 5] = SE; - telnet->bufsize = sk_write(telnet->s, b, n + 6); - b[n + 4] = 0; - logevent(telnet->logctx, - "server subnegotiation: SB TTYPE SEND"); - logeventf(telnet->logctx, - "client subnegotiation: SB TTYPE IS %s", b + 4); - sfree(b); + strbuf *sb = strbuf_new(); + put_byte(sb, IAC); + put_byte(sb, SB); + put_byte(sb, TELOPT_TTYPE); + put_byte(sb, TELQUAL_IS); + size_t tt_start = sb->len; + for (size_t n = 0; termtype[n]; n++) + put_byte(sb, (termtype[n] >= 'a' && termtype[n] <= 'z' ? + termtype[n] + 'A' - 'a' : termtype[n])); + size_t tt_end = sb->len; + put_byte(sb, IAC); + put_byte(sb, SE); + telnet->bufsize = sk_write(telnet->s, sb->s, sb->len); + strbuf_shrink_to(sb, tt_end); + logevent(telnet->logctx, "server subnegotiation: SB TTYPE SEND"); + logeventf(telnet->logctx, "client subnegotiation: SB TTYPE IS %s", + sb->s + tt_start); + strbuf_free(sb); } else logevent(telnet->logctx, "server subnegotiation: SB TTYPE \r\n"); @@ -446,49 +444,34 @@ static void process_subneg(Telnet *telnet) value = RFC_VALUE; var = RFC_VAR; } - bsize = 20; - for (eval = conf_get_str_strs(telnet->conf, CONF_environmt, - NULL, &ekey); - eval != NULL; - eval = conf_get_str_strs(telnet->conf, CONF_environmt, - ekey, &ekey)) - bsize += strlen(ekey) + strlen(eval) + 2; - user = get_remote_username(telnet->conf); - if (user) - bsize += 6 + strlen(user); - - b = snewn(bsize, unsigned char); - b[0] = IAC; - b[1] = SB; - b[2] = telnet->sb_opt; - b[3] = TELQUAL_IS; - n = 4; + + strbuf *sb = strbuf_new(); + put_byte(sb, IAC); + put_byte(sb, SB); + put_byte(sb, telnet->sb_opt); + put_byte(sb, TELQUAL_IS); + char *ekey, *eval; for (eval = conf_get_str_strs(telnet->conf, CONF_environmt, NULL, &ekey); eval != NULL; eval = conf_get_str_strs(telnet->conf, CONF_environmt, ekey, &ekey)) { - b[n++] = var; - for (e = ekey; *e; e++) - b[n++] = *e; - b[n++] = value; - for (e = eval; *e; e++) - b[n++] = *e; + put_byte(sb, var); + put_datapl(sb, ptrlen_from_asciz(ekey)); + put_byte(sb, value); + put_datapl(sb, ptrlen_from_asciz(eval)); } + char *user = get_remote_username(telnet->conf); if (user) { - b[n++] = var; - b[n++] = 'U'; - b[n++] = 'S'; - b[n++] = 'E'; - b[n++] = 'R'; - b[n++] = value; - for (e = user; *e; e++) - b[n++] = *e; + put_byte(sb, var); + put_datalit(sb, "USER"); + put_byte(sb, value); + put_datapl(sb, ptrlen_from_asciz(user)); } - b[n++] = IAC; - b[n++] = SE; - telnet->bufsize = sk_write(telnet->s, b, n); - if (n == 6) { + put_byte(sb, IAC); + put_byte(sb, SE); + telnet->bufsize = sk_write(telnet->s, sb->s, sb->len); + if (sb->len == 6) { logeventf(telnet->logctx, "client subnegotiation: SB %s IS ", telopt(telnet->sb_opt)); @@ -505,7 +488,7 @@ static void process_subneg(Telnet *telnet) if (user) logeventf(telnet->logctx, " USER=%s", user); } - sfree(b); + strbuf_free(sb); sfree(user); } break; @@ -621,11 +604,11 @@ static void do_telnet_read(Telnet *telnet, const char *buf, size_t len) strbuf_free(outbuf); } -static void telnet_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, - const char *error_msg, int error_code) +static void telnet_log(Plug *plug, Socket *s, PlugLogType type, SockAddr *addr, + int port, const char *error_msg, int error_code) { Telnet *telnet = container_of(plug, Telnet, plug); - backend_socket_log(telnet->seat, telnet->logctx, type, addr, port, + backend_socket_log(telnet->seat, telnet->logctx, s, type, addr, port, error_msg, error_code, telnet->conf, telnet->socket_connected); if (type == PLUGLOG_CONNECT_SUCCESS) { diff --git a/pageant.c b/pageant.c index 455e434f..961e1f03 100644 --- a/pageant.c +++ b/pageant.c @@ -1683,7 +1683,7 @@ void pageant_reencrypt_all(void) #define crGetChar(c) do \ { \ while (len == 0) { \ - *crLine =__LINE__; return; case __LINE__:; \ + *crLine = __LINE__; return; case __LINE__:; \ } \ len--; \ (c) = (unsigned char)*data++; \ @@ -1885,7 +1885,7 @@ static int pageant_listen_accepting(Plug *plug, plug, struct pageant_listen_state, plug); struct pageant_conn_state *pc; const char *err; - SocketPeerInfo *peerinfo; + SocketEndpointInfo *peerinfo; pc = snew(struct pageant_conn_state); pc->plug.vt = &pageant_connection_plugvt; @@ -1914,7 +1914,7 @@ static int pageant_listen_accepting(Plug *plug, pageant_listener_client_log(pl->plc, "c#%"SIZEu": new connection", pc->conn_index); } - sk_free_peer_info(peerinfo); + sk_free_endpoint_info(peerinfo); pageant_register_client(&pc->pc); @@ -2673,14 +2673,14 @@ int pageant_sign(struct pageant_pubkey *key, ptrlen message, strbuf *out, } } -struct pageant_pubkey *pageant_pubkey_copy(struct pageant_pubkey *key) +struct pageant_pubkey *pageant_pubkey_copy(struct pageant_pubkey *orig) { - struct pageant_pubkey *ret = snew(struct pageant_pubkey); - ret->blob = strbuf_new(); - put_data(ret->blob, key->blob->s, key->blob->len); - ret->comment = key->comment ? dupstr(key->comment) : NULL; - ret->ssh_version = key->ssh_version; - return ret; + struct pageant_pubkey *copy = snew(struct pageant_pubkey); + copy->blob = strbuf_new(); + put_data(copy->blob, orig->blob->s, orig->blob->len); + copy->comment = orig->comment ? dupstr(orig->comment) : NULL; + copy->ssh_version = orig->ssh_version; + return copy; } void pageant_pubkey_free(struct pageant_pubkey *key) diff --git a/proxy/local.c b/proxy/local.c index 3b2d130c..ff504cce 100644 --- a/proxy/local.c +++ b/proxy/local.c @@ -209,7 +209,8 @@ static void local_proxy_opener_coroutine(void *vctx) put_datapl(logmsg, PTRLEN_LITERAL("Starting local proxy command: ")); put_c_string_literal(logmsg, ptrlen_from_asciz(censored_cmd)); - plug_log(lp->plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg->s, 0); + plug_log(lp->plug, lp->socket, PLUGLOG_PROXY_MSG, NULL, 0, + logmsg->s, 0); strbuf_free(logmsg); sfree(censored_cmd); } diff --git a/proxy/proxy.c b/proxy/proxy.c index bca60a35..b7428816 100644 --- a/proxy/proxy.c +++ b/proxy/proxy.c @@ -41,7 +41,7 @@ static void proxy_activate(ProxySocket *ps) proxy_negotiator_cleanup(ps); - plug_log(ps->plug, PLUGLOG_CONNECT_SUCCESS, NULL, 0, NULL, 0); + plug_log(ps->plug, &ps->sock, PLUGLOG_CONNECT_SUCCESS, NULL, 0, NULL, 0); /* we want to ignore new receive events until we have sent * all of our buffered receive data. @@ -189,12 +189,13 @@ static const char *sk_proxy_socket_error (Socket *s) /* basic proxy plug functions */ -static void plug_proxy_log(Plug *plug, PlugLogType type, SockAddr *addr, - int port, const char *error_msg, int error_code) +static void plug_proxy_log(Plug *plug, Socket *s, PlugLogType type, + SockAddr *addr, int port, + const char *error_msg, int error_code) { ProxySocket *ps = container_of(plug, ProxySocket, plugimpl); - plug_log(ps->plug, type, addr, port, error_msg, error_code); + plug_log(ps->plug, &ps->sock, type, addr, port, error_msg, error_code); } static void plug_proxy_closing(Plug *p, PlugCloseType type, @@ -415,6 +416,19 @@ SockAddr *name_lookup(const char *host, int port, char **canonicalname, } } +static SocketEndpointInfo *sk_proxy_endpoint_info(Socket *s, bool peer) +{ + ProxySocket *ps = container_of(s, ProxySocket, sock); + + /* We can't reliably find out where we ended up connecting _to_: + * that's at the far end of the proxy, and might be anything. */ + if (peer) + return NULL; + + /* But we can at least tell where we're coming _from_. */ + return sk_endpoint_info(ps->sub_socket, false); +} + static const SocketVtable ProxySocket_sockvt = { .plug = sk_proxy_plug, .close = sk_proxy_close, @@ -423,7 +437,7 @@ static const SocketVtable ProxySocket_sockvt = { .write_eof = sk_proxy_write_eof, .set_frozen = sk_proxy_set_frozen, .socket_error = sk_proxy_socket_error, - .peer_info = NULL, + .endpoint_info = sk_proxy_endpoint_info, }; static const PlugVtable ProxySocket_plugvt = { @@ -499,8 +513,7 @@ Socket *new_connection(SockAddr *addr, const char *hostname, int type = conf_get_int(conf, CONF_proxy_type); if (type != PROXY_NONE && - proxy_for_destination(addr, hostname, port, conf)) - { + proxy_for_destination(addr, hostname, port, conf)) { ProxySocket *ps; SockAddr *proxy_addr; char *proxy_canonical_name; @@ -585,7 +598,7 @@ Socket *new_connection(SockAddr *addr, const char *hostname, conf_get_str(conf, CONF_proxy_host), conf_get_int(conf, CONF_proxy_port), hostname, port); - plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg, 0); + plug_log(plug, &ps->sock, PLUGLOG_PROXY_MSG, NULL, 0, logmsg, 0); sfree(logmsg); } @@ -593,7 +606,7 @@ Socket *new_connection(SockAddr *addr, const char *hostname, char *logmsg = dns_log_msg(conf_get_str(conf, CONF_proxy_host), conf_get_int(conf, CONF_addressfamily), "proxy"); - plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg, 0); + plug_log(plug, &ps->sock, PLUGLOG_PROXY_MSG, NULL, 0, logmsg, 0); sfree(logmsg); } @@ -614,7 +627,7 @@ Socket *new_connection(SockAddr *addr, const char *hostname, logmsg = dupprintf("Connecting to %s proxy at %s port %d", vt->type, addrbuf, conf_get_int(conf, CONF_proxy_port)); - plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg, 0); + plug_log(plug, &ps->sock, PLUGLOG_PROXY_MSG, NULL, 0, logmsg, 0); sfree(logmsg); } diff --git a/proxy/sshproxy.c b/proxy/sshproxy.c index 4a797d38..0958c3cd 100644 --- a/proxy/sshproxy.c +++ b/proxy/sshproxy.c @@ -123,11 +123,6 @@ static const char *sshproxy_socket_error(Socket *s) return sp->errmsg; } -static SocketPeerInfo *sshproxy_peer_info(Socket *s) -{ - return NULL; -} - static const SocketVtable SshProxy_sock_vt = { .plug = sshproxy_plug, .close = sshproxy_close, @@ -136,14 +131,14 @@ static const SocketVtable SshProxy_sock_vt = { .write_eof = sshproxy_write_eof, .set_frozen = sshproxy_set_frozen, .socket_error = sshproxy_socket_error, - .peer_info = sshproxy_peer_info, + .endpoint_info = nullsock_endpoint_info, }; static void sshproxy_eventlog(LogPolicy *lp, const char *event) { SshProxy *sp = container_of(lp, SshProxy, logpolicy); - log_proxy_stderr(sp->plug, &sp->psb, event, strlen(event)); - log_proxy_stderr(sp->plug, &sp->psb, "\n", 1); + log_proxy_stderr(sp->plug, &sp->sock, &sp->psb, event, strlen(event)); + log_proxy_stderr(sp->plug, &sp->sock, &sp->psb, "\n", 1); } static int sshproxy_askappend(LogPolicy *lp, Filename *filename, @@ -248,7 +243,8 @@ static void sshproxy_notify_session_started(Seat *seat) interactor_return_seat(sp->clientitr); sp->conn_established = true; - plug_log(sp->plug, PLUGLOG_CONNECT_SUCCESS, sp->addr, sp->port, NULL, 0); + plug_log(sp->plug, &sp->sock, PLUGLOG_CONNECT_SUCCESS, sp->addr, sp->port, + NULL, 0); } static size_t sshproxy_output(Seat *seat, SeatOutputType type, @@ -261,7 +257,7 @@ static size_t sshproxy_output(Seat *seat, SeatOutputType type, try_send_ssh_to_socket(sp); break; case SEAT_OUTPUT_STDERR: - log_proxy_stderr(sp->plug, &sp->psb, data, len); + log_proxy_stderr(sp->plug, &sp->sock, &sp->psb, data, len); break; } return bufchain_size(&sp->ssh_to_socket); @@ -322,8 +318,8 @@ static void sshproxy_send_close(SshProxy *sp) interactor_return_seat(sp->clientitr); if (!sp->conn_established) - plug_log(sp->plug, PLUGLOG_CONNECT_FAILED, sp->addr, sp->port, - sp->errmsg, 0); + plug_log(sp->plug, &sp->sock, PLUGLOG_CONNECT_FAILED, sp->addr, + sp->port, sp->errmsg, 0); if (sp->errmsg) plug_closing_error(sp->plug, sp->errmsg); @@ -405,6 +401,14 @@ static void sshproxy_connection_fatal(Seat *seat, const char *message) } } +static void sshproxy_nonfatal(Seat *seat, const char *message) +{ + SshProxy *sp = container_of(seat, SshProxy, seat); + if (sp->clientseat) + seat_nonfatal(sp->clientseat, "error in proxy SSH connection: %s", + message); +} + static SeatPromptResult sshproxy_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, char *keystr, SeatDialogText *text, HelpCtx helpctx, @@ -569,6 +573,7 @@ static const SeatVtable SshProxy_seat_vt = { .notify_remote_exit = nullseat_notify_remote_exit, .notify_remote_disconnect = sshproxy_notify_remote_disconnect, .connection_fatal = sshproxy_connection_fatal, + .nonfatal = sshproxy_nonfatal, .update_specials_menu = nullseat_update_specials_menu, .get_ttymode = nullseat_get_ttymode, .set_busy_status = nullseat_set_busy_status, diff --git a/proxy/telnet.c b/proxy/telnet.c index a2efb7b4..41cdf8d7 100644 --- a/proxy/telnet.c +++ b/proxy/telnet.c @@ -339,7 +339,8 @@ static void proxy_telnet_process_queue(ProxyNegotiator *pn) put_datapl(logmsg, PTRLEN_LITERAL("Sending Telnet proxy command: ")); put_c_string_literal(logmsg, ptrlen_from_asciz(censored_cmd)); - plug_log(pn->ps->plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg->s, 0); + plug_log(pn->ps->plug, &pn->ps->sock, PLUGLOG_PROXY_MSG, NULL, 0, + logmsg->s, 0); strbuf_free(logmsg); sfree(censored_cmd); } diff --git a/pscp.c b/pscp.c index 38a4c0c0..0f4b2d6c 100644 --- a/pscp.c +++ b/pscp.c @@ -71,6 +71,7 @@ static const SeatVtable pscp_seat_vt = { .notify_remote_exit = nullseat_notify_remote_exit, .notify_remote_disconnect = nullseat_notify_remote_disconnect, .connection_fatal = console_connection_fatal, + .nonfatal = console_nonfatal, .update_specials_menu = nullseat_update_specials_menu, .get_ttymode = nullseat_get_ttymode, .set_busy_status = nullseat_set_busy_status, @@ -390,7 +391,7 @@ static void do_cmd(char *host, char *user, char *cmd) /* Set username */ if (user != NULL && user[0] != '\0') { conf_set_str(conf, CONF_username, user); - } else if (conf_get_str(conf, CONF_username)[0] == '\0') { + } else if (conf_get_str_ambi(conf, CONF_username, NULL)[0] == '\0') { user = get_username(); if (!user) bump("Empty user name"); @@ -534,10 +535,20 @@ static void print_stats(const char *name, uint64_t size, uint64_t done, } /* - * Find a colon in str and return a pointer to the colon. - * This is used to separate hostname from filename. + * Find a colon in str and return a pointer to the colon. + * This is used to separate hostname from filename. + * + * Colons in bracketed IPv6 address literals are ignored, because + * they're logically part of the hostname. + * + * Like strchr in the C standard library, we accept a const char * as + * input, and produce a mutable char * as output. The intention is + * that you EITHER pass a mutable char * input and use the mutability + * of the output, OR pass a const char * as input and don't use the + * mutability, but don't use this to silently launder consts off + * things. */ -static char *colon(char *str) +static char *colon(const char *str) { /* We ignore a leading colon, since the hostname cannot be empty. We also ignore a colon as second character because @@ -547,7 +558,7 @@ static char *colon(char *str) return (NULL); str += host_strcspn(str, ":/\\"); if (*str == ':') - return (str); + return (char *)str; else return (NULL); } @@ -1965,16 +1976,16 @@ static void sink(const char *targ, const char *src) /* * We will copy local files to a remote server. */ -static void toremote(int argc, char *argv[]) +static void toremote(CmdlineArg **args, size_t nargs) { - char *src, *wtarg, *host, *user; - const char *targ; + char *wtarg, *host, *user; + const char *src, *targ; char *cmd; - int i, wc_type; + int wc_type; uploading = true; - wtarg = argv[argc - 1]; + wtarg = dupstr(cmdline_arg_to_str(args[nargs - 1])); /* Separate host from filename */ host = wtarg; @@ -2000,13 +2011,14 @@ static void toremote(int argc, char *argv[]) user = NULL; } - if (argc == 2) { - if (colon(argv[0]) != NULL) - bump("%s: Remote to remote not supported", argv[0]); + if (nargs == 2) { + const char *arg0 = cmdline_arg_to_str(args[0]); + if (colon(arg0) != NULL) + bump("%s: Remote to remote not supported", arg0); - wc_type = test_wildcard(argv[0], true); + wc_type = test_wildcard(arg0, true); if (wc_type == WCTYPE_NONEXISTENT) - bump("%s: No such file or directory\n", argv[0]); + bump("%s: No such file or directory\n", arg0); else if (wc_type == WCTYPE_WILDCARD) targetshouldbedirectory = true; } @@ -2022,8 +2034,8 @@ static void toremote(int argc, char *argv[]) if (scp_source_setup(targ, targetshouldbedirectory)) return; - for (i = 0; i < argc - 1; i++) { - src = argv[i]; + for (size_t i = 0; i < nargs - 1; i++) { + src = cmdline_arg_to_str(args[i]); if (colon(src) != NULL) { tell_user(stderr, "%s: Remote to remote not supported\n", src); errs++; @@ -2060,19 +2072,19 @@ static void toremote(int argc, char *argv[]) /* * We will copy files from a remote server to the local machine. */ -static void tolocal(int argc, char *argv[]) +static void tolocal(CmdlineArg **args, size_t nargs) { - char *wsrc, *host, *user; + char *wsrc_orig, *wsrc, *host, *user; const char *src, *targ; char *cmd; uploading = false; - if (argc != 2) + if (nargs != 2) bump("More than one remote source not supported"); - wsrc = argv[0]; - targ = argv[1]; + wsrc = wsrc_orig = dupstr(cmdline_arg_to_str(args[0])); + targ = cmdline_arg_to_str(args[1]); /* Separate host from filename */ host = wsrc; @@ -2110,20 +2122,20 @@ static void tolocal(int argc, char *argv[]) return; sink(targ, src); + sfree(wsrc_orig); } /* * We will issue a list command to get a remote directory. */ -static void get_dir_list(int argc, char *argv[]) +static void get_dir_list(CmdlineArg **args, size_t nargs) { - char *wsrc, *host, *user; + char *wsrc_orig, *wsrc, *host, *user; const char *src; - char *cmd, *p; const char *q; char c; - wsrc = argv[0]; + wsrc = wsrc_orig = dupstr(cmdline_arg_to_str(args[0])); /* Separate host from filename */ host = wsrc; @@ -2149,24 +2161,18 @@ static void get_dir_list(int argc, char *argv[]) user = NULL; } - cmd = snewn(4 * strlen(src) + 100, char); - strcpy(cmd, "ls -la '"); - p = cmd + strlen(cmd); + strbuf *cmd = strbuf_new(); + put_datalit(cmd, "ls -la '"); for (q = src; *q; q++) { - if (*q == '\'') { - *p++ = '\''; - *p++ = '\\'; - *p++ = '\''; - *p++ = '\''; - } else { - *p++ = *q; - } + if (*q == '\'') + put_datalit(cmd, "'\\''"); + else + put_byte(cmd, *q); } - *p++ = '\''; - *p = '\0'; + put_datalit(cmd, "'"); - do_cmd(host, user, cmd); - sfree(cmd); + do_cmd(host, user, cmd->s); + strbuf_free(cmd); if (using_sftp) { scp_sftp_listdir(src); @@ -2179,6 +2185,8 @@ static void get_dir_list(int argc, char *argv[]) put_byte(scc, c); stripctrl_free(scc); } + + sfree(wsrc_orig); } /* @@ -2245,7 +2253,7 @@ void cmdline_error(const char *p, ...) va_start(ap, p); vfprintf(stderr, p, ap); va_end(ap); - fprintf(stderr, "\n try typing just \"pscp\" for help\n"); + fprintf(stderr, "\n try typing \"pscp -h\" for help\n"); exit(1); } @@ -2261,9 +2269,8 @@ const unsigned cmdline_tooltype = TOOLTYPE_FILETRANSFER; * Main program. (Called `psftp_main' because it gets called from * *sftp.c; bit silly, I know, but it had to be called _something_.) */ -int psftp_main(int argc, char *argv[]) +int psftp_main(CmdlineArgList *arglist) { - int i; bool sanitise_stderr = true; sk_init(); @@ -2272,58 +2279,60 @@ int psftp_main(int argc, char *argv[]) conf = conf_new(); do_defaults(NULL, conf); - for (i = 1; i < argc; i++) { - int ret; - if (argv[i][0] != '-') + size_t arglistpos = 0; + while (arglist->args[arglistpos]) { + CmdlineArg *arg = arglist->args[arglistpos++]; + CmdlineArg *nextarg = arglist->args[arglistpos]; + const char *argstr = cmdline_arg_to_str(arg); + if (argstr[0] != '-') { + arglistpos--; /* logically push that argument back on the list */ break; - ret = cmdline_process_param(argv[i], i+1args + arglistpos; + size_t nscpargs = 0; + while (scpargs[nscpargs]) + nscpargs++; + if (list) { - if (argc != 1) - usage(); - get_dir_list(argc, argv); + if (nscpargs != 1) + cmdline_error("expected a single argument with -ls"); + get_dir_list(scpargs, nscpargs); } else { - - if (argc < 2) - usage(); - if (argc > 2) + if (nscpargs < 2) + cmdline_error("expected at least two arguments"); + if (nscpargs > 2) targetshouldbedirectory = true; - if (colon(argv[argc - 1]) != NULL) - toremote(argc, argv); + if (colon(cmdline_arg_to_str(scpargs[nscpargs - 1])) != NULL) + toremote(scpargs, nscpargs); else - tolocal(argc, argv); + tolocal(scpargs, nscpargs); } if (backend && backend_connected(backend)) { @@ -2362,6 +2375,7 @@ int psftp_main(int argc, char *argv[]) random_save_seed(); cmdline_cleanup(); + cmdline_arg_list_free(arglist); if (backend) { backend_free(backend); backend = NULL; diff --git a/psftp.c b/psftp.c index 1e20c2cd..b3d526c8 100644 --- a/psftp.c +++ b/psftp.c @@ -52,6 +52,7 @@ static const SeatVtable psftp_seat_vt = { .notify_remote_exit = nullseat_notify_remote_exit, .notify_remote_disconnect = nullseat_notify_remote_disconnect, .connection_fatal = console_connection_fatal, + .nonfatal = console_nonfatal, .update_specials_menu = nullseat_update_specials_menu, .get_ttymode = nullseat_get_ttymode, .set_busy_status = nullseat_set_busy_status, @@ -2392,7 +2393,7 @@ static void do_sftp_cleanup(void) } } -int do_sftp(int mode, int modeflags, char *batchfile) +int do_sftp(int mode, int modeflags, const char *batchfile) { FILE *fp; int ret; @@ -2791,15 +2792,15 @@ const unsigned cmdline_tooltype = TOOLTYPE_FILETRANSFER; /* * Main program. Parse arguments etc. */ -int psftp_main(int argc, char *argv[]) +int psftp_main(CmdlineArgList *arglist) { - int i, toret; + int toret; int portnumber = 0; char *userhost, *user; int mode = 0; int modeflags = 0; bool sanitise_stderr = true; - char *batchfile = NULL; + const char *batchfile = NULL; sk_init(); @@ -2809,57 +2810,58 @@ int psftp_main(int argc, char *argv[]) conf = conf_new(); do_defaults(NULL, conf); - for (i = 1; i < argc; i++) { - int retd; - if (argv[i][0] != '-') { + size_t arglistpos = 0; + while (arglist->args[arglistpos]) { + CmdlineArg *arg = arglist->args[arglistpos++]; + CmdlineArg *nextarg = arglist->args[arglistpos]; + const char *argstr = cmdline_arg_to_str(arg); + + if (argstr[0] != '-') { if (userhost) - usage(); + cmdline_error("unexpected extra argument \"%s\"", argstr); else - userhost = dupstr(argv[i]); + userhost = dupstr(argstr); continue; } - retd = cmdline_process_param( - argv[i], i+1 < argc ? argv[i+1] : NULL, 1, conf); + int retd = cmdline_process_param(arg, nextarg, 1, conf); if (retd == -2) { - cmdline_error("option \"%s\" requires an argument", argv[i]); + cmdline_error("option \"%s\" requires an argument", argstr); } else if (retd == 2) { - i++; /* skip next argument */ + arglistpos++; /* skip next argument */ } else if (retd == 1) { /* We have our own verbosity in addition to `flags'. */ if (cmdline_verbose()) verbose = true; - } else if (strcmp(argv[i], "-h") == 0 || - strcmp(argv[i], "-?") == 0 || - strcmp(argv[i], "--help") == 0) { + } else if (strcmp(argstr, "-h") == 0 || + strcmp(argstr, "-?") == 0 || + strcmp(argstr, "--help") == 0) { usage(); - } else if (strcmp(argv[i], "-pgpfp") == 0) { + cleanup_exit(0); + } else if (strcmp(argstr, "-pgpfp") == 0) { pgp_fingerprints(); - return 1; - } else if (strcmp(argv[i], "-V") == 0 || - strcmp(argv[i], "--version") == 0) { + return 0; + } else if (strcmp(argstr, "-V") == 0 || + strcmp(argstr, "--version") == 0) { version(); - } else if (strcmp(argv[i], "-batch") == 0) { - console_batch_mode = true; - } else if (strcmp(argv[i], "-b") == 0 && i + 1 < argc) { + } else if (strcmp(argstr, "-b") == 0 && nextarg) { mode = 1; - batchfile = argv[++i]; - } else if (strcmp(argv[i], "-bc") == 0) { + batchfile = cmdline_arg_to_str(nextarg); + arglistpos++; + } else if (strcmp(argstr, "-bc") == 0) { modeflags = modeflags | 1; - } else if (strcmp(argv[i], "-be") == 0) { + } else if (strcmp(argstr, "-be") == 0) { modeflags = modeflags | 2; - } else if (strcmp(argv[i], "-sanitise-stderr") == 0) { + } else if (strcmp(argstr, "-sanitise-stderr") == 0) { sanitise_stderr = true; - } else if (strcmp(argv[i], "-no-sanitise-stderr") == 0) { + } else if (strcmp(argstr, "-no-sanitise-stderr") == 0) { sanitise_stderr = false; - } else if (strcmp(argv[i], "--") == 0) { - i++; + } else if (strcmp(argstr, "--") == 0) { + arglistpos++; break; } else { - cmdline_error("unknown option \"%s\"", argv[i]); + cmdline_error("unknown option \"%s\"", argstr); } } - argc -= i; - argv += i; backend = NULL; stdio_sink_init(&stderr_ss, stderr); @@ -2897,6 +2899,8 @@ int psftp_main(int argc, char *argv[]) " to connect\n"); } + cmdline_arg_list_free(arglist); + toret = do_sftp(mode, modeflags, batchfile); if (backend && backend_connected(backend)) { diff --git a/psftp.h b/psftp.h index 79327e7a..a253f508 100644 --- a/psftp.h +++ b/psftp.h @@ -55,7 +55,7 @@ void platform_psftp_pre_conn_setup(LogPolicy *lp); * The main program in psftp.c. Called from main() in the platform- * specific code, after doing any platform-specific initialisation. */ -int psftp_main(int argc, char *argv[]); +int psftp_main(CmdlineArgList *arglist); /* * These functions are used by PSCP to transmit progress updates diff --git a/psocks.c b/psocks.c index eb7c8f01..a71a0c98 100644 --- a/psocks.c +++ b/psocks.c @@ -50,7 +50,7 @@ struct psocks_state { unsigned log_flags; RecordDestination rec_dest; char *rec_cmd; - strbuf *subcmd; + bool got_subcmd; ConnectionLayer cl; }; @@ -72,7 +72,7 @@ struct psocks_connection { static SshChannel *psocks_lportfwd_open( ConnectionLayer *cl, const char *hostname, int port, - const char *description, const SocketPeerInfo *pi, Channel *chan); + const char *description, const SocketEndpointInfo *pi, Channel *chan); static const ConnectionLayerVtable psocks_clvt = { .lportfwd_open = psocks_lportfwd_open, @@ -93,8 +93,9 @@ static const SshChannelVtable psocks_scvt = { /* all the rest are NULL */ }; -static void psocks_plug_log(Plug *p, PlugLogType type, SockAddr *addr, - int port, const char *error_msg, int error_code); +static void psocks_plug_log(Plug *p, Socket *s, PlugLogType type, + SockAddr *addr, int port, + const char *error_msg, int error_code); static void psocks_plug_closing(Plug *p, PlugCloseType, const char *error_msg); static void psocks_plug_receive(Plug *p, int urgent, const char *data, size_t len); @@ -154,7 +155,7 @@ static void psocks_connection_establish(void *vctx); static SshChannel *psocks_lportfwd_open( ConnectionLayer *cl, const char *hostname, int port, - const char *description, const SocketPeerInfo *pi, Channel *chan) + const char *description, const SocketEndpointInfo *pi, Channel *chan) { psocks_state *ps = container_of(cl, psocks_state, cl); psocks_connection *conn = snew(psocks_connection); @@ -320,8 +321,9 @@ static void psocks_sc_unthrottle(SshChannel *sc, size_t bufsize) sk_set_frozen(conn->socket, false); } -static void psocks_plug_log(Plug *plug, PlugLogType type, SockAddr *addr, - int port, const char *error_msg, int error_code) +static void psocks_plug_log(Plug *plug, Socket *s, PlugLogType type, + SockAddr *addr, int port, + const char *error_msg, int error_code) { psocks_connection *conn = container_of(plug, psocks_connection, plug); char addrbuf[256]; @@ -407,7 +409,6 @@ psocks_state *psocks_new(const PsocksPlatform *platform) ps->log_flags = LOG_CONNSTATUS; ps->rec_dest = REC_NONE; ps->platform = platform; - ps->subcmd = strbuf_new(); return ps; } @@ -415,19 +416,19 @@ psocks_state *psocks_new(const PsocksPlatform *platform) void psocks_free(psocks_state *ps) { portfwdmgr_free(ps->portfwdmgr); - strbuf_free(ps->subcmd); sfree(ps->rec_cmd); sfree(ps); } -void psocks_cmdline(psocks_state *ps, int argc, char **argv) +void psocks_cmdline(psocks_state *ps, CmdlineArgList *arglist) { bool doing_opts = true; - bool accumulating_exec_args = false; + size_t arglistpos = 0; size_t args_seen = 0; - while (--argc > 0) { - const char *p = *++argv; + while (arglist->args[arglistpos]) { + CmdlineArg *arg = arglist->args[arglistpos++]; + const char *p = cmdline_arg_to_str(arg); if (doing_opts && p[0] == '-' && p[1]) { if (!strcmp(p, "--")) { @@ -444,8 +445,9 @@ void psocks_cmdline(psocks_state *ps, int argc, char **argv) "platform\n"); exit(1); } - if (--argc > 0) { - ps->rec_cmd = dupstr(*++argv); + if (arglist->args[arglistpos] > 0) { + ps->rec_cmd = dupstr( + cmdline_arg_to_str(arglist->args[arglistpos++])); } else { fprintf(stderr, "psocks: expected an argument to '-p'\n"); exit(1); @@ -457,10 +459,15 @@ void psocks_cmdline(psocks_state *ps, int argc, char **argv) "supported on this platform\n"); exit(1); } - accumulating_exec_args = true; + if (!arglist->args[arglistpos]) { + fprintf(stderr, "psocks: --exec requires a command\n"); + exit(1); + } /* Now consume all further argv words for the * subcommand, even if they look like options */ - doing_opts = false; + ps->platform->found_subcommand(arglist->args[arglistpos]); + ps->got_subcmd = true; + break; } else if (!strcmp(p, "--help")) { printf("usage: psocks [ -d ] [ -f"); if (ps->platform->open_pipes) @@ -488,9 +495,7 @@ void psocks_cmdline(psocks_state *ps, int argc, char **argv) exit(1); } } else { - if (accumulating_exec_args) { - put_asciz(ps->subcmd, p); - } else switch (args_seen++) { + switch (args_seen++) { case 0: ps->listen_port = atoi(p); break; @@ -513,8 +518,8 @@ void psocks_start(psocks_state *ps) portfwdmgr_config(ps->portfwdmgr, conf); - if (ps->subcmd->len) - ps->platform->start_subcommand(ps->subcmd); + if (ps->got_subcmd) + ps->platform->start_subcommand(); conf_free(conf); } @@ -529,7 +534,7 @@ int check_stored_host_key(const char *hostname, int port, unreachable("host keys not handled in this tool"); } -void store_host_key(const char *hostname, int port, +void store_host_key(Seat *seat, const char *hostname, int port, const char *keytype, const char *key) { unreachable("host keys not handled in this tool"); diff --git a/psocks.h b/psocks.h index 8ba3b2f2..99ee646e 100644 --- a/psocks.h +++ b/psocks.h @@ -19,10 +19,11 @@ struct PsocksPlatform { PsocksDataSink *(*open_pipes)( const char *cmd, const char *const *direction_args, const char *index_arg, char **err); - void (*start_subcommand)(strbuf *args); + void (*found_subcommand)(CmdlineArg *arg); + void (*start_subcommand)(void); }; psocks_state *psocks_new(const PsocksPlatform *); void psocks_free(psocks_state *ps); -void psocks_cmdline(psocks_state *ps, int argc, char **argv); +void psocks_cmdline(psocks_state *ps, CmdlineArgList *arglist); void psocks_start(psocks_state *ps); diff --git a/putty.h b/putty.h index 2e73fb02..9aadf04b 100644 --- a/putty.h +++ b/putty.h @@ -286,64 +286,21 @@ struct unicode_data { #define LGTYP_PACKETS 3 /* logmode: SSH data packets */ #define LGTYP_SSHRAW 4 /* logmode: SSH raw data */ +/* Platform-generic function to set up a struct unicode_data. This is + * only likely to be useful to test programs; real clients will want + * to use the more flexible per-platform setup functions. */ +void init_ucs_generic(Conf *conf, struct unicode_data *ucsdata); + /* * Enumeration of 'special commands' that can be sent during a * session, separately from the byte stream of ordinary session data. */ typedef enum { - /* - * Commands that are generally useful in multiple backends. - */ - SS_BRK, /* serial-line break */ - SS_EOF, /* end-of-file on session input */ - SS_NOP, /* transmit data with no effect */ - SS_PING, /* try to keep the session alive (probably, but not - * necessarily, implemented as SS_NOP) */ - - /* - * Commands specific to Telnet. - */ - SS_AYT, /* Are You There */ - SS_SYNCH, /* Synch */ - SS_EC, /* Erase Character */ - SS_EL, /* Erase Line */ - SS_GA, /* Go Ahead */ - SS_ABORT, /* Abort Process */ - SS_AO, /* Abort Output */ - SS_IP, /* Interrupt Process */ - SS_SUSP, /* Suspend Process */ - SS_EOR, /* End Of Record */ - SS_EOL, /* Telnet end-of-line sequence (CRLF, as opposed to CR - * NUL that escapes a literal CR) */ - - /* - * Commands specific to SSH. - */ - SS_REKEY, /* trigger an immediate repeat key exchange */ - SS_XCERT, /* cross-certify another host key ('arg' indicates which) */ - - /* - * Send a POSIX-style signal. (Useful in SSH and also pterm.) - * - * We use the master list in ssh/signal-list.h to define these enum - * values, which will come out looking like names of the form - * SS_SIGABRT, SS_SIGINT etc. - */ - #define SIGNAL_MAIN(name, text) SS_SIG ## name, - #define SIGNAL_SUB(name) SS_SIG ## name, - #include "ssh/signal-list.h" - #undef SIGNAL_MAIN - #undef SIGNAL_SUB - - /* - * These aren't really special commands, but they appear in the - * enumeration because the list returned from - * backend_get_specials() will use them to specify the structure - * of the GUI specials menu. - */ - SS_SEP, /* Separator */ - SS_SUBMENU, /* Start a new submenu with specified name */ - SS_EXITMENU, /* Exit current submenu, or end of entire specials list */ + /* The list of enum constants is defined in a separate header so + * they can be reused in other contexts */ + #define SPECIAL(x) SS_ ## x, + #include "specials.h" + #undef SPECIAL } SessionSpecialCode; /* @@ -530,6 +487,13 @@ enum { RESIZE_TERM, RESIZE_DISABLED, RESIZE_FONT, RESIZE_EITHER }; +enum { + /* Mouse-button assignments */ + MOUSE_COMPROMISE, /* xterm-ish but with paste on RB in case no MB exists */ + MOUSE_XTERM, /* xterm-style: MB pastes, RB extends selection */ + MOUSE_WINDOWS /* Windows-style: RB brings up menu. MB still extends. */ +}; + enum { /* Function key types (CONF_funky_type) */ FUNKY_TILDE, @@ -551,6 +515,16 @@ enum { FQ_DEFAULT, FQ_ANTIALIASED, FQ_NONANTIALIASED, FQ_CLEARTYPE }; +enum { + CURSOR_BLOCK, CURSOR_UNDERLINE, CURSOR_VERTICAL_LINE +}; + +enum { + /* these are really bit flags */ + BOLD_STYLE_FONT = 1, + BOLD_STYLE_COLOUR = 2, +}; + enum { SER_PAR_NONE, SER_PAR_ODD, SER_PAR_EVEN, SER_PAR_MARK, SER_PAR_SPACE }; @@ -996,6 +970,17 @@ struct prompts_t { * seat_get_userpass_input(); initially NULL */ SeatPromptResult spr; /* some implementations need to cache one of these */ + /* + * Set this flag to indicate that the caller has encoded the + * prompts in UTF-8, and expects the responses to be UTF-8 too. + * + * Ideally this flag would be unnecessary because it would always + * be true, but for legacy reasons, we have to switch over a bit + * at a time from the old behaviour, and may never manage to get + * rid of it completely. + */ + bool utf8; + /* * Callback you can fill in to be notified when all the prompts' * responses are available. After you receive this notification, a @@ -1215,9 +1200,15 @@ struct SeatVtable { void (*notify_remote_disconnect)(Seat *seat); /* - * Notify the seat that the connection has suffered a fatal error. + * Notify the seat that the connection has suffered an error, + * either fatal to the whole connection or not. + * + * The latter kind of error is expected to be things along the + * lines of 'I/O error storing the new host key', which has + * traditionally been presented via a dialog box or similar. */ void (*connection_fatal)(Seat *seat, const char *message); + void (*nonfatal)(Seat *seat, const char *message); /* * Notify the seat that the list of special commands available @@ -1481,10 +1472,11 @@ static inline bool seat_interactive(Seat *seat) static inline bool seat_get_cursor_position(Seat *seat, int *x, int *y) { return seat->vt->get_cursor_position(seat, x, y); } -/* Unlike the seat's actual method, the public entry point - * seat_connection_fatal is a wrapper function with a printf-like API, - * defined in utils. */ +/* Unlike the seat's actual method, the public entry points + * seat_connection_fatal and seat_nonfatal are wrapper functions with + * a printf-like API, defined in utils. */ void seat_connection_fatal(Seat *seat, const char *fmt, ...) PRINTF_LIKE(2, 3); +void seat_nonfatal(Seat *seat, const char *fmt, ...) PRINTF_LIKE(2, 3); /* Handy aliases for seat_output which set is_stderr to a fixed value. */ static inline size_t seat_stdout(Seat *seat, const void *data, size_t len) @@ -1529,6 +1521,7 @@ void nullseat_notify_session_started(Seat *seat); void nullseat_notify_remote_exit(Seat *seat); void nullseat_notify_remote_disconnect(Seat *seat); void nullseat_connection_fatal(Seat *seat, const char *message); +void nullseat_nonfatal(Seat *seat, const char *message); void nullseat_update_specials_menu(Seat *seat); char *nullseat_get_ttymode(Seat *seat, const char *mode); void nullseat_set_busy_status(Seat *seat, BusyStatus status); @@ -1568,6 +1561,7 @@ bool nullseat_get_cursor_position(Seat *seat, int *x, int *y); */ void console_connection_fatal(Seat *seat, const char *message); +void console_nonfatal(Seat *seat, const char *message); SeatPromptResult console_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, char *keystr, SeatDialogText *text, HelpCtx helpctx, @@ -1793,275 +1787,111 @@ NORETURN void cleanup_exit(int); * Exports from conf.c, and a big enum (via parametric macro) of * configuration option keys. */ -#define CONFIG_OPTIONS(X) \ - /* X(value-type, subkey-type, keyword) */ \ - X(STR, NONE, host) \ - X(INT, NONE, port) \ - X(INT, NONE, protocol) /* PROT_SSH, PROT_TELNET etc */ \ - X(INT, NONE, addressfamily) /* ADDRTYPE_IPV[46] or ADDRTYPE_UNSPEC */ \ - X(INT, NONE, close_on_exit) /* FORCE_ON, FORCE_OFF, AUTO */ \ - X(BOOL, NONE, warn_on_close) \ - X(INT, NONE, ping_interval) /* in seconds */ \ - X(BOOL, NONE, tcp_nodelay) \ - X(BOOL, NONE, tcp_keepalives) \ - X(STR, NONE, loghost) /* logical host being contacted, for host key check */ \ - /* Proxy options */ \ - X(STR, NONE, proxy_exclude_list) \ - X(INT, NONE, proxy_dns) /* FORCE_ON, FORCE_OFF, AUTO */ \ - X(BOOL, NONE, even_proxy_localhost) \ - X(INT, NONE, proxy_type) /* PROXY_NONE, PROXY_SOCKS4, ... */ \ - X(STR, NONE, proxy_host) \ - X(INT, NONE, proxy_port) \ - X(STR, NONE, proxy_username) \ - X(STR, NONE, proxy_password) \ - X(STR, NONE, proxy_telnet_command) \ - X(INT, NONE, proxy_log_to_term) /* FORCE_ON, FORCE_OFF, AUTO */ \ - /* SSH options */ \ - X(STR, NONE, remote_cmd) \ - X(STR, NONE, remote_cmd2) /* fallback if remote_cmd fails; never loaded or saved */ \ - X(BOOL, NONE, nopty) \ - X(BOOL, NONE, compression) \ - X(INT, INT, ssh_kexlist) \ - X(INT, INT, ssh_hklist) \ - X(BOOL, NONE, ssh_prefer_known_hostkeys) \ - X(INT, NONE, ssh_rekey_time) /* in minutes */ \ - X(STR, NONE, ssh_rekey_data) /* string encoding e.g. "100K", "2M", "1G" */ \ - X(BOOL, NONE, tryagent) \ - X(BOOL, NONE, agentfwd) \ - X(BOOL, NONE, change_username) /* allow username switching in SSH-2 */ \ - X(INT, INT, ssh_cipherlist) \ - X(FILENAME, NONE, keyfile) \ - X(FILENAME, NONE, detached_cert) \ - X(STR, NONE, auth_plugin) \ - /* \ - * Which SSH protocol to use. \ - * For historical reasons, the current legal values for CONF_sshprot \ - * are: \ - * 0 = SSH-1 only \ - * 3 = SSH-2 only \ - * We used to also support \ - * 1 = SSH-1 with fallback to SSH-2 \ - * 2 = SSH-2 with fallback to SSH-1 \ - * and we continue to use 0/3 in storage formats rather than the more \ - * obvious 1/2 to avoid surprises if someone saves a session and later \ - * downgrades PuTTY. So it's easier to use these numbers internally too. \ - */ \ - X(INT, NONE, sshprot) \ - X(BOOL, NONE, ssh2_des_cbc) /* "des-cbc" unrecommended SSH-2 cipher */ \ - X(BOOL, NONE, ssh_no_userauth) /* bypass "ssh-userauth" (SSH-2 only) */ \ - X(BOOL, NONE, ssh_no_trivial_userauth) /* disable trivial types of auth */ \ - X(BOOL, NONE, ssh_show_banner) /* show USERAUTH_BANNERs (SSH-2 only) */ \ - X(BOOL, NONE, try_tis_auth) \ - X(BOOL, NONE, try_ki_auth) \ - X(BOOL, NONE, try_gssapi_auth) /* attempt gssapi auth via ssh userauth */ \ - X(BOOL, NONE, try_gssapi_kex) /* attempt gssapi auth via ssh kex */ \ - X(BOOL, NONE, gssapifwd) /* forward tgt via gss */ \ - X(INT, NONE, gssapirekey) /* KEXGSS refresh interval (mins) */ \ - X(INT, INT, ssh_gsslist) /* preference order for local GSS libs */ \ - X(FILENAME, NONE, ssh_gss_custom) \ - X(BOOL, NONE, ssh_subsys) /* run a subsystem rather than a command */ \ - X(BOOL, NONE, ssh_subsys2) /* fallback to go with remote_cmd_ptr2 */ \ - X(BOOL, NONE, ssh_no_shell) /* avoid running a shell */ \ - X(STR, NONE, ssh_nc_host) /* host to connect to in `nc' mode */ \ - X(INT, NONE, ssh_nc_port) /* port to connect to in `nc' mode */ \ - /* Telnet options */ \ - X(STR, NONE, termtype) \ - X(STR, NONE, termspeed) \ - X(STR, STR, ttymodes) /* values are "Vvalue" or "A" */ \ - X(STR, STR, environmt) \ - X(STR, NONE, username) \ - X(BOOL, NONE, username_from_env) \ - X(STR, NONE, localusername) \ - X(BOOL, NONE, rfc_environ) \ - X(BOOL, NONE, passive_telnet) \ - /* Serial port options */ \ - X(STR, NONE, serline) \ - X(INT, NONE, serspeed) \ - X(INT, NONE, serdatabits) \ - X(INT, NONE, serstopbits) \ - X(INT, NONE, serparity) /* SER_PAR_NONE, SER_PAR_ODD, ... */ \ - X(INT, NONE, serflow) /* SER_FLOW_NONE, SER_FLOW_XONXOFF, ... */ \ - /* Supdup options */ \ - X(STR, NONE, supdup_location) \ - X(INT, NONE, supdup_ascii_set) \ - X(BOOL, NONE, supdup_more) \ - X(BOOL, NONE, supdup_scroll) \ - /* Keyboard options */ \ - X(BOOL, NONE, bksp_is_delete) \ - X(BOOL, NONE, rxvt_homeend) \ - X(INT, NONE, funky_type) /* FUNKY_XTERM, FUNKY_LINUX, ... */ \ - X(INT, NONE, sharrow_type) /* SHARROW_APPLICATION, SHARROW_BITMAP, ... */ \ - X(BOOL, NONE, no_applic_c) /* totally disable app cursor keys */ \ - X(BOOL, NONE, no_applic_k) /* totally disable app keypad */ \ - X(BOOL, NONE, no_mouse_rep) /* totally disable mouse reporting */ \ - X(BOOL, NONE, no_remote_resize) /* disable remote resizing */ \ - X(BOOL, NONE, no_alt_screen) /* disable alternate screen */ \ - X(BOOL, NONE, no_remote_wintitle) /* disable remote retitling */ \ - X(BOOL, NONE, no_remote_clearscroll) /* disable ESC[3J */ \ - X(BOOL, NONE, no_dbackspace) /* disable destructive backspace */ \ - X(BOOL, NONE, no_remote_charset) /* disable remote charset config */ \ - X(INT, NONE, remote_qtitle_action) /* remote win title query action - * (TITLE_NONE, TITLE_EMPTY, ...) */ \ - X(BOOL, NONE, app_cursor) \ - X(BOOL, NONE, app_keypad) \ - X(BOOL, NONE, nethack_keypad) \ - X(BOOL, NONE, telnet_keyboard) \ - X(BOOL, NONE, telnet_newline) \ - X(BOOL, NONE, alt_f4) /* is it special? */ \ - X(BOOL, NONE, alt_space) /* is it special? */ \ - X(BOOL, NONE, alt_only) /* is it special? */ \ - X(INT, NONE, localecho) /* FORCE_ON, FORCE_OFF, AUTO */ \ - X(INT, NONE, localedit) /* FORCE_ON, FORCE_OFF, AUTO */ \ - X(BOOL, NONE, alwaysontop) \ - X(BOOL, NONE, fullscreenonaltenter) \ - X(BOOL, NONE, scroll_on_key) \ - X(BOOL, NONE, scroll_on_disp) \ - X(BOOL, NONE, erase_to_scrollback) \ - X(BOOL, NONE, compose_key) \ - X(BOOL, NONE, ctrlaltkeys) \ - X(BOOL, NONE, osx_option_meta) \ - X(BOOL, NONE, osx_command_meta) \ - X(STR, NONE, wintitle) /* initial window title */ \ - /* Terminal options */ \ - X(INT, NONE, savelines) \ - X(BOOL, NONE, dec_om) \ - X(BOOL, NONE, wrap_mode) \ - X(BOOL, NONE, lfhascr) \ - X(INT, NONE, cursor_type) /* 0=block 1=underline 2=vertical */ \ - X(BOOL, NONE, blink_cur) \ - X(INT, NONE, beep) /* BELL_DISABLED, BELL_DEFAULT, ... */ \ - X(INT, NONE, beep_ind) /* B_IND_DISABLED, B_IND_FLASH, ... */ \ - X(BOOL, NONE, bellovl) /* bell overload protection active? */ \ - X(INT, NONE, bellovl_n) /* number of bells to cause overload */ \ - X(INT, NONE, bellovl_t) /* time interval for overload (seconds) */ \ - X(INT, NONE, bellovl_s) /* period of silence to re-enable bell (s) */ \ - X(FILENAME, NONE, bell_wavefile) \ - X(BOOL, NONE, scrollbar) \ - X(BOOL, NONE, scrollbar_in_fullscreen) \ - X(INT, NONE, resize_action) /* RESIZE_TERM, RESIZE_DISABLED, ... */ \ - X(BOOL, NONE, bce) \ - X(BOOL, NONE, blinktext) \ - X(BOOL, NONE, win_name_always) \ - X(INT, NONE, width) \ - X(INT, NONE, height) \ - X(FONT, NONE, font) \ - X(INT, NONE, font_quality) /* FQ_DEFAULT, FQ_ANTIALIASED, ... */ \ - X(FILENAME, NONE, logfilename) \ - X(INT, NONE, logtype) /* LGTYP_NONE, LGTYPE_ASCII, ... */ \ - X(INT, NONE, logxfovr) /* LGXF_OVR, LGXF_APN, LGXF_ASK */ \ - X(BOOL, NONE, logflush) \ - X(BOOL, NONE, logheader) \ - X(BOOL, NONE, logomitpass) \ - X(BOOL, NONE, logomitdata) \ - X(BOOL, NONE, hide_mouseptr) \ - X(BOOL, NONE, sunken_edge) \ - X(INT, NONE, window_border) /* in pixels */ \ - X(STR, NONE, answerback) \ - X(STR, NONE, printer) \ - X(BOOL, NONE, no_arabicshaping) \ - X(BOOL, NONE, no_bidi) \ - /* Colour options */ \ - X(BOOL, NONE, ansi_colour) \ - X(BOOL, NONE, xterm_256_colour) \ - X(BOOL, NONE, true_colour) \ - X(BOOL, NONE, system_colour) \ - X(BOOL, NONE, try_palette) \ - X(INT, NONE, bold_style) /* 1=font 2=colour (3=both) */ \ - X(INT, INT, colours) /* indexed by the CONF_COLOUR_* enum encoding */ \ - /* Selection options */ \ - X(INT, NONE, mouse_is_xterm) /* 0=compromise 1=xterm 2=Windows */ \ - X(BOOL, NONE, rect_select) \ - X(BOOL, NONE, paste_controls) \ - X(BOOL, NONE, rawcnp) \ - X(BOOL, NONE, utf8linedraw) \ - X(BOOL, NONE, rtf_paste) \ - X(BOOL, NONE, mouse_override) \ - X(INT, INT, wordness) \ - X(BOOL, NONE, mouseautocopy) \ - X(INT, NONE, mousepaste) /* CLIPUI_IMPLICIT, CLIPUI_EXPLICIT, ... */ \ - X(INT, NONE, ctrlshiftins) /* CLIPUI_IMPLICIT, CLIPUI_EXPLICIT, ... */ \ - X(INT, NONE, ctrlshiftcv) /* CLIPUI_IMPLICIT, CLIPUI_EXPLICIT, ... */ \ - X(STR, NONE, mousepaste_custom) \ - X(STR, NONE, ctrlshiftins_custom) \ - X(STR, NONE, ctrlshiftcv_custom) \ - /* translations */ \ - X(INT, NONE, vtmode) /* VT_XWINDOWS, VT_OEMANSI, ... */ \ - X(STR, NONE, line_codepage) \ - X(BOOL, NONE, cjk_ambig_wide) \ - X(BOOL, NONE, utf8_override) \ - X(BOOL, NONE, xlat_capslockcyr) \ - /* X11 forwarding */ \ - X(BOOL, NONE, x11_forward) \ - X(STR, NONE, x11_display) \ - X(INT, NONE, x11_auth) /* X11_NO_AUTH, X11_MIT, X11_XDM */ \ - X(FILENAME, NONE, xauthfile) \ - /* port forwarding */ \ - X(BOOL, NONE, lport_acceptall) /* accept conns from hosts other than localhost */ \ - X(BOOL, NONE, rport_acceptall) /* same for remote forwarded ports (SSH-2 only) */ \ - /* \ - * Subkeys for 'portfwd' can have the following forms: \ - * \ - * [LR]localport \ - * [LR]localaddr:localport \ - * \ - * Dynamic forwardings are indicated by an 'L' key, and the \ - * special value "D". For all other forwardings, the value \ - * should be of the form 'host:port'. \ - */ \ - X(STR, STR, portfwd) \ - /* SSH bug compatibility modes. All FORCE_ON/FORCE_OFF/AUTO */ \ - X(INT, NONE, sshbug_ignore1) \ - X(INT, NONE, sshbug_plainpw1) \ - X(INT, NONE, sshbug_rsa1) \ - X(INT, NONE, sshbug_hmac2) \ - X(INT, NONE, sshbug_derivekey2) \ - X(INT, NONE, sshbug_rsapad2) \ - X(INT, NONE, sshbug_pksessid2) \ - X(INT, NONE, sshbug_rekey2) \ - X(INT, NONE, sshbug_maxpkt2) \ - X(INT, NONE, sshbug_ignore2) \ - X(INT, NONE, sshbug_oldgex2) \ - X(INT, NONE, sshbug_winadj) \ - X(INT, NONE, sshbug_chanreq) \ - X(INT, NONE, sshbug_dropstart) \ - X(INT, NONE, sshbug_filter_kexinit) \ - X(INT, NONE, sshbug_rsa_sha2_cert_userauth) \ - /* \ - * ssh_simple means that we promise never to open any channel \ - * other than the main one, which means it can safely use a very \ - * large window in SSH-2. \ - */ \ - X(BOOL, NONE, ssh_simple) \ - X(BOOL, NONE, ssh_connection_sharing) \ - X(BOOL, NONE, ssh_connection_sharing_upstream) \ - X(BOOL, NONE, ssh_connection_sharing_downstream) \ + +/* The master list of option keywords lives in conf.h */ +enum config_primary_key { + #define CONF_OPTION(keyword, ...) CONF_ ## keyword, + #include "conf.h" + #undef CONF_OPTION + + N_CONFIG_OPTIONS +}; + +/* Types that appear in Conf keys and values. */ +enum { /* - * ssh_manual_hostkeys is conceptually a set rather than a - * dictionary: the string subkeys are the important thing, and the - * actual values to which those subkeys map are all "". - */ \ - X(STR, STR, ssh_manual_hostkeys) \ - /* Options for pterm. Should split out into platform-dependent part. */ \ - X(BOOL, NONE, stamp_utmp) \ - X(BOOL, NONE, login_shell) \ - X(BOOL, NONE, scrollbar_on_left) \ - X(BOOL, NONE, shadowbold) \ - X(FONT, NONE, boldfont) \ - X(FONT, NONE, widefont) \ - X(FONT, NONE, wideboldfont) \ - X(INT, NONE, shadowboldoffset) /* in pixels */ \ - X(BOOL, NONE, crhaslf) \ - X(STR, NONE, winclass) \ - /* end of list */ + * CONF_TYPE_NONE is included in this enum because sometimes you + * need a placeholder for 'no type found'. (In Rust you'd leave it + * out, and use Option for those situations.) + * + * In particular, it's used as the subkey type for options that + * don't have subkeys. + */ + CONF_TYPE_NONE, + + /* Booleans, accessed via conf_get_bool and conf_set_bool */ + CONF_TYPE_BOOL, + + /* Integers, accessed via conf_get_int and conf_set_int */ + CONF_TYPE_INT, + + /* + * NUL-terminated char strings, accessed via conf_get_str and + * conf_set_str. + * + * Where character encoding is relevant, these are generally + * expected to be in the host system's default character encoding. + * + * (Character encoding might not be relevant at all: for example, + * if the string is going to be used as a shell command on Unix, + * then the exec system call will want a char string anyway.) + */ + CONF_TYPE_STR, + + /* NUL-terminated char strings encoded in UTF-8, accessed via + * conf_get_utf8 and conf_set_utf8. */ + CONF_TYPE_UTF8, -/* Now define the actual enum of option keywords using that macro. */ -#define CONF_ENUM_DEF(valtype, keytype, keyword) CONF_ ## keyword, -enum config_primary_key { CONFIG_OPTIONS(CONF_ENUM_DEF) N_CONFIG_OPTIONS }; -#undef CONF_ENUM_DEF + /* + * A type that can be _either_ a char string in system encoding + * (aka CONF_TYPE_STR), _or_ a char string in UTF-8 (aka + * CONF_TYPE_UTF8). You can set it to be one or the other via + * conf_set_str or conf_set_utf8. To read it, you must use + * conf_get_str_ambi(), which returns a char string and a boolean + * telling you whether it's UTF-8. + * + * These can't be used as _keys_ in Conf, only as values. (If you + * used them as keys, you'd have to answer the difficult question + * of whether a UTF-8 and a non-UTF-8 string should be considered + * equal.) + */ + CONF_TYPE_STR_AMBI, + + /* PuTTY's OS-specific 'Filename' data type, accessed via + * conf_get_filename and conf_set_filename */ + CONF_TYPE_FILENAME, + + /* PuTTY's GUI-specific 'FontSpec' data type, accessed via + * conf_get_fontspec and conf_set_fontspec */ + CONF_TYPE_FONT, +}; + +struct ConfKeyInfo { + int subkey_type; + int value_type; + + union { + bool bval; + int ival; + const char *sval; + } default_value; + + bool save_custom : 1; + bool load_custom : 1; + bool not_saved : 1; + + const char *save_keyword; + const ConfSaveEnumType *storage_enum; +}; +struct ConfSaveEnumType { + const ConfSaveEnumValue *values; + size_t nvalues; +}; +struct ConfSaveEnumValue { + int confval, storageval; + bool obsolete; +}; + +extern const ConfKeyInfo conf_key_info[]; +bool conf_enum_map_to_storage(const ConfSaveEnumType *etype, + int confval, int *storageval_out); +bool conf_enum_map_from_storage(const ConfSaveEnumType *etype, + int storageval, int *confval_out); /* Functions handling configuration structures. */ Conf *conf_new(void); /* create an empty configuration */ void conf_free(Conf *conf); +void conf_clear(Conf *conf); /* likely only useful for test programs */ Conf *conf_copy(Conf *oldconf); void conf_copy_into(Conf *dest, Conf *src); /* Mandatory accessor functions: enforce by assertion that keys exist. */ @@ -2069,6 +1899,9 @@ bool conf_get_bool(Conf *conf, int key); int conf_get_int(Conf *conf, int key); int conf_get_int_int(Conf *conf, int key, int subkey); char *conf_get_str(Conf *conf, int key); /* result still owned by conf */ +char *conf_get_utf8(Conf *conf, int key); /* result still owned by conf */ +char *conf_get_str_ambi( /* result still owned by conf; 'utf8' may be NULL */ + Conf *conf, int key, bool *utf8); char *conf_get_str_str(Conf *conf, int key, const char *subkey); Filename *conf_get_filename(Conf *conf, int key); FontSpec *conf_get_fontspec(Conf *conf, int key); /* still owned by conf */ @@ -2086,6 +1919,9 @@ void conf_set_bool(Conf *conf, int key, bool value); void conf_set_int(Conf *conf, int key, int value); void conf_set_int_int(Conf *conf, int key, int subkey, int value); void conf_set_str(Conf *conf, int key, const char *value); +void conf_set_utf8(Conf *conf, int key, const char *value); +bool conf_try_set_str(Conf *conf, int key, const char *value); +bool conf_try_set_utf8(Conf *conf, int key, const char *value); void conf_set_str_str(Conf *conf, int key, const char *subkey, const char *val); void conf_del_str_str(Conf *conf, int key, const char *subkey); @@ -2099,7 +1935,15 @@ bool conf_deserialise(Conf *conf, BinarySource *src);/*returns true on success*/ * Functions to copy, free, serialise and deserialise FontSpecs. * Provided per-platform, to go with the platform's idea of a * FontSpec's contents. - */ + * + * The full fontspec_new is declared in the platform header, because + * each platform may need it to have a different prototype, due to + * constructing fonts in different ways. But fontspec_new_default() + * will at least produce _some_ kind of a FontSpec, for use in + * situations where one needs to exist (e.g. to put in a Conf) and be + * freeable but won't actually be used for anything important. + */ +FontSpec *fontspec_new_default(void); FontSpec *fontspec_copy(const FontSpec *f); void fontspec_free(FontSpec *f); void fontspec_serialise(BinarySink *bs, FontSpec *f); @@ -2202,7 +2046,7 @@ void term_lost_clipboard_ownership(Terminal *, int clipboard); void term_update(Terminal *); void term_invalidate(Terminal *); void term_blink(Terminal *, bool set_cursor); -void term_do_paste(Terminal *, const wchar_t *, int); +void term_do_paste(Terminal *, const wchar_t *, size_t); void term_nopaste(Terminal *); void term_copyall(Terminal *, const int *, int); void term_pre_reconfig(Terminal *, Conf *); @@ -2385,28 +2229,7 @@ void ldisc_configure(Ldisc *, Conf *); void ldisc_free(Ldisc *); void ldisc_send(Ldisc *, const void *buf, int len, bool interactive); void ldisc_echoedit_update(Ldisc *); -typedef struct LdiscInputToken { - /* - * Structure that encodes any single item of data that Ldisc can - * buffer: either a single character of raw data, or a session - * special. - */ - bool is_special; - union { - struct { - /* if is_special == false */ - char chr; - }; - struct { - /* if is_special == true */ - SessionSpecialCode code; - int arg; - }; - }; -} LdiscInputToken; -bool ldisc_has_input_buffered(Ldisc *); -LdiscInputToken ldisc_get_input_token(Ldisc *); /* asserts there is input */ -void ldisc_enable_prompt_callback(Ldisc *, prompts_t *); +void ldisc_provide_userpass_le(Ldisc *, TermLineEditor *); void ldisc_check_sendok(Ldisc *); /* @@ -2473,15 +2296,9 @@ extern const char commitid[]; /* * Exports from unicode.c in platform subdirs. */ -#ifndef CP_UTF8 -#define CP_UTF8 65001 -#endif /* void init_ucs(void); -- this is now in platform-specific headers */ bool is_dbcs_leadbyte(int codepage, char byte); -int mb_to_wc(int codepage, int flags, const char *mbstr, int mblen, - wchar_t *wcstr, int wclen); -int wc_to_mb(int codepage, int flags, const wchar_t *wcstr, int wclen, - char *mbstr, int mblen, const char *defchr); +/* For put_mb_to_wc / put_wc_to_mb, see marshal.h */ wchar_t xlat_uskbd2cyrllic(int ch); int check_compose(int first, int second); int decode_codepage(const char *cp_name); @@ -2554,6 +2371,8 @@ bool have_ssh_host_key(const char *host, int port, const char *keytype); * that aren't equivalents to things in windlg.c et al. */ extern bool console_batch_mode, console_antispoof_prompt; +extern bool console_set_batch_mode(bool); +extern bool console_set_stdio_prompts(bool); SeatPromptResult console_get_userpass_input(prompts_t *p); bool is_interactive(void); void console_print_error_msg(const char *prefix, const char *msg); @@ -2562,6 +2381,11 @@ void console_print_error_msg_fmt_v( void console_print_error_msg_fmt(const char *prefix, const char *fmt, ...) PRINTF_LIKE(2, 3); +/* + * Exports from either console frontends or terminal.c. + */ +extern bool set_legacy_charset_handling(bool); + /* * Exports from printing.c in platform subdirs. */ @@ -2578,17 +2402,11 @@ void printer_finish_job(printer_job *); * Exports from cmdline.c (and also cmdline_error(), which is * defined differently in various places and required _by_ * cmdline.c). - * - * Note that cmdline_process_param takes a const option string, but a - * writable argument string. That's not a mistake - that's so it can - * zero out password arguments in the hope of not having them show up - * avoidably in Unix 'ps'. */ struct cmdline_get_passwd_input_state { bool tried; }; #define CMDLINE_GET_PASSWD_INPUT_STATE_INIT { .tried = false } extern const cmdline_get_passwd_input_state cmdline_get_passwd_input_state_new; - -int cmdline_process_param(const char *, char *, int, Conf *); +int cmdline_process_param(CmdlineArg *, CmdlineArg *, int, Conf *); void cmdline_run_saved(Conf *); void cmdline_cleanup(void); SeatPromptResult cmdline_get_passwd_input( @@ -2597,6 +2415,33 @@ bool cmdline_host_ok(Conf *); bool cmdline_verbose(void); bool cmdline_loaded_session(void); +/* + * Abstraction provided by each platform to represent a command-line + * argument. May not be as simple as a default-encoded string: on + * Windows, command lines can be Unicode representing characters not + * in the system codepage, so you might need to retrieve the argument + * in a richer form. + */ +struct CmdlineArgList { + /* args[0], args[1], ... represent the original arguments in the + * command line. Then there's a null pointer. Further arguments + * can be invented to add to the array after that, in which case + * they'll be freed with the rest of the CmdlineArgList, but + * aren't logically part of the original command line. */ + CmdlineArg **args; + size_t nargs, argssize; +}; +struct CmdlineArg { + CmdlineArgList *list; +}; +const char *cmdline_arg_to_utf8(CmdlineArg *arg); /* may fail */ +const char *cmdline_arg_to_str(CmdlineArg *arg); /* must not fail */ +Filename *cmdline_arg_to_filename(CmdlineArg *arg); /* caller must free */ +void cmdline_arg_wipe(CmdlineArg *arg); +CmdlineArg *cmdline_arg_from_str(CmdlineArgList *list, const char *string); +/* Platforms provide their own constructors for CmdlineArgList */ +void cmdline_arg_list_free(CmdlineArgList *list); + /* * Here we have a flags word provided by each tool, which describes * the capabilities of that tool that cmdline.c needs to know about. @@ -2619,6 +2464,7 @@ extern const unsigned cmdline_tooltype; X(TOOLTYPE_HOST_ARG_FROM_LAUNCHABLE_LOAD) \ X(TOOLTYPE_PORT_ARG) \ X(TOOLTYPE_NO_VERBOSE_OPTION) \ + X(TOOLTYPE_GUI) \ /* end of list */ #define BITFLAG_INDEX(val) val ## _bitflag_index, enum { TOOLTYPE_LIST(BITFLAG_INDEX) }; @@ -2925,6 +2771,9 @@ Socket *platform_start_subprocess(const char *cmd, Plug *plug, #define LOW_SURROGATE_END 0xdfff #endif +/* REGIONAL INDICATOR SYMBOL LETTER A-Z */ +#define IS_REGIONAL_INDICATOR_LETTER(wc) ((unsigned)(wc) - 0x1F1E6U < 26) + /* These macros exist in the Windows API, so the environment may * provide them. If not, define them in terms of the above. */ #ifndef IS_HIGH_SURROGATE diff --git a/readme.md b/readme.md index 2d101560..d03b264a 100644 --- a/readme.md +++ b/readme.md @@ -2,7 +2,7 @@ PuTTY 是自由的跨平台 Telnet/SSH 客户端,同时在 Win32 和 Unix 系统下模拟 xterm 终端。其主要作者是 Simon Tatham。 -当前版本为 0.81 测试版,请访问 [PuTTY 网站](http://www.chiark.greenend.org.uk/~sgtatham/putty/)获得更多信息。如果发现英文版本有更新,[请及时通知](https://github.com/larryli/PuTTY/issues/new)。 +当前版本为 0.82 测试版,请访问 [PuTTY 网站](http://www.chiark.greenend.org.uk/~sgtatham/putty/)获得更多信息。如果发现英文版本有更新,[请及时通知](https://github.com/larryli/PuTTY/issues/new)。 ## MIT 许可证 diff --git a/settings.c b/settings.c index 2023dc3f..590320a6 100644 --- a/settings.c +++ b/settings.c @@ -65,7 +65,7 @@ static const struct keyvalwhere hknames[] = { * This is currently precisely the same as the set in * ssh/ttymode-list.h, but could in principle differ if other backends * started to support tty modes (e.g., the pty backend). - * The set of modes in in this array is currently significant for + * The set of modes in this array is currently significant for * settings migration from old versions; if they change, review the * gppmap() invocation for "TerminalModes". */ @@ -112,7 +112,9 @@ const struct BackendVtable *backend_vt_from_proto(int proto) char *get_remote_username(Conf *conf) { - char *username = conf_get_str(conf, CONF_username); + /* We don't worry about whether the username is stored as UTF-8, + * because SSH wants it as UTF-8 */ + char *username = conf_get_str_ambi(conf, CONF_username, NULL); if (*username) { return dupstr(username); } else if (conf_get_bool(conf, CONF_username_from_env)) { @@ -168,7 +170,7 @@ static void gppfile(settings_r *sesskey, const char *name, static bool gppb_raw(settings_r *sesskey, const char *name, bool def) { def = platform_default_b(name, def); - return sesskey ? read_setting_i(sesskey, name, def) != 0 : def; + return read_setting_i(sesskey, name, def) != 0; } static void gppb(settings_r *sesskey, const char *name, bool def, @@ -267,19 +269,10 @@ static bool gppmap(settings_r *sesskey, const char *name, static void wmap(settings_w *sesskey, char const *outkey, Conf *conf, int primary, bool include_values) { - char *buf, *p, *key, *realkey; + char *key, *realkey; const char *val, *q; - int len; - len = 1; /* allow for NUL */ - - for (val = conf_get_str_strs(conf, primary, NULL, &key); - val != NULL; - val = conf_get_str_strs(conf, primary, key, &key)) - len += 2 + 2 * (strlen(key) + strlen(val)); /* allow for escaping */ - - buf = snewn(len, char); - p = buf; + strbuf *sb = strbuf_new(); for (val = conf_get_str_strs(conf, primary, NULL, &key); val != NULL; @@ -304,19 +297,19 @@ static void wmap(settings_w *sesskey, char const *outkey, Conf *conf, realkey = NULL; } - if (p != buf) - *p++ = ','; + if (sb->len) + put_byte(sb, ','); for (q = key; *q; q++) { if (*q == '=' || *q == ',' || *q == '\\') - *p++ = '\\'; - *p++ = *q; + put_byte(sb, '\\'); + put_byte(sb, *q); } if (include_values) { - *p++ = '='; + put_byte(sb, '='); for (q = val; *q; q++) { if (*q == '=' || *q == ',' || *q == '\\') - *p++ = '\\'; - *p++ = *q; + put_byte(sb, '\\'); + put_byte(sb, *q); } } @@ -325,9 +318,8 @@ static void wmap(settings_w *sesskey, char const *outkey, Conf *conf, key = realkey; } } - *p = '\0'; - write_setting_s(sesskey, outkey, buf); - sfree(buf); + write_setting_s(sesskey, outkey, sb->s); + strbuf_free(sb); } static int key2val(const struct keyvalwhere *mapping, @@ -456,34 +448,18 @@ static void wprefs(settings_w *sesskey, const char *name, const struct keyvalwhere *mapping, int nvals, Conf *conf, int primary) { - char *buf, *p; - int i, maxlen; + strbuf *sb = strbuf_new(); - for (maxlen = i = 0; i < nvals; i++) { + for (int i = 0; i < nvals; i++) { const char *s = val2key(mapping, nvals, conf_get_int_int(conf, primary, i)); - if (s) { - maxlen += (maxlen > 0 ? 1 : 0) + strlen(s); - } + if (s) + put_fmt(sb, "%s%s", (sb->len ? "," : ""), s); } - buf = snewn(maxlen + 1, char); - p = buf; - - for (i = 0; i < nvals; i++) { - const char *s = val2key(mapping, nvals, - conf_get_int_int(conf, primary, i)); - if (s) { - p += sprintf(p, "%s%s", (p > buf ? "," : ""), s); - } - } - - assert(p - buf == maxlen); - *p = '\0'; - - write_setting_s(sesskey, name, buf); + write_setting_s(sesskey, name, sb->s); - sfree(buf); + strbuf_free(sb); } static void write_setting_b(settings_w *handle, const char *key, bool value) @@ -551,20 +527,92 @@ char *save_settings(const char *section, Conf *conf) return NULL; } +/* Declare extern references to conf_enum_* types */ +#define CONF_ENUM(name, ...) extern const ConfSaveEnumType conf_enum_##name; +#include "conf-enums.h" +#undef CONF_ENUM + void save_open_settings(settings_w *sesskey, Conf *conf) { int i; const char *p; + /* Save the settings simple enough to handle automatically */ + for (size_t key = 0; key < N_CONFIG_OPTIONS; key++) { + const ConfKeyInfo *info = &conf_key_info[key]; + if (!info->save_custom && !info->not_saved) { + /* Mappings are handled individually below */ + assert(info->subkey_type == CONF_TYPE_NONE); + switch (info->value_type) { + case CONF_TYPE_STR: + write_setting_s(sesskey, info->save_keyword, + conf_get_str(conf, key)); + break; + case CONF_TYPE_STR_AMBI: { + bool orig_is_utf8; + const char *orig = conf_get_str_ambi(conf, key, &orig_is_utf8); + + int cp_from, cp_to; + if (orig_is_utf8) { + cp_from = CP_UTF8; + cp_to = DEFAULT_CODEPAGE; + } else { + cp_from = DEFAULT_CODEPAGE; + cp_to = CP_UTF8; + } + + size_t wlen; + wchar_t *wide = dup_mb_to_wc_c( + cp_from, orig, strlen(orig), &wlen); + + size_t clen; + char *converted = dup_wc_to_mb_c( + cp_to, wide, wlen, "", &clen); + + const char *native, *utf8; + if (orig_is_utf8) { + utf8 = orig; + native = converted; + } else { + native = orig; + utf8 = converted; + } + write_setting_s(sesskey, info->save_keyword, native); + (void)utf8; /* FIXME: also save the UTF-8 version */ + + burnwcs(wide); + burnstr(converted); + break; + } + case CONF_TYPE_INT: { + int ival = conf_get_int(conf, key); + if (info->storage_enum) { + bool success = conf_enum_map_to_storage( + info->storage_enum, ival, &ival); + assert(success && "unmapped integer value"); + } + write_setting_i(sesskey, info->save_keyword, ival); + break; + } + case CONF_TYPE_BOOL: + write_setting_b(sesskey, info->save_keyword, + conf_get_bool(conf, key)); + break; + case CONF_TYPE_FILENAME: + write_setting_filename(sesskey, info->save_keyword, + conf_get_filename(conf, key)); + break; + case CONF_TYPE_FONT: + write_setting_fontspec(sesskey, info->save_keyword, + conf_get_fontspec(conf, key)); + break; + default: + unreachable("bad key type in save_open_settings"); + } + } + } + write_setting_i(sesskey, "Present", 1); - write_setting_s(sesskey, "HostName", conf_get_str(conf, CONF_host)); - write_setting_filename(sesskey, "LogFileName", conf_get_filename(conf, CONF_logfilename)); - write_setting_i(sesskey, "LogType", conf_get_int(conf, CONF_logtype)); - write_setting_i(sesskey, "LogFileClash", conf_get_int(conf, CONF_logxfovr)); - write_setting_b(sesskey, "LogFlush", conf_get_bool(conf, CONF_logflush)); - write_setting_b(sesskey, "LogHeader", conf_get_bool(conf, CONF_logheader)); - write_setting_b(sesskey, "SSHLogOmitPasswords", conf_get_bool(conf, CONF_logomitpass)); - write_setting_b(sesskey, "SSHLogOmitData", conf_get_bool(conf, CONF_logomitdata)); p = "raw"; { const struct BackendVtable *vt = @@ -573,118 +621,31 @@ void save_open_settings(settings_w *sesskey, Conf *conf) p = vt->id; } write_setting_s(sesskey, "Protocol", p); - write_setting_i(sesskey, "PortNumber", conf_get_int(conf, CONF_port)); - /* The CloseOnExit numbers are arranged in a different order from - * the standard FORCE_ON / FORCE_OFF / AUTO. */ - write_setting_i(sesskey, "CloseOnExit", (conf_get_int(conf, CONF_close_on_exit)+2)%3); - write_setting_b(sesskey, "WarnOnClose", !!conf_get_bool(conf, CONF_warn_on_close)); write_setting_i(sesskey, "PingInterval", conf_get_int(conf, CONF_ping_interval) / 60); /* minutes */ write_setting_i(sesskey, "PingIntervalSecs", conf_get_int(conf, CONF_ping_interval) % 60); /* seconds */ - write_setting_b(sesskey, "TCPNoDelay", conf_get_bool(conf, CONF_tcp_nodelay)); - write_setting_b(sesskey, "TCPKeepalives", conf_get_bool(conf, CONF_tcp_keepalives)); - write_setting_s(sesskey, "TerminalType", conf_get_str(conf, CONF_termtype)); - write_setting_s(sesskey, "TerminalSpeed", conf_get_str(conf, CONF_termspeed)); wmap(sesskey, "TerminalModes", conf, CONF_ttymodes, true); - /* Address family selection */ - write_setting_i(sesskey, "AddressFamily", conf_get_int(conf, CONF_addressfamily)); - /* proxy settings */ - write_setting_s(sesskey, "ProxyExcludeList", conf_get_str(conf, CONF_proxy_exclude_list)); - write_setting_i(sesskey, "ProxyDNS", (conf_get_int(conf, CONF_proxy_dns)+2)%3); - write_setting_b(sesskey, "ProxyLocalhost", conf_get_bool(conf, CONF_even_proxy_localhost)); - write_setting_i(sesskey, "ProxyMethod", conf_get_int(conf, CONF_proxy_type)); - write_setting_s(sesskey, "ProxyHost", conf_get_str(conf, CONF_proxy_host)); - write_setting_i(sesskey, "ProxyPort", conf_get_int(conf, CONF_proxy_port)); - write_setting_s(sesskey, "ProxyUsername", conf_get_str(conf, CONF_proxy_username)); - write_setting_s(sesskey, "ProxyPassword", conf_get_str(conf, CONF_proxy_password)); - write_setting_s(sesskey, "ProxyTelnetCommand", conf_get_str(conf, CONF_proxy_telnet_command)); - write_setting_i(sesskey, "ProxyLogToTerm", conf_get_int(conf, CONF_proxy_log_to_term)); wmap(sesskey, "Environment", conf, CONF_environmt, true); - write_setting_s(sesskey, "UserName", conf_get_str(conf, CONF_username)); - write_setting_b(sesskey, "UserNameFromEnvironment", conf_get_bool(conf, CONF_username_from_env)); - write_setting_s(sesskey, "LocalUserName", conf_get_str(conf, CONF_localusername)); - write_setting_b(sesskey, "NoPTY", conf_get_bool(conf, CONF_nopty)); - write_setting_b(sesskey, "Compression", conf_get_bool(conf, CONF_compression)); - write_setting_b(sesskey, "TryAgent", conf_get_bool(conf, CONF_tryagent)); - write_setting_b(sesskey, "AgentFwd", conf_get_bool(conf, CONF_agentfwd)); #ifndef NO_GSSAPI write_setting_b(sesskey, "GssapiFwd", conf_get_bool(conf, CONF_gssapifwd)); #endif - write_setting_b(sesskey, "ChangeUsername", conf_get_bool(conf, CONF_change_username)); wprefs(sesskey, "Cipher", ciphernames, CIPHER_MAX, conf, CONF_ssh_cipherlist); wprefs(sesskey, "KEX", kexnames, KEX_MAX, conf, CONF_ssh_kexlist); wprefs(sesskey, "HostKey", hknames, HK_MAX, conf, CONF_ssh_hklist); - write_setting_b(sesskey, "PreferKnownHostKeys", conf_get_bool(conf, CONF_ssh_prefer_known_hostkeys)); - write_setting_i(sesskey, "RekeyTime", conf_get_int(conf, CONF_ssh_rekey_time)); #ifndef NO_GSSAPI write_setting_i(sesskey, "GssapiRekey", conf_get_int(conf, CONF_gssapirekey)); #endif - write_setting_s(sesskey, "RekeyBytes", conf_get_str(conf, CONF_ssh_rekey_data)); - write_setting_b(sesskey, "SshNoAuth", conf_get_bool(conf, CONF_ssh_no_userauth)); - write_setting_b(sesskey, "SshNoTrivialAuth", conf_get_bool(conf, CONF_ssh_no_trivial_userauth)); - write_setting_b(sesskey, "SshBanner", conf_get_bool(conf, CONF_ssh_show_banner)); - write_setting_b(sesskey, "AuthTIS", conf_get_bool(conf, CONF_try_tis_auth)); - write_setting_b(sesskey, "AuthKI", conf_get_bool(conf, CONF_try_ki_auth)); #ifndef NO_GSSAPI write_setting_b(sesskey, "AuthGSSAPI", conf_get_bool(conf, CONF_try_gssapi_auth)); write_setting_b(sesskey, "AuthGSSAPIKEX", conf_get_bool(conf, CONF_try_gssapi_kex)); wprefs(sesskey, "GSSLibs", gsslibkeywords, ngsslibs, conf, CONF_ssh_gsslist); write_setting_filename(sesskey, "GSSCustom", conf_get_filename(conf, CONF_ssh_gss_custom)); #endif - write_setting_b(sesskey, "SshNoShell", conf_get_bool(conf, CONF_ssh_no_shell)); - write_setting_i(sesskey, "SshProt", conf_get_int(conf, CONF_sshprot)); - write_setting_s(sesskey, "LogHost", conf_get_str(conf, CONF_loghost)); - write_setting_b(sesskey, "SSH2DES", conf_get_bool(conf, CONF_ssh2_des_cbc)); - write_setting_filename(sesskey, "PublicKeyFile", conf_get_filename(conf, CONF_keyfile)); - write_setting_filename(sesskey, "DetachedCertificate", conf_get_filename(conf, CONF_detached_cert)); - write_setting_s(sesskey, "AuthPlugin", conf_get_str(conf, CONF_auth_plugin)); - write_setting_s(sesskey, "RemoteCommand", conf_get_str(conf, CONF_remote_cmd)); - write_setting_b(sesskey, "RFCEnviron", conf_get_bool(conf, CONF_rfc_environ)); - write_setting_b(sesskey, "PassiveTelnet", conf_get_bool(conf, CONF_passive_telnet)); - write_setting_b(sesskey, "BackspaceIsDelete", conf_get_bool(conf, CONF_bksp_is_delete)); - write_setting_b(sesskey, "RXVTHomeEnd", conf_get_bool(conf, CONF_rxvt_homeend)); - write_setting_i(sesskey, "LinuxFunctionKeys", conf_get_int(conf, CONF_funky_type)); - write_setting_i(sesskey, "ShiftedArrowKeys", conf_get_int(conf, CONF_sharrow_type)); - write_setting_b(sesskey, "NoApplicationKeys", conf_get_bool(conf, CONF_no_applic_k)); - write_setting_b(sesskey, "NoApplicationCursors", conf_get_bool(conf, CONF_no_applic_c)); - write_setting_b(sesskey, "NoMouseReporting", conf_get_bool(conf, CONF_no_mouse_rep)); - write_setting_b(sesskey, "NoRemoteResize", conf_get_bool(conf, CONF_no_remote_resize)); - write_setting_b(sesskey, "NoAltScreen", conf_get_bool(conf, CONF_no_alt_screen)); - write_setting_b(sesskey, "NoRemoteWinTitle", conf_get_bool(conf, CONF_no_remote_wintitle)); - write_setting_b(sesskey, "NoRemoteClearScroll", conf_get_bool(conf, CONF_no_remote_clearscroll)); - write_setting_i(sesskey, "RemoteQTitleAction", conf_get_int(conf, CONF_remote_qtitle_action)); - write_setting_b(sesskey, "NoDBackspace", conf_get_bool(conf, CONF_no_dbackspace)); - write_setting_b(sesskey, "NoRemoteCharset", conf_get_bool(conf, CONF_no_remote_charset)); - write_setting_b(sesskey, "ApplicationCursorKeys", conf_get_bool(conf, CONF_app_cursor)); - write_setting_b(sesskey, "ApplicationKeypad", conf_get_bool(conf, CONF_app_keypad)); - write_setting_b(sesskey, "NetHackKeypad", conf_get_bool(conf, CONF_nethack_keypad)); - write_setting_b(sesskey, "AltF4", conf_get_bool(conf, CONF_alt_f4)); - write_setting_b(sesskey, "AltSpace", conf_get_bool(conf, CONF_alt_space)); - write_setting_b(sesskey, "AltOnly", conf_get_bool(conf, CONF_alt_only)); - write_setting_b(sesskey, "ComposeKey", conf_get_bool(conf, CONF_compose_key)); - write_setting_b(sesskey, "CtrlAltKeys", conf_get_bool(conf, CONF_ctrlaltkeys)); #ifdef OSX_META_KEY_CONFIG write_setting_b(sesskey, "OSXOptionMeta", conf_get_bool(conf, CONF_osx_option_meta)); write_setting_b(sesskey, "OSXCommandMeta", conf_get_bool(conf, CONF_osx_command_meta)); #endif - write_setting_b(sesskey, "TelnetKey", conf_get_bool(conf, CONF_telnet_keyboard)); - write_setting_b(sesskey, "TelnetRet", conf_get_bool(conf, CONF_telnet_newline)); - write_setting_i(sesskey, "LocalEcho", conf_get_int(conf, CONF_localecho)); - write_setting_i(sesskey, "LocalEdit", conf_get_int(conf, CONF_localedit)); - write_setting_s(sesskey, "Answerback", conf_get_str(conf, CONF_answerback)); - write_setting_b(sesskey, "AlwaysOnTop", conf_get_bool(conf, CONF_alwaysontop)); - write_setting_b(sesskey, "FullScreenOnAltEnter", conf_get_bool(conf, CONF_fullscreenonaltenter)); - write_setting_b(sesskey, "HideMousePtr", conf_get_bool(conf, CONF_hide_mouseptr)); - write_setting_b(sesskey, "SunkenEdge", conf_get_bool(conf, CONF_sunken_edge)); - write_setting_i(sesskey, "WindowBorder", conf_get_int(conf, CONF_window_border)); - write_setting_i(sesskey, "CurType", conf_get_int(conf, CONF_cursor_type)); - write_setting_b(sesskey, "BlinkCur", conf_get_bool(conf, CONF_blink_cur)); - write_setting_i(sesskey, "Beep", conf_get_int(conf, CONF_beep)); - write_setting_i(sesskey, "BeepInd", conf_get_int(conf, CONF_beep_ind)); - write_setting_filename(sesskey, "BellWaveFile", conf_get_filename(conf, CONF_bell_wavefile)); - write_setting_b(sesskey, "BellOverload", conf_get_bool(conf, CONF_bellovl)); - write_setting_i(sesskey, "BellOverloadN", conf_get_int(conf, CONF_bellovl_n)); write_setting_i(sesskey, "BellOverloadT", conf_get_int(conf, CONF_bellovl_t) #ifdef PUTTY_UNIX_PLATFORM_H * 1000 @@ -695,26 +656,6 @@ void save_open_settings(settings_w *sesskey, Conf *conf) * 1000 #endif ); - write_setting_i(sesskey, "ScrollbackLines", conf_get_int(conf, CONF_savelines)); - write_setting_b(sesskey, "DECOriginMode", conf_get_bool(conf, CONF_dec_om)); - write_setting_b(sesskey, "AutoWrapMode", conf_get_bool(conf, CONF_wrap_mode)); - write_setting_b(sesskey, "LFImpliesCR", conf_get_bool(conf, CONF_lfhascr)); - write_setting_b(sesskey, "CRImpliesLF", conf_get_bool(conf, CONF_crhaslf)); - write_setting_b(sesskey, "DisableArabicShaping", conf_get_bool(conf, CONF_no_arabicshaping)); - write_setting_b(sesskey, "DisableBidi", conf_get_bool(conf, CONF_no_bidi)); - write_setting_b(sesskey, "WinNameAlways", conf_get_bool(conf, CONF_win_name_always)); - write_setting_s(sesskey, "WinTitle", conf_get_str(conf, CONF_wintitle)); - write_setting_i(sesskey, "TermWidth", conf_get_int(conf, CONF_width)); - write_setting_i(sesskey, "TermHeight", conf_get_int(conf, CONF_height)); - write_setting_fontspec(sesskey, "Font", conf_get_fontspec(conf, CONF_font)); - write_setting_i(sesskey, "FontQuality", conf_get_int(conf, CONF_font_quality)); - write_setting_i(sesskey, "FontVTMode", conf_get_int(conf, CONF_vtmode)); - write_setting_b(sesskey, "UseSystemColours", conf_get_bool(conf, CONF_system_colour)); - write_setting_b(sesskey, "TryPalette", conf_get_bool(conf, CONF_try_palette)); - write_setting_b(sesskey, "ANSIColour", conf_get_bool(conf, CONF_ansi_colour)); - write_setting_b(sesskey, "Xterm256Colour", conf_get_bool(conf, CONF_xterm_256_colour)); - write_setting_b(sesskey, "TrueColour", conf_get_bool(conf, CONF_true_colour)); - write_setting_i(sesskey, "BoldAsColour", conf_get_int(conf, CONF_bold_style)-1); for (i = 0; i < 22; i++) { char buf[20], buf2[30]; @@ -725,13 +666,6 @@ void save_open_settings(settings_w *sesskey, Conf *conf) conf_get_int_int(conf, CONF_colours, i*3+2)); write_setting_s(sesskey, buf, buf2); } - write_setting_b(sesskey, "RawCNP", conf_get_bool(conf, CONF_rawcnp)); - write_setting_b(sesskey, "UTF8linedraw", conf_get_bool(conf, CONF_utf8linedraw)); - write_setting_b(sesskey, "PasteRTF", conf_get_bool(conf, CONF_rtf_paste)); - write_setting_i(sesskey, "MouseIsXterm", conf_get_int(conf, CONF_mouse_is_xterm)); - write_setting_b(sesskey, "RectSelect", conf_get_bool(conf, CONF_rect_select)); - write_setting_b(sesskey, "PasteControls", conf_get_bool(conf, CONF_paste_controls)); - write_setting_b(sesskey, "MouseOverride", conf_get_bool(conf, CONF_mouse_override)); for (i = 0; i < 256; i += 32) { char buf[20], buf2[256]; int j; @@ -744,77 +678,14 @@ void save_open_settings(settings_w *sesskey, Conf *conf) } write_setting_s(sesskey, buf, buf2); } - write_setting_b(sesskey, "MouseAutocopy", - conf_get_bool(conf, CONF_mouseautocopy)); write_clip_setting(sesskey, "MousePaste", conf, CONF_mousepaste, CONF_mousepaste_custom); write_clip_setting(sesskey, "CtrlShiftIns", conf, CONF_ctrlshiftins, CONF_ctrlshiftins_custom); write_clip_setting(sesskey, "CtrlShiftCV", conf, CONF_ctrlshiftcv, CONF_ctrlshiftcv_custom); - write_setting_s(sesskey, "LineCodePage", conf_get_str(conf, CONF_line_codepage)); - write_setting_b(sesskey, "CJKAmbigWide", conf_get_bool(conf, CONF_cjk_ambig_wide)); - write_setting_b(sesskey, "UTF8Override", conf_get_bool(conf, CONF_utf8_override)); - write_setting_s(sesskey, "Printer", conf_get_str(conf, CONF_printer)); - write_setting_b(sesskey, "CapsLockCyr", conf_get_bool(conf, CONF_xlat_capslockcyr)); - write_setting_b(sesskey, "ScrollBar", conf_get_bool(conf, CONF_scrollbar)); - write_setting_b(sesskey, "ScrollBarFullScreen", conf_get_bool(conf, CONF_scrollbar_in_fullscreen)); - write_setting_b(sesskey, "ScrollOnKey", conf_get_bool(conf, CONF_scroll_on_key)); - write_setting_b(sesskey, "ScrollOnDisp", conf_get_bool(conf, CONF_scroll_on_disp)); - write_setting_b(sesskey, "EraseToScrollback", conf_get_bool(conf, CONF_erase_to_scrollback)); - write_setting_i(sesskey, "LockSize", conf_get_int(conf, CONF_resize_action)); - write_setting_b(sesskey, "BCE", conf_get_bool(conf, CONF_bce)); - write_setting_b(sesskey, "BlinkText", conf_get_bool(conf, CONF_blinktext)); - write_setting_b(sesskey, "X11Forward", conf_get_bool(conf, CONF_x11_forward)); - write_setting_s(sesskey, "X11Display", conf_get_str(conf, CONF_x11_display)); - write_setting_i(sesskey, "X11AuthType", conf_get_int(conf, CONF_x11_auth)); - write_setting_filename(sesskey, "X11AuthFile", conf_get_filename(conf, CONF_xauthfile)); - write_setting_b(sesskey, "LocalPortAcceptAll", conf_get_bool(conf, CONF_lport_acceptall)); - write_setting_b(sesskey, "RemotePortAcceptAll", conf_get_bool(conf, CONF_rport_acceptall)); wmap(sesskey, "PortForwardings", conf, CONF_portfwd, true); - write_setting_i(sesskey, "BugIgnore1", 2-conf_get_int(conf, CONF_sshbug_ignore1)); - write_setting_i(sesskey, "BugPlainPW1", 2-conf_get_int(conf, CONF_sshbug_plainpw1)); - write_setting_i(sesskey, "BugRSA1", 2-conf_get_int(conf, CONF_sshbug_rsa1)); - write_setting_i(sesskey, "BugIgnore2", 2-conf_get_int(conf, CONF_sshbug_ignore2)); - write_setting_i(sesskey, "BugHMAC2", 2-conf_get_int(conf, CONF_sshbug_hmac2)); - write_setting_i(sesskey, "BugDeriveKey2", 2-conf_get_int(conf, CONF_sshbug_derivekey2)); - write_setting_i(sesskey, "BugRSAPad2", 2-conf_get_int(conf, CONF_sshbug_rsapad2)); - write_setting_i(sesskey, "BugPKSessID2", 2-conf_get_int(conf, CONF_sshbug_pksessid2)); - write_setting_i(sesskey, "BugRekey2", 2-conf_get_int(conf, CONF_sshbug_rekey2)); - write_setting_i(sesskey, "BugMaxPkt2", 2-conf_get_int(conf, CONF_sshbug_maxpkt2)); - write_setting_i(sesskey, "BugOldGex2", 2-conf_get_int(conf, CONF_sshbug_oldgex2)); - write_setting_i(sesskey, "BugWinadj", 2-conf_get_int(conf, CONF_sshbug_winadj)); - write_setting_i(sesskey, "BugChanReq", 2-conf_get_int(conf, CONF_sshbug_chanreq)); - write_setting_i(sesskey, "BugRSASHA2CertUserauth", 2-conf_get_int(conf, CONF_sshbug_rsa_sha2_cert_userauth)); - write_setting_i(sesskey, "BugDropStart", 2-conf_get_int(conf, CONF_sshbug_dropstart)); - write_setting_i(sesskey, "BugFilterKexinit", 2-conf_get_int(conf, CONF_sshbug_filter_kexinit)); - write_setting_b(sesskey, "StampUtmp", conf_get_bool(conf, CONF_stamp_utmp)); - write_setting_b(sesskey, "LoginShell", conf_get_bool(conf, CONF_login_shell)); - write_setting_b(sesskey, "ScrollbarOnLeft", conf_get_bool(conf, CONF_scrollbar_on_left)); - write_setting_fontspec(sesskey, "BoldFont", conf_get_fontspec(conf, CONF_boldfont)); - write_setting_fontspec(sesskey, "WideFont", conf_get_fontspec(conf, CONF_widefont)); - write_setting_fontspec(sesskey, "WideBoldFont", conf_get_fontspec(conf, CONF_wideboldfont)); - write_setting_b(sesskey, "ShadowBold", conf_get_bool(conf, CONF_shadowbold)); - write_setting_i(sesskey, "ShadowBoldOffset", conf_get_int(conf, CONF_shadowboldoffset)); - write_setting_s(sesskey, "SerialLine", conf_get_str(conf, CONF_serline)); - write_setting_i(sesskey, "SerialSpeed", conf_get_int(conf, CONF_serspeed)); - write_setting_i(sesskey, "SerialDataBits", conf_get_int(conf, CONF_serdatabits)); - write_setting_i(sesskey, "SerialStopHalfbits", conf_get_int(conf, CONF_serstopbits)); - write_setting_i(sesskey, "SerialParity", conf_get_int(conf, CONF_serparity)); - write_setting_i(sesskey, "SerialFlowControl", conf_get_int(conf, CONF_serflow)); - write_setting_s(sesskey, "WindowClass", conf_get_str(conf, CONF_winclass)); - write_setting_b(sesskey, "ConnectionSharing", conf_get_bool(conf, CONF_ssh_connection_sharing)); - write_setting_b(sesskey, "ConnectionSharingUpstream", conf_get_bool(conf, CONF_ssh_connection_sharing_upstream)); - write_setting_b(sesskey, "ConnectionSharingDownstream", conf_get_bool(conf, CONF_ssh_connection_sharing_downstream)); wmap(sesskey, "SSHManualHostKeys", conf, CONF_ssh_manual_hostkeys, false); - - /* - * SUPDUP settings - */ - write_setting_s(sesskey, "SUPDUPLocation", conf_get_str(conf, CONF_supdup_location)); - write_setting_i(sesskey, "SUPDUPCharset", conf_get_int(conf, CONF_supdup_ascii_set)); - write_setting_b(sesskey, "SUPDUPMoreProcessing", conf_get_bool(conf, CONF_supdup_more)); - write_setting_b(sesskey, "SUPDUPScrolling", conf_get_bool(conf, CONF_supdup_scroll)); } bool load_settings(const char *section, Conf *conf) @@ -837,19 +708,83 @@ void load_open_settings(settings_r *sesskey, Conf *conf) int i; char *prot; - conf_set_bool(conf, CONF_ssh_subsys, false); /* FIXME: load this properly */ - conf_set_str(conf, CONF_remote_cmd, ""); - conf_set_str(conf, CONF_remote_cmd2, ""); - conf_set_str(conf, CONF_ssh_nc_host, ""); - - gpps(sesskey, "HostName", "", conf, CONF_host); - gppfile(sesskey, "LogFileName", conf, CONF_logfilename); - gppi(sesskey, "LogType", 0, conf, CONF_logtype); - gppi(sesskey, "LogFileClash", LGXF_ASK, conf, CONF_logxfovr); - gppb(sesskey, "LogFlush", true, conf, CONF_logflush); - gppb(sesskey, "LogHeader", true, conf, CONF_logheader); - gppb(sesskey, "SSHLogOmitPasswords", true, conf, CONF_logomitpass); - gppb(sesskey, "SSHLogOmitData", false, conf, CONF_logomitdata); + /* Load the settings simple enough to handle automatically */ + for (size_t key = 0; key < N_CONFIG_OPTIONS; key++) { + const ConfKeyInfo *info = &conf_key_info[key]; + if (info->not_saved) { + /* Mappings are assumed to default to empty */ + if (info->subkey_type == CONF_TYPE_NONE) { + switch (info->value_type) { + case CONF_TYPE_STR: + case CONF_TYPE_STR_AMBI: + conf_set_str(conf, key, info->default_value.sval); + break; + case CONF_TYPE_INT: + conf_set_int(conf, key, info->default_value.ival); + break; + case CONF_TYPE_BOOL: + conf_set_bool(conf, key, info->default_value.bval); + break; + default: + unreachable("bad key type in load_open_settings"); + } + } + } else if (!info->load_custom) { + /* Mappings are handled individually below */ + assert(info->subkey_type == CONF_TYPE_NONE); + switch (info->value_type) { + case CONF_TYPE_STR: + case CONF_TYPE_STR_AMBI: + gpps(sesskey, info->save_keyword, info->default_value.sval, + conf, key); + break; + case CONF_TYPE_INT: + if (!info->storage_enum) { + gppi(sesskey, info->save_keyword, + info->default_value.ival, conf, key); + } else { + /* + * Because our internal defaults are stored as the + * value we want in Conf, but our API for + * retrieving integers from storage requires a + * default value to fill in if no record is found, + * we must first figure out the default _storage_ + * value, ugh. + */ + int defstorage; + bool success = conf_enum_map_to_storage( + info->storage_enum, info->default_value.ival, + &defstorage); + assert(success && "unmapped default"); + + /* Now retrieve the stored value */ + int storageval = gppi_raw(sesskey, info->save_keyword, + defstorage); + + /* And translate it back to Conf representation, + * replacing it with our Conf-rep default on failure */ + int confval; + if (!conf_enum_map_from_storage( + info->storage_enum, storageval, &confval)) + confval = info->default_value.ival; + conf_set_int(conf, key, confval); + } + break; + case CONF_TYPE_BOOL: + gppb(sesskey, info->save_keyword, info->default_value.bval, + conf, key); + break; + case CONF_TYPE_FILENAME: + gppfile(sesskey, info->save_keyword, conf, key); + break; + case CONF_TYPE_FONT: + gppfont(sesskey, info->save_keyword, conf, key); + break; + default: + unreachable("bad key type in load_open_settings"); + } + } + } prot = gpps_raw(sesskey, "Protocol", "default"); conf_set_int(conf, CONF_protocol, default_protocol); @@ -863,13 +798,6 @@ void load_open_settings(settings_r *sesskey, Conf *conf) } sfree(prot); - /* Address family selection */ - gppi(sesskey, "AddressFamily", ADDRTYPE_UNSPEC, conf, CONF_addressfamily); - - /* The CloseOnExit numbers are arranged in a different order from - * the standard FORCE_ON / FORCE_OFF / AUTO. */ - i = gppi_raw(sesskey, "CloseOnExit", 1); conf_set_int(conf, CONF_close_on_exit, (i+1)%3); - gppb(sesskey, "WarnOnClose", true, conf, CONF_warn_on_close); { /* This is two values for backward compatibility with 0.50/0.51 */ int pingmin, pingsec; @@ -877,10 +805,6 @@ void load_open_settings(settings_r *sesskey, Conf *conf) pingsec = gppi_raw(sesskey, "PingIntervalSecs", 0); conf_set_int(conf, CONF_ping_interval, pingmin * 60 + pingsec); } - gppb(sesskey, "TCPNoDelay", true, conf, CONF_tcp_nodelay); - gppb(sesskey, "TCPKeepalives", false, conf, CONF_tcp_keepalives); - gpps(sesskey, "TerminalType", "xterm", conf, CONF_termtype); - gpps(sesskey, "TerminalSpeed", "38400,38400", conf, CONF_termspeed); if (gppmap(sesskey, "TerminalModes", conf, CONF_ttymodes)) { /* * Backwards compatibility with old saved settings. @@ -933,46 +857,26 @@ void load_open_settings(settings_r *sesskey, Conf *conf) } /* proxy settings */ - gpps(sesskey, "ProxyExcludeList", "", conf, CONF_proxy_exclude_list); - i = gppi_raw(sesskey, "ProxyDNS", 1); conf_set_int(conf, CONF_proxy_dns, (i+1)%3); - gppb(sesskey, "ProxyLocalhost", false, conf, CONF_even_proxy_localhost); - gppi(sesskey, "ProxyMethod", -1, conf, CONF_proxy_type); - if (conf_get_int(conf, CONF_proxy_type) == -1) { - int i; - i = gppi_raw(sesskey, "ProxyType", 0); - if (i == 0) - conf_set_int(conf, CONF_proxy_type, PROXY_NONE); - else if (i == 1) - conf_set_int(conf, CONF_proxy_type, PROXY_HTTP); - else if (i == 3) - conf_set_int(conf, CONF_proxy_type, PROXY_TELNET); - else if (i == 4) - conf_set_int(conf, CONF_proxy_type, PROXY_CMD); - else { - i = gppi_raw(sesskey, "ProxySOCKSVersion", 5); - if (i == 5) - conf_set_int(conf, CONF_proxy_type, PROXY_SOCKS5); - else - conf_set_int(conf, CONF_proxy_type, PROXY_SOCKS4); + { + int storageval = gppi_raw(sesskey, "ProxyMethod", -1); + int confval; + if (!conf_enum_map_from_storage(&conf_enum_proxy_type, + storageval, &confval)) { + /* + * Fall back to older ProxyType and ProxySOCKSVersion format + */ + storageval = gppi_raw(sesskey, "ProxyType", 0); + if (conf_enum_map_from_storage(&conf_enum_old_proxy_type, + storageval, &confval)) { + if (confval == PROXY_SOCKS5 && + gppi_raw(sesskey, "ProxySOCKSVersion", 5) == 4) + confval = PROXY_SOCKS4; + } } + conf_set_int(conf, CONF_proxy_type, confval); } - gpps(sesskey, "ProxyHost", "proxy", conf, CONF_proxy_host); - gppi(sesskey, "ProxyPort", 80, conf, CONF_proxy_port); - gpps(sesskey, "ProxyUsername", "", conf, CONF_proxy_username); - gpps(sesskey, "ProxyPassword", "", conf, CONF_proxy_password); - gpps(sesskey, "ProxyTelnetCommand", "connect %host %port\\n", - conf, CONF_proxy_telnet_command); - gppi(sesskey, "ProxyLogToTerm", FORCE_OFF, conf, CONF_proxy_log_to_term); + gppmap(sesskey, "Environment", conf, CONF_environmt); - gpps(sesskey, "UserName", "", conf, CONF_username); - gppb(sesskey, "UserNameFromEnvironment", false, - conf, CONF_username_from_env); - gpps(sesskey, "LocalUserName", "", conf, CONF_localusername); - gppb(sesskey, "NoPTY", false, conf, CONF_nopty); - gppb(sesskey, "Compression", false, conf, CONF_compression); - gppb(sesskey, "TryAgent", true, conf, CONF_tryagent); - gppb(sesskey, "AgentFwd", false, conf, CONF_agentfwd); - gppb(sesskey, "ChangeUsername", false, conf, CONF_change_username); #ifndef NO_GSSAPI gppb(sesskey, "GssapiFwd", false, conf, CONF_gssapifwd); #endif @@ -1027,98 +931,34 @@ void load_open_settings(settings_r *sesskey, Conf *conf) } gprefs(sesskey, "HostKey", "ed25519,ecdsa,rsa,dsa,WARN", hknames, HK_MAX, conf, CONF_ssh_hklist); - gppb(sesskey, "PreferKnownHostKeys", true, conf, CONF_ssh_prefer_known_hostkeys); - gppi(sesskey, "RekeyTime", 60, conf, CONF_ssh_rekey_time); #ifndef NO_GSSAPI gppi(sesskey, "GssapiRekey", GSS_DEF_REKEY_MINS, conf, CONF_gssapirekey); -#endif - gpps(sesskey, "RekeyBytes", "1G", conf, CONF_ssh_rekey_data); - { - /* SSH-2 only by default */ - int sshprot = gppi_raw(sesskey, "SshProt", 3); - /* Old sessions may contain the values corresponding to the fallbacks - * we used to allow; migrate them */ - if (sshprot == 1) sshprot = 0; /* => "SSH-1 only" */ - else if (sshprot == 2) sshprot = 3; /* => "SSH-2 only" */ - conf_set_int(conf, CONF_sshprot, sshprot); - } - gpps(sesskey, "LogHost", "", conf, CONF_loghost); - gppb(sesskey, "SSH2DES", false, conf, CONF_ssh2_des_cbc); - gppb(sesskey, "SshNoAuth", false, conf, CONF_ssh_no_userauth); - gppb(sesskey, "SshNoTrivialAuth", false, conf, CONF_ssh_no_trivial_userauth); - gppb(sesskey, "SshBanner", true, conf, CONF_ssh_show_banner); - gppb(sesskey, "AuthTIS", false, conf, CONF_try_tis_auth); - gppb(sesskey, "AuthKI", true, conf, CONF_try_ki_auth); -#ifndef NO_GSSAPI gppb(sesskey, "AuthGSSAPI", true, conf, CONF_try_gssapi_auth); gppb(sesskey, "AuthGSSAPIKEX", true, conf, CONF_try_gssapi_kex); gprefs(sesskey, "GSSLibs", "\0", gsslibkeywords, ngsslibs, conf, CONF_ssh_gsslist); gppfile(sesskey, "GSSCustom", conf, CONF_ssh_gss_custom); #endif - gppb(sesskey, "SshNoShell", false, conf, CONF_ssh_no_shell); - gppfile(sesskey, "PublicKeyFile", conf, CONF_keyfile); - gppfile(sesskey, "DetachedCertificate", conf, CONF_detached_cert); - gpps(sesskey, "AuthPlugin", "", conf, CONF_auth_plugin); - gpps(sesskey, "RemoteCommand", "", conf, CONF_remote_cmd); - gppb(sesskey, "RFCEnviron", false, conf, CONF_rfc_environ); - gppb(sesskey, "PassiveTelnet", false, conf, CONF_passive_telnet); - gppb(sesskey, "BackspaceIsDelete", true, conf, CONF_bksp_is_delete); - gppb(sesskey, "RXVTHomeEnd", false, conf, CONF_rxvt_homeend); - gppi(sesskey, "LinuxFunctionKeys", 2, conf, CONF_funky_type); - gppi(sesskey, "ShiftedArrowKeys", SHARROW_APPLICATION, conf, - CONF_sharrow_type); - gppb(sesskey, "NoApplicationKeys", true, conf, CONF_no_applic_k); - gppb(sesskey, "NoApplicationCursors", false, conf, CONF_no_applic_c); - gppb(sesskey, "NoMouseReporting", false, conf, CONF_no_mouse_rep); - gppb(sesskey, "NoRemoteResize", false, conf, CONF_no_remote_resize); - gppb(sesskey, "NoAltScreen", false, conf, CONF_no_alt_screen); - gppb(sesskey, "NoRemoteWinTitle", false, conf, CONF_no_remote_wintitle); - gppb(sesskey, "NoRemoteClearScroll", false, - conf, CONF_no_remote_clearscroll); { - /* Backward compatibility */ - int no_remote_qtitle = gppi_raw(sesskey, "NoRemoteQTitle", 1); - /* We deliberately interpret the old setting of "no response" as - * "empty string". This changes the behaviour, but hopefully for - * the better; the user can always recover the old behaviour. */ - gppi(sesskey, "RemoteQTitleAction", - no_remote_qtitle ? TITLE_EMPTY : TITLE_REAL, - conf, CONF_remote_qtitle_action); + int storageval = gppi_raw(sesskey, "RemoteQTitleAction", -1); + int confval; + if (!conf_enum_map_from_storage(&conf_enum_remote_qtitle_action, + storageval, &confval)) { + /* + * Fall back to older NoRemoteQTitle format + */ + storageval = gppi_raw(sesskey, "NoRemoteQTitle", 1); + /* We deliberately interpret the old setting of "no response" as + * "empty string". This changes the behaviour, but hopefully for + * the better; the user can always recover the old behaviour. */ + confval = storageval ? TITLE_EMPTY : TITLE_REAL; + } + conf_set_int(conf, CONF_remote_qtitle_action, confval); } - gppb(sesskey, "NoDBackspace", false, conf, CONF_no_dbackspace); - gppb(sesskey, "NoRemoteCharset", false, conf, CONF_no_remote_charset); - gppb(sesskey, "ApplicationCursorKeys", false, conf, CONF_app_cursor); - gppb(sesskey, "ApplicationKeypad", false, conf, CONF_app_keypad); - gppb(sesskey, "NetHackKeypad", false, conf, CONF_nethack_keypad); - gppb(sesskey, "AltF4", true, conf, CONF_alt_f4); - gppb(sesskey, "AltSpace", false, conf, CONF_alt_space); - gppb(sesskey, "AltOnly", false, conf, CONF_alt_only); - gppb(sesskey, "ComposeKey", false, conf, CONF_compose_key); - gppb(sesskey, "CtrlAltKeys", true, conf, CONF_ctrlaltkeys); #ifdef OSX_META_KEY_CONFIG gppb(sesskey, "OSXOptionMeta", true, conf, CONF_osx_option_meta); gppb(sesskey, "OSXCommandMeta", false, conf, CONF_osx_command_meta); #endif - gppb(sesskey, "TelnetKey", false, conf, CONF_telnet_keyboard); - gppb(sesskey, "TelnetRet", true, conf, CONF_telnet_newline); - gppi(sesskey, "LocalEcho", AUTO, conf, CONF_localecho); - gppi(sesskey, "LocalEdit", AUTO, conf, CONF_localedit); - gpps(sesskey, "Answerback", "PuTTY", conf, CONF_answerback); - gppb(sesskey, "AlwaysOnTop", false, conf, CONF_alwaysontop); - gppb(sesskey, "FullScreenOnAltEnter", false, - conf, CONF_fullscreenonaltenter); - gppb(sesskey, "HideMousePtr", false, conf, CONF_hide_mouseptr); - gppb(sesskey, "SunkenEdge", false, conf, CONF_sunken_edge); - gppi(sesskey, "WindowBorder", 1, conf, CONF_window_border); - gppi(sesskey, "CurType", 0, conf, CONF_cursor_type); - gppb(sesskey, "BlinkCur", false, conf, CONF_blink_cur); - /* pedantic compiler tells me I can't use conf, CONF_beep as an int * :-) */ - gppi(sesskey, "Beep", 1, conf, CONF_beep); - gppi(sesskey, "BeepInd", 0, conf, CONF_beep_ind); - gppfile(sesskey, "BellWaveFile", conf, CONF_bell_wavefile); - gppb(sesskey, "BellOverload", true, conf, CONF_bellovl); - gppi(sesskey, "BellOverloadN", 5, conf, CONF_bellovl_n); i = gppi_raw(sesskey, "BellOverloadT", 2*TICKSPERSEC #ifdef PUTTY_UNIX_PLATFORM_H *1000 @@ -1139,26 +979,6 @@ void load_open_settings(settings_r *sesskey, Conf *conf) / 1000 #endif ); - gppi(sesskey, "ScrollbackLines", 9999, conf, CONF_savelines); - gppb(sesskey, "DECOriginMode", false, conf, CONF_dec_om); - gppb(sesskey, "AutoWrapMode", true, conf, CONF_wrap_mode); - gppb(sesskey, "LFImpliesCR", false, conf, CONF_lfhascr); - gppb(sesskey, "CRImpliesLF", false, conf, CONF_crhaslf); - gppb(sesskey, "DisableArabicShaping", false, conf, CONF_no_arabicshaping); - gppb(sesskey, "DisableBidi", false, conf, CONF_no_bidi); - gppb(sesskey, "WinNameAlways", true, conf, CONF_win_name_always); - gpps(sesskey, "WinTitle", "", conf, CONF_wintitle); - gppi(sesskey, "TermWidth", 80, conf, CONF_width); - gppi(sesskey, "TermHeight", 24, conf, CONF_height); - gppfont(sesskey, "Font", conf, CONF_font); - gppi(sesskey, "FontQuality", FQ_DEFAULT, conf, CONF_font_quality); - gppi(sesskey, "FontVTMode", VT_UNICODE, conf, CONF_vtmode); - gppb(sesskey, "UseSystemColours", false, conf, CONF_system_colour); - gppb(sesskey, "TryPalette", false, conf, CONF_try_palette); - gppb(sesskey, "ANSIColour", true, conf, CONF_ansi_colour); - gppb(sesskey, "Xterm256Colour", true, conf, CONF_xterm_256_colour); - gppb(sesskey, "TrueColour", true, conf, CONF_true_colour); - i = gppi_raw(sesskey, "BoldAsColour", 1); conf_set_int(conf, CONF_bold_style, i+1); for (i = 0; i < 22; i++) { static const char *const defaults[] = { @@ -1179,13 +999,6 @@ void load_open_settings(settings_r *sesskey, Conf *conf) } sfree(buf2); } - gppb(sesskey, "RawCNP", false, conf, CONF_rawcnp); - gppb(sesskey, "UTF8linedraw", false, conf, CONF_utf8linedraw); - gppb(sesskey, "PasteRTF", false, conf, CONF_rtf_paste); - gppi(sesskey, "MouseIsXterm", 0, conf, CONF_mouse_is_xterm); - gppb(sesskey, "RectSelect", false, conf, CONF_rect_select); - gppb(sesskey, "PasteControls", false, conf, CONF_paste_controls); - gppb(sesskey, "MouseOverride", true, conf, CONF_mouse_override); for (i = 0; i < 256; i += 32) { static const char *const defaults[] = { "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0", @@ -1212,8 +1025,6 @@ void load_open_settings(settings_r *sesskey, Conf *conf) } sfree(buf2); } - gppb(sesskey, "MouseAutocopy", CLIPUI_DEFAULT_AUTOCOPY, - conf, CONF_mouseautocopy); read_clip_setting(sesskey, "MousePaste", CLIPUI_DEFAULT_MOUSE, conf, CONF_mousepaste, CONF_mousepaste_custom); read_clip_setting(sesskey, "CtrlShiftIns", CLIPUI_DEFAULT_INS, @@ -1224,32 +1035,8 @@ void load_open_settings(settings_r *sesskey, Conf *conf) * The empty default for LineCodePage will be converted later * into a plausible default for the locale. */ - gpps(sesskey, "LineCodePage", "", conf, CONF_line_codepage); - gppb(sesskey, "CJKAmbigWide", false, conf, CONF_cjk_ambig_wide); - gppb(sesskey, "UTF8Override", true, conf, CONF_utf8_override); - gpps(sesskey, "Printer", "", conf, CONF_printer); - gppb(sesskey, "CapsLockCyr", false, conf, CONF_xlat_capslockcyr); - gppb(sesskey, "ScrollBar", true, conf, CONF_scrollbar); - gppb(sesskey, "ScrollBarFullScreen", false, - conf, CONF_scrollbar_in_fullscreen); - gppb(sesskey, "ScrollOnKey", false, conf, CONF_scroll_on_key); - gppb(sesskey, "ScrollOnDisp", true, conf, CONF_scroll_on_disp); - gppb(sesskey, "EraseToScrollback", true, conf, CONF_erase_to_scrollback); - gppi(sesskey, "LockSize", 0, conf, CONF_resize_action); - gppb(sesskey, "BCE", true, conf, CONF_bce); - gppb(sesskey, "BlinkText", false, conf, CONF_blinktext); - gppb(sesskey, "X11Forward", false, conf, CONF_x11_forward); - gpps(sesskey, "X11Display", "", conf, CONF_x11_display); - gppi(sesskey, "X11AuthType", X11_MIT, conf, CONF_x11_auth); - gppfile(sesskey, "X11AuthFile", conf, CONF_xauthfile); - - gppb(sesskey, "LocalPortAcceptAll", false, conf, CONF_lport_acceptall); - gppb(sesskey, "RemotePortAcceptAll", false, conf, CONF_rport_acceptall); + gppmap(sesskey, "PortForwardings", conf, CONF_portfwd); - i = gppi_raw(sesskey, "BugIgnore1", 0); conf_set_int(conf, CONF_sshbug_ignore1, 2-i); - i = gppi_raw(sesskey, "BugPlainPW1", 0); conf_set_int(conf, CONF_sshbug_plainpw1, 2-i); - i = gppi_raw(sesskey, "BugRSA1", 0); conf_set_int(conf, CONF_sshbug_rsa1, 2-i); - i = gppi_raw(sesskey, "BugIgnore2", 0); conf_set_int(conf, CONF_sshbug_ignore2, 2-i); { int i; i = gppi_raw(sesskey, "BugHMAC2", 0); conf_set_int(conf, CONF_sshbug_hmac2, 2-i); @@ -1259,48 +1046,7 @@ void load_open_settings(settings_r *sesskey, Conf *conf) conf_set_int(conf, CONF_sshbug_hmac2, FORCE_ON); } } - i = gppi_raw(sesskey, "BugDeriveKey2", 0); conf_set_int(conf, CONF_sshbug_derivekey2, 2-i); - i = gppi_raw(sesskey, "BugRSAPad2", 0); conf_set_int(conf, CONF_sshbug_rsapad2, 2-i); - i = gppi_raw(sesskey, "BugPKSessID2", 0); conf_set_int(conf, CONF_sshbug_pksessid2, 2-i); - i = gppi_raw(sesskey, "BugRekey2", 0); conf_set_int(conf, CONF_sshbug_rekey2, 2-i); - i = gppi_raw(sesskey, "BugMaxPkt2", 0); conf_set_int(conf, CONF_sshbug_maxpkt2, 2-i); - i = gppi_raw(sesskey, "BugOldGex2", 0); conf_set_int(conf, CONF_sshbug_oldgex2, 2-i); - i = gppi_raw(sesskey, "BugWinadj", 0); conf_set_int(conf, CONF_sshbug_winadj, 2-i); - i = gppi_raw(sesskey, "BugChanReq", 0); conf_set_int(conf, CONF_sshbug_chanreq, 2-i); - i = gppi_raw(sesskey, "BugRSASHA2CertUserauth", 0); conf_set_int(conf, CONF_sshbug_rsa_sha2_cert_userauth, 2-i); - i = gppi_raw(sesskey, "BugDropStart", 1); conf_set_int(conf, CONF_sshbug_dropstart, 2-i); - i = gppi_raw(sesskey, "BugFilterKexinit", 1); conf_set_int(conf, CONF_sshbug_filter_kexinit, 2-i); - conf_set_bool(conf, CONF_ssh_simple, false); - gppb(sesskey, "StampUtmp", true, conf, CONF_stamp_utmp); - gppb(sesskey, "LoginShell", true, conf, CONF_login_shell); - gppb(sesskey, "ScrollbarOnLeft", false, conf, CONF_scrollbar_on_left); - gppb(sesskey, "ShadowBold", false, conf, CONF_shadowbold); - gppfont(sesskey, "BoldFont", conf, CONF_boldfont); - gppfont(sesskey, "WideFont", conf, CONF_widefont); - gppfont(sesskey, "WideBoldFont", conf, CONF_wideboldfont); - gppi(sesskey, "ShadowBoldOffset", 1, conf, CONF_shadowboldoffset); - gpps(sesskey, "SerialLine", "", conf, CONF_serline); - gppi(sesskey, "SerialSpeed", 115200, conf, CONF_serspeed); - gppi(sesskey, "SerialDataBits", 8, conf, CONF_serdatabits); - gppi(sesskey, "SerialStopHalfbits", 2, conf, CONF_serstopbits); - gppi(sesskey, "SerialParity", SER_PAR_NONE, conf, CONF_serparity); - gppi(sesskey, "SerialFlowControl", SER_FLOW_NONE, conf, CONF_serflow); - gpps(sesskey, "WindowClass", "", conf, CONF_winclass); - gppb(sesskey, "ConnectionSharing", false, - conf, CONF_ssh_connection_sharing); - gppb(sesskey, "ConnectionSharingUpstream", true, - conf, CONF_ssh_connection_sharing_upstream); - gppb(sesskey, "ConnectionSharingDownstream", true, - conf, CONF_ssh_connection_sharing_downstream); gppmap(sesskey, "SSHManualHostKeys", conf, CONF_ssh_manual_hostkeys); - - /* - * SUPDUP settings - */ - gpps(sesskey, "SUPDUPLocation", "The Internet", conf, CONF_supdup_location); - gppi(sesskey, "SUPDUPCharset", false, conf, CONF_supdup_ascii_set); - gppb(sesskey, "SUPDUPMoreProcessing", false, conf, CONF_supdup_more); - gppb(sesskey, "SUPDUPScrolling", false, conf, CONF_supdup_scroll); } bool do_defaults(const char *session, Conf *conf) diff --git a/sign.sh b/sign.sh index 06e1c17f..7e0dbe9e 100755 --- a/sign.sh +++ b/sign.sh @@ -38,7 +38,7 @@ sign() { # through. echo "----- Signing $2 with key '$keyname'" test -f "$3" || \ - gpg --load-extension=idea "$1" -u "$keyname" -o "$3" "$2" + gpg "$1" -u "$keyname" -o "$3" "$2" } cd "$1" diff --git a/specials.h b/specials.h new file mode 100644 index 00000000..007aa31e --- /dev/null +++ b/specials.h @@ -0,0 +1,52 @@ +/* + * Commands that are generally useful in multiple backends. + */ +SPECIAL(BRK) /* serial-line break */ +SPECIAL(EOF) /* end-of-file on session input */ +SPECIAL(NOP) /* transmit data with no effect */ +SPECIAL(PING) /* try to keep the session alive (probably, but not + * necessarily, implemented as SS_NOP) */ + +/* + * Commands specific to Telnet. + */ +SPECIAL(AYT) /* Are You There */ +SPECIAL(SYNCH) /* Synch */ +SPECIAL(EC) /* Erase Character */ +SPECIAL(EL) /* Erase Line */ +SPECIAL(GA) /* Go Ahead */ +SPECIAL(ABORT) /* Abort Process */ +SPECIAL(AO) /* Abort Output */ +SPECIAL(IP) /* Interrupt Process */ +SPECIAL(SUSP) /* Suspend Process */ +SPECIAL(EOR) /* End Of Record */ +SPECIAL(EOL) /* Telnet end-of-line sequence (CRLF, as opposed to + * CR NUL that escapes a literal CR) */ + +/* + * Commands specific to SSH. + */ +SPECIAL(REKEY) /* trigger an immediate repeat key exchange */ +SPECIAL(XCERT) /* cross-certify another host key ('arg' indicates which) */ + +/* + * Send a POSIX-style signal. (Useful in SSH and also pterm.) + * + * We use the master list in ssh/signal-list.h to define these enum + * values, which will come out looking like names of the form + * SS_SIGABRT, SS_SIGINT etc. + */ +#define SIGNAL_MAIN(name, text) SPECIAL(SIG ## name) +#define SIGNAL_SUB(name) SPECIAL(SIG ## name) +#include "ssh/signal-list.h" +#undef SIGNAL_MAIN +#undef SIGNAL_SUB + +/* + * These aren't really special commands, but they appear in the + * enumeration because the list returned from backend_get_specials() + * will use them to specify the structure of the GUI specials menu. + */ +SPECIAL(SEP) /* Separator */ +SPECIAL(SUBMENU) /* Start a new submenu with specified name */ +SPECIAL(EXITMENU) /* Exit current submenu, or end of entire specials list */ diff --git a/ssh.h b/ssh.h index 94d68400..46356f0c 100644 --- a/ssh.h +++ b/ssh.h @@ -225,7 +225,7 @@ struct ConnectionLayerVtable { * PortFwdManager */ SshChannel *(*lportfwd_open)( ConnectionLayer *cl, const char *hostname, int port, - const char *description, const SocketPeerInfo *peerinfo, + const char *description, const SocketEndpointInfo *peerinfo, Channel *chan); /* Initiate opening of a 'session'-type channel */ @@ -234,7 +234,7 @@ struct ConnectionLayerVtable { /* Open outgoing channels for X and agent forwarding. (Used in the * SSH server.) */ SshChannel *(*serverside_x11_open)(ConnectionLayer *cl, Channel *chan, - const SocketPeerInfo *pi); + const SocketEndpointInfo *pi); SshChannel *(*serverside_agent_open)(ConnectionLayer *cl, Channel *chan); /* Add an X11 display for ordinary X forwarding */ @@ -324,12 +324,12 @@ static inline void ssh_rportfwd_remove( { cl->vt->rportfwd_remove(cl, rpf); } static inline SshChannel *ssh_lportfwd_open( ConnectionLayer *cl, const char *host, int port, - const char *desc, const SocketPeerInfo *pi, Channel *chan) + const char *desc, const SocketEndpointInfo *pi, Channel *chan) { return cl->vt->lportfwd_open(cl, host, port, desc, pi, chan); } static inline SshChannel *ssh_session_open(ConnectionLayer *cl, Channel *chan) { return cl->vt->session_open(cl, chan); } static inline SshChannel *ssh_serverside_x11_open( - ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi) + ConnectionLayer *cl, Channel *chan, const SocketEndpointInfo *pi) { return cl->vt->serverside_x11_open(cl, chan, pi); } static inline SshChannel *ssh_serverside_agent_open( ConnectionLayer *cl, Channel *chan) @@ -1383,7 +1383,7 @@ char *platform_get_x_display(void); * calling this function to do the rest of the work. */ void x11_get_auth_from_authfile(struct X11Display *display, - const char *authfilename); + Filename *authfilename); void x11_format_auth_for_authfile( BinarySink *bs, SockAddr *addr, int display_no, ptrlen authproto, ptrlen authdata); diff --git a/ssh/connection1-client.c b/ssh/connection1-client.c index 41bf9716..e7e6f677 100644 --- a/ssh/connection1-client.c +++ b/ssh/connection1-client.c @@ -531,7 +531,7 @@ struct ssh_rportfwd *ssh1_rportfwd_alloc( } SshChannel *ssh1_serverside_x11_open( - ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi) + ConnectionLayer *cl, Channel *chan, const SocketEndpointInfo *pi) { unreachable("Should never be called in the client"); } diff --git a/ssh/connection1-server.c b/ssh/connection1-server.c index cc69bdb3..a2ccf773 100644 --- a/ssh/connection1-server.c +++ b/ssh/connection1-server.c @@ -315,7 +315,7 @@ static void ssh1sesschan_send_exit_signal( } SshChannel *ssh1_serverside_x11_open( - ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi) + ConnectionLayer *cl, Channel *chan, const SocketEndpointInfo *pi) { struct ssh1_connection_state *s = container_of(cl, struct ssh1_connection_state, cl); diff --git a/ssh/connection1.c b/ssh/connection1.c index 7204450a..0156bca1 100644 --- a/ssh/connection1.c +++ b/ssh/connection1.c @@ -48,7 +48,7 @@ static void ssh1_rportfwd_remove( ConnectionLayer *cl, struct ssh_rportfwd *rpf); static SshChannel *ssh1_lportfwd_open( ConnectionLayer *cl, const char *hostname, int port, - const char *description, const SocketPeerInfo *pi, Channel *chan); + const char *description, const SocketEndpointInfo *pi, Channel *chan); static struct X11FakeAuth *ssh1_add_x11_display( ConnectionLayer *cl, int authtype, struct X11Display *disp); static bool ssh1_agent_forwarding_permitted(ConnectionLayer *cl); @@ -641,7 +641,7 @@ static struct X11FakeAuth *ssh1_add_x11_display( static SshChannel *ssh1_lportfwd_open( ConnectionLayer *cl, const char *hostname, int port, - const char *description, const SocketPeerInfo *pi, Channel *chan) + const char *description, const SocketEndpointInfo *pi, Channel *chan) { struct ssh1_connection_state *s = container_of(cl, struct ssh1_connection_state, cl); diff --git a/ssh/connection1.h b/ssh/connection1.h index ff370131..696aa27f 100644 --- a/ssh/connection1.h +++ b/ssh/connection1.h @@ -111,7 +111,7 @@ struct ssh_rportfwd *ssh1_rportfwd_alloc( int addressfamily, const char *log_description, PortFwdRecord *pfr, ssh_sharing_connstate *share_ctx); SshChannel *ssh1_serverside_x11_open( - ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi); + ConnectionLayer *cl, Channel *chan, const SocketEndpointInfo *pi); SshChannel *ssh1_serverside_agent_open(ConnectionLayer *cl, Channel *chan); void ssh1_connection_direction_specific_setup( diff --git a/ssh/connection2-client.c b/ssh/connection2-client.c index f17d1e21..e8e0c137 100644 --- a/ssh/connection2-client.c +++ b/ssh/connection2-client.c @@ -156,7 +156,7 @@ bool ssh2_connection_parse_global_request( PktOut *ssh2_portfwd_chanopen( struct ssh2_connection_state *s, struct ssh2_channel *c, const char *hostname, int port, - const char *description, const SocketPeerInfo *peerinfo) + const char *description, const SocketEndpointInfo *peerinfo) { PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ PktOut *pktout; @@ -321,7 +321,7 @@ SshChannel *ssh2_session_open(ConnectionLayer *cl, Channel *chan) } SshChannel *ssh2_serverside_x11_open( - ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi) + ConnectionLayer *cl, Channel *chan, const SocketEndpointInfo *pi) { unreachable("Should never be called in the client"); } diff --git a/ssh/connection2-server.c b/ssh/connection2-server.c index c871b4b3..20966553 100644 --- a/ssh/connection2-server.c +++ b/ssh/connection2-server.c @@ -109,7 +109,7 @@ bool ssh2_connection_parse_global_request( PktOut *ssh2_portfwd_chanopen( struct ssh2_connection_state *s, struct ssh2_channel *c, const char *hostname, int port, - const char *description, const SocketPeerInfo *pi) + const char *description, const SocketEndpointInfo *pi) { PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ PktOut *pktout; @@ -158,7 +158,7 @@ SshChannel *ssh2_session_open(ConnectionLayer *cl, Channel *chan) } SshChannel *ssh2_serverside_x11_open( - ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi) + ConnectionLayer *cl, Channel *chan, const SocketEndpointInfo *pi) { struct ssh2_connection_state *s = container_of(cl, struct ssh2_connection_state, cl); diff --git a/ssh/connection2.c b/ssh/connection2.c index 49e91e31..ee304908 100644 --- a/ssh/connection2.c +++ b/ssh/connection2.c @@ -33,7 +33,7 @@ static const PacketProtocolLayerVtable ssh2_connection_vtable = { static SshChannel *ssh2_lportfwd_open( ConnectionLayer *cl, const char *hostname, int port, - const char *description, const SocketPeerInfo *pi, Channel *chan); + const char *description, const SocketEndpointInfo *pi, Channel *chan); static struct X11FakeAuth *ssh2_add_x11_display( ConnectionLayer *cl, int authtype, struct X11Display *x11disp); static struct X11FakeAuth *ssh2_add_sharing_x11_display( @@ -1461,7 +1461,7 @@ static void ssh2channel_hint_channel_is_simple(SshChannel *sc) static SshChannel *ssh2_lportfwd_open( ConnectionLayer *cl, const char *hostname, int port, - const char *description, const SocketPeerInfo *pi, Channel *chan) + const char *description, const SocketEndpointInfo *pi, Channel *chan) { struct ssh2_connection_state *s = container_of(cl, struct ssh2_connection_state, cl); diff --git a/ssh/connection2.h b/ssh/connection2.h index 54c3ebf9..941d350e 100644 --- a/ssh/connection2.h +++ b/ssh/connection2.h @@ -159,7 +159,7 @@ PktOut *ssh2_chanopen_init(struct ssh2_channel *c, const char *type); PktOut *ssh2_portfwd_chanopen( struct ssh2_connection_state *s, struct ssh2_channel *c, const char *hostname, int port, - const char *description, const SocketPeerInfo *peerinfo); + const char *description, const SocketEndpointInfo *peerinfo); struct ssh_rportfwd *ssh2_rportfwd_alloc( ConnectionLayer *cl, @@ -170,7 +170,7 @@ void ssh2_rportfwd_remove( ConnectionLayer *cl, struct ssh_rportfwd *rpf); SshChannel *ssh2_session_open(ConnectionLayer *cl, Channel *chan); SshChannel *ssh2_serverside_x11_open( - ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi); + ConnectionLayer *cl, Channel *chan, const SocketEndpointInfo *pi); SshChannel *ssh2_serverside_agent_open(ConnectionLayer *cl, Channel *chan); void ssh2channel_send_exit_status(SshChannel *c, int status); diff --git a/ssh/crc-attack-detector.c b/ssh/crc-attack-detector.c index 18044754..58e4e99b 100644 --- a/ssh/crc-attack-detector.c +++ b/ssh/crc-attack-detector.c @@ -54,10 +54,10 @@ struct crcda_ctx { struct crcda_ctx *crcda_make_context(void) { - struct crcda_ctx *ret = snew(struct crcda_ctx); - ret->h = NULL; - ret->n = HASH_MINSIZE / HASH_ENTRYSIZE; - return ret; + struct crcda_ctx *ctx = snew(struct crcda_ctx); + ctx->h = NULL; + ctx->n = HASH_MINSIZE / HASH_ENTRYSIZE; + return ctx; } void crcda_free_context(struct crcda_ctx *ctx) diff --git a/ssh/kex2-client.c b/ssh/kex2-client.c index 26159bb5..d5425237 100644 --- a/ssh/kex2-client.c +++ b/ssh/kex2-client.c @@ -1021,7 +1021,7 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) ppl_logevent("%s", fingerprint); sfree(fingerprint); - store_host_key(s->savedhost, s->savedport, + store_host_key(s->ppl.seat, s->savedhost, s->savedport, ssh_key_cache_id(s->hkey), s->keystr); /* * Don't forget to store the new key as the one we'll be diff --git a/ssh/mainchan.c b/ssh/mainchan.c index e564330d..5612d446 100644 --- a/ssh/mainchan.c +++ b/ssh/mainchan.c @@ -182,7 +182,9 @@ static void mainchan_open_confirmation(Channel *chan) if (mc->n_req_env) ppl_logevent("Sent %d environment variables", mc->n_req_env); - cmd = conf_get_str(mc->conf, CONF_remote_cmd); + /* Ignore encoding of CONF_remote_cmd so as not to disturb + * legacy handling of non-UTF-8 commands */ + cmd = conf_get_str_ambi(mc->conf, CONF_remote_cmd, NULL); if (conf_get_bool(mc->conf, CONF_ssh_subsys)) { retry_cmd_now = !sshfwd_start_subsystem(mc->sc, true, cmd); } else if (*cmd) { @@ -205,7 +207,9 @@ static void mainchan_open_confirmation(Channel *chan) static void mainchan_try_fallback_command(mainchan *mc) { - const char *cmd = conf_get_str(mc->conf, CONF_remote_cmd2); + /* Ignore encoding of CONF_remote_cmd2 so as not to disturb legacy + * handling of non-UTF-8 commands */ + const char *cmd = conf_get_str_ambi(mc->conf, CONF_remote_cmd2, NULL); if (conf_get_bool(mc->conf, CONF_ssh_subsys2)) { sshfwd_start_subsystem(mc->sc, true, cmd); } else { @@ -288,7 +292,7 @@ static void mainchan_request_response(Channel *chan, bool success) if (success) { ppl_logevent("Started a shell/command"); mainchan_ready(mc); - } else if (*conf_get_str(mc->conf, CONF_remote_cmd2)) { + } else if (*conf_get_str_ambi(mc->conf, CONF_remote_cmd2, NULL)) { ppl_logevent("Primary command failed; attempting fallback"); mainchan_try_fallback_command(mc); } else { diff --git a/ssh/portfwd.c b/ssh/portfwd.c index b4eea3c9..bac20e05 100644 --- a/ssh/portfwd.c +++ b/ssh/portfwd.c @@ -96,18 +96,6 @@ static void free_portlistener_state(struct PortListener *pl) sfree(pl); } -static void pfd_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, - const char *error_msg, int error_code) -{ - /* we have to dump these since we have no interface to logging.c */ -} - -static void pfl_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, - const char *error_msg, int error_code) -{ - /* we have to dump these since we have no interface to logging.c */ -} - static void pfd_close(struct PortForwarding *pf); static void pfd_closing(Plug *plug, PlugCloseType type, const char *error_msg) @@ -152,7 +140,7 @@ static SshChannel *wrap_lportfwd_open( ConnectionLayer *cl, const char *hostname, int port, Socket *s, Channel *chan) { - SocketPeerInfo *pi; + SocketEndpointInfo *pi; char *description; SshChannel *toret; @@ -163,7 +151,7 @@ static SshChannel *wrap_lportfwd_open( description = dupstr("forwarding"); } toret = ssh_lportfwd_open(cl, hostname, port, description, pi, chan); - sk_free_peer_info(pi); + sk_free_endpoint_info(pi); sfree(description); return toret; @@ -431,7 +419,7 @@ static void pfd_sent(Plug *plug, size_t bufsize) } static const PlugVtable PortForwarding_plugvt = { - .log = pfd_log, + .log = nullplug_log, .closing = pfd_closing, .receive = pfd_receive, .sent = pfd_sent, @@ -554,7 +542,7 @@ static int pfl_accepting(Plug *p, accept_fn_t constructor, accept_ctx_t ctx) } static const PlugVtable PortListener_plugvt = { - .log = pfl_log, + .log = nullplug_log, .closing = pfl_closing, .accepting = pfl_accepting, }; diff --git a/ssh/scpserver.c b/ssh/scpserver.c index 15633ecb..0995c998 100644 --- a/ssh/scpserver.c +++ b/ssh/scpserver.c @@ -799,20 +799,17 @@ static void scp_source_process_stack(ScpSource *scp) scp->head = node; /* put back the unfinished READDIR */ node = NULL; /* and prevent it being freed */ } else { - ptrlen subpath; - subpath.len = node->pathname.len + 1 + scp->reply.name.len; - char *subpath_space = snewn(subpath.len, char); - subpath.ptr = subpath_space; - memcpy(subpath_space, node->pathname.ptr, node->pathname.len); - subpath_space[node->pathname.len] = '/'; - memcpy(subpath_space + node->pathname.len + 1, - scp->reply.name.ptr, scp->reply.name.len); + strbuf *subpath = strbuf_new(); + put_datapl(subpath, node->pathname); + put_byte(subpath, '/'); + put_datapl(subpath, scp->reply.name); scp->head = node; /* put back the unfinished READDIR */ node = NULL; /* and prevent it being freed */ - scp_source_push_name(scp, subpath, scp->reply.attrs, NULL); + scp_source_push_name(scp, ptrlen_from_strbuf(subpath), + scp->reply.attrs, NULL); - sfree(subpath_space); + strbuf_free(subpath); } } else if (node->attrs.permissions & PERMS_DIRECTORY) { assert(scp->recursive || node->wildcard); diff --git a/ssh/server.c b/ssh/server.c index a0aa277d..97fc5841 100644 --- a/ssh/server.c +++ b/ssh/server.c @@ -116,6 +116,7 @@ static const SeatVtable server_seat_vt = { .notify_remote_exit = nullseat_notify_remote_exit, .notify_remote_disconnect = nullseat_notify_remote_disconnect, .connection_fatal = nullseat_connection_fatal, + .nonfatal = nullseat_nonfatal, .update_specials_menu = nullseat_update_specials_menu, .get_ttymode = nullseat_get_ttymode, .set_busy_status = nullseat_set_busy_status, @@ -137,13 +138,6 @@ static const SeatVtable server_seat_vt = { .get_cursor_position = nullseat_get_cursor_position, }; -static void server_socket_log(Plug *plug, PlugLogType type, SockAddr *addr, - int port, const char *error_msg, int error_code) -{ - /* server *srv = container_of(plug, server, plug); */ - /* FIXME */ -} - static void server_closing(Plug *plug, PlugCloseType type, const char *error_msg) { @@ -252,7 +246,7 @@ Conf *make_ssh_server_conf(void) void ssh_check_sendok(Ssh *ssh) {} static const PlugVtable ssh_server_plugvt = { - .log = server_socket_log, + .log = nullplug_log, .closing = server_closing, .receive = server_receive, .sent = server_sent, @@ -501,6 +495,7 @@ void ssh_sw_abort(Ssh *ssh, const char *fmt, ...) void ssh_user_close(Ssh *ssh, const char *fmt, ...) { server *srv = container_of(ssh, server, ssh); + ssh_bpp_handle_output(srv->bpp); LOG_FORMATTED_MSG(srv->logctx, fmt); queue_toplevel_callback(ssh_server_free_callback, srv); } diff --git a/ssh/server.h b/ssh/server.h index c2b3647b..dc73747c 100644 --- a/ssh/server.h +++ b/ssh/server.h @@ -23,6 +23,7 @@ struct SshServerConfig { bool stunt_open_unconditional_agent_socket; bool stunt_allow_trivial_ki_auth; bool stunt_return_success_to_pubkey_offer; + bool stunt_close_after_banner; }; Plug *ssh_server_plug( diff --git a/ssh/sesschan.c b/ssh/sesschan.c index cb5f0af1..cc50fd5c 100644 --- a/ssh/sesschan.c +++ b/ssh/sesschan.c @@ -193,6 +193,7 @@ static const SeatVtable sesschan_seat_vt = { .notify_remote_exit = sesschan_notify_remote_exit, .notify_remote_disconnect = nullseat_notify_remote_disconnect, .connection_fatal = sesschan_connection_fatal, + .nonfatal = nullseat_nonfatal, .update_specials_menu = nullseat_update_specials_menu, .get_ttymode = nullseat_get_ttymode, .set_busy_status = nullseat_set_busy_status, @@ -370,19 +371,13 @@ bool sesschan_run_subsystem(Channel *chan, ptrlen subsys) return false; } -static void fwd_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, - const char *error_msg, int error_code) -{ /* don't expect any weirdnesses from a listening socket */ } -static void fwd_closing(Plug *plug, PlugCloseType type, const char *error_msg) -{ /* not here, either */ } - static int xfwd_accepting(Plug *p, accept_fn_t constructor, accept_ctx_t ctx) { sesschan *sess = container_of(p, sesschan, xfwd_plug); Plug *plug; Channel *chan; Socket *s; - SocketPeerInfo *pi; + SocketEndpointInfo *pi; const char *err; chan = portfwd_raw_new(sess->c->cl, &plug, false); @@ -393,14 +388,14 @@ static int xfwd_accepting(Plug *p, accept_fn_t constructor, accept_ctx_t ctx) } pi = sk_peer_info(s); portfwd_raw_setup(chan, s, ssh_serverside_x11_open(sess->c->cl, chan, pi)); - sk_free_peer_info(pi); + sk_free_endpoint_info(pi); return 0; } static const PlugVtable xfwd_plugvt = { - .log = fwd_log, - .closing = fwd_closing, + .log = nullplug_log, + .closing = nullplug_closing, .accepting = xfwd_accepting, }; @@ -472,8 +467,8 @@ static int agentfwd_accepting( } static const PlugVtable agentfwd_plugvt = { - .log = fwd_log, - .closing = fwd_closing, + .log = nullplug_log, + .closing = nullplug_closing, .accepting = agentfwd_accepting, }; diff --git a/ssh/sftp.c b/ssh/sftp.c index 1f98c5ec..09d173bb 100644 --- a/ssh/sftp.c +++ b/ssh/sftp.c @@ -736,7 +736,7 @@ struct fxp_names *fxp_readdir_recv(struct sftp_packet *pktin, { sfree(req); if (pktin->type == SSH_FXP_NAME) { - struct fxp_names *ret; + struct fxp_names *names; unsigned long i; i = get_uint32(pktin); @@ -766,28 +766,28 @@ struct fxp_names *fxp_readdir_recv(struct sftp_packet *pktin, return NULL; } - ret = snew(struct fxp_names); - ret->nnames = i; - ret->names = snewn(ret->nnames, struct fxp_name); - for (i = 0; i < (unsigned long)ret->nnames; i++) { - ret->names[i].filename = mkstr(get_string(pktin)); - ret->names[i].longname = mkstr(get_string(pktin)); - get_fxp_attrs(pktin, &ret->names[i].attrs); + names = snew(struct fxp_names); + names->nnames = i; + names->names = snewn(names->nnames, struct fxp_name); + for (i = 0; i < (unsigned long)names->nnames; i++) { + names->names[i].filename = mkstr(get_string(pktin)); + names->names[i].longname = mkstr(get_string(pktin)); + get_fxp_attrs(pktin, &names->names[i].attrs); } if (get_err(pktin)) { fxp_internal_error("malformed FXP_NAME packet"); - for (i = 0; i < (unsigned long)ret->nnames; i++) { - sfree(ret->names[i].filename); - sfree(ret->names[i].longname); + for (i = 0; i < (unsigned long)names->nnames; i++) { + sfree(names->names[i].filename); + sfree(names->names[i].longname); } - sfree(ret->names); - sfree(ret); + sfree(names->names); + sfree(names); sfree(pktin); return NULL; } sftp_pkt_free(pktin); - return ret; + return names; } else { fxp_got_status(pktin); sftp_pkt_free(pktin); @@ -840,14 +840,14 @@ void fxp_free_names(struct fxp_names *names) /* * Duplicate an fxp_name structure. */ -struct fxp_name *fxp_dup_name(struct fxp_name *name) +struct fxp_name *fxp_dup_name(struct fxp_name *orig) { - struct fxp_name *ret; - ret = snew(struct fxp_name); - ret->filename = dupstr(name->filename); - ret->longname = dupstr(name->longname); - ret->attrs = name->attrs; /* structure copy */ - return ret; + struct fxp_name *copy; + copy = snew(struct fxp_name); + copy->filename = dupstr(orig->filename); + copy->longname = dupstr(orig->longname); + copy->attrs = orig->attrs; /* structure copy */ + return copy; } /* diff --git a/ssh/sharing.c b/ssh/sharing.c index 7d338a3b..2df5894c 100644 --- a/ssh/sharing.c +++ b/ssh/sharing.c @@ -1742,7 +1742,7 @@ static void share_got_pkt_from_downstream(struct ssh_sharing_connstate *cs, #define crGetChar(c) do \ { \ while (len == 0) { \ - *crLine =__LINE__; return; case __LINE__:; \ + *crLine = __LINE__; return; case __LINE__:; \ } \ len--; \ (c) = (unsigned char)*data++; \ @@ -1909,7 +1909,7 @@ static int share_listen_accepting(Plug *plug, plug, struct ssh_sharing_state, plug); struct ssh_sharing_connstate *cs; const char *err; - SocketPeerInfo *peerinfo; + SocketEndpointInfo *peerinfo; /* * A new downstream has connected to us. @@ -1956,7 +1956,7 @@ static int share_listen_accepting(Plug *plug, log_downstream(cs, "connected%s%s", (peerinfo && peerinfo->log_text ? " from " : ""), (peerinfo && peerinfo->log_text ? peerinfo->log_text : "")); - sk_free_peer_info(peerinfo); + sk_free_endpoint_info(peerinfo); return 0; } diff --git a/ssh/ssh.c b/ssh/ssh.c index bee3ebdd..8f9bddd7 100644 --- a/ssh/ssh.c +++ b/ssh/ssh.c @@ -600,8 +600,9 @@ void ssh_sw_abort_deferred(Ssh *ssh, const char *fmt, ...) } } -static void ssh_socket_log(Plug *plug, PlugLogType type, SockAddr *addr, - int port, const char *error_msg, int error_code) +static void ssh_socket_log(Plug *plug, Socket *s, PlugLogType type, + SockAddr *addr, int port, + const char *error_msg, int error_code) { Ssh *ssh = container_of(plug, Ssh, plug); @@ -615,7 +616,7 @@ static void ssh_socket_log(Plug *plug, PlugLogType type, SockAddr *addr, */ if (!ssh->attempting_connshare) - backend_socket_log(ssh->seat, ssh->logctx, type, addr, port, + backend_socket_log(ssh->seat, ssh->logctx, s, type, addr, port, error_msg, error_code, ssh->conf, ssh->session_started); } @@ -839,10 +840,12 @@ static char *connect_to_host( false, true, nodelay, keepalive, &ssh->plug, ssh->conf, &ssh->interactor); if ((err = sk_socket_error(ssh->s)) != NULL) { + char *toret = dupstr(err); + sk_close(ssh->s); ssh->s = NULL; seat_notify_remote_exit(ssh->seat); seat_notify_remote_disconnect(ssh->seat); - return dupstr(err); + return toret; } } diff --git a/ssh/userauth2-client.c b/ssh/userauth2-client.c index 4d4310f6..17317a44 100644 --- a/ssh/userauth2-client.c +++ b/ssh/userauth2-client.c @@ -318,8 +318,9 @@ static bool ssh2_userauth_signflags(struct ssh2_userauth_state *s, return true; } -static void authplugin_plug_log(Plug *plug, PlugLogType type, SockAddr *addr, - int port, const char *err_msg, int err_code) +static void authplugin_plug_log(Plug *plug, Socket *sock, PlugLogType type, + SockAddr *addr, int port, + const char *err_msg, int err_code) { struct ssh2_userauth_state *s = container_of( plug, struct ssh2_userauth_state, authplugin_plug); @@ -763,6 +764,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) */ } else if ((s->username = s->default_username) == NULL) { s->cur_prompt = ssh_ppl_new_prompts(&s->ppl); + s->cur_prompt->utf8 = true; s->cur_prompt->to_server = true; s->cur_prompt->from_server = false; s->cur_prompt->name = dupstr("SSH login name"); @@ -1799,6 +1801,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) s->ppl.bpp->pls->actx = SSH2_PKTCTX_PASSWORD; s->cur_prompt = ssh_ppl_new_prompts(&s->ppl); + s->cur_prompt->utf8 = true; s->cur_prompt->to_server = true; s->cur_prompt->from_server = false; s->cur_prompt->name = dupstr("SSH password"); @@ -1887,6 +1890,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) prompt = get_string(pktin); s->cur_prompt = ssh_ppl_new_prompts(&s->ppl); + s->cur_prompt->utf8 = true; s->cur_prompt->to_server = true; s->cur_prompt->from_server = false; s->cur_prompt->name = dupstr("New SSH password"); @@ -2111,6 +2115,7 @@ static bool ssh2_userauth_ki_setup_prompts( inst = get_string(src); get_string(src); /* skip language tag */ s->cur_prompt = ssh_ppl_new_prompts(&s->ppl); + s->cur_prompt->utf8 = true; s->cur_prompt->to_server = true; s->cur_prompt->from_server = true; diff --git a/ssh/userauth2-server.c b/ssh/userauth2-server.c index 7e231a8e..9d87148d 100644 --- a/ssh/userauth2-server.c +++ b/ssh/userauth2-server.c @@ -114,6 +114,21 @@ static void ssh2_userauth_server_add_session_id( } } +static void ssh2_userauth_server_close_after_banner(void *vctx) +{ + struct ssh2_userauth_server_state *s = + (struct ssh2_userauth_server_state *)vctx; + + if (pq_peek(s->ppl.out_pq)) { + /* Don't close the connection until we've passed on our final banner + * packet to the lower layer */ + queue_toplevel_callback(ssh2_userauth_server_close_after_banner, s); + } else { + ssh_user_close(s->ppl.ssh, "Closing connection on request due to " + "--close-after-banner"); + } +} + static void ssh2_userauth_server_process_queue(PacketProtocolLayer *ppl) { struct ssh2_userauth_server_state *s = @@ -132,6 +147,11 @@ static void ssh2_userauth_server_process_queue(PacketProtocolLayer *ppl) pq_push(s->ppl.out_pq, pktout); } + if (s->ssc->stunt_close_after_banner) { + queue_toplevel_callback(ssh2_userauth_server_close_after_banner, s); + crReturnV; + } + while (1) { crMaybeWaitUntilV((pktin = ssh2_userauth_server_pop(s)) != NULL); if (pktin->type != SSH2_MSG_USERAUTH_REQUEST) { diff --git a/ssh/x11fwd.c b/ssh/x11fwd.c index c0ae59f1..88bdaffe 100644 --- a/ssh/x11fwd.c +++ b/ssh/x11fwd.c @@ -277,12 +277,6 @@ static char *x11_verify(unsigned long peer_ip, int peer_port, return NULL; } -static void x11_log(Plug *p, PlugLogType type, SockAddr *addr, int port, - const char *error_msg, int error_code) -{ - /* We have no interface to the logging module here, so we drop these. */ -} - static void x11_send_init_error(struct X11Connection *conn, const char *err_message); @@ -336,7 +330,7 @@ static void x11_sent(Plug *plug, size_t bufsize) } static const PlugVtable X11Connection_plugvt = { - .log = x11_log, + .log = nullplug_log, .closing = x11_closing, .receive = x11_receive, .sent = x11_sent, diff --git a/sshcr.h b/sshcr.h index 12bfea6c..960c757e 100644 --- a/sshcr.h +++ b/sshcr.h @@ -39,7 +39,7 @@ #define crFinishFreeV } sfree(s); return; } while (0) #define crReturn(z) \ do {\ - *crLine =__LINE__; return (z); case __LINE__:;\ + *crLine = __LINE__; return (z); case __LINE__:;\ } while (0) #define crReturnV \ do {\ @@ -64,22 +64,22 @@ */ #define crMaybeWaitUntil(c) \ do { \ - *crLine =__LINE__; \ + *crLine = __LINE__; \ case __LINE__: if (!(c)) return 0; \ } while (0) #define crMaybeWaitUntilV(c) \ do { \ - *crLine =__LINE__; \ + *crLine = __LINE__; \ case __LINE__: if (!(c)) return; \ } while (0) #define crWaitUntil(c) \ do { \ - *crLine =__LINE__; return; \ + *crLine = __LINE__; return; \ case __LINE__: if (!(c)) return 0; \ } while (0) #define crWaitUntilV(c) \ do { \ - *crLine =__LINE__; return; \ + *crLine = __LINE__; return; \ case __LINE__: if (!(c)) return; \ } while (0) diff --git a/sshpubk.c b/sshpubk.c index 2c9e58a6..eaee4258 100644 --- a/sshpubk.c +++ b/sshpubk.c @@ -705,7 +705,7 @@ ssh2_userkey *ppk_load_s(BinarySource *src, const char *passphrase, { char header[40], *b, *encryption, *comment, *mac; const ssh_keyalg *alg; - ssh2_userkey *ret; + ssh2_userkey *ukey; strbuf *public_blob, *private_blob, *cipher_mac_keys_blob; strbuf *passphrase_salt = strbuf_new(); ptrlen cipherkey, cipheriv, mackey; @@ -716,7 +716,7 @@ ssh2_userkey *ppk_load_s(BinarySource *src, const char *passphrase, const char *error = NULL; ppk_save_parameters params; - ret = NULL; /* return NULL for most errors */ + ukey = NULL; /* return NULL for most errors */ encryption = comment = mac = NULL; public_blob = private_blob = cipher_mac_keys_blob = NULL; @@ -952,10 +952,10 @@ ssh2_userkey *ppk_load_s(BinarySource *src, const char *passphrase, * unencrypted. Otherwise, it means Wrong Passphrase. */ if (ciphertype->keylen != 0) { error = "wrong passphrase"; - ret = SSH2_WRONG_PASSPHRASE; + ukey = SSH2_WRONG_PASSPHRASE; } else { error = "MAC failed"; - ret = NULL; + ukey = NULL; } goto error; } @@ -964,15 +964,15 @@ ssh2_userkey *ppk_load_s(BinarySource *src, const char *passphrase, /* * Create and return the key. */ - ret = snew(ssh2_userkey); - ret->comment = comment; + ukey = snew(ssh2_userkey); + ukey->comment = comment; comment = NULL; - ret->key = ssh_key_new_priv( + ukey->key = ssh_key_new_priv( alg, ptrlen_from_strbuf(public_blob), ptrlen_from_strbuf(private_blob)); - if (!ret->key) { - sfree(ret); - ret = NULL; + if (!ukey->key) { + sfree(ukey); + ukey = NULL; error = "createkey failed"; goto error; } @@ -997,7 +997,7 @@ ssh2_userkey *ppk_load_s(BinarySource *src, const char *passphrase, strbuf_free(passphrase_salt); if (errorstr) *errorstr = error; - return ret; + return ukey; } ssh2_userkey *ppk_load_f(const Filename *filename, const char *passphrase, @@ -1096,6 +1096,8 @@ static bool rfc4716_loadpub(BinarySource *src, char **algorithm, } } sfree(line); line = NULL; + if (!get_avail(src)) + break; line = mkstr(get_chomped_line(src)); } diff --git a/storage.h b/storage.h index e9138f40..09e61368 100644 --- a/storage.h +++ b/storage.h @@ -89,8 +89,10 @@ int check_stored_host_key(const char *hostname, int port, /* * Write a host key into the database, overwriting any previous * entry that might have been there. + * + * A Seat is provided for error-reporting purposes. */ -void store_host_key(const char *hostname, int port, +void store_host_key(Seat *seat, const char *hostname, int port, const char *keytype, const char *key); /* ---------------------------------------------------------------------- diff --git a/stubs/CMakeLists.txt b/stubs/CMakeLists.txt index dc02aca3..0b702853 100644 --- a/stubs/CMakeLists.txt +++ b/stubs/CMakeLists.txt @@ -28,4 +28,5 @@ add_sources_from_current_dir(utils null-mac.c null-opener.c null-plug.c - null-seat.c) + null-seat.c + null-socket.c) diff --git a/stubs/no-agent.c b/stubs/no-agent.c new file mode 100644 index 00000000..d4c755e2 --- /dev/null +++ b/stubs/no-agent.c @@ -0,0 +1,11 @@ +#include "putty.h" + +bool agent_exists(void) { return false; } +Socket *agent_connect(Plug *plug) { + return new_error_socket_fmt( + plug, "no actual networking in this application"); +} +void agent_cancel_query(agent_pending_query *pq) {} +agent_pending_query *agent_query( + strbuf *query, void **out, int *outlen, + void (*callback)(void *, void *, int), void *callback_ctx) {return NULL;} diff --git a/stubs/no-callback.c b/stubs/no-callback.c new file mode 100644 index 00000000..6c12f2b7 --- /dev/null +++ b/stubs/no-callback.c @@ -0,0 +1,33 @@ +/* + * Stub version of the callback.c functions. Doesn't let anyone + * _schedule_ a callback (because that would lead them into the false + * assumption that it would actually happen later on), but permits the + * other functions without error, on the grounds that it's well + * defined what they would do if nobody had scheduled any callbacks. + */ + +#include "putty.h" + +void queue_idempotent_callback(struct IdempotentCallback *ic) +{ + unreachable("callbacks are not supported in this application"); +} + +void delete_callbacks_for_context(void *ctx) +{ +} + +void queue_toplevel_callback(toplevel_callback_fn_t fn, void *ctx) +{ + unreachable("callbacks are not supported in this application"); +} + +bool run_toplevel_callbacks(void) +{ + return false; +} + +bool toplevel_callback_pending(void) +{ + return false; +} diff --git a/stubs/no-console.c b/stubs/no-console.c new file mode 100644 index 00000000..580cfd70 --- /dev/null +++ b/stubs/no-console.c @@ -0,0 +1,15 @@ +/* + * Stub functions for when console.c is not linked into a program. + */ + +#include "putty.h" + +bool console_set_batch_mode(bool newvalue) +{ + return false; +} + +bool console_set_stdio_prompts(bool newvalue) +{ + return false; +} diff --git a/stubs/no-gss.c b/stubs/no-gss.c index 844a1323..dd0adb6d 100644 --- a/stubs/no-gss.c +++ b/stubs/no-gss.c @@ -6,6 +6,25 @@ #include "putty.h" +#include "ssh/pgssapi.h" +#include "ssh/gss.h" +#include "ssh/gssc.h" + const int ngsslibs = 0; const char *const gsslibnames[1] = { "dummy" }; const struct keyvalwhere gsslibkeywords[1] = { { "dummy", 0, -1, -1 } }; + +struct ssh_gss_liblist *ssh_gss_setup(Conf *conf) +{ + struct ssh_gss_liblist *list = snew(struct ssh_gss_liblist); + + list->libraries = NULL; + list->nlibraries = 0; + return list; +} + +void ssh_gss_cleanup(struct ssh_gss_liblist *list) +{ + sfree(list->libraries); /* I know it's always NULL, but stay consistent */ + sfree(list); +} diff --git a/stubs/no-ldisc.c b/stubs/no-ldisc.c new file mode 100644 index 00000000..a3d115ce --- /dev/null +++ b/stubs/no-ldisc.c @@ -0,0 +1,37 @@ +#include "putty.h" + +struct Ldisc_tag { + int dummy; +}; + +Ldisc *ldisc_create(Conf *conf, Terminal *term, Backend *backend, Seat *seat) +{ + Ldisc *ldisc = snew(Ldisc); + memset(ldisc, 0, sizeof(Ldisc)); + return ldisc; +} + +void ldisc_configure(Ldisc *ldisc, Conf *conf) +{ +} + +void ldisc_free(Ldisc *ldisc) +{ + sfree(ldisc); +} + +void ldisc_echoedit_update(Ldisc *ldisc) +{ +} + +void ldisc_provide_userpass_le(Ldisc *ldisc, TermLineEditor *le) +{ +} + +void ldisc_check_sendok(Ldisc *ldisc) +{ +} + +void ldisc_send(Ldisc *ldisc, const void *vbuf, int len, bool interactive) +{ +} diff --git a/stubs/no-lineedit.c b/stubs/no-lineedit.c new file mode 100644 index 00000000..219572d4 --- /dev/null +++ b/stubs/no-lineedit.c @@ -0,0 +1,18 @@ +/* + * Stubs of functions in lineedit.c, for use in programs that don't + * have any use for line editing (e.g. because they don't have a + * terminal either). + */ + +#include "putty.h" +#include "terminal.h" + +TermLineEditor *lineedit_new(Terminal *term, unsigned flags, + TermLineEditorCallbackReceiver *receiver) +{ + return NULL; +} +void lineedit_free(TermLineEditor *le) {} +void lineedit_input(TermLineEditor *le, char ch, bool dedicated) {} +void lineedit_modify_flags(TermLineEditor *le, unsigned clr, unsigned flip) {} +void lineedit_send_line(TermLineEditor *le) {} diff --git a/stubs/no-logging.c b/stubs/no-logging.c new file mode 100644 index 00000000..cb361c08 --- /dev/null +++ b/stubs/no-logging.c @@ -0,0 +1,20 @@ +/* + * Stub module implementing the logging API for tools that don't do + * session logging. + */ + +#include "putty.h" + +void logtraffic(LogContext *ctx, unsigned char c, int logmode) {} +void logflush(LogContext *ctx) {} +void logevent(LogContext *ctx, const char *event) {} +void log_free(LogContext *ctx) {} +void log_reconfig(LogContext *ctx, Conf *conf) {} +void log_packet(LogContext *ctx, int direction, int type, + const char *texttype, const void *data, size_t len, + int n_blanks, const struct logblank_t *blanks, + const unsigned long *seq, + unsigned downstream_id, const char *additional_log_text) {} + +LogContext *log_init(LogPolicy *lp, Conf *conf) +{ return NULL; } diff --git a/stubs/no-network.c b/stubs/no-network.c new file mode 100644 index 00000000..df97438a --- /dev/null +++ b/stubs/no-network.c @@ -0,0 +1,144 @@ +/* + * Stub version of the whole networking abstraction. + */ + +#include "putty.h" +#include "network.h" + +struct SockAddr { + int dummy; +}; + +void sk_init(void) +{ +} + +void sk_cleanup(void) +{ +} + +SockAddr *sk_namelookup(const char *host, char **canonicalname, + int address_family) +{ + return snew(SockAddr); +} +SockAddr *sk_nonamelookup(const char *host) +{ + return snew(SockAddr); +} + +void sk_getaddr(SockAddr *addr, char *buf, int buflen) +{ + strncpy(buf, "nonsense", buflen); +} + +bool sk_addr_needs_port(SockAddr *addr) +{ + return true; +} + +bool sk_hostname_is_local(const char *name) +{ + return false; +} + +bool sk_address_is_local(SockAddr *addr) +{ + return false; +} + +bool sk_address_is_special_local(SockAddr *addr) +{ + return false; +} + +int sk_addrtype(SockAddr *addr) +{ + return ADDRTYPE_UNSPEC; +} + +void sk_addrcopy(SockAddr *addr, char *buf) +{ +} + +void sk_addr_free(SockAddr *addr) +{ + sfree(addr); +} + +SockAddr *sk_addr_dup(SockAddr *addr) +{ + return snew(SockAddr); +} + +Socket *sk_new(SockAddr *addr, int port, bool privport, bool oobinline, + bool nodelay, bool keepalive, Plug *plug) +{ + return new_error_socket_fmt( + plug, "no actual networking in this application"); +} + +Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug, + bool local_host_only, int orig_address_family) +{ + return new_error_socket_fmt( + plug, "no actual networking in this application"); +} + +void *(sk_getxdmdata)(Socket *sock, int *lenp) +{ + return NULL; +} + +void plug_closing_errno(Plug *plug, int error) +{ + plug_closing(plug, PLUGCLOSE_ERROR, "dummy"); +} + +const char *sk_addr_error(SockAddr *addr) +{ + return "no actual network addresses in this application"; +} + +int net_service_lookup(const char *service) +{ + return 0; +} + +char *get_hostname(void) +{ + return dupstr("dummy-hostname"); +} + +SockAddr *platform_get_x11_unix_address(const char *sockpath, int displaynum) +{ + return snew(SockAddr); +} + +SockAddr *unix_sock_addr(const char *path) +{ + return snew(SockAddr); +} + +SockAddr *sk_namedpipe_addr(const char *pipename) +{ + return snew(SockAddr); +} + +Socket *new_unix_listener(SockAddr *listenaddr, Plug *plug) +{ + return new_error_socket_fmt( + plug, "no actual networking in this application"); +} + +Socket *platform_start_subprocess(const char *cmd, Plug *plug, + const char *prefix) +{ + return new_error_socket_fmt( + plug, "no actual networking in this application"); +} + +#ifdef PUTTY_WINDOWS_PLATFORM_H +void plug_closing_system_error(Plug *plug, DWORD error) {} +void plug_closing_winsock_error(Plug *plug, DWORD error) {} +#endif diff --git a/stubs/no-printing.c b/stubs/no-printing.c new file mode 100644 index 00000000..6c8859e7 --- /dev/null +++ b/stubs/no-printing.c @@ -0,0 +1,18 @@ +/* + * Stub module implementing the printing API for tools that don't + * print. + */ + +#include "putty.h" + +printer_job *printer_start_job(char *printer) { return NULL; } +void printer_job_data(printer_job *pj, const void *data, size_t len) {} +void printer_finish_job(printer_job *pj) {} + +printer_enum *printer_start_enum(int *nprinters_ptr) +{ + *nprinters_ptr = 0; + return NULL; +} +char *printer_get_name(printer_enum *pe, int i) { return NULL; } +void printer_finish_enum(printer_enum *pe) {} diff --git a/stubs/no-storage.c b/stubs/no-storage.c new file mode 100644 index 00000000..a8671a15 --- /dev/null +++ b/stubs/no-storage.c @@ -0,0 +1,40 @@ +/* + * Stub module implementing the saved-session storage APIs for tools + * that don't load or save sessions. + */ + +#include "putty.h" + +settings_w *open_settings_w(const char *sessionname, char **errmsg) +{ return NULL; } +void write_setting_s(settings_w *handle, const char *key, const char *value) +{ unreachable("where did you get a settings_w from?"); } +void write_setting_i(settings_w *handle, const char *key, int value) +{ unreachable("where did you get a settings_w from?"); } +void write_setting_fontspec(settings_w *handle, const char *name, FontSpec *fs) +{ unreachable("where did you get a settings_w from?"); } +void write_setting_filename(settings_w *handle, const char *name, Filename *fn) +{ unreachable("where did you get a settings_w from?"); } +void close_settings_w(settings_w *handle) +{ unreachable("where did you get a settings_w from?"); } + +settings_r *open_settings_r(const char *sessionname) +{ return NULL; } +char *read_setting_s(settings_r *handle, const char *key) +{ return NULL; } +int read_setting_i(settings_r *handle, const char *key, int defvalue) +{ return defvalue; } +FontSpec *read_setting_fontspec(settings_r *handle, const char *name) +{ return fontspec_new_default(); } +Filename *read_setting_filename(settings_r *handle, const char *name) +{ return filename_from_str(""); } +void close_settings_r(settings_r *handle) { } + +void del_settings(const char *sessionname) {} + +settings_e *enum_settings_start(void) +{ return NULL; } +bool enum_settings_next(settings_e *handle, strbuf *out) +{ unreachable("where did you get a settings_e from?"); } +void enum_settings_finish(settings_e *handle) +{ unreachable("where did you get a settings_e from?"); } diff --git a/stubs/no-timing.c b/stubs/no-timing.c index d1a0ef9f..0575fb53 100644 --- a/stubs/no-timing.c +++ b/stubs/no-timing.c @@ -19,3 +19,8 @@ unsigned long schedule_timer(int ticks, timer_fn_t fn, void *ctx) void expire_timer_context(void *ctx) { } + +unsigned long timing_last_clock(void) +{ + return 0; +} diff --git a/stubs/null-plug.c b/stubs/null-plug.c index d583d156..4ce287ac 100644 --- a/stubs/null-plug.c +++ b/stubs/null-plug.c @@ -7,7 +7,7 @@ #include "putty.h" -void nullplug_log(Plug *plug, PlugLogType type, SockAddr *addr, +void nullplug_log(Plug *plug, Socket *s, PlugLogType type, SockAddr *addr, int port, const char *err_msg, int err_code) { } diff --git a/stubs/null-seat.c b/stubs/null-seat.c index 2db45127..67c25af0 100644 --- a/stubs/null-seat.c +++ b/stubs/null-seat.c @@ -17,6 +17,7 @@ void nullseat_notify_session_started(Seat *seat) {} void nullseat_notify_remote_exit(Seat *seat) {} void nullseat_notify_remote_disconnect(Seat *seat) {} void nullseat_connection_fatal(Seat *seat, const char *message) {} +void nullseat_nonfatal(Seat *seat, const char *message) {} void nullseat_update_specials_menu(Seat *seat) {} char *nullseat_get_ttymode(Seat *seat, const char *mode) { return NULL; } void nullseat_set_busy_status(Seat *seat, BusyStatus status) {} diff --git a/stubs/null-socket.c b/stubs/null-socket.c new file mode 100644 index 00000000..ef194824 --- /dev/null +++ b/stubs/null-socket.c @@ -0,0 +1,12 @@ +/* + * null-socket.c: provide a null implementation of any Socket vtable + * method that might otherwise need to be reimplemented in multiple + * places as a no-op. + */ + +#include "putty.h" + +SocketEndpointInfo *nullsock_endpoint_info(Socket *s, bool peer) +{ + return NULL; +} diff --git a/terminal/bidi.c b/terminal/bidi.c index c17671b6..ba92118b 100644 --- a/terminal/bidi.c +++ b/terminal/bidi.c @@ -13,11 +13,11 @@ * ----------------- * * This algorithm is up to date with Unicode Standard Annex #9 - * revision 44: + * revision 46: * - * https://www.unicode.org/reports/tr9/tr9-44.html + * https://www.unicode.org/reports/tr9/tr9-46.html * - * and passes the full conformance test suite in Unicode 14.0.0. + * and passes the full conformance test suite in Unicode 15.0.0. * * Paragraph and line handling * --------------------------- @@ -320,1327 +320,13 @@ static const shape_node shapetypes[] = { /* * Returns the bidi character type of ch. - * - * The data table in this function is constructed from the Unicode - * Character Database version 14.0.0, downloadable from unicode.org at - * the URL - * - * https://www.unicode.org/Public/14.0.0/ucd/ - * - * by the following fragment of Perl: - -perl -ne '@_=split ";"; $num = hex $_[0]; $type = $_[4];' \ - -e '$fl = ($_[1] =~ /First/ ? 1 : $_[1] =~ /Last/ ? 2 : 0);' \ - -e 'if ($type eq $runtype and ($runend == $num-1 or ' \ - -e ' ($fl==2 and $pfl==1))) {$runend = $num;} else { &reset; }' \ - -e '$pfl=$fl; END { &reset }; sub reset {' \ - -e 'printf" {0x%04x, 0x%04x, %s},\n",$runstart,$runend,$runtype' \ - -e ' if defined $runstart and $runtype ne "ON";' \ - -e '$runstart=$runend=$num; $runtype=$type;}' \ - UnicodeData.txt - */ unsigned char bidi_getType(int ch) { static const struct { int first, last, type; } lookup[] = { - {0x0000, 0x0008, BN}, - {0x0009, 0x0009, S}, - {0x000a, 0x000a, B}, - {0x000b, 0x000b, S}, - {0x000c, 0x000c, WS}, - {0x000d, 0x000d, B}, - {0x000e, 0x001b, BN}, - {0x001c, 0x001e, B}, - {0x001f, 0x001f, S}, - {0x0020, 0x0020, WS}, - {0x0023, 0x0025, ET}, - {0x002b, 0x002b, ES}, - {0x002c, 0x002c, CS}, - {0x002d, 0x002d, ES}, - {0x002e, 0x002f, CS}, - {0x0030, 0x0039, EN}, - {0x003a, 0x003a, CS}, - {0x0041, 0x005a, L}, - {0x0061, 0x007a, L}, - {0x007f, 0x0084, BN}, - {0x0085, 0x0085, B}, - {0x0086, 0x009f, BN}, - {0x00a0, 0x00a0, CS}, - {0x00a2, 0x00a5, ET}, - {0x00aa, 0x00aa, L}, - {0x00ad, 0x00ad, BN}, - {0x00b0, 0x00b1, ET}, - {0x00b2, 0x00b3, EN}, - {0x00b5, 0x00b5, L}, - {0x00b9, 0x00b9, EN}, - {0x00ba, 0x00ba, L}, - {0x00c0, 0x00d6, L}, - {0x00d8, 0x00f6, L}, - {0x00f8, 0x02b8, L}, - {0x02bb, 0x02c1, L}, - {0x02d0, 0x02d1, L}, - {0x02e0, 0x02e4, L}, - {0x02ee, 0x02ee, L}, - {0x0300, 0x036f, NSM}, - {0x0370, 0x0373, L}, - {0x0376, 0x0377, L}, - {0x037a, 0x037d, L}, - {0x037f, 0x037f, L}, - {0x0386, 0x0386, L}, - {0x0388, 0x038a, L}, - {0x038c, 0x038c, L}, - {0x038e, 0x03a1, L}, - {0x03a3, 0x03f5, L}, - {0x03f7, 0x0482, L}, - {0x0483, 0x0489, NSM}, - {0x048a, 0x052f, L}, - {0x0531, 0x0556, L}, - {0x0559, 0x0589, L}, - {0x058f, 0x058f, ET}, - {0x0591, 0x05bd, NSM}, - {0x05be, 0x05be, R}, - {0x05bf, 0x05bf, NSM}, - {0x05c0, 0x05c0, R}, - {0x05c1, 0x05c2, NSM}, - {0x05c3, 0x05c3, R}, - {0x05c4, 0x05c5, NSM}, - {0x05c6, 0x05c6, R}, - {0x05c7, 0x05c7, NSM}, - {0x05d0, 0x05ea, R}, - {0x05ef, 0x05f4, R}, - {0x0600, 0x0605, AN}, - {0x0608, 0x0608, AL}, - {0x0609, 0x060a, ET}, - {0x060b, 0x060b, AL}, - {0x060c, 0x060c, CS}, - {0x060d, 0x060d, AL}, - {0x0610, 0x061a, NSM}, - {0x061b, 0x064a, AL}, - {0x064b, 0x065f, NSM}, - {0x0660, 0x0669, AN}, - {0x066a, 0x066a, ET}, - {0x066b, 0x066c, AN}, - {0x066d, 0x066f, AL}, - {0x0670, 0x0670, NSM}, - {0x0671, 0x06d5, AL}, - {0x06d6, 0x06dc, NSM}, - {0x06dd, 0x06dd, AN}, - {0x06df, 0x06e4, NSM}, - {0x06e5, 0x06e6, AL}, - {0x06e7, 0x06e8, NSM}, - {0x06ea, 0x06ed, NSM}, - {0x06ee, 0x06ef, AL}, - {0x06f0, 0x06f9, EN}, - {0x06fa, 0x070d, AL}, - {0x070f, 0x0710, AL}, - {0x0711, 0x0711, NSM}, - {0x0712, 0x072f, AL}, - {0x0730, 0x074a, NSM}, - {0x074d, 0x07a5, AL}, - {0x07a6, 0x07b0, NSM}, - {0x07b1, 0x07b1, AL}, - {0x07c0, 0x07ea, R}, - {0x07eb, 0x07f3, NSM}, - {0x07f4, 0x07f5, R}, - {0x07fa, 0x07fa, R}, - {0x07fd, 0x07fd, NSM}, - {0x07fe, 0x0815, R}, - {0x0816, 0x0819, NSM}, - {0x081a, 0x081a, R}, - {0x081b, 0x0823, NSM}, - {0x0824, 0x0824, R}, - {0x0825, 0x0827, NSM}, - {0x0828, 0x0828, R}, - {0x0829, 0x082d, NSM}, - {0x0830, 0x083e, R}, - {0x0840, 0x0858, R}, - {0x0859, 0x085b, NSM}, - {0x085e, 0x085e, R}, - {0x0860, 0x086a, AL}, - {0x0870, 0x088e, AL}, - {0x0890, 0x0891, AN}, - {0x0898, 0x089f, NSM}, - {0x08a0, 0x08c9, AL}, - {0x08ca, 0x08e1, NSM}, - {0x08e2, 0x08e2, AN}, - {0x08e3, 0x0902, NSM}, - {0x0903, 0x0939, L}, - {0x093a, 0x093a, NSM}, - {0x093b, 0x093b, L}, - {0x093c, 0x093c, NSM}, - {0x093d, 0x0940, L}, - {0x0941, 0x0948, NSM}, - {0x0949, 0x094c, L}, - {0x094d, 0x094d, NSM}, - {0x094e, 0x0950, L}, - {0x0951, 0x0957, NSM}, - {0x0958, 0x0961, L}, - {0x0962, 0x0963, NSM}, - {0x0964, 0x0980, L}, - {0x0981, 0x0981, NSM}, - {0x0982, 0x0983, L}, - {0x0985, 0x098c, L}, - {0x098f, 0x0990, L}, - {0x0993, 0x09a8, L}, - {0x09aa, 0x09b0, L}, - {0x09b2, 0x09b2, L}, - {0x09b6, 0x09b9, L}, - {0x09bc, 0x09bc, NSM}, - {0x09bd, 0x09c0, L}, - {0x09c1, 0x09c4, NSM}, - {0x09c7, 0x09c8, L}, - {0x09cb, 0x09cc, L}, - {0x09cd, 0x09cd, NSM}, - {0x09ce, 0x09ce, L}, - {0x09d7, 0x09d7, L}, - {0x09dc, 0x09dd, L}, - {0x09df, 0x09e1, L}, - {0x09e2, 0x09e3, NSM}, - {0x09e6, 0x09f1, L}, - {0x09f2, 0x09f3, ET}, - {0x09f4, 0x09fa, L}, - {0x09fb, 0x09fb, ET}, - {0x09fc, 0x09fd, L}, - {0x09fe, 0x09fe, NSM}, - {0x0a01, 0x0a02, NSM}, - {0x0a03, 0x0a03, L}, - {0x0a05, 0x0a0a, L}, - {0x0a0f, 0x0a10, L}, - {0x0a13, 0x0a28, L}, - {0x0a2a, 0x0a30, L}, - {0x0a32, 0x0a33, L}, - {0x0a35, 0x0a36, L}, - {0x0a38, 0x0a39, L}, - {0x0a3c, 0x0a3c, NSM}, - {0x0a3e, 0x0a40, L}, - {0x0a41, 0x0a42, NSM}, - {0x0a47, 0x0a48, NSM}, - {0x0a4b, 0x0a4d, NSM}, - {0x0a51, 0x0a51, NSM}, - {0x0a59, 0x0a5c, L}, - {0x0a5e, 0x0a5e, L}, - {0x0a66, 0x0a6f, L}, - {0x0a70, 0x0a71, NSM}, - {0x0a72, 0x0a74, L}, - {0x0a75, 0x0a75, NSM}, - {0x0a76, 0x0a76, L}, - {0x0a81, 0x0a82, NSM}, - {0x0a83, 0x0a83, L}, - {0x0a85, 0x0a8d, L}, - {0x0a8f, 0x0a91, L}, - {0x0a93, 0x0aa8, L}, - {0x0aaa, 0x0ab0, L}, - {0x0ab2, 0x0ab3, L}, - {0x0ab5, 0x0ab9, L}, - {0x0abc, 0x0abc, NSM}, - {0x0abd, 0x0ac0, L}, - {0x0ac1, 0x0ac5, NSM}, - {0x0ac7, 0x0ac8, NSM}, - {0x0ac9, 0x0ac9, L}, - {0x0acb, 0x0acc, L}, - {0x0acd, 0x0acd, NSM}, - {0x0ad0, 0x0ad0, L}, - {0x0ae0, 0x0ae1, L}, - {0x0ae2, 0x0ae3, NSM}, - {0x0ae6, 0x0af0, L}, - {0x0af1, 0x0af1, ET}, - {0x0af9, 0x0af9, L}, - {0x0afa, 0x0aff, NSM}, - {0x0b01, 0x0b01, NSM}, - {0x0b02, 0x0b03, L}, - {0x0b05, 0x0b0c, L}, - {0x0b0f, 0x0b10, L}, - {0x0b13, 0x0b28, L}, - {0x0b2a, 0x0b30, L}, - {0x0b32, 0x0b33, L}, - {0x0b35, 0x0b39, L}, - {0x0b3c, 0x0b3c, NSM}, - {0x0b3d, 0x0b3e, L}, - {0x0b3f, 0x0b3f, NSM}, - {0x0b40, 0x0b40, L}, - {0x0b41, 0x0b44, NSM}, - {0x0b47, 0x0b48, L}, - {0x0b4b, 0x0b4c, L}, - {0x0b4d, 0x0b4d, NSM}, - {0x0b55, 0x0b56, NSM}, - {0x0b57, 0x0b57, L}, - {0x0b5c, 0x0b5d, L}, - {0x0b5f, 0x0b61, L}, - {0x0b62, 0x0b63, NSM}, - {0x0b66, 0x0b77, L}, - {0x0b82, 0x0b82, NSM}, - {0x0b83, 0x0b83, L}, - {0x0b85, 0x0b8a, L}, - {0x0b8e, 0x0b90, L}, - {0x0b92, 0x0b95, L}, - {0x0b99, 0x0b9a, L}, - {0x0b9c, 0x0b9c, L}, - {0x0b9e, 0x0b9f, L}, - {0x0ba3, 0x0ba4, L}, - {0x0ba8, 0x0baa, L}, - {0x0bae, 0x0bb9, L}, - {0x0bbe, 0x0bbf, L}, - {0x0bc0, 0x0bc0, NSM}, - {0x0bc1, 0x0bc2, L}, - {0x0bc6, 0x0bc8, L}, - {0x0bca, 0x0bcc, L}, - {0x0bcd, 0x0bcd, NSM}, - {0x0bd0, 0x0bd0, L}, - {0x0bd7, 0x0bd7, L}, - {0x0be6, 0x0bf2, L}, - {0x0bf9, 0x0bf9, ET}, - {0x0c00, 0x0c00, NSM}, - {0x0c01, 0x0c03, L}, - {0x0c04, 0x0c04, NSM}, - {0x0c05, 0x0c0c, L}, - {0x0c0e, 0x0c10, L}, - {0x0c12, 0x0c28, L}, - {0x0c2a, 0x0c39, L}, - {0x0c3c, 0x0c3c, NSM}, - {0x0c3d, 0x0c3d, L}, - {0x0c3e, 0x0c40, NSM}, - {0x0c41, 0x0c44, L}, - {0x0c46, 0x0c48, NSM}, - {0x0c4a, 0x0c4d, NSM}, - {0x0c55, 0x0c56, NSM}, - {0x0c58, 0x0c5a, L}, - {0x0c5d, 0x0c5d, L}, - {0x0c60, 0x0c61, L}, - {0x0c62, 0x0c63, NSM}, - {0x0c66, 0x0c6f, L}, - {0x0c77, 0x0c77, L}, - {0x0c7f, 0x0c80, L}, - {0x0c81, 0x0c81, NSM}, - {0x0c82, 0x0c8c, L}, - {0x0c8e, 0x0c90, L}, - {0x0c92, 0x0ca8, L}, - {0x0caa, 0x0cb3, L}, - {0x0cb5, 0x0cb9, L}, - {0x0cbc, 0x0cbc, NSM}, - {0x0cbd, 0x0cc4, L}, - {0x0cc6, 0x0cc8, L}, - {0x0cca, 0x0ccb, L}, - {0x0ccc, 0x0ccd, NSM}, - {0x0cd5, 0x0cd6, L}, - {0x0cdd, 0x0cde, L}, - {0x0ce0, 0x0ce1, L}, - {0x0ce2, 0x0ce3, NSM}, - {0x0ce6, 0x0cef, L}, - {0x0cf1, 0x0cf2, L}, - {0x0d00, 0x0d01, NSM}, - {0x0d02, 0x0d0c, L}, - {0x0d0e, 0x0d10, L}, - {0x0d12, 0x0d3a, L}, - {0x0d3b, 0x0d3c, NSM}, - {0x0d3d, 0x0d40, L}, - {0x0d41, 0x0d44, NSM}, - {0x0d46, 0x0d48, L}, - {0x0d4a, 0x0d4c, L}, - {0x0d4d, 0x0d4d, NSM}, - {0x0d4e, 0x0d4f, L}, - {0x0d54, 0x0d61, L}, - {0x0d62, 0x0d63, NSM}, - {0x0d66, 0x0d7f, L}, - {0x0d81, 0x0d81, NSM}, - {0x0d82, 0x0d83, L}, - {0x0d85, 0x0d96, L}, - {0x0d9a, 0x0db1, L}, - {0x0db3, 0x0dbb, L}, - {0x0dbd, 0x0dbd, L}, - {0x0dc0, 0x0dc6, L}, - {0x0dca, 0x0dca, NSM}, - {0x0dcf, 0x0dd1, L}, - {0x0dd2, 0x0dd4, NSM}, - {0x0dd6, 0x0dd6, NSM}, - {0x0dd8, 0x0ddf, L}, - {0x0de6, 0x0def, L}, - {0x0df2, 0x0df4, L}, - {0x0e01, 0x0e30, L}, - {0x0e31, 0x0e31, NSM}, - {0x0e32, 0x0e33, L}, - {0x0e34, 0x0e3a, NSM}, - {0x0e3f, 0x0e3f, ET}, - {0x0e40, 0x0e46, L}, - {0x0e47, 0x0e4e, NSM}, - {0x0e4f, 0x0e5b, L}, - {0x0e81, 0x0e82, L}, - {0x0e84, 0x0e84, L}, - {0x0e86, 0x0e8a, L}, - {0x0e8c, 0x0ea3, L}, - {0x0ea5, 0x0ea5, L}, - {0x0ea7, 0x0eb0, L}, - {0x0eb1, 0x0eb1, NSM}, - {0x0eb2, 0x0eb3, L}, - {0x0eb4, 0x0ebc, NSM}, - {0x0ebd, 0x0ebd, L}, - {0x0ec0, 0x0ec4, L}, - {0x0ec6, 0x0ec6, L}, - {0x0ec8, 0x0ecd, NSM}, - {0x0ed0, 0x0ed9, L}, - {0x0edc, 0x0edf, L}, - {0x0f00, 0x0f17, L}, - {0x0f18, 0x0f19, NSM}, - {0x0f1a, 0x0f34, L}, - {0x0f35, 0x0f35, NSM}, - {0x0f36, 0x0f36, L}, - {0x0f37, 0x0f37, NSM}, - {0x0f38, 0x0f38, L}, - {0x0f39, 0x0f39, NSM}, - {0x0f3e, 0x0f47, L}, - {0x0f49, 0x0f6c, L}, - {0x0f71, 0x0f7e, NSM}, - {0x0f7f, 0x0f7f, L}, - {0x0f80, 0x0f84, NSM}, - {0x0f85, 0x0f85, L}, - {0x0f86, 0x0f87, NSM}, - {0x0f88, 0x0f8c, L}, - {0x0f8d, 0x0f97, NSM}, - {0x0f99, 0x0fbc, NSM}, - {0x0fbe, 0x0fc5, L}, - {0x0fc6, 0x0fc6, NSM}, - {0x0fc7, 0x0fcc, L}, - {0x0fce, 0x0fda, L}, - {0x1000, 0x102c, L}, - {0x102d, 0x1030, NSM}, - {0x1031, 0x1031, L}, - {0x1032, 0x1037, NSM}, - {0x1038, 0x1038, L}, - {0x1039, 0x103a, NSM}, - {0x103b, 0x103c, L}, - {0x103d, 0x103e, NSM}, - {0x103f, 0x1057, L}, - {0x1058, 0x1059, NSM}, - {0x105a, 0x105d, L}, - {0x105e, 0x1060, NSM}, - {0x1061, 0x1070, L}, - {0x1071, 0x1074, NSM}, - {0x1075, 0x1081, L}, - {0x1082, 0x1082, NSM}, - {0x1083, 0x1084, L}, - {0x1085, 0x1086, NSM}, - {0x1087, 0x108c, L}, - {0x108d, 0x108d, NSM}, - {0x108e, 0x109c, L}, - {0x109d, 0x109d, NSM}, - {0x109e, 0x10c5, L}, - {0x10c7, 0x10c7, L}, - {0x10cd, 0x10cd, L}, - {0x10d0, 0x1248, L}, - {0x124a, 0x124d, L}, - {0x1250, 0x1256, L}, - {0x1258, 0x1258, L}, - {0x125a, 0x125d, L}, - {0x1260, 0x1288, L}, - {0x128a, 0x128d, L}, - {0x1290, 0x12b0, L}, - {0x12b2, 0x12b5, L}, - {0x12b8, 0x12be, L}, - {0x12c0, 0x12c0, L}, - {0x12c2, 0x12c5, L}, - {0x12c8, 0x12d6, L}, - {0x12d8, 0x1310, L}, - {0x1312, 0x1315, L}, - {0x1318, 0x135a, L}, - {0x135d, 0x135f, NSM}, - {0x1360, 0x137c, L}, - {0x1380, 0x138f, L}, - {0x13a0, 0x13f5, L}, - {0x13f8, 0x13fd, L}, - {0x1401, 0x167f, L}, - {0x1680, 0x1680, WS}, - {0x1681, 0x169a, L}, - {0x16a0, 0x16f8, L}, - {0x1700, 0x1711, L}, - {0x1712, 0x1714, NSM}, - {0x1715, 0x1715, L}, - {0x171f, 0x1731, L}, - {0x1732, 0x1733, NSM}, - {0x1734, 0x1736, L}, - {0x1740, 0x1751, L}, - {0x1752, 0x1753, NSM}, - {0x1760, 0x176c, L}, - {0x176e, 0x1770, L}, - {0x1772, 0x1773, NSM}, - {0x1780, 0x17b3, L}, - {0x17b4, 0x17b5, NSM}, - {0x17b6, 0x17b6, L}, - {0x17b7, 0x17bd, NSM}, - {0x17be, 0x17c5, L}, - {0x17c6, 0x17c6, NSM}, - {0x17c7, 0x17c8, L}, - {0x17c9, 0x17d3, NSM}, - {0x17d4, 0x17da, L}, - {0x17db, 0x17db, ET}, - {0x17dc, 0x17dc, L}, - {0x17dd, 0x17dd, NSM}, - {0x17e0, 0x17e9, L}, - {0x180b, 0x180d, NSM}, - {0x180e, 0x180e, BN}, - {0x180f, 0x180f, NSM}, - {0x1810, 0x1819, L}, - {0x1820, 0x1878, L}, - {0x1880, 0x1884, L}, - {0x1885, 0x1886, NSM}, - {0x1887, 0x18a8, L}, - {0x18a9, 0x18a9, NSM}, - {0x18aa, 0x18aa, L}, - {0x18b0, 0x18f5, L}, - {0x1900, 0x191e, L}, - {0x1920, 0x1922, NSM}, - {0x1923, 0x1926, L}, - {0x1927, 0x1928, NSM}, - {0x1929, 0x192b, L}, - {0x1930, 0x1931, L}, - {0x1932, 0x1932, NSM}, - {0x1933, 0x1938, L}, - {0x1939, 0x193b, NSM}, - {0x1946, 0x196d, L}, - {0x1970, 0x1974, L}, - {0x1980, 0x19ab, L}, - {0x19b0, 0x19c9, L}, - {0x19d0, 0x19da, L}, - {0x1a00, 0x1a16, L}, - {0x1a17, 0x1a18, NSM}, - {0x1a19, 0x1a1a, L}, - {0x1a1b, 0x1a1b, NSM}, - {0x1a1e, 0x1a55, L}, - {0x1a56, 0x1a56, NSM}, - {0x1a57, 0x1a57, L}, - {0x1a58, 0x1a5e, NSM}, - {0x1a60, 0x1a60, NSM}, - {0x1a61, 0x1a61, L}, - {0x1a62, 0x1a62, NSM}, - {0x1a63, 0x1a64, L}, - {0x1a65, 0x1a6c, NSM}, - {0x1a6d, 0x1a72, L}, - {0x1a73, 0x1a7c, NSM}, - {0x1a7f, 0x1a7f, NSM}, - {0x1a80, 0x1a89, L}, - {0x1a90, 0x1a99, L}, - {0x1aa0, 0x1aad, L}, - {0x1ab0, 0x1ace, NSM}, - {0x1b00, 0x1b03, NSM}, - {0x1b04, 0x1b33, L}, - {0x1b34, 0x1b34, NSM}, - {0x1b35, 0x1b35, L}, - {0x1b36, 0x1b3a, NSM}, - {0x1b3b, 0x1b3b, L}, - {0x1b3c, 0x1b3c, NSM}, - {0x1b3d, 0x1b41, L}, - {0x1b42, 0x1b42, NSM}, - {0x1b43, 0x1b4c, L}, - {0x1b50, 0x1b6a, L}, - {0x1b6b, 0x1b73, NSM}, - {0x1b74, 0x1b7e, L}, - {0x1b80, 0x1b81, NSM}, - {0x1b82, 0x1ba1, L}, - {0x1ba2, 0x1ba5, NSM}, - {0x1ba6, 0x1ba7, L}, - {0x1ba8, 0x1ba9, NSM}, - {0x1baa, 0x1baa, L}, - {0x1bab, 0x1bad, NSM}, - {0x1bae, 0x1be5, L}, - {0x1be6, 0x1be6, NSM}, - {0x1be7, 0x1be7, L}, - {0x1be8, 0x1be9, NSM}, - {0x1bea, 0x1bec, L}, - {0x1bed, 0x1bed, NSM}, - {0x1bee, 0x1bee, L}, - {0x1bef, 0x1bf1, NSM}, - {0x1bf2, 0x1bf3, L}, - {0x1bfc, 0x1c2b, L}, - {0x1c2c, 0x1c33, NSM}, - {0x1c34, 0x1c35, L}, - {0x1c36, 0x1c37, NSM}, - {0x1c3b, 0x1c49, L}, - {0x1c4d, 0x1c88, L}, - {0x1c90, 0x1cba, L}, - {0x1cbd, 0x1cc7, L}, - {0x1cd0, 0x1cd2, NSM}, - {0x1cd3, 0x1cd3, L}, - {0x1cd4, 0x1ce0, NSM}, - {0x1ce1, 0x1ce1, L}, - {0x1ce2, 0x1ce8, NSM}, - {0x1ce9, 0x1cec, L}, - {0x1ced, 0x1ced, NSM}, - {0x1cee, 0x1cf3, L}, - {0x1cf4, 0x1cf4, NSM}, - {0x1cf5, 0x1cf7, L}, - {0x1cf8, 0x1cf9, NSM}, - {0x1cfa, 0x1cfa, L}, - {0x1d00, 0x1dbf, L}, - {0x1dc0, 0x1dff, NSM}, - {0x1e00, 0x1f15, L}, - {0x1f18, 0x1f1d, L}, - {0x1f20, 0x1f45, L}, - {0x1f48, 0x1f4d, L}, - {0x1f50, 0x1f57, L}, - {0x1f59, 0x1f59, L}, - {0x1f5b, 0x1f5b, L}, - {0x1f5d, 0x1f5d, L}, - {0x1f5f, 0x1f7d, L}, - {0x1f80, 0x1fb4, L}, - {0x1fb6, 0x1fbc, L}, - {0x1fbe, 0x1fbe, L}, - {0x1fc2, 0x1fc4, L}, - {0x1fc6, 0x1fcc, L}, - {0x1fd0, 0x1fd3, L}, - {0x1fd6, 0x1fdb, L}, - {0x1fe0, 0x1fec, L}, - {0x1ff2, 0x1ff4, L}, - {0x1ff6, 0x1ffc, L}, - {0x2000, 0x200a, WS}, - {0x200b, 0x200d, BN}, - {0x200e, 0x200e, L}, - {0x200f, 0x200f, R}, - {0x2028, 0x2028, WS}, - {0x2029, 0x2029, B}, - {0x202a, 0x202a, LRE}, - {0x202b, 0x202b, RLE}, - {0x202c, 0x202c, PDF}, - {0x202d, 0x202d, LRO}, - {0x202e, 0x202e, RLO}, - {0x202f, 0x202f, CS}, - {0x2030, 0x2034, ET}, - {0x2044, 0x2044, CS}, - {0x205f, 0x205f, WS}, - {0x2060, 0x2064, BN}, - {0x2066, 0x2066, LRI}, - {0x2067, 0x2067, RLI}, - {0x2068, 0x2068, FSI}, - {0x2069, 0x2069, PDI}, - {0x206a, 0x206f, BN}, - {0x2070, 0x2070, EN}, - {0x2071, 0x2071, L}, - {0x2074, 0x2079, EN}, - {0x207a, 0x207b, ES}, - {0x207f, 0x207f, L}, - {0x2080, 0x2089, EN}, - {0x208a, 0x208b, ES}, - {0x2090, 0x209c, L}, - {0x20a0, 0x20c0, ET}, - {0x20d0, 0x20f0, NSM}, - {0x2102, 0x2102, L}, - {0x2107, 0x2107, L}, - {0x210a, 0x2113, L}, - {0x2115, 0x2115, L}, - {0x2119, 0x211d, L}, - {0x2124, 0x2124, L}, - {0x2126, 0x2126, L}, - {0x2128, 0x2128, L}, - {0x212a, 0x212d, L}, - {0x212e, 0x212e, ET}, - {0x212f, 0x2139, L}, - {0x213c, 0x213f, L}, - {0x2145, 0x2149, L}, - {0x214e, 0x214f, L}, - {0x2160, 0x2188, L}, - {0x2212, 0x2212, ES}, - {0x2213, 0x2213, ET}, - {0x2336, 0x237a, L}, - {0x2395, 0x2395, L}, - {0x2488, 0x249b, EN}, - {0x249c, 0x24e9, L}, - {0x26ac, 0x26ac, L}, - {0x2800, 0x28ff, L}, - {0x2c00, 0x2ce4, L}, - {0x2ceb, 0x2cee, L}, - {0x2cef, 0x2cf1, NSM}, - {0x2cf2, 0x2cf3, L}, - {0x2d00, 0x2d25, L}, - {0x2d27, 0x2d27, L}, - {0x2d2d, 0x2d2d, L}, - {0x2d30, 0x2d67, L}, - {0x2d6f, 0x2d70, L}, - {0x2d7f, 0x2d7f, NSM}, - {0x2d80, 0x2d96, L}, - {0x2da0, 0x2da6, L}, - {0x2da8, 0x2dae, L}, - {0x2db0, 0x2db6, L}, - {0x2db8, 0x2dbe, L}, - {0x2dc0, 0x2dc6, L}, - {0x2dc8, 0x2dce, L}, - {0x2dd0, 0x2dd6, L}, - {0x2dd8, 0x2dde, L}, - {0x2de0, 0x2dff, NSM}, - {0x3000, 0x3000, WS}, - {0x3005, 0x3007, L}, - {0x3021, 0x3029, L}, - {0x302a, 0x302d, NSM}, - {0x302e, 0x302f, L}, - {0x3031, 0x3035, L}, - {0x3038, 0x303c, L}, - {0x3041, 0x3096, L}, - {0x3099, 0x309a, NSM}, - {0x309d, 0x309f, L}, - {0x30a1, 0x30fa, L}, - {0x30fc, 0x30ff, L}, - {0x3105, 0x312f, L}, - {0x3131, 0x318e, L}, - {0x3190, 0x31bf, L}, - {0x31f0, 0x321c, L}, - {0x3220, 0x324f, L}, - {0x3260, 0x327b, L}, - {0x327f, 0x32b0, L}, - {0x32c0, 0x32cb, L}, - {0x32d0, 0x3376, L}, - {0x337b, 0x33dd, L}, - {0x33e0, 0x33fe, L}, - {0x3400, 0x4dbf, L}, - {0x4e00, 0xa48c, L}, - {0xa4d0, 0xa60c, L}, - {0xa610, 0xa62b, L}, - {0xa640, 0xa66e, L}, - {0xa66f, 0xa672, NSM}, - {0xa674, 0xa67d, NSM}, - {0xa680, 0xa69d, L}, - {0xa69e, 0xa69f, NSM}, - {0xa6a0, 0xa6ef, L}, - {0xa6f0, 0xa6f1, NSM}, - {0xa6f2, 0xa6f7, L}, - {0xa722, 0xa787, L}, - {0xa789, 0xa7ca, L}, - {0xa7d0, 0xa7d1, L}, - {0xa7d3, 0xa7d3, L}, - {0xa7d5, 0xa7d9, L}, - {0xa7f2, 0xa801, L}, - {0xa802, 0xa802, NSM}, - {0xa803, 0xa805, L}, - {0xa806, 0xa806, NSM}, - {0xa807, 0xa80a, L}, - {0xa80b, 0xa80b, NSM}, - {0xa80c, 0xa824, L}, - {0xa825, 0xa826, NSM}, - {0xa827, 0xa827, L}, - {0xa82c, 0xa82c, NSM}, - {0xa830, 0xa837, L}, - {0xa838, 0xa839, ET}, - {0xa840, 0xa873, L}, - {0xa880, 0xa8c3, L}, - {0xa8c4, 0xa8c5, NSM}, - {0xa8ce, 0xa8d9, L}, - {0xa8e0, 0xa8f1, NSM}, - {0xa8f2, 0xa8fe, L}, - {0xa8ff, 0xa8ff, NSM}, - {0xa900, 0xa925, L}, - {0xa926, 0xa92d, NSM}, - {0xa92e, 0xa946, L}, - {0xa947, 0xa951, NSM}, - {0xa952, 0xa953, L}, - {0xa95f, 0xa97c, L}, - {0xa980, 0xa982, NSM}, - {0xa983, 0xa9b2, L}, - {0xa9b3, 0xa9b3, NSM}, - {0xa9b4, 0xa9b5, L}, - {0xa9b6, 0xa9b9, NSM}, - {0xa9ba, 0xa9bb, L}, - {0xa9bc, 0xa9bd, NSM}, - {0xa9be, 0xa9cd, L}, - {0xa9cf, 0xa9d9, L}, - {0xa9de, 0xa9e4, L}, - {0xa9e5, 0xa9e5, NSM}, - {0xa9e6, 0xa9fe, L}, - {0xaa00, 0xaa28, L}, - {0xaa29, 0xaa2e, NSM}, - {0xaa2f, 0xaa30, L}, - {0xaa31, 0xaa32, NSM}, - {0xaa33, 0xaa34, L}, - {0xaa35, 0xaa36, NSM}, - {0xaa40, 0xaa42, L}, - {0xaa43, 0xaa43, NSM}, - {0xaa44, 0xaa4b, L}, - {0xaa4c, 0xaa4c, NSM}, - {0xaa4d, 0xaa4d, L}, - {0xaa50, 0xaa59, L}, - {0xaa5c, 0xaa7b, L}, - {0xaa7c, 0xaa7c, NSM}, - {0xaa7d, 0xaaaf, L}, - {0xaab0, 0xaab0, NSM}, - {0xaab1, 0xaab1, L}, - {0xaab2, 0xaab4, NSM}, - {0xaab5, 0xaab6, L}, - {0xaab7, 0xaab8, NSM}, - {0xaab9, 0xaabd, L}, - {0xaabe, 0xaabf, NSM}, - {0xaac0, 0xaac0, L}, - {0xaac1, 0xaac1, NSM}, - {0xaac2, 0xaac2, L}, - {0xaadb, 0xaaeb, L}, - {0xaaec, 0xaaed, NSM}, - {0xaaee, 0xaaf5, L}, - {0xaaf6, 0xaaf6, NSM}, - {0xab01, 0xab06, L}, - {0xab09, 0xab0e, L}, - {0xab11, 0xab16, L}, - {0xab20, 0xab26, L}, - {0xab28, 0xab2e, L}, - {0xab30, 0xab69, L}, - {0xab70, 0xabe4, L}, - {0xabe5, 0xabe5, NSM}, - {0xabe6, 0xabe7, L}, - {0xabe8, 0xabe8, NSM}, - {0xabe9, 0xabec, L}, - {0xabed, 0xabed, NSM}, - {0xabf0, 0xabf9, L}, - {0xac00, 0xd7a3, L}, - {0xd7b0, 0xd7c6, L}, - {0xd7cb, 0xd7fb, L}, - {0xd800, 0xfa6d, L}, - {0xfa70, 0xfad9, L}, - {0xfb00, 0xfb06, L}, - {0xfb13, 0xfb17, L}, - {0xfb1d, 0xfb1d, R}, - {0xfb1e, 0xfb1e, NSM}, - {0xfb1f, 0xfb28, R}, - {0xfb29, 0xfb29, ES}, - {0xfb2a, 0xfb36, R}, - {0xfb38, 0xfb3c, R}, - {0xfb3e, 0xfb3e, R}, - {0xfb40, 0xfb41, R}, - {0xfb43, 0xfb44, R}, - {0xfb46, 0xfb4f, R}, - {0xfb50, 0xfbc2, AL}, - {0xfbd3, 0xfd3d, AL}, - {0xfd50, 0xfd8f, AL}, - {0xfd92, 0xfdc7, AL}, - {0xfdf0, 0xfdfc, AL}, - {0xfe00, 0xfe0f, NSM}, - {0xfe20, 0xfe2f, NSM}, - {0xfe50, 0xfe50, CS}, - {0xfe52, 0xfe52, CS}, - {0xfe55, 0xfe55, CS}, - {0xfe5f, 0xfe5f, ET}, - {0xfe62, 0xfe63, ES}, - {0xfe69, 0xfe6a, ET}, - {0xfe70, 0xfe74, AL}, - {0xfe76, 0xfefc, AL}, - {0xfeff, 0xfeff, BN}, - {0xff03, 0xff05, ET}, - {0xff0b, 0xff0b, ES}, - {0xff0c, 0xff0c, CS}, - {0xff0d, 0xff0d, ES}, - {0xff0e, 0xff0f, CS}, - {0xff10, 0xff19, EN}, - {0xff1a, 0xff1a, CS}, - {0xff21, 0xff3a, L}, - {0xff41, 0xff5a, L}, - {0xff66, 0xffbe, L}, - {0xffc2, 0xffc7, L}, - {0xffca, 0xffcf, L}, - {0xffd2, 0xffd7, L}, - {0xffda, 0xffdc, L}, - {0xffe0, 0xffe1, ET}, - {0xffe5, 0xffe6, ET}, - {0x10000, 0x1000b, L}, - {0x1000d, 0x10026, L}, - {0x10028, 0x1003a, L}, - {0x1003c, 0x1003d, L}, - {0x1003f, 0x1004d, L}, - {0x10050, 0x1005d, L}, - {0x10080, 0x100fa, L}, - {0x10100, 0x10100, L}, - {0x10102, 0x10102, L}, - {0x10107, 0x10133, L}, - {0x10137, 0x1013f, L}, - {0x1018d, 0x1018e, L}, - {0x101d0, 0x101fc, L}, - {0x101fd, 0x101fd, NSM}, - {0x10280, 0x1029c, L}, - {0x102a0, 0x102d0, L}, - {0x102e0, 0x102e0, NSM}, - {0x102e1, 0x102fb, EN}, - {0x10300, 0x10323, L}, - {0x1032d, 0x1034a, L}, - {0x10350, 0x10375, L}, - {0x10376, 0x1037a, NSM}, - {0x10380, 0x1039d, L}, - {0x1039f, 0x103c3, L}, - {0x103c8, 0x103d5, L}, - {0x10400, 0x1049d, L}, - {0x104a0, 0x104a9, L}, - {0x104b0, 0x104d3, L}, - {0x104d8, 0x104fb, L}, - {0x10500, 0x10527, L}, - {0x10530, 0x10563, L}, - {0x1056f, 0x1057a, L}, - {0x1057c, 0x1058a, L}, - {0x1058c, 0x10592, L}, - {0x10594, 0x10595, L}, - {0x10597, 0x105a1, L}, - {0x105a3, 0x105b1, L}, - {0x105b3, 0x105b9, L}, - {0x105bb, 0x105bc, L}, - {0x10600, 0x10736, L}, - {0x10740, 0x10755, L}, - {0x10760, 0x10767, L}, - {0x10780, 0x10785, L}, - {0x10787, 0x107b0, L}, - {0x107b2, 0x107ba, L}, - {0x10800, 0x10805, R}, - {0x10808, 0x10808, R}, - {0x1080a, 0x10835, R}, - {0x10837, 0x10838, R}, - {0x1083c, 0x1083c, R}, - {0x1083f, 0x10855, R}, - {0x10857, 0x1089e, R}, - {0x108a7, 0x108af, R}, - {0x108e0, 0x108f2, R}, - {0x108f4, 0x108f5, R}, - {0x108fb, 0x1091b, R}, - {0x10920, 0x10939, R}, - {0x1093f, 0x1093f, R}, - {0x10980, 0x109b7, R}, - {0x109bc, 0x109cf, R}, - {0x109d2, 0x10a00, R}, - {0x10a01, 0x10a03, NSM}, - {0x10a05, 0x10a06, NSM}, - {0x10a0c, 0x10a0f, NSM}, - {0x10a10, 0x10a13, R}, - {0x10a15, 0x10a17, R}, - {0x10a19, 0x10a35, R}, - {0x10a38, 0x10a3a, NSM}, - {0x10a3f, 0x10a3f, NSM}, - {0x10a40, 0x10a48, R}, - {0x10a50, 0x10a58, R}, - {0x10a60, 0x10a9f, R}, - {0x10ac0, 0x10ae4, R}, - {0x10ae5, 0x10ae6, NSM}, - {0x10aeb, 0x10af6, R}, - {0x10b00, 0x10b35, R}, - {0x10b40, 0x10b55, R}, - {0x10b58, 0x10b72, R}, - {0x10b78, 0x10b91, R}, - {0x10b99, 0x10b9c, R}, - {0x10ba9, 0x10baf, R}, - {0x10c00, 0x10c48, R}, - {0x10c80, 0x10cb2, R}, - {0x10cc0, 0x10cf2, R}, - {0x10cfa, 0x10cff, R}, - {0x10d00, 0x10d23, AL}, - {0x10d24, 0x10d27, NSM}, - {0x10d30, 0x10d39, AN}, - {0x10e60, 0x10e7e, AN}, - {0x10e80, 0x10ea9, R}, - {0x10eab, 0x10eac, NSM}, - {0x10ead, 0x10ead, R}, - {0x10eb0, 0x10eb1, R}, - {0x10f00, 0x10f27, R}, - {0x10f30, 0x10f45, AL}, - {0x10f46, 0x10f50, NSM}, - {0x10f51, 0x10f59, AL}, - {0x10f70, 0x10f81, R}, - {0x10f82, 0x10f85, NSM}, - {0x10f86, 0x10f89, R}, - {0x10fb0, 0x10fcb, R}, - {0x10fe0, 0x10ff6, R}, - {0x11000, 0x11000, L}, - {0x11001, 0x11001, NSM}, - {0x11002, 0x11037, L}, - {0x11038, 0x11046, NSM}, - {0x11047, 0x1104d, L}, - {0x11066, 0x1106f, L}, - {0x11070, 0x11070, NSM}, - {0x11071, 0x11072, L}, - {0x11073, 0x11074, NSM}, - {0x11075, 0x11075, L}, - {0x1107f, 0x11081, NSM}, - {0x11082, 0x110b2, L}, - {0x110b3, 0x110b6, NSM}, - {0x110b7, 0x110b8, L}, - {0x110b9, 0x110ba, NSM}, - {0x110bb, 0x110c1, L}, - {0x110c2, 0x110c2, NSM}, - {0x110cd, 0x110cd, L}, - {0x110d0, 0x110e8, L}, - {0x110f0, 0x110f9, L}, - {0x11100, 0x11102, NSM}, - {0x11103, 0x11126, L}, - {0x11127, 0x1112b, NSM}, - {0x1112c, 0x1112c, L}, - {0x1112d, 0x11134, NSM}, - {0x11136, 0x11147, L}, - {0x11150, 0x11172, L}, - {0x11173, 0x11173, NSM}, - {0x11174, 0x11176, L}, - {0x11180, 0x11181, NSM}, - {0x11182, 0x111b5, L}, - {0x111b6, 0x111be, NSM}, - {0x111bf, 0x111c8, L}, - {0x111c9, 0x111cc, NSM}, - {0x111cd, 0x111ce, L}, - {0x111cf, 0x111cf, NSM}, - {0x111d0, 0x111df, L}, - {0x111e1, 0x111f4, L}, - {0x11200, 0x11211, L}, - {0x11213, 0x1122e, L}, - {0x1122f, 0x11231, NSM}, - {0x11232, 0x11233, L}, - {0x11234, 0x11234, NSM}, - {0x11235, 0x11235, L}, - {0x11236, 0x11237, NSM}, - {0x11238, 0x1123d, L}, - {0x1123e, 0x1123e, NSM}, - {0x11280, 0x11286, L}, - {0x11288, 0x11288, L}, - {0x1128a, 0x1128d, L}, - {0x1128f, 0x1129d, L}, - {0x1129f, 0x112a9, L}, - {0x112b0, 0x112de, L}, - {0x112df, 0x112df, NSM}, - {0x112e0, 0x112e2, L}, - {0x112e3, 0x112ea, NSM}, - {0x112f0, 0x112f9, L}, - {0x11300, 0x11301, NSM}, - {0x11302, 0x11303, L}, - {0x11305, 0x1130c, L}, - {0x1130f, 0x11310, L}, - {0x11313, 0x11328, L}, - {0x1132a, 0x11330, L}, - {0x11332, 0x11333, L}, - {0x11335, 0x11339, L}, - {0x1133b, 0x1133c, NSM}, - {0x1133d, 0x1133f, L}, - {0x11340, 0x11340, NSM}, - {0x11341, 0x11344, L}, - {0x11347, 0x11348, L}, - {0x1134b, 0x1134d, L}, - {0x11350, 0x11350, L}, - {0x11357, 0x11357, L}, - {0x1135d, 0x11363, L}, - {0x11366, 0x1136c, NSM}, - {0x11370, 0x11374, NSM}, - {0x11400, 0x11437, L}, - {0x11438, 0x1143f, NSM}, - {0x11440, 0x11441, L}, - {0x11442, 0x11444, NSM}, - {0x11445, 0x11445, L}, - {0x11446, 0x11446, NSM}, - {0x11447, 0x1145b, L}, - {0x1145d, 0x1145d, L}, - {0x1145e, 0x1145e, NSM}, - {0x1145f, 0x11461, L}, - {0x11480, 0x114b2, L}, - {0x114b3, 0x114b8, NSM}, - {0x114b9, 0x114b9, L}, - {0x114ba, 0x114ba, NSM}, - {0x114bb, 0x114be, L}, - {0x114bf, 0x114c0, NSM}, - {0x114c1, 0x114c1, L}, - {0x114c2, 0x114c3, NSM}, - {0x114c4, 0x114c7, L}, - {0x114d0, 0x114d9, L}, - {0x11580, 0x115b1, L}, - {0x115b2, 0x115b5, NSM}, - {0x115b8, 0x115bb, L}, - {0x115bc, 0x115bd, NSM}, - {0x115be, 0x115be, L}, - {0x115bf, 0x115c0, NSM}, - {0x115c1, 0x115db, L}, - {0x115dc, 0x115dd, NSM}, - {0x11600, 0x11632, L}, - {0x11633, 0x1163a, NSM}, - {0x1163b, 0x1163c, L}, - {0x1163d, 0x1163d, NSM}, - {0x1163e, 0x1163e, L}, - {0x1163f, 0x11640, NSM}, - {0x11641, 0x11644, L}, - {0x11650, 0x11659, L}, - {0x11680, 0x116aa, L}, - {0x116ab, 0x116ab, NSM}, - {0x116ac, 0x116ac, L}, - {0x116ad, 0x116ad, NSM}, - {0x116ae, 0x116af, L}, - {0x116b0, 0x116b5, NSM}, - {0x116b6, 0x116b6, L}, - {0x116b7, 0x116b7, NSM}, - {0x116b8, 0x116b9, L}, - {0x116c0, 0x116c9, L}, - {0x11700, 0x1171a, L}, - {0x1171d, 0x1171f, NSM}, - {0x11720, 0x11721, L}, - {0x11722, 0x11725, NSM}, - {0x11726, 0x11726, L}, - {0x11727, 0x1172b, NSM}, - {0x11730, 0x11746, L}, - {0x11800, 0x1182e, L}, - {0x1182f, 0x11837, NSM}, - {0x11838, 0x11838, L}, - {0x11839, 0x1183a, NSM}, - {0x1183b, 0x1183b, L}, - {0x118a0, 0x118f2, L}, - {0x118ff, 0x11906, L}, - {0x11909, 0x11909, L}, - {0x1190c, 0x11913, L}, - {0x11915, 0x11916, L}, - {0x11918, 0x11935, L}, - {0x11937, 0x11938, L}, - {0x1193b, 0x1193c, NSM}, - {0x1193d, 0x1193d, L}, - {0x1193e, 0x1193e, NSM}, - {0x1193f, 0x11942, L}, - {0x11943, 0x11943, NSM}, - {0x11944, 0x11946, L}, - {0x11950, 0x11959, L}, - {0x119a0, 0x119a7, L}, - {0x119aa, 0x119d3, L}, - {0x119d4, 0x119d7, NSM}, - {0x119da, 0x119db, NSM}, - {0x119dc, 0x119df, L}, - {0x119e0, 0x119e0, NSM}, - {0x119e1, 0x119e4, L}, - {0x11a00, 0x11a00, L}, - {0x11a01, 0x11a06, NSM}, - {0x11a07, 0x11a08, L}, - {0x11a09, 0x11a0a, NSM}, - {0x11a0b, 0x11a32, L}, - {0x11a33, 0x11a38, NSM}, - {0x11a39, 0x11a3a, L}, - {0x11a3b, 0x11a3e, NSM}, - {0x11a3f, 0x11a46, L}, - {0x11a47, 0x11a47, NSM}, - {0x11a50, 0x11a50, L}, - {0x11a51, 0x11a56, NSM}, - {0x11a57, 0x11a58, L}, - {0x11a59, 0x11a5b, NSM}, - {0x11a5c, 0x11a89, L}, - {0x11a8a, 0x11a96, NSM}, - {0x11a97, 0x11a97, L}, - {0x11a98, 0x11a99, NSM}, - {0x11a9a, 0x11aa2, L}, - {0x11ab0, 0x11af8, L}, - {0x11c00, 0x11c08, L}, - {0x11c0a, 0x11c2f, L}, - {0x11c30, 0x11c36, NSM}, - {0x11c38, 0x11c3d, NSM}, - {0x11c3e, 0x11c45, L}, - {0x11c50, 0x11c6c, L}, - {0x11c70, 0x11c8f, L}, - {0x11c92, 0x11ca7, NSM}, - {0x11ca9, 0x11ca9, L}, - {0x11caa, 0x11cb0, NSM}, - {0x11cb1, 0x11cb1, L}, - {0x11cb2, 0x11cb3, NSM}, - {0x11cb4, 0x11cb4, L}, - {0x11cb5, 0x11cb6, NSM}, - {0x11d00, 0x11d06, L}, - {0x11d08, 0x11d09, L}, - {0x11d0b, 0x11d30, L}, - {0x11d31, 0x11d36, NSM}, - {0x11d3a, 0x11d3a, NSM}, - {0x11d3c, 0x11d3d, NSM}, - {0x11d3f, 0x11d45, NSM}, - {0x11d46, 0x11d46, L}, - {0x11d47, 0x11d47, NSM}, - {0x11d50, 0x11d59, L}, - {0x11d60, 0x11d65, L}, - {0x11d67, 0x11d68, L}, - {0x11d6a, 0x11d8e, L}, - {0x11d90, 0x11d91, NSM}, - {0x11d93, 0x11d94, L}, - {0x11d95, 0x11d95, NSM}, - {0x11d96, 0x11d96, L}, - {0x11d97, 0x11d97, NSM}, - {0x11d98, 0x11d98, L}, - {0x11da0, 0x11da9, L}, - {0x11ee0, 0x11ef2, L}, - {0x11ef3, 0x11ef4, NSM}, - {0x11ef5, 0x11ef8, L}, - {0x11fb0, 0x11fb0, L}, - {0x11fc0, 0x11fd4, L}, - {0x11fdd, 0x11fe0, ET}, - {0x11fff, 0x12399, L}, - {0x12400, 0x1246e, L}, - {0x12470, 0x12474, L}, - {0x12480, 0x12543, L}, - {0x12f90, 0x12ff2, L}, - {0x13000, 0x1342e, L}, - {0x13430, 0x13438, L}, - {0x14400, 0x14646, L}, - {0x16800, 0x16a38, L}, - {0x16a40, 0x16a5e, L}, - {0x16a60, 0x16a69, L}, - {0x16a6e, 0x16abe, L}, - {0x16ac0, 0x16ac9, L}, - {0x16ad0, 0x16aed, L}, - {0x16af0, 0x16af4, NSM}, - {0x16af5, 0x16af5, L}, - {0x16b00, 0x16b2f, L}, - {0x16b30, 0x16b36, NSM}, - {0x16b37, 0x16b45, L}, - {0x16b50, 0x16b59, L}, - {0x16b5b, 0x16b61, L}, - {0x16b63, 0x16b77, L}, - {0x16b7d, 0x16b8f, L}, - {0x16e40, 0x16e9a, L}, - {0x16f00, 0x16f4a, L}, - {0x16f4f, 0x16f4f, NSM}, - {0x16f50, 0x16f87, L}, - {0x16f8f, 0x16f92, NSM}, - {0x16f93, 0x16f9f, L}, - {0x16fe0, 0x16fe1, L}, - {0x16fe3, 0x16fe3, L}, - {0x16fe4, 0x16fe4, NSM}, - {0x16ff0, 0x16ff1, L}, - {0x17000, 0x187f7, L}, - {0x18800, 0x18cd5, L}, - {0x18d00, 0x18d08, L}, - {0x1aff0, 0x1aff3, L}, - {0x1aff5, 0x1affb, L}, - {0x1affd, 0x1affe, L}, - {0x1b000, 0x1b122, L}, - {0x1b150, 0x1b152, L}, - {0x1b164, 0x1b167, L}, - {0x1b170, 0x1b2fb, L}, - {0x1bc00, 0x1bc6a, L}, - {0x1bc70, 0x1bc7c, L}, - {0x1bc80, 0x1bc88, L}, - {0x1bc90, 0x1bc99, L}, - {0x1bc9c, 0x1bc9c, L}, - {0x1bc9d, 0x1bc9e, NSM}, - {0x1bc9f, 0x1bc9f, L}, - {0x1bca0, 0x1bca3, BN}, - {0x1cf00, 0x1cf2d, NSM}, - {0x1cf30, 0x1cf46, NSM}, - {0x1cf50, 0x1cfc3, L}, - {0x1d000, 0x1d0f5, L}, - {0x1d100, 0x1d126, L}, - {0x1d129, 0x1d166, L}, - {0x1d167, 0x1d169, NSM}, - {0x1d16a, 0x1d172, L}, - {0x1d173, 0x1d17a, BN}, - {0x1d17b, 0x1d182, NSM}, - {0x1d183, 0x1d184, L}, - {0x1d185, 0x1d18b, NSM}, - {0x1d18c, 0x1d1a9, L}, - {0x1d1aa, 0x1d1ad, NSM}, - {0x1d1ae, 0x1d1e8, L}, - {0x1d242, 0x1d244, NSM}, - {0x1d2e0, 0x1d2f3, L}, - {0x1d360, 0x1d378, L}, - {0x1d400, 0x1d454, L}, - {0x1d456, 0x1d49c, L}, - {0x1d49e, 0x1d49f, L}, - {0x1d4a2, 0x1d4a2, L}, - {0x1d4a5, 0x1d4a6, L}, - {0x1d4a9, 0x1d4ac, L}, - {0x1d4ae, 0x1d4b9, L}, - {0x1d4bb, 0x1d4bb, L}, - {0x1d4bd, 0x1d4c3, L}, - {0x1d4c5, 0x1d505, L}, - {0x1d507, 0x1d50a, L}, - {0x1d50d, 0x1d514, L}, - {0x1d516, 0x1d51c, L}, - {0x1d51e, 0x1d539, L}, - {0x1d53b, 0x1d53e, L}, - {0x1d540, 0x1d544, L}, - {0x1d546, 0x1d546, L}, - {0x1d54a, 0x1d550, L}, - {0x1d552, 0x1d6a5, L}, - {0x1d6a8, 0x1d6da, L}, - {0x1d6dc, 0x1d714, L}, - {0x1d716, 0x1d74e, L}, - {0x1d750, 0x1d788, L}, - {0x1d78a, 0x1d7c2, L}, - {0x1d7c4, 0x1d7cb, L}, - {0x1d7ce, 0x1d7ff, EN}, - {0x1d800, 0x1d9ff, L}, - {0x1da00, 0x1da36, NSM}, - {0x1da37, 0x1da3a, L}, - {0x1da3b, 0x1da6c, NSM}, - {0x1da6d, 0x1da74, L}, - {0x1da75, 0x1da75, NSM}, - {0x1da76, 0x1da83, L}, - {0x1da84, 0x1da84, NSM}, - {0x1da85, 0x1da8b, L}, - {0x1da9b, 0x1da9f, NSM}, - {0x1daa1, 0x1daaf, NSM}, - {0x1df00, 0x1df1e, L}, - {0x1e000, 0x1e006, NSM}, - {0x1e008, 0x1e018, NSM}, - {0x1e01b, 0x1e021, NSM}, - {0x1e023, 0x1e024, NSM}, - {0x1e026, 0x1e02a, NSM}, - {0x1e100, 0x1e12c, L}, - {0x1e130, 0x1e136, NSM}, - {0x1e137, 0x1e13d, L}, - {0x1e140, 0x1e149, L}, - {0x1e14e, 0x1e14f, L}, - {0x1e290, 0x1e2ad, L}, - {0x1e2ae, 0x1e2ae, NSM}, - {0x1e2c0, 0x1e2eb, L}, - {0x1e2ec, 0x1e2ef, NSM}, - {0x1e2f0, 0x1e2f9, L}, - {0x1e2ff, 0x1e2ff, ET}, - {0x1e7e0, 0x1e7e6, L}, - {0x1e7e8, 0x1e7eb, L}, - {0x1e7ed, 0x1e7ee, L}, - {0x1e7f0, 0x1e7fe, L}, - {0x1e800, 0x1e8c4, R}, - {0x1e8c7, 0x1e8cf, R}, - {0x1e8d0, 0x1e8d6, NSM}, - {0x1e900, 0x1e943, R}, - {0x1e944, 0x1e94a, NSM}, - {0x1e94b, 0x1e94b, R}, - {0x1e950, 0x1e959, R}, - {0x1e95e, 0x1e95f, R}, - {0x1ec71, 0x1ecb4, AL}, - {0x1ed01, 0x1ed3d, AL}, - {0x1ee00, 0x1ee03, AL}, - {0x1ee05, 0x1ee1f, AL}, - {0x1ee21, 0x1ee22, AL}, - {0x1ee24, 0x1ee24, AL}, - {0x1ee27, 0x1ee27, AL}, - {0x1ee29, 0x1ee32, AL}, - {0x1ee34, 0x1ee37, AL}, - {0x1ee39, 0x1ee39, AL}, - {0x1ee3b, 0x1ee3b, AL}, - {0x1ee42, 0x1ee42, AL}, - {0x1ee47, 0x1ee47, AL}, - {0x1ee49, 0x1ee49, AL}, - {0x1ee4b, 0x1ee4b, AL}, - {0x1ee4d, 0x1ee4f, AL}, - {0x1ee51, 0x1ee52, AL}, - {0x1ee54, 0x1ee54, AL}, - {0x1ee57, 0x1ee57, AL}, - {0x1ee59, 0x1ee59, AL}, - {0x1ee5b, 0x1ee5b, AL}, - {0x1ee5d, 0x1ee5d, AL}, - {0x1ee5f, 0x1ee5f, AL}, - {0x1ee61, 0x1ee62, AL}, - {0x1ee64, 0x1ee64, AL}, - {0x1ee67, 0x1ee6a, AL}, - {0x1ee6c, 0x1ee72, AL}, - {0x1ee74, 0x1ee77, AL}, - {0x1ee79, 0x1ee7c, AL}, - {0x1ee7e, 0x1ee7e, AL}, - {0x1ee80, 0x1ee89, AL}, - {0x1ee8b, 0x1ee9b, AL}, - {0x1eea1, 0x1eea3, AL}, - {0x1eea5, 0x1eea9, AL}, - {0x1eeab, 0x1eebb, AL}, - {0x1f100, 0x1f10a, EN}, - {0x1f110, 0x1f12e, L}, - {0x1f130, 0x1f169, L}, - {0x1f170, 0x1f1ac, L}, - {0x1f1e6, 0x1f202, L}, - {0x1f210, 0x1f23b, L}, - {0x1f240, 0x1f248, L}, - {0x1f250, 0x1f251, L}, - {0x1fbf0, 0x1fbf9, EN}, - {0x20000, 0x2a6df, L}, - {0x2a700, 0x2b738, L}, - {0x2b740, 0x2b81d, L}, - {0x2b820, 0x2cea1, L}, - {0x2ceb0, 0x2ebe0, L}, - {0x2f800, 0x2fa1d, L}, - {0x30000, 0x3134a, L}, - {0xe0001, 0xe0001, BN}, - {0xe0020, 0xe007f, BN}, - {0xe0100, 0xe01ef, NSM}, - {0xf0000, 0xffffd, L}, - {0x100000, 0x10fffd, L}, + #include "unicode/bidi_type.h" }; int i, j, k; @@ -1671,27 +357,6 @@ unsigned char bidi_getType(int ch) /* * Return the mirrored version of a glyph. - - * The data table in this function is constructed from the Unicode - * Character Database version 14.0.0, downloadable from unicode.org at - * the URL - * - * https://www.unicode.org/Public/14.0.0/ucd/ - * - * by the following fragment of Perl: - -perl -e ' - while (<<>>) { - chomp; s{\s}{}g; s{#.*$}{}; next unless /./; - @_ = split /;/, $_; - $src = hex $_[0]; $dst = hex $_[1]; - $m{$src}=$dst; $m{$dst}=$src; - } - for $src (sort {$a <=> $b} keys %m) { - printf " {0x%04x, 0x%04x},\n", $src, $m{$src}; - } -' BidiMirroring.txt - * * FIXME: there are also glyphs which the text rendering engine is * supposed to display left-right reflected, since no mirrored glyph @@ -1706,434 +371,7 @@ static unsigned mirror_glyph(unsigned int ch) static const struct { unsigned src, dst; } mirror_pairs[] = { - {0x0028, 0x0029}, - {0x0029, 0x0028}, - {0x003c, 0x003e}, - {0x003e, 0x003c}, - {0x005b, 0x005d}, - {0x005d, 0x005b}, - {0x007b, 0x007d}, - {0x007d, 0x007b}, - {0x00ab, 0x00bb}, - {0x00bb, 0x00ab}, - {0x0f3a, 0x0f3b}, - {0x0f3b, 0x0f3a}, - {0x0f3c, 0x0f3d}, - {0x0f3d, 0x0f3c}, - {0x169b, 0x169c}, - {0x169c, 0x169b}, - {0x2039, 0x203a}, - {0x203a, 0x2039}, - {0x2045, 0x2046}, - {0x2046, 0x2045}, - {0x207d, 0x207e}, - {0x207e, 0x207d}, - {0x208d, 0x208e}, - {0x208e, 0x208d}, - {0x2208, 0x220b}, - {0x2209, 0x220c}, - {0x220a, 0x220d}, - {0x220b, 0x2208}, - {0x220c, 0x2209}, - {0x220d, 0x220a}, - {0x2215, 0x29f5}, - {0x221f, 0x2bfe}, - {0x2220, 0x29a3}, - {0x2221, 0x299b}, - {0x2222, 0x29a0}, - {0x2224, 0x2aee}, - {0x223c, 0x223d}, - {0x223d, 0x223c}, - {0x2243, 0x22cd}, - {0x2245, 0x224c}, - {0x224c, 0x2245}, - {0x2252, 0x2253}, - {0x2253, 0x2252}, - {0x2254, 0x2255}, - {0x2255, 0x2254}, - {0x2264, 0x2265}, - {0x2265, 0x2264}, - {0x2266, 0x2267}, - {0x2267, 0x2266}, - {0x2268, 0x2269}, - {0x2269, 0x2268}, - {0x226a, 0x226b}, - {0x226b, 0x226a}, - {0x226e, 0x226f}, - {0x226f, 0x226e}, - {0x2270, 0x2271}, - {0x2271, 0x2270}, - {0x2272, 0x2273}, - {0x2273, 0x2272}, - {0x2274, 0x2275}, - {0x2275, 0x2274}, - {0x2276, 0x2277}, - {0x2277, 0x2276}, - {0x2278, 0x2279}, - {0x2279, 0x2278}, - {0x227a, 0x227b}, - {0x227b, 0x227a}, - {0x227c, 0x227d}, - {0x227d, 0x227c}, - {0x227e, 0x227f}, - {0x227f, 0x227e}, - {0x2280, 0x2281}, - {0x2281, 0x2280}, - {0x2282, 0x2283}, - {0x2283, 0x2282}, - {0x2284, 0x2285}, - {0x2285, 0x2284}, - {0x2286, 0x2287}, - {0x2287, 0x2286}, - {0x2288, 0x2289}, - {0x2289, 0x2288}, - {0x228a, 0x228b}, - {0x228b, 0x228a}, - {0x228f, 0x2290}, - {0x2290, 0x228f}, - {0x2291, 0x2292}, - {0x2292, 0x2291}, - {0x2298, 0x29b8}, - {0x22a2, 0x22a3}, - {0x22a3, 0x22a2}, - {0x22a6, 0x2ade}, - {0x22a8, 0x2ae4}, - {0x22a9, 0x2ae3}, - {0x22ab, 0x2ae5}, - {0x22b0, 0x22b1}, - {0x22b1, 0x22b0}, - {0x22b2, 0x22b3}, - {0x22b3, 0x22b2}, - {0x22b4, 0x22b5}, - {0x22b5, 0x22b4}, - {0x22b6, 0x22b7}, - {0x22b7, 0x22b6}, - {0x22b8, 0x27dc}, - {0x22c9, 0x22ca}, - {0x22ca, 0x22c9}, - {0x22cb, 0x22cc}, - {0x22cc, 0x22cb}, - {0x22cd, 0x2243}, - {0x22d0, 0x22d1}, - {0x22d1, 0x22d0}, - {0x22d6, 0x22d7}, - {0x22d7, 0x22d6}, - {0x22d8, 0x22d9}, - {0x22d9, 0x22d8}, - {0x22da, 0x22db}, - {0x22db, 0x22da}, - {0x22dc, 0x22dd}, - {0x22dd, 0x22dc}, - {0x22de, 0x22df}, - {0x22df, 0x22de}, - {0x22e0, 0x22e1}, - {0x22e1, 0x22e0}, - {0x22e2, 0x22e3}, - {0x22e3, 0x22e2}, - {0x22e4, 0x22e5}, - {0x22e5, 0x22e4}, - {0x22e6, 0x22e7}, - {0x22e7, 0x22e6}, - {0x22e8, 0x22e9}, - {0x22e9, 0x22e8}, - {0x22ea, 0x22eb}, - {0x22eb, 0x22ea}, - {0x22ec, 0x22ed}, - {0x22ed, 0x22ec}, - {0x22f0, 0x22f1}, - {0x22f1, 0x22f0}, - {0x22f2, 0x22fa}, - {0x22f3, 0x22fb}, - {0x22f4, 0x22fc}, - {0x22f6, 0x22fd}, - {0x22f7, 0x22fe}, - {0x22fa, 0x22f2}, - {0x22fb, 0x22f3}, - {0x22fc, 0x22f4}, - {0x22fd, 0x22f6}, - {0x22fe, 0x22f7}, - {0x2308, 0x2309}, - {0x2309, 0x2308}, - {0x230a, 0x230b}, - {0x230b, 0x230a}, - {0x2329, 0x232a}, - {0x232a, 0x2329}, - {0x2768, 0x2769}, - {0x2769, 0x2768}, - {0x276a, 0x276b}, - {0x276b, 0x276a}, - {0x276c, 0x276d}, - {0x276d, 0x276c}, - {0x276e, 0x276f}, - {0x276f, 0x276e}, - {0x2770, 0x2771}, - {0x2771, 0x2770}, - {0x2772, 0x2773}, - {0x2773, 0x2772}, - {0x2774, 0x2775}, - {0x2775, 0x2774}, - {0x27c3, 0x27c4}, - {0x27c4, 0x27c3}, - {0x27c5, 0x27c6}, - {0x27c6, 0x27c5}, - {0x27c8, 0x27c9}, - {0x27c9, 0x27c8}, - {0x27cb, 0x27cd}, - {0x27cd, 0x27cb}, - {0x27d5, 0x27d6}, - {0x27d6, 0x27d5}, - {0x27dc, 0x22b8}, - {0x27dd, 0x27de}, - {0x27de, 0x27dd}, - {0x27e2, 0x27e3}, - {0x27e3, 0x27e2}, - {0x27e4, 0x27e5}, - {0x27e5, 0x27e4}, - {0x27e6, 0x27e7}, - {0x27e7, 0x27e6}, - {0x27e8, 0x27e9}, - {0x27e9, 0x27e8}, - {0x27ea, 0x27eb}, - {0x27eb, 0x27ea}, - {0x27ec, 0x27ed}, - {0x27ed, 0x27ec}, - {0x27ee, 0x27ef}, - {0x27ef, 0x27ee}, - {0x2983, 0x2984}, - {0x2984, 0x2983}, - {0x2985, 0x2986}, - {0x2986, 0x2985}, - {0x2987, 0x2988}, - {0x2988, 0x2987}, - {0x2989, 0x298a}, - {0x298a, 0x2989}, - {0x298b, 0x298c}, - {0x298c, 0x298b}, - {0x298d, 0x2990}, - {0x298e, 0x298f}, - {0x298f, 0x298e}, - {0x2990, 0x298d}, - {0x2991, 0x2992}, - {0x2992, 0x2991}, - {0x2993, 0x2994}, - {0x2994, 0x2993}, - {0x2995, 0x2996}, - {0x2996, 0x2995}, - {0x2997, 0x2998}, - {0x2998, 0x2997}, - {0x299b, 0x2221}, - {0x29a0, 0x2222}, - {0x29a3, 0x2220}, - {0x29a4, 0x29a5}, - {0x29a5, 0x29a4}, - {0x29a8, 0x29a9}, - {0x29a9, 0x29a8}, - {0x29aa, 0x29ab}, - {0x29ab, 0x29aa}, - {0x29ac, 0x29ad}, - {0x29ad, 0x29ac}, - {0x29ae, 0x29af}, - {0x29af, 0x29ae}, - {0x29b8, 0x2298}, - {0x29c0, 0x29c1}, - {0x29c1, 0x29c0}, - {0x29c4, 0x29c5}, - {0x29c5, 0x29c4}, - {0x29cf, 0x29d0}, - {0x29d0, 0x29cf}, - {0x29d1, 0x29d2}, - {0x29d2, 0x29d1}, - {0x29d4, 0x29d5}, - {0x29d5, 0x29d4}, - {0x29d8, 0x29d9}, - {0x29d9, 0x29d8}, - {0x29da, 0x29db}, - {0x29db, 0x29da}, - {0x29e8, 0x29e9}, - {0x29e9, 0x29e8}, - {0x29f5, 0x2215}, - {0x29f8, 0x29f9}, - {0x29f9, 0x29f8}, - {0x29fc, 0x29fd}, - {0x29fd, 0x29fc}, - {0x2a2b, 0x2a2c}, - {0x2a2c, 0x2a2b}, - {0x2a2d, 0x2a2e}, - {0x2a2e, 0x2a2d}, - {0x2a34, 0x2a35}, - {0x2a35, 0x2a34}, - {0x2a3c, 0x2a3d}, - {0x2a3d, 0x2a3c}, - {0x2a64, 0x2a65}, - {0x2a65, 0x2a64}, - {0x2a79, 0x2a7a}, - {0x2a7a, 0x2a79}, - {0x2a7b, 0x2a7c}, - {0x2a7c, 0x2a7b}, - {0x2a7d, 0x2a7e}, - {0x2a7e, 0x2a7d}, - {0x2a7f, 0x2a80}, - {0x2a80, 0x2a7f}, - {0x2a81, 0x2a82}, - {0x2a82, 0x2a81}, - {0x2a83, 0x2a84}, - {0x2a84, 0x2a83}, - {0x2a85, 0x2a86}, - {0x2a86, 0x2a85}, - {0x2a87, 0x2a88}, - {0x2a88, 0x2a87}, - {0x2a89, 0x2a8a}, - {0x2a8a, 0x2a89}, - {0x2a8b, 0x2a8c}, - {0x2a8c, 0x2a8b}, - {0x2a8d, 0x2a8e}, - {0x2a8e, 0x2a8d}, - {0x2a8f, 0x2a90}, - {0x2a90, 0x2a8f}, - {0x2a91, 0x2a92}, - {0x2a92, 0x2a91}, - {0x2a93, 0x2a94}, - {0x2a94, 0x2a93}, - {0x2a95, 0x2a96}, - {0x2a96, 0x2a95}, - {0x2a97, 0x2a98}, - {0x2a98, 0x2a97}, - {0x2a99, 0x2a9a}, - {0x2a9a, 0x2a99}, - {0x2a9b, 0x2a9c}, - {0x2a9c, 0x2a9b}, - {0x2a9d, 0x2a9e}, - {0x2a9e, 0x2a9d}, - {0x2a9f, 0x2aa0}, - {0x2aa0, 0x2a9f}, - {0x2aa1, 0x2aa2}, - {0x2aa2, 0x2aa1}, - {0x2aa6, 0x2aa7}, - {0x2aa7, 0x2aa6}, - {0x2aa8, 0x2aa9}, - {0x2aa9, 0x2aa8}, - {0x2aaa, 0x2aab}, - {0x2aab, 0x2aaa}, - {0x2aac, 0x2aad}, - {0x2aad, 0x2aac}, - {0x2aaf, 0x2ab0}, - {0x2ab0, 0x2aaf}, - {0x2ab1, 0x2ab2}, - {0x2ab2, 0x2ab1}, - {0x2ab3, 0x2ab4}, - {0x2ab4, 0x2ab3}, - {0x2ab5, 0x2ab6}, - {0x2ab6, 0x2ab5}, - {0x2ab7, 0x2ab8}, - {0x2ab8, 0x2ab7}, - {0x2ab9, 0x2aba}, - {0x2aba, 0x2ab9}, - {0x2abb, 0x2abc}, - {0x2abc, 0x2abb}, - {0x2abd, 0x2abe}, - {0x2abe, 0x2abd}, - {0x2abf, 0x2ac0}, - {0x2ac0, 0x2abf}, - {0x2ac1, 0x2ac2}, - {0x2ac2, 0x2ac1}, - {0x2ac3, 0x2ac4}, - {0x2ac4, 0x2ac3}, - {0x2ac5, 0x2ac6}, - {0x2ac6, 0x2ac5}, - {0x2ac7, 0x2ac8}, - {0x2ac8, 0x2ac7}, - {0x2ac9, 0x2aca}, - {0x2aca, 0x2ac9}, - {0x2acb, 0x2acc}, - {0x2acc, 0x2acb}, - {0x2acd, 0x2ace}, - {0x2ace, 0x2acd}, - {0x2acf, 0x2ad0}, - {0x2ad0, 0x2acf}, - {0x2ad1, 0x2ad2}, - {0x2ad2, 0x2ad1}, - {0x2ad3, 0x2ad4}, - {0x2ad4, 0x2ad3}, - {0x2ad5, 0x2ad6}, - {0x2ad6, 0x2ad5}, - {0x2ade, 0x22a6}, - {0x2ae3, 0x22a9}, - {0x2ae4, 0x22a8}, - {0x2ae5, 0x22ab}, - {0x2aec, 0x2aed}, - {0x2aed, 0x2aec}, - {0x2aee, 0x2224}, - {0x2af7, 0x2af8}, - {0x2af8, 0x2af7}, - {0x2af9, 0x2afa}, - {0x2afa, 0x2af9}, - {0x2bfe, 0x221f}, - {0x2e02, 0x2e03}, - {0x2e03, 0x2e02}, - {0x2e04, 0x2e05}, - {0x2e05, 0x2e04}, - {0x2e09, 0x2e0a}, - {0x2e0a, 0x2e09}, - {0x2e0c, 0x2e0d}, - {0x2e0d, 0x2e0c}, - {0x2e1c, 0x2e1d}, - {0x2e1d, 0x2e1c}, - {0x2e20, 0x2e21}, - {0x2e21, 0x2e20}, - {0x2e22, 0x2e23}, - {0x2e23, 0x2e22}, - {0x2e24, 0x2e25}, - {0x2e25, 0x2e24}, - {0x2e26, 0x2e27}, - {0x2e27, 0x2e26}, - {0x2e28, 0x2e29}, - {0x2e29, 0x2e28}, - {0x2e55, 0x2e56}, - {0x2e56, 0x2e55}, - {0x2e57, 0x2e58}, - {0x2e58, 0x2e57}, - {0x2e59, 0x2e5a}, - {0x2e5a, 0x2e59}, - {0x2e5b, 0x2e5c}, - {0x2e5c, 0x2e5b}, - {0x3008, 0x3009}, - {0x3009, 0x3008}, - {0x300a, 0x300b}, - {0x300b, 0x300a}, - {0x300c, 0x300d}, - {0x300d, 0x300c}, - {0x300e, 0x300f}, - {0x300f, 0x300e}, - {0x3010, 0x3011}, - {0x3011, 0x3010}, - {0x3014, 0x3015}, - {0x3015, 0x3014}, - {0x3016, 0x3017}, - {0x3017, 0x3016}, - {0x3018, 0x3019}, - {0x3019, 0x3018}, - {0x301a, 0x301b}, - {0x301b, 0x301a}, - {0xfe59, 0xfe5a}, - {0xfe5a, 0xfe59}, - {0xfe5b, 0xfe5c}, - {0xfe5c, 0xfe5b}, - {0xfe5d, 0xfe5e}, - {0xfe5e, 0xfe5d}, - {0xfe64, 0xfe65}, - {0xfe65, 0xfe64}, - {0xff08, 0xff09}, - {0xff09, 0xff08}, - {0xff1c, 0xff1e}, - {0xff1e, 0xff1c}, - {0xff3b, 0xff3d}, - {0xff3d, 0xff3b}, - {0xff5b, 0xff5d}, - {0xff5d, 0xff5b}, - {0xff5f, 0xff60}, - {0xff60, 0xff5f}, - {0xff62, 0xff63}, - {0xff63, 0xff62}, + #include "unicode/bidi_mirror.h" }; int i, j, k; @@ -2157,41 +395,6 @@ static unsigned mirror_glyph(unsigned int ch) /* * Identify the bracket characters treated specially by bidi rule * BD19, and return their paired character(s). - * - * The data table in this function is constructed from the Unicode - * Character Database version 14.0.0, downloadable from unicode.org at - * the URL - * - * https://www.unicode.org/Public/14.0.0/ucd/ - * - * by the following fragment of Perl: - -perl -e ' - open BIDIBRACKETS, "<", $ARGV[0] or die; - while () { - chomp; s{\s}{}g; s{#.*$}{}; next unless /./; - @_ = split /;/, $_; - $src = hex $_[0]; $dst = hex $_[1]; $kind = $_[2]; - $m{$src}=[$kind, $dst]; - } - open UNICODEDATA, "<", $ARGV[1] or die; - while () { - chomp; @_ = split /;/, $_; - $src = hex $_[0]; next unless defined $m{$src}; - if ($_[5] =~ /^[0-9a-f]+$/i) { - $equiv = hex $_[5]; - $e{$src} = $equiv; - $e{$equiv} = $src; - } - } - for $src (sort {$a <=> $b} keys %m) { - ($kind, $dst) = @{$m{$src}}; - $equiv = 0 + $e{$dst}; - printf " {0x%04x, {0x%04x, 0x%04x, %s}},\n", $src, $dst, $equiv, - $kind eq "c" ? "BT_CLOSE" : "BT_OPEN"; - } -' BidiBrackets.txt UnicodeData.txt - */ typedef enum { BT_NONE, BT_OPEN, BT_CLOSE } BracketType; typedef struct BracketTypeData { @@ -2204,134 +407,7 @@ static BracketTypeData bracket_type(unsigned int ch) unsigned src; BracketTypeData payload; } bracket_pairs[] = { - {0x0028, {0x0029, 0x0000, BT_OPEN}}, - {0x0029, {0x0028, 0x0000, BT_CLOSE}}, - {0x005b, {0x005d, 0x0000, BT_OPEN}}, - {0x005d, {0x005b, 0x0000, BT_CLOSE}}, - {0x007b, {0x007d, 0x0000, BT_OPEN}}, - {0x007d, {0x007b, 0x0000, BT_CLOSE}}, - {0x0f3a, {0x0f3b, 0x0000, BT_OPEN}}, - {0x0f3b, {0x0f3a, 0x0000, BT_CLOSE}}, - {0x0f3c, {0x0f3d, 0x0000, BT_OPEN}}, - {0x0f3d, {0x0f3c, 0x0000, BT_CLOSE}}, - {0x169b, {0x169c, 0x0000, BT_OPEN}}, - {0x169c, {0x169b, 0x0000, BT_CLOSE}}, - {0x2045, {0x2046, 0x0000, BT_OPEN}}, - {0x2046, {0x2045, 0x0000, BT_CLOSE}}, - {0x207d, {0x207e, 0x0000, BT_OPEN}}, - {0x207e, {0x207d, 0x0000, BT_CLOSE}}, - {0x208d, {0x208e, 0x0000, BT_OPEN}}, - {0x208e, {0x208d, 0x0000, BT_CLOSE}}, - {0x2308, {0x2309, 0x0000, BT_OPEN}}, - {0x2309, {0x2308, 0x0000, BT_CLOSE}}, - {0x230a, {0x230b, 0x0000, BT_OPEN}}, - {0x230b, {0x230a, 0x0000, BT_CLOSE}}, - {0x2329, {0x232a, 0x3009, BT_OPEN}}, - {0x232a, {0x2329, 0x3008, BT_CLOSE}}, - {0x2768, {0x2769, 0x0000, BT_OPEN}}, - {0x2769, {0x2768, 0x0000, BT_CLOSE}}, - {0x276a, {0x276b, 0x0000, BT_OPEN}}, - {0x276b, {0x276a, 0x0000, BT_CLOSE}}, - {0x276c, {0x276d, 0x0000, BT_OPEN}}, - {0x276d, {0x276c, 0x0000, BT_CLOSE}}, - {0x276e, {0x276f, 0x0000, BT_OPEN}}, - {0x276f, {0x276e, 0x0000, BT_CLOSE}}, - {0x2770, {0x2771, 0x0000, BT_OPEN}}, - {0x2771, {0x2770, 0x0000, BT_CLOSE}}, - {0x2772, {0x2773, 0x0000, BT_OPEN}}, - {0x2773, {0x2772, 0x0000, BT_CLOSE}}, - {0x2774, {0x2775, 0x0000, BT_OPEN}}, - {0x2775, {0x2774, 0x0000, BT_CLOSE}}, - {0x27c5, {0x27c6, 0x0000, BT_OPEN}}, - {0x27c6, {0x27c5, 0x0000, BT_CLOSE}}, - {0x27e6, {0x27e7, 0x0000, BT_OPEN}}, - {0x27e7, {0x27e6, 0x0000, BT_CLOSE}}, - {0x27e8, {0x27e9, 0x0000, BT_OPEN}}, - {0x27e9, {0x27e8, 0x0000, BT_CLOSE}}, - {0x27ea, {0x27eb, 0x0000, BT_OPEN}}, - {0x27eb, {0x27ea, 0x0000, BT_CLOSE}}, - {0x27ec, {0x27ed, 0x0000, BT_OPEN}}, - {0x27ed, {0x27ec, 0x0000, BT_CLOSE}}, - {0x27ee, {0x27ef, 0x0000, BT_OPEN}}, - {0x27ef, {0x27ee, 0x0000, BT_CLOSE}}, - {0x2983, {0x2984, 0x0000, BT_OPEN}}, - {0x2984, {0x2983, 0x0000, BT_CLOSE}}, - {0x2985, {0x2986, 0x0000, BT_OPEN}}, - {0x2986, {0x2985, 0x0000, BT_CLOSE}}, - {0x2987, {0x2988, 0x0000, BT_OPEN}}, - {0x2988, {0x2987, 0x0000, BT_CLOSE}}, - {0x2989, {0x298a, 0x0000, BT_OPEN}}, - {0x298a, {0x2989, 0x0000, BT_CLOSE}}, - {0x298b, {0x298c, 0x0000, BT_OPEN}}, - {0x298c, {0x298b, 0x0000, BT_CLOSE}}, - {0x298d, {0x2990, 0x0000, BT_OPEN}}, - {0x298e, {0x298f, 0x0000, BT_CLOSE}}, - {0x298f, {0x298e, 0x0000, BT_OPEN}}, - {0x2990, {0x298d, 0x0000, BT_CLOSE}}, - {0x2991, {0x2992, 0x0000, BT_OPEN}}, - {0x2992, {0x2991, 0x0000, BT_CLOSE}}, - {0x2993, {0x2994, 0x0000, BT_OPEN}}, - {0x2994, {0x2993, 0x0000, BT_CLOSE}}, - {0x2995, {0x2996, 0x0000, BT_OPEN}}, - {0x2996, {0x2995, 0x0000, BT_CLOSE}}, - {0x2997, {0x2998, 0x0000, BT_OPEN}}, - {0x2998, {0x2997, 0x0000, BT_CLOSE}}, - {0x29d8, {0x29d9, 0x0000, BT_OPEN}}, - {0x29d9, {0x29d8, 0x0000, BT_CLOSE}}, - {0x29da, {0x29db, 0x0000, BT_OPEN}}, - {0x29db, {0x29da, 0x0000, BT_CLOSE}}, - {0x29fc, {0x29fd, 0x0000, BT_OPEN}}, - {0x29fd, {0x29fc, 0x0000, BT_CLOSE}}, - {0x2e22, {0x2e23, 0x0000, BT_OPEN}}, - {0x2e23, {0x2e22, 0x0000, BT_CLOSE}}, - {0x2e24, {0x2e25, 0x0000, BT_OPEN}}, - {0x2e25, {0x2e24, 0x0000, BT_CLOSE}}, - {0x2e26, {0x2e27, 0x0000, BT_OPEN}}, - {0x2e27, {0x2e26, 0x0000, BT_CLOSE}}, - {0x2e28, {0x2e29, 0x0000, BT_OPEN}}, - {0x2e29, {0x2e28, 0x0000, BT_CLOSE}}, - {0x2e55, {0x2e56, 0x0000, BT_OPEN}}, - {0x2e56, {0x2e55, 0x0000, BT_CLOSE}}, - {0x2e57, {0x2e58, 0x0000, BT_OPEN}}, - {0x2e58, {0x2e57, 0x0000, BT_CLOSE}}, - {0x2e59, {0x2e5a, 0x0000, BT_OPEN}}, - {0x2e5a, {0x2e59, 0x0000, BT_CLOSE}}, - {0x2e5b, {0x2e5c, 0x0000, BT_OPEN}}, - {0x2e5c, {0x2e5b, 0x0000, BT_CLOSE}}, - {0x3008, {0x3009, 0x232a, BT_OPEN}}, - {0x3009, {0x3008, 0x2329, BT_CLOSE}}, - {0x300a, {0x300b, 0x0000, BT_OPEN}}, - {0x300b, {0x300a, 0x0000, BT_CLOSE}}, - {0x300c, {0x300d, 0x0000, BT_OPEN}}, - {0x300d, {0x300c, 0x0000, BT_CLOSE}}, - {0x300e, {0x300f, 0x0000, BT_OPEN}}, - {0x300f, {0x300e, 0x0000, BT_CLOSE}}, - {0x3010, {0x3011, 0x0000, BT_OPEN}}, - {0x3011, {0x3010, 0x0000, BT_CLOSE}}, - {0x3014, {0x3015, 0x0000, BT_OPEN}}, - {0x3015, {0x3014, 0x0000, BT_CLOSE}}, - {0x3016, {0x3017, 0x0000, BT_OPEN}}, - {0x3017, {0x3016, 0x0000, BT_CLOSE}}, - {0x3018, {0x3019, 0x0000, BT_OPEN}}, - {0x3019, {0x3018, 0x0000, BT_CLOSE}}, - {0x301a, {0x301b, 0x0000, BT_OPEN}}, - {0x301b, {0x301a, 0x0000, BT_CLOSE}}, - {0xfe59, {0xfe5a, 0x0000, BT_OPEN}}, - {0xfe5a, {0xfe59, 0x0000, BT_CLOSE}}, - {0xfe5b, {0xfe5c, 0x0000, BT_OPEN}}, - {0xfe5c, {0xfe5b, 0x0000, BT_CLOSE}}, - {0xfe5d, {0xfe5e, 0x0000, BT_OPEN}}, - {0xfe5e, {0xfe5d, 0x0000, BT_CLOSE}}, - {0xff08, {0xff09, 0x0000, BT_OPEN}}, - {0xff09, {0xff08, 0x0000, BT_CLOSE}}, - {0xff3b, {0xff3d, 0x0000, BT_OPEN}}, - {0xff3d, {0xff3b, 0x0000, BT_CLOSE}}, - {0xff5b, {0xff5d, 0x0000, BT_OPEN}}, - {0xff5d, {0xff5b, 0x0000, BT_CLOSE}}, - {0xff5f, {0xff60, 0x0000, BT_OPEN}}, - {0xff60, {0xff5f, 0x0000, BT_CLOSE}}, - {0xff62, {0xff63, 0x0000, BT_OPEN}}, - {0xff63, {0xff62, 0x0000, BT_CLOSE}}, + #include "unicode/bidi_brackets.h" }; int i, j, k; diff --git a/terminal/lineedit.c b/terminal/lineedit.c new file mode 100644 index 00000000..eaaa3a18 --- /dev/null +++ b/terminal/lineedit.c @@ -0,0 +1,520 @@ +/* + * Implementation of local line editing. Used during username and + * password input at login time, and also by ldisc during the main + * session (if the session's virtual terminal is in that mode). + * + * Because we're tied into a real GUI terminal (and not a completely + * standalone line-discipline module that deals purely with byte + * streams), we can support a slightly richer input interface than + * plain bytes. + * + * In particular, the 'dedicated' flag sent along with every byte is + * used to distinguish control codes input via Ctrl+letter from the + * same code input by a dedicated key like Return or Backspace. This + * allows us to interpret the Ctrl+letter key combination as inputting + * a literal control character to go into the line buffer, and the + * dedicated-key version as performing an editing function. + */ + +#include "putty.h" +#include "terminal.h" + +typedef struct BufChar BufChar; + +struct TermLineEditor { + Terminal *term; + BufChar *head, *tail; + unsigned flags; + bool quote_next_char; + TermLineEditorCallbackReceiver *receiver; +}; + +struct BufChar { + BufChar *prev, *next; + + /* The bytes of the character, to be sent on the wire */ + char wire[6]; + uint8_t nwire; + + /* Whether this character is considered complete */ + bool complete; + + /* Width of the character when it was displayed, in terminal cells */ + uint8_t width; + + /* Whether this character counts as whitespace, for ^W purposes */ + bool space; +}; + +TermLineEditor *lineedit_new(Terminal *term, unsigned flags, + TermLineEditorCallbackReceiver *receiver) +{ + TermLineEditor *le = snew(TermLineEditor); + le->term = term; + le->head = le->tail = NULL; + le->flags = flags; + le->quote_next_char = false; + le->receiver = receiver; + return le; +} + +static void bufchar_free(BufChar *bc) +{ + smemclr(bc, sizeof(*bc)); + sfree(bc); +} + +static void lineedit_free_buffer(TermLineEditor *le) +{ + while (le->head) { + BufChar *bc = le->head; + le->head = bc->next; + bufchar_free(bc); + } + le->tail = NULL; +} + +void lineedit_free(TermLineEditor *le) +{ + lineedit_free_buffer(le); + sfree(le); +} + +void lineedit_modify_flags(TermLineEditor *le, unsigned clr, unsigned flip) +{ + le->flags &= ~clr; + le->flags ^= flip; +} + +static void lineedit_term_write(TermLineEditor *le, ptrlen data) +{ + le->receiver->vt->to_terminal(le->receiver, data); +} + +static void lineedit_term_newline(TermLineEditor *le) +{ + lineedit_term_write(le, PTRLEN_LITERAL("\x0D\x0A")); +} + +static inline void lineedit_send_data(TermLineEditor *le, ptrlen data) +{ + le->receiver->vt->to_backend(le->receiver, data); +} + +static inline void lineedit_special(TermLineEditor *le, + SessionSpecialCode code, int arg) +{ + le->receiver->vt->special(le->receiver, code, arg); +} + +static inline void lineedit_send_newline(TermLineEditor *le) +{ + le->receiver->vt->newline(le->receiver); +} + +static void lineedit_delete_char(TermLineEditor *le) +{ + if (le->tail) { + BufChar *bc = le->tail; + le->tail = bc->prev; + if (!le->tail) + le->head = NULL; + else + le->tail->next = NULL; + + for (unsigned i = 0; i < bc->width; i++) + lineedit_term_write(le, PTRLEN_LITERAL("\x08 \x08")); + + bufchar_free(bc); + } +} + +static void lineedit_delete_word(TermLineEditor *le) +{ + /* + * Deleting a word stops at the _start_ of a word, i.e. at any + * boundary with a space on the left and a non-space on the right. + */ + if (!le->tail) + return; + + while (true) { + bool deleted_char_is_space = le->tail->space; + lineedit_delete_char(le); + if (!le->tail) + break; /* we've cleared the whole line */ + if (le->tail->space && !deleted_char_is_space) + break; /* we've just reached a word boundary */ + } +} + +static void lineedit_delete_line(TermLineEditor *le) +{ + while (le->tail) + lineedit_delete_char(le); + lineedit_special(le, SS_EL, 0); +} + +void lineedit_send_line(TermLineEditor *le) +{ + for (BufChar *bc = le->head; bc; bc = bc->next) + lineedit_send_data(le, make_ptrlen(bc->wire, bc->nwire)); + lineedit_free_buffer(le); + le->quote_next_char = false; +} + +static void lineedit_complete_line(TermLineEditor *le) +{ + lineedit_term_newline(le); + lineedit_send_line(le); + lineedit_send_newline(le); +} + +/* + * Send data to the terminal to display a BufChar. As a side effect, + * update bc->width to indicate how many character cells we think were + * taken up by what we just wrote. No other change to bc is made. + */ +static void lineedit_display_bufchar(TermLineEditor *le, BufChar *bc, + unsigned chr) +{ + char buf[6]; + buffer_sink bs[1]; + buffer_sink_init(bs, buf, lenof(buf)); + + /* Handle single-byte character set translation. */ + if (!in_utf(le->term) && DIRECT_CHAR(chr)) { + /* + * If we're not in UTF-8, i.e. we're in a single-byte + * character set, then first we must feed the input byte + * through term_translate, which will tell us whether it's a + * control character or not. (That varies with the charset: + * e.g. ISO 8859-1 and Win1252 disagree on a lot of + * 0x80-0x9F). + * + * In principle, we could pass NULL as our term_utf8_decode + * pointer, on the grounds that since the terminal isn't in + * UTF-8 mode term_translate shouldn't access it. But that + * seems needlessly reckless; we'll make up an empty one. + */ + term_utf8_decode dummy_utf8 = { .state = 0, .chr = 0, .size = 0 }; + chr = term_translate( + le->term, &dummy_utf8, (unsigned char)chr); + + /* + * After that, chr will be either a control-character value + * (00-1F, 7F, 80-9F), or a byte value ORed with one of the + * CSET_FOO character set indicators. The latter indicates + * that it's a printing character in this charset, in which + * case it takes up one character cell. + */ + if (chr & CSET_MASK) { + put_byte(bs, chr); + bc->width = 1; + goto got_char; + } + } + + /* + * If we got here without taking the 'goto' above, then we're now + * holding an actual Unicode character. + */ + assert(!IS_SURROGATE(chr)); /* and it should be an _actual_ one */ + + /* + * Deal with symbolic representations of control characters. + */ + + if (chr < 0x20 || chr == 0x7F) { + /* + * Represent C0 controls as '^C' or similar, and 7F as ^?. + */ + put_byte(bs, '^'); + put_byte(bs, chr ^ 0x40); + bc->width = 2; + goto got_char; + } + + if (chr >= 0x80 && chr < 0xA0) { + /* + * Represent C1 controls as <9B> or similar. + */ + put_fmt(bs, "<%02X>", chr); + bc->width = 4; + goto got_char; + } + + /* + * And if we get _here_, we're holding a printing (or at least not + * _control_, even if zero-width) Unicode character, which _must_ + * mean that the terminal is currently in UTF-8 mode (since if it + * were not then printing characters would have gone through the + * term_translate case above). So we can just write the UTF-8 for + * the character - but we must also pay attention to its width in + * character cells, which might be 0, 1 or 2. + */ + assert(in_utf(le->term)); + put_utf8_char(bs, chr); + bc->width = term_char_width(le->term, chr); + + got_char: + lineedit_term_write(le, make_ptrlen(buf, bs->out - buf)); +} + +/* Called when we've just added a byte to a UTF-8 character and want + * to see if it's complete */ +static void lineedit_check_utf8_complete(TermLineEditor *le, BufChar *bc) +{ + BinarySource src[1]; + BinarySource_BARE_INIT(src, bc->wire, bc->nwire); + DecodeUTF8Failure err; + unsigned chr = decode_utf8(src, &err); + if (err == DUTF8_E_OUT_OF_DATA) + return; /* not complete yet */ + + /* Any other error code is regarded as complete, and we just + * display the character as the U+FFFD that decode_utf8 will have + * returned anyway */ + bc->complete = true; + bc->space = (chr == ' '); + lineedit_display_bufchar(le, bc, chr); +} + +static void lineedit_input_printing_char(TermLineEditor *le, char ch); + +static void lineedit_redraw_line(TermLineEditor *le) +{ + /* FIXME: I'm not 100% sure this is the behaviour I really want in + * this situation, but it's at least very simple to implement */ + BufChar *prevhead = le->head; + le->head = le->tail = NULL; + while (prevhead) { + BufChar *bc = prevhead; + prevhead = prevhead->next; + + for (unsigned i = 0; i < bc->nwire; i++) + lineedit_input_printing_char(le, bc->wire[i]); + bufchar_free(bc); + } +} + +#define CTRL(c) ((char) (0x40 ^ (unsigned char)c)) + +void lineedit_input(TermLineEditor *le, char ch, bool dedicated) +{ + if (le->quote_next_char) { + /* + * If the previous keypress was ^V, 'quoting' the next + * character to be treated literally, then skip all the + * editing-control processing, and clear that flag. + */ + le->quote_next_char = false; + } else { + /* + * Input events that are only valid with the 'dedicated' flag. + * These are limited to the control codes that _have_ + * dedicated keys. + * + * Any case we actually handle here ends with a 'return' + * statement, so that if we fall out of the end of this switch + * at all, it's because the byte hasn't been handled here and + * will fall into the next switch dealing with ordinary input. + */ + if (dedicated) { + switch (ch) { + /* + * The Backspace key. + * + * Since our terminal can be configured to send either + * ^H or 7F (aka ^?) via the backspace key, we accept + * both. + * + * (We could query the Terminal's configuration here + * and accept only the one of those codes that the + * terminal is currently set to. But it's pointless, + * because whichever one the terminal isn't set to, + * the front end won't be sending it with + * dedicated=true anyway.) + */ + case CTRL('H'): + case 0x7F: + lineedit_delete_char(le); + return; + + /* + * The Return key. + */ + case CTRL('M'): + lineedit_complete_line(le); + return; + } + } + + /* + * Editing and special functions in response to ordinary keys + * or Ctrl+key combinations. Many editing functions have to be + * supported in this mode, like ^W and ^U, because there are + * no dedicated keys that generate the same control codes + * anyway. + * + * Again, we return if the key was handled. The final + * processing of ordinary data to go into the input buffer + * happens if we break from this switch. + */ + switch (ch) { + case CTRL('W'): + lineedit_delete_word(le); + return; + + case CTRL('U'): + lineedit_delete_line(le); + return; + + case CTRL('['): + if (!(le->flags & LE_ESC_ERASES)) + break; /* treat as normal input */ + lineedit_delete_line(le); + return; + + case CTRL('R'): + lineedit_term_write(le, PTRLEN_LITERAL("^R")); + lineedit_term_newline(le); + lineedit_redraw_line(le); + return; + + case CTRL('V'): + le->quote_next_char = true; + return; + + case CTRL('C'): + lineedit_delete_line(le); + if (!(le->flags & LE_INTERRUPT)) + break; /* treat as normal input */ + lineedit_special(le, SS_IP, 0); + return; + + case CTRL('Z'): + lineedit_delete_line(le); + if (!(le->flags & LE_SUSPEND)) + break; /* treat as normal input */ + lineedit_special(le, SS_SUSP, 0); + return; + + case CTRL('\\'): + lineedit_delete_line(le); + if (!(le->flags & LE_ABORT)) + break; /* treat as normal input */ + lineedit_special(le, SS_ABORT, 0); + return; + + case CTRL('D'): + if (le->flags & LE_EOF_ALWAYS) { + /* Our client wants to treat ^D / EOF as a special + * character in their own way. Just send an EOF + * special. */ + lineedit_special(le, SS_EOF, 0); + return; + } + + /* + * Otherwise, ^D has the same behaviour as in Unix tty + * line editing: if the edit buffer is non-empty then it's + * sent immediately without a newline, and if it is empty + * then an EOF is sent. + */ + if (le->head) { + lineedit_send_line(le); + return; + } + + lineedit_special(le, SS_EOF, 0); + return; + + case CTRL('J'): + if (le->flags & LE_CRLF_NEWLINE) { + /* + * If the previous character in the buffer is a + * literal Ctrl-M, and now the user sends Ctrl-J, then + * we amalgamate both into a newline event. + */ + if (le->tail && le->tail->nwire == 1 && + le->tail->wire[0] == CTRL('M')) { + lineedit_delete_char(le); /* erase ^J from buffer */ + lineedit_complete_line(le); + return; + } + } + } + } + + /* + * If we get to here, we've exhausted the options for treating our + * character as an editing or special function of any kind. Treat + * it as a printing character, or part of one. + */ + lineedit_input_printing_char(le, ch); +} + +static void lineedit_input_printing_char(TermLineEditor *le, char ch) +{ + /* + * Append ch to the line buffer, either as a new BufChar or by + * adding it to a previous one containing an as yet incomplete + * UTF-8 encoding. + */ + if (le->tail && !le->tail->complete) { + BufChar *bc = le->tail; + + /* + * If we're in UTF-8 mode, and ch is a UTF-8 continuation + * byte, then we can append it to bc, which we've just checked + * is missing at least one of those. + */ + if (in_utf(le->term) && (unsigned char)ch - 0x80U < 0x40) { + assert(bc->nwire < lenof(bc->wire)); + bc->wire[bc->nwire++] = ch; + lineedit_check_utf8_complete(le, bc); + return; + } + + /* + * Otherwise, the previous incomplete character can't be + * extended. Mark it as complete, and if possible, display it + * as a replacement character indicating that something weird + * happened. + */ + bc->complete = true; + if (in_utf(le->term)) + lineedit_display_bufchar(le, bc, 0xFFFD); + + /* + * But we still haven't processed the byte we're holding. Fall + * through to the next step, where we make a fresh BufChar for + * it. + */ + } + + /* + * Make a fresh BufChar. + */ + BufChar *bc = snew(BufChar); + bc->prev = le->tail; + le->tail = bc; + if (bc->prev) + bc->prev->next = bc; + else + le->head = bc; + bc->next = NULL; + bc->complete = false; + bc->space = false; + + bc->nwire = 1; + bc->wire[0] = ch; + if (in_utf(le->term)) { + lineedit_check_utf8_complete(le, bc); + } else { + bc->complete = true; /* always, in a single-byte charset */ + bc->space = (bc->wire[0] == ' '); + lineedit_display_bufchar(le, bc, CSET_ASCII | (unsigned char)ch); + } +} diff --git a/terminal/terminal.c b/terminal/terminal.c index 90c2880d..e127ff6e 100644 --- a/terminal/terminal.c +++ b/terminal/terminal.c @@ -62,6 +62,9 @@ static const char sco2ansicolour[] = { 0, 4, 2, 6, 1, 5, 3, 7 }; #define sel_nl_sz (sizeof(sel_nl)/sizeof(wchar_t)) static const wchar_t sel_nl[] = SEL_NL; +/* forward declaration */ +static void term_userpass_state_free(struct term_userpass_state *s); + /* * Fetch the character at a particular position in a line array, * for purposes of `wordtype'. The reason this isn't just a simple @@ -86,8 +89,7 @@ static const wchar_t sel_nl[] = SEL_NL; * Internal prototypes. */ static void resizeline(Terminal *, termline *, int); -static termline *lineptr(Terminal *, int, int, int); -static void unlineptr(termline *); +static termline *lineptr(Terminal *, int, int); static void check_line_size(Terminal *, termline *); static void do_paint(Terminal *); static void erase_lots(Terminal *, bool, bool, bool); @@ -128,7 +130,7 @@ static void freetermline(termline *line) } } -static void unlineptr(termline *line) +void term_release_line(termline *line) { if (line->temporary) freetermline(line); @@ -1168,12 +1170,19 @@ static void null_line_error(Terminal *term, int y, int lineno, term->alt_sblines, whichtree, treeindex, commitid); } +static inline int checkscr(int y, int lineno) +{ + if (y < 0) + modalfatalbox("screen line %d < 0 in terminal.c:%d", y, lineno); + return y; +} + /* * Retrieve a line of the screen or of the scrollback, according to * whether the y coordinate is non-negative or negative * (respectively). */ -static termline *lineptr(Terminal *term, int y, int lineno, int screen) +static termline *lineptr(Terminal *term, int y, int lineno) { termline *line; tree234 *whichtree; @@ -1185,8 +1194,6 @@ static termline *lineptr(Terminal *term, int y, int lineno, int screen) } else { int altlines = 0; - assert(!screen); - if (term->erase_to_scrollback && term->alt_which && term->alt_screen) { altlines = term->alt_sblines; @@ -1237,8 +1244,29 @@ static termline *lineptr(Terminal *term, int y, int lineno, int screen) return line; } -#define lineptr(x) (lineptr)(term,x,__LINE__,0) -#define scrlineptr(x) (lineptr)(term,x,__LINE__,1) +/* + * Macro wrappers for lineptr. The distinction between lineptr and + * scrlineptr is that lineptr can retrieve any line, from the screen + * _or_ from scrollback, and in return, you have to call unlineptr + * when you're done with it, in case it was a dynamically allocated + * line decompressed from scrollback that needs freeing. But + * scrlineptr will only retrieve lines from the active screen (and + * enforces this by an assertion), which means it's always just + * returning a pointer to an existing unpacked termline, and you don't + * have to call unlineptr afterwards. So drawing code (which might + * need the scrollback) will have to call lineptr/unlineptr, but + * update code during term_out can call scrlineptr. + * + * The 'assertion' in scrlineptr is done using a helper function that + * returns the input column number, which allows this macro to avoid + * double-evaluating its argument. + */ +#define lineptr(x) (lineptr)(term,x,__LINE__) +#define scrlineptr(x) (lineptr)(term,checkscr(x,__LINE__),__LINE__) +#define unlineptr(line) term_release_line(line) + +/* Wrapper for external use (e.g. tests), without the __LINE__ parameter */ +termline *term_get_line(Terminal *term, int y) { return lineptr(y); } /* * Coerce a termline to the terminal's current width. Unlike the @@ -1611,6 +1639,7 @@ static void term_copy_stuff_from_conf(Terminal *term) term->bellovl_s = conf_get_int(term->conf, CONF_bellovl_s); term->bellovl_t = conf_get_int(term->conf, CONF_bellovl_t); term->no_bidi = conf_get_bool(term->conf, CONF_no_bidi); + term->no_bracketed_paste = conf_get_bool(term->conf, CONF_no_bracketed_paste); term->bksp_is_delete = conf_get_bool(term->conf, CONF_bksp_is_delete); term->blink_cur = conf_get_bool(term->conf, CONF_blink_cur); term->blinktext = conf_get_bool(term->conf, CONF_blinktext); @@ -1652,19 +1681,17 @@ static void term_copy_stuff_from_conf(Terminal *term) */ { char *answerback = conf_get_str(term->conf, CONF_answerback); - int maxlen = strlen(answerback); - term->answerback = snewn(maxlen, char); - term->answerbacklen = 0; + strbuf_clear(term->answerback); while (*answerback) { char *n; char c = ctrlparse(answerback, &n); if (n) { - term->answerback[term->answerbacklen++] = c; + put_byte(term->answerback, c); answerback = n; } else { - term->answerback[term->answerbacklen++] = *answerback++; + put_byte(term->answerback, *answerback++); } } } @@ -1770,8 +1797,13 @@ void term_reconfig(Terminal *term, Conf *conf) conf_free(term->conf); term->conf = conf_copy(conf); - if (reset_wrap) + if (reset_wrap) { term->alt_wrap = term->wrap = conf_get_bool(term->conf, CONF_wrap_mode); + if (!term->wrap) + term->wrapnext = false; + if (!term->alt_wrap) + term->alt_wnext = false; + } if (reset_decom) term->alt_om = term->dec_om = conf_get_bool(term->conf, CONF_dec_om); if (reset_bce) { @@ -2019,104 +2051,47 @@ Terminal *term_init(Conf *myconf, struct unicode_data *ucsdata, TermWin *win) * that need it. */ term = snew(Terminal); + memset(term, 0, sizeof(Terminal)); term->win = win; term->ucsdata = ucsdata; term->conf = conf_copy(myconf); - term->logctx = NULL; term->compatibility_level = TM_PUTTY; strcpy(term->id_string, "\033[?6c"); - term->cblink_pending = term->tblink_pending = false; - term->paste_buffer = NULL; - term->paste_len = 0; bufchain_init(&term->inbuf); bufchain_init(&term->printer_buf); - term->printing = term->only_printing = false; - term->print_job = NULL; - term->vt52_mode = false; - term->cr_lf_return = false; - term->mouse_is_down = 0; - term->reset_132 = false; - term->cblinker = false; - term->tblinker = false; term->has_focus = true; - term->repeat_off = false; term->termstate = TOPLEVEL; term->selstate = NO_SELECTION; - term->curstype = 0; + term->answerback = strbuf_new(); term_copy_stuff_from_conf(term); - term->screen = term->alt_screen = term->scrollback = NULL; - term->tempsblines = 0; - term->alt_sblines = 0; - term->disptop = 0; - term->disptext = NULL; term->dispcursx = term->dispcursy = -1; - term->tabs = NULL; deselect(term); term->rows = term->cols = -1; power_on(term, true); - term->beephead = term->beeptail = NULL; - term->nbeeps = 0; - term->lastbeep = false; - term->beep_overloaded = false; term->attr_mask = 0xffffffff; - term->backend = NULL; - term->in_term_out = false; - term->ltemp = NULL; - term->ltemp_size = 0; - term->wcFrom = NULL; - term->wcTo = NULL; - term->wcFromTo_size = 0; - - term->window_update_pending = false; - term->window_update_cooldown = false; - - term->bidi_cache_size = 0; - term->pre_bidi_cache = term->post_bidi_cache = NULL; /* FULL-TERMCHAR */ term->basic_erase_char.chr = CSET_ASCII | ' '; term->basic_erase_char.attr = ATTR_DEFAULT; - term->basic_erase_char.cc_next = 0; term->basic_erase_char.truecolour.fg = optionalrgb_none; term->basic_erase_char.truecolour.bg = optionalrgb_none; term->erase_char = term->basic_erase_char; - term->last_selected_text = NULL; - term->last_selected_attr = NULL; - term->last_selected_tc = NULL; - term->last_selected_len = 0; /* TermWin implementations will typically extend these with * clipboard ids they know about */ term->mouse_select_clipboards[0] = CLIP_LOCAL; term->n_mouse_select_clipboards = 1; term->mouse_paste_clipboard = CLIP_NULL; - term->last_graphic_char = 0; - term->trusted = true; - term->bracketed_paste_active = false; - term->window_title = dupstr(""); term->icon_title = dupstr(""); term->wintitle_codepage = term->icontitle_codepage = DEFAULT_CODEPAGE; - term->minimised = false; - term->winpos_x = term->winpos_y = 0; - term->winpixsize_x = term->winpixsize_y = 0; - term->win_move_pending = false; term->win_resize_pending = WIN_RESIZE_NO; - term->win_zorder_pending = false; - term->win_minimise_pending = false; - term->win_maximise_pending = false; - term->win_title_pending = false; - term->win_icon_title_pending = false; - term->win_pointer_shape_pending = false; - term->win_refresh_pending = false; - term->win_scrollbar_update_pending = false; - term->win_palette_pending = false; term->bidi_ctx = bidi_new_context(); @@ -2152,14 +2127,14 @@ void term_free(Terminal *term) sfree(beep); } bufchain_clear(&term->inbuf); - if(term->print_job) + if (term->print_job) printer_finish_job(term->print_job); bufchain_clear(&term->printer_buf); sfree(term->paste_buffer); sfree(term->ltemp); sfree(term->wcFrom); sfree(term->wcTo); - sfree(term->answerback); + strbuf_free(term->answerback); for (i = 0; i < term->bidi_cache_size; i++) { sfree(term->pre_bidi_cache[i].chars); @@ -2182,6 +2157,10 @@ void term_free(Terminal *term) bidi_free_context(term->bidi_ctx); + /* In case a term_userpass_state is still around */ + if (term->userpass_state) + term_userpass_state_free(term->userpass_state); + sfree(term); } @@ -3120,6 +3099,8 @@ static void toggle_mode(Terminal *term, int mode, int query, bool state) break; case 7: /* DECAWM: auto wrap */ term->wrap = state; + if (!term->wrap) + term->wrapnext = false; break; case 8: /* DECARM: auto key repeat */ term->repeat_off = !state; @@ -3452,22 +3433,30 @@ static void term_display_graphic_char(Terminal *term, unsigned long c) term->curs.x++; if (term->curs.x >= linecols) { term->curs.x = linecols - 1; - term->wrapnext = true; - if (term->wrap && term->vt52_mode) { - cline->lattr |= LATTR_WRAPPED; - if (term->curs.y == term->marg_b) - scroll(term, term->marg_t, term->marg_b, 1, true); - else if (term->curs.y < term->rows - 1) - term->curs.y++; - term->curs.x = 0; - term->wrapnext = false; + + if (term->wrap) { + if (!term->vt52_mode) { + /* Set the wrapnext flag, so that the next character + * wraps, but this one doesn't. */ + term->wrapnext = true; + } else { + /* VT52 mode expects simpler handling, and we just + * wrap straight away. */ + cline->lattr |= LATTR_WRAPPED; + if (term->curs.y == term->marg_b) + scroll(term, term->marg_t, term->marg_b, 1, true); + else if (term->curs.y < term->rows - 1) + term->curs.y++; + term->curs.x = 0; + term->wrapnext = false; + } } } seen_disp_event(term); } static strbuf *term_input_data_from_unicode( - Terminal *term, const wchar_t *widebuf, int len) + Terminal *term, const wchar_t *widebuf, size_t len) { strbuf *buf = strbuf_new(); @@ -3476,7 +3465,7 @@ static strbuf *term_input_data_from_unicode( * Translate input wide characters into UTF-8 to go in the * terminal's input data queue. */ - for (int i = 0; i < len; i++) { + for (size_t i = 0; i < len; i++) { unsigned long ch = widebuf[i]; if (IS_SURROGATE(ch)) { @@ -3495,8 +3484,7 @@ static strbuf *term_input_data_from_unicode( } } - char utf8_chr[6]; - put_data(buf, utf8_chr, encode_utf8(utf8_chr, ch)); + put_utf8_char(buf, ch); } } else { /* @@ -3509,33 +3497,28 @@ static strbuf *term_input_data_from_unicode( * (But also we must allow space for the trailing NUL that * wc_to_mb will write.) */ - char *bufptr = strbuf_append(buf, len + 1); - int rv; - rv = wc_to_mb(term->ucsdata->line_codepage, 0, widebuf, len, - bufptr, len + 1, NULL); - strbuf_shrink_to(buf, rv < 0 ? 0 : rv); + put_wc_to_mb(buf, term->ucsdata->line_codepage, widebuf, len, ""); } return buf; } static strbuf *term_input_data_from_charset( - Terminal *term, int codepage, const char *str, int len) + Terminal *term, int codepage, const char *str, size_t len) { - strbuf *buf; - if (codepage < 0) { - buf = strbuf_new(); + strbuf *buf = strbuf_new(); put_data(buf, str, len); + return buf; } else { - int widesize = len * 2; /* allow for UTF-16 surrogates */ - wchar_t *widebuf = snewn(widesize, wchar_t); - int widelen = mb_to_wc(codepage, 0, str, len, widebuf, widesize); - buf = term_input_data_from_unicode(term, widebuf, widelen); - sfree(widebuf); + strbuf *wide = strbuf_new(); + put_mb_to_wc(wide, codepage, str, len); + strbuf *buf = term_input_data_from_unicode( + term, (const wchar_t *)wide->s, wide->len / sizeof(wchar_t)); + strbuf_free(wide); + return buf; } - return buf; } static inline void term_bracketed_paste_start(Terminal *term) @@ -3665,10 +3648,6 @@ unsigned long term_translate( if (t > 0x10FFFF) return UCSINVALID; - /* This is currently a TagPhobic application.. */ - if (t >= 0xE0000 && t <= 0xE007F) - return UCSINCOMPLETE; - /* U+FEFF is best seen as a null. */ if (t == 0xFEFF) return UCSINCOMPLETE; @@ -3904,7 +3883,7 @@ static void term_out(Terminal *term, bool called_from_term_data) if (term->ldisc) { strbuf *buf = term_input_data_from_charset( term, DEFAULT_CODEPAGE, - term->answerback, term->answerbacklen); + term->answerback->s, term->answerback->len); ldisc_send(term->ldisc, buf->s, buf->len, false); strbuf_free(buf); } @@ -5095,7 +5074,7 @@ static void term_out(Terminal *term, bool called_from_term_data) int i = def(term->esc_args[0], 1); pos old_curs = term->curs; - for(;i>0 && term->curs.x>0; i--) { + for (; i>0 && term->curs.x>0; i--) { do { term->curs.x--; } while (term->curs.x >0 && @@ -5687,6 +5666,7 @@ static void term_out(Terminal *term, bool called_from_term_data) case 'w': /* Autowrap off */ /* compatibility(ATARI) */ term->wrap = false; + term->wrapnext = false; break; case 'R': @@ -5902,8 +5882,7 @@ static termchar *term_bidi_line(Terminal *term, struct termline *ldata, bidi_char); } - for(it=0; itcols ; it++) - { + for (it=0; itcols ; it++) { unsigned long uc = (ldata->chars[it].chr); switch (uc & CSET_MASK) { @@ -5957,10 +5936,10 @@ static termchar *term_bidi_line(Terminal *term, struct termline *ldata, nbc++; } - if(!term->no_bidi) + if (!term->no_bidi) do_bidi(term->bidi_ctx, term->wcFrom, nbc); - if(!term->no_arabicshaping) { + if (!term->no_arabicshaping) { do_shape(term->wcFrom, term->wcTo, nbc); } else { /* If we're not calling do_shape, we must copy the @@ -6027,6 +6006,10 @@ static void do_paint_draw(Terminal *term, termline *ldata, int x, int y, ldata->lattr, term->basic_erase_char.truecolour); win_draw_trust_sigil(term->win, x, y); } else { + if (ccount == 2 && + IS_REGIONAL_INDICATOR_LETTER(ch[0]) && + IS_REGIONAL_INDICATOR_LETTER(ch[1])) + attr |= ATTR_WIDE | TATTR_COMBINING; win_draw_text(term->win, x, y, ch, ccount, attr, ldata->lattr, tc); if (attr & (TATTR_ACTCURS | TATTR_PASCURS)) win_draw_cursor(term->win, x, y, ch, ccount, @@ -6293,7 +6276,7 @@ static void do_paint(Terminal *term) tc = term->erase_char.truecolour; for (j = 0; j < term->cols; j++) { unsigned long tattr, tchar; - bool break_run, do_copy; + bool break_run, do_copy, next_run_dirty = false; termchar *d = lchars + j; tattr = newline[j].attr; @@ -6329,6 +6312,29 @@ static void do_paint(Terminal *term) (j > 0 && d[-1].cc_next != 0)) break_run = true; + /* + * Break on both sides of a regional indicator letter. + */ + if (IS_REGIONAL_INDICATOR_LETTER(tchar)) { + break_run = true; + if (j+1 < term->cols) { + /* Also, check if there are any changes to whether or + * not we're drawing this and the next character as a + * single flag glyph. */ + bool flag_now = IS_REGIONAL_INDICATOR_LETTER(d[1].chr); + bool flag_before = ( + IS_REGIONAL_INDICATOR_LETTER( + term->disptext[i]->chars[j].chr) && + IS_REGIONAL_INDICATOR_LETTER( + term->disptext[i]->chars[j+1].chr) && + (term->disptext[i]->chars[j].attr & DATTR_STARTRUN)); + if (flag_now != flag_before) + next_run_dirty = true; /* must redraw this flag */ + } + } else if (j>0 && IS_REGIONAL_INDICATOR_LETTER(d[-1].chr)) { + break_run = true; + } + /* * Break on both sides of a trust sigil. */ @@ -6357,7 +6363,7 @@ static void do_paint(Terminal *term) cset = CSET_OF(tchar); if (term->ucsdata->dbcs_screenfont) last_run_dirty = dirty_run; - dirty_run = dirty_line; + dirty_run = dirty_line || next_run_dirty; } do_copy = false; @@ -6436,6 +6442,44 @@ static void do_paint(Terminal *term) copy_termchar(term->disptext[i], j, d); } } + + /* If it's a regional indicator letter, and so is the next + * one, then also step to the next one, keeping the flag + * sequence together. */ + if (IS_REGIONAL_INDICATOR_LETTER(d->chr) && + (j+1 < term->cols && IS_REGIONAL_INDICATOR_LETTER(d[1].chr))) { + j++; + d++; + + /* Set ATTR_WIDE, so that the pair is displayed as one */ + attr |= ATTR_WIDE; + + /* Include the second letter in the text buffer */ + unsigned long rchar = d->chr; +#ifdef PLATFORM_IS_UTF16 + sgrowarrayn(ch, chlen, ccount, 2); + ch[ccount++] = (wchar_t)HIGH_SURROGATE_OF(rchar); + ch[ccount++] = (wchar_t)LOW_SURROGATE_OF(rchar); +#else + sgrowarrayn(ch, chlen, ccount, 1); + ch[ccount++] = (wchar_t)rchar; +#endif + + /* Display the cursor, if it's on the right half */ + if (i == our_curs_y && j == our_curs_x) { + attr |= cursor; + term->disptext[i]->chars[j-1].attr |= cursor; + } + + if (!termchars_equal_override( + &term->disptext[i]->chars[j], + d, rchar, term->disptext[i]->chars[j-1].attr)) + dirty_run = true; + + copy_termchar(term->disptext[i], j, d); + term->disptext[i]->chars[j].attr = + term->disptext[i]->chars[j-1].attr & ~DATTR_STARTRUN; + } } if (dirty_run && ccount > 0) do_paint_draw(term, ldata, start, i, ch, ccount, attr, tc); @@ -6634,10 +6678,6 @@ static void clipme(Terminal *term, pos top, pos bottom, bool rect, bool desel, } while (poslt(top, bottom) && poslt(top, nlpos)) { -#if 0 - char cbuf[16], *p; - sprintf(cbuf, "", (ldata[top.x] & 0xFFFF)); -#else wchar_t cbuf[16], *p; int c; int x = top.x; @@ -6689,26 +6729,26 @@ static void clipme(Terminal *term, pos top, pos bottom, bool rect, bool desel, if (DIRECT_FONT(uc)) { if (c >= ' ' && c != 0x7F) { - char buf[4]; - WCHAR wbuf[4]; - int rv; + char buf[2]; + buffer_sink bs[1]; + buffer_sink_init(bs, cbuf, + sizeof(cbuf) - sizeof(wchar_t)); if (is_dbcs_leadbyte(term->ucsdata->font_codepage, (BYTE) c)) { buf[0] = c; buf[1] = (char) (0xFF & ldata->chars[top.x + 1].chr); - rv = mb_to_wc(term->ucsdata->font_codepage, 0, buf, 2, wbuf, 4); + put_mb_to_wc(bs, term->ucsdata->font_codepage, + buf, 2); top.x++; } else { buf[0] = c; - rv = mb_to_wc(term->ucsdata->font_codepage, 0, buf, 1, wbuf, 4); + put_mb_to_wc(bs, term->ucsdata->font_codepage, + buf, 1); } - if (rv > 0) { - memcpy(cbuf, wbuf, rv * sizeof(wchar_t)); - cbuf[rv] = 0; - } + assert(!bs->overflowed); + *(wchar_t *)bs->out = L'\0'; } } -#endif for (p = cbuf; *p; p++) clip_addchar(&buf, *p, attr, tc); @@ -6819,68 +6859,39 @@ static int wordtype(Terminal *term, int uc) int start, end, ctype; }; static const struct ucsword ucs_words[] = { - { - 128, 160, 0}, { - 161, 191, 1}, { - 215, 215, 1}, { - 247, 247, 1}, { - 0x037e, 0x037e, 1}, /* Greek question mark */ - { - 0x0387, 0x0387, 1}, /* Greek ano teleia */ - { - 0x055a, 0x055f, 1}, /* Armenian punctuation */ - { - 0x0589, 0x0589, 1}, /* Armenian full stop */ - { - 0x0700, 0x070d, 1}, /* Syriac punctuation */ - { - 0x104a, 0x104f, 1}, /* Myanmar punctuation */ - { - 0x10fb, 0x10fb, 1}, /* Georgian punctuation */ - { - 0x1361, 0x1368, 1}, /* Ethiopic punctuation */ - { - 0x166d, 0x166e, 1}, /* Canadian Syl. punctuation */ - { - 0x17d4, 0x17dc, 1}, /* Khmer punctuation */ - { - 0x1800, 0x180a, 1}, /* Mongolian punctuation */ - { - 0x2000, 0x200a, 0}, /* Various spaces */ - { - 0x2070, 0x207f, 2}, /* superscript */ - { - 0x2080, 0x208f, 2}, /* subscript */ - { - 0x200b, 0x27ff, 1}, /* punctuation and symbols */ - { - 0x3000, 0x3000, 0}, /* ideographic space */ - { - 0x3001, 0x3020, 1}, /* ideographic punctuation */ - { - 0x303f, 0x309f, 3}, /* Hiragana */ - { - 0x30a0, 0x30ff, 3}, /* Katakana */ - { - 0x3300, 0x9fff, 3}, /* CJK Ideographs */ - { - 0xac00, 0xd7a3, 3}, /* Hangul Syllables */ - { - 0xf900, 0xfaff, 3}, /* CJK Ideographs */ - { - 0xfe30, 0xfe6b, 1}, /* punctuation forms */ - { - 0xff00, 0xff0f, 1}, /* half/fullwidth ASCII */ - { - 0xff1a, 0xff20, 1}, /* half/fullwidth ASCII */ - { - 0xff3b, 0xff40, 1}, /* half/fullwidth ASCII */ - { - 0xff5b, 0xff64, 1}, /* half/fullwidth ASCII */ - { - 0xfff0, 0xffff, 0}, /* half/fullwidth ASCII */ - { - 0, 0, 0} + {128, 160, 0}, + {161, 191, 1}, + {215, 215, 1}, + {247, 247, 1}, + {0x037e, 0x037e, 1}, /* Greek question mark */ + {0x0387, 0x0387, 1}, /* Greek ano teleia */ + {0x055a, 0x055f, 1}, /* Armenian punctuation */ + {0x0589, 0x0589, 1}, /* Armenian full stop */ + {0x0700, 0x070d, 1}, /* Syriac punctuation */ + {0x104a, 0x104f, 1}, /* Myanmar punctuation */ + {0x10fb, 0x10fb, 1}, /* Georgian punctuation */ + {0x1361, 0x1368, 1}, /* Ethiopic punctuation */ + {0x166d, 0x166e, 1}, /* Canadian Syl. punctuation */ + {0x17d4, 0x17dc, 1}, /* Khmer punctuation */ + {0x1800, 0x180a, 1}, /* Mongolian punctuation */ + {0x2000, 0x200a, 0}, /* Various spaces */ + {0x2070, 0x207f, 2}, /* superscript */ + {0x2080, 0x208f, 2}, /* subscript */ + {0x200b, 0x27ff, 1}, /* punctuation and symbols */ + {0x3000, 0x3000, 0}, /* ideographic space */ + {0x3001, 0x3020, 1}, /* ideographic punctuation */ + {0x303f, 0x309f, 3}, /* Hiragana */ + {0x30a0, 0x30ff, 3}, /* Katakana */ + {0x3300, 0x9fff, 3}, /* CJK Ideographs */ + {0xac00, 0xd7a3, 3}, /* Hangul Syllables */ + {0xf900, 0xfaff, 3}, /* CJK Ideographs */ + {0xfe30, 0xfe6b, 1}, /* punctuation forms */ + {0xff00, 0xff0f, 1}, /* half/fullwidth ASCII */ + {0xff1a, 0xff20, 1}, /* half/fullwidth ASCII */ + {0xff3b, 0xff40, 1}, /* half/fullwidth ASCII */ + {0xff5b, 0xff64, 1}, /* half/fullwidth ASCII */ + {0xfff0, 0xffff, 0}, /* half/fullwidth ASCII */ + {0, 0, 0} }; const struct ucsword *wptr; @@ -7057,7 +7068,7 @@ static void term_paste_callback(void *vterm) return; while (term->paste_pos < term->paste_len) { - int n = 0; + size_t n = 0; while (n + term->paste_pos < term->paste_len) { if (term->paste_buffer[term->paste_pos + n++] == '\015') break; @@ -7092,7 +7103,7 @@ static bool wstartswith(const wchar_t *a, size_t alen, return alen >= blen && !wcsncmp(a, b, blen); } -void term_do_paste(Terminal *term, const wchar_t *data, int len) +void term_do_paste(Terminal *term, const wchar_t *data, size_t len) { const wchar_t *p; bool paste_controls = conf_get_bool(term->conf, CONF_paste_controls); @@ -7111,7 +7122,7 @@ void term_do_paste(Terminal *term, const wchar_t *data, int len) term->paste_pos = term->paste_len = 0; term->paste_buffer = snewn(len + 12, wchar_t); - if (term->bracketed_paste) + if (term->bracketed_paste && !term->no_bracketed_paste) term_bracketed_paste_start(term); p = data; @@ -7166,6 +7177,7 @@ void term_do_paste(Terminal *term, const wchar_t *data, int len) if (term->ldisc) { strbuf *buf = term_input_data_from_unicode( term, term->paste_buffer, term->paste_len); + assert(buf->len <= INT_MAX); /* because paste_len was also small */ term_keyinput_internal(term, buf->s, buf->len, false); strbuf_free(buf); } @@ -7256,8 +7268,7 @@ void term_mouse(Terminal *term, Mouse_Button braw, Mouse_Button bcooked, (term->selstate != ABOUT_TO) && (term->selstate != DRAGGING)) { int encstate = 0, r, c; bool wheel; - char abuf[32]; - int len = 0; + char *response = NULL; if (term->ldisc) { @@ -7348,14 +7359,18 @@ void term_mouse(Terminal *term, Mouse_Button braw, Mouse_Button bcooked, /* Check the extensions in decreasing order of preference. Encoding the release event above assumes that 1006 comes first. */ if (term->xterm_extended_mouse) { - len = sprintf(abuf, "\033[<%d;%d;%d%c", encstate, c, r, a == MA_RELEASE ? 'm' : 'M'); + response = dupprintf("\033[<%d;%d;%d%c", encstate, c, r, + a == MA_RELEASE ? 'm' : 'M'); } else if (term->urxvt_extended_mouse) { - len = sprintf(abuf, "\033[%d;%d;%dM", encstate + 32, c, r); + response = dupprintf("\033[%d;%d;%dM", encstate + 32, c, r); } else if (c <= 223 && r <= 223) { - len = sprintf(abuf, "\033[M%c%c%c", encstate + 32, c + 32, r + 32); + response = dupprintf("\033[M%c%c%c", encstate + 32, + c + 32, r + 32); + } + if (response) { + ldisc_send(term->ldisc, response, strlen(response), false); + sfree(response); } - if (len > 0) - ldisc_send(term->ldisc, abuf, len, false); } return; } @@ -7884,15 +7899,19 @@ char *term_get_ttymode(Terminal *term, const char *mode) } struct term_userpass_state { + prompts_t *prompts; size_t curr_prompt; - bool done_prompt; /* printed out prompt yet? */ + enum TermUserpassPromptState { + TUS_INITIAL, /* haven't even printed the prompt yet */ + TUS_ACTIVE, /* prompt is currently receiving user input */ + TUS_ABORTED, /* user pressed ^C or ^D to cancel prompt */ + } prompt_state; + Terminal *term; + TermLineEditor *le; + TermLineEditorCallbackReceiver le_rcv; }; -/* Tiny wrapper to make it easier to write lots of little strings */ -static inline void term_write(Terminal *term, ptrlen data) -{ - term_data(term, data.ptr, data.len); -} +static void term_userpass_next_prompt(struct term_userpass_state *s); /* * Signal that a prompts_t is done. This involves sending a @@ -7905,11 +7924,130 @@ static inline SeatPromptResult signal_prompts_t(Terminal *term, prompts_t *p, assert(p->callback && "Asynchronous userpass input requires a callback"); queue_toplevel_callback(p->callback, p->callback_ctx); if (term->ldisc) - ldisc_enable_prompt_callback(term->ldisc, NULL); + ldisc_provide_userpass_le(term->ldisc, NULL); p->spr = spr; + if (p->data) { + term_userpass_state_free(p->data); + p->data = NULL; + } return spr; } +/* Tiny wrapper to make it easier to write lots of little strings */ +static inline void term_write(Terminal *term, ptrlen data) +{ + term_data(term, data.ptr, data.len); +} + +static void term_lineedit_to_terminal( + TermLineEditorCallbackReceiver *rcv, ptrlen data) +{ + struct term_userpass_state *s = container_of( + rcv, struct term_userpass_state, le_rcv); + prompt_t *pr = s->prompts->prompts[s->curr_prompt]; + if (pr->echo) + term_write(s->term, data); +} + +static void term_lineedit_to_backend( + TermLineEditorCallbackReceiver *rcv, ptrlen data) +{ + struct term_userpass_state *s = container_of( + rcv, struct term_userpass_state, le_rcv); + prompt_t *pr = s->prompts->prompts[s->curr_prompt]; + put_datapl(pr->result, data); +} + +static void term_lineedit_newline(TermLineEditorCallbackReceiver *rcv) +{ + struct term_userpass_state *s = container_of( + rcv, struct term_userpass_state, le_rcv); + + prompt_t *pr = s->prompts->prompts[s->curr_prompt]; + if (!pr->echo) { + /* If echo is disabled, we won't have printed the newline in + * term_lineedit_to_terminal, so print it now */ + term_write(s->term, PTRLEN_LITERAL("\x0D\x0A")); + } + + ldisc_provide_userpass_le(s->term->ldisc, NULL); + s->curr_prompt++; + s->prompt_state = TUS_INITIAL; + term_userpass_next_prompt(s); +} + +static void term_lineedit_special( + TermLineEditorCallbackReceiver *rcv, SessionSpecialCode code, int arg) +{ + struct term_userpass_state *s = container_of( + rcv, struct term_userpass_state, le_rcv); + switch (code) { + case SS_IP: + case SS_EOF: + ldisc_provide_userpass_le(s->term->ldisc, NULL); + s->prompt_state = TUS_ABORTED; + signal_prompts_t(s->term, s->prompts, SPR_USER_ABORT); + default: + break; + } +} + +static const TermLineEditorCallbackReceiverVtable +term_userpass_lineedit_receiver_vt = { + .to_terminal = term_lineedit_to_terminal, + .to_backend = term_lineedit_to_backend, + .special = term_lineedit_special, + .newline = term_lineedit_newline, +}; + +static struct term_userpass_state *term_userpass_state_new( + Terminal *term, prompts_t *prompts) +{ + struct term_userpass_state *s = snew(struct term_userpass_state); + s->prompts = prompts; + s->curr_prompt = 0; + s->prompt_state = TUS_INITIAL; + s->term = term; + s->le_rcv.vt = &term_userpass_lineedit_receiver_vt; + s->le = lineedit_new(term, LE_INTERRUPT | LE_EOF_ALWAYS | LE_ESC_ERASES, + &s->le_rcv); + assert(!term->userpass_state); + term->userpass_state = s; + return s; +} + +static void term_userpass_state_free(struct term_userpass_state *s) +{ + assert(s->term->userpass_state == s); + s->term->userpass_state = NULL; + lineedit_free(s->le); + sfree(s); +} + +static void term_userpass_next_prompt(struct term_userpass_state *s) +{ + if (s->prompt_state != TUS_INITIAL) + return; + if (s->curr_prompt < s->prompts->n_prompts) { + prompt_t *pr = s->prompts->prompts[s->curr_prompt]; + term_write(s->term, ptrlen_from_asciz(pr->prompt)); + s->prompt_state = TUS_ACTIVE; + ldisc_provide_userpass_le(s->term->ldisc, s->le); + } else { + /* This triggers the callback provided by the userpass client, + * which will call term_userpass_state to fetch the result + * we're storing here */ + signal_prompts_t(s->term, s->prompts, SPR_OK); + } +} + +static bool terminal_use_utf8 = true; +bool set_legacy_charset_handling(bool newvalue) +{ + terminal_use_utf8 = !newvalue; + return true; +} + /* * Process some terminal data in the course of username/password * input. @@ -7934,10 +8072,9 @@ SeatPromptResult term_get_userpass_input(Terminal *term, prompts_t *p) /* * First call. Set some stuff up. */ - p->data = s = snew(struct term_userpass_state); + p->data = s = term_userpass_state_new(term, p); p->spr = SPR_INCOMPLETE; - s->curr_prompt = 0; - s->done_prompt = false; + term->userpass_utf8_override = p->utf8 && terminal_use_utf8; /* We only print the `name' caption if we have to... */ if (p->name_reqd && p->name) { ptrlen plname = ptrlen_from_asciz(p->name); @@ -7960,98 +8097,11 @@ SeatPromptResult term_get_userpass_input(Terminal *term, prompts_t *p) for (i = 0; i < (int)p->n_prompts; i++) prompt_set_result(p->prompts[i], ""); } + /* And print the first prompt. */ + term_userpass_next_prompt(s); } - while (s->curr_prompt < p->n_prompts) { - - prompt_t *pr = p->prompts[s->curr_prompt]; - bool finished_prompt = false; - - if (!s->done_prompt) { - term_write(term, ptrlen_from_asciz(pr->prompt)); - s->done_prompt = true; - } - - /* Breaking out here ensures that the prompt is printed even - * if we're now waiting for user data. */ - if (!ldisc_has_input_buffered(term->ldisc)) - break; - - /* FIXME: should we be using local-line-editing code instead? */ - while (!finished_prompt && ldisc_has_input_buffered(term->ldisc)) { - LdiscInputToken tok = ldisc_get_input_token(term->ldisc); - - char c; - if (tok.is_special) { - switch (tok.code) { - case SS_EOL: c = 13; break; - case SS_EC: c = 8; break; - case SS_IP: c = 3; break; - case SS_EOF: c = 3; break; - default: continue; - } - } else { - c = tok.chr; - } - - switch (c) { - case 10: - case 13: - term_write(term, PTRLEN_LITERAL("\r\n")); - /* go to next prompt, if any */ - s->curr_prompt++; - s->done_prompt = false; - finished_prompt = true; /* break out */ - break; - case 8: - case 127: - if (pr->result->len > 0) { - if (pr->echo) - term_write(term, PTRLEN_LITERAL("\b \b")); - strbuf_shrink_by(pr->result, 1); - } - break; - case 21: - case 27: - while (pr->result->len > 0) { - if (pr->echo) - term_write(term, PTRLEN_LITERAL("\b \b")); - strbuf_shrink_by(pr->result, 1); - } - break; - case 3: - case 4: - /* Immediate abort. */ - term_write(term, PTRLEN_LITERAL("\r\n")); - sfree(s); - p->data = NULL; - return signal_prompts_t(term, p, SPR_USER_ABORT); - default: - /* - * This simplistic check for printability is disabled - * when we're doing password input, because some people - * have control characters in their passwords. - */ - if (!pr->echo || (c >= ' ' && c <= '~') || - ((unsigned char) c >= 160)) { - put_byte(pr->result, c); - if (pr->echo) - term_write(term, make_ptrlen(&c, 1)); - } - break; - } - } - - } - - if (s->curr_prompt < p->n_prompts) { - ldisc_enable_prompt_callback(term->ldisc, p); - return SPR_INCOMPLETE; - } else { - sfree(s); - p->data = NULL; - return signal_prompts_t(term, p, SPR_OK); - } + return SPR_INCOMPLETE; } void term_notify_minimised(Terminal *term, bool minimised) diff --git a/terminal/terminal.h b/terminal/terminal.h index e0361313..e5bb16ae 100644 --- a/terminal/terminal.h +++ b/terminal/terminal.h @@ -1,9 +1,6 @@ /* - * Internals of the Terminal structure, for those other modules - * which need to look inside it. It would be nice if this could be - * folded back into terminal.c in future, with an abstraction layer - * to handle everything that other modules need to know about it; - * but for the moment, this will do. + * Internals of the Terminal structure, used by other modules in the + * terminal subdirectory and by test suites. */ #ifndef PUTTY_TERMINAL_H @@ -74,6 +71,8 @@ struct term_utf8_decode { int size; /* The size of the UTF character. */ }; +struct term_userpass_state; + struct terminal_tag { int compatibility_level; @@ -158,6 +157,7 @@ struct terminal_tag { int raw_mouse_reported_y; bool bracketed_paste, bracketed_paste_active; + bool no_bracketed_paste; /* disabled in configuration */ int cset_attr[2]; @@ -227,7 +227,7 @@ struct terminal_tag { int attr_mask; wchar_t *paste_buffer; - int paste_len, paste_pos; + size_t paste_len, paste_pos; Backend *backend; @@ -302,8 +302,7 @@ struct terminal_tag { * the former every time. */ bool ansi_colour; - char *answerback; - int answerbacklen; + strbuf *answerback; bool no_arabicshaping; int beep; bool bellovl; @@ -429,11 +428,21 @@ struct terminal_tag { WIN_RESIZE_NO, WIN_RESIZE_NEED_SEND, WIN_RESIZE_AWAIT_REPLY } win_resize_pending; int win_resize_pending_w, win_resize_pending_h; + + /* + * Indicates whether term_get_userpass_input is currently using + * the terminal to present a password prompt or similar, and if + * so, whether it's overridden the terminal into UTF-8 mode. + */ + struct term_userpass_state *userpass_state; + bool userpass_utf8_override; }; static inline bool in_utf(Terminal *term) { - return term->utf || term->ucsdata->line_codepage == CP_UTF8; + return (term->utf || + term->ucsdata->line_codepage == CP_UTF8 || + (term->userpass_state && term->userpass_utf8_override)); } unsigned long term_translate( @@ -562,4 +571,47 @@ static inline bool decpos_fn(pos *p, int cols) #define incpos(p) incpos_fn(&(p), GET_TERM_COLS) #define decpos(p) decpos_fn(&(p), GET_TERM_COLS) +struct TermLineEditorCallbackReceiverVtable { + void (*to_terminal)(TermLineEditorCallbackReceiver *rcv, ptrlen data); + void (*to_backend)(TermLineEditorCallbackReceiver *rcv, ptrlen data); + void (*special)(TermLineEditorCallbackReceiver *rcv, + SessionSpecialCode code, int arg); + void (*newline)(TermLineEditorCallbackReceiver *rcv); +}; +struct TermLineEditorCallbackReceiver { + const TermLineEditorCallbackReceiverVtable *vt; +}; +TermLineEditor *lineedit_new(Terminal *term, unsigned flags, + TermLineEditorCallbackReceiver *receiver); +void lineedit_free(TermLineEditor *le); +void lineedit_input(TermLineEditor *le, char ch, bool dedicated); +void lineedit_modify_flags(TermLineEditor *le, unsigned clr, unsigned flip); +void lineedit_send_line(TermLineEditor *le); + +/* + * Flags controlling the behaviour of TermLineEditor. + */ +#define LINEEDIT_FLAGS(X) \ + X(LE_INTERRUPT) /* pass SS_IP back to client on ^C */ \ + X(LE_SUSPEND) /* pass SS_SUSP back to client on ^Z */ \ + X(LE_ABORT) /* pass SS_ABORT back to client on ^\ */ \ + X(LE_EOF_ALWAYS) /* pass SS_EOF to client on *any* ^D \ + * (not just if the line buffer is empty) */ \ + X(LE_ESC_ERASES) /* make ESC erase the line, as well as ^U */ \ + X(LE_CRLF_NEWLINE) /* interpret manual ^M^J the same as Return */ \ + /* end of list */ +enum { + #define ALLOCATE_BIT_POSITION(flag) flag ## _bitpos, + LINEEDIT_FLAGS(ALLOCATE_BIT_POSITION) + #undef ALLOCATE_BIT_POSITION +}; +enum { + #define DEFINE_FLAG_BIT(flag) flag = 1 << flag ## _bitpos, + LINEEDIT_FLAGS(DEFINE_FLAG_BIT) + #undef DEFINE_FLAG_BIT +}; + +termline *term_get_line(Terminal *term, int y); +void term_release_line(termline *line); + #endif diff --git a/test/cryptsuite.py b/test/cryptsuite.py index 3f3a0ca7..5cdba58e 100755 --- a/test/cryptsuite.py +++ b/test/cryptsuite.py @@ -2708,6 +2708,62 @@ def testKeyMethods(self): self.assertFalse(ssh_key_verify( key, badsig, test_message)) + def testShortRSASignatures(self): + key = ssh_key_new_priv('rsa', b64(""" +AAAAB3NzaC1yc2EAAAADAQABAAABAQDeoTvwEDg46K7vYrQFFwbo2sBPahNoiw7i +RMbpwuOIH8sAOFAWzDvIZpDODGkobwc2hM8FRlZUg3lTgDaqVuMJOupG0xzOqehu +Fw3kXrm6ScWxaUXs+b5o88sqXBBYs91KmsWqYKTUUwDBuDHdo8Neq8h8SJqspCo4 +qctIoLoTrXYMqonfHXZp4bIn5WPN6jNL9pLi7Y+sl8aLe4w73aZzxMphecQfMMVJ +ezmv9zgA7gKw5ErorIGKCF44YRbvNisZA5j2DyLLsd/ztw2ikIEnx9Ng33+tGEBC +zq2RYb4ZtuT9dHXcNiHx3CqLLlFrjl13hQamjwoVy4ydDIdQZjYz +"""), b64(""" +AAABADhDwXUrdDoVvFhttpduuWVSG7Y2Vc9fDZTr0uWzRnPZrSFSGhOY7Cb6nPAm +PNFmNgl2SSfJHfpf++K5jZdBPEHR7PGXWzlzwXVJSE6GDiRhjqAGvhBlEdVOf/Ml +r0/rrSq0sO4dXKr4i0FqPtgIElEz0whuBQFKwAzwBJtHW5+rBzGLvoHh560UN4IK +SU3jv/nDMvPohEBfOA0puqYAfZM8PmU/kbgERPLyPp/dfnGBC4LlOnAcak+2RNu6 +yQ5v0NKksyYidCI76Ztf3B9asNNN4AbTg8JbzABwjtxR+0bDOOi0RwAJC2Wn2FOy +WiJsQWjz/fMnJW8WVg3DR/va/4EAAACBAP8rwn1vy4Y/S1EixR1XZM9F1OvJQwzN +EKhD1Qbr1YlLX3oZRnulJg/j0HupGnKCRh8DulNbrmXlMFdeZHDVFw87/o73DTCQ +g2qnRNUwJdBkePrA563vVx6OXe5TSF3+3SRNMesAdN8ExvGeOFP10z0FZhS3Zuco +y4WrxQBXVetBAAAAgQDfWmh5CRJEBCJbLHMdZK8QAw2QbKiD0VTw7PkDWJuSbYDB +AvEhoFuXUO/yMIfCCFjUuzO+3iumk8F/lFTkJ620Aah5tzRcKCtyW4YuoQUYMgpW +5/hGIL4M4bvUDGUGI3+SOn8qfAzCCsFD0FdR6ms0pMubaJQmeiI2wyM9ehOIcwAA +AIEAmKEX1YZHNtx/D/SaTsl6z+KwmOluqjzyjrwL16QLpIR1/F7lAjSnMGz3yORp ++314D3yZzKutpalwwsS4+z7838pilVaV7iWqF4TMDKYZ/6/baRXpwxrwFqvWXwQ3 +cLerc7bpA/IeVovoTirt0RNxdMPIVv3XsXE7pqatJBwOcQE= +""")) + + def decode(data): + pos = 0 + alg, data = ssh_decode_string(data, True) + sig, data = ssh_decode_string(data, True) + self.assertEqual(data, b'') + return alg, sig + + # An RSA signature over each hash which comes out a byte short + # with this key. Found in the obvious manner, by signing + # "message0", "message1", ... until one was short. + # + # We expect the ssh-rsa signature to be stored short, and the + # other two to be padded with a zero byte. + blob = ssh_key_sign(key, "message79", 0) + alg, sig = decode(blob) + self.assertEqual(alg, b"ssh-rsa") + self.assertEqual(len(sig), 255) # one byte short + self.assertNotEqual(sig[0], 0) + + blob = ssh_key_sign(key, "message208", 2) + alg, sig = decode(blob) + self.assertEqual(alg, b"rsa-sha2-256") + self.assertEqual(len(sig), 256) # full-length + self.assertEqual(sig[0], 0) # and has a leading zero byte + + blob = ssh_key_sign(key, "message461", 4) + alg, sig = decode(blob) + self.assertEqual(alg, b"rsa-sha2-512") + self.assertEqual(len(sig), 256) # full-length + self.assertEqual(sig[0], 0) # and has a leading zero byte + def testPPKLoadSave(self): # Stability test of PPK load/save functions. input_clear_key = b"""\ @@ -2893,6 +2949,36 @@ def testRSA1LoadSave(self): self.assertEqual(rsa1_save_sb(k2, comment, pp), input_encrypted_key) + def testRFC4716(self): + key = """\ +---- BEGIN SSH2 PUBLIC KEY ---- +Comment: "rsa-key-20240810" +AAAAB3NzaC1yc2EAAAADAQABAAABAQCKdLtvsewMpsbWQCNs8VOWKlh6eQT0gzbc +IoDLFPk5uVS1HjAEEjIZaXAB86PHTeJhkwEMlMXZ8mUZwAcZkuqKVCSib/VkuMEv +wXa4cOf70XMBUtUgRJ5bJRMsA8PNkZN/OQHyyBLgTXGoFPWq73A3fxPZIe8BSAN+ +mPuILX1GHUKbBzT56xRNwB5nHkg0MStEotkIzg3xRNIXB9qyP6ILO4Qax2n7+XJS +lmzr0KDJq5ZNSEZV4IprvAYBeEtvdBfLrRM4kifpVDE7ZrVXtKOIGDsxdEEBeqqy +LzN/Ly+uECsga2hoc+P/ZHMULMZkCfrOyWdeXz7BR/acLZJoT579 +---- END SSH2 PUBLIC KEY ---- +""" + + comment = b"rsa-key-20240810" + public_blob = b64(""" +AAAAB3NzaC1yc2EAAAADAQABAAABAQCKdLtvsewMpsbWQCNs8VOWKlh6eQT0gzbc +IoDLFPk5uVS1HjAEEjIZaXAB86PHTeJhkwEMlMXZ8mUZwAcZkuqKVCSib/VkuMEv +wXa4cOf70XMBUtUgRJ5bJRMsA8PNkZN/OQHyyBLgTXGoFPWq73A3fxPZIe8BSAN+ +mPuILX1GHUKbBzT56xRNwB5nHkg0MStEotkIzg3xRNIXB9qyP6ILO4Qax2n7+XJS +lmzr0KDJq5ZNSEZV4IprvAYBeEtvdBfLrRM4kifpVDE7ZrVXtKOIGDsxdEEBeqqy +LzN/Ly+uECsga2hoc+P/ZHMULMZkCfrOyWdeXz7BR/acLZJoT579 +""") + + self.assertEqual(ppk_loadpub_s(key), + (True, b'ssh-rsa', public_blob, comment, None)) + + self.assertEqual(ppk_loadpub_s(key[:len(key)//2]), + (False, None, b'', None, + b"invalid end line in SSH-2 public key file")) + def testOpenSSHCert(self): def per_base_keytype_tests(alg, run_validation_tests=False, run_ca_rsa_tests=False, ca_signflags=None): @@ -3187,8 +3273,9 @@ def testAESGCMBlockBoundaries(self): def aesgcm(key, iv, aes_impl, gcm_impl): c = ssh_cipher_new('aes{:d}_gcm_{}'.format(8*len(key), aes_impl)) + if c is None: return None, None # skip test if HW AES not available m = ssh2_mac_new('aesgcm_{}'.format(gcm_impl), c) - if m is None: return # skip test if HW GCM not available + if m is None: return None, None # skip test if HW GCM not available c.setkey(key) c.setiv(iv + b'\0'*4) m.setkey(b'') @@ -3242,6 +3329,7 @@ def test_one(aes_impl, gcm_impl): '5b60142bfcf4e5b0a9ada3451799866e') c, m = aesgcm(key, iv, aes_impl, gcm_impl) + if c is None or m is None: return # skip if HW impl unavailable len_dec = c.decrypt_length(aad, 123) self.assertEqual(len_dec, aad) # length not actually encrypted m.start() @@ -3339,9 +3427,11 @@ def test(gcm, cbc, iv_fixed, iv_msg): for impl in get_aes_impls(): with self.subTest(aes_impl=impl): gcm = ssh_cipher_new('aes{:d}_gcm_{}'.format(8*len(key), impl)) + if gcm is None: continue # skip if HW AES unavailable gcm.setkey(key) cbc = ssh_cipher_new('aes{:d}_cbc_{}'.format(8*len(key), impl)) + # assume if gcm_ is available, cbc_ will be too cbc.setkey(key) # A simple test to ensure the low word gets diff --git a/test/dsa_nonce_recover.py b/test/dsa_nonce_recover.py old mode 100644 new mode 100755 index b6159b31..f4731027 --- a/test/dsa_nonce_recover.py +++ b/test/dsa_nonce_recover.py @@ -1,21 +1,26 @@ #!/usr/bin/env python3 -# Recover the nonce value k used in integer DSA or NIST-style ECDSA, -# starting from the private key and the signature. -# -# _Without_ the private key, recovering the nonce is equivalent to -# recovering the private key itself. But with it, it's a trivial piece -# of modular arithmetic. -# -# This script generates a load of test signatures from various keys, -# recovers the nonces used, and prints them. This allows an eyeball -# check of whether they're evenly distributed. +''' +Recover the nonce value k used in integer DSA or NIST-style ECDSA, +starting from the private key and the signature. + +_Without_ the private key, recovering the nonce is equivalent to +recovering the private key itself. But with it, it's a trivial piece +of modular arithmetic. + +This script generates a load of test signatures from various keys, +recovers the nonces used, and prints them. This allows an eyeball +check of whether they're evenly distributed. +''' + +import argparse from base64 import b64decode as b64 from eccref import * from testcrypt import * from ssh import * +from agenttest import agent_query def recover_nonce(order, hashalg, privint, transform_hash, r, s, message): w = int(mp_invert(s, order)) @@ -44,40 +49,94 @@ def ecdsa_decode_sig(signature): s = int(mp_from_bytes_be(s)) return r, s -def test(privkey, decode_sig, transform_hash, order, hashalg, algid, obits): - print("----", algid) - print("k=0x{{:0{}b}}".format(obits).format(order)) - privblob = ssh_key_private_blob(privkey) - privint = int(mp_from_bytes_be(ssh_decode_string(privblob))) - for message in (f"msg{i}".encode('ASCII') for i in range(100)): - signature = ssh_key_sign(privkey, message, 0) - r, s = decode_sig(signature) - nonce = recover_nonce(order, hashalg, privint, transform_hash, - r, s, message) - print("k=0x{{:0{}b}}".format(obits).format(nonce)) - -def test_dsa(pubblob, privblob): - privkey = ssh_key_new_priv('dsa', pubblob, privblob) - _, buf = ssh_decode_string(pubblob, return_rest=True) - p, buf = ssh_decode_string(buf, return_rest=True) - q, buf = ssh_decode_string(buf, return_rest=True) - g, buf = ssh_decode_string(buf, return_rest=True) - p = int(mp_from_bytes_be(p)) - q = int(mp_from_bytes_be(q)) - g = int(mp_from_bytes_be(g)) - transform_hash = lambda h: h - test(privkey, dsa_decode_sig, transform_hash, q, 'sha1', 'dsa', 160) - -def test_ecdsa(algid, curve, hashalg, pubblob, privblob): - privkey = ssh_key_new_priv(algid, pubblob, privblob) - obits = int(mp_get_nbits(curve.G_order)) - def transform_hash(z): - shift = max(0, mp_get_nbits(z) - obits) - return mp_rshift_safe(z, shift) - test(privkey, ecdsa_decode_sig, transform_hash, curve.G_order, hashalg, - algid, obits) - -test_dsa(b64('AAAAB3NzaC1kc3MAAABhAJyWZzjVddGdyc5JPu/WPrC07vKRAmlqO6TUi49ah96iRcM7/D1aRMVAdYBepQ2mf1fsQTmvoC9KgQa79nN3kHhz0voQBKOuKI1ZAodfVOgpP4xmcXgjaA73Vjz22n4newAAABUA6l7/vIveaiA33YYv+SKcKLQaA8cAAABgbErc8QLw/WDz7mhVRZrU+9x3Tfs68j3eW+B/d7Rz1ZCqMYDk7r/F8dlBdQlYhpQvhuSBgzoFa0+qPvSSxPmutgb94wNqhHlVIUb9ZOJNloNr2lXiPP//Wu51TxXAEvAAAAAAYQCcQ9mufXtZa5RyfwT4NuLivdsidP4HRoLXdlnppfFAbNdbhxE0Us8WZt+a/443bwKnYxgif8dgxv5UROnWTngWu0jbJHpaDcTc9lRyTeSUiZZK312s/Sl7qDk3/Du7RUI='), b64('AAAAFGx3ft7G8AQzFsjhle7PWardUXh3')) -test_ecdsa('p256', p256, 'sha256', b64('AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBHkYQ0sQoq5LbJI1VMWhw3bV43TSYi3WVpqIgKcBKK91TcFFlAMZgceOHQ0xAFYcSczIttLvFu+xkcLXrRd4N7Q='), b64('AAAAIQCV/1VqiCsHZm/n+bq7lHEHlyy7KFgZBEbzqYaWtbx48Q==')) -test_ecdsa('p384', p384, 'sha384', b64('AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBMYK8PUtfAlJwKaBTIGEuCzH0vqOMa4UbcjrBbTbkGVSUnfo+nuC80NCdj9JJMs1jvfF8GzKLc5z8H3nZyM741/BUFjV7rEHsQFDek4KyWvKkEgKiTlZid19VukNo1q2Hg=='), b64('AAAAMGsfTmdB4zHdbiQ2euTSdzM6UKEOnrVjMAWwHEYvmG5qUOcBnn62fJDRJy67L+QGdg==')) -test_ecdsa('p521', p521, 'sha512', b64('AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAFrGthlKM152vu2Ghk+R7iO9/M6e+hTehNZ6+FBwof4HPkPB2/HHXj5+w5ynWyUrWiX5TI2riuJEIrJErcRH5LglADnJDX2w4yrKZ+wDHSz9lwh9p2F+B5R952es6gX3RJRkGA+qhKpKup8gKx78RMbleX8wgRtIu+4YMUnKb1edREiRg=='), b64('AAAAQgFh7VNJFUljWhhyAEiL0z+UPs/QggcMTd3Vv2aKDeBdCRl5di8r+BMm39L7bRzxRMEtW5NSKlDtE8MFEGdIE9khsw==')) +class SignerBase: + def test(self, privkey, decode_sig, transform_hash, order, hashalg, + algid, obits): + print("----", algid) + print("k=0x{{:0{}b}}".format(obits).format(order)) + privblob = ssh_key_private_blob(privkey) + privint = int(mp_from_bytes_be(ssh_decode_string(privblob))) + self.setup_key(privkey) + for message in (f"msg{i}".encode('ASCII') for i in range(100)): + signature = self.sign(privkey, message) + r, s = decode_sig(signature) + nonce = recover_nonce(order, hashalg, privint, transform_hash, + r, s, message) + print("k=0x{{:0{}b}}".format(obits).format(nonce)) + self.cleanup_key(privkey) + + def test_dsa(self, pubblob, privblob): + privkey = ssh_key_new_priv('dsa', pubblob, privblob) + _, buf = ssh_decode_string(pubblob, return_rest=True) + p, buf = ssh_decode_string(buf, return_rest=True) + q, buf = ssh_decode_string(buf, return_rest=True) + g, buf = ssh_decode_string(buf, return_rest=True) + p = int(mp_from_bytes_be(p)) + q = int(mp_from_bytes_be(q)) + g = int(mp_from_bytes_be(g)) + transform_hash = lambda h: h + self.test(privkey, dsa_decode_sig, transform_hash, q, 'sha1', 'dsa', + 160) + + def test_ecdsa(self, algid, curve, hashalg, pubblob, privblob): + privkey = ssh_key_new_priv(algid, pubblob, privblob) + obits = int(mp_get_nbits(curve.G_order)) + def transform_hash(z): + shift = max(0, mp_get_nbits(z) - obits) + return mp_rshift_safe(z, shift) + self.test(privkey, ecdsa_decode_sig, transform_hash, curve.G_order, + hashalg, algid, obits) + +class TestcryptSigner(SignerBase): + def setup_key(self, key): + pass + def cleanup_key(self, key): + pass + def sign(self, key, message): + return ssh_key_sign(key, message, 0) + +class AgentSigner(SignerBase): + def setup_key(self, key): + alg = ssh_decode_string(key.public_blob()) + msg = (ssh_byte(SSH2_AGENTC_ADD_IDENTITY) + + ssh_string(alg) + + key.openssh_blob() + + ssh_string(b"dsa_nonce_recover test key")) + result = agent_query(msg) + assert result == ssh_byte(SSH_AGENT_SUCCESS) + + def cleanup_key(self, key): + msg = (ssh_byte(SSH2_AGENTC_REMOVE_IDENTITY) + + ssh_string(key.public_blob())) + result = agent_query(msg) + assert result == ssh_byte(SSH_AGENT_SUCCESS) + + def sign(self, key, message): + msg = (ssh_byte(SSH2_AGENTC_SIGN_REQUEST) + + ssh_string(key.public_blob()) + + ssh_string(message)) + rsp = agent_query(msg) + t, rsp = ssh_decode_byte(rsp, True) + assert t == SSH2_AGENT_SIGN_RESPONSE + sig, rsp = ssh_decode_string(rsp, True) + assert len(rsp) == 0 + return sig + +def main(): + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("--agent", action="store_true", + help="Test an SSH agent instead of testcrypt. " + "(Still needs testcrypt.)") + args = parser.parse_args() + + signer = AgentSigner() if args.agent else TestcryptSigner() + + signer.test_dsa(b64('AAAAB3NzaC1kc3MAAABhAJyWZzjVddGdyc5JPu/WPrC07vKRAmlqO6TUi49ah96iRcM7/D1aRMVAdYBepQ2mf1fsQTmvoC9KgQa79nN3kHhz0voQBKOuKI1ZAodfVOgpP4xmcXgjaA73Vjz22n4newAAABUA6l7/vIveaiA33YYv+SKcKLQaA8cAAABgbErc8QLw/WDz7mhVRZrU+9x3Tfs68j3eW+B/d7Rz1ZCqMYDk7r/F8dlBdQlYhpQvhuSBgzoFa0+qPvSSxPmutgb94wNqhHlVIUb9ZOJNloNr2lXiPP//Wu51TxXAEvAAAAAAYQCcQ9mufXtZa5RyfwT4NuLivdsidP4HRoLXdlnppfFAbNdbhxE0Us8WZt+a/443bwKnYxgif8dgxv5UROnWTngWu0jbJHpaDcTc9lRyTeSUiZZK312s/Sl7qDk3/Du7RUI='), b64('AAAAFGx3ft7G8AQzFsjhle7PWardUXh3')) + signer.test_ecdsa('p256', p256, 'sha256', b64('AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBHkYQ0sQoq5LbJI1VMWhw3bV43TSYi3WVpqIgKcBKK91TcFFlAMZgceOHQ0xAFYcSczIttLvFu+xkcLXrRd4N7Q='), b64('AAAAIQCV/1VqiCsHZm/n+bq7lHEHlyy7KFgZBEbzqYaWtbx48Q==')) + signer.test_ecdsa('p384', p384, 'sha384', b64('AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBMYK8PUtfAlJwKaBTIGEuCzH0vqOMa4UbcjrBbTbkGVSUnfo+nuC80NCdj9JJMs1jvfF8GzKLc5z8H3nZyM741/BUFjV7rEHsQFDek4KyWvKkEgKiTlZid19VukNo1q2Hg=='), b64('AAAAMGsfTmdB4zHdbiQ2euTSdzM6UKEOnrVjMAWwHEYvmG5qUOcBnn62fJDRJy67L+QGdg==')) + signer.test_ecdsa('p521', p521, 'sha512', b64('AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAFrGthlKM152vu2Ghk+R7iO9/M6e+hTehNZ6+FBwof4HPkPB2/HHXj5+w5ynWyUrWiX5TI2riuJEIrJErcRH5LglADnJDX2w4yrKZ+wDHSz9lwh9p2F+B5R952es6gX3RJRkGA+qhKpKup8gKx78RMbleX8wgRtIu+4YMUnKb1edREiRg=='), b64('AAAAQgFh7VNJFUljWhhyAEiL0z+UPs/QggcMTd3Vv2aKDeBdCRl5di8r+BMm39L7bRzxRMEtW5NSKlDtE8MFEGdIE9khsw==')) + +if __name__ == '__main__': + main() diff --git a/test/fuzzterm.c b/test/fuzzterm.c index 0d4597b1..45ba91dd 100644 --- a/test/fuzzterm.c +++ b/test/fuzzterm.c @@ -21,9 +21,7 @@ int main(int argc, char **argv) conf = conf_new(); do_defaults(NULL, conf); - init_ucs(&ucsdata, conf_get_str(conf, CONF_line_codepage), - conf_get_bool(conf, CONF_utf8_override), - CS_NONE, conf_get_int(conf, CONF_vtmode)); + init_ucs_generic(conf, &ucsdata); term = term_init(conf, &ucsdata, &termwin); term_size(term, 24, 80, 10000); @@ -121,10 +119,7 @@ static const TermWinVtable fuzz_termwin_vt = { void ldisc_send(Ldisc *ldisc, const void *buf, int len, bool interactive) {} void ldisc_echoedit_update(Ldisc *ldisc) {} -bool ldisc_has_input_buffered(Ldisc *ldisc) { return false; } -LdiscInputToken ldisc_get_input_token(Ldisc *ldisc) -{ unreachable("This fake ldisc never has any buffered input"); } -void ldisc_enable_prompt_callback(Ldisc *ldisc, prompts_t *p) +void ldisc_provide_userpass_le(Ldisc *ldisc, TermLineEditor *le) { unreachable("This fake ldisc should never be used for user/pass prompts"); } void modalfatalbox(const char *fmt, ...) { exit(0); } void nonfatal(const char *fmt, ...) { } @@ -201,7 +196,7 @@ int platform_default_i(const char *name, int def) FontSpec *platform_default_fontspec(const char *name) { - return fontspec_new(""); + return fontspec_new_default(); } Filename *platform_default_filename(const char *name) diff --git a/test/test_conf.c b/test/test_conf.c new file mode 100644 index 00000000..33a25a48 --- /dev/null +++ b/test/test_conf.c @@ -0,0 +1,1052 @@ +#define SUPERSEDE_FONTSPEC_FOR_TESTING + +#include "putty.h" +#include "storage.h" + +void modalfatalbox(const char *p, ...) +{ + va_list ap; + fprintf(stderr, "FATAL ERROR: "); + va_start(ap, p); + vfprintf(stderr, p, ap); + va_end(ap); + fputc('\n', stderr); + exit(1); +} + +char *platform_default_s(const char *name) +{ return NULL; } +bool platform_default_b(const char *name, bool def) +{ return def; } +int platform_default_i(const char *name, int def) +{ return def; } +FontSpec *platform_default_fontspec(const char *name) +{ return fontspec_new_default(); } +Filename *platform_default_filename(const char *name) +{ return filename_from_str(""); } +char *platform_get_x_display(void) { return NULL; } + +void read_random_seed(noise_consumer_t consumer) {} +void write_random_seed(void *data, int len) +{ unreachable("no random seed in this application"); } +bool have_ssh_host_key(const char *hostname, int port, + const char *keytype) { return false; } +int check_stored_host_key(const char *hostname, int port, + const char *keytype, const char *key) { return 1; } +void store_host_key(Seat *seat, const char *hostname, int port, + const char *keytype, const char *key) +{ unreachable("no actual host keys in this application"); } + +host_ca_enum *enum_host_ca_start(void) { return NULL; } +bool enum_host_ca_next(host_ca_enum *handle, strbuf *out) { return false; } +void enum_host_ca_finish(host_ca_enum *handle) {} +host_ca *host_ca_load(const char *name) { return NULL; } + +void old_keyfile_warning(void) { } + +const bool share_can_be_upstream = false; +const bool share_can_be_downstream = false; + +struct FontSpec { + char *name; +}; + +FontSpec *fontspec_new(const char *name) +{ + FontSpec *f = snew(FontSpec); + f->name = dupstr(name); + return f; +} + +FontSpec *fontspec_new_default(void) +{ + return fontspec_new(""); +} + +FontSpec *fontspec_copy(const FontSpec *f) +{ + return fontspec_new(f->name); +} + +void fontspec_free(FontSpec *f) +{ + sfree(f->name); + sfree(f); +} + +void fontspec_serialise(BinarySink *bs, FontSpec *f) +{ + put_asciz(bs, f->name); +} + +FontSpec *fontspec_deserialise(BinarySource *src) +{ + return fontspec_new(get_asciz(src)); +} + +#define MAXKEY 16 + +typedef enum { + SAVE_UNSET, SAVE_S, SAVE_I, SAVE_FONTSPEC, SAVE_FILENAME +} SaveType; + +typedef struct SaveItem { + const char *key; + SaveType type; + union { + char sval[4096]; + int ival; + }; +} SaveItem; + +struct settings_w { + size_t n; + SaveItem si[MAXKEY]; +}; + +settings_w *open_settings_w(const char *sessionname, char **errmsg) +{ return NULL; } +void close_settings_w(settings_w *sw) +{ unreachable("we don't open and close in this test program"); } +settings_r *open_settings_r(const char *sessionname) +{ return NULL; } +void close_settings_r(settings_r *sr) { } + +/* Work around lack of true snprintf before VS2015 */ +#if defined _WINDOWS && \ + !defined __MINGW32__ && \ + !defined __WINE__ && \ + _MSC_VER < 1900 +#define snprintf _snprintf +#endif + +void write_setting_s(settings_w *sw, const char *key, const char *value) +{ + for (size_t i = 0; i < sw->n; i++) { + if (!strcmp(key, sw->si[i].key)) { + sw->si[i].type = SAVE_S; + snprintf(sw->si[i].sval, sizeof(sw->si[i].sval), "%s", value); + break; + } + } +} + +void write_setting_i(settings_w *sw, const char *key, int value) +{ + for (size_t i = 0; i < sw->n; i++) { + if (!strcmp(key, sw->si[i].key)) { + sw->si[i].type = SAVE_I; + sw->si[i].ival = value; + break; + } + } +} + +void write_setting_fontspec(settings_w *sw, const char *key, FontSpec *fs) +{ + for (size_t i = 0; i < sw->n; i++) { + if (!strcmp(key, sw->si[i].key)) { + sw->si[i].type = SAVE_FONTSPEC; + snprintf(sw->si[i].sval, sizeof(sw->si[i].sval), "%s", fs->name); + break; + } + } +} + +void write_setting_filename(settings_w *sw, const char *key, Filename *fn) +{ + for (size_t i = 0; i < sw->n; i++) { + if (!strcmp(key, sw->si[i].key)) { + sw->si[i].type = SAVE_FILENAME; + snprintf(sw->si[i].sval, sizeof(sw->si[i].sval), "%s", + filename_to_str(fn)); + break; + } + } +} + +struct settings_r { + size_t n; + SaveItem si[MAXKEY]; +}; + +char *read_setting_s(settings_r *sr, const char *key) +{ + if (sr) + for (size_t i = 0; i < sr->n; i++) + if (!strcmp(key, sr->si[i].key) && sr->si[i].type == SAVE_S) + return dupstr(sr->si[i].sval); + return NULL; +} + +int read_setting_i(settings_r *sr, const char *key, int defvalue) +{ + if (sr) + for (size_t i = 0; i < sr->n; i++) + if (!strcmp(key, sr->si[i].key) && sr->si[i].type == SAVE_I) + return sr->si[i].ival; + return defvalue; +} + +FontSpec *read_setting_fontspec(settings_r *sr, const char *key) +{ + if (sr) + for (size_t i = 0; i < sr->n; i++) + if (!strcmp(key, sr->si[i].key) && sr->si[i].type == SAVE_FONTSPEC) + return fontspec_new(sr->si[i].sval); + return NULL; +} + +Filename *read_setting_filename(settings_r *sr, const char *key) +{ + if (sr) + for (size_t i = 0; i < sr->n; i++) + if (!strcmp(key, sr->si[i].key) && sr->si[i].type == SAVE_FILENAME) + return filename_from_str(sr->si[i].sval); + return NULL; +} + +void del_settings(const char *sessionname) {} + +settings_e *enum_settings_start(void) +{ return NULL; } +bool enum_settings_next(settings_e *handle, strbuf *out) +{ unreachable("where did you get a settings_e from?"); } +void enum_settings_finish(settings_e *handle) +{ unreachable("where did you get a settings_e from?"); } + +static int nfails = 0; + +void test_str_simple(int confid, const char *saveid, const char *defexp) +{ + Conf *conf = conf_new(); + + do_defaults(NULL, conf); + const char *defgot = conf_get_str(conf, confid); + if (0 != strcmp(defgot, defexp)) { + printf("fail test_str_simple(%s): default = '%s', expected '%s'\n", + saveid, defgot, defexp); + nfails++; + } + + for (int i = 0; i < 2; i++) { + settings_w sw = { + .n = 1, + .si[0].key = saveid, + .si[0].type = SAVE_UNSET, + }; + static const char *const teststrings[] = { "foo", "bar" }; + const char *teststring = teststrings[i]; + conf_set_str(conf, confid, teststring); + save_open_settings(&sw, conf); + if (sw.si[0].type != SAVE_S) { + printf("fail test_str_simple(%s): saved type = %d, expected %d\n", + saveid, sw.si[0].type, SAVE_S); + nfails++; + } else if (0 != strcmp(sw.si[0].sval, teststring)) { + printf("fail test_str_simple(%s): " + "saved string = '%s', expected '%s'\n", + saveid, sw.si[0].sval, teststring); + nfails++; + } + + conf_clear(conf); + settings_r sr = { + .n = 1, + .si[0].key = saveid, + .si[0].type = SAVE_S, + }; + snprintf(sr.si[0].sval, sizeof(sr.si[0].sval), "%s", teststring); + load_open_settings(&sr, conf); + const char *loaded = conf_get_str(conf, confid); + if (0 != strcmp(loaded, teststring)) { + printf("fail test_str_simple(%s): " + "loaded string = '%s', expected '%s'\n", + saveid, loaded, teststring); + nfails++; + } + } + + conf_free(conf); +} + +void test_utf8_simple(int confid, const char *saveid, const char *defexp) +{ + Conf *conf = conf_new(); + + do_defaults(NULL, conf); + const char *defgot = conf_get_utf8(conf, confid); + if (0 != strcmp(defgot, defexp)) { + printf("fail test_utf8_simple(%s): default = '%s', expected '%s'\n", + saveid, defgot, defexp); + nfails++; + } + + for (int i = 0; i < 2; i++) { + settings_w sw = { + .n = 1, + .si[0].key = saveid, + .si[0].type = SAVE_UNSET, + }; + static const char *const teststrings[] = { "foo", "bar" }; + const char *teststring = teststrings[i]; + conf_set_utf8(conf, confid, teststring); + save_open_settings(&sw, conf); + if (sw.si[0].type != SAVE_S) { + printf("fail test_utf8_simple(%s): saved type = %d, expected %d\n", + saveid, sw.si[0].type, SAVE_S); + nfails++; + } else if (0 != strcmp(sw.si[0].sval, teststring)) { + printf("fail test_utf8_simple(%s): " + "saved string = '%s', expected '%s'\n", + saveid, sw.si[0].sval, teststring); + nfails++; + } + + conf_clear(conf); + settings_r sr = { + .n = 1, + .si[0].key = saveid, + .si[0].type = SAVE_S, + }; + snprintf(sr.si[0].sval, sizeof(sr.si[0].sval), "%s", teststring); + load_open_settings(&sr, conf); + const char *loaded = conf_get_utf8(conf, confid); + if (0 != strcmp(loaded, teststring)) { + printf("fail test_utf8_simple(%s): " + "loaded string = '%s', expected '%s'\n", + saveid, loaded, teststring); + nfails++; + } + } + + conf_free(conf); +} + +void test_str_ambi_simple(int confid, const char *saveid, + const char *defexp, bool defutf8) +{ + Conf *conf = conf_new(); + bool utf8; + + do_defaults(NULL, conf); + const char *defgot = conf_get_str_ambi(conf, confid, &utf8); + if (0 != strcmp(defgot, defexp) || utf8 != defutf8) { + printf("fail test_str_ambi_simple(%s): " + "default = '%s' (%s), expected '%s' (%s)\n", + saveid, defgot, utf8 ? "native" : "UTF-8", + defexp, defutf8 ? "native" : "UTF-8"); + nfails++; + } + + for (int i = 0; i < 2; i++) { + settings_w sw = { + .n = 1, + .si[0].key = saveid, + .si[0].type = SAVE_UNSET, + }; + static const char *const teststrings[] = { "foo", "bar" }; + const char *teststring = teststrings[i]; + conf_set_str(conf, confid, teststring); + save_open_settings(&sw, conf); + if (sw.si[0].type != SAVE_S) { + printf("fail test_str_ambi_simple(%s): " + "saved type = %d, expected %d\n", + saveid, sw.si[0].type, SAVE_S); + nfails++; + } else if (0 != strcmp(sw.si[0].sval, teststring)) { + printf("fail test_str_ambi_simple(%s): " + "saved string = '%s', expected '%s'\n", + saveid, sw.si[0].sval, teststring); + nfails++; + } + + conf_clear(conf); + settings_r sr = { + .n = 1, + .si[0].key = saveid, + .si[0].type = SAVE_S, + }; + snprintf(sr.si[0].sval, sizeof(sr.si[0].sval), "%s", teststring); + load_open_settings(&sr, conf); + const char *loaded = conf_get_str_ambi(conf, confid, &utf8); + if (0 != strcmp(loaded, teststring) || utf8) { + printf("fail test_str_ambi_simple(%s): " + "loaded string = '%s' (%s), expected '%s' (native)\n", + saveid, loaded, utf8 ? "native" : "UTF-8", teststring); + nfails++; + } + } + + conf_free(conf); +} + +void test_int_simple(int confid, const char *saveid, int defexp) +{ + Conf *conf = conf_new(); + + do_defaults(NULL, conf); + int defgot = conf_get_int(conf, confid); + if (defgot != defexp) { + printf("fail test_int_simple(%s): default = %d, expected %d\n", + saveid, defgot, defexp); + nfails++; + } + + for (int i = 0; i < 2; i++) { + settings_w sw = { + .n = 1, + .si[0].key = saveid, + .si[0].type = SAVE_UNSET, + }; + static const int testints[] = { 12345, 54321 }; + int testint = testints[i]; + conf_set_int(conf, confid, testint); + save_open_settings(&sw, conf); + if (sw.si[0].type != SAVE_I) { + printf("fail test_int_simple(%s): saved type = %d, expected %d\n", + saveid, sw.si[0].type, SAVE_I); + nfails++; + } else if (sw.si[0].ival != testint) { + printf("fail test_int_simple(%s): " + "saved integer = %d, expected %d\n", + saveid, sw.si[0].ival, testint); + nfails++; + } + + conf_clear(conf); + settings_r sr = { + .n = 1, + .si[0].key = saveid, + .si[0].type = SAVE_I, + .si[0].ival = testint, + }; + load_open_settings(&sr, conf); + int loaded = conf_get_int(conf, confid); + if (loaded != testint) { + printf("fail test_int_simple(%s): " + "loaded integer = %d, expected %d\n", + saveid, loaded, testint); + nfails++; + } + } + + conf_free(conf); +} + +void test_int_translated_internal( + int confid, const char *saveid, bool test_save, bool test_load, + void (*load_prepare)(settings_r *), int defexp, va_list ap) +{ + Conf *conf = conf_new(); + + do_defaults(NULL, conf); + int defgot = conf_get_int(conf, confid); + if (defgot != defexp) { + printf("fail test_int_translated(%s): default = %d, expected %d\n", + saveid, defgot, defexp); + nfails++; + } + + int confval = va_arg(ap, int); + while (confval != -1) { + int storageval = va_arg(ap, int); + + if (test_save) { + settings_w sw = { + .n = 1, + .si[0].key = saveid, + .si[0].type = SAVE_UNSET, + }; + conf_set_int(conf, confid, confval); + save_open_settings(&sw, conf); + if (sw.si[0].type != SAVE_I) { + printf("fail test_int_translated(%s): " + "saved type = %d, expected %d\n", + saveid, sw.si[0].type, SAVE_I); + nfails++; + } else if (sw.si[0].ival != storageval) { + printf("fail test_int_translated(%s.%d.%d): " + "saved integer = %d, expected %d\n", + saveid, confval, storageval, sw.si[0].ival, storageval); + nfails++; + } + } + + if (test_load) { + conf_clear(conf); + settings_r sr = { + .n = 1, + .si[0].key = saveid, + .si[0].type = SAVE_I, + .si[0].ival = storageval, + }; + if (load_prepare) + load_prepare(&sr); + load_open_settings(&sr, conf); + int loaded = conf_get_int(conf, confid); + if (loaded != confval) { + printf("fail test_int_translated(%s.%d.%d): " + "loaded integer = %d, expected %d\n", + saveid, confval, storageval, loaded, confval); + nfails++; + } + } + + confval = va_arg(ap, int); + } + + conf_free(conf); +} + +void test_int_translated(int confid, const char *saveid, int defexp, ...) +{ + va_list ap; + va_start(ap, defexp); + test_int_translated_internal(confid, saveid, true, true, NULL, defexp, ap); + va_end(ap); +} + +void test_int_translated_load_legacy( + int confid, const char *saveid, void (*load_prepare)(settings_r *), + int defexp, ...) +{ + va_list ap; + va_start(ap, defexp); + test_int_translated_internal(confid, saveid, false, true, load_prepare, + defexp, ap); + va_end(ap); +} + +void test_bool_simple(int confid, const char *saveid, bool defexp) +{ + Conf *conf = conf_new(); + + do_defaults(NULL, conf); + bool defgot = conf_get_bool(conf, confid); + if (defgot != defexp) { + printf("fail test_bool_simple(%s): default = %d, expected %d\n", + saveid, defgot, defexp); + nfails++; + } + + for (int i = 0; i < 2; i++) { + settings_w sw = { + .n = 1, + .si[0].key = saveid, + .si[0].type = SAVE_UNSET, + }; + static const bool testbools[] = { false, true }; + bool testbool = testbools[i]; + conf_set_bool(conf, confid, testbool); + save_open_settings(&sw, conf); + if (sw.si[0].type != SAVE_I) { + printf("fail test_bool_simple(%s): saved type = %d, expected %d\n", + saveid, sw.si[0].type, SAVE_I); + nfails++; + } else if (sw.si[0].ival != testbool) { + printf("fail test_bool_simple(%s): " + "saved integer = %d, expected %d\n", + saveid, sw.si[0].ival, testbool); + nfails++; + } + + conf_clear(conf); + settings_r sr = { + .n = 1, + .si[0].key = saveid, + .si[0].type = SAVE_I, + .si[0].ival = testbool, + }; + load_open_settings(&sr, conf); + bool loaded = conf_get_bool(conf, confid); + if (loaded != testbool) { + printf("fail test_bool_simple(%s): " + "loaded boolean = %d, expected %d\n", + saveid, loaded, testbool); + nfails++; + } + } + + conf_free(conf); +} + +void test_file_simple(int confid, const char *saveid) +{ + Conf *conf = conf_new(); + + do_defaults(NULL, conf); + + for (int i = 0; i < 2; i++) { + settings_w sw = { + .n = 1, + .si[0].key = saveid, + .si[0].type = SAVE_UNSET, + }; + static const char *const teststrings[] = { "foo", "bar" }; + const char *teststring = teststrings[i]; + Filename *testfn = filename_from_str(teststring); + conf_set_filename(conf, confid, testfn); + filename_free(testfn); + save_open_settings(&sw, conf); + if (sw.si[0].type != SAVE_FILENAME) { + printf("fail test_file_simple(%s): saved type = %d, expected %d\n", + saveid, sw.si[0].type, SAVE_FILENAME); + nfails++; + } else if (0 != strcmp(sw.si[0].sval, teststring)) { + printf("fail test_file_simple(%s): " + "saved string = '%s', expected '%s'\n", + saveid, sw.si[0].sval, teststring); + nfails++; + } + + conf_clear(conf); + settings_r sr = { + .n = 1, + .si[0].key = saveid, + .si[0].type = SAVE_FILENAME, + }; + snprintf(sr.si[0].sval, sizeof(sr.si[0].sval), "%s", teststring); + load_open_settings(&sr, conf); + const char *loaded = filename_to_str(conf_get_filename(conf, confid)); + if (0 != strcmp(loaded, teststring)) { + printf("fail test_file_simple(%s): " + "loaded string = '%s', expected '%s'\n", + saveid, loaded, teststring); + nfails++; + } + } + + conf_free(conf); +} + +void test_font_simple(int confid, const char *saveid) +{ + Conf *conf = conf_new(); + + do_defaults(NULL, conf); + + for (int i = 0; i < 2; i++) { + settings_w sw = { + .n = 1, + .si[0].key = saveid, + .si[0].type = SAVE_UNSET, + }; + static const char *const teststrings[] = { "foo", "bar" }; + const char *teststring = teststrings[i]; + FontSpec *testfs = fontspec_new(teststring); + conf_set_fontspec(conf, confid, testfs); + fontspec_free(testfs); + save_open_settings(&sw, conf); + if (sw.si[0].type != SAVE_FONTSPEC) { + printf("fail test_font_simple(%s): saved type = %d, expected %d\n", + saveid, sw.si[0].type, SAVE_FONTSPEC); + nfails++; + } else if (0 != strcmp(sw.si[0].sval, teststring)) { + printf("fail test_font_simple(%s): " + "saved string = '%s', expected '%s'\n", + saveid, sw.si[0].sval, teststring); + nfails++; + } + + conf_clear(conf); + settings_r sr = { + .n = 1, + .si[0].key = saveid, + .si[0].type = SAVE_FONTSPEC, + }; + snprintf(sr.si[0].sval, sizeof(sr.si[0].sval), "%s", teststring); + load_open_settings(&sr, conf); + const char *loaded = conf_get_fontspec(conf, confid)->name; + if (0 != strcmp(loaded, teststring)) { + printf("fail test_file_simple(%s): " + "loaded string = '%s', expected '%s'\n", + saveid, loaded, teststring); + nfails++; + } + } + + conf_free(conf); +} + +static void load_prepare_socks4(settings_r *sr) +{ + size_t pos = sr->n++; + sr->si[pos].key = "ProxySOCKSVersion"; + sr->si[pos].type = SAVE_I; + sr->si[pos].ival = 4; +} + +void test_simple(void) +{ + test_str_simple(CONF_host, "HostName", ""); + test_int_translated(CONF_addressfamily, "AddressFamily", ADDRTYPE_UNSPEC, + ADDRTYPE_UNSPEC, 0, ADDRTYPE_IPV4, 1, + ADDRTYPE_IPV6, 2, -1); + test_bool_simple(CONF_warn_on_close, "WarnOnClose", true); + test_bool_simple(CONF_tcp_nodelay, "TCPNoDelay", true); + test_bool_simple(CONF_tcp_keepalives, "TCPKeepalives", false); + test_str_simple(CONF_loghost, "LogHost", ""); + test_str_simple(CONF_proxy_exclude_list, "ProxyExcludeList", ""); + test_bool_simple(CONF_even_proxy_localhost, "ProxyLocalhost", false); + test_str_simple(CONF_proxy_host, "ProxyHost", "proxy"); + test_int_simple(CONF_proxy_port, "ProxyPort", 80); + test_str_simple(CONF_proxy_username, "ProxyUsername", ""); + test_str_simple(CONF_proxy_password, "ProxyPassword", ""); + test_str_simple(CONF_proxy_telnet_command, "ProxyTelnetCommand", "connect %host %port\\n"); + test_int_translated(CONF_proxy_log_to_term, "ProxyLogToTerm", FORCE_OFF, + FORCE_ON, 0, FORCE_OFF, 1, AUTO, 2, -1); + test_str_ambi_simple(CONF_remote_cmd, "RemoteCommand", "", false); + test_bool_simple(CONF_nopty, "NoPTY", false); + test_bool_simple(CONF_compression, "Compression", false); + test_bool_simple(CONF_ssh_prefer_known_hostkeys, "PreferKnownHostKeys", true); + test_int_simple(CONF_ssh_rekey_time, "RekeyTime", 60); + test_str_simple(CONF_ssh_rekey_data, "RekeyBytes", "1G"); + test_bool_simple(CONF_tryagent, "TryAgent", true); + test_bool_simple(CONF_agentfwd, "AgentFwd", false); + test_bool_simple(CONF_change_username, "ChangeUsername", false); + test_file_simple(CONF_keyfile, "PublicKeyFile"); + test_file_simple(CONF_detached_cert, "DetachedCertificate"); + test_str_simple(CONF_auth_plugin, "AuthPlugin", ""); + test_bool_simple(CONF_ssh2_des_cbc, "SSH2DES", false); + test_bool_simple(CONF_ssh_no_userauth, "SshNoAuth", false); + test_bool_simple(CONF_ssh_no_trivial_userauth, "SshNoTrivialAuth", false); + test_bool_simple(CONF_ssh_show_banner, "SshBanner", true); + test_bool_simple(CONF_try_tis_auth, "AuthTIS", false); + test_bool_simple(CONF_try_ki_auth, "AuthKI", true); + test_bool_simple(CONF_ssh_no_shell, "SshNoShell", false); + test_str_simple(CONF_termtype, "TerminalType", "xterm"); + test_str_simple(CONF_termspeed, "TerminalSpeed", "38400,38400"); + test_str_ambi_simple(CONF_username, "UserName", "", false); + test_bool_simple(CONF_username_from_env, "UserNameFromEnvironment", false); + test_str_simple(CONF_localusername, "LocalUserName", ""); + test_bool_simple(CONF_rfc_environ, "RFCEnviron", false); + test_bool_simple(CONF_passive_telnet, "PassiveTelnet", false); + test_str_simple(CONF_serline, "SerialLine", ""); + test_int_simple(CONF_serspeed, "SerialSpeed", 9600); + test_int_simple(CONF_serdatabits, "SerialDataBits", 8); + test_int_simple(CONF_serstopbits, "SerialStopHalfbits", 2); + test_int_translated(CONF_serparity, "SerialParity", SER_PAR_NONE, + SER_PAR_NONE, 0, SER_PAR_ODD, 1, SER_PAR_EVEN, 2, + SER_PAR_MARK, 3, SER_PAR_SPACE, 4, -1); + test_int_translated(CONF_serflow, "SerialFlowControl", SER_FLOW_XONXOFF, + SER_FLOW_NONE, 0, SER_FLOW_XONXOFF, 1, + SER_FLOW_RTSCTS, 2, SER_FLOW_DSRDTR, 3, -1); + test_str_simple(CONF_supdup_location, "SUPDUPLocation", "The Internet"); + test_int_translated(CONF_supdup_ascii_set, "SUPDUPCharset", + SUPDUP_CHARSET_ASCII, + SUPDUP_CHARSET_ASCII, 0, + SUPDUP_CHARSET_ITS, 1, + SUPDUP_CHARSET_WAITS, 2, -1); + test_bool_simple(CONF_supdup_more, "SUPDUPMoreProcessing", false); + test_bool_simple(CONF_supdup_scroll, "SUPDUPScrolling", false); + test_bool_simple(CONF_bksp_is_delete, "BackspaceIsDelete", true); + test_bool_simple(CONF_rxvt_homeend, "RXVTHomeEnd", false); + test_int_translated(CONF_funky_type, "LinuxFunctionKeys", FUNKY_TILDE, + FUNKY_TILDE, 0, FUNKY_LINUX, 1, FUNKY_XTERM, 2, + FUNKY_VT400, 3, FUNKY_VT100P, 4, FUNKY_SCO, 5, + FUNKY_XTERM_216, 6, -1); + test_int_translated(CONF_sharrow_type, "ShiftedArrowKeys", + SHARROW_APPLICATION, + SHARROW_APPLICATION, 0, SHARROW_BITMAP, 1, -1); + test_bool_simple(CONF_no_applic_c, "NoApplicationCursors", false); + test_bool_simple(CONF_no_applic_k, "NoApplicationKeys", false); + test_bool_simple(CONF_no_mouse_rep, "NoMouseReporting", false); + test_bool_simple(CONF_no_remote_resize, "NoRemoteResize", false); + test_bool_simple(CONF_no_alt_screen, "NoAltScreen", false); + test_bool_simple(CONF_no_remote_wintitle, "NoRemoteWinTitle", false); + test_bool_simple(CONF_no_remote_clearscroll, "NoRemoteClearScroll", false); + test_bool_simple(CONF_no_dbackspace, "NoDBackspace", false); + test_bool_simple(CONF_no_remote_charset, "NoRemoteCharset", false); + /* note we have no test for CONF_remote_qtitle_action because no default */ + test_bool_simple(CONF_app_cursor, "ApplicationCursorKeys", false); + test_bool_simple(CONF_app_keypad, "ApplicationKeypad", false); + test_bool_simple(CONF_nethack_keypad, "NetHackKeypad", false); + test_bool_simple(CONF_telnet_keyboard, "TelnetKey", false); + test_bool_simple(CONF_telnet_newline, "TelnetRet", true); + test_bool_simple(CONF_alt_f4, "AltF4", true); + test_bool_simple(CONF_alt_space, "AltSpace", false); + test_bool_simple(CONF_alt_only, "AltOnly", false); + test_int_translated(CONF_localecho, "LocalEcho", AUTO, + FORCE_ON, 0, FORCE_OFF, 1, AUTO, 2, -1); + test_int_translated(CONF_localedit, "LocalEdit", AUTO, + FORCE_ON, 0, FORCE_OFF, 1, AUTO, 2, -1); + test_bool_simple(CONF_alwaysontop, "AlwaysOnTop", false); + test_bool_simple(CONF_fullscreenonaltenter, "FullScreenOnAltEnter", false); + test_bool_simple(CONF_scroll_on_key, "ScrollOnKey", false); + test_bool_simple(CONF_scroll_on_disp, "ScrollOnDisp", true); + test_bool_simple(CONF_erase_to_scrollback, "EraseToScrollback", true); + test_bool_simple(CONF_compose_key, "ComposeKey", false); + test_bool_simple(CONF_ctrlaltkeys, "CtrlAltKeys", true); + test_str_simple(CONF_wintitle, "WinTitle", ""); + test_int_simple(CONF_savelines, "ScrollbackLines", 2000); + test_bool_simple(CONF_dec_om, "DECOriginMode", false); + test_bool_simple(CONF_wrap_mode, "AutoWrapMode", true); + test_bool_simple(CONF_lfhascr, "LFImpliesCR", false); + test_int_translated(CONF_cursor_type, "CurType", CURSOR_BLOCK, + CURSOR_BLOCK, 0, CURSOR_UNDERLINE, 1, + CURSOR_VERTICAL_LINE, 2, -1); + test_bool_simple(CONF_blink_cur, "BlinkCur", false); + test_int_translated(CONF_beep, "Beep", 1, + BELL_DISABLED, 0, BELL_DEFAULT, 1, BELL_VISUAL, 2, + BELL_WAVEFILE, 3, BELL_PCSPEAKER, 4, -1); + test_int_translated(CONF_beep_ind, "BeepInd", 0, + B_IND_DISABLED, 0, B_IND_FLASH, 1, B_IND_STEADY, 2, -1); + test_bool_simple(CONF_bellovl, "BellOverload", true); + test_int_simple(CONF_bellovl_n, "BellOverloadN", 5); + test_file_simple(CONF_bell_wavefile, "BellWaveFile"); + test_bool_simple(CONF_scrollbar, "ScrollBar", true); + test_bool_simple(CONF_scrollbar_in_fullscreen, "ScrollBarFullScreen", false); + test_int_translated(CONF_resize_action, "LockSize", RESIZE_TERM, + RESIZE_TERM, 0, RESIZE_DISABLED, 1, RESIZE_FONT, 2, + RESIZE_EITHER, 3, -1); + test_bool_simple(CONF_bce, "BCE", true); + test_bool_simple(CONF_blinktext, "BlinkText", false); + test_bool_simple(CONF_win_name_always, "WinNameAlways", true); + test_int_simple(CONF_width, "TermWidth", 80); + test_int_simple(CONF_height, "TermHeight", 24); + test_font_simple(CONF_font, "Font"); + test_int_translated(CONF_font_quality, "FontQuality", FQ_DEFAULT, + FQ_DEFAULT, 0, FQ_ANTIALIASED, 1, FQ_NONANTIALIASED, 2, + FQ_CLEARTYPE, 3, -1); + test_file_simple(CONF_logfilename, "LogFileName"); + test_int_translated(CONF_logtype, "LogType", LGTYP_NONE, + LGTYP_NONE, 0, LGTYP_ASCII, 1, LGTYP_DEBUG, 2, + LGTYP_PACKETS, 3, LGTYP_SSHRAW, 4, -1); + /* FIXME: this won't work because -1 is also the terminator, darn */ + test_int_translated(CONF_logxfovr, "LogFileClash", LGXF_ASK, + LGXF_OVR, 1, LGXF_APN, 0, LGXF_ASK, -1, -1); + test_bool_simple(CONF_logflush, "LogFlush", true); + test_bool_simple(CONF_logheader, "LogHeader", true); + test_bool_simple(CONF_logomitpass, "SSHLogOmitPasswords", true); + test_bool_simple(CONF_logomitdata, "SSHLogOmitData", false); + test_bool_simple(CONF_hide_mouseptr, "HideMousePtr", false); + test_bool_simple(CONF_sunken_edge, "SunkenEdge", false); + test_int_simple(CONF_window_border, "WindowBorder", 1); + test_str_simple(CONF_answerback, "Answerback", "PuTTY"); + test_str_simple(CONF_printer, "Printer", ""); + test_bool_simple(CONF_no_arabicshaping, "DisableArabicShaping", false); + test_bool_simple(CONF_no_bidi, "DisableBidi", false); + test_bool_simple(CONF_ansi_colour, "ANSIColour", true); + test_bool_simple(CONF_xterm_256_colour, "Xterm256Colour", true); + test_bool_simple(CONF_true_colour, "TrueColour", true); + test_bool_simple(CONF_system_colour, "UseSystemColours", false); + test_bool_simple(CONF_try_palette, "TryPalette", false); + test_int_translated(CONF_mouse_is_xterm, "MouseIsXterm", 0, + MOUSE_COMPROMISE, 0, MOUSE_XTERM, 1, + MOUSE_WINDOWS, 2, -1); + test_bool_simple(CONF_rect_select, "RectSelect", false); + test_bool_simple(CONF_paste_controls, "PasteControls", false); + test_bool_simple(CONF_rawcnp, "RawCNP", false); + test_bool_simple(CONF_utf8linedraw, "UTF8linedraw", false); + test_bool_simple(CONF_rtf_paste, "PasteRTF", false); + test_bool_simple(CONF_mouse_override, "MouseOverride", true); + test_bool_simple(CONF_mouseautocopy, "MouseAutocopy", CLIPUI_DEFAULT_AUTOCOPY); + test_int_translated(CONF_vtmode, "FontVTMode", VT_UNICODE, + VT_XWINDOWS, 0, + VT_OEMANSI, 1, + VT_OEMONLY, 2, + VT_POORMAN, 3, + VT_UNICODE, 4, + -1); + test_str_simple(CONF_line_codepage, "LineCodePage", ""); + test_bool_simple(CONF_cjk_ambig_wide, "CJKAmbigWide", false); + test_bool_simple(CONF_utf8_override, "UTF8Override", true); + test_bool_simple(CONF_xlat_capslockcyr, "CapsLockCyr", false); + test_bool_simple(CONF_x11_forward, "X11Forward", false); + test_str_simple(CONF_x11_display, "X11Display", ""); + test_int_translated(CONF_x11_auth, "X11AuthType", X11_MIT, + X11_NO_AUTH, 0, X11_MIT, 1, X11_XDM, 2, -1); + test_file_simple(CONF_xauthfile, "X11AuthFile"); + test_bool_simple(CONF_lport_acceptall, "LocalPortAcceptAll", false); + test_bool_simple(CONF_rport_acceptall, "RemotePortAcceptAll", false); + test_bool_simple(CONF_ssh_connection_sharing, "ConnectionSharing", false); + test_bool_simple(CONF_ssh_connection_sharing_upstream, "ConnectionSharingUpstream", true); + test_bool_simple(CONF_ssh_connection_sharing_downstream, "ConnectionSharingDownstream", true); + test_bool_simple(CONF_stamp_utmp, "StampUtmp", true); + test_bool_simple(CONF_login_shell, "LoginShell", true); + test_bool_simple(CONF_scrollbar_on_left, "ScrollbarOnLeft", false); + test_bool_simple(CONF_shadowbold, "ShadowBold", false); + test_font_simple(CONF_boldfont, "BoldFont"); + test_font_simple(CONF_widefont, "WideFont"); + test_font_simple(CONF_wideboldfont, "WideBoldFont"); + test_int_simple(CONF_shadowboldoffset, "ShadowBoldOffset", 1); + test_bool_simple(CONF_crhaslf, "CRImpliesLF", false); + test_str_simple(CONF_winclass, "WindowClass", ""); + test_int_translated(CONF_close_on_exit, "CloseOnExit", AUTO, + FORCE_OFF, 0, AUTO, 1, FORCE_ON, 2, -1); + test_int_translated(CONF_proxy_dns, "ProxyDNS", AUTO, + FORCE_OFF, 0, AUTO, 1, FORCE_ON, 2, -1); + test_int_translated(CONF_bold_style, "BoldAsColour", AUTO, + 1, 0, 2, 1, 3, 2, -1); + test_int_translated(CONF_sshbug_ignore1, "BugIgnore1", AUTO, + AUTO, 0, FORCE_OFF, 1, FORCE_ON, 2, -1); + test_int_translated(CONF_sshbug_plainpw1, "BugPlainPW1", AUTO, + AUTO, 0, FORCE_OFF, 1, FORCE_ON, 2, -1); + test_int_translated(CONF_sshbug_rsa1, "BugRSA1", AUTO, + AUTO, 0, FORCE_OFF, 1, FORCE_ON, 2, -1); + test_int_translated(CONF_sshbug_ignore2, "BugIgnore2", AUTO, + AUTO, 0, FORCE_OFF, 1, FORCE_ON, 2, -1); + test_int_translated(CONF_sshbug_derivekey2, "BugDeriveKey2", AUTO, + AUTO, 0, FORCE_OFF, 1, FORCE_ON, 2, -1); + test_int_translated(CONF_sshbug_rsapad2, "BugRSAPad2", AUTO, + AUTO, 0, FORCE_OFF, 1, FORCE_ON, 2, -1); + test_int_translated(CONF_sshbug_pksessid2, "BugPKSessID2", AUTO, + AUTO, 0, FORCE_OFF, 1, FORCE_ON, 2, -1); + test_int_translated(CONF_sshbug_rekey2, "BugRekey2", AUTO, + AUTO, 0, FORCE_OFF, 1, FORCE_ON, 2, -1); + test_int_translated(CONF_sshbug_maxpkt2, "BugMaxPkt2", AUTO, + AUTO, 0, FORCE_OFF, 1, FORCE_ON, 2, -1); + test_int_translated(CONF_sshbug_oldgex2, "BugOldGex2", AUTO, + AUTO, 0, FORCE_OFF, 1, FORCE_ON, 2, -1); + test_int_translated(CONF_sshbug_winadj, "BugWinadj", AUTO, + AUTO, 0, FORCE_OFF, 1, FORCE_ON, 2, -1); + test_int_translated(CONF_sshbug_chanreq, "BugChanReq", AUTO, + AUTO, 0, FORCE_OFF, 1, FORCE_ON, 2, -1); + test_int_translated(CONF_sshbug_dropstart, "BugDropStart", FORCE_OFF, + FORCE_OFF, 1, FORCE_ON, 2, -1); + test_int_translated(CONF_sshbug_filter_kexinit, "BugFilterKexinit", FORCE_OFF, + FORCE_OFF, 1, FORCE_ON, 2, -1); + test_int_translated(CONF_sshbug_rsa_sha2_cert_userauth, "BugRSASHA2CertUserauth", AUTO, + AUTO, 0, FORCE_OFF, 1, FORCE_ON, 2, -1); + test_int_translated(CONF_proxy_type, "ProxyMethod", PROXY_NONE, + PROXY_NONE, 0, PROXY_SOCKS4, 1, PROXY_SOCKS5, 2, + PROXY_HTTP, 3, PROXY_TELNET, 4, PROXY_CMD, 5, + PROXY_SSH_TCPIP, 6, PROXY_SSH_EXEC, 7, + PROXY_SSH_SUBSYSTEM, 8, -1); + test_int_translated_load_legacy( + CONF_proxy_type, "ProxyType", NULL, PROXY_NONE, + PROXY_HTTP, 1, PROXY_SOCKS5, 2, PROXY_TELNET, 3, PROXY_CMD, 4, -1); + test_int_translated_load_legacy( + CONF_proxy_type, "ProxyType", load_prepare_socks4, PROXY_NONE, + PROXY_HTTP, 1, PROXY_SOCKS4, 2, PROXY_TELNET, 3, PROXY_CMD, 4, -1); + test_int_translated(CONF_remote_qtitle_action, "RemoteQTitleAction", TITLE_EMPTY, + TITLE_NONE, 0, TITLE_EMPTY, 1, TITLE_REAL, 2, -1); + test_int_translated_load_legacy( + CONF_remote_qtitle_action, "NoRemoteQTitle", NULL, TITLE_EMPTY, + TITLE_REAL, 0, TITLE_EMPTY, 1, -1); +} + +void test_conf_key_info(void) +{ + struct test_data { + const char *name; + bool got_value_type : 1; + bool got_subkey_type : 1; + bool got_default : 1; + bool got_default_int : 1; + bool got_default_str : 1; + bool got_default_bool : 1; + bool got_save_keyword : 1; + bool got_storage_enum : 1; + bool save_custom : 1; + bool load_custom : 1; + bool not_saved : 1; + }; + +#define CONF_OPTION(id, ...) { .name = "CONF_" #id, __VA_ARGS__ }, +#define VALUE_TYPE(x) .got_value_type = true +#define SUBKEY_TYPE(x) .got_subkey_type = true +#define DEFAULT_INT(x) .got_default_int = true, .got_default = true +#define DEFAULT_STR(x) .got_default_str = true, .got_default = true +#define DEFAULT_BOOL(x) .got_default_bool = true, .got_default = true +#define SAVE_KEYWORD(x) .got_save_keyword = true +#define STORAGE_ENUM(x) .got_storage_enum = true +#define SAVE_CUSTOM .save_custom = true +#define LOAD_CUSTOM .load_custom = true +#define NOT_SAVED .not_saved = true + + static const struct test_data conf_key_test_data[] = { + #include "conf.h" + }; + + for (size_t key = 0; key < N_CONFIG_OPTIONS; key++) { + const ConfKeyInfo *info = &conf_key_info[key]; + const struct test_data *td = &conf_key_test_data[key]; + + if (!td->got_value_type) { + fprintf(stderr, "%s: no value type\n", td->name); + nfails++; + } + + if (td->got_default && info->subkey_type != CONF_TYPE_NONE) { + fprintf(stderr, "%s: is a mapping but has a default\n", td->name); + nfails++; + } + + if ((td->got_default_int && info->value_type != CONF_TYPE_INT) || + (td->got_default_str && + (info->value_type != CONF_TYPE_STR && + info->value_type != CONF_TYPE_STR_AMBI && + info->value_type != CONF_TYPE_UTF8)) || + (td->got_default_bool && info->value_type != CONF_TYPE_BOOL)) { + fprintf(stderr, "%s: default doesn't match type\n", td->name); + nfails++; + } + + if (td->got_storage_enum && info->value_type != CONF_TYPE_INT) { + fprintf(stderr, "%s: has STORAGE_ENUM but isn't an int\n", + td->name); + nfails++; + } + + if (td->not_saved) { + if (!td->got_default && info->subkey_type == CONF_TYPE_NONE) { + fprintf(stderr, "%s: simple unsaved setting but has no " + "default\n", td->name); + nfails++; + } + + if (td->got_save_keyword) { + fprintf(stderr, "%s: not saved but has SAVE_KEYWORD\n", + td->name); + nfails++; + } + + if (td->save_custom) { + fprintf(stderr, "%s: not saved but has SAVE_CUSTOM\n", + td->name); + nfails++; + } + + if (td->load_custom) { + fprintf(stderr, "%s: not saved but has LOAD_CUSTOM\n", + td->name); + nfails++; + } + + if (td->got_storage_enum) { + fprintf(stderr, "%s: not saved but has STORAGE_ENUM\n", + td->name); + nfails++; + } + + } else { + if (td->load_custom && td->save_custom) { + if (td->got_save_keyword) { + fprintf(stderr, "%s: no automatic save or load but has " + "SAVE_KEYWORD\n", td->name); + nfails++; + } + + if (td->got_storage_enum) { + fprintf(stderr, "%s: no automatic save or load but has " + "STORAGE_ENUM\n", td->name); + nfails++; + } + } else { + if (!td->got_save_keyword) { + fprintf(stderr, "%s: missing SAVE_KEYWORD\n", td->name); + nfails++; + } + } + } + } +} + +int main(void) +{ + test_conf_key_info(); + test_simple(); + return nfails != 0; +} diff --git a/test/test_lineedit.c b/test/test_lineedit.c new file mode 100644 index 00000000..fe0be162 --- /dev/null +++ b/test/test_lineedit.c @@ -0,0 +1,773 @@ +#include "putty.h" +#include "terminal.h" + +void modalfatalbox(const char *p, ...) +{ + va_list ap; + fprintf(stderr, "FATAL ERROR: "); + va_start(ap, p); + vfprintf(stderr, p, ap); + va_end(ap); + fputc('\n', stderr); + exit(1); +} + +const char *const appname = "test_lineedit"; + +char *platform_default_s(const char *name) +{ return NULL; } +bool platform_default_b(const char *name, bool def) +{ return def; } +int platform_default_i(const char *name, int def) +{ return def; } +FontSpec *platform_default_fontspec(const char *name) +{ return fontspec_new_default(); } +Filename *platform_default_filename(const char *name) +{ return filename_from_str(""); } + +struct SpecialRecord { + SessionSpecialCode code; + int arg; +}; + +typedef struct Mock { + Terminal *term; + Ldisc *ldisc; + Conf *conf; + struct unicode_data ucsdata[1]; + + bool echo, edit; + strbuf *to_terminal, *to_backend; + + struct SpecialRecord *specials; + size_t nspecials, specialsize; + + strbuf *context; /* for printing in failed tests */ + + bool any_test_failed; + + TermWin tw; + Seat seat; + Backend backend; +} Mock; + +static size_t mock_output(Seat *seat, SeatOutputType type, + const void *data, size_t len) +{ + Mock *mk = container_of(seat, Mock, seat); + put_data(mk->to_terminal, data, len); + return 0; +} + +static void mock_send(Backend *be, const char *buf, size_t len) +{ + Mock *mk = container_of(be, Mock, backend); + put_data(mk->to_backend, buf, len); +} + +static void mock_special(Backend *be, SessionSpecialCode code, int arg) +{ + Mock *mk = container_of(be, Mock, backend); + sgrowarray(mk->specials, mk->specialsize, mk->nspecials); + mk->specials[mk->nspecials].code = code; + mk->specials[mk->nspecials].arg = arg; + mk->nspecials++; +} + +static bool mock_ldisc_option_state(Backend *be, int option) +{ + Mock *mk = container_of(be, Mock, backend); + switch (option) { + case LD_ECHO: + return mk->echo; + case LD_EDIT: + return mk->edit; + default: + unreachable("bad ldisc option"); + } +} + +static void mock_provide_ldisc(Backend *be, Ldisc *ldisc) +{ + Mock *mk = container_of(be, Mock, backend); + mk->ldisc = ldisc; +} + +static bool mock_sendok(Backend *be) +{ + Mock *mk = container_of(be, Mock, backend); + (void)mk; + /* FIXME: perhaps make this settable, to test the input_queue system? */ + return true; +} + +static void mock_set_raw_mouse_mode(TermWin *win, bool enable) {} +static void mock_palette_get_overrides(TermWin *tw, Terminal *term) {} + +static const TermWinVtable mock_termwin_vt = { + .set_raw_mouse_mode = mock_set_raw_mouse_mode, + .palette_get_overrides = mock_palette_get_overrides, +}; + +static const SeatVtable mock_seat_vt = { + .output = mock_output, + .echoedit_update = nullseat_echoedit_update, +}; + +static const BackendVtable mock_backend_vt = { + .sendok = mock_sendok, + .send = mock_send, + .special = mock_special, + .ldisc_option_state = mock_ldisc_option_state, + .provide_ldisc = mock_provide_ldisc, + .id = "mock", +}; + +static Mock *mock_new(void) +{ + Mock *mk = snew(Mock); + memset(mk, 0, sizeof(*mk)); + + mk->conf = conf_new(); + do_defaults(NULL, mk->conf); + + init_ucs_generic(mk->conf, mk->ucsdata); + mk->ucsdata->line_codepage = CP_437; + + mk->context = strbuf_new(); + mk->to_terminal = strbuf_new(); + mk->to_backend = strbuf_new(); + + mk->tw.vt = &mock_termwin_vt; + mk->seat.vt = &mock_seat_vt; + mk->backend.vt = &mock_backend_vt; + + return mk; +} + +static void mock_free(Mock *mk) +{ + strbuf_free(mk->context); + strbuf_free(mk->to_terminal); + strbuf_free(mk->to_backend); + conf_free(mk->conf); + term_free(mk->term); + sfree(mk->specials); + sfree(mk); +} + +static void reset(Mock *mk) +{ + strbuf_clear(mk->context); + strbuf_clear(mk->to_terminal); + strbuf_clear(mk->to_backend); + mk->nspecials = 0; +} + +static void test_context(Mock *mk, const char *fmt, ...) +{ + strbuf_clear(mk->context); + va_list ap; + va_start(ap, fmt); + put_fmtv(mk->context, fmt, ap); + va_end(ap); +} + +static void print_context(Mock *mk, const char *file, int line) +{ + printf("%s:%d", file, line); + if (mk->context->len) + printf(" (%s)", mk->context->s); + printf(": "); +} + +#define EXPECT(mk, what, ...) \ + expect_ ## what(mk, __FILE__, __LINE__, __VA_ARGS__) + +static void expect_backend(Mock *mk, const char *file, int line, + ptrlen expected) +{ + ptrlen actual = ptrlen_from_strbuf(mk->to_backend); + if (!ptrlen_eq_ptrlen(expected, actual)) { + print_context(mk, file, line); + printf("expected backend output \""); + write_c_string_literal(stdout, expected); + printf("\", got \""); + write_c_string_literal(stdout, actual); + printf("\"\n"); + mk->any_test_failed = true; + } +} + +static void expect_terminal(Mock *mk, const char *file, int line, + ptrlen expected) +{ + ptrlen actual = ptrlen_from_strbuf(mk->to_terminal); + if (!ptrlen_eq_ptrlen(expected, actual)) { + print_context(mk, file, line); + printf("expected terminal output \""); + write_c_string_literal(stdout, expected); + printf("\", got \""); + write_c_string_literal(stdout, actual); + printf("\"\n"); + mk->any_test_failed = true; + } +} + +static void expect_specials(Mock *mk, const char *file, int line, + size_t nspecials, ...) +{ + va_list ap; + + static const char *const special_names[] = { +#define SPECIAL(x) #x, +#include "specials.h" +#undef SPECIAL + }; + + bool match; + if (nspecials != mk->nspecials) { + match = false; + } else { + match = true; + va_start(ap, nspecials); + for (size_t i = 0; i < nspecials; i++) { + SessionSpecialCode code = va_arg(ap, SessionSpecialCode); + int arg = va_arg(ap, int); + if (code != mk->specials[i].code || arg != mk->specials[i].arg) + match = false; + } + va_end(ap); + } + + if (!match) { + print_context(mk, file, line); + printf("expected specials ["); + va_start(ap, nspecials); + for (size_t i = 0; i < nspecials; i++) { + SessionSpecialCode code = va_arg(ap, SessionSpecialCode); + int arg = va_arg(ap, int); + printf(" %s.%d", special_names[code], arg); + } + va_end(ap); + printf(" ], got ["); + for (size_t i = 0; i < mk->nspecials; i++) { + printf(" %s.%d", special_names[mk->specials[i].code], + mk->specials[i].arg); + } + printf(" ]\n"); + mk->any_test_failed = true; + } +} + +static void test_noedit(Mock *mk) +{ + mk->edit = false; + mk->echo = false; + + /* + * In non-echo and non-edit mode, the channel is 8-bit clean + */ + for (unsigned c = 0; c < 256; c++) { + char buf[1]; + + test_context(mk, "c=%02x", c); + buf[0] = c; + ldisc_send(mk->ldisc, buf, 1, false); + EXPECT(mk, backend, make_ptrlen(buf, 1)); + reset(mk); + } + /* ... regardless of the 'interactive' flag */ + for (unsigned c = 0; c < 256; c++) { + char buf[1]; + + test_context(mk, "c=%02x", c); + buf[0] = c; + ldisc_send(mk->ldisc, buf, 1, true); + EXPECT(mk, backend, make_ptrlen(buf, 1)); + reset(mk); + } + /* ... and any nonzero character does the same thing even if sent + * with the magic -1 length flag */ + for (unsigned c = 1; c < 256; c++) { + char buf[2]; + + test_context(mk, "c=%02x", c); + buf[0] = c; + buf[1] = '\0'; + ldisc_send(mk->ldisc, buf, -1, true); + EXPECT(mk, backend, make_ptrlen(buf, 1)); + reset(mk); + } + + /* + * Test the special-character cases for Telnet. + */ + conf_set_int(mk->conf, CONF_protocol, PROT_TELNET); + conf_set_bool(mk->conf, CONF_telnet_newline, false); + conf_set_bool(mk->conf, CONF_telnet_keyboard, false); + ldisc_configure(mk->ldisc, mk->conf); + + /* Without telnet_newline or telnet_keyboard, these all do the + * normal thing */ + ldisc_send(mk->ldisc, "\x0D", -1, true); + EXPECT(mk, backend, PTRLEN_LITERAL("\x0D")); + reset(mk); + ldisc_send(mk->ldisc, "\x08", -1, true); + EXPECT(mk, backend, PTRLEN_LITERAL("\x08")); + reset(mk); + ldisc_send(mk->ldisc, "\x7F", -1, true); + EXPECT(mk, backend, PTRLEN_LITERAL("\x7F")); + reset(mk); + ldisc_send(mk->ldisc, "\x03", -1, true); + EXPECT(mk, backend, PTRLEN_LITERAL("\x03")); + reset(mk); + ldisc_send(mk->ldisc, "\x1A", -1, true); + EXPECT(mk, backend, PTRLEN_LITERAL("\x1A")); + reset(mk); + + /* telnet_newline controls translation of CR into SS_EOL */ + conf_set_bool(mk->conf, CONF_telnet_newline, true); + ldisc_configure(mk->ldisc, mk->conf); + ldisc_send(mk->ldisc, "\x0D", -1, true); + EXPECT(mk, specials, 1, SS_EOL, 0); + reset(mk); + + /* And telnet_keyboard controls the others */ + conf_set_bool(mk->conf, CONF_telnet_newline, false); + conf_set_bool(mk->conf, CONF_telnet_keyboard, true); + ldisc_configure(mk->ldisc, mk->conf); + ldisc_send(mk->ldisc, "\x08", -1, true); + EXPECT(mk, specials, 1, SS_EC, 0); + reset(mk); + ldisc_send(mk->ldisc, "\x7F", -1, true); + EXPECT(mk, specials, 1, SS_EC, 0); + reset(mk); + ldisc_send(mk->ldisc, "\x03", -1, true); + EXPECT(mk, specials, 1, SS_IP, 0); + reset(mk); + ldisc_send(mk->ldisc, "\x1A", -1, true); + EXPECT(mk, specials, 1, SS_SUSP, 0); + reset(mk); + + /* + * In echo-but-no-edit mode, we also expect that every character + * is echoed back to the display as a side effect, including when + * it's sent as a special -1 keystroke. + * + * This state only comes up in Telnet, because that has protocol + * options to independently configure echo and edit. Telnet is + * also the most complicated of the protocols because of the above + * special cases, so we stay in Telnet mode for this test. + */ + mk->echo = true; + for (unsigned c = 0; c < 256; c++) { + char buf[1]; + + test_context(mk, "c=%02x", c); + buf[0] = c; + ldisc_send(mk->ldisc, buf, 1, false); + EXPECT(mk, terminal, make_ptrlen(buf, 1)); + reset(mk); + } + for (unsigned c = 1; c < 256; c++) { + char buf[2]; + + test_context(mk, "c=%02x", c); + buf[0] = c; + buf[1] = '\0'; + ldisc_send(mk->ldisc, buf, -1, true); + EXPECT(mk, terminal, make_ptrlen(buf, 1)); + reset(mk); + } + + do_defaults(NULL, mk->conf); + ldisc_configure(mk->ldisc, mk->conf); +} + +static void test_edit(Mock *mk, bool echo) +{ + static const char *const ctls = "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"; + + mk->edit = true; + mk->echo = echo; + +#define EXPECT_TERMINAL(mk, val) do { \ + if (!echo) EXPECT(mk, terminal, PTRLEN_LITERAL("")); \ + else EXPECT(mk, terminal, val); \ + } while (0) + + /* ASCII printing characters all print when entered, but don't go + * to the terminal until Return is pressed */ + for (unsigned c = 0x20; c < 0x7F; c++) { + char buf[3]; + + test_context(mk, "c=%02x", c); + buf[0] = c; + ldisc_send(mk->ldisc, buf, 1, false); + EXPECT(mk, backend, PTRLEN_LITERAL("")); + EXPECT_TERMINAL(mk, make_ptrlen(buf, 1)); + ldisc_send(mk->ldisc, "\015", 1, false); + buf[1] = '\015'; + buf[2] = '\012'; + EXPECT(mk, backend, make_ptrlen(buf, 3)); + EXPECT_TERMINAL(mk, make_ptrlen(buf, 3)); + reset(mk); + } + + /* C0 control characters mostly show up as ^X or similar */ + for (unsigned c = 0; c < 0x1F; c++) { + char backbuf[3]; + char termbuf[4]; + + switch (ctls[c]) { + case 'D': continue; /* ^D sends EOF */ + case 'M': continue; /* ^M is Return */ + case 'R': continue; /* ^R redisplays line */ + case 'U': continue; /* ^U deletes the line */ + case 'V': continue; /* ^V makes the next char literal */ + case 'W': continue; /* ^W deletes a word */ + /* + * ^H / ^? are not included here. Those are treated + * literally if sent as plain input bytes. Only sending + * them as special via length==-1 causes them to act as + * backspace, which I think was simply because there _is_ + * a dedicated key that can do that function, so there's + * no need to also eat the Ctrl+thing combo. + */ + + /* + * Also, ^C, ^Z and ^\ self-insert (when not in Telnet + * mode) but have the side effect of erasing the line + * buffer so far. In this loop, that doesn't show up, + * because the line buffer is empty already. However, I + * don't test that, because it's silly, and probably + * doesn't want to keep happening! + */ + } + + test_context(mk, "c=%02x", c); + backbuf[0] = c; + ldisc_send(mk->ldisc, backbuf, 1, false); + EXPECT(mk, backend, PTRLEN_LITERAL("")); + termbuf[0] = '^'; + termbuf[1] = ctls[c]; + EXPECT_TERMINAL(mk, make_ptrlen(termbuf, 2)); + ldisc_send(mk->ldisc, "\015", 1, false); + backbuf[1] = '\015'; + backbuf[2] = '\012'; + EXPECT(mk, backend, make_ptrlen(backbuf, 3)); + termbuf[2] = '\015'; + termbuf[3] = '\012'; + EXPECT_TERMINAL(mk, make_ptrlen(termbuf, 4)); + reset(mk); + } + + /* Prefixed with ^V, the same is true of _all_ C0 controls */ + for (unsigned c = 0; c < 0x1F; c++) { + char backbuf[3]; + char termbuf[4]; + + test_context(mk, "c=%02x", c); + backbuf[0] = 'V' & 0x1F; + ldisc_send(mk->ldisc, backbuf, 1, false); + backbuf[0] = c; + ldisc_send(mk->ldisc, backbuf, 1, false); + EXPECT(mk, backend, PTRLEN_LITERAL("")); + termbuf[0] = '^'; + termbuf[1] = ctls[c]; + EXPECT_TERMINAL(mk, make_ptrlen(termbuf, 2)); + ldisc_send(mk->ldisc, "\015", 1, false); + backbuf[1] = '\015'; + backbuf[2] = '\012'; + EXPECT(mk, backend, make_ptrlen(backbuf, 3)); + termbuf[2] = '\015'; + termbuf[3] = '\012'; + EXPECT_TERMINAL(mk, make_ptrlen(termbuf, 4)); + reset(mk); + } + + /* Deleting an ASCII character sends a single BSB and deletes just + * that byte from the buffer */ + ldisc_send(mk->ldisc, "ab", 2, false); + EXPECT(mk, backend, PTRLEN_LITERAL("")); + EXPECT_TERMINAL(mk, PTRLEN_LITERAL("ab")); + ldisc_send(mk->ldisc, "\x08", -1, false); + EXPECT(mk, backend, PTRLEN_LITERAL("")); + EXPECT_TERMINAL(mk, PTRLEN_LITERAL("ab\x08 \x08")); + ldisc_send(mk->ldisc, "\x0D", -1, false); + EXPECT(mk, backend, PTRLEN_LITERAL("a\x0D\x0A")); + reset(mk); + + /* Deleting a character written as a ^X code sends two BSBs to + * wipe out the two-character display sequence */ + ldisc_send(mk->ldisc, "a\x02", 2, false); + EXPECT(mk, backend, PTRLEN_LITERAL("")); + EXPECT_TERMINAL(mk, PTRLEN_LITERAL("a^B")); + ldisc_send(mk->ldisc, "\x7F", -1, false); + EXPECT(mk, backend, PTRLEN_LITERAL("")); + EXPECT_TERMINAL(mk, PTRLEN_LITERAL("a^B\x08 \x08\x08 \x08")); + ldisc_send(mk->ldisc, "\x0D", -1, false); + EXPECT(mk, backend, PTRLEN_LITERAL("a\x0D\x0A")); + reset(mk); + + /* ^D sends the line editing buffer without a trailing Return, if + * it's non-empty */ + ldisc_send(mk->ldisc, "abc\x04", 4, false); + EXPECT(mk, backend, PTRLEN_LITERAL("abc")); + EXPECT_TERMINAL(mk, PTRLEN_LITERAL("abc")); + ldisc_send(mk->ldisc, "\x0D", -1, false); + EXPECT(mk, backend, PTRLEN_LITERAL("abc\x0D\x0A")); + reset(mk); + + /* But if the buffer is empty, ^D sends SS_EOF */ + ldisc_send(mk->ldisc, "\x04", 1, false); + EXPECT(mk, backend, PTRLEN_LITERAL("")); + EXPECT_TERMINAL(mk, PTRLEN_LITERAL("")); + EXPECT(mk, specials, 1, SS_EOF, 0); + reset(mk); + + /* ^R redraws the current line, after printing "^R" at the end of + * the previous attempt to make it clear that that's what + * happened */ + ldisc_send(mk->ldisc, "a\x01", 2, false); + EXPECT(mk, backend, PTRLEN_LITERAL("")); + EXPECT_TERMINAL(mk, PTRLEN_LITERAL("a^A")); + ldisc_send(mk->ldisc, "\x12", 1, false); + EXPECT(mk, backend, PTRLEN_LITERAL("")); + EXPECT_TERMINAL(mk, PTRLEN_LITERAL("a^A^R\x0D\x0A" "a^A")); + ldisc_send(mk->ldisc, "\x0D", -1, false); + reset(mk); + + /* ^U deletes the whole line */ + ldisc_send(mk->ldisc, "a b c", 5, false); + EXPECT(mk, backend, PTRLEN_LITERAL("")); + EXPECT_TERMINAL(mk, PTRLEN_LITERAL("a b c")); + ldisc_send(mk->ldisc, "\x15", 1, false); + EXPECT(mk, backend, PTRLEN_LITERAL("")); + EXPECT_TERMINAL( + mk, PTRLEN_LITERAL( + "a b c\x08 \x08\x08 \x08\x08 \x08\x08 \x08\x08 \x08")); + ldisc_send(mk->ldisc, "\x0D", -1, false); + EXPECT(mk, backend, PTRLEN_LITERAL("\x0D\x0A")); + EXPECT_TERMINAL( + mk, PTRLEN_LITERAL( + "a b c\x08 \x08\x08 \x08\x08 \x08\x08 \x08\x08 \x08\x0D\x0A")); + reset(mk); + /* And it still knows that a control character written as ^X takes + * two BSBs to delete */ + ldisc_send(mk->ldisc, "a\x02" "c", 3, false); + EXPECT(mk, backend, PTRLEN_LITERAL("")); + EXPECT_TERMINAL(mk, PTRLEN_LITERAL("a^Bc")); + ldisc_send(mk->ldisc, "\x15", 1, false); + EXPECT(mk, backend, PTRLEN_LITERAL("")); + EXPECT_TERMINAL( + mk, PTRLEN_LITERAL("a^Bc\x08 \x08\x08 \x08\x08 \x08\x08 \x08")); + ldisc_send(mk->ldisc, "\x0D", -1, false); + reset(mk); + + /* ^W deletes a word, which means that it deletes to the most + * recent boundary with a space on the left and a nonspace on the + * right. (Or the beginning of the string, whichever comes first.) */ + ldisc_send(mk->ldisc, "hello, world\x17", 13, false); + EXPECT(mk, backend, PTRLEN_LITERAL("")); + EXPECT_TERMINAL( + mk, PTRLEN_LITERAL( + "hello, world\x08 \x08\x08 \x08\x08 \x08\x08 \x08\x08 \x08")); + ldisc_send(mk->ldisc, "\x0D", 1, false); + EXPECT(mk, backend, PTRLEN_LITERAL("hello, \x0D\x0A")); + reset(mk); + ldisc_send(mk->ldisc, "hello, world \x17", 14, false); + EXPECT(mk, backend, PTRLEN_LITERAL("")); + EXPECT_TERMINAL( + mk, PTRLEN_LITERAL( + "hello, world " + "\x08 \x08\x08 \x08\x08 \x08\x08 \x08\x08 \x08\x08 \x08")); + ldisc_send(mk->ldisc, "\x0D", 1, false); + EXPECT(mk, backend, PTRLEN_LITERAL("hello, \x0D\x0A")); + reset(mk); + ldisc_send(mk->ldisc, " hello \x17", 8, false); + EXPECT(mk, backend, PTRLEN_LITERAL("")); + EXPECT_TERMINAL( + mk, PTRLEN_LITERAL( + " hello \x08 \x08\x08 \x08\x08 \x08\x08 \x08\x08 \x08\x08 \x08")); + ldisc_send(mk->ldisc, "\x0D", 1, false); + EXPECT(mk, backend, PTRLEN_LITERAL(" \x0D\x0A")); + reset(mk); + ldisc_send(mk->ldisc, "hello \x17", 7, false); + EXPECT(mk, backend, PTRLEN_LITERAL("")); + EXPECT_TERMINAL( + mk, PTRLEN_LITERAL( + "hello \x08 \x08\x08 \x08\x08 \x08\x08 \x08\x08 \x08\x08 \x08")); + ldisc_send(mk->ldisc, "\x0D", 1, false); + EXPECT(mk, backend, PTRLEN_LITERAL("\x0D\x0A")); + reset(mk); + /* And this too knows that a control character written as ^X takes + * two BSBs to delete */ + ldisc_send(mk->ldisc, "a\x02" "c", 3, false); + EXPECT(mk, backend, PTRLEN_LITERAL("")); + EXPECT_TERMINAL(mk, PTRLEN_LITERAL("a^Bc")); + ldisc_send(mk->ldisc, "\x17", 1, false); + EXPECT(mk, backend, PTRLEN_LITERAL("")); + EXPECT_TERMINAL( + mk, PTRLEN_LITERAL("a^Bc\x08 \x08\x08 \x08\x08 \x08\x08 \x08")); + ldisc_send(mk->ldisc, "\x0D", -1, false); + reset(mk); + + /* Test handling of ^C and friends in non-telnet_keyboard mode */ + ldisc_send(mk->ldisc, "abc\x03", 4, false); + EXPECT(mk, backend, PTRLEN_LITERAL("")); + EXPECT_TERMINAL(mk, PTRLEN_LITERAL("abc\x08 \x08\x08 \x08\x08 \x08^C")); + EXPECT(mk, specials, 1, SS_EL, 0); + ldisc_send(mk->ldisc, "\x0D", -1, false); + reset(mk); + ldisc_send(mk->ldisc, "abc\x1a", 4, false); + EXPECT(mk, backend, PTRLEN_LITERAL("")); + EXPECT_TERMINAL(mk, PTRLEN_LITERAL("abc\x08 \x08\x08 \x08\x08 \x08^Z")); + EXPECT(mk, specials, 1, SS_EL, 0); + ldisc_send(mk->ldisc, "\x0D", -1, false); + reset(mk); + ldisc_send(mk->ldisc, "abc\x1c", 4, false); + EXPECT(mk, backend, PTRLEN_LITERAL("")); + EXPECT_TERMINAL(mk, PTRLEN_LITERAL("abc\x08 \x08\x08 \x08\x08 \x08^\\")); + EXPECT(mk, specials, 1, SS_EL, 0); + ldisc_send(mk->ldisc, "\x0D", -1, false); + reset(mk); + + /* And in telnet_keyboard mode */ + conf_set_bool(mk->conf, CONF_telnet_keyboard, true); + ldisc_configure(mk->ldisc, mk->conf); + + /* FIXME: should we _really_ be sending EL before each of these? */ + ldisc_send(mk->ldisc, "abc\x03", 4, false); + EXPECT(mk, backend, PTRLEN_LITERAL("")); + EXPECT_TERMINAL(mk, PTRLEN_LITERAL("abc\x08 \x08\x08 \x08\x08 \x08")); + EXPECT(mk, specials, 2, SS_EL, 0, SS_IP, 0); + ldisc_send(mk->ldisc, "\x0D", -1, false); + reset(mk); + ldisc_send(mk->ldisc, "abc\x1a", 4, false); + EXPECT(mk, backend, PTRLEN_LITERAL("")); + EXPECT_TERMINAL(mk, PTRLEN_LITERAL("abc\x08 \x08\x08 \x08\x08 \x08")); + EXPECT(mk, specials, 2, SS_EL, 0, SS_SUSP, 0); + ldisc_send(mk->ldisc, "\x0D", -1, false); + reset(mk); + ldisc_send(mk->ldisc, "abc\x1c", 4, false); + EXPECT(mk, backend, PTRLEN_LITERAL("")); + EXPECT_TERMINAL(mk, PTRLEN_LITERAL("abc\x08 \x08\x08 \x08\x08 \x08")); + EXPECT(mk, specials, 2, SS_EL, 0, SS_ABORT, 0); + ldisc_send(mk->ldisc, "\x0D", -1, false); + reset(mk); + + conf_set_bool(mk->conf, CONF_telnet_keyboard, false); + ldisc_configure(mk->ldisc, mk->conf); + + /* Test UTF-8 characters of various lengths and ensure deleting + * one deletes the whole character from the buffer (by pressing + * Return and seeing what gets sent) but sends a number of BSBs + * corresponding to the character's terminal width */ + mk->term->utf = true; + + ldisc_send(mk->ldisc, "\xC2\xA0\xC2\xA1", 4, false); + EXPECT(mk, backend, PTRLEN_LITERAL("")); + EXPECT_TERMINAL(mk, PTRLEN_LITERAL("\xC2\xA0\xC2\xA1")); + ldisc_send(mk->ldisc, "\x08", -1, false); + EXPECT(mk, backend, PTRLEN_LITERAL("")); + EXPECT_TERMINAL(mk, PTRLEN_LITERAL("\xC2\xA0\xC2\xA1\x08 \x08")); + ldisc_send(mk->ldisc, "\x0D", -1, false); + EXPECT(mk, backend, PTRLEN_LITERAL("\xC2\xA0\x0D\x0A")); + reset(mk); + + ldisc_send(mk->ldisc, "\xE2\xA0\x80\xE2\xA0\x81", 6, false); + EXPECT(mk, backend, PTRLEN_LITERAL("")); + EXPECT_TERMINAL(mk, PTRLEN_LITERAL("\xE2\xA0\x80\xE2\xA0\x81")); + ldisc_send(mk->ldisc, "\x08", -1, false); + EXPECT(mk, backend, PTRLEN_LITERAL("")); + EXPECT_TERMINAL(mk, PTRLEN_LITERAL("\xE2\xA0\x80\xE2\xA0\x81\x08 \x08")); + ldisc_send(mk->ldisc, "\x0D", -1, false); + EXPECT(mk, backend, PTRLEN_LITERAL("\xE2\xA0\x80\x0D\x0A")); + reset(mk); + + ldisc_send(mk->ldisc, "\xF0\x90\x80\x80\xF0\x90\x80\x81", 8, false); + EXPECT(mk, backend, PTRLEN_LITERAL("")); + EXPECT_TERMINAL(mk, PTRLEN_LITERAL("\xF0\x90\x80\x80\xF0\x90\x80\x81")); + ldisc_send(mk->ldisc, "\x08", -1, false); + EXPECT(mk, backend, PTRLEN_LITERAL("")); + EXPECT_TERMINAL(mk, PTRLEN_LITERAL("\xF0\x90\x80\x80\xF0\x90\x80\x81" + "\x08 \x08")); + ldisc_send(mk->ldisc, "\x0D", -1, false); + EXPECT(mk, backend, PTRLEN_LITERAL("\xF0\x90\x80\x80\x0D\x0A")); + reset(mk); + + /* Double-width characters (Hangul, as it happens) */ + ldisc_send(mk->ldisc, "\xEA\xB0\x80\xEA\xB0\x81", 6, false); + EXPECT(mk, backend, PTRLEN_LITERAL("")); + EXPECT_TERMINAL(mk, PTRLEN_LITERAL("\xEA\xB0\x80\xEA\xB0\x81")); + ldisc_send(mk->ldisc, "\x08", -1, false); + EXPECT(mk, backend, PTRLEN_LITERAL("")); + EXPECT_TERMINAL(mk, PTRLEN_LITERAL("\xEA\xB0\x80\xEA\xB0\x81" + "\x08 \x08\x08 \x08")); + ldisc_send(mk->ldisc, "\x0D", -1, false); + EXPECT(mk, backend, PTRLEN_LITERAL("\xEA\xB0\x80\x0D\x0A")); + reset(mk); + + /* Zero-width characters */ + ldisc_send(mk->ldisc, "\xE2\x80\x8B\xE2\x80\x8B", 6, false); + EXPECT(mk, backend, PTRLEN_LITERAL("")); + EXPECT_TERMINAL(mk, PTRLEN_LITERAL("\xE2\x80\x8B\xE2\x80\x8B")); + ldisc_send(mk->ldisc, "\x08", -1, false); + EXPECT(mk, backend, PTRLEN_LITERAL("")); + EXPECT_TERMINAL(mk, PTRLEN_LITERAL("\xE2\x80\x8B\xE2\x80\x8B")); + ldisc_send(mk->ldisc, "\x0D", -1, false); + EXPECT(mk, backend, PTRLEN_LITERAL("\xE2\x80\x8B\x0D\x0A")); + reset(mk); + + /* And reset back to non-UTF-8 mode and expect high-bit-set bytes + * to be treated individually, as characters in a single-byte + * charset. (In our case, given the test config, that will be + * CP437, but it makes no difference to the editing behaviour.) */ + mk->term->utf = false; + ldisc_send(mk->ldisc, "\xC2\xA0\xC2\xA1", 4, false); + EXPECT(mk, backend, PTRLEN_LITERAL("")); + EXPECT_TERMINAL(mk, PTRLEN_LITERAL("\xC2\xA0\xC2\xA1")); + ldisc_send(mk->ldisc, "\x08", -1, false); + EXPECT(mk, backend, PTRLEN_LITERAL("")); + EXPECT_TERMINAL(mk, PTRLEN_LITERAL("\xC2\xA0\xC2\xA1\x08 \x08")); + ldisc_send(mk->ldisc, "\x0D", -1, false); + EXPECT(mk, backend, PTRLEN_LITERAL("\xC2\xA0\xC2\x0D\x0A")); + reset(mk); + + /* Make sure we flush all the terminal contents at the end of this + * function */ + ldisc_send(mk->ldisc, "\x0D", 1, false); + reset(mk); + +#undef EXPECT_TERMINAL + +} + +const struct BackendVtable *const backends[] = { &mock_backend_vt, NULL }; + +int main(void) +{ + Mock *mk = mock_new(); + mk->term = term_init(mk->conf, mk->ucsdata, &mk->tw); + Ldisc *ldisc = ldisc_create(mk->conf, mk->term, &mk->backend, &mk->seat); + term_size(mk->term, 80, 24, 0); + + test_noedit(mk); + test_edit(mk, true); + test_edit(mk, false); + + ldisc_free(ldisc); + + bool failed = mk->any_test_failed; + mock_free(mk); + + if (failed) { + printf("Test suite FAILED!\n"); + return 1; + } else { + printf("Test suite passed\n"); + return 0; + } +} diff --git a/test/test_terminal.c b/test/test_terminal.c new file mode 100644 index 00000000..5ebc1204 --- /dev/null +++ b/test/test_terminal.c @@ -0,0 +1,504 @@ +#include "putty.h" +#include "terminal.h" + +void modalfatalbox(const char *p, ...) +{ + va_list ap; + fprintf(stderr, "FATAL ERROR: "); + va_start(ap, p); + vfprintf(stderr, p, ap); + va_end(ap); + fputc('\n', stderr); + exit(1); +} + +const char *const appname = "test_lineedit"; + +char *platform_default_s(const char *name) +{ return NULL; } +bool platform_default_b(const char *name, bool def) +{ return def; } +int platform_default_i(const char *name, int def) +{ return def; } +FontSpec *platform_default_fontspec(const char *name) +{ return fontspec_new_default(); } +Filename *platform_default_filename(const char *name) +{ return filename_from_str(""); } + +const struct BackendVtable *const backends[] = { NULL }; + +typedef struct Mock { + Terminal *term; + Conf *conf; + struct unicode_data ucsdata[1]; + + strbuf *context; + + bool any_test_failed; + + TermWin tw; +} Mock; + +static bool mock_setup_draw_ctx(TermWin *win) { return false; } +static void mock_draw_text(TermWin *win, int x, int y, wchar_t *text, int len, + unsigned long attrs, int lattrs, truecolour tc) {} +static void mock_draw_cursor(TermWin *win, int x, int y, wchar_t *text, + int len, unsigned long attrs, int lattrs, + truecolour tc) {} +static void mock_set_raw_mouse_mode(TermWin *win, bool enable) {} +static void mock_set_raw_mouse_mode_pointer(TermWin *win, bool enable) {} +static void mock_palette_set(TermWin *win, unsigned start, unsigned ncolours, + const rgb *colours) {} +static void mock_palette_get_overrides(TermWin *tw, Terminal *term) {} + +static const TermWinVtable mock_termwin_vt = { + .setup_draw_ctx = mock_setup_draw_ctx, + .draw_text = mock_draw_text, + .draw_cursor = mock_draw_cursor, + .set_raw_mouse_mode = mock_set_raw_mouse_mode, + .set_raw_mouse_mode_pointer = mock_set_raw_mouse_mode_pointer, + .palette_set = mock_palette_set, + .palette_get_overrides = mock_palette_get_overrides, +}; + +static Mock *mock_new(void) +{ + Mock *mk = snew(Mock); + memset(mk, 0, sizeof(*mk)); + + mk->conf = conf_new(); + do_defaults(NULL, mk->conf); + + init_ucs_generic(mk->conf, mk->ucsdata); + mk->ucsdata->line_codepage = CP_ISO8859_1; + + mk->context = strbuf_new(); + + mk->tw.vt = &mock_termwin_vt; + + return mk; +} + +static void mock_free(Mock *mk) +{ + strbuf_free(mk->context); + conf_free(mk->conf); + term_free(mk->term); + sfree(mk); +} + +static void reset(Mock *mk) +{ + term_pwron(mk->term, true); + term_size(mk->term, 24, 80, 0); + term_set_trust_status(mk->term, false); + strbuf_clear(mk->context); +} + +#if 0 + +static void test_context(Mock *mk, const char *fmt, ...) +{ + strbuf_clear(mk->context); + va_list ap; + va_start(ap, fmt); + put_fmtv(mk->context, fmt, ap); + va_end(ap); +} + +#endif + +static void report_fail(Mock *mk, const char *file, int line, + const char *fmt, ...) +{ + printf("%s:%d", file, line); + if (mk->context->len) + printf(" (%s)", mk->context->s); + printf(": "); + va_list ap; + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + printf("\n"); + mk->any_test_failed = true; +} + +static inline void check_iequal(Mock *mk, const char *file, int line, + long long lhs, long long rhs) +{ + if (lhs != rhs) + report_fail(mk, file, line, "%lld != %lld / %#llx != %#llx", + lhs, rhs, lhs, rhs); +} + +#define IEQUAL(lhs, rhs) check_iequal(mk, __FILE__, __LINE__, lhs, rhs) + +static inline void term_datapl(Terminal *term, ptrlen pl) +{ + term_data(term, pl.ptr, pl.len); +} + +static struct termchar get_termchar(Terminal *term, int x, int y) +{ + termline *tl = term_get_line(term, y); + termchar tc; + if (0 <= x && x < tl->cols) + tc = tl->chars[x]; + else + tc = term->erase_char; + term_release_line(tl); + return tc; +} + +static unsigned short get_lineattr(Terminal *term, int y) +{ + termline *tl = term_get_line(term, y); + unsigned short lattr = tl->lattr; + term_release_line(tl); + return lattr; +} + +static void test_hello_world(Mock *mk) +{ + /* A trivial test just to kick off this test framework */ + mk->ucsdata->line_codepage = CP_ISO8859_1; + + reset(mk); + term_datapl(mk->term, PTRLEN_LITERAL("hello, world")); + IEQUAL(mk->term->curs.x, 12); + IEQUAL(mk->term->curs.y, 0); + IEQUAL(get_termchar(mk->term, 0, 0).chr, CSET_ASCII | 'h'); + IEQUAL(get_termchar(mk->term, 1, 0).chr, CSET_ASCII | 'e'); + IEQUAL(get_termchar(mk->term, 2, 0).chr, CSET_ASCII | 'l'); + IEQUAL(get_termchar(mk->term, 3, 0).chr, CSET_ASCII | 'l'); + IEQUAL(get_termchar(mk->term, 4, 0).chr, CSET_ASCII | 'o'); + IEQUAL(get_termchar(mk->term, 5, 0).chr, CSET_ASCII | ','); + IEQUAL(get_termchar(mk->term, 6, 0).chr, CSET_ASCII | ' '); + IEQUAL(get_termchar(mk->term, 7, 0).chr, CSET_ASCII | 'w'); + IEQUAL(get_termchar(mk->term, 8, 0).chr, CSET_ASCII | 'o'); + IEQUAL(get_termchar(mk->term, 9, 0).chr, CSET_ASCII | 'r'); + IEQUAL(get_termchar(mk->term, 10, 0).chr, CSET_ASCII | 'l'); + IEQUAL(get_termchar(mk->term, 11, 0).chr, CSET_ASCII | 'd'); +} + +static void test_wrap(Mock *mk) +{ + /* Test behaviour when printing characters wrap to the next line */ + mk->ucsdata->line_codepage = CP_UTF8; + + /* Print 'abc' without enough space for the c, in wrapping mode */ + reset(mk); + mk->term->curs.x = 78; + mk->term->curs.y = 0; + mk->term->wrap = true; + /* The 'a' prints without anything unusual happening */ + term_datapl(mk->term, PTRLEN_LITERAL("a")); + IEQUAL(mk->term->curs.x, 79); + IEQUAL(mk->term->curs.y, 0); + IEQUAL(mk->term->wrapnext, 0); + IEQUAL(get_termchar(mk->term, 78, 0).chr, CSET_ASCII | 'a'); + /* The 'b' prints, leaving the cursor where it is with wrapnext set */ + term_datapl(mk->term, PTRLEN_LITERAL("b")); + IEQUAL(mk->term->curs.x, 79); + IEQUAL(mk->term->curs.y, 0); + IEQUAL(mk->term->wrapnext, 1); + IEQUAL(get_lineattr(mk->term, 0), 0); + IEQUAL(get_termchar(mk->term, 79, 0).chr, CSET_ASCII | 'b'); + /* And now the 'c' causes a deferred wrap and goes to the next line */ + term_datapl(mk->term, PTRLEN_LITERAL("c")); + IEQUAL(mk->term->curs.x, 1); + IEQUAL(mk->term->curs.y, 1); + IEQUAL(mk->term->wrapnext, 0); + IEQUAL(get_lineattr(mk->term, 0), LATTR_WRAPPED); + IEQUAL(get_termchar(mk->term, 79, 0).chr, CSET_ASCII | 'b'); + IEQUAL(get_termchar(mk->term, 0, 1).chr, CSET_ASCII | 'c'); + /* If we backspace once, the cursor moves back on to the c */ + term_datapl(mk->term, PTRLEN_LITERAL("\b")); + IEQUAL(mk->term->curs.x, 0); + IEQUAL(mk->term->curs.y, 1); + IEQUAL(mk->term->wrapnext, 0); + /* Now backspace again, and the cursor returns to the b */ + term_datapl(mk->term, PTRLEN_LITERAL("\b")); + IEQUAL(mk->term->curs.x, 79); + IEQUAL(mk->term->curs.y, 0); + IEQUAL(mk->term->wrapnext, 0); + + /* Now try it with a double-width character in place of ab */ + mk->term->curs.x = 78; + mk->term->curs.y = 0; + mk->term->wrap = true; + /* The DW character goes directly to the wrapnext state */ + term_datapl(mk->term, PTRLEN_LITERAL("\xEA\xB0\x80")); + IEQUAL(mk->term->curs.x, 79); + IEQUAL(mk->term->curs.y, 0); + IEQUAL(mk->term->wrapnext, 1); + IEQUAL(get_termchar(mk->term, 78, 0).chr, 0xAC00); + IEQUAL(get_termchar(mk->term, 79, 0).chr, UCSWIDE); + /* And the 'c' causes a deferred wrap as before */ + term_datapl(mk->term, PTRLEN_LITERAL("c")); + IEQUAL(mk->term->curs.x, 1); + IEQUAL(mk->term->curs.y, 1); + IEQUAL(mk->term->wrapnext, 0); + IEQUAL(get_lineattr(mk->term, 0), LATTR_WRAPPED); + IEQUAL(get_termchar(mk->term, 78, 0).chr, 0xAC00); + IEQUAL(get_termchar(mk->term, 79, 0).chr, UCSWIDE); + IEQUAL(get_termchar(mk->term, 0, 1).chr, CSET_ASCII | 'c'); + /* If we backspace once, the cursor moves back on to the c */ + term_datapl(mk->term, PTRLEN_LITERAL("\b")); + IEQUAL(mk->term->curs.x, 0); + IEQUAL(mk->term->curs.y, 1); + IEQUAL(mk->term->wrapnext, 0); + /* Now backspace again, and the cursor goes to the RHS of the DW char */ + term_datapl(mk->term, PTRLEN_LITERAL("\b")); + IEQUAL(mk->term->curs.x, 79); + IEQUAL(mk->term->curs.y, 0); + IEQUAL(mk->term->wrapnext, 0); + + /* Now put the DW character in place of bc */ + reset(mk); + mk->term->curs.x = 78; + mk->term->curs.y = 0; + mk->term->wrap = true; + /* The 'a' prints as before */ + term_datapl(mk->term, PTRLEN_LITERAL("a")); + IEQUAL(mk->term->curs.x, 79); + IEQUAL(mk->term->curs.y, 0); + IEQUAL(mk->term->wrapnext, 0); + IEQUAL(get_termchar(mk->term, 78, 0).chr, CSET_ASCII | 'a'); + /* The DW character wraps, setting LATTR_WRAPPED2 */ + term_datapl(mk->term, PTRLEN_LITERAL("\xEA\xB0\x80")); + IEQUAL(mk->term->curs.x, 2); + IEQUAL(mk->term->curs.y, 1); + IEQUAL(mk->term->wrapnext, 0); + IEQUAL(get_lineattr(mk->term, 0), LATTR_WRAPPED | LATTR_WRAPPED2); + IEQUAL(get_termchar(mk->term, 78, 0).chr, CSET_ASCII | 'a'); + IEQUAL(get_termchar(mk->term, 79, 0).chr, CSET_ASCII | ' '); + IEQUAL(get_termchar(mk->term, 0, 1).chr, 0xAC00); + IEQUAL(get_termchar(mk->term, 1, 1).chr, UCSWIDE); + /* If we backspace once, cursor moves to the RHS of the DW char */ + term_datapl(mk->term, PTRLEN_LITERAL("\b")); + IEQUAL(mk->term->curs.x, 1); + IEQUAL(mk->term->curs.y, 1); + IEQUAL(mk->term->wrapnext, 0); + /* Backspace again, and cursor moves from RHS to LHS of that char */ + term_datapl(mk->term, PTRLEN_LITERAL("\b")); + IEQUAL(mk->term->curs.x, 0); + IEQUAL(mk->term->curs.y, 1); + IEQUAL(mk->term->wrapnext, 0); + /* Now backspace again, and the cursor skips the empty column so + * that it can return to the previous logical character, to wit, the a */ + term_datapl(mk->term, PTRLEN_LITERAL("\b")); + IEQUAL(mk->term->curs.x, 78); + IEQUAL(mk->term->curs.y, 0); + IEQUAL(mk->term->wrapnext, 0); + + /* Print 'ab' up to the rightmost column, and then backspace */ + reset(mk); + mk->term->curs.x = 78; + mk->term->curs.y = 0; + mk->term->wrap = true; + /* As before, the 'ab' put us in the rightmost column with wrapnext set */ + term_datapl(mk->term, PTRLEN_LITERAL("ab")); + IEQUAL(mk->term->curs.x, 79); + IEQUAL(mk->term->curs.y, 0); + IEQUAL(mk->term->wrapnext, 1); + IEQUAL(get_lineattr(mk->term, 0), 0); + IEQUAL(get_termchar(mk->term, 78, 0).chr, CSET_ASCII | 'a'); + IEQUAL(get_termchar(mk->term, 79, 0).chr, CSET_ASCII | 'b'); + /* Backspacing just clears the wrapnext flag, so we're logically + * back on the b again */ + term_datapl(mk->term, PTRLEN_LITERAL("\b")); + IEQUAL(mk->term->curs.x, 79); + IEQUAL(mk->term->curs.y, 0); + IEQUAL(mk->term->wrapnext, 0); + + /* For completeness, the easy case: just print 'a' then backspace */ + reset(mk); + mk->term->curs.x = 78; + mk->term->curs.y = 0; + mk->term->wrap = true; + /* 'a' printed in column n-1 takes us to column n */ + term_datapl(mk->term, PTRLEN_LITERAL("a")); + IEQUAL(mk->term->curs.x, 79); + IEQUAL(mk->term->curs.y, 0); + IEQUAL(mk->term->wrapnext, 0); + IEQUAL(get_lineattr(mk->term, 0), 0); + IEQUAL(get_termchar(mk->term, 78, 0).chr, CSET_ASCII | 'a'); + /* Backspacing moves us back a space on to the a */ + term_datapl(mk->term, PTRLEN_LITERAL("\b")); + IEQUAL(mk->term->curs.x, 78); + IEQUAL(mk->term->curs.y, 0); + IEQUAL(mk->term->wrapnext, 0); + + /* + * Now test the special cases that arise when the terminal is only + * one column wide! + */ + + reset(mk); + term_size(mk->term, 24, 1, 0); + mk->term->curs.x = 0; + mk->term->curs.y = 0; + mk->term->wrap = true; + /* Printing a single-width character takes us into wrapnext immediately */ + term_datapl(mk->term, PTRLEN_LITERAL("a")); + IEQUAL(mk->term->curs.x, 0); + IEQUAL(mk->term->curs.y, 0); + IEQUAL(mk->term->wrapnext, 1); + IEQUAL(get_lineattr(mk->term, 0), 0); + IEQUAL(get_termchar(mk->term, 0, 0).chr, CSET_ASCII | 'a'); + /* Printing a second one wraps, and takes us _back_ to wrapnext */ + term_datapl(mk->term, PTRLEN_LITERAL("b")); + IEQUAL(mk->term->curs.x, 0); + IEQUAL(mk->term->curs.y, 1); + IEQUAL(mk->term->wrapnext, 1); + IEQUAL(get_lineattr(mk->term, 0), LATTR_WRAPPED); + IEQUAL(get_termchar(mk->term, 0, 0).chr, CSET_ASCII | 'a'); + IEQUAL(get_termchar(mk->term, 0, 1).chr, CSET_ASCII | 'b'); + /* Backspacing once clears the wrapnext flag, putting us on the b */ + term_datapl(mk->term, PTRLEN_LITERAL("\b")); + IEQUAL(mk->term->curs.x, 0); + IEQUAL(mk->term->curs.y, 1); + IEQUAL(mk->term->wrapnext, 0); + /* Backspacing again returns to the previous line, putting us on the a */ + term_datapl(mk->term, PTRLEN_LITERAL("\b")); + IEQUAL(mk->term->curs.x, 0); + IEQUAL(mk->term->curs.y, 0); + IEQUAL(mk->term->wrapnext, 0); + + /* And now try with a double-width character */ + reset(mk); + term_size(mk->term, 24, 1, 0); + mk->term->curs.x = 0; + mk->term->curs.y = 0; + mk->term->wrap = true; + /* DW character won't fit at all, so it transforms into U+FFFD + * REPLACEMENT CHARACTER and then behaves like a SW char */ + term_datapl(mk->term, PTRLEN_LITERAL("\xEA\xB0\x80")); + IEQUAL(mk->term->curs.x, 0); + IEQUAL(mk->term->curs.y, 0); + IEQUAL(mk->term->wrapnext, 1); + IEQUAL(get_lineattr(mk->term, 0), 0); + IEQUAL(get_termchar(mk->term, 0, 0).chr, 0xFFFD); +} + +static void test_nonwrap(Mock *mk) +{ + /* Test behaviour when printing characters hit end of line without wrap. + * The wrapnext flag is never set in this mode. */ + mk->ucsdata->line_codepage = CP_UTF8; + + /* Print 'abc' without enough space for the c */ + reset(mk); + mk->term->curs.x = 78; + mk->term->curs.y = 0; + mk->term->wrap = false; + /* The 'a' prints without anything unusual happening */ + term_datapl(mk->term, PTRLEN_LITERAL("a")); + IEQUAL(mk->term->curs.x, 79); + IEQUAL(mk->term->curs.y, 0); + IEQUAL(mk->term->wrapnext, 0); + IEQUAL(get_termchar(mk->term, 78, 0).chr, CSET_ASCII | 'a'); + /* The 'b' prints, leaving the cursor where it is */ + term_datapl(mk->term, PTRLEN_LITERAL("b")); + IEQUAL(mk->term->curs.x, 79); + IEQUAL(mk->term->curs.y, 0); + IEQUAL(mk->term->wrapnext, 0); + IEQUAL(get_lineattr(mk->term, 0), 0); + IEQUAL(get_termchar(mk->term, 79, 0).chr, CSET_ASCII | 'b'); + /* The 'c' overwrites the b */ + term_datapl(mk->term, PTRLEN_LITERAL("c")); + IEQUAL(mk->term->curs.x, 79); + IEQUAL(mk->term->curs.y, 0); + IEQUAL(mk->term->wrapnext, 0); + IEQUAL(get_lineattr(mk->term, 0), 0); + IEQUAL(get_termchar(mk->term, 78, 0).chr, CSET_ASCII | 'a'); + IEQUAL(get_termchar(mk->term, 79, 0).chr, CSET_ASCII | 'c'); + /* Since wrapnext was never set, backspacing returns us to the a */ + term_datapl(mk->term, PTRLEN_LITERAL("\b")); + IEQUAL(mk->term->curs.x, 78); + IEQUAL(mk->term->curs.y, 0); + IEQUAL(mk->term->wrapnext, 0); + + /* Now try it with a double-width character in place of ab */ + mk->term->curs.x = 78; + mk->term->curs.y = 0; + mk->term->wrap = false; + /* The DW character occupies the rightmost two columns */ + term_datapl(mk->term, PTRLEN_LITERAL("\xEA\xB0\x80")); + IEQUAL(mk->term->curs.x, 79); + IEQUAL(mk->term->curs.y, 0); + IEQUAL(mk->term->wrapnext, 0); + IEQUAL(get_termchar(mk->term, 78, 0).chr, 0xAC00); + IEQUAL(get_termchar(mk->term, 79, 0).chr, UCSWIDE); + /* The 'c' must overprint the RHS of the DW char, clearing the LHS */ + term_datapl(mk->term, PTRLEN_LITERAL("c")); + IEQUAL(mk->term->curs.x, 79); + IEQUAL(mk->term->curs.y, 0); + IEQUAL(mk->term->wrapnext, 0); + IEQUAL(get_lineattr(mk->term, 0), 0); + IEQUAL(get_termchar(mk->term, 78, 0).chr, CSET_ASCII | ' '); + IEQUAL(get_termchar(mk->term, 79, 0).chr, CSET_ASCII | 'c'); + + /* Now put the DW char in place of the bc */ + reset(mk); + mk->term->curs.x = 78; + mk->term->curs.y = 0; + mk->term->wrap = false; + /* The 'a' prints as before */ + term_datapl(mk->term, PTRLEN_LITERAL("a")); + IEQUAL(mk->term->curs.x, 79); + IEQUAL(mk->term->curs.y, 0); + IEQUAL(mk->term->wrapnext, 0); + IEQUAL(get_termchar(mk->term, 78, 0).chr, CSET_ASCII | 'a'); + /* The DW char won't fit, so turns into U+FFFD REPLACEMENT CHARACTER */ + term_datapl(mk->term, PTRLEN_LITERAL("\xEA\xB0\x80")); + IEQUAL(mk->term->curs.x, 79); + IEQUAL(mk->term->curs.y, 0); + IEQUAL(mk->term->wrapnext, 0); + IEQUAL(get_lineattr(mk->term, 0), 0); + IEQUAL(get_termchar(mk->term, 78, 0).chr, CSET_ASCII | 'a'); + IEQUAL(get_termchar(mk->term, 79, 0).chr, 0xFFFD); + + /* Just for completeness, try both of those together */ + reset(mk); + mk->term->curs.x = 78; + mk->term->curs.y = 0; + mk->term->wrap = false; + /* First DW character occupies the rightmost columns */ + term_datapl(mk->term, PTRLEN_LITERAL("\xEA\xB0\x80")); + IEQUAL(mk->term->curs.x, 79); + IEQUAL(mk->term->curs.y, 0); + IEQUAL(mk->term->wrapnext, 0); + IEQUAL(get_termchar(mk->term, 78, 0).chr, 0xAC00); + IEQUAL(get_termchar(mk->term, 79, 0).chr, UCSWIDE); + /* Second DW char becomes U+FFFD, overwriting RHS of the first one */ + term_datapl(mk->term, PTRLEN_LITERAL("\xEA\xB0\x81")); + IEQUAL(mk->term->curs.x, 79); + IEQUAL(mk->term->curs.y, 0); + IEQUAL(mk->term->wrapnext, 0); + IEQUAL(get_lineattr(mk->term, 0), 0); + IEQUAL(get_termchar(mk->term, 78, 0).chr, CSET_ASCII | ' '); + IEQUAL(get_termchar(mk->term, 79, 0).chr, 0xFFFD); +} + +int main(void) +{ + Mock *mk = mock_new(); + mk->term = term_init(mk->conf, mk->ucsdata, &mk->tw); + + test_hello_world(mk); + test_wrap(mk); + test_nonwrap(mk); + + bool failed = mk->any_test_failed; + mock_free(mk); + + if (failed) { + printf("Test suite FAILED!\n"); + return 1; + } else { + printf("Test suite passed\n"); + return 0; + } +} diff --git a/test/testcrypt-func.h b/test/testcrypt-func.h index cff2b86e..05121458 100644 --- a/test/testcrypt-func.h +++ b/test/testcrypt-func.h @@ -269,7 +269,7 @@ FUNC(opt_val_hash, blake2b_new_general, ARG(uint, hashlen)) * to ssh2_mac_new. Also, again, I've invented an ssh2_mac_update so * you can put data into the MAC. */ -FUNC(val_mac, ssh2_mac_new, ARG(macalg, alg), ARG(opt_val_cipher, cipher)) +FUNC(opt_val_mac, ssh2_mac_new, ARG(macalg, alg), ARG(opt_val_cipher, cipher)) FUNC(void, ssh2_mac_setkey, ARG(val_mac, m), ARG(val_string_ptrlen, key)) FUNC(void, ssh2_mac_start, ARG(val_mac, m)) FUNC(void, ssh2_mac_update, ARG(val_mac, m), ARG(val_string_ptrlen, data)) diff --git a/test/testcrypt.c b/test/testcrypt.c index 3755ae72..3b495bba 100644 --- a/test/testcrypt.c +++ b/test/testcrypt.c @@ -584,6 +584,7 @@ NULLABLE_RETURN_WRAPPER(val_string, strbuf *) NULLABLE_RETURN_WRAPPER(val_string_asciz, char *) NULLABLE_RETURN_WRAPPER(val_string_asciz_const, const char *) NULLABLE_RETURN_WRAPPER(val_cipher, ssh_cipher *) +NULLABLE_RETURN_WRAPPER(val_mac, ssh2_mac *) NULLABLE_RETURN_WRAPPER(val_hash, ssh_hash *) NULLABLE_RETURN_WRAPPER(val_key, ssh_key *) NULLABLE_RETURN_WRAPPER(val_mpint, mp_int *) @@ -1613,13 +1614,8 @@ static void process_line(BinarySource *in, strbuf *out) #define FUNC_INNER(outtype, fname, realname, ...) \ DISPATCH_INTERNAL(#fname,handle_##fname); -#define ARG1(type, arg) -#define ARGN(type, arg) -#define VOID #include "testcrypt-func.h" #undef FUNC_INNER -#undef ARG -#undef VOID #undef DISPATCH_INTERNAL diff --git a/test/testcrypt.py b/test/testcrypt.py index 66f63d5c..217dbf35 100644 --- a/test/testcrypt.py +++ b/test/testcrypt.py @@ -308,8 +308,8 @@ def _lex_testcrypt_header(header): # And then match a token '({})'.format('|'.join(( # Punctuation - '\(', - '\)', + r'\(', + r'\)', ',', # Identifier '[A-Za-z_][A-Za-z0-9_]*', diff --git a/test/utf8.txt b/test/utf8.txt index d7e1d773..c76fc678 100644 --- a/test/utf8.txt +++ b/test/utf8.txt @@ -21,3 +21,10 @@ Arabic and bidirectional text: Mixed LTR and RTL text: جرير رضي back to LTR. East Asian Ambiguous characters: ¼½¾¼½¾¼½¾¼½¾¼½¾¼½¾¼½¾¼½¾¼½¾¼½¾ + +Emoji via U+FE0F: ❤️ ☺️ ☹️ (narrow, because wcwidth mishandles these) +Dedicated emoji: 💜 🙂 🙁 (wide and should look correct) +Combined via ZWJ: 👩‍💻 (PuTTY doesn't understand ZWJ) +Skin tone mod: 👩🏻 👩🏿 (wcwidth doesn't know those are modifiers) +Flags: 🇬🇧 🇺🇦 🇪🇺 (should work in GTK 2 or better) +Flags using tags: 🏴󠁧󠁢󠁥󠁮󠁧󠁿 🏴󠁧󠁢󠁳󠁣󠁴󠁿 🏴󠁧󠁢󠁷󠁬󠁳󠁿 (the tags are treated as combining marks) diff --git a/unicode/ambiguous_wide_chars.h b/unicode/ambiguous_wide_chars.h new file mode 100644 index 00000000..7369e7a5 --- /dev/null +++ b/unicode/ambiguous_wide_chars.h @@ -0,0 +1,189 @@ +/* + * Autogenerated by read_ucd.py from The Unicode Standard 16.0.0 + * + * Identify Unicode characters that are width-ambiguous: some regimes + * regard them as occupying two adjacent character cells in a terminal, + * and others do not. + * + * Used by utils/wcwidth.c. + */ + +{0x00a1, 0x00a1}, +{0x00a4, 0x00a4}, +{0x00a7, 0x00a8}, +{0x00aa, 0x00aa}, +{0x00ad, 0x00ae}, +{0x00b0, 0x00b4}, +{0x00b6, 0x00ba}, +{0x00bc, 0x00bf}, +{0x00c6, 0x00c6}, +{0x00d0, 0x00d0}, +{0x00d7, 0x00d8}, +{0x00de, 0x00e1}, +{0x00e6, 0x00e6}, +{0x00e8, 0x00ea}, +{0x00ec, 0x00ed}, +{0x00f0, 0x00f0}, +{0x00f2, 0x00f3}, +{0x00f7, 0x00fa}, +{0x00fc, 0x00fc}, +{0x00fe, 0x00fe}, +{0x0101, 0x0101}, +{0x0111, 0x0111}, +{0x0113, 0x0113}, +{0x011b, 0x011b}, +{0x0126, 0x0127}, +{0x012b, 0x012b}, +{0x0131, 0x0133}, +{0x0138, 0x0138}, +{0x013f, 0x0142}, +{0x0144, 0x0144}, +{0x0148, 0x014b}, +{0x014d, 0x014d}, +{0x0152, 0x0153}, +{0x0166, 0x0167}, +{0x016b, 0x016b}, +{0x01ce, 0x01ce}, +{0x01d0, 0x01d0}, +{0x01d2, 0x01d2}, +{0x01d4, 0x01d4}, +{0x01d6, 0x01d6}, +{0x01d8, 0x01d8}, +{0x01da, 0x01da}, +{0x01dc, 0x01dc}, +{0x0251, 0x0251}, +{0x0261, 0x0261}, +{0x02c4, 0x02c4}, +{0x02c7, 0x02c7}, +{0x02c9, 0x02cb}, +{0x02cd, 0x02cd}, +{0x02d0, 0x02d0}, +{0x02d8, 0x02db}, +{0x02dd, 0x02dd}, +{0x02df, 0x02df}, +{0x0300, 0x036f}, +{0x0391, 0x03a1}, +{0x03a3, 0x03a9}, +{0x03b1, 0x03c1}, +{0x03c3, 0x03c9}, +{0x0401, 0x0401}, +{0x0410, 0x044f}, +{0x0451, 0x0451}, +{0x2010, 0x2010}, +{0x2013, 0x2016}, +{0x2018, 0x2019}, +{0x201c, 0x201d}, +{0x2020, 0x2022}, +{0x2024, 0x2027}, +{0x2030, 0x2030}, +{0x2032, 0x2033}, +{0x2035, 0x2035}, +{0x203b, 0x203b}, +{0x203e, 0x203e}, +{0x2074, 0x2074}, +{0x207f, 0x207f}, +{0x2081, 0x2084}, +{0x20ac, 0x20ac}, +{0x2103, 0x2103}, +{0x2105, 0x2105}, +{0x2109, 0x2109}, +{0x2113, 0x2113}, +{0x2116, 0x2116}, +{0x2121, 0x2122}, +{0x2126, 0x2126}, +{0x212b, 0x212b}, +{0x2153, 0x2154}, +{0x215b, 0x215e}, +{0x2160, 0x216b}, +{0x2170, 0x2179}, +{0x2189, 0x2189}, +{0x2190, 0x2199}, +{0x21b8, 0x21b9}, +{0x21d2, 0x21d2}, +{0x21d4, 0x21d4}, +{0x21e7, 0x21e7}, +{0x2200, 0x2200}, +{0x2202, 0x2203}, +{0x2207, 0x2208}, +{0x220b, 0x220b}, +{0x220f, 0x220f}, +{0x2211, 0x2211}, +{0x2215, 0x2215}, +{0x221a, 0x221a}, +{0x221d, 0x2220}, +{0x2223, 0x2223}, +{0x2225, 0x2225}, +{0x2227, 0x222c}, +{0x222e, 0x222e}, +{0x2234, 0x2237}, +{0x223c, 0x223d}, +{0x2248, 0x2248}, +{0x224c, 0x224c}, +{0x2252, 0x2252}, +{0x2260, 0x2261}, +{0x2264, 0x2267}, +{0x226a, 0x226b}, +{0x226e, 0x226f}, +{0x2282, 0x2283}, +{0x2286, 0x2287}, +{0x2295, 0x2295}, +{0x2299, 0x2299}, +{0x22a5, 0x22a5}, +{0x22bf, 0x22bf}, +{0x2312, 0x2312}, +{0x2460, 0x24e9}, +{0x24eb, 0x254b}, +{0x2550, 0x2573}, +{0x2580, 0x258f}, +{0x2592, 0x2595}, +{0x25a0, 0x25a1}, +{0x25a3, 0x25a9}, +{0x25b2, 0x25b3}, +{0x25b6, 0x25b7}, +{0x25bc, 0x25bd}, +{0x25c0, 0x25c1}, +{0x25c6, 0x25c8}, +{0x25cb, 0x25cb}, +{0x25ce, 0x25d1}, +{0x25e2, 0x25e5}, +{0x25ef, 0x25ef}, +{0x2605, 0x2606}, +{0x2609, 0x2609}, +{0x260e, 0x260f}, +{0x261c, 0x261c}, +{0x261e, 0x261e}, +{0x2640, 0x2640}, +{0x2642, 0x2642}, +{0x2660, 0x2661}, +{0x2663, 0x2665}, +{0x2667, 0x266a}, +{0x266c, 0x266d}, +{0x266f, 0x266f}, +{0x269e, 0x269f}, +{0x26bf, 0x26bf}, +{0x26c6, 0x26cd}, +{0x26cf, 0x26d3}, +{0x26d5, 0x26e1}, +{0x26e3, 0x26e3}, +{0x26e8, 0x26e9}, +{0x26eb, 0x26f1}, +{0x26f4, 0x26f4}, +{0x26f6, 0x26f9}, +{0x26fb, 0x26fc}, +{0x26fe, 0x26ff}, +{0x273d, 0x273d}, +{0x2776, 0x277f}, +{0x2b56, 0x2b59}, +{0x3248, 0x324f}, +{0xe000, 0xf8ff}, +{0xfe00, 0xfe0f}, +{0xfffd, 0xfffd}, +{0x1f100, 0x1f10a}, +{0x1f110, 0x1f12d}, +{0x1f130, 0x1f169}, +{0x1f170, 0x1f18d}, +{0x1f18f, 0x1f190}, +{0x1f19b, 0x1f1ac}, +{0xe0100, 0xe01ef}, +{0xf0000, 0xffffd}, +{0x100000, 0x10fffd}, diff --git a/unicode/bidi_brackets.h b/unicode/bidi_brackets.h new file mode 100644 index 00000000..6cfd4d25 --- /dev/null +++ b/unicode/bidi_brackets.h @@ -0,0 +1,139 @@ +/* + * Autogenerated by read_ucd.py from The Unicode Standard 16.0.0 + * + * Identify Unicode characters that count as brackets for the purposes of + * bidirectional text layout. For each one, indicate whether it's an open + * or closed bracket, and identify up to two characters that can act as + * its counterpart. + * + * Used by terminal/bidi.c. + */ + +{0x0028, {0x0029, 0x0000, BT_OPEN}}, +{0x0029, {0x0028, 0x0000, BT_CLOSE}}, +{0x005b, {0x005d, 0x0000, BT_OPEN}}, +{0x005d, {0x005b, 0x0000, BT_CLOSE}}, +{0x007b, {0x007d, 0x0000, BT_OPEN}}, +{0x007d, {0x007b, 0x0000, BT_CLOSE}}, +{0x0f3a, {0x0f3b, 0x0000, BT_OPEN}}, +{0x0f3b, {0x0f3a, 0x0000, BT_CLOSE}}, +{0x0f3c, {0x0f3d, 0x0000, BT_OPEN}}, +{0x0f3d, {0x0f3c, 0x0000, BT_CLOSE}}, +{0x169b, {0x169c, 0x0000, BT_OPEN}}, +{0x169c, {0x169b, 0x0000, BT_CLOSE}}, +{0x2045, {0x2046, 0x0000, BT_OPEN}}, +{0x2046, {0x2045, 0x0000, BT_CLOSE}}, +{0x207d, {0x207e, 0x0000, BT_OPEN}}, +{0x207e, {0x207d, 0x0000, BT_CLOSE}}, +{0x208d, {0x208e, 0x0000, BT_OPEN}}, +{0x208e, {0x208d, 0x0000, BT_CLOSE}}, +{0x2308, {0x2309, 0x0000, BT_OPEN}}, +{0x2309, {0x2308, 0x0000, BT_CLOSE}}, +{0x230a, {0x230b, 0x0000, BT_OPEN}}, +{0x230b, {0x230a, 0x0000, BT_CLOSE}}, +{0x2329, {0x232a, 0x3009, BT_OPEN}}, +{0x232a, {0x2329, 0x3008, BT_CLOSE}}, +{0x2768, {0x2769, 0x0000, BT_OPEN}}, +{0x2769, {0x2768, 0x0000, BT_CLOSE}}, +{0x276a, {0x276b, 0x0000, BT_OPEN}}, +{0x276b, {0x276a, 0x0000, BT_CLOSE}}, +{0x276c, {0x276d, 0x0000, BT_OPEN}}, +{0x276d, {0x276c, 0x0000, BT_CLOSE}}, +{0x276e, {0x276f, 0x0000, BT_OPEN}}, +{0x276f, {0x276e, 0x0000, BT_CLOSE}}, +{0x2770, {0x2771, 0x0000, BT_OPEN}}, +{0x2771, {0x2770, 0x0000, BT_CLOSE}}, +{0x2772, {0x2773, 0x0000, BT_OPEN}}, +{0x2773, {0x2772, 0x0000, BT_CLOSE}}, +{0x2774, {0x2775, 0x0000, BT_OPEN}}, +{0x2775, {0x2774, 0x0000, BT_CLOSE}}, +{0x27c5, {0x27c6, 0x0000, BT_OPEN}}, +{0x27c6, {0x27c5, 0x0000, BT_CLOSE}}, +{0x27e6, {0x27e7, 0x0000, BT_OPEN}}, +{0x27e7, {0x27e6, 0x0000, BT_CLOSE}}, +{0x27e8, {0x27e9, 0x0000, BT_OPEN}}, +{0x27e9, {0x27e8, 0x0000, BT_CLOSE}}, +{0x27ea, {0x27eb, 0x0000, BT_OPEN}}, +{0x27eb, {0x27ea, 0x0000, BT_CLOSE}}, +{0x27ec, {0x27ed, 0x0000, BT_OPEN}}, +{0x27ed, {0x27ec, 0x0000, BT_CLOSE}}, +{0x27ee, {0x27ef, 0x0000, BT_OPEN}}, +{0x27ef, {0x27ee, 0x0000, BT_CLOSE}}, +{0x2983, {0x2984, 0x0000, BT_OPEN}}, +{0x2984, {0x2983, 0x0000, BT_CLOSE}}, +{0x2985, {0x2986, 0x0000, BT_OPEN}}, +{0x2986, {0x2985, 0x0000, BT_CLOSE}}, +{0x2987, {0x2988, 0x0000, BT_OPEN}}, +{0x2988, {0x2987, 0x0000, BT_CLOSE}}, +{0x2989, {0x298a, 0x0000, BT_OPEN}}, +{0x298a, {0x2989, 0x0000, BT_CLOSE}}, +{0x298b, {0x298c, 0x0000, BT_OPEN}}, +{0x298c, {0x298b, 0x0000, BT_CLOSE}}, +{0x298d, {0x2990, 0x0000, BT_OPEN}}, +{0x298e, {0x298f, 0x0000, BT_CLOSE}}, +{0x298f, {0x298e, 0x0000, BT_OPEN}}, +{0x2990, {0x298d, 0x0000, BT_CLOSE}}, +{0x2991, {0x2992, 0x0000, BT_OPEN}}, +{0x2992, {0x2991, 0x0000, BT_CLOSE}}, +{0x2993, {0x2994, 0x0000, BT_OPEN}}, +{0x2994, {0x2993, 0x0000, BT_CLOSE}}, +{0x2995, {0x2996, 0x0000, BT_OPEN}}, +{0x2996, {0x2995, 0x0000, BT_CLOSE}}, +{0x2997, {0x2998, 0x0000, BT_OPEN}}, +{0x2998, {0x2997, 0x0000, BT_CLOSE}}, +{0x29d8, {0x29d9, 0x0000, BT_OPEN}}, +{0x29d9, {0x29d8, 0x0000, BT_CLOSE}}, +{0x29da, {0x29db, 0x0000, BT_OPEN}}, +{0x29db, {0x29da, 0x0000, BT_CLOSE}}, +{0x29fc, {0x29fd, 0x0000, BT_OPEN}}, +{0x29fd, {0x29fc, 0x0000, BT_CLOSE}}, +{0x2e22, {0x2e23, 0x0000, BT_OPEN}}, +{0x2e23, {0x2e22, 0x0000, BT_CLOSE}}, +{0x2e24, {0x2e25, 0x0000, BT_OPEN}}, +{0x2e25, {0x2e24, 0x0000, BT_CLOSE}}, +{0x2e26, {0x2e27, 0x0000, BT_OPEN}}, +{0x2e27, {0x2e26, 0x0000, BT_CLOSE}}, +{0x2e28, {0x2e29, 0x0000, BT_OPEN}}, +{0x2e29, {0x2e28, 0x0000, BT_CLOSE}}, +{0x2e55, {0x2e56, 0x0000, BT_OPEN}}, +{0x2e56, {0x2e55, 0x0000, BT_CLOSE}}, +{0x2e57, {0x2e58, 0x0000, BT_OPEN}}, +{0x2e58, {0x2e57, 0x0000, BT_CLOSE}}, +{0x2e59, {0x2e5a, 0x0000, BT_OPEN}}, +{0x2e5a, {0x2e59, 0x0000, BT_CLOSE}}, +{0x2e5b, {0x2e5c, 0x0000, BT_OPEN}}, +{0x2e5c, {0x2e5b, 0x0000, BT_CLOSE}}, +{0x3008, {0x3009, 0x232a, BT_OPEN}}, +{0x3009, {0x3008, 0x2329, BT_CLOSE}}, +{0x300a, {0x300b, 0x0000, BT_OPEN}}, +{0x300b, {0x300a, 0x0000, BT_CLOSE}}, +{0x300c, {0x300d, 0x0000, BT_OPEN}}, +{0x300d, {0x300c, 0x0000, BT_CLOSE}}, +{0x300e, {0x300f, 0x0000, BT_OPEN}}, +{0x300f, {0x300e, 0x0000, BT_CLOSE}}, +{0x3010, {0x3011, 0x0000, BT_OPEN}}, +{0x3011, {0x3010, 0x0000, BT_CLOSE}}, +{0x3014, {0x3015, 0x0000, BT_OPEN}}, +{0x3015, {0x3014, 0x0000, BT_CLOSE}}, +{0x3016, {0x3017, 0x0000, BT_OPEN}}, +{0x3017, {0x3016, 0x0000, BT_CLOSE}}, +{0x3018, {0x3019, 0x0000, BT_OPEN}}, +{0x3019, {0x3018, 0x0000, BT_CLOSE}}, +{0x301a, {0x301b, 0x0000, BT_OPEN}}, +{0x301b, {0x301a, 0x0000, BT_CLOSE}}, +{0xfe59, {0xfe5a, 0x0000, BT_OPEN}}, +{0xfe5a, {0xfe59, 0x0000, BT_CLOSE}}, +{0xfe5b, {0xfe5c, 0x0000, BT_OPEN}}, +{0xfe5c, {0xfe5b, 0x0000, BT_CLOSE}}, +{0xfe5d, {0xfe5e, 0x0000, BT_OPEN}}, +{0xfe5e, {0xfe5d, 0x0000, BT_CLOSE}}, +{0xff08, {0xff09, 0x0000, BT_OPEN}}, +{0xff09, {0xff08, 0x0000, BT_CLOSE}}, +{0xff3b, {0xff3d, 0x0000, BT_OPEN}}, +{0xff3d, {0xff3b, 0x0000, BT_CLOSE}}, +{0xff5b, {0xff5d, 0x0000, BT_OPEN}}, +{0xff5d, {0xff5b, 0x0000, BT_CLOSE}}, +{0xff5f, {0xff60, 0x0000, BT_OPEN}}, +{0xff60, {0xff5f, 0x0000, BT_CLOSE}}, +{0xff62, {0xff63, 0x0000, BT_OPEN}}, +{0xff63, {0xff62, 0x0000, BT_CLOSE}}, diff --git a/unicode/bidi_mirror.h b/unicode/bidi_mirror.h new file mode 100644 index 00000000..39ea5c91 --- /dev/null +++ b/unicode/bidi_mirror.h @@ -0,0 +1,437 @@ +/* + * Autogenerated by read_ucd.py from The Unicode Standard 16.0.0 + * + * Map each Unicode character to its mirrored form when printing right to + * left. + * + * Used by terminal/bidi.c. + */ + +{0x0028, 0x0029}, +{0x0029, 0x0028}, +{0x003c, 0x003e}, +{0x003e, 0x003c}, +{0x005b, 0x005d}, +{0x005d, 0x005b}, +{0x007b, 0x007d}, +{0x007d, 0x007b}, +{0x00ab, 0x00bb}, +{0x00bb, 0x00ab}, +{0x0f3a, 0x0f3b}, +{0x0f3b, 0x0f3a}, +{0x0f3c, 0x0f3d}, +{0x0f3d, 0x0f3c}, +{0x169b, 0x169c}, +{0x169c, 0x169b}, +{0x2039, 0x203a}, +{0x203a, 0x2039}, +{0x2045, 0x2046}, +{0x2046, 0x2045}, +{0x207d, 0x207e}, +{0x207e, 0x207d}, +{0x208d, 0x208e}, +{0x208e, 0x208d}, +{0x2208, 0x220b}, +{0x2209, 0x220c}, +{0x220a, 0x220d}, +{0x220b, 0x2208}, +{0x220c, 0x2209}, +{0x220d, 0x220a}, +{0x2215, 0x29f5}, +{0x221f, 0x2bfe}, +{0x2220, 0x29a3}, +{0x2221, 0x299b}, +{0x2222, 0x29a0}, +{0x2224, 0x2aee}, +{0x223c, 0x223d}, +{0x223d, 0x223c}, +{0x2243, 0x22cd}, +{0x2245, 0x224c}, +{0x224c, 0x2245}, +{0x2252, 0x2253}, +{0x2253, 0x2252}, +{0x2254, 0x2255}, +{0x2255, 0x2254}, +{0x2264, 0x2265}, +{0x2265, 0x2264}, +{0x2266, 0x2267}, +{0x2267, 0x2266}, +{0x2268, 0x2269}, +{0x2269, 0x2268}, +{0x226a, 0x226b}, +{0x226b, 0x226a}, +{0x226e, 0x226f}, +{0x226f, 0x226e}, +{0x2270, 0x2271}, +{0x2271, 0x2270}, +{0x2272, 0x2273}, +{0x2273, 0x2272}, +{0x2274, 0x2275}, +{0x2275, 0x2274}, +{0x2276, 0x2277}, +{0x2277, 0x2276}, +{0x2278, 0x2279}, +{0x2279, 0x2278}, +{0x227a, 0x227b}, +{0x227b, 0x227a}, +{0x227c, 0x227d}, +{0x227d, 0x227c}, +{0x227e, 0x227f}, +{0x227f, 0x227e}, +{0x2280, 0x2281}, +{0x2281, 0x2280}, +{0x2282, 0x2283}, +{0x2283, 0x2282}, +{0x2284, 0x2285}, +{0x2285, 0x2284}, +{0x2286, 0x2287}, +{0x2287, 0x2286}, +{0x2288, 0x2289}, +{0x2289, 0x2288}, +{0x228a, 0x228b}, +{0x228b, 0x228a}, +{0x228f, 0x2290}, +{0x2290, 0x228f}, +{0x2291, 0x2292}, +{0x2292, 0x2291}, +{0x2298, 0x29b8}, +{0x22a2, 0x22a3}, +{0x22a3, 0x22a2}, +{0x22a6, 0x2ade}, +{0x22a8, 0x2ae4}, +{0x22a9, 0x2ae3}, +{0x22ab, 0x2ae5}, +{0x22b0, 0x22b1}, +{0x22b1, 0x22b0}, +{0x22b2, 0x22b3}, +{0x22b3, 0x22b2}, +{0x22b4, 0x22b5}, +{0x22b5, 0x22b4}, +{0x22b6, 0x22b7}, +{0x22b7, 0x22b6}, +{0x22b8, 0x27dc}, +{0x22c9, 0x22ca}, +{0x22ca, 0x22c9}, +{0x22cb, 0x22cc}, +{0x22cc, 0x22cb}, +{0x22cd, 0x2243}, +{0x22d0, 0x22d1}, +{0x22d1, 0x22d0}, +{0x22d6, 0x22d7}, +{0x22d7, 0x22d6}, +{0x22d8, 0x22d9}, +{0x22d9, 0x22d8}, +{0x22da, 0x22db}, +{0x22db, 0x22da}, +{0x22dc, 0x22dd}, +{0x22dd, 0x22dc}, +{0x22de, 0x22df}, +{0x22df, 0x22de}, +{0x22e0, 0x22e1}, +{0x22e1, 0x22e0}, +{0x22e2, 0x22e3}, +{0x22e3, 0x22e2}, +{0x22e4, 0x22e5}, +{0x22e5, 0x22e4}, +{0x22e6, 0x22e7}, +{0x22e7, 0x22e6}, +{0x22e8, 0x22e9}, +{0x22e9, 0x22e8}, +{0x22ea, 0x22eb}, +{0x22eb, 0x22ea}, +{0x22ec, 0x22ed}, +{0x22ed, 0x22ec}, +{0x22f0, 0x22f1}, +{0x22f1, 0x22f0}, +{0x22f2, 0x22fa}, +{0x22f3, 0x22fb}, +{0x22f4, 0x22fc}, +{0x22f6, 0x22fd}, +{0x22f7, 0x22fe}, +{0x22fa, 0x22f2}, +{0x22fb, 0x22f3}, +{0x22fc, 0x22f4}, +{0x22fd, 0x22f6}, +{0x22fe, 0x22f7}, +{0x2308, 0x2309}, +{0x2309, 0x2308}, +{0x230a, 0x230b}, +{0x230b, 0x230a}, +{0x2329, 0x232a}, +{0x232a, 0x2329}, +{0x2768, 0x2769}, +{0x2769, 0x2768}, +{0x276a, 0x276b}, +{0x276b, 0x276a}, +{0x276c, 0x276d}, +{0x276d, 0x276c}, +{0x276e, 0x276f}, +{0x276f, 0x276e}, +{0x2770, 0x2771}, +{0x2771, 0x2770}, +{0x2772, 0x2773}, +{0x2773, 0x2772}, +{0x2774, 0x2775}, +{0x2775, 0x2774}, +{0x27c3, 0x27c4}, +{0x27c4, 0x27c3}, +{0x27c5, 0x27c6}, +{0x27c6, 0x27c5}, +{0x27c8, 0x27c9}, +{0x27c9, 0x27c8}, +{0x27cb, 0x27cd}, +{0x27cd, 0x27cb}, +{0x27d5, 0x27d6}, +{0x27d6, 0x27d5}, +{0x27dc, 0x22b8}, +{0x27dd, 0x27de}, +{0x27de, 0x27dd}, +{0x27e2, 0x27e3}, +{0x27e3, 0x27e2}, +{0x27e4, 0x27e5}, +{0x27e5, 0x27e4}, +{0x27e6, 0x27e7}, +{0x27e7, 0x27e6}, +{0x27e8, 0x27e9}, +{0x27e9, 0x27e8}, +{0x27ea, 0x27eb}, +{0x27eb, 0x27ea}, +{0x27ec, 0x27ed}, +{0x27ed, 0x27ec}, +{0x27ee, 0x27ef}, +{0x27ef, 0x27ee}, +{0x2983, 0x2984}, +{0x2984, 0x2983}, +{0x2985, 0x2986}, +{0x2986, 0x2985}, +{0x2987, 0x2988}, +{0x2988, 0x2987}, +{0x2989, 0x298a}, +{0x298a, 0x2989}, +{0x298b, 0x298c}, +{0x298c, 0x298b}, +{0x298d, 0x2990}, +{0x298e, 0x298f}, +{0x298f, 0x298e}, +{0x2990, 0x298d}, +{0x2991, 0x2992}, +{0x2992, 0x2991}, +{0x2993, 0x2994}, +{0x2994, 0x2993}, +{0x2995, 0x2996}, +{0x2996, 0x2995}, +{0x2997, 0x2998}, +{0x2998, 0x2997}, +{0x299b, 0x2221}, +{0x29a0, 0x2222}, +{0x29a3, 0x2220}, +{0x29a4, 0x29a5}, +{0x29a5, 0x29a4}, +{0x29a8, 0x29a9}, +{0x29a9, 0x29a8}, +{0x29aa, 0x29ab}, +{0x29ab, 0x29aa}, +{0x29ac, 0x29ad}, +{0x29ad, 0x29ac}, +{0x29ae, 0x29af}, +{0x29af, 0x29ae}, +{0x29b8, 0x2298}, +{0x29c0, 0x29c1}, +{0x29c1, 0x29c0}, +{0x29c4, 0x29c5}, +{0x29c5, 0x29c4}, +{0x29cf, 0x29d0}, +{0x29d0, 0x29cf}, +{0x29d1, 0x29d2}, +{0x29d2, 0x29d1}, +{0x29d4, 0x29d5}, +{0x29d5, 0x29d4}, +{0x29d8, 0x29d9}, +{0x29d9, 0x29d8}, +{0x29da, 0x29db}, +{0x29db, 0x29da}, +{0x29e8, 0x29e9}, +{0x29e9, 0x29e8}, +{0x29f5, 0x2215}, +{0x29f8, 0x29f9}, +{0x29f9, 0x29f8}, +{0x29fc, 0x29fd}, +{0x29fd, 0x29fc}, +{0x2a2b, 0x2a2c}, +{0x2a2c, 0x2a2b}, +{0x2a2d, 0x2a2e}, +{0x2a2e, 0x2a2d}, +{0x2a34, 0x2a35}, +{0x2a35, 0x2a34}, +{0x2a3c, 0x2a3d}, +{0x2a3d, 0x2a3c}, +{0x2a64, 0x2a65}, +{0x2a65, 0x2a64}, +{0x2a79, 0x2a7a}, +{0x2a7a, 0x2a79}, +{0x2a7b, 0x2a7c}, +{0x2a7c, 0x2a7b}, +{0x2a7d, 0x2a7e}, +{0x2a7e, 0x2a7d}, +{0x2a7f, 0x2a80}, +{0x2a80, 0x2a7f}, +{0x2a81, 0x2a82}, +{0x2a82, 0x2a81}, +{0x2a83, 0x2a84}, +{0x2a84, 0x2a83}, +{0x2a85, 0x2a86}, +{0x2a86, 0x2a85}, +{0x2a87, 0x2a88}, +{0x2a88, 0x2a87}, +{0x2a89, 0x2a8a}, +{0x2a8a, 0x2a89}, +{0x2a8b, 0x2a8c}, +{0x2a8c, 0x2a8b}, +{0x2a8d, 0x2a8e}, +{0x2a8e, 0x2a8d}, +{0x2a8f, 0x2a90}, +{0x2a90, 0x2a8f}, +{0x2a91, 0x2a92}, +{0x2a92, 0x2a91}, +{0x2a93, 0x2a94}, +{0x2a94, 0x2a93}, +{0x2a95, 0x2a96}, +{0x2a96, 0x2a95}, +{0x2a97, 0x2a98}, +{0x2a98, 0x2a97}, +{0x2a99, 0x2a9a}, +{0x2a9a, 0x2a99}, +{0x2a9b, 0x2a9c}, +{0x2a9c, 0x2a9b}, +{0x2a9d, 0x2a9e}, +{0x2a9e, 0x2a9d}, +{0x2a9f, 0x2aa0}, +{0x2aa0, 0x2a9f}, +{0x2aa1, 0x2aa2}, +{0x2aa2, 0x2aa1}, +{0x2aa6, 0x2aa7}, +{0x2aa7, 0x2aa6}, +{0x2aa8, 0x2aa9}, +{0x2aa9, 0x2aa8}, +{0x2aaa, 0x2aab}, +{0x2aab, 0x2aaa}, +{0x2aac, 0x2aad}, +{0x2aad, 0x2aac}, +{0x2aaf, 0x2ab0}, +{0x2ab0, 0x2aaf}, +{0x2ab1, 0x2ab2}, +{0x2ab2, 0x2ab1}, +{0x2ab3, 0x2ab4}, +{0x2ab4, 0x2ab3}, +{0x2ab5, 0x2ab6}, +{0x2ab6, 0x2ab5}, +{0x2ab7, 0x2ab8}, +{0x2ab8, 0x2ab7}, +{0x2ab9, 0x2aba}, +{0x2aba, 0x2ab9}, +{0x2abb, 0x2abc}, +{0x2abc, 0x2abb}, +{0x2abd, 0x2abe}, +{0x2abe, 0x2abd}, +{0x2abf, 0x2ac0}, +{0x2ac0, 0x2abf}, +{0x2ac1, 0x2ac2}, +{0x2ac2, 0x2ac1}, +{0x2ac3, 0x2ac4}, +{0x2ac4, 0x2ac3}, +{0x2ac5, 0x2ac6}, +{0x2ac6, 0x2ac5}, +{0x2ac7, 0x2ac8}, +{0x2ac8, 0x2ac7}, +{0x2ac9, 0x2aca}, +{0x2aca, 0x2ac9}, +{0x2acb, 0x2acc}, +{0x2acc, 0x2acb}, +{0x2acd, 0x2ace}, +{0x2ace, 0x2acd}, +{0x2acf, 0x2ad0}, +{0x2ad0, 0x2acf}, +{0x2ad1, 0x2ad2}, +{0x2ad2, 0x2ad1}, +{0x2ad3, 0x2ad4}, +{0x2ad4, 0x2ad3}, +{0x2ad5, 0x2ad6}, +{0x2ad6, 0x2ad5}, +{0x2ade, 0x22a6}, +{0x2ae3, 0x22a9}, +{0x2ae4, 0x22a8}, +{0x2ae5, 0x22ab}, +{0x2aec, 0x2aed}, +{0x2aed, 0x2aec}, +{0x2aee, 0x2224}, +{0x2af7, 0x2af8}, +{0x2af8, 0x2af7}, +{0x2af9, 0x2afa}, +{0x2afa, 0x2af9}, +{0x2bfe, 0x221f}, +{0x2e02, 0x2e03}, +{0x2e03, 0x2e02}, +{0x2e04, 0x2e05}, +{0x2e05, 0x2e04}, +{0x2e09, 0x2e0a}, +{0x2e0a, 0x2e09}, +{0x2e0c, 0x2e0d}, +{0x2e0d, 0x2e0c}, +{0x2e1c, 0x2e1d}, +{0x2e1d, 0x2e1c}, +{0x2e20, 0x2e21}, +{0x2e21, 0x2e20}, +{0x2e22, 0x2e23}, +{0x2e23, 0x2e22}, +{0x2e24, 0x2e25}, +{0x2e25, 0x2e24}, +{0x2e26, 0x2e27}, +{0x2e27, 0x2e26}, +{0x2e28, 0x2e29}, +{0x2e29, 0x2e28}, +{0x2e55, 0x2e56}, +{0x2e56, 0x2e55}, +{0x2e57, 0x2e58}, +{0x2e58, 0x2e57}, +{0x2e59, 0x2e5a}, +{0x2e5a, 0x2e59}, +{0x2e5b, 0x2e5c}, +{0x2e5c, 0x2e5b}, +{0x3008, 0x3009}, +{0x3009, 0x3008}, +{0x300a, 0x300b}, +{0x300b, 0x300a}, +{0x300c, 0x300d}, +{0x300d, 0x300c}, +{0x300e, 0x300f}, +{0x300f, 0x300e}, +{0x3010, 0x3011}, +{0x3011, 0x3010}, +{0x3014, 0x3015}, +{0x3015, 0x3014}, +{0x3016, 0x3017}, +{0x3017, 0x3016}, +{0x3018, 0x3019}, +{0x3019, 0x3018}, +{0x301a, 0x301b}, +{0x301b, 0x301a}, +{0xfe59, 0xfe5a}, +{0xfe5a, 0xfe59}, +{0xfe5b, 0xfe5c}, +{0xfe5c, 0xfe5b}, +{0xfe5d, 0xfe5e}, +{0xfe5e, 0xfe5d}, +{0xfe64, 0xfe65}, +{0xfe65, 0xfe64}, +{0xff08, 0xff09}, +{0xff09, 0xff08}, +{0xff1c, 0xff1e}, +{0xff1e, 0xff1c}, +{0xff3b, 0xff3d}, +{0xff3d, 0xff3b}, +{0xff5b, 0xff5d}, +{0xff5d, 0xff5b}, +{0xff5f, 0xff60}, +{0xff60, 0xff5f}, +{0xff62, 0xff63}, +{0xff63, 0xff62}, diff --git a/unicode/bidi_type.h b/unicode/bidi_type.h new file mode 100644 index 00000000..7e617044 --- /dev/null +++ b/unicode/bidi_type.h @@ -0,0 +1,1381 @@ +/* + * Autogenerated by read_ucd.py from The Unicode Standard 16.0.0 + * + * Bidirectional type of every Unicode character, excluding those with + * type ON. + * + * Used by terminal/bidi.c, whose associated lookup function returns ON + * by default for anything not in this list. + */ + +{0x0000, 0x0008, BN}, +{0x0009, 0x0009, S}, +{0x000a, 0x000a, B}, +{0x000b, 0x000b, S}, +{0x000c, 0x000c, WS}, +{0x000d, 0x000d, B}, +{0x000e, 0x001b, BN}, +{0x001c, 0x001e, B}, +{0x001f, 0x001f, S}, +{0x0020, 0x0020, WS}, +{0x0023, 0x0025, ET}, +{0x002b, 0x002b, ES}, +{0x002c, 0x002c, CS}, +{0x002d, 0x002d, ES}, +{0x002e, 0x002f, CS}, +{0x0030, 0x0039, EN}, +{0x003a, 0x003a, CS}, +{0x0041, 0x005a, L}, +{0x0061, 0x007a, L}, +{0x007f, 0x0084, BN}, +{0x0085, 0x0085, B}, +{0x0086, 0x009f, BN}, +{0x00a0, 0x00a0, CS}, +{0x00a2, 0x00a5, ET}, +{0x00aa, 0x00aa, L}, +{0x00ad, 0x00ad, BN}, +{0x00b0, 0x00b1, ET}, +{0x00b2, 0x00b3, EN}, +{0x00b5, 0x00b5, L}, +{0x00b9, 0x00b9, EN}, +{0x00ba, 0x00ba, L}, +{0x00c0, 0x00d6, L}, +{0x00d8, 0x00f6, L}, +{0x00f8, 0x02b8, L}, +{0x02bb, 0x02c1, L}, +{0x02d0, 0x02d1, L}, +{0x02e0, 0x02e4, L}, +{0x02ee, 0x02ee, L}, +{0x0300, 0x036f, NSM}, +{0x0370, 0x0373, L}, +{0x0376, 0x0377, L}, +{0x037a, 0x037d, L}, +{0x037f, 0x037f, L}, +{0x0386, 0x0386, L}, +{0x0388, 0x038a, L}, +{0x038c, 0x038c, L}, +{0x038e, 0x03a1, L}, +{0x03a3, 0x03f5, L}, +{0x03f7, 0x0482, L}, +{0x0483, 0x0489, NSM}, +{0x048a, 0x052f, L}, +{0x0531, 0x0556, L}, +{0x0559, 0x0589, L}, +{0x058f, 0x058f, ET}, +{0x0591, 0x05bd, NSM}, +{0x05be, 0x05be, R}, +{0x05bf, 0x05bf, NSM}, +{0x05c0, 0x05c0, R}, +{0x05c1, 0x05c2, NSM}, +{0x05c3, 0x05c3, R}, +{0x05c4, 0x05c5, NSM}, +{0x05c6, 0x05c6, R}, +{0x05c7, 0x05c7, NSM}, +{0x05d0, 0x05ea, R}, +{0x05ef, 0x05f4, R}, +{0x0600, 0x0605, AN}, +{0x0608, 0x0608, AL}, +{0x0609, 0x060a, ET}, +{0x060b, 0x060b, AL}, +{0x060c, 0x060c, CS}, +{0x060d, 0x060d, AL}, +{0x0610, 0x061a, NSM}, +{0x061b, 0x064a, AL}, +{0x064b, 0x065f, NSM}, +{0x0660, 0x0669, AN}, +{0x066a, 0x066a, ET}, +{0x066b, 0x066c, AN}, +{0x066d, 0x066f, AL}, +{0x0670, 0x0670, NSM}, +{0x0671, 0x06d5, AL}, +{0x06d6, 0x06dc, NSM}, +{0x06dd, 0x06dd, AN}, +{0x06df, 0x06e4, NSM}, +{0x06e5, 0x06e6, AL}, +{0x06e7, 0x06e8, NSM}, +{0x06ea, 0x06ed, NSM}, +{0x06ee, 0x06ef, AL}, +{0x06f0, 0x06f9, EN}, +{0x06fa, 0x070d, AL}, +{0x070f, 0x0710, AL}, +{0x0711, 0x0711, NSM}, +{0x0712, 0x072f, AL}, +{0x0730, 0x074a, NSM}, +{0x074d, 0x07a5, AL}, +{0x07a6, 0x07b0, NSM}, +{0x07b1, 0x07b1, AL}, +{0x07c0, 0x07ea, R}, +{0x07eb, 0x07f3, NSM}, +{0x07f4, 0x07f5, R}, +{0x07fa, 0x07fa, R}, +{0x07fd, 0x07fd, NSM}, +{0x07fe, 0x0815, R}, +{0x0816, 0x0819, NSM}, +{0x081a, 0x081a, R}, +{0x081b, 0x0823, NSM}, +{0x0824, 0x0824, R}, +{0x0825, 0x0827, NSM}, +{0x0828, 0x0828, R}, +{0x0829, 0x082d, NSM}, +{0x0830, 0x083e, R}, +{0x0840, 0x0858, R}, +{0x0859, 0x085b, NSM}, +{0x085e, 0x085e, R}, +{0x0860, 0x086a, AL}, +{0x0870, 0x088e, AL}, +{0x0890, 0x0891, AN}, +{0x0897, 0x089f, NSM}, +{0x08a0, 0x08c9, AL}, +{0x08ca, 0x08e1, NSM}, +{0x08e2, 0x08e2, AN}, +{0x08e3, 0x0902, NSM}, +{0x0903, 0x0939, L}, +{0x093a, 0x093a, NSM}, +{0x093b, 0x093b, L}, +{0x093c, 0x093c, NSM}, +{0x093d, 0x0940, L}, +{0x0941, 0x0948, NSM}, +{0x0949, 0x094c, L}, +{0x094d, 0x094d, NSM}, +{0x094e, 0x0950, L}, +{0x0951, 0x0957, NSM}, +{0x0958, 0x0961, L}, +{0x0962, 0x0963, NSM}, +{0x0964, 0x0980, L}, +{0x0981, 0x0981, NSM}, +{0x0982, 0x0983, L}, +{0x0985, 0x098c, L}, +{0x098f, 0x0990, L}, +{0x0993, 0x09a8, L}, +{0x09aa, 0x09b0, L}, +{0x09b2, 0x09b2, L}, +{0x09b6, 0x09b9, L}, +{0x09bc, 0x09bc, NSM}, +{0x09bd, 0x09c0, L}, +{0x09c1, 0x09c4, NSM}, +{0x09c7, 0x09c8, L}, +{0x09cb, 0x09cc, L}, +{0x09cd, 0x09cd, NSM}, +{0x09ce, 0x09ce, L}, +{0x09d7, 0x09d7, L}, +{0x09dc, 0x09dd, L}, +{0x09df, 0x09e1, L}, +{0x09e2, 0x09e3, NSM}, +{0x09e6, 0x09f1, L}, +{0x09f2, 0x09f3, ET}, +{0x09f4, 0x09fa, L}, +{0x09fb, 0x09fb, ET}, +{0x09fc, 0x09fd, L}, +{0x09fe, 0x09fe, NSM}, +{0x0a01, 0x0a02, NSM}, +{0x0a03, 0x0a03, L}, +{0x0a05, 0x0a0a, L}, +{0x0a0f, 0x0a10, L}, +{0x0a13, 0x0a28, L}, +{0x0a2a, 0x0a30, L}, +{0x0a32, 0x0a33, L}, +{0x0a35, 0x0a36, L}, +{0x0a38, 0x0a39, L}, +{0x0a3c, 0x0a3c, NSM}, +{0x0a3e, 0x0a40, L}, +{0x0a41, 0x0a42, NSM}, +{0x0a47, 0x0a48, NSM}, +{0x0a4b, 0x0a4d, NSM}, +{0x0a51, 0x0a51, NSM}, +{0x0a59, 0x0a5c, L}, +{0x0a5e, 0x0a5e, L}, +{0x0a66, 0x0a6f, L}, +{0x0a70, 0x0a71, NSM}, +{0x0a72, 0x0a74, L}, +{0x0a75, 0x0a75, NSM}, +{0x0a76, 0x0a76, L}, +{0x0a81, 0x0a82, NSM}, +{0x0a83, 0x0a83, L}, +{0x0a85, 0x0a8d, L}, +{0x0a8f, 0x0a91, L}, +{0x0a93, 0x0aa8, L}, +{0x0aaa, 0x0ab0, L}, +{0x0ab2, 0x0ab3, L}, +{0x0ab5, 0x0ab9, L}, +{0x0abc, 0x0abc, NSM}, +{0x0abd, 0x0ac0, L}, +{0x0ac1, 0x0ac5, NSM}, +{0x0ac7, 0x0ac8, NSM}, +{0x0ac9, 0x0ac9, L}, +{0x0acb, 0x0acc, L}, +{0x0acd, 0x0acd, NSM}, +{0x0ad0, 0x0ad0, L}, +{0x0ae0, 0x0ae1, L}, +{0x0ae2, 0x0ae3, NSM}, +{0x0ae6, 0x0af0, L}, +{0x0af1, 0x0af1, ET}, +{0x0af9, 0x0af9, L}, +{0x0afa, 0x0aff, NSM}, +{0x0b01, 0x0b01, NSM}, +{0x0b02, 0x0b03, L}, +{0x0b05, 0x0b0c, L}, +{0x0b0f, 0x0b10, L}, +{0x0b13, 0x0b28, L}, +{0x0b2a, 0x0b30, L}, +{0x0b32, 0x0b33, L}, +{0x0b35, 0x0b39, L}, +{0x0b3c, 0x0b3c, NSM}, +{0x0b3d, 0x0b3e, L}, +{0x0b3f, 0x0b3f, NSM}, +{0x0b40, 0x0b40, L}, +{0x0b41, 0x0b44, NSM}, +{0x0b47, 0x0b48, L}, +{0x0b4b, 0x0b4c, L}, +{0x0b4d, 0x0b4d, NSM}, +{0x0b55, 0x0b56, NSM}, +{0x0b57, 0x0b57, L}, +{0x0b5c, 0x0b5d, L}, +{0x0b5f, 0x0b61, L}, +{0x0b62, 0x0b63, NSM}, +{0x0b66, 0x0b77, L}, +{0x0b82, 0x0b82, NSM}, +{0x0b83, 0x0b83, L}, +{0x0b85, 0x0b8a, L}, +{0x0b8e, 0x0b90, L}, +{0x0b92, 0x0b95, L}, +{0x0b99, 0x0b9a, L}, +{0x0b9c, 0x0b9c, L}, +{0x0b9e, 0x0b9f, L}, +{0x0ba3, 0x0ba4, L}, +{0x0ba8, 0x0baa, L}, +{0x0bae, 0x0bb9, L}, +{0x0bbe, 0x0bbf, L}, +{0x0bc0, 0x0bc0, NSM}, +{0x0bc1, 0x0bc2, L}, +{0x0bc6, 0x0bc8, L}, +{0x0bca, 0x0bcc, L}, +{0x0bcd, 0x0bcd, NSM}, +{0x0bd0, 0x0bd0, L}, +{0x0bd7, 0x0bd7, L}, +{0x0be6, 0x0bf2, L}, +{0x0bf9, 0x0bf9, ET}, +{0x0c00, 0x0c00, NSM}, +{0x0c01, 0x0c03, L}, +{0x0c04, 0x0c04, NSM}, +{0x0c05, 0x0c0c, L}, +{0x0c0e, 0x0c10, L}, +{0x0c12, 0x0c28, L}, +{0x0c2a, 0x0c39, L}, +{0x0c3c, 0x0c3c, NSM}, +{0x0c3d, 0x0c3d, L}, +{0x0c3e, 0x0c40, NSM}, +{0x0c41, 0x0c44, L}, +{0x0c46, 0x0c48, NSM}, +{0x0c4a, 0x0c4d, NSM}, +{0x0c55, 0x0c56, NSM}, +{0x0c58, 0x0c5a, L}, +{0x0c5d, 0x0c5d, L}, +{0x0c60, 0x0c61, L}, +{0x0c62, 0x0c63, NSM}, +{0x0c66, 0x0c6f, L}, +{0x0c77, 0x0c77, L}, +{0x0c7f, 0x0c80, L}, +{0x0c81, 0x0c81, NSM}, +{0x0c82, 0x0c8c, L}, +{0x0c8e, 0x0c90, L}, +{0x0c92, 0x0ca8, L}, +{0x0caa, 0x0cb3, L}, +{0x0cb5, 0x0cb9, L}, +{0x0cbc, 0x0cbc, NSM}, +{0x0cbd, 0x0cc4, L}, +{0x0cc6, 0x0cc8, L}, +{0x0cca, 0x0ccb, L}, +{0x0ccc, 0x0ccd, NSM}, +{0x0cd5, 0x0cd6, L}, +{0x0cdd, 0x0cde, L}, +{0x0ce0, 0x0ce1, L}, +{0x0ce2, 0x0ce3, NSM}, +{0x0ce6, 0x0cef, L}, +{0x0cf1, 0x0cf3, L}, +{0x0d00, 0x0d01, NSM}, +{0x0d02, 0x0d0c, L}, +{0x0d0e, 0x0d10, L}, +{0x0d12, 0x0d3a, L}, +{0x0d3b, 0x0d3c, NSM}, +{0x0d3d, 0x0d40, L}, +{0x0d41, 0x0d44, NSM}, +{0x0d46, 0x0d48, L}, +{0x0d4a, 0x0d4c, L}, +{0x0d4d, 0x0d4d, NSM}, +{0x0d4e, 0x0d4f, L}, +{0x0d54, 0x0d61, L}, +{0x0d62, 0x0d63, NSM}, +{0x0d66, 0x0d7f, L}, +{0x0d81, 0x0d81, NSM}, +{0x0d82, 0x0d83, L}, +{0x0d85, 0x0d96, L}, +{0x0d9a, 0x0db1, L}, +{0x0db3, 0x0dbb, L}, +{0x0dbd, 0x0dbd, L}, +{0x0dc0, 0x0dc6, L}, +{0x0dca, 0x0dca, NSM}, +{0x0dcf, 0x0dd1, L}, +{0x0dd2, 0x0dd4, NSM}, +{0x0dd6, 0x0dd6, NSM}, +{0x0dd8, 0x0ddf, L}, +{0x0de6, 0x0def, L}, +{0x0df2, 0x0df4, L}, +{0x0e01, 0x0e30, L}, +{0x0e31, 0x0e31, NSM}, +{0x0e32, 0x0e33, L}, +{0x0e34, 0x0e3a, NSM}, +{0x0e3f, 0x0e3f, ET}, +{0x0e40, 0x0e46, L}, +{0x0e47, 0x0e4e, NSM}, +{0x0e4f, 0x0e5b, L}, +{0x0e81, 0x0e82, L}, +{0x0e84, 0x0e84, L}, +{0x0e86, 0x0e8a, L}, +{0x0e8c, 0x0ea3, L}, +{0x0ea5, 0x0ea5, L}, +{0x0ea7, 0x0eb0, L}, +{0x0eb1, 0x0eb1, NSM}, +{0x0eb2, 0x0eb3, L}, +{0x0eb4, 0x0ebc, NSM}, +{0x0ebd, 0x0ebd, L}, +{0x0ec0, 0x0ec4, L}, +{0x0ec6, 0x0ec6, L}, +{0x0ec8, 0x0ece, NSM}, +{0x0ed0, 0x0ed9, L}, +{0x0edc, 0x0edf, L}, +{0x0f00, 0x0f17, L}, +{0x0f18, 0x0f19, NSM}, +{0x0f1a, 0x0f34, L}, +{0x0f35, 0x0f35, NSM}, +{0x0f36, 0x0f36, L}, +{0x0f37, 0x0f37, NSM}, +{0x0f38, 0x0f38, L}, +{0x0f39, 0x0f39, NSM}, +{0x0f3e, 0x0f47, L}, +{0x0f49, 0x0f6c, L}, +{0x0f71, 0x0f7e, NSM}, +{0x0f7f, 0x0f7f, L}, +{0x0f80, 0x0f84, NSM}, +{0x0f85, 0x0f85, L}, +{0x0f86, 0x0f87, NSM}, +{0x0f88, 0x0f8c, L}, +{0x0f8d, 0x0f97, NSM}, +{0x0f99, 0x0fbc, NSM}, +{0x0fbe, 0x0fc5, L}, +{0x0fc6, 0x0fc6, NSM}, +{0x0fc7, 0x0fcc, L}, +{0x0fce, 0x0fda, L}, +{0x1000, 0x102c, L}, +{0x102d, 0x1030, NSM}, +{0x1031, 0x1031, L}, +{0x1032, 0x1037, NSM}, +{0x1038, 0x1038, L}, +{0x1039, 0x103a, NSM}, +{0x103b, 0x103c, L}, +{0x103d, 0x103e, NSM}, +{0x103f, 0x1057, L}, +{0x1058, 0x1059, NSM}, +{0x105a, 0x105d, L}, +{0x105e, 0x1060, NSM}, +{0x1061, 0x1070, L}, +{0x1071, 0x1074, NSM}, +{0x1075, 0x1081, L}, +{0x1082, 0x1082, NSM}, +{0x1083, 0x1084, L}, +{0x1085, 0x1086, NSM}, +{0x1087, 0x108c, L}, +{0x108d, 0x108d, NSM}, +{0x108e, 0x109c, L}, +{0x109d, 0x109d, NSM}, +{0x109e, 0x10c5, L}, +{0x10c7, 0x10c7, L}, +{0x10cd, 0x10cd, L}, +{0x10d0, 0x1248, L}, +{0x124a, 0x124d, L}, +{0x1250, 0x1256, L}, +{0x1258, 0x1258, L}, +{0x125a, 0x125d, L}, +{0x1260, 0x1288, L}, +{0x128a, 0x128d, L}, +{0x1290, 0x12b0, L}, +{0x12b2, 0x12b5, L}, +{0x12b8, 0x12be, L}, +{0x12c0, 0x12c0, L}, +{0x12c2, 0x12c5, L}, +{0x12c8, 0x12d6, L}, +{0x12d8, 0x1310, L}, +{0x1312, 0x1315, L}, +{0x1318, 0x135a, L}, +{0x135d, 0x135f, NSM}, +{0x1360, 0x137c, L}, +{0x1380, 0x138f, L}, +{0x13a0, 0x13f5, L}, +{0x13f8, 0x13fd, L}, +{0x1401, 0x167f, L}, +{0x1680, 0x1680, WS}, +{0x1681, 0x169a, L}, +{0x16a0, 0x16f8, L}, +{0x1700, 0x1711, L}, +{0x1712, 0x1714, NSM}, +{0x1715, 0x1715, L}, +{0x171f, 0x1731, L}, +{0x1732, 0x1733, NSM}, +{0x1734, 0x1736, L}, +{0x1740, 0x1751, L}, +{0x1752, 0x1753, NSM}, +{0x1760, 0x176c, L}, +{0x176e, 0x1770, L}, +{0x1772, 0x1773, NSM}, +{0x1780, 0x17b3, L}, +{0x17b4, 0x17b5, NSM}, +{0x17b6, 0x17b6, L}, +{0x17b7, 0x17bd, NSM}, +{0x17be, 0x17c5, L}, +{0x17c6, 0x17c6, NSM}, +{0x17c7, 0x17c8, L}, +{0x17c9, 0x17d3, NSM}, +{0x17d4, 0x17da, L}, +{0x17db, 0x17db, ET}, +{0x17dc, 0x17dc, L}, +{0x17dd, 0x17dd, NSM}, +{0x17e0, 0x17e9, L}, +{0x180b, 0x180d, NSM}, +{0x180e, 0x180e, BN}, +{0x180f, 0x180f, NSM}, +{0x1810, 0x1819, L}, +{0x1820, 0x1878, L}, +{0x1880, 0x1884, L}, +{0x1885, 0x1886, NSM}, +{0x1887, 0x18a8, L}, +{0x18a9, 0x18a9, NSM}, +{0x18aa, 0x18aa, L}, +{0x18b0, 0x18f5, L}, +{0x1900, 0x191e, L}, +{0x1920, 0x1922, NSM}, +{0x1923, 0x1926, L}, +{0x1927, 0x1928, NSM}, +{0x1929, 0x192b, L}, +{0x1930, 0x1931, L}, +{0x1932, 0x1932, NSM}, +{0x1933, 0x1938, L}, +{0x1939, 0x193b, NSM}, +{0x1946, 0x196d, L}, +{0x1970, 0x1974, L}, +{0x1980, 0x19ab, L}, +{0x19b0, 0x19c9, L}, +{0x19d0, 0x19da, L}, +{0x1a00, 0x1a16, L}, +{0x1a17, 0x1a18, NSM}, +{0x1a19, 0x1a1a, L}, +{0x1a1b, 0x1a1b, NSM}, +{0x1a1e, 0x1a55, L}, +{0x1a56, 0x1a56, NSM}, +{0x1a57, 0x1a57, L}, +{0x1a58, 0x1a5e, NSM}, +{0x1a60, 0x1a60, NSM}, +{0x1a61, 0x1a61, L}, +{0x1a62, 0x1a62, NSM}, +{0x1a63, 0x1a64, L}, +{0x1a65, 0x1a6c, NSM}, +{0x1a6d, 0x1a72, L}, +{0x1a73, 0x1a7c, NSM}, +{0x1a7f, 0x1a7f, NSM}, +{0x1a80, 0x1a89, L}, +{0x1a90, 0x1a99, L}, +{0x1aa0, 0x1aad, L}, +{0x1ab0, 0x1ace, NSM}, +{0x1b00, 0x1b03, NSM}, +{0x1b04, 0x1b33, L}, +{0x1b34, 0x1b34, NSM}, +{0x1b35, 0x1b35, L}, +{0x1b36, 0x1b3a, NSM}, +{0x1b3b, 0x1b3b, L}, +{0x1b3c, 0x1b3c, NSM}, +{0x1b3d, 0x1b41, L}, +{0x1b42, 0x1b42, NSM}, +{0x1b43, 0x1b4c, L}, +{0x1b4e, 0x1b6a, L}, +{0x1b6b, 0x1b73, NSM}, +{0x1b74, 0x1b7f, L}, +{0x1b80, 0x1b81, NSM}, +{0x1b82, 0x1ba1, L}, +{0x1ba2, 0x1ba5, NSM}, +{0x1ba6, 0x1ba7, L}, +{0x1ba8, 0x1ba9, NSM}, +{0x1baa, 0x1baa, L}, +{0x1bab, 0x1bad, NSM}, +{0x1bae, 0x1be5, L}, +{0x1be6, 0x1be6, NSM}, +{0x1be7, 0x1be7, L}, +{0x1be8, 0x1be9, NSM}, +{0x1bea, 0x1bec, L}, +{0x1bed, 0x1bed, NSM}, +{0x1bee, 0x1bee, L}, +{0x1bef, 0x1bf1, NSM}, +{0x1bf2, 0x1bf3, L}, +{0x1bfc, 0x1c2b, L}, +{0x1c2c, 0x1c33, NSM}, +{0x1c34, 0x1c35, L}, +{0x1c36, 0x1c37, NSM}, +{0x1c3b, 0x1c49, L}, +{0x1c4d, 0x1c8a, L}, +{0x1c90, 0x1cba, L}, +{0x1cbd, 0x1cc7, L}, +{0x1cd0, 0x1cd2, NSM}, +{0x1cd3, 0x1cd3, L}, +{0x1cd4, 0x1ce0, NSM}, +{0x1ce1, 0x1ce1, L}, +{0x1ce2, 0x1ce8, NSM}, +{0x1ce9, 0x1cec, L}, +{0x1ced, 0x1ced, NSM}, +{0x1cee, 0x1cf3, L}, +{0x1cf4, 0x1cf4, NSM}, +{0x1cf5, 0x1cf7, L}, +{0x1cf8, 0x1cf9, NSM}, +{0x1cfa, 0x1cfa, L}, +{0x1d00, 0x1dbf, L}, +{0x1dc0, 0x1dff, NSM}, +{0x1e00, 0x1f15, L}, +{0x1f18, 0x1f1d, L}, +{0x1f20, 0x1f45, L}, +{0x1f48, 0x1f4d, L}, +{0x1f50, 0x1f57, L}, +{0x1f59, 0x1f59, L}, +{0x1f5b, 0x1f5b, L}, +{0x1f5d, 0x1f5d, L}, +{0x1f5f, 0x1f7d, L}, +{0x1f80, 0x1fb4, L}, +{0x1fb6, 0x1fbc, L}, +{0x1fbe, 0x1fbe, L}, +{0x1fc2, 0x1fc4, L}, +{0x1fc6, 0x1fcc, L}, +{0x1fd0, 0x1fd3, L}, +{0x1fd6, 0x1fdb, L}, +{0x1fe0, 0x1fec, L}, +{0x1ff2, 0x1ff4, L}, +{0x1ff6, 0x1ffc, L}, +{0x2000, 0x200a, WS}, +{0x200b, 0x200d, BN}, +{0x200e, 0x200e, L}, +{0x200f, 0x200f, R}, +{0x2028, 0x2028, WS}, +{0x2029, 0x2029, B}, +{0x202a, 0x202a, LRE}, +{0x202b, 0x202b, RLE}, +{0x202c, 0x202c, PDF}, +{0x202d, 0x202d, LRO}, +{0x202e, 0x202e, RLO}, +{0x202f, 0x202f, CS}, +{0x2030, 0x2034, ET}, +{0x2044, 0x2044, CS}, +{0x205f, 0x205f, WS}, +{0x2060, 0x2064, BN}, +{0x2066, 0x2066, LRI}, +{0x2067, 0x2067, RLI}, +{0x2068, 0x2068, FSI}, +{0x2069, 0x2069, PDI}, +{0x206a, 0x206f, BN}, +{0x2070, 0x2070, EN}, +{0x2071, 0x2071, L}, +{0x2074, 0x2079, EN}, +{0x207a, 0x207b, ES}, +{0x207f, 0x207f, L}, +{0x2080, 0x2089, EN}, +{0x208a, 0x208b, ES}, +{0x2090, 0x209c, L}, +{0x20a0, 0x20c0, ET}, +{0x20d0, 0x20f0, NSM}, +{0x2102, 0x2102, L}, +{0x2107, 0x2107, L}, +{0x210a, 0x2113, L}, +{0x2115, 0x2115, L}, +{0x2119, 0x211d, L}, +{0x2124, 0x2124, L}, +{0x2126, 0x2126, L}, +{0x2128, 0x2128, L}, +{0x212a, 0x212d, L}, +{0x212e, 0x212e, ET}, +{0x212f, 0x2139, L}, +{0x213c, 0x213f, L}, +{0x2145, 0x2149, L}, +{0x214e, 0x214f, L}, +{0x2160, 0x2188, L}, +{0x2212, 0x2212, ES}, +{0x2213, 0x2213, ET}, +{0x2336, 0x237a, L}, +{0x2395, 0x2395, L}, +{0x2488, 0x249b, EN}, +{0x249c, 0x24e9, L}, +{0x26ac, 0x26ac, L}, +{0x2800, 0x28ff, L}, +{0x2c00, 0x2ce4, L}, +{0x2ceb, 0x2cee, L}, +{0x2cef, 0x2cf1, NSM}, +{0x2cf2, 0x2cf3, L}, +{0x2d00, 0x2d25, L}, +{0x2d27, 0x2d27, L}, +{0x2d2d, 0x2d2d, L}, +{0x2d30, 0x2d67, L}, +{0x2d6f, 0x2d70, L}, +{0x2d7f, 0x2d7f, NSM}, +{0x2d80, 0x2d96, L}, +{0x2da0, 0x2da6, L}, +{0x2da8, 0x2dae, L}, +{0x2db0, 0x2db6, L}, +{0x2db8, 0x2dbe, L}, +{0x2dc0, 0x2dc6, L}, +{0x2dc8, 0x2dce, L}, +{0x2dd0, 0x2dd6, L}, +{0x2dd8, 0x2dde, L}, +{0x2de0, 0x2dff, NSM}, +{0x3000, 0x3000, WS}, +{0x3005, 0x3007, L}, +{0x3021, 0x3029, L}, +{0x302a, 0x302d, NSM}, +{0x302e, 0x302f, L}, +{0x3031, 0x3035, L}, +{0x3038, 0x303c, L}, +{0x3041, 0x3096, L}, +{0x3099, 0x309a, NSM}, +{0x309d, 0x309f, L}, +{0x30a1, 0x30fa, L}, +{0x30fc, 0x30ff, L}, +{0x3105, 0x312f, L}, +{0x3131, 0x318e, L}, +{0x3190, 0x31bf, L}, +{0x31f0, 0x321c, L}, +{0x3220, 0x324f, L}, +{0x3260, 0x327b, L}, +{0x327f, 0x32b0, L}, +{0x32c0, 0x32cb, L}, +{0x32d0, 0x3376, L}, +{0x337b, 0x33dd, L}, +{0x33e0, 0x33fe, L}, +{0x3400, 0x4dbf, L}, +{0x4e00, 0xa48c, L}, +{0xa4d0, 0xa60c, L}, +{0xa610, 0xa62b, L}, +{0xa640, 0xa66e, L}, +{0xa66f, 0xa672, NSM}, +{0xa674, 0xa67d, NSM}, +{0xa680, 0xa69d, L}, +{0xa69e, 0xa69f, NSM}, +{0xa6a0, 0xa6ef, L}, +{0xa6f0, 0xa6f1, NSM}, +{0xa6f2, 0xa6f7, L}, +{0xa722, 0xa787, L}, +{0xa789, 0xa7cd, L}, +{0xa7d0, 0xa7d1, L}, +{0xa7d3, 0xa7d3, L}, +{0xa7d5, 0xa7dc, L}, +{0xa7f2, 0xa801, L}, +{0xa802, 0xa802, NSM}, +{0xa803, 0xa805, L}, +{0xa806, 0xa806, NSM}, +{0xa807, 0xa80a, L}, +{0xa80b, 0xa80b, NSM}, +{0xa80c, 0xa824, L}, +{0xa825, 0xa826, NSM}, +{0xa827, 0xa827, L}, +{0xa82c, 0xa82c, NSM}, +{0xa830, 0xa837, L}, +{0xa838, 0xa839, ET}, +{0xa840, 0xa873, L}, +{0xa880, 0xa8c3, L}, +{0xa8c4, 0xa8c5, NSM}, +{0xa8ce, 0xa8d9, L}, +{0xa8e0, 0xa8f1, NSM}, +{0xa8f2, 0xa8fe, L}, +{0xa8ff, 0xa8ff, NSM}, +{0xa900, 0xa925, L}, +{0xa926, 0xa92d, NSM}, +{0xa92e, 0xa946, L}, +{0xa947, 0xa951, NSM}, +{0xa952, 0xa953, L}, +{0xa95f, 0xa97c, L}, +{0xa980, 0xa982, NSM}, +{0xa983, 0xa9b2, L}, +{0xa9b3, 0xa9b3, NSM}, +{0xa9b4, 0xa9b5, L}, +{0xa9b6, 0xa9b9, NSM}, +{0xa9ba, 0xa9bb, L}, +{0xa9bc, 0xa9bd, NSM}, +{0xa9be, 0xa9cd, L}, +{0xa9cf, 0xa9d9, L}, +{0xa9de, 0xa9e4, L}, +{0xa9e5, 0xa9e5, NSM}, +{0xa9e6, 0xa9fe, L}, +{0xaa00, 0xaa28, L}, +{0xaa29, 0xaa2e, NSM}, +{0xaa2f, 0xaa30, L}, +{0xaa31, 0xaa32, NSM}, +{0xaa33, 0xaa34, L}, +{0xaa35, 0xaa36, NSM}, +{0xaa40, 0xaa42, L}, +{0xaa43, 0xaa43, NSM}, +{0xaa44, 0xaa4b, L}, +{0xaa4c, 0xaa4c, NSM}, +{0xaa4d, 0xaa4d, L}, +{0xaa50, 0xaa59, L}, +{0xaa5c, 0xaa7b, L}, +{0xaa7c, 0xaa7c, NSM}, +{0xaa7d, 0xaaaf, L}, +{0xaab0, 0xaab0, NSM}, +{0xaab1, 0xaab1, L}, +{0xaab2, 0xaab4, NSM}, +{0xaab5, 0xaab6, L}, +{0xaab7, 0xaab8, NSM}, +{0xaab9, 0xaabd, L}, +{0xaabe, 0xaabf, NSM}, +{0xaac0, 0xaac0, L}, +{0xaac1, 0xaac1, NSM}, +{0xaac2, 0xaac2, L}, +{0xaadb, 0xaaeb, L}, +{0xaaec, 0xaaed, NSM}, +{0xaaee, 0xaaf5, L}, +{0xaaf6, 0xaaf6, NSM}, +{0xab01, 0xab06, L}, +{0xab09, 0xab0e, L}, +{0xab11, 0xab16, L}, +{0xab20, 0xab26, L}, +{0xab28, 0xab2e, L}, +{0xab30, 0xab69, L}, +{0xab70, 0xabe4, L}, +{0xabe5, 0xabe5, NSM}, +{0xabe6, 0xabe7, L}, +{0xabe8, 0xabe8, NSM}, +{0xabe9, 0xabec, L}, +{0xabed, 0xabed, NSM}, +{0xabf0, 0xabf9, L}, +{0xac00, 0xd7a3, L}, +{0xd7b0, 0xd7c6, L}, +{0xd7cb, 0xd7fb, L}, +{0xd800, 0xfa6d, L}, +{0xfa70, 0xfad9, L}, +{0xfb00, 0xfb06, L}, +{0xfb13, 0xfb17, L}, +{0xfb1d, 0xfb1d, R}, +{0xfb1e, 0xfb1e, NSM}, +{0xfb1f, 0xfb28, R}, +{0xfb29, 0xfb29, ES}, +{0xfb2a, 0xfb36, R}, +{0xfb38, 0xfb3c, R}, +{0xfb3e, 0xfb3e, R}, +{0xfb40, 0xfb41, R}, +{0xfb43, 0xfb44, R}, +{0xfb46, 0xfb4f, R}, +{0xfb50, 0xfbc2, AL}, +{0xfbd3, 0xfd3d, AL}, +{0xfd50, 0xfd8f, AL}, +{0xfd92, 0xfdc7, AL}, +{0xfdf0, 0xfdfc, AL}, +{0xfe00, 0xfe0f, NSM}, +{0xfe20, 0xfe2f, NSM}, +{0xfe50, 0xfe50, CS}, +{0xfe52, 0xfe52, CS}, +{0xfe55, 0xfe55, CS}, +{0xfe5f, 0xfe5f, ET}, +{0xfe62, 0xfe63, ES}, +{0xfe69, 0xfe6a, ET}, +{0xfe70, 0xfe74, AL}, +{0xfe76, 0xfefc, AL}, +{0xfeff, 0xfeff, BN}, +{0xff03, 0xff05, ET}, +{0xff0b, 0xff0b, ES}, +{0xff0c, 0xff0c, CS}, +{0xff0d, 0xff0d, ES}, +{0xff0e, 0xff0f, CS}, +{0xff10, 0xff19, EN}, +{0xff1a, 0xff1a, CS}, +{0xff21, 0xff3a, L}, +{0xff41, 0xff5a, L}, +{0xff66, 0xffbe, L}, +{0xffc2, 0xffc7, L}, +{0xffca, 0xffcf, L}, +{0xffd2, 0xffd7, L}, +{0xffda, 0xffdc, L}, +{0xffe0, 0xffe1, ET}, +{0xffe5, 0xffe6, ET}, +{0x10000, 0x1000b, L}, +{0x1000d, 0x10026, L}, +{0x10028, 0x1003a, L}, +{0x1003c, 0x1003d, L}, +{0x1003f, 0x1004d, L}, +{0x10050, 0x1005d, L}, +{0x10080, 0x100fa, L}, +{0x10100, 0x10100, L}, +{0x10102, 0x10102, L}, +{0x10107, 0x10133, L}, +{0x10137, 0x1013f, L}, +{0x1018d, 0x1018e, L}, +{0x101d0, 0x101fc, L}, +{0x101fd, 0x101fd, NSM}, +{0x10280, 0x1029c, L}, +{0x102a0, 0x102d0, L}, +{0x102e0, 0x102e0, NSM}, +{0x102e1, 0x102fb, EN}, +{0x10300, 0x10323, L}, +{0x1032d, 0x1034a, L}, +{0x10350, 0x10375, L}, +{0x10376, 0x1037a, NSM}, +{0x10380, 0x1039d, L}, +{0x1039f, 0x103c3, L}, +{0x103c8, 0x103d5, L}, +{0x10400, 0x1049d, L}, +{0x104a0, 0x104a9, L}, +{0x104b0, 0x104d3, L}, +{0x104d8, 0x104fb, L}, +{0x10500, 0x10527, L}, +{0x10530, 0x10563, L}, +{0x1056f, 0x1057a, L}, +{0x1057c, 0x1058a, L}, +{0x1058c, 0x10592, L}, +{0x10594, 0x10595, L}, +{0x10597, 0x105a1, L}, +{0x105a3, 0x105b1, L}, +{0x105b3, 0x105b9, L}, +{0x105bb, 0x105bc, L}, +{0x105c0, 0x105f3, L}, +{0x10600, 0x10736, L}, +{0x10740, 0x10755, L}, +{0x10760, 0x10767, L}, +{0x10780, 0x10785, L}, +{0x10787, 0x107b0, L}, +{0x107b2, 0x107ba, L}, +{0x10800, 0x10805, R}, +{0x10808, 0x10808, R}, +{0x1080a, 0x10835, R}, +{0x10837, 0x10838, R}, +{0x1083c, 0x1083c, R}, +{0x1083f, 0x10855, R}, +{0x10857, 0x1089e, R}, +{0x108a7, 0x108af, R}, +{0x108e0, 0x108f2, R}, +{0x108f4, 0x108f5, R}, +{0x108fb, 0x1091b, R}, +{0x10920, 0x10939, R}, +{0x1093f, 0x1093f, R}, +{0x10980, 0x109b7, R}, +{0x109bc, 0x109cf, R}, +{0x109d2, 0x10a00, R}, +{0x10a01, 0x10a03, NSM}, +{0x10a05, 0x10a06, NSM}, +{0x10a0c, 0x10a0f, NSM}, +{0x10a10, 0x10a13, R}, +{0x10a15, 0x10a17, R}, +{0x10a19, 0x10a35, R}, +{0x10a38, 0x10a3a, NSM}, +{0x10a3f, 0x10a3f, NSM}, +{0x10a40, 0x10a48, R}, +{0x10a50, 0x10a58, R}, +{0x10a60, 0x10a9f, R}, +{0x10ac0, 0x10ae4, R}, +{0x10ae5, 0x10ae6, NSM}, +{0x10aeb, 0x10af6, R}, +{0x10b00, 0x10b35, R}, +{0x10b40, 0x10b55, R}, +{0x10b58, 0x10b72, R}, +{0x10b78, 0x10b91, R}, +{0x10b99, 0x10b9c, R}, +{0x10ba9, 0x10baf, R}, +{0x10c00, 0x10c48, R}, +{0x10c80, 0x10cb2, R}, +{0x10cc0, 0x10cf2, R}, +{0x10cfa, 0x10cff, R}, +{0x10d00, 0x10d23, AL}, +{0x10d24, 0x10d27, NSM}, +{0x10d30, 0x10d39, AN}, +{0x10d40, 0x10d49, AN}, +{0x10d4a, 0x10d65, R}, +{0x10d69, 0x10d6d, NSM}, +{0x10d6f, 0x10d85, R}, +{0x10d8e, 0x10d8f, R}, +{0x10e60, 0x10e7e, AN}, +{0x10e80, 0x10ea9, R}, +{0x10eab, 0x10eac, NSM}, +{0x10ead, 0x10ead, R}, +{0x10eb0, 0x10eb1, R}, +{0x10ec2, 0x10ec4, AL}, +{0x10efc, 0x10eff, NSM}, +{0x10f00, 0x10f27, R}, +{0x10f30, 0x10f45, AL}, +{0x10f46, 0x10f50, NSM}, +{0x10f51, 0x10f59, AL}, +{0x10f70, 0x10f81, R}, +{0x10f82, 0x10f85, NSM}, +{0x10f86, 0x10f89, R}, +{0x10fb0, 0x10fcb, R}, +{0x10fe0, 0x10ff6, R}, +{0x11000, 0x11000, L}, +{0x11001, 0x11001, NSM}, +{0x11002, 0x11037, L}, +{0x11038, 0x11046, NSM}, +{0x11047, 0x1104d, L}, +{0x11066, 0x1106f, L}, +{0x11070, 0x11070, NSM}, +{0x11071, 0x11072, L}, +{0x11073, 0x11074, NSM}, +{0x11075, 0x11075, L}, +{0x1107f, 0x11081, NSM}, +{0x11082, 0x110b2, L}, +{0x110b3, 0x110b6, NSM}, +{0x110b7, 0x110b8, L}, +{0x110b9, 0x110ba, NSM}, +{0x110bb, 0x110c1, L}, +{0x110c2, 0x110c2, NSM}, +{0x110cd, 0x110cd, L}, +{0x110d0, 0x110e8, L}, +{0x110f0, 0x110f9, L}, +{0x11100, 0x11102, NSM}, +{0x11103, 0x11126, L}, +{0x11127, 0x1112b, NSM}, +{0x1112c, 0x1112c, L}, +{0x1112d, 0x11134, NSM}, +{0x11136, 0x11147, L}, +{0x11150, 0x11172, L}, +{0x11173, 0x11173, NSM}, +{0x11174, 0x11176, L}, +{0x11180, 0x11181, NSM}, +{0x11182, 0x111b5, L}, +{0x111b6, 0x111be, NSM}, +{0x111bf, 0x111c8, L}, +{0x111c9, 0x111cc, NSM}, +{0x111cd, 0x111ce, L}, +{0x111cf, 0x111cf, NSM}, +{0x111d0, 0x111df, L}, +{0x111e1, 0x111f4, L}, +{0x11200, 0x11211, L}, +{0x11213, 0x1122e, L}, +{0x1122f, 0x11231, NSM}, +{0x11232, 0x11233, L}, +{0x11234, 0x11234, NSM}, +{0x11235, 0x11235, L}, +{0x11236, 0x11237, NSM}, +{0x11238, 0x1123d, L}, +{0x1123e, 0x1123e, NSM}, +{0x1123f, 0x11240, L}, +{0x11241, 0x11241, NSM}, +{0x11280, 0x11286, L}, +{0x11288, 0x11288, L}, +{0x1128a, 0x1128d, L}, +{0x1128f, 0x1129d, L}, +{0x1129f, 0x112a9, L}, +{0x112b0, 0x112de, L}, +{0x112df, 0x112df, NSM}, +{0x112e0, 0x112e2, L}, +{0x112e3, 0x112ea, NSM}, +{0x112f0, 0x112f9, L}, +{0x11300, 0x11301, NSM}, +{0x11302, 0x11303, L}, +{0x11305, 0x1130c, L}, +{0x1130f, 0x11310, L}, +{0x11313, 0x11328, L}, +{0x1132a, 0x11330, L}, +{0x11332, 0x11333, L}, +{0x11335, 0x11339, L}, +{0x1133b, 0x1133c, NSM}, +{0x1133d, 0x1133f, L}, +{0x11340, 0x11340, NSM}, +{0x11341, 0x11344, L}, +{0x11347, 0x11348, L}, +{0x1134b, 0x1134d, L}, +{0x11350, 0x11350, L}, +{0x11357, 0x11357, L}, +{0x1135d, 0x11363, L}, +{0x11366, 0x1136c, NSM}, +{0x11370, 0x11374, NSM}, +{0x11380, 0x11389, L}, +{0x1138b, 0x1138b, L}, +{0x1138e, 0x1138e, L}, +{0x11390, 0x113b5, L}, +{0x113b7, 0x113ba, L}, +{0x113bb, 0x113c0, NSM}, +{0x113c2, 0x113c2, L}, +{0x113c5, 0x113c5, L}, +{0x113c7, 0x113ca, L}, +{0x113cc, 0x113cd, L}, +{0x113ce, 0x113ce, NSM}, +{0x113cf, 0x113cf, L}, +{0x113d0, 0x113d0, NSM}, +{0x113d1, 0x113d1, L}, +{0x113d2, 0x113d2, NSM}, +{0x113d3, 0x113d5, L}, +{0x113d7, 0x113d8, L}, +{0x113e1, 0x113e2, NSM}, +{0x11400, 0x11437, L}, +{0x11438, 0x1143f, NSM}, +{0x11440, 0x11441, L}, +{0x11442, 0x11444, NSM}, +{0x11445, 0x11445, L}, +{0x11446, 0x11446, NSM}, +{0x11447, 0x1145b, L}, +{0x1145d, 0x1145d, L}, +{0x1145e, 0x1145e, NSM}, +{0x1145f, 0x11461, L}, +{0x11480, 0x114b2, L}, +{0x114b3, 0x114b8, NSM}, +{0x114b9, 0x114b9, L}, +{0x114ba, 0x114ba, NSM}, +{0x114bb, 0x114be, L}, +{0x114bf, 0x114c0, NSM}, +{0x114c1, 0x114c1, L}, +{0x114c2, 0x114c3, NSM}, +{0x114c4, 0x114c7, L}, +{0x114d0, 0x114d9, L}, +{0x11580, 0x115b1, L}, +{0x115b2, 0x115b5, NSM}, +{0x115b8, 0x115bb, L}, +{0x115bc, 0x115bd, NSM}, +{0x115be, 0x115be, L}, +{0x115bf, 0x115c0, NSM}, +{0x115c1, 0x115db, L}, +{0x115dc, 0x115dd, NSM}, +{0x11600, 0x11632, L}, +{0x11633, 0x1163a, NSM}, +{0x1163b, 0x1163c, L}, +{0x1163d, 0x1163d, NSM}, +{0x1163e, 0x1163e, L}, +{0x1163f, 0x11640, NSM}, +{0x11641, 0x11644, L}, +{0x11650, 0x11659, L}, +{0x11680, 0x116aa, L}, +{0x116ab, 0x116ab, NSM}, +{0x116ac, 0x116ac, L}, +{0x116ad, 0x116ad, NSM}, +{0x116ae, 0x116af, L}, +{0x116b0, 0x116b5, NSM}, +{0x116b6, 0x116b6, L}, +{0x116b7, 0x116b7, NSM}, +{0x116b8, 0x116b9, L}, +{0x116c0, 0x116c9, L}, +{0x116d0, 0x116e3, L}, +{0x11700, 0x1171a, L}, +{0x1171d, 0x1171d, NSM}, +{0x1171e, 0x1171e, L}, +{0x1171f, 0x1171f, NSM}, +{0x11720, 0x11721, L}, +{0x11722, 0x11725, NSM}, +{0x11726, 0x11726, L}, +{0x11727, 0x1172b, NSM}, +{0x11730, 0x11746, L}, +{0x11800, 0x1182e, L}, +{0x1182f, 0x11837, NSM}, +{0x11838, 0x11838, L}, +{0x11839, 0x1183a, NSM}, +{0x1183b, 0x1183b, L}, +{0x118a0, 0x118f2, L}, +{0x118ff, 0x11906, L}, +{0x11909, 0x11909, L}, +{0x1190c, 0x11913, L}, +{0x11915, 0x11916, L}, +{0x11918, 0x11935, L}, +{0x11937, 0x11938, L}, +{0x1193b, 0x1193c, NSM}, +{0x1193d, 0x1193d, L}, +{0x1193e, 0x1193e, NSM}, +{0x1193f, 0x11942, L}, +{0x11943, 0x11943, NSM}, +{0x11944, 0x11946, L}, +{0x11950, 0x11959, L}, +{0x119a0, 0x119a7, L}, +{0x119aa, 0x119d3, L}, +{0x119d4, 0x119d7, NSM}, +{0x119da, 0x119db, NSM}, +{0x119dc, 0x119df, L}, +{0x119e0, 0x119e0, NSM}, +{0x119e1, 0x119e4, L}, +{0x11a00, 0x11a00, L}, +{0x11a01, 0x11a06, NSM}, +{0x11a07, 0x11a08, L}, +{0x11a09, 0x11a0a, NSM}, +{0x11a0b, 0x11a32, L}, +{0x11a33, 0x11a38, NSM}, +{0x11a39, 0x11a3a, L}, +{0x11a3b, 0x11a3e, NSM}, +{0x11a3f, 0x11a46, L}, +{0x11a47, 0x11a47, NSM}, +{0x11a50, 0x11a50, L}, +{0x11a51, 0x11a56, NSM}, +{0x11a57, 0x11a58, L}, +{0x11a59, 0x11a5b, NSM}, +{0x11a5c, 0x11a89, L}, +{0x11a8a, 0x11a96, NSM}, +{0x11a97, 0x11a97, L}, +{0x11a98, 0x11a99, NSM}, +{0x11a9a, 0x11aa2, L}, +{0x11ab0, 0x11af8, L}, +{0x11b00, 0x11b09, L}, +{0x11bc0, 0x11be1, L}, +{0x11bf0, 0x11bf9, L}, +{0x11c00, 0x11c08, L}, +{0x11c0a, 0x11c2f, L}, +{0x11c30, 0x11c36, NSM}, +{0x11c38, 0x11c3d, NSM}, +{0x11c3e, 0x11c45, L}, +{0x11c50, 0x11c6c, L}, +{0x11c70, 0x11c8f, L}, +{0x11c92, 0x11ca7, NSM}, +{0x11ca9, 0x11ca9, L}, +{0x11caa, 0x11cb0, NSM}, +{0x11cb1, 0x11cb1, L}, +{0x11cb2, 0x11cb3, NSM}, +{0x11cb4, 0x11cb4, L}, +{0x11cb5, 0x11cb6, NSM}, +{0x11d00, 0x11d06, L}, +{0x11d08, 0x11d09, L}, +{0x11d0b, 0x11d30, L}, +{0x11d31, 0x11d36, NSM}, +{0x11d3a, 0x11d3a, NSM}, +{0x11d3c, 0x11d3d, NSM}, +{0x11d3f, 0x11d45, NSM}, +{0x11d46, 0x11d46, L}, +{0x11d47, 0x11d47, NSM}, +{0x11d50, 0x11d59, L}, +{0x11d60, 0x11d65, L}, +{0x11d67, 0x11d68, L}, +{0x11d6a, 0x11d8e, L}, +{0x11d90, 0x11d91, NSM}, +{0x11d93, 0x11d94, L}, +{0x11d95, 0x11d95, NSM}, +{0x11d96, 0x11d96, L}, +{0x11d97, 0x11d97, NSM}, +{0x11d98, 0x11d98, L}, +{0x11da0, 0x11da9, L}, +{0x11ee0, 0x11ef2, L}, +{0x11ef3, 0x11ef4, NSM}, +{0x11ef5, 0x11ef8, L}, +{0x11f00, 0x11f01, NSM}, +{0x11f02, 0x11f10, L}, +{0x11f12, 0x11f35, L}, +{0x11f36, 0x11f3a, NSM}, +{0x11f3e, 0x11f3f, L}, +{0x11f40, 0x11f40, NSM}, +{0x11f41, 0x11f41, L}, +{0x11f42, 0x11f42, NSM}, +{0x11f43, 0x11f59, L}, +{0x11f5a, 0x11f5a, NSM}, +{0x11fb0, 0x11fb0, L}, +{0x11fc0, 0x11fd4, L}, +{0x11fdd, 0x11fe0, ET}, +{0x11fff, 0x12399, L}, +{0x12400, 0x1246e, L}, +{0x12470, 0x12474, L}, +{0x12480, 0x12543, L}, +{0x12f90, 0x12ff2, L}, +{0x13000, 0x1343f, L}, +{0x13440, 0x13440, NSM}, +{0x13441, 0x13446, L}, +{0x13447, 0x13455, NSM}, +{0x13460, 0x143fa, L}, +{0x14400, 0x14646, L}, +{0x16100, 0x1611d, L}, +{0x1611e, 0x16129, NSM}, +{0x1612a, 0x1612c, L}, +{0x1612d, 0x1612f, NSM}, +{0x16130, 0x16139, L}, +{0x16800, 0x16a38, L}, +{0x16a40, 0x16a5e, L}, +{0x16a60, 0x16a69, L}, +{0x16a6e, 0x16abe, L}, +{0x16ac0, 0x16ac9, L}, +{0x16ad0, 0x16aed, L}, +{0x16af0, 0x16af4, NSM}, +{0x16af5, 0x16af5, L}, +{0x16b00, 0x16b2f, L}, +{0x16b30, 0x16b36, NSM}, +{0x16b37, 0x16b45, L}, +{0x16b50, 0x16b59, L}, +{0x16b5b, 0x16b61, L}, +{0x16b63, 0x16b77, L}, +{0x16b7d, 0x16b8f, L}, +{0x16d40, 0x16d79, L}, +{0x16e40, 0x16e9a, L}, +{0x16f00, 0x16f4a, L}, +{0x16f4f, 0x16f4f, NSM}, +{0x16f50, 0x16f87, L}, +{0x16f8f, 0x16f92, NSM}, +{0x16f93, 0x16f9f, L}, +{0x16fe0, 0x16fe1, L}, +{0x16fe3, 0x16fe3, L}, +{0x16fe4, 0x16fe4, NSM}, +{0x16ff0, 0x16ff1, L}, +{0x17000, 0x187f7, L}, +{0x18800, 0x18cd5, L}, +{0x18cff, 0x18d08, L}, +{0x1aff0, 0x1aff3, L}, +{0x1aff5, 0x1affb, L}, +{0x1affd, 0x1affe, L}, +{0x1b000, 0x1b122, L}, +{0x1b132, 0x1b132, L}, +{0x1b150, 0x1b152, L}, +{0x1b155, 0x1b155, L}, +{0x1b164, 0x1b167, L}, +{0x1b170, 0x1b2fb, L}, +{0x1bc00, 0x1bc6a, L}, +{0x1bc70, 0x1bc7c, L}, +{0x1bc80, 0x1bc88, L}, +{0x1bc90, 0x1bc99, L}, +{0x1bc9c, 0x1bc9c, L}, +{0x1bc9d, 0x1bc9e, NSM}, +{0x1bc9f, 0x1bc9f, L}, +{0x1bca0, 0x1bca3, BN}, +{0x1ccd6, 0x1ccef, L}, +{0x1ccf0, 0x1ccf9, EN}, +{0x1cf00, 0x1cf2d, NSM}, +{0x1cf30, 0x1cf46, NSM}, +{0x1cf50, 0x1cfc3, L}, +{0x1d000, 0x1d0f5, L}, +{0x1d100, 0x1d126, L}, +{0x1d129, 0x1d166, L}, +{0x1d167, 0x1d169, NSM}, +{0x1d16a, 0x1d172, L}, +{0x1d173, 0x1d17a, BN}, +{0x1d17b, 0x1d182, NSM}, +{0x1d183, 0x1d184, L}, +{0x1d185, 0x1d18b, NSM}, +{0x1d18c, 0x1d1a9, L}, +{0x1d1aa, 0x1d1ad, NSM}, +{0x1d1ae, 0x1d1e8, L}, +{0x1d242, 0x1d244, NSM}, +{0x1d2c0, 0x1d2d3, L}, +{0x1d2e0, 0x1d2f3, L}, +{0x1d360, 0x1d378, L}, +{0x1d400, 0x1d454, L}, +{0x1d456, 0x1d49c, L}, +{0x1d49e, 0x1d49f, L}, +{0x1d4a2, 0x1d4a2, L}, +{0x1d4a5, 0x1d4a6, L}, +{0x1d4a9, 0x1d4ac, L}, +{0x1d4ae, 0x1d4b9, L}, +{0x1d4bb, 0x1d4bb, L}, +{0x1d4bd, 0x1d4c3, L}, +{0x1d4c5, 0x1d505, L}, +{0x1d507, 0x1d50a, L}, +{0x1d50d, 0x1d514, L}, +{0x1d516, 0x1d51c, L}, +{0x1d51e, 0x1d539, L}, +{0x1d53b, 0x1d53e, L}, +{0x1d540, 0x1d544, L}, +{0x1d546, 0x1d546, L}, +{0x1d54a, 0x1d550, L}, +{0x1d552, 0x1d6a5, L}, +{0x1d6a8, 0x1d6c0, L}, +{0x1d6c2, 0x1d6da, L}, +{0x1d6dc, 0x1d6fa, L}, +{0x1d6fc, 0x1d714, L}, +{0x1d716, 0x1d734, L}, +{0x1d736, 0x1d74e, L}, +{0x1d750, 0x1d76e, L}, +{0x1d770, 0x1d788, L}, +{0x1d78a, 0x1d7a8, L}, +{0x1d7aa, 0x1d7c2, L}, +{0x1d7c4, 0x1d7cb, L}, +{0x1d7ce, 0x1d7ff, EN}, +{0x1d800, 0x1d9ff, L}, +{0x1da00, 0x1da36, NSM}, +{0x1da37, 0x1da3a, L}, +{0x1da3b, 0x1da6c, NSM}, +{0x1da6d, 0x1da74, L}, +{0x1da75, 0x1da75, NSM}, +{0x1da76, 0x1da83, L}, +{0x1da84, 0x1da84, NSM}, +{0x1da85, 0x1da8b, L}, +{0x1da9b, 0x1da9f, NSM}, +{0x1daa1, 0x1daaf, NSM}, +{0x1df00, 0x1df1e, L}, +{0x1df25, 0x1df2a, L}, +{0x1e000, 0x1e006, NSM}, +{0x1e008, 0x1e018, NSM}, +{0x1e01b, 0x1e021, NSM}, +{0x1e023, 0x1e024, NSM}, +{0x1e026, 0x1e02a, NSM}, +{0x1e030, 0x1e06d, L}, +{0x1e08f, 0x1e08f, NSM}, +{0x1e100, 0x1e12c, L}, +{0x1e130, 0x1e136, NSM}, +{0x1e137, 0x1e13d, L}, +{0x1e140, 0x1e149, L}, +{0x1e14e, 0x1e14f, L}, +{0x1e290, 0x1e2ad, L}, +{0x1e2ae, 0x1e2ae, NSM}, +{0x1e2c0, 0x1e2eb, L}, +{0x1e2ec, 0x1e2ef, NSM}, +{0x1e2f0, 0x1e2f9, L}, +{0x1e2ff, 0x1e2ff, ET}, +{0x1e4d0, 0x1e4eb, L}, +{0x1e4ec, 0x1e4ef, NSM}, +{0x1e4f0, 0x1e4f9, L}, +{0x1e5d0, 0x1e5ed, L}, +{0x1e5ee, 0x1e5ef, NSM}, +{0x1e5f0, 0x1e5fa, L}, +{0x1e5ff, 0x1e5ff, L}, +{0x1e7e0, 0x1e7e6, L}, +{0x1e7e8, 0x1e7eb, L}, +{0x1e7ed, 0x1e7ee, L}, +{0x1e7f0, 0x1e7fe, L}, +{0x1e800, 0x1e8c4, R}, +{0x1e8c7, 0x1e8cf, R}, +{0x1e8d0, 0x1e8d6, NSM}, +{0x1e900, 0x1e943, R}, +{0x1e944, 0x1e94a, NSM}, +{0x1e94b, 0x1e94b, R}, +{0x1e950, 0x1e959, R}, +{0x1e95e, 0x1e95f, R}, +{0x1ec71, 0x1ecb4, AL}, +{0x1ed01, 0x1ed3d, AL}, +{0x1ee00, 0x1ee03, AL}, +{0x1ee05, 0x1ee1f, AL}, +{0x1ee21, 0x1ee22, AL}, +{0x1ee24, 0x1ee24, AL}, +{0x1ee27, 0x1ee27, AL}, +{0x1ee29, 0x1ee32, AL}, +{0x1ee34, 0x1ee37, AL}, +{0x1ee39, 0x1ee39, AL}, +{0x1ee3b, 0x1ee3b, AL}, +{0x1ee42, 0x1ee42, AL}, +{0x1ee47, 0x1ee47, AL}, +{0x1ee49, 0x1ee49, AL}, +{0x1ee4b, 0x1ee4b, AL}, +{0x1ee4d, 0x1ee4f, AL}, +{0x1ee51, 0x1ee52, AL}, +{0x1ee54, 0x1ee54, AL}, +{0x1ee57, 0x1ee57, AL}, +{0x1ee59, 0x1ee59, AL}, +{0x1ee5b, 0x1ee5b, AL}, +{0x1ee5d, 0x1ee5d, AL}, +{0x1ee5f, 0x1ee5f, AL}, +{0x1ee61, 0x1ee62, AL}, +{0x1ee64, 0x1ee64, AL}, +{0x1ee67, 0x1ee6a, AL}, +{0x1ee6c, 0x1ee72, AL}, +{0x1ee74, 0x1ee77, AL}, +{0x1ee79, 0x1ee7c, AL}, +{0x1ee7e, 0x1ee7e, AL}, +{0x1ee80, 0x1ee89, AL}, +{0x1ee8b, 0x1ee9b, AL}, +{0x1eea1, 0x1eea3, AL}, +{0x1eea5, 0x1eea9, AL}, +{0x1eeab, 0x1eebb, AL}, +{0x1f100, 0x1f10a, EN}, +{0x1f110, 0x1f12e, L}, +{0x1f130, 0x1f169, L}, +{0x1f170, 0x1f1ac, L}, +{0x1f1e6, 0x1f202, L}, +{0x1f210, 0x1f23b, L}, +{0x1f240, 0x1f248, L}, +{0x1f250, 0x1f251, L}, +{0x1fbf0, 0x1fbf9, EN}, +{0x20000, 0x2a6df, L}, +{0x2a700, 0x2b739, L}, +{0x2b740, 0x2b81d, L}, +{0x2b820, 0x2cea1, L}, +{0x2ceb0, 0x2ebe0, L}, +{0x2ebf0, 0x2ee5d, L}, +{0x2f800, 0x2fa1d, L}, +{0x30000, 0x3134a, L}, +{0x31350, 0x323af, L}, +{0xe0001, 0xe0001, BN}, +{0xe0020, 0xe007f, BN}, +{0xe0100, 0xe01ef, NSM}, +{0xf0000, 0xffffd, L}, +{0x100000, 0x10fffd, L}, diff --git a/unicode/canonical_comp.h b/unicode/canonical_comp.h new file mode 100644 index 00000000..14db8050 --- /dev/null +++ b/unicode/canonical_comp.h @@ -0,0 +1,970 @@ +/* + * Autogenerated by read_ucd.py from The Unicode Standard 16.0.0 + * + * List the pairs of Unicode characters that canonically recompose to a + * single character in NFC. + * + * Used by utils/unicode-norm.c. + */ + +{0x003c, 0x0338, 0x226e}, +{0x003d, 0x0338, 0x2260}, +{0x003e, 0x0338, 0x226f}, +{0x0041, 0x0300, 0x00c0}, +{0x0041, 0x0301, 0x00c1}, +{0x0041, 0x0302, 0x00c2}, +{0x0041, 0x0303, 0x00c3}, +{0x0041, 0x0304, 0x0100}, +{0x0041, 0x0306, 0x0102}, +{0x0041, 0x0307, 0x0226}, +{0x0041, 0x0308, 0x00c4}, +{0x0041, 0x0309, 0x1ea2}, +{0x0041, 0x030a, 0x00c5}, +{0x0041, 0x030c, 0x01cd}, +{0x0041, 0x030f, 0x0200}, +{0x0041, 0x0311, 0x0202}, +{0x0041, 0x0323, 0x1ea0}, +{0x0041, 0x0325, 0x1e00}, +{0x0041, 0x0328, 0x0104}, +{0x0042, 0x0307, 0x1e02}, +{0x0042, 0x0323, 0x1e04}, +{0x0042, 0x0331, 0x1e06}, +{0x0043, 0x0301, 0x0106}, +{0x0043, 0x0302, 0x0108}, +{0x0043, 0x0307, 0x010a}, +{0x0043, 0x030c, 0x010c}, +{0x0043, 0x0327, 0x00c7}, +{0x0044, 0x0307, 0x1e0a}, +{0x0044, 0x030c, 0x010e}, +{0x0044, 0x0323, 0x1e0c}, +{0x0044, 0x0327, 0x1e10}, +{0x0044, 0x032d, 0x1e12}, +{0x0044, 0x0331, 0x1e0e}, +{0x0045, 0x0300, 0x00c8}, +{0x0045, 0x0301, 0x00c9}, +{0x0045, 0x0302, 0x00ca}, +{0x0045, 0x0303, 0x1ebc}, +{0x0045, 0x0304, 0x0112}, +{0x0045, 0x0306, 0x0114}, +{0x0045, 0x0307, 0x0116}, +{0x0045, 0x0308, 0x00cb}, +{0x0045, 0x0309, 0x1eba}, +{0x0045, 0x030c, 0x011a}, +{0x0045, 0x030f, 0x0204}, +{0x0045, 0x0311, 0x0206}, +{0x0045, 0x0323, 0x1eb8}, +{0x0045, 0x0327, 0x0228}, +{0x0045, 0x0328, 0x0118}, +{0x0045, 0x032d, 0x1e18}, +{0x0045, 0x0330, 0x1e1a}, +{0x0046, 0x0307, 0x1e1e}, +{0x0047, 0x0301, 0x01f4}, +{0x0047, 0x0302, 0x011c}, +{0x0047, 0x0304, 0x1e20}, +{0x0047, 0x0306, 0x011e}, +{0x0047, 0x0307, 0x0120}, +{0x0047, 0x030c, 0x01e6}, +{0x0047, 0x0327, 0x0122}, +{0x0048, 0x0302, 0x0124}, +{0x0048, 0x0307, 0x1e22}, +{0x0048, 0x0308, 0x1e26}, +{0x0048, 0x030c, 0x021e}, +{0x0048, 0x0323, 0x1e24}, +{0x0048, 0x0327, 0x1e28}, +{0x0048, 0x032e, 0x1e2a}, +{0x0049, 0x0300, 0x00cc}, +{0x0049, 0x0301, 0x00cd}, +{0x0049, 0x0302, 0x00ce}, +{0x0049, 0x0303, 0x0128}, +{0x0049, 0x0304, 0x012a}, +{0x0049, 0x0306, 0x012c}, +{0x0049, 0x0307, 0x0130}, +{0x0049, 0x0308, 0x00cf}, +{0x0049, 0x0309, 0x1ec8}, +{0x0049, 0x030c, 0x01cf}, +{0x0049, 0x030f, 0x0208}, +{0x0049, 0x0311, 0x020a}, +{0x0049, 0x0323, 0x1eca}, +{0x0049, 0x0328, 0x012e}, +{0x0049, 0x0330, 0x1e2c}, +{0x004a, 0x0302, 0x0134}, +{0x004b, 0x0301, 0x1e30}, +{0x004b, 0x030c, 0x01e8}, +{0x004b, 0x0323, 0x1e32}, +{0x004b, 0x0327, 0x0136}, +{0x004b, 0x0331, 0x1e34}, +{0x004c, 0x0301, 0x0139}, +{0x004c, 0x030c, 0x013d}, +{0x004c, 0x0323, 0x1e36}, +{0x004c, 0x0327, 0x013b}, +{0x004c, 0x032d, 0x1e3c}, +{0x004c, 0x0331, 0x1e3a}, +{0x004d, 0x0301, 0x1e3e}, +{0x004d, 0x0307, 0x1e40}, +{0x004d, 0x0323, 0x1e42}, +{0x004e, 0x0300, 0x01f8}, +{0x004e, 0x0301, 0x0143}, +{0x004e, 0x0303, 0x00d1}, +{0x004e, 0x0307, 0x1e44}, +{0x004e, 0x030c, 0x0147}, +{0x004e, 0x0323, 0x1e46}, +{0x004e, 0x0327, 0x0145}, +{0x004e, 0x032d, 0x1e4a}, +{0x004e, 0x0331, 0x1e48}, +{0x004f, 0x0300, 0x00d2}, +{0x004f, 0x0301, 0x00d3}, +{0x004f, 0x0302, 0x00d4}, +{0x004f, 0x0303, 0x00d5}, +{0x004f, 0x0304, 0x014c}, +{0x004f, 0x0306, 0x014e}, +{0x004f, 0x0307, 0x022e}, +{0x004f, 0x0308, 0x00d6}, +{0x004f, 0x0309, 0x1ece}, +{0x004f, 0x030b, 0x0150}, +{0x004f, 0x030c, 0x01d1}, +{0x004f, 0x030f, 0x020c}, +{0x004f, 0x0311, 0x020e}, +{0x004f, 0x031b, 0x01a0}, +{0x004f, 0x0323, 0x1ecc}, +{0x004f, 0x0328, 0x01ea}, +{0x0050, 0x0301, 0x1e54}, +{0x0050, 0x0307, 0x1e56}, +{0x0052, 0x0301, 0x0154}, +{0x0052, 0x0307, 0x1e58}, +{0x0052, 0x030c, 0x0158}, +{0x0052, 0x030f, 0x0210}, +{0x0052, 0x0311, 0x0212}, +{0x0052, 0x0323, 0x1e5a}, +{0x0052, 0x0327, 0x0156}, +{0x0052, 0x0331, 0x1e5e}, +{0x0053, 0x0301, 0x015a}, +{0x0053, 0x0302, 0x015c}, +{0x0053, 0x0307, 0x1e60}, +{0x0053, 0x030c, 0x0160}, +{0x0053, 0x0323, 0x1e62}, +{0x0053, 0x0326, 0x0218}, +{0x0053, 0x0327, 0x015e}, +{0x0054, 0x0307, 0x1e6a}, +{0x0054, 0x030c, 0x0164}, +{0x0054, 0x0323, 0x1e6c}, +{0x0054, 0x0326, 0x021a}, +{0x0054, 0x0327, 0x0162}, +{0x0054, 0x032d, 0x1e70}, +{0x0054, 0x0331, 0x1e6e}, +{0x0055, 0x0300, 0x00d9}, +{0x0055, 0x0301, 0x00da}, +{0x0055, 0x0302, 0x00db}, +{0x0055, 0x0303, 0x0168}, +{0x0055, 0x0304, 0x016a}, +{0x0055, 0x0306, 0x016c}, +{0x0055, 0x0308, 0x00dc}, +{0x0055, 0x0309, 0x1ee6}, +{0x0055, 0x030a, 0x016e}, +{0x0055, 0x030b, 0x0170}, +{0x0055, 0x030c, 0x01d3}, +{0x0055, 0x030f, 0x0214}, +{0x0055, 0x0311, 0x0216}, +{0x0055, 0x031b, 0x01af}, +{0x0055, 0x0323, 0x1ee4}, +{0x0055, 0x0324, 0x1e72}, +{0x0055, 0x0328, 0x0172}, +{0x0055, 0x032d, 0x1e76}, +{0x0055, 0x0330, 0x1e74}, +{0x0056, 0x0303, 0x1e7c}, +{0x0056, 0x0323, 0x1e7e}, +{0x0057, 0x0300, 0x1e80}, +{0x0057, 0x0301, 0x1e82}, +{0x0057, 0x0302, 0x0174}, +{0x0057, 0x0307, 0x1e86}, +{0x0057, 0x0308, 0x1e84}, +{0x0057, 0x0323, 0x1e88}, +{0x0058, 0x0307, 0x1e8a}, +{0x0058, 0x0308, 0x1e8c}, +{0x0059, 0x0300, 0x1ef2}, +{0x0059, 0x0301, 0x00dd}, +{0x0059, 0x0302, 0x0176}, +{0x0059, 0x0303, 0x1ef8}, +{0x0059, 0x0304, 0x0232}, +{0x0059, 0x0307, 0x1e8e}, +{0x0059, 0x0308, 0x0178}, +{0x0059, 0x0309, 0x1ef6}, +{0x0059, 0x0323, 0x1ef4}, +{0x005a, 0x0301, 0x0179}, +{0x005a, 0x0302, 0x1e90}, +{0x005a, 0x0307, 0x017b}, +{0x005a, 0x030c, 0x017d}, +{0x005a, 0x0323, 0x1e92}, +{0x005a, 0x0331, 0x1e94}, +{0x0061, 0x0300, 0x00e0}, +{0x0061, 0x0301, 0x00e1}, +{0x0061, 0x0302, 0x00e2}, +{0x0061, 0x0303, 0x00e3}, +{0x0061, 0x0304, 0x0101}, +{0x0061, 0x0306, 0x0103}, +{0x0061, 0x0307, 0x0227}, +{0x0061, 0x0308, 0x00e4}, +{0x0061, 0x0309, 0x1ea3}, +{0x0061, 0x030a, 0x00e5}, +{0x0061, 0x030c, 0x01ce}, +{0x0061, 0x030f, 0x0201}, +{0x0061, 0x0311, 0x0203}, +{0x0061, 0x0323, 0x1ea1}, +{0x0061, 0x0325, 0x1e01}, +{0x0061, 0x0328, 0x0105}, +{0x0062, 0x0307, 0x1e03}, +{0x0062, 0x0323, 0x1e05}, +{0x0062, 0x0331, 0x1e07}, +{0x0063, 0x0301, 0x0107}, +{0x0063, 0x0302, 0x0109}, +{0x0063, 0x0307, 0x010b}, +{0x0063, 0x030c, 0x010d}, +{0x0063, 0x0327, 0x00e7}, +{0x0064, 0x0307, 0x1e0b}, +{0x0064, 0x030c, 0x010f}, +{0x0064, 0x0323, 0x1e0d}, +{0x0064, 0x0327, 0x1e11}, +{0x0064, 0x032d, 0x1e13}, +{0x0064, 0x0331, 0x1e0f}, +{0x0065, 0x0300, 0x00e8}, +{0x0065, 0x0301, 0x00e9}, +{0x0065, 0x0302, 0x00ea}, +{0x0065, 0x0303, 0x1ebd}, +{0x0065, 0x0304, 0x0113}, +{0x0065, 0x0306, 0x0115}, +{0x0065, 0x0307, 0x0117}, +{0x0065, 0x0308, 0x00eb}, +{0x0065, 0x0309, 0x1ebb}, +{0x0065, 0x030c, 0x011b}, +{0x0065, 0x030f, 0x0205}, +{0x0065, 0x0311, 0x0207}, +{0x0065, 0x0323, 0x1eb9}, +{0x0065, 0x0327, 0x0229}, +{0x0065, 0x0328, 0x0119}, +{0x0065, 0x032d, 0x1e19}, +{0x0065, 0x0330, 0x1e1b}, +{0x0066, 0x0307, 0x1e1f}, +{0x0067, 0x0301, 0x01f5}, +{0x0067, 0x0302, 0x011d}, +{0x0067, 0x0304, 0x1e21}, +{0x0067, 0x0306, 0x011f}, +{0x0067, 0x0307, 0x0121}, +{0x0067, 0x030c, 0x01e7}, +{0x0067, 0x0327, 0x0123}, +{0x0068, 0x0302, 0x0125}, +{0x0068, 0x0307, 0x1e23}, +{0x0068, 0x0308, 0x1e27}, +{0x0068, 0x030c, 0x021f}, +{0x0068, 0x0323, 0x1e25}, +{0x0068, 0x0327, 0x1e29}, +{0x0068, 0x032e, 0x1e2b}, +{0x0068, 0x0331, 0x1e96}, +{0x0069, 0x0300, 0x00ec}, +{0x0069, 0x0301, 0x00ed}, +{0x0069, 0x0302, 0x00ee}, +{0x0069, 0x0303, 0x0129}, +{0x0069, 0x0304, 0x012b}, +{0x0069, 0x0306, 0x012d}, +{0x0069, 0x0308, 0x00ef}, +{0x0069, 0x0309, 0x1ec9}, +{0x0069, 0x030c, 0x01d0}, +{0x0069, 0x030f, 0x0209}, +{0x0069, 0x0311, 0x020b}, +{0x0069, 0x0323, 0x1ecb}, +{0x0069, 0x0328, 0x012f}, +{0x0069, 0x0330, 0x1e2d}, +{0x006a, 0x0302, 0x0135}, +{0x006a, 0x030c, 0x01f0}, +{0x006b, 0x0301, 0x1e31}, +{0x006b, 0x030c, 0x01e9}, +{0x006b, 0x0323, 0x1e33}, +{0x006b, 0x0327, 0x0137}, +{0x006b, 0x0331, 0x1e35}, +{0x006c, 0x0301, 0x013a}, +{0x006c, 0x030c, 0x013e}, +{0x006c, 0x0323, 0x1e37}, +{0x006c, 0x0327, 0x013c}, +{0x006c, 0x032d, 0x1e3d}, +{0x006c, 0x0331, 0x1e3b}, +{0x006d, 0x0301, 0x1e3f}, +{0x006d, 0x0307, 0x1e41}, +{0x006d, 0x0323, 0x1e43}, +{0x006e, 0x0300, 0x01f9}, +{0x006e, 0x0301, 0x0144}, +{0x006e, 0x0303, 0x00f1}, +{0x006e, 0x0307, 0x1e45}, +{0x006e, 0x030c, 0x0148}, +{0x006e, 0x0323, 0x1e47}, +{0x006e, 0x0327, 0x0146}, +{0x006e, 0x032d, 0x1e4b}, +{0x006e, 0x0331, 0x1e49}, +{0x006f, 0x0300, 0x00f2}, +{0x006f, 0x0301, 0x00f3}, +{0x006f, 0x0302, 0x00f4}, +{0x006f, 0x0303, 0x00f5}, +{0x006f, 0x0304, 0x014d}, +{0x006f, 0x0306, 0x014f}, +{0x006f, 0x0307, 0x022f}, +{0x006f, 0x0308, 0x00f6}, +{0x006f, 0x0309, 0x1ecf}, +{0x006f, 0x030b, 0x0151}, +{0x006f, 0x030c, 0x01d2}, +{0x006f, 0x030f, 0x020d}, +{0x006f, 0x0311, 0x020f}, +{0x006f, 0x031b, 0x01a1}, +{0x006f, 0x0323, 0x1ecd}, +{0x006f, 0x0328, 0x01eb}, +{0x0070, 0x0301, 0x1e55}, +{0x0070, 0x0307, 0x1e57}, +{0x0072, 0x0301, 0x0155}, +{0x0072, 0x0307, 0x1e59}, +{0x0072, 0x030c, 0x0159}, +{0x0072, 0x030f, 0x0211}, +{0x0072, 0x0311, 0x0213}, +{0x0072, 0x0323, 0x1e5b}, +{0x0072, 0x0327, 0x0157}, +{0x0072, 0x0331, 0x1e5f}, +{0x0073, 0x0301, 0x015b}, +{0x0073, 0x0302, 0x015d}, +{0x0073, 0x0307, 0x1e61}, +{0x0073, 0x030c, 0x0161}, +{0x0073, 0x0323, 0x1e63}, +{0x0073, 0x0326, 0x0219}, +{0x0073, 0x0327, 0x015f}, +{0x0074, 0x0307, 0x1e6b}, +{0x0074, 0x0308, 0x1e97}, +{0x0074, 0x030c, 0x0165}, +{0x0074, 0x0323, 0x1e6d}, +{0x0074, 0x0326, 0x021b}, +{0x0074, 0x0327, 0x0163}, +{0x0074, 0x032d, 0x1e71}, +{0x0074, 0x0331, 0x1e6f}, +{0x0075, 0x0300, 0x00f9}, +{0x0075, 0x0301, 0x00fa}, +{0x0075, 0x0302, 0x00fb}, +{0x0075, 0x0303, 0x0169}, +{0x0075, 0x0304, 0x016b}, +{0x0075, 0x0306, 0x016d}, +{0x0075, 0x0308, 0x00fc}, +{0x0075, 0x0309, 0x1ee7}, +{0x0075, 0x030a, 0x016f}, +{0x0075, 0x030b, 0x0171}, +{0x0075, 0x030c, 0x01d4}, +{0x0075, 0x030f, 0x0215}, +{0x0075, 0x0311, 0x0217}, +{0x0075, 0x031b, 0x01b0}, +{0x0075, 0x0323, 0x1ee5}, +{0x0075, 0x0324, 0x1e73}, +{0x0075, 0x0328, 0x0173}, +{0x0075, 0x032d, 0x1e77}, +{0x0075, 0x0330, 0x1e75}, +{0x0076, 0x0303, 0x1e7d}, +{0x0076, 0x0323, 0x1e7f}, +{0x0077, 0x0300, 0x1e81}, +{0x0077, 0x0301, 0x1e83}, +{0x0077, 0x0302, 0x0175}, +{0x0077, 0x0307, 0x1e87}, +{0x0077, 0x0308, 0x1e85}, +{0x0077, 0x030a, 0x1e98}, +{0x0077, 0x0323, 0x1e89}, +{0x0078, 0x0307, 0x1e8b}, +{0x0078, 0x0308, 0x1e8d}, +{0x0079, 0x0300, 0x1ef3}, +{0x0079, 0x0301, 0x00fd}, +{0x0079, 0x0302, 0x0177}, +{0x0079, 0x0303, 0x1ef9}, +{0x0079, 0x0304, 0x0233}, +{0x0079, 0x0307, 0x1e8f}, +{0x0079, 0x0308, 0x00ff}, +{0x0079, 0x0309, 0x1ef7}, +{0x0079, 0x030a, 0x1e99}, +{0x0079, 0x0323, 0x1ef5}, +{0x007a, 0x0301, 0x017a}, +{0x007a, 0x0302, 0x1e91}, +{0x007a, 0x0307, 0x017c}, +{0x007a, 0x030c, 0x017e}, +{0x007a, 0x0323, 0x1e93}, +{0x007a, 0x0331, 0x1e95}, +{0x00a8, 0x0300, 0x1fed}, +{0x00a8, 0x0301, 0x0385}, +{0x00a8, 0x0342, 0x1fc1}, +{0x00c2, 0x0300, 0x1ea6}, +{0x00c2, 0x0301, 0x1ea4}, +{0x00c2, 0x0303, 0x1eaa}, +{0x00c2, 0x0309, 0x1ea8}, +{0x00c4, 0x0304, 0x01de}, +{0x00c5, 0x0301, 0x01fa}, +{0x00c6, 0x0301, 0x01fc}, +{0x00c6, 0x0304, 0x01e2}, +{0x00c7, 0x0301, 0x1e08}, +{0x00ca, 0x0300, 0x1ec0}, +{0x00ca, 0x0301, 0x1ebe}, +{0x00ca, 0x0303, 0x1ec4}, +{0x00ca, 0x0309, 0x1ec2}, +{0x00cf, 0x0301, 0x1e2e}, +{0x00d4, 0x0300, 0x1ed2}, +{0x00d4, 0x0301, 0x1ed0}, +{0x00d4, 0x0303, 0x1ed6}, +{0x00d4, 0x0309, 0x1ed4}, +{0x00d5, 0x0301, 0x1e4c}, +{0x00d5, 0x0304, 0x022c}, +{0x00d5, 0x0308, 0x1e4e}, +{0x00d6, 0x0304, 0x022a}, +{0x00d8, 0x0301, 0x01fe}, +{0x00dc, 0x0300, 0x01db}, +{0x00dc, 0x0301, 0x01d7}, +{0x00dc, 0x0304, 0x01d5}, +{0x00dc, 0x030c, 0x01d9}, +{0x00e2, 0x0300, 0x1ea7}, +{0x00e2, 0x0301, 0x1ea5}, +{0x00e2, 0x0303, 0x1eab}, +{0x00e2, 0x0309, 0x1ea9}, +{0x00e4, 0x0304, 0x01df}, +{0x00e5, 0x0301, 0x01fb}, +{0x00e6, 0x0301, 0x01fd}, +{0x00e6, 0x0304, 0x01e3}, +{0x00e7, 0x0301, 0x1e09}, +{0x00ea, 0x0300, 0x1ec1}, +{0x00ea, 0x0301, 0x1ebf}, +{0x00ea, 0x0303, 0x1ec5}, +{0x00ea, 0x0309, 0x1ec3}, +{0x00ef, 0x0301, 0x1e2f}, +{0x00f4, 0x0300, 0x1ed3}, +{0x00f4, 0x0301, 0x1ed1}, +{0x00f4, 0x0303, 0x1ed7}, +{0x00f4, 0x0309, 0x1ed5}, +{0x00f5, 0x0301, 0x1e4d}, +{0x00f5, 0x0304, 0x022d}, +{0x00f5, 0x0308, 0x1e4f}, +{0x00f6, 0x0304, 0x022b}, +{0x00f8, 0x0301, 0x01ff}, +{0x00fc, 0x0300, 0x01dc}, +{0x00fc, 0x0301, 0x01d8}, +{0x00fc, 0x0304, 0x01d6}, +{0x00fc, 0x030c, 0x01da}, +{0x0102, 0x0300, 0x1eb0}, +{0x0102, 0x0301, 0x1eae}, +{0x0102, 0x0303, 0x1eb4}, +{0x0102, 0x0309, 0x1eb2}, +{0x0103, 0x0300, 0x1eb1}, +{0x0103, 0x0301, 0x1eaf}, +{0x0103, 0x0303, 0x1eb5}, +{0x0103, 0x0309, 0x1eb3}, +{0x0112, 0x0300, 0x1e14}, +{0x0112, 0x0301, 0x1e16}, +{0x0113, 0x0300, 0x1e15}, +{0x0113, 0x0301, 0x1e17}, +{0x014c, 0x0300, 0x1e50}, +{0x014c, 0x0301, 0x1e52}, +{0x014d, 0x0300, 0x1e51}, +{0x014d, 0x0301, 0x1e53}, +{0x015a, 0x0307, 0x1e64}, +{0x015b, 0x0307, 0x1e65}, +{0x0160, 0x0307, 0x1e66}, +{0x0161, 0x0307, 0x1e67}, +{0x0168, 0x0301, 0x1e78}, +{0x0169, 0x0301, 0x1e79}, +{0x016a, 0x0308, 0x1e7a}, +{0x016b, 0x0308, 0x1e7b}, +{0x017f, 0x0307, 0x1e9b}, +{0x01a0, 0x0300, 0x1edc}, +{0x01a0, 0x0301, 0x1eda}, +{0x01a0, 0x0303, 0x1ee0}, +{0x01a0, 0x0309, 0x1ede}, +{0x01a0, 0x0323, 0x1ee2}, +{0x01a1, 0x0300, 0x1edd}, +{0x01a1, 0x0301, 0x1edb}, +{0x01a1, 0x0303, 0x1ee1}, +{0x01a1, 0x0309, 0x1edf}, +{0x01a1, 0x0323, 0x1ee3}, +{0x01af, 0x0300, 0x1eea}, +{0x01af, 0x0301, 0x1ee8}, +{0x01af, 0x0303, 0x1eee}, +{0x01af, 0x0309, 0x1eec}, +{0x01af, 0x0323, 0x1ef0}, +{0x01b0, 0x0300, 0x1eeb}, +{0x01b0, 0x0301, 0x1ee9}, +{0x01b0, 0x0303, 0x1eef}, +{0x01b0, 0x0309, 0x1eed}, +{0x01b0, 0x0323, 0x1ef1}, +{0x01b7, 0x030c, 0x01ee}, +{0x01ea, 0x0304, 0x01ec}, +{0x01eb, 0x0304, 0x01ed}, +{0x0226, 0x0304, 0x01e0}, +{0x0227, 0x0304, 0x01e1}, +{0x0228, 0x0306, 0x1e1c}, +{0x0229, 0x0306, 0x1e1d}, +{0x022e, 0x0304, 0x0230}, +{0x022f, 0x0304, 0x0231}, +{0x0292, 0x030c, 0x01ef}, +{0x0391, 0x0300, 0x1fba}, +{0x0391, 0x0301, 0x0386}, +{0x0391, 0x0304, 0x1fb9}, +{0x0391, 0x0306, 0x1fb8}, +{0x0391, 0x0313, 0x1f08}, +{0x0391, 0x0314, 0x1f09}, +{0x0391, 0x0345, 0x1fbc}, +{0x0395, 0x0300, 0x1fc8}, +{0x0395, 0x0301, 0x0388}, +{0x0395, 0x0313, 0x1f18}, +{0x0395, 0x0314, 0x1f19}, +{0x0397, 0x0300, 0x1fca}, +{0x0397, 0x0301, 0x0389}, +{0x0397, 0x0313, 0x1f28}, +{0x0397, 0x0314, 0x1f29}, +{0x0397, 0x0345, 0x1fcc}, +{0x0399, 0x0300, 0x1fda}, +{0x0399, 0x0301, 0x038a}, +{0x0399, 0x0304, 0x1fd9}, +{0x0399, 0x0306, 0x1fd8}, +{0x0399, 0x0308, 0x03aa}, +{0x0399, 0x0313, 0x1f38}, +{0x0399, 0x0314, 0x1f39}, +{0x039f, 0x0300, 0x1ff8}, +{0x039f, 0x0301, 0x038c}, +{0x039f, 0x0313, 0x1f48}, +{0x039f, 0x0314, 0x1f49}, +{0x03a1, 0x0314, 0x1fec}, +{0x03a5, 0x0300, 0x1fea}, +{0x03a5, 0x0301, 0x038e}, +{0x03a5, 0x0304, 0x1fe9}, +{0x03a5, 0x0306, 0x1fe8}, +{0x03a5, 0x0308, 0x03ab}, +{0x03a5, 0x0314, 0x1f59}, +{0x03a9, 0x0300, 0x1ffa}, +{0x03a9, 0x0301, 0x038f}, +{0x03a9, 0x0313, 0x1f68}, +{0x03a9, 0x0314, 0x1f69}, +{0x03a9, 0x0345, 0x1ffc}, +{0x03ac, 0x0345, 0x1fb4}, +{0x03ae, 0x0345, 0x1fc4}, +{0x03b1, 0x0300, 0x1f70}, +{0x03b1, 0x0301, 0x03ac}, +{0x03b1, 0x0304, 0x1fb1}, +{0x03b1, 0x0306, 0x1fb0}, +{0x03b1, 0x0313, 0x1f00}, +{0x03b1, 0x0314, 0x1f01}, +{0x03b1, 0x0342, 0x1fb6}, +{0x03b1, 0x0345, 0x1fb3}, +{0x03b5, 0x0300, 0x1f72}, +{0x03b5, 0x0301, 0x03ad}, +{0x03b5, 0x0313, 0x1f10}, +{0x03b5, 0x0314, 0x1f11}, +{0x03b7, 0x0300, 0x1f74}, +{0x03b7, 0x0301, 0x03ae}, +{0x03b7, 0x0313, 0x1f20}, +{0x03b7, 0x0314, 0x1f21}, +{0x03b7, 0x0342, 0x1fc6}, +{0x03b7, 0x0345, 0x1fc3}, +{0x03b9, 0x0300, 0x1f76}, +{0x03b9, 0x0301, 0x03af}, +{0x03b9, 0x0304, 0x1fd1}, +{0x03b9, 0x0306, 0x1fd0}, +{0x03b9, 0x0308, 0x03ca}, +{0x03b9, 0x0313, 0x1f30}, +{0x03b9, 0x0314, 0x1f31}, +{0x03b9, 0x0342, 0x1fd6}, +{0x03bf, 0x0300, 0x1f78}, +{0x03bf, 0x0301, 0x03cc}, +{0x03bf, 0x0313, 0x1f40}, +{0x03bf, 0x0314, 0x1f41}, +{0x03c1, 0x0313, 0x1fe4}, +{0x03c1, 0x0314, 0x1fe5}, +{0x03c5, 0x0300, 0x1f7a}, +{0x03c5, 0x0301, 0x03cd}, +{0x03c5, 0x0304, 0x1fe1}, +{0x03c5, 0x0306, 0x1fe0}, +{0x03c5, 0x0308, 0x03cb}, +{0x03c5, 0x0313, 0x1f50}, +{0x03c5, 0x0314, 0x1f51}, +{0x03c5, 0x0342, 0x1fe6}, +{0x03c9, 0x0300, 0x1f7c}, +{0x03c9, 0x0301, 0x03ce}, +{0x03c9, 0x0313, 0x1f60}, +{0x03c9, 0x0314, 0x1f61}, +{0x03c9, 0x0342, 0x1ff6}, +{0x03c9, 0x0345, 0x1ff3}, +{0x03ca, 0x0300, 0x1fd2}, +{0x03ca, 0x0301, 0x0390}, +{0x03ca, 0x0342, 0x1fd7}, +{0x03cb, 0x0300, 0x1fe2}, +{0x03cb, 0x0301, 0x03b0}, +{0x03cb, 0x0342, 0x1fe7}, +{0x03ce, 0x0345, 0x1ff4}, +{0x03d2, 0x0301, 0x03d3}, +{0x03d2, 0x0308, 0x03d4}, +{0x0406, 0x0308, 0x0407}, +{0x0410, 0x0306, 0x04d0}, +{0x0410, 0x0308, 0x04d2}, +{0x0413, 0x0301, 0x0403}, +{0x0415, 0x0300, 0x0400}, +{0x0415, 0x0306, 0x04d6}, +{0x0415, 0x0308, 0x0401}, +{0x0416, 0x0306, 0x04c1}, +{0x0416, 0x0308, 0x04dc}, +{0x0417, 0x0308, 0x04de}, +{0x0418, 0x0300, 0x040d}, +{0x0418, 0x0304, 0x04e2}, +{0x0418, 0x0306, 0x0419}, +{0x0418, 0x0308, 0x04e4}, +{0x041a, 0x0301, 0x040c}, +{0x041e, 0x0308, 0x04e6}, +{0x0423, 0x0304, 0x04ee}, +{0x0423, 0x0306, 0x040e}, +{0x0423, 0x0308, 0x04f0}, +{0x0423, 0x030b, 0x04f2}, +{0x0427, 0x0308, 0x04f4}, +{0x042b, 0x0308, 0x04f8}, +{0x042d, 0x0308, 0x04ec}, +{0x0430, 0x0306, 0x04d1}, +{0x0430, 0x0308, 0x04d3}, +{0x0433, 0x0301, 0x0453}, +{0x0435, 0x0300, 0x0450}, +{0x0435, 0x0306, 0x04d7}, +{0x0435, 0x0308, 0x0451}, +{0x0436, 0x0306, 0x04c2}, +{0x0436, 0x0308, 0x04dd}, +{0x0437, 0x0308, 0x04df}, +{0x0438, 0x0300, 0x045d}, +{0x0438, 0x0304, 0x04e3}, +{0x0438, 0x0306, 0x0439}, +{0x0438, 0x0308, 0x04e5}, +{0x043a, 0x0301, 0x045c}, +{0x043e, 0x0308, 0x04e7}, +{0x0443, 0x0304, 0x04ef}, +{0x0443, 0x0306, 0x045e}, +{0x0443, 0x0308, 0x04f1}, +{0x0443, 0x030b, 0x04f3}, +{0x0447, 0x0308, 0x04f5}, +{0x044b, 0x0308, 0x04f9}, +{0x044d, 0x0308, 0x04ed}, +{0x0456, 0x0308, 0x0457}, +{0x0474, 0x030f, 0x0476}, +{0x0475, 0x030f, 0x0477}, +{0x04d8, 0x0308, 0x04da}, +{0x04d9, 0x0308, 0x04db}, +{0x04e8, 0x0308, 0x04ea}, +{0x04e9, 0x0308, 0x04eb}, +{0x0627, 0x0653, 0x0622}, +{0x0627, 0x0654, 0x0623}, +{0x0627, 0x0655, 0x0625}, +{0x0648, 0x0654, 0x0624}, +{0x064a, 0x0654, 0x0626}, +{0x06c1, 0x0654, 0x06c2}, +{0x06d2, 0x0654, 0x06d3}, +{0x06d5, 0x0654, 0x06c0}, +{0x0928, 0x093c, 0x0929}, +{0x0930, 0x093c, 0x0931}, +{0x0933, 0x093c, 0x0934}, +{0x09c7, 0x09be, 0x09cb}, +{0x09c7, 0x09d7, 0x09cc}, +{0x0b47, 0x0b3e, 0x0b4b}, +{0x0b47, 0x0b56, 0x0b48}, +{0x0b47, 0x0b57, 0x0b4c}, +{0x0b92, 0x0bd7, 0x0b94}, +{0x0bc6, 0x0bbe, 0x0bca}, +{0x0bc6, 0x0bd7, 0x0bcc}, +{0x0bc7, 0x0bbe, 0x0bcb}, +{0x0c46, 0x0c56, 0x0c48}, +{0x0cbf, 0x0cd5, 0x0cc0}, +{0x0cc6, 0x0cc2, 0x0cca}, +{0x0cc6, 0x0cd5, 0x0cc7}, +{0x0cc6, 0x0cd6, 0x0cc8}, +{0x0cca, 0x0cd5, 0x0ccb}, +{0x0d46, 0x0d3e, 0x0d4a}, +{0x0d46, 0x0d57, 0x0d4c}, +{0x0d47, 0x0d3e, 0x0d4b}, +{0x0dd9, 0x0dca, 0x0dda}, +{0x0dd9, 0x0dcf, 0x0ddc}, +{0x0dd9, 0x0ddf, 0x0dde}, +{0x0ddc, 0x0dca, 0x0ddd}, +{0x1025, 0x102e, 0x1026}, +{0x1b05, 0x1b35, 0x1b06}, +{0x1b07, 0x1b35, 0x1b08}, +{0x1b09, 0x1b35, 0x1b0a}, +{0x1b0b, 0x1b35, 0x1b0c}, +{0x1b0d, 0x1b35, 0x1b0e}, +{0x1b11, 0x1b35, 0x1b12}, +{0x1b3a, 0x1b35, 0x1b3b}, +{0x1b3c, 0x1b35, 0x1b3d}, +{0x1b3e, 0x1b35, 0x1b40}, +{0x1b3f, 0x1b35, 0x1b41}, +{0x1b42, 0x1b35, 0x1b43}, +{0x1e36, 0x0304, 0x1e38}, +{0x1e37, 0x0304, 0x1e39}, +{0x1e5a, 0x0304, 0x1e5c}, +{0x1e5b, 0x0304, 0x1e5d}, +{0x1e62, 0x0307, 0x1e68}, +{0x1e63, 0x0307, 0x1e69}, +{0x1ea0, 0x0302, 0x1eac}, +{0x1ea0, 0x0306, 0x1eb6}, +{0x1ea1, 0x0302, 0x1ead}, +{0x1ea1, 0x0306, 0x1eb7}, +{0x1eb8, 0x0302, 0x1ec6}, +{0x1eb9, 0x0302, 0x1ec7}, +{0x1ecc, 0x0302, 0x1ed8}, +{0x1ecd, 0x0302, 0x1ed9}, +{0x1f00, 0x0300, 0x1f02}, +{0x1f00, 0x0301, 0x1f04}, +{0x1f00, 0x0342, 0x1f06}, +{0x1f00, 0x0345, 0x1f80}, +{0x1f01, 0x0300, 0x1f03}, +{0x1f01, 0x0301, 0x1f05}, +{0x1f01, 0x0342, 0x1f07}, +{0x1f01, 0x0345, 0x1f81}, +{0x1f02, 0x0345, 0x1f82}, +{0x1f03, 0x0345, 0x1f83}, +{0x1f04, 0x0345, 0x1f84}, +{0x1f05, 0x0345, 0x1f85}, +{0x1f06, 0x0345, 0x1f86}, +{0x1f07, 0x0345, 0x1f87}, +{0x1f08, 0x0300, 0x1f0a}, +{0x1f08, 0x0301, 0x1f0c}, +{0x1f08, 0x0342, 0x1f0e}, +{0x1f08, 0x0345, 0x1f88}, +{0x1f09, 0x0300, 0x1f0b}, +{0x1f09, 0x0301, 0x1f0d}, +{0x1f09, 0x0342, 0x1f0f}, +{0x1f09, 0x0345, 0x1f89}, +{0x1f0a, 0x0345, 0x1f8a}, +{0x1f0b, 0x0345, 0x1f8b}, +{0x1f0c, 0x0345, 0x1f8c}, +{0x1f0d, 0x0345, 0x1f8d}, +{0x1f0e, 0x0345, 0x1f8e}, +{0x1f0f, 0x0345, 0x1f8f}, +{0x1f10, 0x0300, 0x1f12}, +{0x1f10, 0x0301, 0x1f14}, +{0x1f11, 0x0300, 0x1f13}, +{0x1f11, 0x0301, 0x1f15}, +{0x1f18, 0x0300, 0x1f1a}, +{0x1f18, 0x0301, 0x1f1c}, +{0x1f19, 0x0300, 0x1f1b}, +{0x1f19, 0x0301, 0x1f1d}, +{0x1f20, 0x0300, 0x1f22}, +{0x1f20, 0x0301, 0x1f24}, +{0x1f20, 0x0342, 0x1f26}, +{0x1f20, 0x0345, 0x1f90}, +{0x1f21, 0x0300, 0x1f23}, +{0x1f21, 0x0301, 0x1f25}, +{0x1f21, 0x0342, 0x1f27}, +{0x1f21, 0x0345, 0x1f91}, +{0x1f22, 0x0345, 0x1f92}, +{0x1f23, 0x0345, 0x1f93}, +{0x1f24, 0x0345, 0x1f94}, +{0x1f25, 0x0345, 0x1f95}, +{0x1f26, 0x0345, 0x1f96}, +{0x1f27, 0x0345, 0x1f97}, +{0x1f28, 0x0300, 0x1f2a}, +{0x1f28, 0x0301, 0x1f2c}, +{0x1f28, 0x0342, 0x1f2e}, +{0x1f28, 0x0345, 0x1f98}, +{0x1f29, 0x0300, 0x1f2b}, +{0x1f29, 0x0301, 0x1f2d}, +{0x1f29, 0x0342, 0x1f2f}, +{0x1f29, 0x0345, 0x1f99}, +{0x1f2a, 0x0345, 0x1f9a}, +{0x1f2b, 0x0345, 0x1f9b}, +{0x1f2c, 0x0345, 0x1f9c}, +{0x1f2d, 0x0345, 0x1f9d}, +{0x1f2e, 0x0345, 0x1f9e}, +{0x1f2f, 0x0345, 0x1f9f}, +{0x1f30, 0x0300, 0x1f32}, +{0x1f30, 0x0301, 0x1f34}, +{0x1f30, 0x0342, 0x1f36}, +{0x1f31, 0x0300, 0x1f33}, +{0x1f31, 0x0301, 0x1f35}, +{0x1f31, 0x0342, 0x1f37}, +{0x1f38, 0x0300, 0x1f3a}, +{0x1f38, 0x0301, 0x1f3c}, +{0x1f38, 0x0342, 0x1f3e}, +{0x1f39, 0x0300, 0x1f3b}, +{0x1f39, 0x0301, 0x1f3d}, +{0x1f39, 0x0342, 0x1f3f}, +{0x1f40, 0x0300, 0x1f42}, +{0x1f40, 0x0301, 0x1f44}, +{0x1f41, 0x0300, 0x1f43}, +{0x1f41, 0x0301, 0x1f45}, +{0x1f48, 0x0300, 0x1f4a}, +{0x1f48, 0x0301, 0x1f4c}, +{0x1f49, 0x0300, 0x1f4b}, +{0x1f49, 0x0301, 0x1f4d}, +{0x1f50, 0x0300, 0x1f52}, +{0x1f50, 0x0301, 0x1f54}, +{0x1f50, 0x0342, 0x1f56}, +{0x1f51, 0x0300, 0x1f53}, +{0x1f51, 0x0301, 0x1f55}, +{0x1f51, 0x0342, 0x1f57}, +{0x1f59, 0x0300, 0x1f5b}, +{0x1f59, 0x0301, 0x1f5d}, +{0x1f59, 0x0342, 0x1f5f}, +{0x1f60, 0x0300, 0x1f62}, +{0x1f60, 0x0301, 0x1f64}, +{0x1f60, 0x0342, 0x1f66}, +{0x1f60, 0x0345, 0x1fa0}, +{0x1f61, 0x0300, 0x1f63}, +{0x1f61, 0x0301, 0x1f65}, +{0x1f61, 0x0342, 0x1f67}, +{0x1f61, 0x0345, 0x1fa1}, +{0x1f62, 0x0345, 0x1fa2}, +{0x1f63, 0x0345, 0x1fa3}, +{0x1f64, 0x0345, 0x1fa4}, +{0x1f65, 0x0345, 0x1fa5}, +{0x1f66, 0x0345, 0x1fa6}, +{0x1f67, 0x0345, 0x1fa7}, +{0x1f68, 0x0300, 0x1f6a}, +{0x1f68, 0x0301, 0x1f6c}, +{0x1f68, 0x0342, 0x1f6e}, +{0x1f68, 0x0345, 0x1fa8}, +{0x1f69, 0x0300, 0x1f6b}, +{0x1f69, 0x0301, 0x1f6d}, +{0x1f69, 0x0342, 0x1f6f}, +{0x1f69, 0x0345, 0x1fa9}, +{0x1f6a, 0x0345, 0x1faa}, +{0x1f6b, 0x0345, 0x1fab}, +{0x1f6c, 0x0345, 0x1fac}, +{0x1f6d, 0x0345, 0x1fad}, +{0x1f6e, 0x0345, 0x1fae}, +{0x1f6f, 0x0345, 0x1faf}, +{0x1f70, 0x0345, 0x1fb2}, +{0x1f74, 0x0345, 0x1fc2}, +{0x1f7c, 0x0345, 0x1ff2}, +{0x1fb6, 0x0345, 0x1fb7}, +{0x1fbf, 0x0300, 0x1fcd}, +{0x1fbf, 0x0301, 0x1fce}, +{0x1fbf, 0x0342, 0x1fcf}, +{0x1fc6, 0x0345, 0x1fc7}, +{0x1ff6, 0x0345, 0x1ff7}, +{0x1ffe, 0x0300, 0x1fdd}, +{0x1ffe, 0x0301, 0x1fde}, +{0x1ffe, 0x0342, 0x1fdf}, +{0x2190, 0x0338, 0x219a}, +{0x2192, 0x0338, 0x219b}, +{0x2194, 0x0338, 0x21ae}, +{0x21d0, 0x0338, 0x21cd}, +{0x21d2, 0x0338, 0x21cf}, +{0x21d4, 0x0338, 0x21ce}, +{0x2203, 0x0338, 0x2204}, +{0x2208, 0x0338, 0x2209}, +{0x220b, 0x0338, 0x220c}, +{0x2223, 0x0338, 0x2224}, +{0x2225, 0x0338, 0x2226}, +{0x223c, 0x0338, 0x2241}, +{0x2243, 0x0338, 0x2244}, +{0x2245, 0x0338, 0x2247}, +{0x2248, 0x0338, 0x2249}, +{0x224d, 0x0338, 0x226d}, +{0x2261, 0x0338, 0x2262}, +{0x2264, 0x0338, 0x2270}, +{0x2265, 0x0338, 0x2271}, +{0x2272, 0x0338, 0x2274}, +{0x2273, 0x0338, 0x2275}, +{0x2276, 0x0338, 0x2278}, +{0x2277, 0x0338, 0x2279}, +{0x227a, 0x0338, 0x2280}, +{0x227b, 0x0338, 0x2281}, +{0x227c, 0x0338, 0x22e0}, +{0x227d, 0x0338, 0x22e1}, +{0x2282, 0x0338, 0x2284}, +{0x2283, 0x0338, 0x2285}, +{0x2286, 0x0338, 0x2288}, +{0x2287, 0x0338, 0x2289}, +{0x2291, 0x0338, 0x22e2}, +{0x2292, 0x0338, 0x22e3}, +{0x22a2, 0x0338, 0x22ac}, +{0x22a8, 0x0338, 0x22ad}, +{0x22a9, 0x0338, 0x22ae}, +{0x22ab, 0x0338, 0x22af}, +{0x22b2, 0x0338, 0x22ea}, +{0x22b3, 0x0338, 0x22eb}, +{0x22b4, 0x0338, 0x22ec}, +{0x22b5, 0x0338, 0x22ed}, +{0x3046, 0x3099, 0x3094}, +{0x304b, 0x3099, 0x304c}, +{0x304d, 0x3099, 0x304e}, +{0x304f, 0x3099, 0x3050}, +{0x3051, 0x3099, 0x3052}, +{0x3053, 0x3099, 0x3054}, +{0x3055, 0x3099, 0x3056}, +{0x3057, 0x3099, 0x3058}, +{0x3059, 0x3099, 0x305a}, +{0x305b, 0x3099, 0x305c}, +{0x305d, 0x3099, 0x305e}, +{0x305f, 0x3099, 0x3060}, +{0x3061, 0x3099, 0x3062}, +{0x3064, 0x3099, 0x3065}, +{0x3066, 0x3099, 0x3067}, +{0x3068, 0x3099, 0x3069}, +{0x306f, 0x3099, 0x3070}, +{0x306f, 0x309a, 0x3071}, +{0x3072, 0x3099, 0x3073}, +{0x3072, 0x309a, 0x3074}, +{0x3075, 0x3099, 0x3076}, +{0x3075, 0x309a, 0x3077}, +{0x3078, 0x3099, 0x3079}, +{0x3078, 0x309a, 0x307a}, +{0x307b, 0x3099, 0x307c}, +{0x307b, 0x309a, 0x307d}, +{0x309d, 0x3099, 0x309e}, +{0x30a6, 0x3099, 0x30f4}, +{0x30ab, 0x3099, 0x30ac}, +{0x30ad, 0x3099, 0x30ae}, +{0x30af, 0x3099, 0x30b0}, +{0x30b1, 0x3099, 0x30b2}, +{0x30b3, 0x3099, 0x30b4}, +{0x30b5, 0x3099, 0x30b6}, +{0x30b7, 0x3099, 0x30b8}, +{0x30b9, 0x3099, 0x30ba}, +{0x30bb, 0x3099, 0x30bc}, +{0x30bd, 0x3099, 0x30be}, +{0x30bf, 0x3099, 0x30c0}, +{0x30c1, 0x3099, 0x30c2}, +{0x30c4, 0x3099, 0x30c5}, +{0x30c6, 0x3099, 0x30c7}, +{0x30c8, 0x3099, 0x30c9}, +{0x30cf, 0x3099, 0x30d0}, +{0x30cf, 0x309a, 0x30d1}, +{0x30d2, 0x3099, 0x30d3}, +{0x30d2, 0x309a, 0x30d4}, +{0x30d5, 0x3099, 0x30d6}, +{0x30d5, 0x309a, 0x30d7}, +{0x30d8, 0x3099, 0x30d9}, +{0x30d8, 0x309a, 0x30da}, +{0x30db, 0x3099, 0x30dc}, +{0x30db, 0x309a, 0x30dd}, +{0x30ef, 0x3099, 0x30f7}, +{0x30f0, 0x3099, 0x30f8}, +{0x30f1, 0x3099, 0x30f9}, +{0x30f2, 0x3099, 0x30fa}, +{0x30fd, 0x3099, 0x30fe}, +{0x105d2, 0x0307, 0x105c9}, +{0x105da, 0x0307, 0x105e4}, +{0x11099, 0x110ba, 0x1109a}, +{0x1109b, 0x110ba, 0x1109c}, +{0x110a5, 0x110ba, 0x110ab}, +{0x11131, 0x11127, 0x1112e}, +{0x11132, 0x11127, 0x1112f}, +{0x11347, 0x1133e, 0x1134b}, +{0x11347, 0x11357, 0x1134c}, +{0x11382, 0x113c9, 0x11383}, +{0x11384, 0x113bb, 0x11385}, +{0x1138b, 0x113c2, 0x1138e}, +{0x11390, 0x113c9, 0x11391}, +{0x113c2, 0x113b8, 0x113c7}, +{0x113c2, 0x113c2, 0x113c5}, +{0x113c2, 0x113c9, 0x113c8}, +{0x114b9, 0x114b0, 0x114bc}, +{0x114b9, 0x114ba, 0x114bb}, +{0x114b9, 0x114bd, 0x114be}, +{0x115b8, 0x115af, 0x115ba}, +{0x115b9, 0x115af, 0x115bb}, +{0x11935, 0x11930, 0x11938}, +{0x1611e, 0x1611e, 0x16121}, +{0x1611e, 0x1611f, 0x16123}, +{0x1611e, 0x16120, 0x16125}, +{0x1611e, 0x16129, 0x16122}, +{0x16121, 0x1611f, 0x16126}, +{0x16121, 0x16120, 0x16128}, +{0x16122, 0x1611f, 0x16127}, +{0x16129, 0x1611f, 0x16124}, +{0x16d63, 0x16d67, 0x16d69}, +{0x16d67, 0x16d67, 0x16d68}, +{0x16d69, 0x16d67, 0x16d6a}, diff --git a/unicode/canonical_decomp.h b/unicode/canonical_decomp.h new file mode 100644 index 00000000..5396b05f --- /dev/null +++ b/unicode/canonical_decomp.h @@ -0,0 +1,2091 @@ +/* + * Autogenerated by read_ucd.py from The Unicode Standard 16.0.0 + * + * List the canonical decomposition of every Unicode character that has + * one. This consists of up to two characters, but those may need + * decomposition in turn. + * + * Used by utils/unicode-norm.c. + */ + +{0x00c0, 0x0041, 0x0300}, +{0x00c1, 0x0041, 0x0301}, +{0x00c2, 0x0041, 0x0302}, +{0x00c3, 0x0041, 0x0303}, +{0x00c4, 0x0041, 0x0308}, +{0x00c5, 0x0041, 0x030a}, +{0x00c7, 0x0043, 0x0327}, +{0x00c8, 0x0045, 0x0300}, +{0x00c9, 0x0045, 0x0301}, +{0x00ca, 0x0045, 0x0302}, +{0x00cb, 0x0045, 0x0308}, +{0x00cc, 0x0049, 0x0300}, +{0x00cd, 0x0049, 0x0301}, +{0x00ce, 0x0049, 0x0302}, +{0x00cf, 0x0049, 0x0308}, +{0x00d1, 0x004e, 0x0303}, +{0x00d2, 0x004f, 0x0300}, +{0x00d3, 0x004f, 0x0301}, +{0x00d4, 0x004f, 0x0302}, +{0x00d5, 0x004f, 0x0303}, +{0x00d6, 0x004f, 0x0308}, +{0x00d9, 0x0055, 0x0300}, +{0x00da, 0x0055, 0x0301}, +{0x00db, 0x0055, 0x0302}, +{0x00dc, 0x0055, 0x0308}, +{0x00dd, 0x0059, 0x0301}, +{0x00e0, 0x0061, 0x0300}, +{0x00e1, 0x0061, 0x0301}, +{0x00e2, 0x0061, 0x0302}, +{0x00e3, 0x0061, 0x0303}, +{0x00e4, 0x0061, 0x0308}, +{0x00e5, 0x0061, 0x030a}, +{0x00e7, 0x0063, 0x0327}, +{0x00e8, 0x0065, 0x0300}, +{0x00e9, 0x0065, 0x0301}, +{0x00ea, 0x0065, 0x0302}, +{0x00eb, 0x0065, 0x0308}, +{0x00ec, 0x0069, 0x0300}, +{0x00ed, 0x0069, 0x0301}, +{0x00ee, 0x0069, 0x0302}, +{0x00ef, 0x0069, 0x0308}, +{0x00f1, 0x006e, 0x0303}, +{0x00f2, 0x006f, 0x0300}, +{0x00f3, 0x006f, 0x0301}, +{0x00f4, 0x006f, 0x0302}, +{0x00f5, 0x006f, 0x0303}, +{0x00f6, 0x006f, 0x0308}, +{0x00f9, 0x0075, 0x0300}, +{0x00fa, 0x0075, 0x0301}, +{0x00fb, 0x0075, 0x0302}, +{0x00fc, 0x0075, 0x0308}, +{0x00fd, 0x0079, 0x0301}, +{0x00ff, 0x0079, 0x0308}, +{0x0100, 0x0041, 0x0304}, +{0x0101, 0x0061, 0x0304}, +{0x0102, 0x0041, 0x0306}, +{0x0103, 0x0061, 0x0306}, +{0x0104, 0x0041, 0x0328}, +{0x0105, 0x0061, 0x0328}, +{0x0106, 0x0043, 0x0301}, +{0x0107, 0x0063, 0x0301}, +{0x0108, 0x0043, 0x0302}, +{0x0109, 0x0063, 0x0302}, +{0x010a, 0x0043, 0x0307}, +{0x010b, 0x0063, 0x0307}, +{0x010c, 0x0043, 0x030c}, +{0x010d, 0x0063, 0x030c}, +{0x010e, 0x0044, 0x030c}, +{0x010f, 0x0064, 0x030c}, +{0x0112, 0x0045, 0x0304}, +{0x0113, 0x0065, 0x0304}, +{0x0114, 0x0045, 0x0306}, +{0x0115, 0x0065, 0x0306}, +{0x0116, 0x0045, 0x0307}, +{0x0117, 0x0065, 0x0307}, +{0x0118, 0x0045, 0x0328}, +{0x0119, 0x0065, 0x0328}, +{0x011a, 0x0045, 0x030c}, +{0x011b, 0x0065, 0x030c}, +{0x011c, 0x0047, 0x0302}, +{0x011d, 0x0067, 0x0302}, +{0x011e, 0x0047, 0x0306}, +{0x011f, 0x0067, 0x0306}, +{0x0120, 0x0047, 0x0307}, +{0x0121, 0x0067, 0x0307}, +{0x0122, 0x0047, 0x0327}, +{0x0123, 0x0067, 0x0327}, +{0x0124, 0x0048, 0x0302}, +{0x0125, 0x0068, 0x0302}, +{0x0128, 0x0049, 0x0303}, +{0x0129, 0x0069, 0x0303}, +{0x012a, 0x0049, 0x0304}, +{0x012b, 0x0069, 0x0304}, +{0x012c, 0x0049, 0x0306}, +{0x012d, 0x0069, 0x0306}, +{0x012e, 0x0049, 0x0328}, +{0x012f, 0x0069, 0x0328}, +{0x0130, 0x0049, 0x0307}, +{0x0134, 0x004a, 0x0302}, +{0x0135, 0x006a, 0x0302}, +{0x0136, 0x004b, 0x0327}, +{0x0137, 0x006b, 0x0327}, +{0x0139, 0x004c, 0x0301}, +{0x013a, 0x006c, 0x0301}, +{0x013b, 0x004c, 0x0327}, +{0x013c, 0x006c, 0x0327}, +{0x013d, 0x004c, 0x030c}, +{0x013e, 0x006c, 0x030c}, +{0x0143, 0x004e, 0x0301}, +{0x0144, 0x006e, 0x0301}, +{0x0145, 0x004e, 0x0327}, +{0x0146, 0x006e, 0x0327}, +{0x0147, 0x004e, 0x030c}, +{0x0148, 0x006e, 0x030c}, +{0x014c, 0x004f, 0x0304}, +{0x014d, 0x006f, 0x0304}, +{0x014e, 0x004f, 0x0306}, +{0x014f, 0x006f, 0x0306}, +{0x0150, 0x004f, 0x030b}, +{0x0151, 0x006f, 0x030b}, +{0x0154, 0x0052, 0x0301}, +{0x0155, 0x0072, 0x0301}, +{0x0156, 0x0052, 0x0327}, +{0x0157, 0x0072, 0x0327}, +{0x0158, 0x0052, 0x030c}, +{0x0159, 0x0072, 0x030c}, +{0x015a, 0x0053, 0x0301}, +{0x015b, 0x0073, 0x0301}, +{0x015c, 0x0053, 0x0302}, +{0x015d, 0x0073, 0x0302}, +{0x015e, 0x0053, 0x0327}, +{0x015f, 0x0073, 0x0327}, +{0x0160, 0x0053, 0x030c}, +{0x0161, 0x0073, 0x030c}, +{0x0162, 0x0054, 0x0327}, +{0x0163, 0x0074, 0x0327}, +{0x0164, 0x0054, 0x030c}, +{0x0165, 0x0074, 0x030c}, +{0x0168, 0x0055, 0x0303}, +{0x0169, 0x0075, 0x0303}, +{0x016a, 0x0055, 0x0304}, +{0x016b, 0x0075, 0x0304}, +{0x016c, 0x0055, 0x0306}, +{0x016d, 0x0075, 0x0306}, +{0x016e, 0x0055, 0x030a}, +{0x016f, 0x0075, 0x030a}, +{0x0170, 0x0055, 0x030b}, +{0x0171, 0x0075, 0x030b}, +{0x0172, 0x0055, 0x0328}, +{0x0173, 0x0075, 0x0328}, +{0x0174, 0x0057, 0x0302}, +{0x0175, 0x0077, 0x0302}, +{0x0176, 0x0059, 0x0302}, +{0x0177, 0x0079, 0x0302}, +{0x0178, 0x0059, 0x0308}, +{0x0179, 0x005a, 0x0301}, +{0x017a, 0x007a, 0x0301}, +{0x017b, 0x005a, 0x0307}, +{0x017c, 0x007a, 0x0307}, +{0x017d, 0x005a, 0x030c}, +{0x017e, 0x007a, 0x030c}, +{0x01a0, 0x004f, 0x031b}, +{0x01a1, 0x006f, 0x031b}, +{0x01af, 0x0055, 0x031b}, +{0x01b0, 0x0075, 0x031b}, +{0x01cd, 0x0041, 0x030c}, +{0x01ce, 0x0061, 0x030c}, +{0x01cf, 0x0049, 0x030c}, +{0x01d0, 0x0069, 0x030c}, +{0x01d1, 0x004f, 0x030c}, +{0x01d2, 0x006f, 0x030c}, +{0x01d3, 0x0055, 0x030c}, +{0x01d4, 0x0075, 0x030c}, +{0x01d5, 0x00dc, 0x0304}, +{0x01d6, 0x00fc, 0x0304}, +{0x01d7, 0x00dc, 0x0301}, +{0x01d8, 0x00fc, 0x0301}, +{0x01d9, 0x00dc, 0x030c}, +{0x01da, 0x00fc, 0x030c}, +{0x01db, 0x00dc, 0x0300}, +{0x01dc, 0x00fc, 0x0300}, +{0x01de, 0x00c4, 0x0304}, +{0x01df, 0x00e4, 0x0304}, +{0x01e0, 0x0226, 0x0304}, +{0x01e1, 0x0227, 0x0304}, +{0x01e2, 0x00c6, 0x0304}, +{0x01e3, 0x00e6, 0x0304}, +{0x01e6, 0x0047, 0x030c}, +{0x01e7, 0x0067, 0x030c}, +{0x01e8, 0x004b, 0x030c}, +{0x01e9, 0x006b, 0x030c}, +{0x01ea, 0x004f, 0x0328}, +{0x01eb, 0x006f, 0x0328}, +{0x01ec, 0x01ea, 0x0304}, +{0x01ed, 0x01eb, 0x0304}, +{0x01ee, 0x01b7, 0x030c}, +{0x01ef, 0x0292, 0x030c}, +{0x01f0, 0x006a, 0x030c}, +{0x01f4, 0x0047, 0x0301}, +{0x01f5, 0x0067, 0x0301}, +{0x01f8, 0x004e, 0x0300}, +{0x01f9, 0x006e, 0x0300}, +{0x01fa, 0x00c5, 0x0301}, +{0x01fb, 0x00e5, 0x0301}, +{0x01fc, 0x00c6, 0x0301}, +{0x01fd, 0x00e6, 0x0301}, +{0x01fe, 0x00d8, 0x0301}, +{0x01ff, 0x00f8, 0x0301}, +{0x0200, 0x0041, 0x030f}, +{0x0201, 0x0061, 0x030f}, +{0x0202, 0x0041, 0x0311}, +{0x0203, 0x0061, 0x0311}, +{0x0204, 0x0045, 0x030f}, +{0x0205, 0x0065, 0x030f}, +{0x0206, 0x0045, 0x0311}, +{0x0207, 0x0065, 0x0311}, +{0x0208, 0x0049, 0x030f}, +{0x0209, 0x0069, 0x030f}, +{0x020a, 0x0049, 0x0311}, +{0x020b, 0x0069, 0x0311}, +{0x020c, 0x004f, 0x030f}, +{0x020d, 0x006f, 0x030f}, +{0x020e, 0x004f, 0x0311}, +{0x020f, 0x006f, 0x0311}, +{0x0210, 0x0052, 0x030f}, +{0x0211, 0x0072, 0x030f}, +{0x0212, 0x0052, 0x0311}, +{0x0213, 0x0072, 0x0311}, +{0x0214, 0x0055, 0x030f}, +{0x0215, 0x0075, 0x030f}, +{0x0216, 0x0055, 0x0311}, +{0x0217, 0x0075, 0x0311}, +{0x0218, 0x0053, 0x0326}, +{0x0219, 0x0073, 0x0326}, +{0x021a, 0x0054, 0x0326}, +{0x021b, 0x0074, 0x0326}, +{0x021e, 0x0048, 0x030c}, +{0x021f, 0x0068, 0x030c}, +{0x0226, 0x0041, 0x0307}, +{0x0227, 0x0061, 0x0307}, +{0x0228, 0x0045, 0x0327}, +{0x0229, 0x0065, 0x0327}, +{0x022a, 0x00d6, 0x0304}, +{0x022b, 0x00f6, 0x0304}, +{0x022c, 0x00d5, 0x0304}, +{0x022d, 0x00f5, 0x0304}, +{0x022e, 0x004f, 0x0307}, +{0x022f, 0x006f, 0x0307}, +{0x0230, 0x022e, 0x0304}, +{0x0231, 0x022f, 0x0304}, +{0x0232, 0x0059, 0x0304}, +{0x0233, 0x0079, 0x0304}, +{0x0340, 0x0300, 0}, +{0x0341, 0x0301, 0}, +{0x0343, 0x0313, 0}, +{0x0344, 0x0308, 0x0301}, +{0x0374, 0x02b9, 0}, +{0x037e, 0x003b, 0}, +{0x0385, 0x00a8, 0x0301}, +{0x0386, 0x0391, 0x0301}, +{0x0387, 0x00b7, 0}, +{0x0388, 0x0395, 0x0301}, +{0x0389, 0x0397, 0x0301}, +{0x038a, 0x0399, 0x0301}, +{0x038c, 0x039f, 0x0301}, +{0x038e, 0x03a5, 0x0301}, +{0x038f, 0x03a9, 0x0301}, +{0x0390, 0x03ca, 0x0301}, +{0x03aa, 0x0399, 0x0308}, +{0x03ab, 0x03a5, 0x0308}, +{0x03ac, 0x03b1, 0x0301}, +{0x03ad, 0x03b5, 0x0301}, +{0x03ae, 0x03b7, 0x0301}, +{0x03af, 0x03b9, 0x0301}, +{0x03b0, 0x03cb, 0x0301}, +{0x03ca, 0x03b9, 0x0308}, +{0x03cb, 0x03c5, 0x0308}, +{0x03cc, 0x03bf, 0x0301}, +{0x03cd, 0x03c5, 0x0301}, +{0x03ce, 0x03c9, 0x0301}, +{0x03d3, 0x03d2, 0x0301}, +{0x03d4, 0x03d2, 0x0308}, +{0x0400, 0x0415, 0x0300}, +{0x0401, 0x0415, 0x0308}, +{0x0403, 0x0413, 0x0301}, +{0x0407, 0x0406, 0x0308}, +{0x040c, 0x041a, 0x0301}, +{0x040d, 0x0418, 0x0300}, +{0x040e, 0x0423, 0x0306}, +{0x0419, 0x0418, 0x0306}, +{0x0439, 0x0438, 0x0306}, +{0x0450, 0x0435, 0x0300}, +{0x0451, 0x0435, 0x0308}, +{0x0453, 0x0433, 0x0301}, +{0x0457, 0x0456, 0x0308}, +{0x045c, 0x043a, 0x0301}, +{0x045d, 0x0438, 0x0300}, +{0x045e, 0x0443, 0x0306}, +{0x0476, 0x0474, 0x030f}, +{0x0477, 0x0475, 0x030f}, +{0x04c1, 0x0416, 0x0306}, +{0x04c2, 0x0436, 0x0306}, +{0x04d0, 0x0410, 0x0306}, +{0x04d1, 0x0430, 0x0306}, +{0x04d2, 0x0410, 0x0308}, +{0x04d3, 0x0430, 0x0308}, +{0x04d6, 0x0415, 0x0306}, +{0x04d7, 0x0435, 0x0306}, +{0x04da, 0x04d8, 0x0308}, +{0x04db, 0x04d9, 0x0308}, +{0x04dc, 0x0416, 0x0308}, +{0x04dd, 0x0436, 0x0308}, +{0x04de, 0x0417, 0x0308}, +{0x04df, 0x0437, 0x0308}, +{0x04e2, 0x0418, 0x0304}, +{0x04e3, 0x0438, 0x0304}, +{0x04e4, 0x0418, 0x0308}, +{0x04e5, 0x0438, 0x0308}, +{0x04e6, 0x041e, 0x0308}, +{0x04e7, 0x043e, 0x0308}, +{0x04ea, 0x04e8, 0x0308}, +{0x04eb, 0x04e9, 0x0308}, +{0x04ec, 0x042d, 0x0308}, +{0x04ed, 0x044d, 0x0308}, +{0x04ee, 0x0423, 0x0304}, +{0x04ef, 0x0443, 0x0304}, +{0x04f0, 0x0423, 0x0308}, +{0x04f1, 0x0443, 0x0308}, +{0x04f2, 0x0423, 0x030b}, +{0x04f3, 0x0443, 0x030b}, +{0x04f4, 0x0427, 0x0308}, +{0x04f5, 0x0447, 0x0308}, +{0x04f8, 0x042b, 0x0308}, +{0x04f9, 0x044b, 0x0308}, +{0x0622, 0x0627, 0x0653}, +{0x0623, 0x0627, 0x0654}, +{0x0624, 0x0648, 0x0654}, +{0x0625, 0x0627, 0x0655}, +{0x0626, 0x064a, 0x0654}, +{0x06c0, 0x06d5, 0x0654}, +{0x06c2, 0x06c1, 0x0654}, +{0x06d3, 0x06d2, 0x0654}, +{0x0929, 0x0928, 0x093c}, +{0x0931, 0x0930, 0x093c}, +{0x0934, 0x0933, 0x093c}, +{0x0958, 0x0915, 0x093c}, +{0x0959, 0x0916, 0x093c}, +{0x095a, 0x0917, 0x093c}, +{0x095b, 0x091c, 0x093c}, +{0x095c, 0x0921, 0x093c}, +{0x095d, 0x0922, 0x093c}, +{0x095e, 0x092b, 0x093c}, +{0x095f, 0x092f, 0x093c}, +{0x09cb, 0x09c7, 0x09be}, +{0x09cc, 0x09c7, 0x09d7}, +{0x09dc, 0x09a1, 0x09bc}, +{0x09dd, 0x09a2, 0x09bc}, +{0x09df, 0x09af, 0x09bc}, +{0x0a33, 0x0a32, 0x0a3c}, +{0x0a36, 0x0a38, 0x0a3c}, +{0x0a59, 0x0a16, 0x0a3c}, +{0x0a5a, 0x0a17, 0x0a3c}, +{0x0a5b, 0x0a1c, 0x0a3c}, +{0x0a5e, 0x0a2b, 0x0a3c}, +{0x0b48, 0x0b47, 0x0b56}, +{0x0b4b, 0x0b47, 0x0b3e}, +{0x0b4c, 0x0b47, 0x0b57}, +{0x0b5c, 0x0b21, 0x0b3c}, +{0x0b5d, 0x0b22, 0x0b3c}, +{0x0b94, 0x0b92, 0x0bd7}, +{0x0bca, 0x0bc6, 0x0bbe}, +{0x0bcb, 0x0bc7, 0x0bbe}, +{0x0bcc, 0x0bc6, 0x0bd7}, +{0x0c48, 0x0c46, 0x0c56}, +{0x0cc0, 0x0cbf, 0x0cd5}, +{0x0cc7, 0x0cc6, 0x0cd5}, +{0x0cc8, 0x0cc6, 0x0cd6}, +{0x0cca, 0x0cc6, 0x0cc2}, +{0x0ccb, 0x0cca, 0x0cd5}, +{0x0d4a, 0x0d46, 0x0d3e}, +{0x0d4b, 0x0d47, 0x0d3e}, +{0x0d4c, 0x0d46, 0x0d57}, +{0x0dda, 0x0dd9, 0x0dca}, +{0x0ddc, 0x0dd9, 0x0dcf}, +{0x0ddd, 0x0ddc, 0x0dca}, +{0x0dde, 0x0dd9, 0x0ddf}, +{0x0f43, 0x0f42, 0x0fb7}, +{0x0f4d, 0x0f4c, 0x0fb7}, +{0x0f52, 0x0f51, 0x0fb7}, +{0x0f57, 0x0f56, 0x0fb7}, +{0x0f5c, 0x0f5b, 0x0fb7}, +{0x0f69, 0x0f40, 0x0fb5}, +{0x0f73, 0x0f71, 0x0f72}, +{0x0f75, 0x0f71, 0x0f74}, +{0x0f76, 0x0fb2, 0x0f80}, +{0x0f78, 0x0fb3, 0x0f80}, +{0x0f81, 0x0f71, 0x0f80}, +{0x0f93, 0x0f92, 0x0fb7}, +{0x0f9d, 0x0f9c, 0x0fb7}, +{0x0fa2, 0x0fa1, 0x0fb7}, +{0x0fa7, 0x0fa6, 0x0fb7}, +{0x0fac, 0x0fab, 0x0fb7}, +{0x0fb9, 0x0f90, 0x0fb5}, +{0x1026, 0x1025, 0x102e}, +{0x1b06, 0x1b05, 0x1b35}, +{0x1b08, 0x1b07, 0x1b35}, +{0x1b0a, 0x1b09, 0x1b35}, +{0x1b0c, 0x1b0b, 0x1b35}, +{0x1b0e, 0x1b0d, 0x1b35}, +{0x1b12, 0x1b11, 0x1b35}, +{0x1b3b, 0x1b3a, 0x1b35}, +{0x1b3d, 0x1b3c, 0x1b35}, +{0x1b40, 0x1b3e, 0x1b35}, +{0x1b41, 0x1b3f, 0x1b35}, +{0x1b43, 0x1b42, 0x1b35}, +{0x1e00, 0x0041, 0x0325}, +{0x1e01, 0x0061, 0x0325}, +{0x1e02, 0x0042, 0x0307}, +{0x1e03, 0x0062, 0x0307}, +{0x1e04, 0x0042, 0x0323}, +{0x1e05, 0x0062, 0x0323}, +{0x1e06, 0x0042, 0x0331}, +{0x1e07, 0x0062, 0x0331}, +{0x1e08, 0x00c7, 0x0301}, +{0x1e09, 0x00e7, 0x0301}, +{0x1e0a, 0x0044, 0x0307}, +{0x1e0b, 0x0064, 0x0307}, +{0x1e0c, 0x0044, 0x0323}, +{0x1e0d, 0x0064, 0x0323}, +{0x1e0e, 0x0044, 0x0331}, +{0x1e0f, 0x0064, 0x0331}, +{0x1e10, 0x0044, 0x0327}, +{0x1e11, 0x0064, 0x0327}, +{0x1e12, 0x0044, 0x032d}, +{0x1e13, 0x0064, 0x032d}, +{0x1e14, 0x0112, 0x0300}, +{0x1e15, 0x0113, 0x0300}, +{0x1e16, 0x0112, 0x0301}, +{0x1e17, 0x0113, 0x0301}, +{0x1e18, 0x0045, 0x032d}, +{0x1e19, 0x0065, 0x032d}, +{0x1e1a, 0x0045, 0x0330}, +{0x1e1b, 0x0065, 0x0330}, +{0x1e1c, 0x0228, 0x0306}, +{0x1e1d, 0x0229, 0x0306}, +{0x1e1e, 0x0046, 0x0307}, +{0x1e1f, 0x0066, 0x0307}, +{0x1e20, 0x0047, 0x0304}, +{0x1e21, 0x0067, 0x0304}, +{0x1e22, 0x0048, 0x0307}, +{0x1e23, 0x0068, 0x0307}, +{0x1e24, 0x0048, 0x0323}, +{0x1e25, 0x0068, 0x0323}, +{0x1e26, 0x0048, 0x0308}, +{0x1e27, 0x0068, 0x0308}, +{0x1e28, 0x0048, 0x0327}, +{0x1e29, 0x0068, 0x0327}, +{0x1e2a, 0x0048, 0x032e}, +{0x1e2b, 0x0068, 0x032e}, +{0x1e2c, 0x0049, 0x0330}, +{0x1e2d, 0x0069, 0x0330}, +{0x1e2e, 0x00cf, 0x0301}, +{0x1e2f, 0x00ef, 0x0301}, +{0x1e30, 0x004b, 0x0301}, +{0x1e31, 0x006b, 0x0301}, +{0x1e32, 0x004b, 0x0323}, +{0x1e33, 0x006b, 0x0323}, +{0x1e34, 0x004b, 0x0331}, +{0x1e35, 0x006b, 0x0331}, +{0x1e36, 0x004c, 0x0323}, +{0x1e37, 0x006c, 0x0323}, +{0x1e38, 0x1e36, 0x0304}, +{0x1e39, 0x1e37, 0x0304}, +{0x1e3a, 0x004c, 0x0331}, +{0x1e3b, 0x006c, 0x0331}, +{0x1e3c, 0x004c, 0x032d}, +{0x1e3d, 0x006c, 0x032d}, +{0x1e3e, 0x004d, 0x0301}, +{0x1e3f, 0x006d, 0x0301}, +{0x1e40, 0x004d, 0x0307}, +{0x1e41, 0x006d, 0x0307}, +{0x1e42, 0x004d, 0x0323}, +{0x1e43, 0x006d, 0x0323}, +{0x1e44, 0x004e, 0x0307}, +{0x1e45, 0x006e, 0x0307}, +{0x1e46, 0x004e, 0x0323}, +{0x1e47, 0x006e, 0x0323}, +{0x1e48, 0x004e, 0x0331}, +{0x1e49, 0x006e, 0x0331}, +{0x1e4a, 0x004e, 0x032d}, +{0x1e4b, 0x006e, 0x032d}, +{0x1e4c, 0x00d5, 0x0301}, +{0x1e4d, 0x00f5, 0x0301}, +{0x1e4e, 0x00d5, 0x0308}, +{0x1e4f, 0x00f5, 0x0308}, +{0x1e50, 0x014c, 0x0300}, +{0x1e51, 0x014d, 0x0300}, +{0x1e52, 0x014c, 0x0301}, +{0x1e53, 0x014d, 0x0301}, +{0x1e54, 0x0050, 0x0301}, +{0x1e55, 0x0070, 0x0301}, +{0x1e56, 0x0050, 0x0307}, +{0x1e57, 0x0070, 0x0307}, +{0x1e58, 0x0052, 0x0307}, +{0x1e59, 0x0072, 0x0307}, +{0x1e5a, 0x0052, 0x0323}, +{0x1e5b, 0x0072, 0x0323}, +{0x1e5c, 0x1e5a, 0x0304}, +{0x1e5d, 0x1e5b, 0x0304}, +{0x1e5e, 0x0052, 0x0331}, +{0x1e5f, 0x0072, 0x0331}, +{0x1e60, 0x0053, 0x0307}, +{0x1e61, 0x0073, 0x0307}, +{0x1e62, 0x0053, 0x0323}, +{0x1e63, 0x0073, 0x0323}, +{0x1e64, 0x015a, 0x0307}, +{0x1e65, 0x015b, 0x0307}, +{0x1e66, 0x0160, 0x0307}, +{0x1e67, 0x0161, 0x0307}, +{0x1e68, 0x1e62, 0x0307}, +{0x1e69, 0x1e63, 0x0307}, +{0x1e6a, 0x0054, 0x0307}, +{0x1e6b, 0x0074, 0x0307}, +{0x1e6c, 0x0054, 0x0323}, +{0x1e6d, 0x0074, 0x0323}, +{0x1e6e, 0x0054, 0x0331}, +{0x1e6f, 0x0074, 0x0331}, +{0x1e70, 0x0054, 0x032d}, +{0x1e71, 0x0074, 0x032d}, +{0x1e72, 0x0055, 0x0324}, +{0x1e73, 0x0075, 0x0324}, +{0x1e74, 0x0055, 0x0330}, +{0x1e75, 0x0075, 0x0330}, +{0x1e76, 0x0055, 0x032d}, +{0x1e77, 0x0075, 0x032d}, +{0x1e78, 0x0168, 0x0301}, +{0x1e79, 0x0169, 0x0301}, +{0x1e7a, 0x016a, 0x0308}, +{0x1e7b, 0x016b, 0x0308}, +{0x1e7c, 0x0056, 0x0303}, +{0x1e7d, 0x0076, 0x0303}, +{0x1e7e, 0x0056, 0x0323}, +{0x1e7f, 0x0076, 0x0323}, +{0x1e80, 0x0057, 0x0300}, +{0x1e81, 0x0077, 0x0300}, +{0x1e82, 0x0057, 0x0301}, +{0x1e83, 0x0077, 0x0301}, +{0x1e84, 0x0057, 0x0308}, +{0x1e85, 0x0077, 0x0308}, +{0x1e86, 0x0057, 0x0307}, +{0x1e87, 0x0077, 0x0307}, +{0x1e88, 0x0057, 0x0323}, +{0x1e89, 0x0077, 0x0323}, +{0x1e8a, 0x0058, 0x0307}, +{0x1e8b, 0x0078, 0x0307}, +{0x1e8c, 0x0058, 0x0308}, +{0x1e8d, 0x0078, 0x0308}, +{0x1e8e, 0x0059, 0x0307}, +{0x1e8f, 0x0079, 0x0307}, +{0x1e90, 0x005a, 0x0302}, +{0x1e91, 0x007a, 0x0302}, +{0x1e92, 0x005a, 0x0323}, +{0x1e93, 0x007a, 0x0323}, +{0x1e94, 0x005a, 0x0331}, +{0x1e95, 0x007a, 0x0331}, +{0x1e96, 0x0068, 0x0331}, +{0x1e97, 0x0074, 0x0308}, +{0x1e98, 0x0077, 0x030a}, +{0x1e99, 0x0079, 0x030a}, +{0x1e9b, 0x017f, 0x0307}, +{0x1ea0, 0x0041, 0x0323}, +{0x1ea1, 0x0061, 0x0323}, +{0x1ea2, 0x0041, 0x0309}, +{0x1ea3, 0x0061, 0x0309}, +{0x1ea4, 0x00c2, 0x0301}, +{0x1ea5, 0x00e2, 0x0301}, +{0x1ea6, 0x00c2, 0x0300}, +{0x1ea7, 0x00e2, 0x0300}, +{0x1ea8, 0x00c2, 0x0309}, +{0x1ea9, 0x00e2, 0x0309}, +{0x1eaa, 0x00c2, 0x0303}, +{0x1eab, 0x00e2, 0x0303}, +{0x1eac, 0x1ea0, 0x0302}, +{0x1ead, 0x1ea1, 0x0302}, +{0x1eae, 0x0102, 0x0301}, +{0x1eaf, 0x0103, 0x0301}, +{0x1eb0, 0x0102, 0x0300}, +{0x1eb1, 0x0103, 0x0300}, +{0x1eb2, 0x0102, 0x0309}, +{0x1eb3, 0x0103, 0x0309}, +{0x1eb4, 0x0102, 0x0303}, +{0x1eb5, 0x0103, 0x0303}, +{0x1eb6, 0x1ea0, 0x0306}, +{0x1eb7, 0x1ea1, 0x0306}, +{0x1eb8, 0x0045, 0x0323}, +{0x1eb9, 0x0065, 0x0323}, +{0x1eba, 0x0045, 0x0309}, +{0x1ebb, 0x0065, 0x0309}, +{0x1ebc, 0x0045, 0x0303}, +{0x1ebd, 0x0065, 0x0303}, +{0x1ebe, 0x00ca, 0x0301}, +{0x1ebf, 0x00ea, 0x0301}, +{0x1ec0, 0x00ca, 0x0300}, +{0x1ec1, 0x00ea, 0x0300}, +{0x1ec2, 0x00ca, 0x0309}, +{0x1ec3, 0x00ea, 0x0309}, +{0x1ec4, 0x00ca, 0x0303}, +{0x1ec5, 0x00ea, 0x0303}, +{0x1ec6, 0x1eb8, 0x0302}, +{0x1ec7, 0x1eb9, 0x0302}, +{0x1ec8, 0x0049, 0x0309}, +{0x1ec9, 0x0069, 0x0309}, +{0x1eca, 0x0049, 0x0323}, +{0x1ecb, 0x0069, 0x0323}, +{0x1ecc, 0x004f, 0x0323}, +{0x1ecd, 0x006f, 0x0323}, +{0x1ece, 0x004f, 0x0309}, +{0x1ecf, 0x006f, 0x0309}, +{0x1ed0, 0x00d4, 0x0301}, +{0x1ed1, 0x00f4, 0x0301}, +{0x1ed2, 0x00d4, 0x0300}, +{0x1ed3, 0x00f4, 0x0300}, +{0x1ed4, 0x00d4, 0x0309}, +{0x1ed5, 0x00f4, 0x0309}, +{0x1ed6, 0x00d4, 0x0303}, +{0x1ed7, 0x00f4, 0x0303}, +{0x1ed8, 0x1ecc, 0x0302}, +{0x1ed9, 0x1ecd, 0x0302}, +{0x1eda, 0x01a0, 0x0301}, +{0x1edb, 0x01a1, 0x0301}, +{0x1edc, 0x01a0, 0x0300}, +{0x1edd, 0x01a1, 0x0300}, +{0x1ede, 0x01a0, 0x0309}, +{0x1edf, 0x01a1, 0x0309}, +{0x1ee0, 0x01a0, 0x0303}, +{0x1ee1, 0x01a1, 0x0303}, +{0x1ee2, 0x01a0, 0x0323}, +{0x1ee3, 0x01a1, 0x0323}, +{0x1ee4, 0x0055, 0x0323}, +{0x1ee5, 0x0075, 0x0323}, +{0x1ee6, 0x0055, 0x0309}, +{0x1ee7, 0x0075, 0x0309}, +{0x1ee8, 0x01af, 0x0301}, +{0x1ee9, 0x01b0, 0x0301}, +{0x1eea, 0x01af, 0x0300}, +{0x1eeb, 0x01b0, 0x0300}, +{0x1eec, 0x01af, 0x0309}, +{0x1eed, 0x01b0, 0x0309}, +{0x1eee, 0x01af, 0x0303}, +{0x1eef, 0x01b0, 0x0303}, +{0x1ef0, 0x01af, 0x0323}, +{0x1ef1, 0x01b0, 0x0323}, +{0x1ef2, 0x0059, 0x0300}, +{0x1ef3, 0x0079, 0x0300}, +{0x1ef4, 0x0059, 0x0323}, +{0x1ef5, 0x0079, 0x0323}, +{0x1ef6, 0x0059, 0x0309}, +{0x1ef7, 0x0079, 0x0309}, +{0x1ef8, 0x0059, 0x0303}, +{0x1ef9, 0x0079, 0x0303}, +{0x1f00, 0x03b1, 0x0313}, +{0x1f01, 0x03b1, 0x0314}, +{0x1f02, 0x1f00, 0x0300}, +{0x1f03, 0x1f01, 0x0300}, +{0x1f04, 0x1f00, 0x0301}, +{0x1f05, 0x1f01, 0x0301}, +{0x1f06, 0x1f00, 0x0342}, +{0x1f07, 0x1f01, 0x0342}, +{0x1f08, 0x0391, 0x0313}, +{0x1f09, 0x0391, 0x0314}, +{0x1f0a, 0x1f08, 0x0300}, +{0x1f0b, 0x1f09, 0x0300}, +{0x1f0c, 0x1f08, 0x0301}, +{0x1f0d, 0x1f09, 0x0301}, +{0x1f0e, 0x1f08, 0x0342}, +{0x1f0f, 0x1f09, 0x0342}, +{0x1f10, 0x03b5, 0x0313}, +{0x1f11, 0x03b5, 0x0314}, +{0x1f12, 0x1f10, 0x0300}, +{0x1f13, 0x1f11, 0x0300}, +{0x1f14, 0x1f10, 0x0301}, +{0x1f15, 0x1f11, 0x0301}, +{0x1f18, 0x0395, 0x0313}, +{0x1f19, 0x0395, 0x0314}, +{0x1f1a, 0x1f18, 0x0300}, +{0x1f1b, 0x1f19, 0x0300}, +{0x1f1c, 0x1f18, 0x0301}, +{0x1f1d, 0x1f19, 0x0301}, +{0x1f20, 0x03b7, 0x0313}, +{0x1f21, 0x03b7, 0x0314}, +{0x1f22, 0x1f20, 0x0300}, +{0x1f23, 0x1f21, 0x0300}, +{0x1f24, 0x1f20, 0x0301}, +{0x1f25, 0x1f21, 0x0301}, +{0x1f26, 0x1f20, 0x0342}, +{0x1f27, 0x1f21, 0x0342}, +{0x1f28, 0x0397, 0x0313}, +{0x1f29, 0x0397, 0x0314}, +{0x1f2a, 0x1f28, 0x0300}, +{0x1f2b, 0x1f29, 0x0300}, +{0x1f2c, 0x1f28, 0x0301}, +{0x1f2d, 0x1f29, 0x0301}, +{0x1f2e, 0x1f28, 0x0342}, +{0x1f2f, 0x1f29, 0x0342}, +{0x1f30, 0x03b9, 0x0313}, +{0x1f31, 0x03b9, 0x0314}, +{0x1f32, 0x1f30, 0x0300}, +{0x1f33, 0x1f31, 0x0300}, +{0x1f34, 0x1f30, 0x0301}, +{0x1f35, 0x1f31, 0x0301}, +{0x1f36, 0x1f30, 0x0342}, +{0x1f37, 0x1f31, 0x0342}, +{0x1f38, 0x0399, 0x0313}, +{0x1f39, 0x0399, 0x0314}, +{0x1f3a, 0x1f38, 0x0300}, +{0x1f3b, 0x1f39, 0x0300}, +{0x1f3c, 0x1f38, 0x0301}, +{0x1f3d, 0x1f39, 0x0301}, +{0x1f3e, 0x1f38, 0x0342}, +{0x1f3f, 0x1f39, 0x0342}, +{0x1f40, 0x03bf, 0x0313}, +{0x1f41, 0x03bf, 0x0314}, +{0x1f42, 0x1f40, 0x0300}, +{0x1f43, 0x1f41, 0x0300}, +{0x1f44, 0x1f40, 0x0301}, +{0x1f45, 0x1f41, 0x0301}, +{0x1f48, 0x039f, 0x0313}, +{0x1f49, 0x039f, 0x0314}, +{0x1f4a, 0x1f48, 0x0300}, +{0x1f4b, 0x1f49, 0x0300}, +{0x1f4c, 0x1f48, 0x0301}, +{0x1f4d, 0x1f49, 0x0301}, +{0x1f50, 0x03c5, 0x0313}, +{0x1f51, 0x03c5, 0x0314}, +{0x1f52, 0x1f50, 0x0300}, +{0x1f53, 0x1f51, 0x0300}, +{0x1f54, 0x1f50, 0x0301}, +{0x1f55, 0x1f51, 0x0301}, +{0x1f56, 0x1f50, 0x0342}, +{0x1f57, 0x1f51, 0x0342}, +{0x1f59, 0x03a5, 0x0314}, +{0x1f5b, 0x1f59, 0x0300}, +{0x1f5d, 0x1f59, 0x0301}, +{0x1f5f, 0x1f59, 0x0342}, +{0x1f60, 0x03c9, 0x0313}, +{0x1f61, 0x03c9, 0x0314}, +{0x1f62, 0x1f60, 0x0300}, +{0x1f63, 0x1f61, 0x0300}, +{0x1f64, 0x1f60, 0x0301}, +{0x1f65, 0x1f61, 0x0301}, +{0x1f66, 0x1f60, 0x0342}, +{0x1f67, 0x1f61, 0x0342}, +{0x1f68, 0x03a9, 0x0313}, +{0x1f69, 0x03a9, 0x0314}, +{0x1f6a, 0x1f68, 0x0300}, +{0x1f6b, 0x1f69, 0x0300}, +{0x1f6c, 0x1f68, 0x0301}, +{0x1f6d, 0x1f69, 0x0301}, +{0x1f6e, 0x1f68, 0x0342}, +{0x1f6f, 0x1f69, 0x0342}, +{0x1f70, 0x03b1, 0x0300}, +{0x1f71, 0x03ac, 0}, +{0x1f72, 0x03b5, 0x0300}, +{0x1f73, 0x03ad, 0}, +{0x1f74, 0x03b7, 0x0300}, +{0x1f75, 0x03ae, 0}, +{0x1f76, 0x03b9, 0x0300}, +{0x1f77, 0x03af, 0}, +{0x1f78, 0x03bf, 0x0300}, +{0x1f79, 0x03cc, 0}, +{0x1f7a, 0x03c5, 0x0300}, +{0x1f7b, 0x03cd, 0}, +{0x1f7c, 0x03c9, 0x0300}, +{0x1f7d, 0x03ce, 0}, +{0x1f80, 0x1f00, 0x0345}, +{0x1f81, 0x1f01, 0x0345}, +{0x1f82, 0x1f02, 0x0345}, +{0x1f83, 0x1f03, 0x0345}, +{0x1f84, 0x1f04, 0x0345}, +{0x1f85, 0x1f05, 0x0345}, +{0x1f86, 0x1f06, 0x0345}, +{0x1f87, 0x1f07, 0x0345}, +{0x1f88, 0x1f08, 0x0345}, +{0x1f89, 0x1f09, 0x0345}, +{0x1f8a, 0x1f0a, 0x0345}, +{0x1f8b, 0x1f0b, 0x0345}, +{0x1f8c, 0x1f0c, 0x0345}, +{0x1f8d, 0x1f0d, 0x0345}, +{0x1f8e, 0x1f0e, 0x0345}, +{0x1f8f, 0x1f0f, 0x0345}, +{0x1f90, 0x1f20, 0x0345}, +{0x1f91, 0x1f21, 0x0345}, +{0x1f92, 0x1f22, 0x0345}, +{0x1f93, 0x1f23, 0x0345}, +{0x1f94, 0x1f24, 0x0345}, +{0x1f95, 0x1f25, 0x0345}, +{0x1f96, 0x1f26, 0x0345}, +{0x1f97, 0x1f27, 0x0345}, +{0x1f98, 0x1f28, 0x0345}, +{0x1f99, 0x1f29, 0x0345}, +{0x1f9a, 0x1f2a, 0x0345}, +{0x1f9b, 0x1f2b, 0x0345}, +{0x1f9c, 0x1f2c, 0x0345}, +{0x1f9d, 0x1f2d, 0x0345}, +{0x1f9e, 0x1f2e, 0x0345}, +{0x1f9f, 0x1f2f, 0x0345}, +{0x1fa0, 0x1f60, 0x0345}, +{0x1fa1, 0x1f61, 0x0345}, +{0x1fa2, 0x1f62, 0x0345}, +{0x1fa3, 0x1f63, 0x0345}, +{0x1fa4, 0x1f64, 0x0345}, +{0x1fa5, 0x1f65, 0x0345}, +{0x1fa6, 0x1f66, 0x0345}, +{0x1fa7, 0x1f67, 0x0345}, +{0x1fa8, 0x1f68, 0x0345}, +{0x1fa9, 0x1f69, 0x0345}, +{0x1faa, 0x1f6a, 0x0345}, +{0x1fab, 0x1f6b, 0x0345}, +{0x1fac, 0x1f6c, 0x0345}, +{0x1fad, 0x1f6d, 0x0345}, +{0x1fae, 0x1f6e, 0x0345}, +{0x1faf, 0x1f6f, 0x0345}, +{0x1fb0, 0x03b1, 0x0306}, +{0x1fb1, 0x03b1, 0x0304}, +{0x1fb2, 0x1f70, 0x0345}, +{0x1fb3, 0x03b1, 0x0345}, +{0x1fb4, 0x03ac, 0x0345}, +{0x1fb6, 0x03b1, 0x0342}, +{0x1fb7, 0x1fb6, 0x0345}, +{0x1fb8, 0x0391, 0x0306}, +{0x1fb9, 0x0391, 0x0304}, +{0x1fba, 0x0391, 0x0300}, +{0x1fbb, 0x0386, 0}, +{0x1fbc, 0x0391, 0x0345}, +{0x1fbe, 0x03b9, 0}, +{0x1fc1, 0x00a8, 0x0342}, +{0x1fc2, 0x1f74, 0x0345}, +{0x1fc3, 0x03b7, 0x0345}, +{0x1fc4, 0x03ae, 0x0345}, +{0x1fc6, 0x03b7, 0x0342}, +{0x1fc7, 0x1fc6, 0x0345}, +{0x1fc8, 0x0395, 0x0300}, +{0x1fc9, 0x0388, 0}, +{0x1fca, 0x0397, 0x0300}, +{0x1fcb, 0x0389, 0}, +{0x1fcc, 0x0397, 0x0345}, +{0x1fcd, 0x1fbf, 0x0300}, +{0x1fce, 0x1fbf, 0x0301}, +{0x1fcf, 0x1fbf, 0x0342}, +{0x1fd0, 0x03b9, 0x0306}, +{0x1fd1, 0x03b9, 0x0304}, +{0x1fd2, 0x03ca, 0x0300}, +{0x1fd3, 0x0390, 0}, +{0x1fd6, 0x03b9, 0x0342}, +{0x1fd7, 0x03ca, 0x0342}, +{0x1fd8, 0x0399, 0x0306}, +{0x1fd9, 0x0399, 0x0304}, +{0x1fda, 0x0399, 0x0300}, +{0x1fdb, 0x038a, 0}, +{0x1fdd, 0x1ffe, 0x0300}, +{0x1fde, 0x1ffe, 0x0301}, +{0x1fdf, 0x1ffe, 0x0342}, +{0x1fe0, 0x03c5, 0x0306}, +{0x1fe1, 0x03c5, 0x0304}, +{0x1fe2, 0x03cb, 0x0300}, +{0x1fe3, 0x03b0, 0}, +{0x1fe4, 0x03c1, 0x0313}, +{0x1fe5, 0x03c1, 0x0314}, +{0x1fe6, 0x03c5, 0x0342}, +{0x1fe7, 0x03cb, 0x0342}, +{0x1fe8, 0x03a5, 0x0306}, +{0x1fe9, 0x03a5, 0x0304}, +{0x1fea, 0x03a5, 0x0300}, +{0x1feb, 0x038e, 0}, +{0x1fec, 0x03a1, 0x0314}, +{0x1fed, 0x00a8, 0x0300}, +{0x1fee, 0x0385, 0}, +{0x1fef, 0x0060, 0}, +{0x1ff2, 0x1f7c, 0x0345}, +{0x1ff3, 0x03c9, 0x0345}, +{0x1ff4, 0x03ce, 0x0345}, +{0x1ff6, 0x03c9, 0x0342}, +{0x1ff7, 0x1ff6, 0x0345}, +{0x1ff8, 0x039f, 0x0300}, +{0x1ff9, 0x038c, 0}, +{0x1ffa, 0x03a9, 0x0300}, +{0x1ffb, 0x038f, 0}, +{0x1ffc, 0x03a9, 0x0345}, +{0x1ffd, 0x00b4, 0}, +{0x2000, 0x2002, 0}, +{0x2001, 0x2003, 0}, +{0x2126, 0x03a9, 0}, +{0x212a, 0x004b, 0}, +{0x212b, 0x00c5, 0}, +{0x219a, 0x2190, 0x0338}, +{0x219b, 0x2192, 0x0338}, +{0x21ae, 0x2194, 0x0338}, +{0x21cd, 0x21d0, 0x0338}, +{0x21ce, 0x21d4, 0x0338}, +{0x21cf, 0x21d2, 0x0338}, +{0x2204, 0x2203, 0x0338}, +{0x2209, 0x2208, 0x0338}, +{0x220c, 0x220b, 0x0338}, +{0x2224, 0x2223, 0x0338}, +{0x2226, 0x2225, 0x0338}, +{0x2241, 0x223c, 0x0338}, +{0x2244, 0x2243, 0x0338}, +{0x2247, 0x2245, 0x0338}, +{0x2249, 0x2248, 0x0338}, +{0x2260, 0x003d, 0x0338}, +{0x2262, 0x2261, 0x0338}, +{0x226d, 0x224d, 0x0338}, +{0x226e, 0x003c, 0x0338}, +{0x226f, 0x003e, 0x0338}, +{0x2270, 0x2264, 0x0338}, +{0x2271, 0x2265, 0x0338}, +{0x2274, 0x2272, 0x0338}, +{0x2275, 0x2273, 0x0338}, +{0x2278, 0x2276, 0x0338}, +{0x2279, 0x2277, 0x0338}, +{0x2280, 0x227a, 0x0338}, +{0x2281, 0x227b, 0x0338}, +{0x2284, 0x2282, 0x0338}, +{0x2285, 0x2283, 0x0338}, +{0x2288, 0x2286, 0x0338}, +{0x2289, 0x2287, 0x0338}, +{0x22ac, 0x22a2, 0x0338}, +{0x22ad, 0x22a8, 0x0338}, +{0x22ae, 0x22a9, 0x0338}, +{0x22af, 0x22ab, 0x0338}, +{0x22e0, 0x227c, 0x0338}, +{0x22e1, 0x227d, 0x0338}, +{0x22e2, 0x2291, 0x0338}, +{0x22e3, 0x2292, 0x0338}, +{0x22ea, 0x22b2, 0x0338}, +{0x22eb, 0x22b3, 0x0338}, +{0x22ec, 0x22b4, 0x0338}, +{0x22ed, 0x22b5, 0x0338}, +{0x2329, 0x3008, 0}, +{0x232a, 0x3009, 0}, +{0x2adc, 0x2add, 0x0338}, +{0x304c, 0x304b, 0x3099}, +{0x304e, 0x304d, 0x3099}, +{0x3050, 0x304f, 0x3099}, +{0x3052, 0x3051, 0x3099}, +{0x3054, 0x3053, 0x3099}, +{0x3056, 0x3055, 0x3099}, +{0x3058, 0x3057, 0x3099}, +{0x305a, 0x3059, 0x3099}, +{0x305c, 0x305b, 0x3099}, +{0x305e, 0x305d, 0x3099}, +{0x3060, 0x305f, 0x3099}, +{0x3062, 0x3061, 0x3099}, +{0x3065, 0x3064, 0x3099}, +{0x3067, 0x3066, 0x3099}, +{0x3069, 0x3068, 0x3099}, +{0x3070, 0x306f, 0x3099}, +{0x3071, 0x306f, 0x309a}, +{0x3073, 0x3072, 0x3099}, +{0x3074, 0x3072, 0x309a}, +{0x3076, 0x3075, 0x3099}, +{0x3077, 0x3075, 0x309a}, +{0x3079, 0x3078, 0x3099}, +{0x307a, 0x3078, 0x309a}, +{0x307c, 0x307b, 0x3099}, +{0x307d, 0x307b, 0x309a}, +{0x3094, 0x3046, 0x3099}, +{0x309e, 0x309d, 0x3099}, +{0x30ac, 0x30ab, 0x3099}, +{0x30ae, 0x30ad, 0x3099}, +{0x30b0, 0x30af, 0x3099}, +{0x30b2, 0x30b1, 0x3099}, +{0x30b4, 0x30b3, 0x3099}, +{0x30b6, 0x30b5, 0x3099}, +{0x30b8, 0x30b7, 0x3099}, +{0x30ba, 0x30b9, 0x3099}, +{0x30bc, 0x30bb, 0x3099}, +{0x30be, 0x30bd, 0x3099}, +{0x30c0, 0x30bf, 0x3099}, +{0x30c2, 0x30c1, 0x3099}, +{0x30c5, 0x30c4, 0x3099}, +{0x30c7, 0x30c6, 0x3099}, +{0x30c9, 0x30c8, 0x3099}, +{0x30d0, 0x30cf, 0x3099}, +{0x30d1, 0x30cf, 0x309a}, +{0x30d3, 0x30d2, 0x3099}, +{0x30d4, 0x30d2, 0x309a}, +{0x30d6, 0x30d5, 0x3099}, +{0x30d7, 0x30d5, 0x309a}, +{0x30d9, 0x30d8, 0x3099}, +{0x30da, 0x30d8, 0x309a}, +{0x30dc, 0x30db, 0x3099}, +{0x30dd, 0x30db, 0x309a}, +{0x30f4, 0x30a6, 0x3099}, +{0x30f7, 0x30ef, 0x3099}, +{0x30f8, 0x30f0, 0x3099}, +{0x30f9, 0x30f1, 0x3099}, +{0x30fa, 0x30f2, 0x3099}, +{0x30fe, 0x30fd, 0x3099}, +{0xf900, 0x8c48, 0}, +{0xf901, 0x66f4, 0}, +{0xf902, 0x8eca, 0}, +{0xf903, 0x8cc8, 0}, +{0xf904, 0x6ed1, 0}, +{0xf905, 0x4e32, 0}, +{0xf906, 0x53e5, 0}, +{0xf907, 0x9f9c, 0}, +{0xf908, 0x9f9c, 0}, +{0xf909, 0x5951, 0}, +{0xf90a, 0x91d1, 0}, +{0xf90b, 0x5587, 0}, +{0xf90c, 0x5948, 0}, +{0xf90d, 0x61f6, 0}, +{0xf90e, 0x7669, 0}, +{0xf90f, 0x7f85, 0}, +{0xf910, 0x863f, 0}, +{0xf911, 0x87ba, 0}, +{0xf912, 0x88f8, 0}, +{0xf913, 0x908f, 0}, +{0xf914, 0x6a02, 0}, +{0xf915, 0x6d1b, 0}, +{0xf916, 0x70d9, 0}, +{0xf917, 0x73de, 0}, +{0xf918, 0x843d, 0}, +{0xf919, 0x916a, 0}, +{0xf91a, 0x99f1, 0}, +{0xf91b, 0x4e82, 0}, +{0xf91c, 0x5375, 0}, +{0xf91d, 0x6b04, 0}, +{0xf91e, 0x721b, 0}, +{0xf91f, 0x862d, 0}, +{0xf920, 0x9e1e, 0}, +{0xf921, 0x5d50, 0}, +{0xf922, 0x6feb, 0}, +{0xf923, 0x85cd, 0}, +{0xf924, 0x8964, 0}, +{0xf925, 0x62c9, 0}, +{0xf926, 0x81d8, 0}, +{0xf927, 0x881f, 0}, +{0xf928, 0x5eca, 0}, +{0xf929, 0x6717, 0}, +{0xf92a, 0x6d6a, 0}, +{0xf92b, 0x72fc, 0}, +{0xf92c, 0x90ce, 0}, +{0xf92d, 0x4f86, 0}, +{0xf92e, 0x51b7, 0}, +{0xf92f, 0x52de, 0}, +{0xf930, 0x64c4, 0}, +{0xf931, 0x6ad3, 0}, +{0xf932, 0x7210, 0}, +{0xf933, 0x76e7, 0}, +{0xf934, 0x8001, 0}, +{0xf935, 0x8606, 0}, +{0xf936, 0x865c, 0}, +{0xf937, 0x8def, 0}, +{0xf938, 0x9732, 0}, +{0xf939, 0x9b6f, 0}, +{0xf93a, 0x9dfa, 0}, +{0xf93b, 0x788c, 0}, +{0xf93c, 0x797f, 0}, +{0xf93d, 0x7da0, 0}, +{0xf93e, 0x83c9, 0}, +{0xf93f, 0x9304, 0}, +{0xf940, 0x9e7f, 0}, +{0xf941, 0x8ad6, 0}, +{0xf942, 0x58df, 0}, +{0xf943, 0x5f04, 0}, +{0xf944, 0x7c60, 0}, +{0xf945, 0x807e, 0}, +{0xf946, 0x7262, 0}, +{0xf947, 0x78ca, 0}, +{0xf948, 0x8cc2, 0}, +{0xf949, 0x96f7, 0}, +{0xf94a, 0x58d8, 0}, +{0xf94b, 0x5c62, 0}, +{0xf94c, 0x6a13, 0}, +{0xf94d, 0x6dda, 0}, +{0xf94e, 0x6f0f, 0}, +{0xf94f, 0x7d2f, 0}, +{0xf950, 0x7e37, 0}, +{0xf951, 0x964b, 0}, +{0xf952, 0x52d2, 0}, +{0xf953, 0x808b, 0}, +{0xf954, 0x51dc, 0}, +{0xf955, 0x51cc, 0}, +{0xf956, 0x7a1c, 0}, +{0xf957, 0x7dbe, 0}, +{0xf958, 0x83f1, 0}, +{0xf959, 0x9675, 0}, +{0xf95a, 0x8b80, 0}, +{0xf95b, 0x62cf, 0}, +{0xf95c, 0x6a02, 0}, +{0xf95d, 0x8afe, 0}, +{0xf95e, 0x4e39, 0}, +{0xf95f, 0x5be7, 0}, +{0xf960, 0x6012, 0}, +{0xf961, 0x7387, 0}, +{0xf962, 0x7570, 0}, +{0xf963, 0x5317, 0}, +{0xf964, 0x78fb, 0}, +{0xf965, 0x4fbf, 0}, +{0xf966, 0x5fa9, 0}, +{0xf967, 0x4e0d, 0}, +{0xf968, 0x6ccc, 0}, +{0xf969, 0x6578, 0}, +{0xf96a, 0x7d22, 0}, +{0xf96b, 0x53c3, 0}, +{0xf96c, 0x585e, 0}, +{0xf96d, 0x7701, 0}, +{0xf96e, 0x8449, 0}, +{0xf96f, 0x8aaa, 0}, +{0xf970, 0x6bba, 0}, +{0xf971, 0x8fb0, 0}, +{0xf972, 0x6c88, 0}, +{0xf973, 0x62fe, 0}, +{0xf974, 0x82e5, 0}, +{0xf975, 0x63a0, 0}, +{0xf976, 0x7565, 0}, +{0xf977, 0x4eae, 0}, +{0xf978, 0x5169, 0}, +{0xf979, 0x51c9, 0}, +{0xf97a, 0x6881, 0}, +{0xf97b, 0x7ce7, 0}, +{0xf97c, 0x826f, 0}, +{0xf97d, 0x8ad2, 0}, +{0xf97e, 0x91cf, 0}, +{0xf97f, 0x52f5, 0}, +{0xf980, 0x5442, 0}, +{0xf981, 0x5973, 0}, +{0xf982, 0x5eec, 0}, +{0xf983, 0x65c5, 0}, +{0xf984, 0x6ffe, 0}, +{0xf985, 0x792a, 0}, +{0xf986, 0x95ad, 0}, +{0xf987, 0x9a6a, 0}, +{0xf988, 0x9e97, 0}, +{0xf989, 0x9ece, 0}, +{0xf98a, 0x529b, 0}, +{0xf98b, 0x66c6, 0}, +{0xf98c, 0x6b77, 0}, +{0xf98d, 0x8f62, 0}, +{0xf98e, 0x5e74, 0}, +{0xf98f, 0x6190, 0}, +{0xf990, 0x6200, 0}, +{0xf991, 0x649a, 0}, +{0xf992, 0x6f23, 0}, +{0xf993, 0x7149, 0}, +{0xf994, 0x7489, 0}, +{0xf995, 0x79ca, 0}, +{0xf996, 0x7df4, 0}, +{0xf997, 0x806f, 0}, +{0xf998, 0x8f26, 0}, +{0xf999, 0x84ee, 0}, +{0xf99a, 0x9023, 0}, +{0xf99b, 0x934a, 0}, +{0xf99c, 0x5217, 0}, +{0xf99d, 0x52a3, 0}, +{0xf99e, 0x54bd, 0}, +{0xf99f, 0x70c8, 0}, +{0xf9a0, 0x88c2, 0}, +{0xf9a1, 0x8aaa, 0}, +{0xf9a2, 0x5ec9, 0}, +{0xf9a3, 0x5ff5, 0}, +{0xf9a4, 0x637b, 0}, +{0xf9a5, 0x6bae, 0}, +{0xf9a6, 0x7c3e, 0}, +{0xf9a7, 0x7375, 0}, +{0xf9a8, 0x4ee4, 0}, +{0xf9a9, 0x56f9, 0}, +{0xf9aa, 0x5be7, 0}, +{0xf9ab, 0x5dba, 0}, +{0xf9ac, 0x601c, 0}, +{0xf9ad, 0x73b2, 0}, +{0xf9ae, 0x7469, 0}, +{0xf9af, 0x7f9a, 0}, +{0xf9b0, 0x8046, 0}, +{0xf9b1, 0x9234, 0}, +{0xf9b2, 0x96f6, 0}, +{0xf9b3, 0x9748, 0}, +{0xf9b4, 0x9818, 0}, +{0xf9b5, 0x4f8b, 0}, +{0xf9b6, 0x79ae, 0}, +{0xf9b7, 0x91b4, 0}, +{0xf9b8, 0x96b8, 0}, +{0xf9b9, 0x60e1, 0}, +{0xf9ba, 0x4e86, 0}, +{0xf9bb, 0x50da, 0}, +{0xf9bc, 0x5bee, 0}, +{0xf9bd, 0x5c3f, 0}, +{0xf9be, 0x6599, 0}, +{0xf9bf, 0x6a02, 0}, +{0xf9c0, 0x71ce, 0}, +{0xf9c1, 0x7642, 0}, +{0xf9c2, 0x84fc, 0}, +{0xf9c3, 0x907c, 0}, +{0xf9c4, 0x9f8d, 0}, +{0xf9c5, 0x6688, 0}, +{0xf9c6, 0x962e, 0}, +{0xf9c7, 0x5289, 0}, +{0xf9c8, 0x677b, 0}, +{0xf9c9, 0x67f3, 0}, +{0xf9ca, 0x6d41, 0}, +{0xf9cb, 0x6e9c, 0}, +{0xf9cc, 0x7409, 0}, +{0xf9cd, 0x7559, 0}, +{0xf9ce, 0x786b, 0}, +{0xf9cf, 0x7d10, 0}, +{0xf9d0, 0x985e, 0}, +{0xf9d1, 0x516d, 0}, +{0xf9d2, 0x622e, 0}, +{0xf9d3, 0x9678, 0}, +{0xf9d4, 0x502b, 0}, +{0xf9d5, 0x5d19, 0}, +{0xf9d6, 0x6dea, 0}, +{0xf9d7, 0x8f2a, 0}, +{0xf9d8, 0x5f8b, 0}, +{0xf9d9, 0x6144, 0}, +{0xf9da, 0x6817, 0}, +{0xf9db, 0x7387, 0}, +{0xf9dc, 0x9686, 0}, +{0xf9dd, 0x5229, 0}, +{0xf9de, 0x540f, 0}, +{0xf9df, 0x5c65, 0}, +{0xf9e0, 0x6613, 0}, +{0xf9e1, 0x674e, 0}, +{0xf9e2, 0x68a8, 0}, +{0xf9e3, 0x6ce5, 0}, +{0xf9e4, 0x7406, 0}, +{0xf9e5, 0x75e2, 0}, +{0xf9e6, 0x7f79, 0}, +{0xf9e7, 0x88cf, 0}, +{0xf9e8, 0x88e1, 0}, +{0xf9e9, 0x91cc, 0}, +{0xf9ea, 0x96e2, 0}, +{0xf9eb, 0x533f, 0}, +{0xf9ec, 0x6eba, 0}, +{0xf9ed, 0x541d, 0}, +{0xf9ee, 0x71d0, 0}, +{0xf9ef, 0x7498, 0}, +{0xf9f0, 0x85fa, 0}, +{0xf9f1, 0x96a3, 0}, +{0xf9f2, 0x9c57, 0}, +{0xf9f3, 0x9e9f, 0}, +{0xf9f4, 0x6797, 0}, +{0xf9f5, 0x6dcb, 0}, +{0xf9f6, 0x81e8, 0}, +{0xf9f7, 0x7acb, 0}, +{0xf9f8, 0x7b20, 0}, +{0xf9f9, 0x7c92, 0}, +{0xf9fa, 0x72c0, 0}, +{0xf9fb, 0x7099, 0}, +{0xf9fc, 0x8b58, 0}, +{0xf9fd, 0x4ec0, 0}, +{0xf9fe, 0x8336, 0}, +{0xf9ff, 0x523a, 0}, +{0xfa00, 0x5207, 0}, +{0xfa01, 0x5ea6, 0}, +{0xfa02, 0x62d3, 0}, +{0xfa03, 0x7cd6, 0}, +{0xfa04, 0x5b85, 0}, +{0xfa05, 0x6d1e, 0}, +{0xfa06, 0x66b4, 0}, +{0xfa07, 0x8f3b, 0}, +{0xfa08, 0x884c, 0}, +{0xfa09, 0x964d, 0}, +{0xfa0a, 0x898b, 0}, +{0xfa0b, 0x5ed3, 0}, +{0xfa0c, 0x5140, 0}, +{0xfa0d, 0x55c0, 0}, +{0xfa10, 0x585a, 0}, +{0xfa12, 0x6674, 0}, +{0xfa15, 0x51de, 0}, +{0xfa16, 0x732a, 0}, +{0xfa17, 0x76ca, 0}, +{0xfa18, 0x793c, 0}, +{0xfa19, 0x795e, 0}, +{0xfa1a, 0x7965, 0}, +{0xfa1b, 0x798f, 0}, +{0xfa1c, 0x9756, 0}, +{0xfa1d, 0x7cbe, 0}, +{0xfa1e, 0x7fbd, 0}, +{0xfa20, 0x8612, 0}, +{0xfa22, 0x8af8, 0}, +{0xfa25, 0x9038, 0}, +{0xfa26, 0x90fd, 0}, +{0xfa2a, 0x98ef, 0}, +{0xfa2b, 0x98fc, 0}, +{0xfa2c, 0x9928, 0}, +{0xfa2d, 0x9db4, 0}, +{0xfa2e, 0x90de, 0}, +{0xfa2f, 0x96b7, 0}, +{0xfa30, 0x4fae, 0}, +{0xfa31, 0x50e7, 0}, +{0xfa32, 0x514d, 0}, +{0xfa33, 0x52c9, 0}, +{0xfa34, 0x52e4, 0}, +{0xfa35, 0x5351, 0}, +{0xfa36, 0x559d, 0}, +{0xfa37, 0x5606, 0}, +{0xfa38, 0x5668, 0}, +{0xfa39, 0x5840, 0}, +{0xfa3a, 0x58a8, 0}, +{0xfa3b, 0x5c64, 0}, +{0xfa3c, 0x5c6e, 0}, +{0xfa3d, 0x6094, 0}, +{0xfa3e, 0x6168, 0}, +{0xfa3f, 0x618e, 0}, +{0xfa40, 0x61f2, 0}, +{0xfa41, 0x654f, 0}, +{0xfa42, 0x65e2, 0}, +{0xfa43, 0x6691, 0}, +{0xfa44, 0x6885, 0}, +{0xfa45, 0x6d77, 0}, +{0xfa46, 0x6e1a, 0}, +{0xfa47, 0x6f22, 0}, +{0xfa48, 0x716e, 0}, +{0xfa49, 0x722b, 0}, +{0xfa4a, 0x7422, 0}, +{0xfa4b, 0x7891, 0}, +{0xfa4c, 0x793e, 0}, +{0xfa4d, 0x7949, 0}, +{0xfa4e, 0x7948, 0}, +{0xfa4f, 0x7950, 0}, +{0xfa50, 0x7956, 0}, +{0xfa51, 0x795d, 0}, +{0xfa52, 0x798d, 0}, +{0xfa53, 0x798e, 0}, +{0xfa54, 0x7a40, 0}, +{0xfa55, 0x7a81, 0}, +{0xfa56, 0x7bc0, 0}, +{0xfa57, 0x7df4, 0}, +{0xfa58, 0x7e09, 0}, +{0xfa59, 0x7e41, 0}, +{0xfa5a, 0x7f72, 0}, +{0xfa5b, 0x8005, 0}, +{0xfa5c, 0x81ed, 0}, +{0xfa5d, 0x8279, 0}, +{0xfa5e, 0x8279, 0}, +{0xfa5f, 0x8457, 0}, +{0xfa60, 0x8910, 0}, +{0xfa61, 0x8996, 0}, +{0xfa62, 0x8b01, 0}, +{0xfa63, 0x8b39, 0}, +{0xfa64, 0x8cd3, 0}, +{0xfa65, 0x8d08, 0}, +{0xfa66, 0x8fb6, 0}, +{0xfa67, 0x9038, 0}, +{0xfa68, 0x96e3, 0}, +{0xfa69, 0x97ff, 0}, +{0xfa6a, 0x983b, 0}, +{0xfa6b, 0x6075, 0}, +{0xfa6c, 0x242ee, 0}, +{0xfa6d, 0x8218, 0}, +{0xfa70, 0x4e26, 0}, +{0xfa71, 0x51b5, 0}, +{0xfa72, 0x5168, 0}, +{0xfa73, 0x4f80, 0}, +{0xfa74, 0x5145, 0}, +{0xfa75, 0x5180, 0}, +{0xfa76, 0x52c7, 0}, +{0xfa77, 0x52fa, 0}, +{0xfa78, 0x559d, 0}, +{0xfa79, 0x5555, 0}, +{0xfa7a, 0x5599, 0}, +{0xfa7b, 0x55e2, 0}, +{0xfa7c, 0x585a, 0}, +{0xfa7d, 0x58b3, 0}, +{0xfa7e, 0x5944, 0}, +{0xfa7f, 0x5954, 0}, +{0xfa80, 0x5a62, 0}, +{0xfa81, 0x5b28, 0}, +{0xfa82, 0x5ed2, 0}, +{0xfa83, 0x5ed9, 0}, +{0xfa84, 0x5f69, 0}, +{0xfa85, 0x5fad, 0}, +{0xfa86, 0x60d8, 0}, +{0xfa87, 0x614e, 0}, +{0xfa88, 0x6108, 0}, +{0xfa89, 0x618e, 0}, +{0xfa8a, 0x6160, 0}, +{0xfa8b, 0x61f2, 0}, +{0xfa8c, 0x6234, 0}, +{0xfa8d, 0x63c4, 0}, +{0xfa8e, 0x641c, 0}, +{0xfa8f, 0x6452, 0}, +{0xfa90, 0x6556, 0}, +{0xfa91, 0x6674, 0}, +{0xfa92, 0x6717, 0}, +{0xfa93, 0x671b, 0}, +{0xfa94, 0x6756, 0}, +{0xfa95, 0x6b79, 0}, +{0xfa96, 0x6bba, 0}, +{0xfa97, 0x6d41, 0}, +{0xfa98, 0x6edb, 0}, +{0xfa99, 0x6ecb, 0}, +{0xfa9a, 0x6f22, 0}, +{0xfa9b, 0x701e, 0}, +{0xfa9c, 0x716e, 0}, +{0xfa9d, 0x77a7, 0}, +{0xfa9e, 0x7235, 0}, +{0xfa9f, 0x72af, 0}, +{0xfaa0, 0x732a, 0}, +{0xfaa1, 0x7471, 0}, +{0xfaa2, 0x7506, 0}, +{0xfaa3, 0x753b, 0}, +{0xfaa4, 0x761d, 0}, +{0xfaa5, 0x761f, 0}, +{0xfaa6, 0x76ca, 0}, +{0xfaa7, 0x76db, 0}, +{0xfaa8, 0x76f4, 0}, +{0xfaa9, 0x774a, 0}, +{0xfaaa, 0x7740, 0}, +{0xfaab, 0x78cc, 0}, +{0xfaac, 0x7ab1, 0}, +{0xfaad, 0x7bc0, 0}, +{0xfaae, 0x7c7b, 0}, +{0xfaaf, 0x7d5b, 0}, +{0xfab0, 0x7df4, 0}, +{0xfab1, 0x7f3e, 0}, +{0xfab2, 0x8005, 0}, +{0xfab3, 0x8352, 0}, +{0xfab4, 0x83ef, 0}, +{0xfab5, 0x8779, 0}, +{0xfab6, 0x8941, 0}, +{0xfab7, 0x8986, 0}, +{0xfab8, 0x8996, 0}, +{0xfab9, 0x8abf, 0}, +{0xfaba, 0x8af8, 0}, +{0xfabb, 0x8acb, 0}, +{0xfabc, 0x8b01, 0}, +{0xfabd, 0x8afe, 0}, +{0xfabe, 0x8aed, 0}, +{0xfabf, 0x8b39, 0}, +{0xfac0, 0x8b8a, 0}, +{0xfac1, 0x8d08, 0}, +{0xfac2, 0x8f38, 0}, +{0xfac3, 0x9072, 0}, +{0xfac4, 0x9199, 0}, +{0xfac5, 0x9276, 0}, +{0xfac6, 0x967c, 0}, +{0xfac7, 0x96e3, 0}, +{0xfac8, 0x9756, 0}, +{0xfac9, 0x97db, 0}, +{0xfaca, 0x97ff, 0}, +{0xfacb, 0x980b, 0}, +{0xfacc, 0x983b, 0}, +{0xfacd, 0x9b12, 0}, +{0xface, 0x9f9c, 0}, +{0xfacf, 0x2284a, 0}, +{0xfad0, 0x22844, 0}, +{0xfad1, 0x233d5, 0}, +{0xfad2, 0x3b9d, 0}, +{0xfad3, 0x4018, 0}, +{0xfad4, 0x4039, 0}, +{0xfad5, 0x25249, 0}, +{0xfad6, 0x25cd0, 0}, +{0xfad7, 0x27ed3, 0}, +{0xfad8, 0x9f43, 0}, +{0xfad9, 0x9f8e, 0}, +{0xfb1d, 0x05d9, 0x05b4}, +{0xfb1f, 0x05f2, 0x05b7}, +{0xfb2a, 0x05e9, 0x05c1}, +{0xfb2b, 0x05e9, 0x05c2}, +{0xfb2c, 0xfb49, 0x05c1}, +{0xfb2d, 0xfb49, 0x05c2}, +{0xfb2e, 0x05d0, 0x05b7}, +{0xfb2f, 0x05d0, 0x05b8}, +{0xfb30, 0x05d0, 0x05bc}, +{0xfb31, 0x05d1, 0x05bc}, +{0xfb32, 0x05d2, 0x05bc}, +{0xfb33, 0x05d3, 0x05bc}, +{0xfb34, 0x05d4, 0x05bc}, +{0xfb35, 0x05d5, 0x05bc}, +{0xfb36, 0x05d6, 0x05bc}, +{0xfb38, 0x05d8, 0x05bc}, +{0xfb39, 0x05d9, 0x05bc}, +{0xfb3a, 0x05da, 0x05bc}, +{0xfb3b, 0x05db, 0x05bc}, +{0xfb3c, 0x05dc, 0x05bc}, +{0xfb3e, 0x05de, 0x05bc}, +{0xfb40, 0x05e0, 0x05bc}, +{0xfb41, 0x05e1, 0x05bc}, +{0xfb43, 0x05e3, 0x05bc}, +{0xfb44, 0x05e4, 0x05bc}, +{0xfb46, 0x05e6, 0x05bc}, +{0xfb47, 0x05e7, 0x05bc}, +{0xfb48, 0x05e8, 0x05bc}, +{0xfb49, 0x05e9, 0x05bc}, +{0xfb4a, 0x05ea, 0x05bc}, +{0xfb4b, 0x05d5, 0x05b9}, +{0xfb4c, 0x05d1, 0x05bf}, +{0xfb4d, 0x05db, 0x05bf}, +{0xfb4e, 0x05e4, 0x05bf}, +{0x105c9, 0x105d2, 0x0307}, +{0x105e4, 0x105da, 0x0307}, +{0x1109a, 0x11099, 0x110ba}, +{0x1109c, 0x1109b, 0x110ba}, +{0x110ab, 0x110a5, 0x110ba}, +{0x1112e, 0x11131, 0x11127}, +{0x1112f, 0x11132, 0x11127}, +{0x1134b, 0x11347, 0x1133e}, +{0x1134c, 0x11347, 0x11357}, +{0x11383, 0x11382, 0x113c9}, +{0x11385, 0x11384, 0x113bb}, +{0x1138e, 0x1138b, 0x113c2}, +{0x11391, 0x11390, 0x113c9}, +{0x113c5, 0x113c2, 0x113c2}, +{0x113c7, 0x113c2, 0x113b8}, +{0x113c8, 0x113c2, 0x113c9}, +{0x114bb, 0x114b9, 0x114ba}, +{0x114bc, 0x114b9, 0x114b0}, +{0x114be, 0x114b9, 0x114bd}, +{0x115ba, 0x115b8, 0x115af}, +{0x115bb, 0x115b9, 0x115af}, +{0x11938, 0x11935, 0x11930}, +{0x16121, 0x1611e, 0x1611e}, +{0x16122, 0x1611e, 0x16129}, +{0x16123, 0x1611e, 0x1611f}, +{0x16124, 0x16129, 0x1611f}, +{0x16125, 0x1611e, 0x16120}, +{0x16126, 0x16121, 0x1611f}, +{0x16127, 0x16122, 0x1611f}, +{0x16128, 0x16121, 0x16120}, +{0x16d68, 0x16d67, 0x16d67}, +{0x16d69, 0x16d63, 0x16d67}, +{0x16d6a, 0x16d69, 0x16d67}, +{0x1d15e, 0x1d157, 0x1d165}, +{0x1d15f, 0x1d158, 0x1d165}, +{0x1d160, 0x1d15f, 0x1d16e}, +{0x1d161, 0x1d15f, 0x1d16f}, +{0x1d162, 0x1d15f, 0x1d170}, +{0x1d163, 0x1d15f, 0x1d171}, +{0x1d164, 0x1d15f, 0x1d172}, +{0x1d1bb, 0x1d1b9, 0x1d165}, +{0x1d1bc, 0x1d1ba, 0x1d165}, +{0x1d1bd, 0x1d1bb, 0x1d16e}, +{0x1d1be, 0x1d1bc, 0x1d16e}, +{0x1d1bf, 0x1d1bb, 0x1d16f}, +{0x1d1c0, 0x1d1bc, 0x1d16f}, +{0x2f800, 0x4e3d, 0}, +{0x2f801, 0x4e38, 0}, +{0x2f802, 0x4e41, 0}, +{0x2f803, 0x20122, 0}, +{0x2f804, 0x4f60, 0}, +{0x2f805, 0x4fae, 0}, +{0x2f806, 0x4fbb, 0}, +{0x2f807, 0x5002, 0}, +{0x2f808, 0x507a, 0}, +{0x2f809, 0x5099, 0}, +{0x2f80a, 0x50e7, 0}, +{0x2f80b, 0x50cf, 0}, +{0x2f80c, 0x349e, 0}, +{0x2f80d, 0x2063a, 0}, +{0x2f80e, 0x514d, 0}, +{0x2f80f, 0x5154, 0}, +{0x2f810, 0x5164, 0}, +{0x2f811, 0x5177, 0}, +{0x2f812, 0x2051c, 0}, +{0x2f813, 0x34b9, 0}, +{0x2f814, 0x5167, 0}, +{0x2f815, 0x518d, 0}, +{0x2f816, 0x2054b, 0}, +{0x2f817, 0x5197, 0}, +{0x2f818, 0x51a4, 0}, +{0x2f819, 0x4ecc, 0}, +{0x2f81a, 0x51ac, 0}, +{0x2f81b, 0x51b5, 0}, +{0x2f81c, 0x291df, 0}, +{0x2f81d, 0x51f5, 0}, +{0x2f81e, 0x5203, 0}, +{0x2f81f, 0x34df, 0}, +{0x2f820, 0x523b, 0}, +{0x2f821, 0x5246, 0}, +{0x2f822, 0x5272, 0}, +{0x2f823, 0x5277, 0}, +{0x2f824, 0x3515, 0}, +{0x2f825, 0x52c7, 0}, +{0x2f826, 0x52c9, 0}, +{0x2f827, 0x52e4, 0}, +{0x2f828, 0x52fa, 0}, +{0x2f829, 0x5305, 0}, +{0x2f82a, 0x5306, 0}, +{0x2f82b, 0x5317, 0}, +{0x2f82c, 0x5349, 0}, +{0x2f82d, 0x5351, 0}, +{0x2f82e, 0x535a, 0}, +{0x2f82f, 0x5373, 0}, +{0x2f830, 0x537d, 0}, +{0x2f831, 0x537f, 0}, +{0x2f832, 0x537f, 0}, +{0x2f833, 0x537f, 0}, +{0x2f834, 0x20a2c, 0}, +{0x2f835, 0x7070, 0}, +{0x2f836, 0x53ca, 0}, +{0x2f837, 0x53df, 0}, +{0x2f838, 0x20b63, 0}, +{0x2f839, 0x53eb, 0}, +{0x2f83a, 0x53f1, 0}, +{0x2f83b, 0x5406, 0}, +{0x2f83c, 0x549e, 0}, +{0x2f83d, 0x5438, 0}, +{0x2f83e, 0x5448, 0}, +{0x2f83f, 0x5468, 0}, +{0x2f840, 0x54a2, 0}, +{0x2f841, 0x54f6, 0}, +{0x2f842, 0x5510, 0}, +{0x2f843, 0x5553, 0}, +{0x2f844, 0x5563, 0}, +{0x2f845, 0x5584, 0}, +{0x2f846, 0x5584, 0}, +{0x2f847, 0x5599, 0}, +{0x2f848, 0x55ab, 0}, +{0x2f849, 0x55b3, 0}, +{0x2f84a, 0x55c2, 0}, +{0x2f84b, 0x5716, 0}, +{0x2f84c, 0x5606, 0}, +{0x2f84d, 0x5717, 0}, +{0x2f84e, 0x5651, 0}, +{0x2f84f, 0x5674, 0}, +{0x2f850, 0x5207, 0}, +{0x2f851, 0x58ee, 0}, +{0x2f852, 0x57ce, 0}, +{0x2f853, 0x57f4, 0}, +{0x2f854, 0x580d, 0}, +{0x2f855, 0x578b, 0}, +{0x2f856, 0x5832, 0}, +{0x2f857, 0x5831, 0}, +{0x2f858, 0x58ac, 0}, +{0x2f859, 0x214e4, 0}, +{0x2f85a, 0x58f2, 0}, +{0x2f85b, 0x58f7, 0}, +{0x2f85c, 0x5906, 0}, +{0x2f85d, 0x591a, 0}, +{0x2f85e, 0x5922, 0}, +{0x2f85f, 0x5962, 0}, +{0x2f860, 0x216a8, 0}, +{0x2f861, 0x216ea, 0}, +{0x2f862, 0x59ec, 0}, +{0x2f863, 0x5a1b, 0}, +{0x2f864, 0x5a27, 0}, +{0x2f865, 0x59d8, 0}, +{0x2f866, 0x5a66, 0}, +{0x2f867, 0x36ee, 0}, +{0x2f868, 0x36fc, 0}, +{0x2f869, 0x5b08, 0}, +{0x2f86a, 0x5b3e, 0}, +{0x2f86b, 0x5b3e, 0}, +{0x2f86c, 0x219c8, 0}, +{0x2f86d, 0x5bc3, 0}, +{0x2f86e, 0x5bd8, 0}, +{0x2f86f, 0x5be7, 0}, +{0x2f870, 0x5bf3, 0}, +{0x2f871, 0x21b18, 0}, +{0x2f872, 0x5bff, 0}, +{0x2f873, 0x5c06, 0}, +{0x2f874, 0x5f53, 0}, +{0x2f875, 0x5c22, 0}, +{0x2f876, 0x3781, 0}, +{0x2f877, 0x5c60, 0}, +{0x2f878, 0x5c6e, 0}, +{0x2f879, 0x5cc0, 0}, +{0x2f87a, 0x5c8d, 0}, +{0x2f87b, 0x21de4, 0}, +{0x2f87c, 0x5d43, 0}, +{0x2f87d, 0x21de6, 0}, +{0x2f87e, 0x5d6e, 0}, +{0x2f87f, 0x5d6b, 0}, +{0x2f880, 0x5d7c, 0}, +{0x2f881, 0x5de1, 0}, +{0x2f882, 0x5de2, 0}, +{0x2f883, 0x382f, 0}, +{0x2f884, 0x5dfd, 0}, +{0x2f885, 0x5e28, 0}, +{0x2f886, 0x5e3d, 0}, +{0x2f887, 0x5e69, 0}, +{0x2f888, 0x3862, 0}, +{0x2f889, 0x22183, 0}, +{0x2f88a, 0x387c, 0}, +{0x2f88b, 0x5eb0, 0}, +{0x2f88c, 0x5eb3, 0}, +{0x2f88d, 0x5eb6, 0}, +{0x2f88e, 0x5eca, 0}, +{0x2f88f, 0x2a392, 0}, +{0x2f890, 0x5efe, 0}, +{0x2f891, 0x22331, 0}, +{0x2f892, 0x22331, 0}, +{0x2f893, 0x8201, 0}, +{0x2f894, 0x5f22, 0}, +{0x2f895, 0x5f22, 0}, +{0x2f896, 0x38c7, 0}, +{0x2f897, 0x232b8, 0}, +{0x2f898, 0x261da, 0}, +{0x2f899, 0x5f62, 0}, +{0x2f89a, 0x5f6b, 0}, +{0x2f89b, 0x38e3, 0}, +{0x2f89c, 0x5f9a, 0}, +{0x2f89d, 0x5fcd, 0}, +{0x2f89e, 0x5fd7, 0}, +{0x2f89f, 0x5ff9, 0}, +{0x2f8a0, 0x6081, 0}, +{0x2f8a1, 0x393a, 0}, +{0x2f8a2, 0x391c, 0}, +{0x2f8a3, 0x6094, 0}, +{0x2f8a4, 0x226d4, 0}, +{0x2f8a5, 0x60c7, 0}, +{0x2f8a6, 0x6148, 0}, +{0x2f8a7, 0x614c, 0}, +{0x2f8a8, 0x614e, 0}, +{0x2f8a9, 0x614c, 0}, +{0x2f8aa, 0x617a, 0}, +{0x2f8ab, 0x618e, 0}, +{0x2f8ac, 0x61b2, 0}, +{0x2f8ad, 0x61a4, 0}, +{0x2f8ae, 0x61af, 0}, +{0x2f8af, 0x61de, 0}, +{0x2f8b0, 0x61f2, 0}, +{0x2f8b1, 0x61f6, 0}, +{0x2f8b2, 0x6210, 0}, +{0x2f8b3, 0x621b, 0}, +{0x2f8b4, 0x625d, 0}, +{0x2f8b5, 0x62b1, 0}, +{0x2f8b6, 0x62d4, 0}, +{0x2f8b7, 0x6350, 0}, +{0x2f8b8, 0x22b0c, 0}, +{0x2f8b9, 0x633d, 0}, +{0x2f8ba, 0x62fc, 0}, +{0x2f8bb, 0x6368, 0}, +{0x2f8bc, 0x6383, 0}, +{0x2f8bd, 0x63e4, 0}, +{0x2f8be, 0x22bf1, 0}, +{0x2f8bf, 0x6422, 0}, +{0x2f8c0, 0x63c5, 0}, +{0x2f8c1, 0x63a9, 0}, +{0x2f8c2, 0x3a2e, 0}, +{0x2f8c3, 0x6469, 0}, +{0x2f8c4, 0x647e, 0}, +{0x2f8c5, 0x649d, 0}, +{0x2f8c6, 0x6477, 0}, +{0x2f8c7, 0x3a6c, 0}, +{0x2f8c8, 0x654f, 0}, +{0x2f8c9, 0x656c, 0}, +{0x2f8ca, 0x2300a, 0}, +{0x2f8cb, 0x65e3, 0}, +{0x2f8cc, 0x66f8, 0}, +{0x2f8cd, 0x6649, 0}, +{0x2f8ce, 0x3b19, 0}, +{0x2f8cf, 0x6691, 0}, +{0x2f8d0, 0x3b08, 0}, +{0x2f8d1, 0x3ae4, 0}, +{0x2f8d2, 0x5192, 0}, +{0x2f8d3, 0x5195, 0}, +{0x2f8d4, 0x6700, 0}, +{0x2f8d5, 0x669c, 0}, +{0x2f8d6, 0x80ad, 0}, +{0x2f8d7, 0x43d9, 0}, +{0x2f8d8, 0x6717, 0}, +{0x2f8d9, 0x671b, 0}, +{0x2f8da, 0x6721, 0}, +{0x2f8db, 0x675e, 0}, +{0x2f8dc, 0x6753, 0}, +{0x2f8dd, 0x233c3, 0}, +{0x2f8de, 0x3b49, 0}, +{0x2f8df, 0x67fa, 0}, +{0x2f8e0, 0x6785, 0}, +{0x2f8e1, 0x6852, 0}, +{0x2f8e2, 0x6885, 0}, +{0x2f8e3, 0x2346d, 0}, +{0x2f8e4, 0x688e, 0}, +{0x2f8e5, 0x681f, 0}, +{0x2f8e6, 0x6914, 0}, +{0x2f8e7, 0x3b9d, 0}, +{0x2f8e8, 0x6942, 0}, +{0x2f8e9, 0x69a3, 0}, +{0x2f8ea, 0x69ea, 0}, +{0x2f8eb, 0x6aa8, 0}, +{0x2f8ec, 0x236a3, 0}, +{0x2f8ed, 0x6adb, 0}, +{0x2f8ee, 0x3c18, 0}, +{0x2f8ef, 0x6b21, 0}, +{0x2f8f0, 0x238a7, 0}, +{0x2f8f1, 0x6b54, 0}, +{0x2f8f2, 0x3c4e, 0}, +{0x2f8f3, 0x6b72, 0}, +{0x2f8f4, 0x6b9f, 0}, +{0x2f8f5, 0x6bba, 0}, +{0x2f8f6, 0x6bbb, 0}, +{0x2f8f7, 0x23a8d, 0}, +{0x2f8f8, 0x21d0b, 0}, +{0x2f8f9, 0x23afa, 0}, +{0x2f8fa, 0x6c4e, 0}, +{0x2f8fb, 0x23cbc, 0}, +{0x2f8fc, 0x6cbf, 0}, +{0x2f8fd, 0x6ccd, 0}, +{0x2f8fe, 0x6c67, 0}, +{0x2f8ff, 0x6d16, 0}, +{0x2f900, 0x6d3e, 0}, +{0x2f901, 0x6d77, 0}, +{0x2f902, 0x6d41, 0}, +{0x2f903, 0x6d69, 0}, +{0x2f904, 0x6d78, 0}, +{0x2f905, 0x6d85, 0}, +{0x2f906, 0x23d1e, 0}, +{0x2f907, 0x6d34, 0}, +{0x2f908, 0x6e2f, 0}, +{0x2f909, 0x6e6e, 0}, +{0x2f90a, 0x3d33, 0}, +{0x2f90b, 0x6ecb, 0}, +{0x2f90c, 0x6ec7, 0}, +{0x2f90d, 0x23ed1, 0}, +{0x2f90e, 0x6df9, 0}, +{0x2f90f, 0x6f6e, 0}, +{0x2f910, 0x23f5e, 0}, +{0x2f911, 0x23f8e, 0}, +{0x2f912, 0x6fc6, 0}, +{0x2f913, 0x7039, 0}, +{0x2f914, 0x701e, 0}, +{0x2f915, 0x701b, 0}, +{0x2f916, 0x3d96, 0}, +{0x2f917, 0x704a, 0}, +{0x2f918, 0x707d, 0}, +{0x2f919, 0x7077, 0}, +{0x2f91a, 0x70ad, 0}, +{0x2f91b, 0x20525, 0}, +{0x2f91c, 0x7145, 0}, +{0x2f91d, 0x24263, 0}, +{0x2f91e, 0x719c, 0}, +{0x2f91f, 0x243ab, 0}, +{0x2f920, 0x7228, 0}, +{0x2f921, 0x7235, 0}, +{0x2f922, 0x7250, 0}, +{0x2f923, 0x24608, 0}, +{0x2f924, 0x7280, 0}, +{0x2f925, 0x7295, 0}, +{0x2f926, 0x24735, 0}, +{0x2f927, 0x24814, 0}, +{0x2f928, 0x737a, 0}, +{0x2f929, 0x738b, 0}, +{0x2f92a, 0x3eac, 0}, +{0x2f92b, 0x73a5, 0}, +{0x2f92c, 0x3eb8, 0}, +{0x2f92d, 0x3eb8, 0}, +{0x2f92e, 0x7447, 0}, +{0x2f92f, 0x745c, 0}, +{0x2f930, 0x7471, 0}, +{0x2f931, 0x7485, 0}, +{0x2f932, 0x74ca, 0}, +{0x2f933, 0x3f1b, 0}, +{0x2f934, 0x7524, 0}, +{0x2f935, 0x24c36, 0}, +{0x2f936, 0x753e, 0}, +{0x2f937, 0x24c92, 0}, +{0x2f938, 0x7570, 0}, +{0x2f939, 0x2219f, 0}, +{0x2f93a, 0x7610, 0}, +{0x2f93b, 0x24fa1, 0}, +{0x2f93c, 0x24fb8, 0}, +{0x2f93d, 0x25044, 0}, +{0x2f93e, 0x3ffc, 0}, +{0x2f93f, 0x4008, 0}, +{0x2f940, 0x76f4, 0}, +{0x2f941, 0x250f3, 0}, +{0x2f942, 0x250f2, 0}, +{0x2f943, 0x25119, 0}, +{0x2f944, 0x25133, 0}, +{0x2f945, 0x771e, 0}, +{0x2f946, 0x771f, 0}, +{0x2f947, 0x771f, 0}, +{0x2f948, 0x774a, 0}, +{0x2f949, 0x4039, 0}, +{0x2f94a, 0x778b, 0}, +{0x2f94b, 0x4046, 0}, +{0x2f94c, 0x4096, 0}, +{0x2f94d, 0x2541d, 0}, +{0x2f94e, 0x784e, 0}, +{0x2f94f, 0x788c, 0}, +{0x2f950, 0x78cc, 0}, +{0x2f951, 0x40e3, 0}, +{0x2f952, 0x25626, 0}, +{0x2f953, 0x7956, 0}, +{0x2f954, 0x2569a, 0}, +{0x2f955, 0x256c5, 0}, +{0x2f956, 0x798f, 0}, +{0x2f957, 0x79eb, 0}, +{0x2f958, 0x412f, 0}, +{0x2f959, 0x7a40, 0}, +{0x2f95a, 0x7a4a, 0}, +{0x2f95b, 0x7a4f, 0}, +{0x2f95c, 0x2597c, 0}, +{0x2f95d, 0x25aa7, 0}, +{0x2f95e, 0x25aa7, 0}, +{0x2f95f, 0x7aee, 0}, +{0x2f960, 0x4202, 0}, +{0x2f961, 0x25bab, 0}, +{0x2f962, 0x7bc6, 0}, +{0x2f963, 0x7bc9, 0}, +{0x2f964, 0x4227, 0}, +{0x2f965, 0x25c80, 0}, +{0x2f966, 0x7cd2, 0}, +{0x2f967, 0x42a0, 0}, +{0x2f968, 0x7ce8, 0}, +{0x2f969, 0x7ce3, 0}, +{0x2f96a, 0x7d00, 0}, +{0x2f96b, 0x25f86, 0}, +{0x2f96c, 0x7d63, 0}, +{0x2f96d, 0x4301, 0}, +{0x2f96e, 0x7dc7, 0}, +{0x2f96f, 0x7e02, 0}, +{0x2f970, 0x7e45, 0}, +{0x2f971, 0x4334, 0}, +{0x2f972, 0x26228, 0}, +{0x2f973, 0x26247, 0}, +{0x2f974, 0x4359, 0}, +{0x2f975, 0x262d9, 0}, +{0x2f976, 0x7f7a, 0}, +{0x2f977, 0x2633e, 0}, +{0x2f978, 0x7f95, 0}, +{0x2f979, 0x7ffa, 0}, +{0x2f97a, 0x8005, 0}, +{0x2f97b, 0x264da, 0}, +{0x2f97c, 0x26523, 0}, +{0x2f97d, 0x8060, 0}, +{0x2f97e, 0x265a8, 0}, +{0x2f97f, 0x8070, 0}, +{0x2f980, 0x2335f, 0}, +{0x2f981, 0x43d5, 0}, +{0x2f982, 0x80b2, 0}, +{0x2f983, 0x8103, 0}, +{0x2f984, 0x440b, 0}, +{0x2f985, 0x813e, 0}, +{0x2f986, 0x5ab5, 0}, +{0x2f987, 0x267a7, 0}, +{0x2f988, 0x267b5, 0}, +{0x2f989, 0x23393, 0}, +{0x2f98a, 0x2339c, 0}, +{0x2f98b, 0x8201, 0}, +{0x2f98c, 0x8204, 0}, +{0x2f98d, 0x8f9e, 0}, +{0x2f98e, 0x446b, 0}, +{0x2f98f, 0x8291, 0}, +{0x2f990, 0x828b, 0}, +{0x2f991, 0x829d, 0}, +{0x2f992, 0x52b3, 0}, +{0x2f993, 0x82b1, 0}, +{0x2f994, 0x82b3, 0}, +{0x2f995, 0x82bd, 0}, +{0x2f996, 0x82e6, 0}, +{0x2f997, 0x26b3c, 0}, +{0x2f998, 0x82e5, 0}, +{0x2f999, 0x831d, 0}, +{0x2f99a, 0x8363, 0}, +{0x2f99b, 0x83ad, 0}, +{0x2f99c, 0x8323, 0}, +{0x2f99d, 0x83bd, 0}, +{0x2f99e, 0x83e7, 0}, +{0x2f99f, 0x8457, 0}, +{0x2f9a0, 0x8353, 0}, +{0x2f9a1, 0x83ca, 0}, +{0x2f9a2, 0x83cc, 0}, +{0x2f9a3, 0x83dc, 0}, +{0x2f9a4, 0x26c36, 0}, +{0x2f9a5, 0x26d6b, 0}, +{0x2f9a6, 0x26cd5, 0}, +{0x2f9a7, 0x452b, 0}, +{0x2f9a8, 0x84f1, 0}, +{0x2f9a9, 0x84f3, 0}, +{0x2f9aa, 0x8516, 0}, +{0x2f9ab, 0x273ca, 0}, +{0x2f9ac, 0x8564, 0}, +{0x2f9ad, 0x26f2c, 0}, +{0x2f9ae, 0x455d, 0}, +{0x2f9af, 0x4561, 0}, +{0x2f9b0, 0x26fb1, 0}, +{0x2f9b1, 0x270d2, 0}, +{0x2f9b2, 0x456b, 0}, +{0x2f9b3, 0x8650, 0}, +{0x2f9b4, 0x865c, 0}, +{0x2f9b5, 0x8667, 0}, +{0x2f9b6, 0x8669, 0}, +{0x2f9b7, 0x86a9, 0}, +{0x2f9b8, 0x8688, 0}, +{0x2f9b9, 0x870e, 0}, +{0x2f9ba, 0x86e2, 0}, +{0x2f9bb, 0x8779, 0}, +{0x2f9bc, 0x8728, 0}, +{0x2f9bd, 0x876b, 0}, +{0x2f9be, 0x8786, 0}, +{0x2f9bf, 0x45d7, 0}, +{0x2f9c0, 0x87e1, 0}, +{0x2f9c1, 0x8801, 0}, +{0x2f9c2, 0x45f9, 0}, +{0x2f9c3, 0x8860, 0}, +{0x2f9c4, 0x8863, 0}, +{0x2f9c5, 0x27667, 0}, +{0x2f9c6, 0x88d7, 0}, +{0x2f9c7, 0x88de, 0}, +{0x2f9c8, 0x4635, 0}, +{0x2f9c9, 0x88fa, 0}, +{0x2f9ca, 0x34bb, 0}, +{0x2f9cb, 0x278ae, 0}, +{0x2f9cc, 0x27966, 0}, +{0x2f9cd, 0x46be, 0}, +{0x2f9ce, 0x46c7, 0}, +{0x2f9cf, 0x8aa0, 0}, +{0x2f9d0, 0x8aed, 0}, +{0x2f9d1, 0x8b8a, 0}, +{0x2f9d2, 0x8c55, 0}, +{0x2f9d3, 0x27ca8, 0}, +{0x2f9d4, 0x8cab, 0}, +{0x2f9d5, 0x8cc1, 0}, +{0x2f9d6, 0x8d1b, 0}, +{0x2f9d7, 0x8d77, 0}, +{0x2f9d8, 0x27f2f, 0}, +{0x2f9d9, 0x20804, 0}, +{0x2f9da, 0x8dcb, 0}, +{0x2f9db, 0x8dbc, 0}, +{0x2f9dc, 0x8df0, 0}, +{0x2f9dd, 0x208de, 0}, +{0x2f9de, 0x8ed4, 0}, +{0x2f9df, 0x8f38, 0}, +{0x2f9e0, 0x285d2, 0}, +{0x2f9e1, 0x285ed, 0}, +{0x2f9e2, 0x9094, 0}, +{0x2f9e3, 0x90f1, 0}, +{0x2f9e4, 0x9111, 0}, +{0x2f9e5, 0x2872e, 0}, +{0x2f9e6, 0x911b, 0}, +{0x2f9e7, 0x9238, 0}, +{0x2f9e8, 0x92d7, 0}, +{0x2f9e9, 0x92d8, 0}, +{0x2f9ea, 0x927c, 0}, +{0x2f9eb, 0x93f9, 0}, +{0x2f9ec, 0x9415, 0}, +{0x2f9ed, 0x28bfa, 0}, +{0x2f9ee, 0x958b, 0}, +{0x2f9ef, 0x4995, 0}, +{0x2f9f0, 0x95b7, 0}, +{0x2f9f1, 0x28d77, 0}, +{0x2f9f2, 0x49e6, 0}, +{0x2f9f3, 0x96c3, 0}, +{0x2f9f4, 0x5db2, 0}, +{0x2f9f5, 0x9723, 0}, +{0x2f9f6, 0x29145, 0}, +{0x2f9f7, 0x2921a, 0}, +{0x2f9f8, 0x4a6e, 0}, +{0x2f9f9, 0x4a76, 0}, +{0x2f9fa, 0x97e0, 0}, +{0x2f9fb, 0x2940a, 0}, +{0x2f9fc, 0x4ab2, 0}, +{0x2f9fd, 0x29496, 0}, +{0x2f9fe, 0x980b, 0}, +{0x2f9ff, 0x980b, 0}, +{0x2fa00, 0x9829, 0}, +{0x2fa01, 0x295b6, 0}, +{0x2fa02, 0x98e2, 0}, +{0x2fa03, 0x4b33, 0}, +{0x2fa04, 0x9929, 0}, +{0x2fa05, 0x99a7, 0}, +{0x2fa06, 0x99c2, 0}, +{0x2fa07, 0x99fe, 0}, +{0x2fa08, 0x4bce, 0}, +{0x2fa09, 0x29b30, 0}, +{0x2fa0a, 0x9b12, 0}, +{0x2fa0b, 0x9c40, 0}, +{0x2fa0c, 0x9cfd, 0}, +{0x2fa0d, 0x4cce, 0}, +{0x2fa0e, 0x4ced, 0}, +{0x2fa0f, 0x9d67, 0}, +{0x2fa10, 0x2a0ce, 0}, +{0x2fa11, 0x4cf8, 0}, +{0x2fa12, 0x2a105, 0}, +{0x2fa13, 0x2a20e, 0}, +{0x2fa14, 0x2a291, 0}, +{0x2fa15, 0x9ebb, 0}, +{0x2fa16, 0x4d56, 0}, +{0x2fa17, 0x9ef9, 0}, +{0x2fa18, 0x9efe, 0}, +{0x2fa19, 0x9f05, 0}, +{0x2fa1a, 0x9f0f, 0}, +{0x2fa1b, 0x9f16, 0}, +{0x2fa1c, 0x9f3b, 0}, +{0x2fa1d, 0x2a600, 0}, diff --git a/unicode/combining_classes.h b/unicode/combining_classes.h new file mode 100644 index 00000000..b9a4cdab --- /dev/null +++ b/unicode/combining_classes.h @@ -0,0 +1,403 @@ +/* + * Autogenerated by read_ucd.py from The Unicode Standard 16.0.0 + * + * List the canonical combining class of each Unicode character, if it is + * not zero. This controls how combining marks can be reordered by the + * Unicode normalisation algorithms. + * + * Used by utils/unicode-norm.c. + */ + +{0x0300, 0x0314, 230}, +{0x0315, 0x0315, 232}, +{0x0316, 0x0319, 220}, +{0x031a, 0x031a, 232}, +{0x031b, 0x031b, 216}, +{0x031c, 0x0320, 220}, +{0x0321, 0x0322, 202}, +{0x0323, 0x0326, 220}, +{0x0327, 0x0328, 202}, +{0x0329, 0x0333, 220}, +{0x0334, 0x0338, 1}, +{0x0339, 0x033c, 220}, +{0x033d, 0x0344, 230}, +{0x0345, 0x0345, 240}, +{0x0346, 0x0346, 230}, +{0x0347, 0x0349, 220}, +{0x034a, 0x034c, 230}, +{0x034d, 0x034e, 220}, +{0x0350, 0x0352, 230}, +{0x0353, 0x0356, 220}, +{0x0357, 0x0357, 230}, +{0x0358, 0x0358, 232}, +{0x0359, 0x035a, 220}, +{0x035b, 0x035b, 230}, +{0x035c, 0x035c, 233}, +{0x035d, 0x035e, 234}, +{0x035f, 0x035f, 233}, +{0x0360, 0x0361, 234}, +{0x0362, 0x0362, 233}, +{0x0363, 0x036f, 230}, +{0x0483, 0x0487, 230}, +{0x0591, 0x0591, 220}, +{0x0592, 0x0595, 230}, +{0x0596, 0x0596, 220}, +{0x0597, 0x0599, 230}, +{0x059a, 0x059a, 222}, +{0x059b, 0x059b, 220}, +{0x059c, 0x05a1, 230}, +{0x05a2, 0x05a7, 220}, +{0x05a8, 0x05a9, 230}, +{0x05aa, 0x05aa, 220}, +{0x05ab, 0x05ac, 230}, +{0x05ad, 0x05ad, 222}, +{0x05ae, 0x05ae, 228}, +{0x05af, 0x05af, 230}, +{0x05b0, 0x05b0, 10}, +{0x05b1, 0x05b1, 11}, +{0x05b2, 0x05b2, 12}, +{0x05b3, 0x05b3, 13}, +{0x05b4, 0x05b4, 14}, +{0x05b5, 0x05b5, 15}, +{0x05b6, 0x05b6, 16}, +{0x05b7, 0x05b7, 17}, +{0x05b8, 0x05b8, 18}, +{0x05b9, 0x05ba, 19}, +{0x05bb, 0x05bb, 20}, +{0x05bc, 0x05bc, 21}, +{0x05bd, 0x05bd, 22}, +{0x05bf, 0x05bf, 23}, +{0x05c1, 0x05c1, 24}, +{0x05c2, 0x05c2, 25}, +{0x05c4, 0x05c4, 230}, +{0x05c5, 0x05c5, 220}, +{0x05c7, 0x05c7, 18}, +{0x0610, 0x0617, 230}, +{0x0618, 0x0618, 30}, +{0x0619, 0x0619, 31}, +{0x061a, 0x061a, 32}, +{0x064b, 0x064b, 27}, +{0x064c, 0x064c, 28}, +{0x064d, 0x064d, 29}, +{0x064e, 0x064e, 30}, +{0x064f, 0x064f, 31}, +{0x0650, 0x0650, 32}, +{0x0651, 0x0651, 33}, +{0x0652, 0x0652, 34}, +{0x0653, 0x0654, 230}, +{0x0655, 0x0656, 220}, +{0x0657, 0x065b, 230}, +{0x065c, 0x065c, 220}, +{0x065d, 0x065e, 230}, +{0x065f, 0x065f, 220}, +{0x0670, 0x0670, 35}, +{0x06d6, 0x06dc, 230}, +{0x06df, 0x06e2, 230}, +{0x06e3, 0x06e3, 220}, +{0x06e4, 0x06e4, 230}, +{0x06e7, 0x06e8, 230}, +{0x06ea, 0x06ea, 220}, +{0x06eb, 0x06ec, 230}, +{0x06ed, 0x06ed, 220}, +{0x0711, 0x0711, 36}, +{0x0730, 0x0730, 230}, +{0x0731, 0x0731, 220}, +{0x0732, 0x0733, 230}, +{0x0734, 0x0734, 220}, +{0x0735, 0x0736, 230}, +{0x0737, 0x0739, 220}, +{0x073a, 0x073a, 230}, +{0x073b, 0x073c, 220}, +{0x073d, 0x073d, 230}, +{0x073e, 0x073e, 220}, +{0x073f, 0x0741, 230}, +{0x0742, 0x0742, 220}, +{0x0743, 0x0743, 230}, +{0x0744, 0x0744, 220}, +{0x0745, 0x0745, 230}, +{0x0746, 0x0746, 220}, +{0x0747, 0x0747, 230}, +{0x0748, 0x0748, 220}, +{0x0749, 0x074a, 230}, +{0x07eb, 0x07f1, 230}, +{0x07f2, 0x07f2, 220}, +{0x07f3, 0x07f3, 230}, +{0x07fd, 0x07fd, 220}, +{0x0816, 0x0819, 230}, +{0x081b, 0x0823, 230}, +{0x0825, 0x0827, 230}, +{0x0829, 0x082d, 230}, +{0x0859, 0x085b, 220}, +{0x0897, 0x0898, 230}, +{0x0899, 0x089b, 220}, +{0x089c, 0x089f, 230}, +{0x08ca, 0x08ce, 230}, +{0x08cf, 0x08d3, 220}, +{0x08d4, 0x08e1, 230}, +{0x08e3, 0x08e3, 220}, +{0x08e4, 0x08e5, 230}, +{0x08e6, 0x08e6, 220}, +{0x08e7, 0x08e8, 230}, +{0x08e9, 0x08e9, 220}, +{0x08ea, 0x08ec, 230}, +{0x08ed, 0x08ef, 220}, +{0x08f0, 0x08f0, 27}, +{0x08f1, 0x08f1, 28}, +{0x08f2, 0x08f2, 29}, +{0x08f3, 0x08f5, 230}, +{0x08f6, 0x08f6, 220}, +{0x08f7, 0x08f8, 230}, +{0x08f9, 0x08fa, 220}, +{0x08fb, 0x08ff, 230}, +{0x093c, 0x093c, 7}, +{0x094d, 0x094d, 9}, +{0x0951, 0x0951, 230}, +{0x0952, 0x0952, 220}, +{0x0953, 0x0954, 230}, +{0x09bc, 0x09bc, 7}, +{0x09cd, 0x09cd, 9}, +{0x09fe, 0x09fe, 230}, +{0x0a3c, 0x0a3c, 7}, +{0x0a4d, 0x0a4d, 9}, +{0x0abc, 0x0abc, 7}, +{0x0acd, 0x0acd, 9}, +{0x0b3c, 0x0b3c, 7}, +{0x0b4d, 0x0b4d, 9}, +{0x0bcd, 0x0bcd, 9}, +{0x0c3c, 0x0c3c, 7}, +{0x0c4d, 0x0c4d, 9}, +{0x0c55, 0x0c55, 84}, +{0x0c56, 0x0c56, 91}, +{0x0cbc, 0x0cbc, 7}, +{0x0ccd, 0x0ccd, 9}, +{0x0d3b, 0x0d3c, 9}, +{0x0d4d, 0x0d4d, 9}, +{0x0dca, 0x0dca, 9}, +{0x0e38, 0x0e39, 103}, +{0x0e3a, 0x0e3a, 9}, +{0x0e48, 0x0e4b, 107}, +{0x0eb8, 0x0eb9, 118}, +{0x0eba, 0x0eba, 9}, +{0x0ec8, 0x0ecb, 122}, +{0x0f18, 0x0f19, 220}, +{0x0f35, 0x0f35, 220}, +{0x0f37, 0x0f37, 220}, +{0x0f39, 0x0f39, 216}, +{0x0f71, 0x0f71, 129}, +{0x0f72, 0x0f72, 130}, +{0x0f74, 0x0f74, 132}, +{0x0f7a, 0x0f7d, 130}, +{0x0f80, 0x0f80, 130}, +{0x0f82, 0x0f83, 230}, +{0x0f84, 0x0f84, 9}, +{0x0f86, 0x0f87, 230}, +{0x0fc6, 0x0fc6, 220}, +{0x1037, 0x1037, 7}, +{0x1039, 0x103a, 9}, +{0x108d, 0x108d, 220}, +{0x135d, 0x135f, 230}, +{0x1714, 0x1715, 9}, +{0x1734, 0x1734, 9}, +{0x17d2, 0x17d2, 9}, +{0x17dd, 0x17dd, 230}, +{0x18a9, 0x18a9, 228}, +{0x1939, 0x1939, 222}, +{0x193a, 0x193a, 230}, +{0x193b, 0x193b, 220}, +{0x1a17, 0x1a17, 230}, +{0x1a18, 0x1a18, 220}, +{0x1a60, 0x1a60, 9}, +{0x1a75, 0x1a7c, 230}, +{0x1a7f, 0x1a7f, 220}, +{0x1ab0, 0x1ab4, 230}, +{0x1ab5, 0x1aba, 220}, +{0x1abb, 0x1abc, 230}, +{0x1abd, 0x1abd, 220}, +{0x1abf, 0x1ac0, 220}, +{0x1ac1, 0x1ac2, 230}, +{0x1ac3, 0x1ac4, 220}, +{0x1ac5, 0x1ac9, 230}, +{0x1aca, 0x1aca, 220}, +{0x1acb, 0x1ace, 230}, +{0x1b34, 0x1b34, 7}, +{0x1b44, 0x1b44, 9}, +{0x1b6b, 0x1b6b, 230}, +{0x1b6c, 0x1b6c, 220}, +{0x1b6d, 0x1b73, 230}, +{0x1baa, 0x1bab, 9}, +{0x1be6, 0x1be6, 7}, +{0x1bf2, 0x1bf3, 9}, +{0x1c37, 0x1c37, 7}, +{0x1cd0, 0x1cd2, 230}, +{0x1cd4, 0x1cd4, 1}, +{0x1cd5, 0x1cd9, 220}, +{0x1cda, 0x1cdb, 230}, +{0x1cdc, 0x1cdf, 220}, +{0x1ce0, 0x1ce0, 230}, +{0x1ce2, 0x1ce8, 1}, +{0x1ced, 0x1ced, 220}, +{0x1cf4, 0x1cf4, 230}, +{0x1cf8, 0x1cf9, 230}, +{0x1dc0, 0x1dc1, 230}, +{0x1dc2, 0x1dc2, 220}, +{0x1dc3, 0x1dc9, 230}, +{0x1dca, 0x1dca, 220}, +{0x1dcb, 0x1dcc, 230}, +{0x1dcd, 0x1dcd, 234}, +{0x1dce, 0x1dce, 214}, +{0x1dcf, 0x1dcf, 220}, +{0x1dd0, 0x1dd0, 202}, +{0x1dd1, 0x1df5, 230}, +{0x1df6, 0x1df6, 232}, +{0x1df7, 0x1df8, 228}, +{0x1df9, 0x1df9, 220}, +{0x1dfa, 0x1dfa, 218}, +{0x1dfb, 0x1dfb, 230}, +{0x1dfc, 0x1dfc, 233}, +{0x1dfd, 0x1dfd, 220}, +{0x1dfe, 0x1dfe, 230}, +{0x1dff, 0x1dff, 220}, +{0x20d0, 0x20d1, 230}, +{0x20d2, 0x20d3, 1}, +{0x20d4, 0x20d7, 230}, +{0x20d8, 0x20da, 1}, +{0x20db, 0x20dc, 230}, +{0x20e1, 0x20e1, 230}, +{0x20e5, 0x20e6, 1}, +{0x20e7, 0x20e7, 230}, +{0x20e8, 0x20e8, 220}, +{0x20e9, 0x20e9, 230}, +{0x20ea, 0x20eb, 1}, +{0x20ec, 0x20ef, 220}, +{0x20f0, 0x20f0, 230}, +{0x2cef, 0x2cf1, 230}, +{0x2d7f, 0x2d7f, 9}, +{0x2de0, 0x2dff, 230}, +{0x302a, 0x302a, 218}, +{0x302b, 0x302b, 228}, +{0x302c, 0x302c, 232}, +{0x302d, 0x302d, 222}, +{0x302e, 0x302f, 224}, +{0x3099, 0x309a, 8}, +{0xa66f, 0xa66f, 230}, +{0xa674, 0xa67d, 230}, +{0xa69e, 0xa69f, 230}, +{0xa6f0, 0xa6f1, 230}, +{0xa806, 0xa806, 9}, +{0xa82c, 0xa82c, 9}, +{0xa8c4, 0xa8c4, 9}, +{0xa8e0, 0xa8f1, 230}, +{0xa92b, 0xa92d, 220}, +{0xa953, 0xa953, 9}, +{0xa9b3, 0xa9b3, 7}, +{0xa9c0, 0xa9c0, 9}, +{0xaab0, 0xaab0, 230}, +{0xaab2, 0xaab3, 230}, +{0xaab4, 0xaab4, 220}, +{0xaab7, 0xaab8, 230}, +{0xaabe, 0xaabf, 230}, +{0xaac1, 0xaac1, 230}, +{0xaaf6, 0xaaf6, 9}, +{0xabed, 0xabed, 9}, +{0xfb1e, 0xfb1e, 26}, +{0xfe20, 0xfe26, 230}, +{0xfe27, 0xfe2d, 220}, +{0xfe2e, 0xfe2f, 230}, +{0x101fd, 0x101fd, 220}, +{0x102e0, 0x102e0, 220}, +{0x10376, 0x1037a, 230}, +{0x10a0d, 0x10a0d, 220}, +{0x10a0f, 0x10a0f, 230}, +{0x10a38, 0x10a38, 230}, +{0x10a39, 0x10a39, 1}, +{0x10a3a, 0x10a3a, 220}, +{0x10a3f, 0x10a3f, 9}, +{0x10ae5, 0x10ae5, 230}, +{0x10ae6, 0x10ae6, 220}, +{0x10d24, 0x10d27, 230}, +{0x10d69, 0x10d6d, 230}, +{0x10eab, 0x10eac, 230}, +{0x10efd, 0x10eff, 220}, +{0x10f46, 0x10f47, 220}, +{0x10f48, 0x10f4a, 230}, +{0x10f4b, 0x10f4b, 220}, +{0x10f4c, 0x10f4c, 230}, +{0x10f4d, 0x10f50, 220}, +{0x10f82, 0x10f82, 230}, +{0x10f83, 0x10f83, 220}, +{0x10f84, 0x10f84, 230}, +{0x10f85, 0x10f85, 220}, +{0x11046, 0x11046, 9}, +{0x11070, 0x11070, 9}, +{0x1107f, 0x1107f, 9}, +{0x110b9, 0x110b9, 9}, +{0x110ba, 0x110ba, 7}, +{0x11100, 0x11102, 230}, +{0x11133, 0x11134, 9}, +{0x11173, 0x11173, 7}, +{0x111c0, 0x111c0, 9}, +{0x111ca, 0x111ca, 7}, +{0x11235, 0x11235, 9}, +{0x11236, 0x11236, 7}, +{0x112e9, 0x112e9, 7}, +{0x112ea, 0x112ea, 9}, +{0x1133b, 0x1133c, 7}, +{0x1134d, 0x1134d, 9}, +{0x11366, 0x1136c, 230}, +{0x11370, 0x11374, 230}, +{0x113ce, 0x113d0, 9}, +{0x11442, 0x11442, 9}, +{0x11446, 0x11446, 7}, +{0x1145e, 0x1145e, 230}, +{0x114c2, 0x114c2, 9}, +{0x114c3, 0x114c3, 7}, +{0x115bf, 0x115bf, 9}, +{0x115c0, 0x115c0, 7}, +{0x1163f, 0x1163f, 9}, +{0x116b6, 0x116b6, 9}, +{0x116b7, 0x116b7, 7}, +{0x1172b, 0x1172b, 9}, +{0x11839, 0x11839, 9}, +{0x1183a, 0x1183a, 7}, +{0x1193d, 0x1193e, 9}, +{0x11943, 0x11943, 7}, +{0x119e0, 0x119e0, 9}, +{0x11a34, 0x11a34, 9}, +{0x11a47, 0x11a47, 9}, +{0x11a99, 0x11a99, 9}, +{0x11c3f, 0x11c3f, 9}, +{0x11d42, 0x11d42, 7}, +{0x11d44, 0x11d45, 9}, +{0x11d97, 0x11d97, 9}, +{0x11f41, 0x11f42, 9}, +{0x1612f, 0x1612f, 9}, +{0x16af0, 0x16af4, 1}, +{0x16b30, 0x16b36, 230}, +{0x16ff0, 0x16ff1, 6}, +{0x1bc9e, 0x1bc9e, 1}, +{0x1d165, 0x1d166, 216}, +{0x1d167, 0x1d169, 1}, +{0x1d16d, 0x1d16d, 226}, +{0x1d16e, 0x1d172, 216}, +{0x1d17b, 0x1d182, 220}, +{0x1d185, 0x1d189, 230}, +{0x1d18a, 0x1d18b, 220}, +{0x1d1aa, 0x1d1ad, 230}, +{0x1d242, 0x1d244, 230}, +{0x1e000, 0x1e006, 230}, +{0x1e008, 0x1e018, 230}, +{0x1e01b, 0x1e021, 230}, +{0x1e023, 0x1e024, 230}, +{0x1e026, 0x1e02a, 230}, +{0x1e08f, 0x1e08f, 230}, +{0x1e130, 0x1e136, 230}, +{0x1e2ae, 0x1e2ae, 230}, +{0x1e2ec, 0x1e2ef, 230}, +{0x1e4ec, 0x1e4ed, 232}, +{0x1e4ee, 0x1e4ee, 220}, +{0x1e4ef, 0x1e4ef, 230}, +{0x1e5ee, 0x1e5ee, 230}, +{0x1e5ef, 0x1e5ef, 220}, +{0x1e8d0, 0x1e8d6, 220}, +{0x1e944, 0x1e949, 230}, +{0x1e94a, 0x1e94a, 7}, diff --git a/unicode/known_chars.h b/unicode/known_chars.h new file mode 100644 index 00000000..a4a2819d --- /dev/null +++ b/unicode/known_chars.h @@ -0,0 +1,740 @@ +/* + * Autogenerated by read_ucd.py from The Unicode Standard 16.0.0 + * + * List the Unicode code points that are known to this version of the + * standard at all. + * + * Used by utils/unicode-known.c. + */ + +{0x0000, 0x0377}, +{0x037a, 0x037f}, +{0x0384, 0x038a}, +{0x038c, 0x038c}, +{0x038e, 0x03a1}, +{0x03a3, 0x052f}, +{0x0531, 0x0556}, +{0x0559, 0x058a}, +{0x058d, 0x058f}, +{0x0591, 0x05c7}, +{0x05d0, 0x05ea}, +{0x05ef, 0x05f4}, +{0x0600, 0x070d}, +{0x070f, 0x074a}, +{0x074d, 0x07b1}, +{0x07c0, 0x07fa}, +{0x07fd, 0x082d}, +{0x0830, 0x083e}, +{0x0840, 0x085b}, +{0x085e, 0x085e}, +{0x0860, 0x086a}, +{0x0870, 0x088e}, +{0x0890, 0x0891}, +{0x0897, 0x0983}, +{0x0985, 0x098c}, +{0x098f, 0x0990}, +{0x0993, 0x09a8}, +{0x09aa, 0x09b0}, +{0x09b2, 0x09b2}, +{0x09b6, 0x09b9}, +{0x09bc, 0x09c4}, +{0x09c7, 0x09c8}, +{0x09cb, 0x09ce}, +{0x09d7, 0x09d7}, +{0x09dc, 0x09dd}, +{0x09df, 0x09e3}, +{0x09e6, 0x09fe}, +{0x0a01, 0x0a03}, +{0x0a05, 0x0a0a}, +{0x0a0f, 0x0a10}, +{0x0a13, 0x0a28}, +{0x0a2a, 0x0a30}, +{0x0a32, 0x0a33}, +{0x0a35, 0x0a36}, +{0x0a38, 0x0a39}, +{0x0a3c, 0x0a3c}, +{0x0a3e, 0x0a42}, +{0x0a47, 0x0a48}, +{0x0a4b, 0x0a4d}, +{0x0a51, 0x0a51}, +{0x0a59, 0x0a5c}, +{0x0a5e, 0x0a5e}, +{0x0a66, 0x0a76}, +{0x0a81, 0x0a83}, +{0x0a85, 0x0a8d}, +{0x0a8f, 0x0a91}, +{0x0a93, 0x0aa8}, +{0x0aaa, 0x0ab0}, +{0x0ab2, 0x0ab3}, +{0x0ab5, 0x0ab9}, +{0x0abc, 0x0ac5}, +{0x0ac7, 0x0ac9}, +{0x0acb, 0x0acd}, +{0x0ad0, 0x0ad0}, +{0x0ae0, 0x0ae3}, +{0x0ae6, 0x0af1}, +{0x0af9, 0x0aff}, +{0x0b01, 0x0b03}, +{0x0b05, 0x0b0c}, +{0x0b0f, 0x0b10}, +{0x0b13, 0x0b28}, +{0x0b2a, 0x0b30}, +{0x0b32, 0x0b33}, +{0x0b35, 0x0b39}, +{0x0b3c, 0x0b44}, +{0x0b47, 0x0b48}, +{0x0b4b, 0x0b4d}, +{0x0b55, 0x0b57}, +{0x0b5c, 0x0b5d}, +{0x0b5f, 0x0b63}, +{0x0b66, 0x0b77}, +{0x0b82, 0x0b83}, +{0x0b85, 0x0b8a}, +{0x0b8e, 0x0b90}, +{0x0b92, 0x0b95}, +{0x0b99, 0x0b9a}, +{0x0b9c, 0x0b9c}, +{0x0b9e, 0x0b9f}, +{0x0ba3, 0x0ba4}, +{0x0ba8, 0x0baa}, +{0x0bae, 0x0bb9}, +{0x0bbe, 0x0bc2}, +{0x0bc6, 0x0bc8}, +{0x0bca, 0x0bcd}, +{0x0bd0, 0x0bd0}, +{0x0bd7, 0x0bd7}, +{0x0be6, 0x0bfa}, +{0x0c00, 0x0c0c}, +{0x0c0e, 0x0c10}, +{0x0c12, 0x0c28}, +{0x0c2a, 0x0c39}, +{0x0c3c, 0x0c44}, +{0x0c46, 0x0c48}, +{0x0c4a, 0x0c4d}, +{0x0c55, 0x0c56}, +{0x0c58, 0x0c5a}, +{0x0c5d, 0x0c5d}, +{0x0c60, 0x0c63}, +{0x0c66, 0x0c6f}, +{0x0c77, 0x0c8c}, +{0x0c8e, 0x0c90}, +{0x0c92, 0x0ca8}, +{0x0caa, 0x0cb3}, +{0x0cb5, 0x0cb9}, +{0x0cbc, 0x0cc4}, +{0x0cc6, 0x0cc8}, +{0x0cca, 0x0ccd}, +{0x0cd5, 0x0cd6}, +{0x0cdd, 0x0cde}, +{0x0ce0, 0x0ce3}, +{0x0ce6, 0x0cef}, +{0x0cf1, 0x0cf3}, +{0x0d00, 0x0d0c}, +{0x0d0e, 0x0d10}, +{0x0d12, 0x0d44}, +{0x0d46, 0x0d48}, +{0x0d4a, 0x0d4f}, +{0x0d54, 0x0d63}, +{0x0d66, 0x0d7f}, +{0x0d81, 0x0d83}, +{0x0d85, 0x0d96}, +{0x0d9a, 0x0db1}, +{0x0db3, 0x0dbb}, +{0x0dbd, 0x0dbd}, +{0x0dc0, 0x0dc6}, +{0x0dca, 0x0dca}, +{0x0dcf, 0x0dd4}, +{0x0dd6, 0x0dd6}, +{0x0dd8, 0x0ddf}, +{0x0de6, 0x0def}, +{0x0df2, 0x0df4}, +{0x0e01, 0x0e3a}, +{0x0e3f, 0x0e5b}, +{0x0e81, 0x0e82}, +{0x0e84, 0x0e84}, +{0x0e86, 0x0e8a}, +{0x0e8c, 0x0ea3}, +{0x0ea5, 0x0ea5}, +{0x0ea7, 0x0ebd}, +{0x0ec0, 0x0ec4}, +{0x0ec6, 0x0ec6}, +{0x0ec8, 0x0ece}, +{0x0ed0, 0x0ed9}, +{0x0edc, 0x0edf}, +{0x0f00, 0x0f47}, +{0x0f49, 0x0f6c}, +{0x0f71, 0x0f97}, +{0x0f99, 0x0fbc}, +{0x0fbe, 0x0fcc}, +{0x0fce, 0x0fda}, +{0x1000, 0x10c5}, +{0x10c7, 0x10c7}, +{0x10cd, 0x10cd}, +{0x10d0, 0x1248}, +{0x124a, 0x124d}, +{0x1250, 0x1256}, +{0x1258, 0x1258}, +{0x125a, 0x125d}, +{0x1260, 0x1288}, +{0x128a, 0x128d}, +{0x1290, 0x12b0}, +{0x12b2, 0x12b5}, +{0x12b8, 0x12be}, +{0x12c0, 0x12c0}, +{0x12c2, 0x12c5}, +{0x12c8, 0x12d6}, +{0x12d8, 0x1310}, +{0x1312, 0x1315}, +{0x1318, 0x135a}, +{0x135d, 0x137c}, +{0x1380, 0x1399}, +{0x13a0, 0x13f5}, +{0x13f8, 0x13fd}, +{0x1400, 0x169c}, +{0x16a0, 0x16f8}, +{0x1700, 0x1715}, +{0x171f, 0x1736}, +{0x1740, 0x1753}, +{0x1760, 0x176c}, +{0x176e, 0x1770}, +{0x1772, 0x1773}, +{0x1780, 0x17dd}, +{0x17e0, 0x17e9}, +{0x17f0, 0x17f9}, +{0x1800, 0x1819}, +{0x1820, 0x1878}, +{0x1880, 0x18aa}, +{0x18b0, 0x18f5}, +{0x1900, 0x191e}, +{0x1920, 0x192b}, +{0x1930, 0x193b}, +{0x1940, 0x1940}, +{0x1944, 0x196d}, +{0x1970, 0x1974}, +{0x1980, 0x19ab}, +{0x19b0, 0x19c9}, +{0x19d0, 0x19da}, +{0x19de, 0x1a1b}, +{0x1a1e, 0x1a5e}, +{0x1a60, 0x1a7c}, +{0x1a7f, 0x1a89}, +{0x1a90, 0x1a99}, +{0x1aa0, 0x1aad}, +{0x1ab0, 0x1ace}, +{0x1b00, 0x1b4c}, +{0x1b4e, 0x1bf3}, +{0x1bfc, 0x1c37}, +{0x1c3b, 0x1c49}, +{0x1c4d, 0x1c8a}, +{0x1c90, 0x1cba}, +{0x1cbd, 0x1cc7}, +{0x1cd0, 0x1cfa}, +{0x1d00, 0x1f15}, +{0x1f18, 0x1f1d}, +{0x1f20, 0x1f45}, +{0x1f48, 0x1f4d}, +{0x1f50, 0x1f57}, +{0x1f59, 0x1f59}, +{0x1f5b, 0x1f5b}, +{0x1f5d, 0x1f5d}, +{0x1f5f, 0x1f7d}, +{0x1f80, 0x1fb4}, +{0x1fb6, 0x1fc4}, +{0x1fc6, 0x1fd3}, +{0x1fd6, 0x1fdb}, +{0x1fdd, 0x1fef}, +{0x1ff2, 0x1ff4}, +{0x1ff6, 0x1ffe}, +{0x2000, 0x2064}, +{0x2066, 0x2071}, +{0x2074, 0x208e}, +{0x2090, 0x209c}, +{0x20a0, 0x20c0}, +{0x20d0, 0x20f0}, +{0x2100, 0x218b}, +{0x2190, 0x2429}, +{0x2440, 0x244a}, +{0x2460, 0x2b73}, +{0x2b76, 0x2b95}, +{0x2b97, 0x2cf3}, +{0x2cf9, 0x2d25}, +{0x2d27, 0x2d27}, +{0x2d2d, 0x2d2d}, +{0x2d30, 0x2d67}, +{0x2d6f, 0x2d70}, +{0x2d7f, 0x2d96}, +{0x2da0, 0x2da6}, +{0x2da8, 0x2dae}, +{0x2db0, 0x2db6}, +{0x2db8, 0x2dbe}, +{0x2dc0, 0x2dc6}, +{0x2dc8, 0x2dce}, +{0x2dd0, 0x2dd6}, +{0x2dd8, 0x2dde}, +{0x2de0, 0x2e5d}, +{0x2e80, 0x2e99}, +{0x2e9b, 0x2ef3}, +{0x2f00, 0x2fd5}, +{0x2ff0, 0x303f}, +{0x3041, 0x3096}, +{0x3099, 0x30ff}, +{0x3105, 0x312f}, +{0x3131, 0x318e}, +{0x3190, 0x31e5}, +{0x31ef, 0x321e}, +{0x3220, 0xa48c}, +{0xa490, 0xa4c6}, +{0xa4d0, 0xa62b}, +{0xa640, 0xa6f7}, +{0xa700, 0xa7cd}, +{0xa7d0, 0xa7d1}, +{0xa7d3, 0xa7d3}, +{0xa7d5, 0xa7dc}, +{0xa7f2, 0xa82c}, +{0xa830, 0xa839}, +{0xa840, 0xa877}, +{0xa880, 0xa8c5}, +{0xa8ce, 0xa8d9}, +{0xa8e0, 0xa953}, +{0xa95f, 0xa97c}, +{0xa980, 0xa9cd}, +{0xa9cf, 0xa9d9}, +{0xa9de, 0xa9fe}, +{0xaa00, 0xaa36}, +{0xaa40, 0xaa4d}, +{0xaa50, 0xaa59}, +{0xaa5c, 0xaac2}, +{0xaadb, 0xaaf6}, +{0xab01, 0xab06}, +{0xab09, 0xab0e}, +{0xab11, 0xab16}, +{0xab20, 0xab26}, +{0xab28, 0xab2e}, +{0xab30, 0xab6b}, +{0xab70, 0xabed}, +{0xabf0, 0xabf9}, +{0xac00, 0xd7a3}, +{0xd7b0, 0xd7c6}, +{0xd7cb, 0xd7fb}, +{0xd800, 0xfa6d}, +{0xfa70, 0xfad9}, +{0xfb00, 0xfb06}, +{0xfb13, 0xfb17}, +{0xfb1d, 0xfb36}, +{0xfb38, 0xfb3c}, +{0xfb3e, 0xfb3e}, +{0xfb40, 0xfb41}, +{0xfb43, 0xfb44}, +{0xfb46, 0xfbc2}, +{0xfbd3, 0xfd8f}, +{0xfd92, 0xfdc7}, +{0xfdcf, 0xfdcf}, +{0xfdf0, 0xfe19}, +{0xfe20, 0xfe52}, +{0xfe54, 0xfe66}, +{0xfe68, 0xfe6b}, +{0xfe70, 0xfe74}, +{0xfe76, 0xfefc}, +{0xfeff, 0xfeff}, +{0xff01, 0xffbe}, +{0xffc2, 0xffc7}, +{0xffca, 0xffcf}, +{0xffd2, 0xffd7}, +{0xffda, 0xffdc}, +{0xffe0, 0xffe6}, +{0xffe8, 0xffee}, +{0xfff9, 0xfffd}, +{0x10000, 0x1000b}, +{0x1000d, 0x10026}, +{0x10028, 0x1003a}, +{0x1003c, 0x1003d}, +{0x1003f, 0x1004d}, +{0x10050, 0x1005d}, +{0x10080, 0x100fa}, +{0x10100, 0x10102}, +{0x10107, 0x10133}, +{0x10137, 0x1018e}, +{0x10190, 0x1019c}, +{0x101a0, 0x101a0}, +{0x101d0, 0x101fd}, +{0x10280, 0x1029c}, +{0x102a0, 0x102d0}, +{0x102e0, 0x102fb}, +{0x10300, 0x10323}, +{0x1032d, 0x1034a}, +{0x10350, 0x1037a}, +{0x10380, 0x1039d}, +{0x1039f, 0x103c3}, +{0x103c8, 0x103d5}, +{0x10400, 0x1049d}, +{0x104a0, 0x104a9}, +{0x104b0, 0x104d3}, +{0x104d8, 0x104fb}, +{0x10500, 0x10527}, +{0x10530, 0x10563}, +{0x1056f, 0x1057a}, +{0x1057c, 0x1058a}, +{0x1058c, 0x10592}, +{0x10594, 0x10595}, +{0x10597, 0x105a1}, +{0x105a3, 0x105b1}, +{0x105b3, 0x105b9}, +{0x105bb, 0x105bc}, +{0x105c0, 0x105f3}, +{0x10600, 0x10736}, +{0x10740, 0x10755}, +{0x10760, 0x10767}, +{0x10780, 0x10785}, +{0x10787, 0x107b0}, +{0x107b2, 0x107ba}, +{0x10800, 0x10805}, +{0x10808, 0x10808}, +{0x1080a, 0x10835}, +{0x10837, 0x10838}, +{0x1083c, 0x1083c}, +{0x1083f, 0x10855}, +{0x10857, 0x1089e}, +{0x108a7, 0x108af}, +{0x108e0, 0x108f2}, +{0x108f4, 0x108f5}, +{0x108fb, 0x1091b}, +{0x1091f, 0x10939}, +{0x1093f, 0x1093f}, +{0x10980, 0x109b7}, +{0x109bc, 0x109cf}, +{0x109d2, 0x10a03}, +{0x10a05, 0x10a06}, +{0x10a0c, 0x10a13}, +{0x10a15, 0x10a17}, +{0x10a19, 0x10a35}, +{0x10a38, 0x10a3a}, +{0x10a3f, 0x10a48}, +{0x10a50, 0x10a58}, +{0x10a60, 0x10a9f}, +{0x10ac0, 0x10ae6}, +{0x10aeb, 0x10af6}, +{0x10b00, 0x10b35}, +{0x10b39, 0x10b55}, +{0x10b58, 0x10b72}, +{0x10b78, 0x10b91}, +{0x10b99, 0x10b9c}, +{0x10ba9, 0x10baf}, +{0x10c00, 0x10c48}, +{0x10c80, 0x10cb2}, +{0x10cc0, 0x10cf2}, +{0x10cfa, 0x10d27}, +{0x10d30, 0x10d39}, +{0x10d40, 0x10d65}, +{0x10d69, 0x10d85}, +{0x10d8e, 0x10d8f}, +{0x10e60, 0x10e7e}, +{0x10e80, 0x10ea9}, +{0x10eab, 0x10ead}, +{0x10eb0, 0x10eb1}, +{0x10ec2, 0x10ec4}, +{0x10efc, 0x10f27}, +{0x10f30, 0x10f59}, +{0x10f70, 0x10f89}, +{0x10fb0, 0x10fcb}, +{0x10fe0, 0x10ff6}, +{0x11000, 0x1104d}, +{0x11052, 0x11075}, +{0x1107f, 0x110c2}, +{0x110cd, 0x110cd}, +{0x110d0, 0x110e8}, +{0x110f0, 0x110f9}, +{0x11100, 0x11134}, +{0x11136, 0x11147}, +{0x11150, 0x11176}, +{0x11180, 0x111df}, +{0x111e1, 0x111f4}, +{0x11200, 0x11211}, +{0x11213, 0x11241}, +{0x11280, 0x11286}, +{0x11288, 0x11288}, +{0x1128a, 0x1128d}, +{0x1128f, 0x1129d}, +{0x1129f, 0x112a9}, +{0x112b0, 0x112ea}, +{0x112f0, 0x112f9}, +{0x11300, 0x11303}, +{0x11305, 0x1130c}, +{0x1130f, 0x11310}, +{0x11313, 0x11328}, +{0x1132a, 0x11330}, +{0x11332, 0x11333}, +{0x11335, 0x11339}, +{0x1133b, 0x11344}, +{0x11347, 0x11348}, +{0x1134b, 0x1134d}, +{0x11350, 0x11350}, +{0x11357, 0x11357}, +{0x1135d, 0x11363}, +{0x11366, 0x1136c}, +{0x11370, 0x11374}, +{0x11380, 0x11389}, +{0x1138b, 0x1138b}, +{0x1138e, 0x1138e}, +{0x11390, 0x113b5}, +{0x113b7, 0x113c0}, +{0x113c2, 0x113c2}, +{0x113c5, 0x113c5}, +{0x113c7, 0x113ca}, +{0x113cc, 0x113d5}, +{0x113d7, 0x113d8}, +{0x113e1, 0x113e2}, +{0x11400, 0x1145b}, +{0x1145d, 0x11461}, +{0x11480, 0x114c7}, +{0x114d0, 0x114d9}, +{0x11580, 0x115b5}, +{0x115b8, 0x115dd}, +{0x11600, 0x11644}, +{0x11650, 0x11659}, +{0x11660, 0x1166c}, +{0x11680, 0x116b9}, +{0x116c0, 0x116c9}, +{0x116d0, 0x116e3}, +{0x11700, 0x1171a}, +{0x1171d, 0x1172b}, +{0x11730, 0x11746}, +{0x11800, 0x1183b}, +{0x118a0, 0x118f2}, +{0x118ff, 0x11906}, +{0x11909, 0x11909}, +{0x1190c, 0x11913}, +{0x11915, 0x11916}, +{0x11918, 0x11935}, +{0x11937, 0x11938}, +{0x1193b, 0x11946}, +{0x11950, 0x11959}, +{0x119a0, 0x119a7}, +{0x119aa, 0x119d7}, +{0x119da, 0x119e4}, +{0x11a00, 0x11a47}, +{0x11a50, 0x11aa2}, +{0x11ab0, 0x11af8}, +{0x11b00, 0x11b09}, +{0x11bc0, 0x11be1}, +{0x11bf0, 0x11bf9}, +{0x11c00, 0x11c08}, +{0x11c0a, 0x11c36}, +{0x11c38, 0x11c45}, +{0x11c50, 0x11c6c}, +{0x11c70, 0x11c8f}, +{0x11c92, 0x11ca7}, +{0x11ca9, 0x11cb6}, +{0x11d00, 0x11d06}, +{0x11d08, 0x11d09}, +{0x11d0b, 0x11d36}, +{0x11d3a, 0x11d3a}, +{0x11d3c, 0x11d3d}, +{0x11d3f, 0x11d47}, +{0x11d50, 0x11d59}, +{0x11d60, 0x11d65}, +{0x11d67, 0x11d68}, +{0x11d6a, 0x11d8e}, +{0x11d90, 0x11d91}, +{0x11d93, 0x11d98}, +{0x11da0, 0x11da9}, +{0x11ee0, 0x11ef8}, +{0x11f00, 0x11f10}, +{0x11f12, 0x11f3a}, +{0x11f3e, 0x11f5a}, +{0x11fb0, 0x11fb0}, +{0x11fc0, 0x11ff1}, +{0x11fff, 0x12399}, +{0x12400, 0x1246e}, +{0x12470, 0x12474}, +{0x12480, 0x12543}, +{0x12f90, 0x12ff2}, +{0x13000, 0x13455}, +{0x13460, 0x143fa}, +{0x14400, 0x14646}, +{0x16100, 0x16139}, +{0x16800, 0x16a38}, +{0x16a40, 0x16a5e}, +{0x16a60, 0x16a69}, +{0x16a6e, 0x16abe}, +{0x16ac0, 0x16ac9}, +{0x16ad0, 0x16aed}, +{0x16af0, 0x16af5}, +{0x16b00, 0x16b45}, +{0x16b50, 0x16b59}, +{0x16b5b, 0x16b61}, +{0x16b63, 0x16b77}, +{0x16b7d, 0x16b8f}, +{0x16d40, 0x16d79}, +{0x16e40, 0x16e9a}, +{0x16f00, 0x16f4a}, +{0x16f4f, 0x16f87}, +{0x16f8f, 0x16f9f}, +{0x16fe0, 0x16fe4}, +{0x16ff0, 0x16ff1}, +{0x17000, 0x187f7}, +{0x18800, 0x18cd5}, +{0x18cff, 0x18d08}, +{0x1aff0, 0x1aff3}, +{0x1aff5, 0x1affb}, +{0x1affd, 0x1affe}, +{0x1b000, 0x1b122}, +{0x1b132, 0x1b132}, +{0x1b150, 0x1b152}, +{0x1b155, 0x1b155}, +{0x1b164, 0x1b167}, +{0x1b170, 0x1b2fb}, +{0x1bc00, 0x1bc6a}, +{0x1bc70, 0x1bc7c}, +{0x1bc80, 0x1bc88}, +{0x1bc90, 0x1bc99}, +{0x1bc9c, 0x1bca3}, +{0x1cc00, 0x1ccf9}, +{0x1cd00, 0x1ceb3}, +{0x1cf00, 0x1cf2d}, +{0x1cf30, 0x1cf46}, +{0x1cf50, 0x1cfc3}, +{0x1d000, 0x1d0f5}, +{0x1d100, 0x1d126}, +{0x1d129, 0x1d1ea}, +{0x1d200, 0x1d245}, +{0x1d2c0, 0x1d2d3}, +{0x1d2e0, 0x1d2f3}, +{0x1d300, 0x1d356}, +{0x1d360, 0x1d378}, +{0x1d400, 0x1d454}, +{0x1d456, 0x1d49c}, +{0x1d49e, 0x1d49f}, +{0x1d4a2, 0x1d4a2}, +{0x1d4a5, 0x1d4a6}, +{0x1d4a9, 0x1d4ac}, +{0x1d4ae, 0x1d4b9}, +{0x1d4bb, 0x1d4bb}, +{0x1d4bd, 0x1d4c3}, +{0x1d4c5, 0x1d505}, +{0x1d507, 0x1d50a}, +{0x1d50d, 0x1d514}, +{0x1d516, 0x1d51c}, +{0x1d51e, 0x1d539}, +{0x1d53b, 0x1d53e}, +{0x1d540, 0x1d544}, +{0x1d546, 0x1d546}, +{0x1d54a, 0x1d550}, +{0x1d552, 0x1d6a5}, +{0x1d6a8, 0x1d7cb}, +{0x1d7ce, 0x1da8b}, +{0x1da9b, 0x1da9f}, +{0x1daa1, 0x1daaf}, +{0x1df00, 0x1df1e}, +{0x1df25, 0x1df2a}, +{0x1e000, 0x1e006}, +{0x1e008, 0x1e018}, +{0x1e01b, 0x1e021}, +{0x1e023, 0x1e024}, +{0x1e026, 0x1e02a}, +{0x1e030, 0x1e06d}, +{0x1e08f, 0x1e08f}, +{0x1e100, 0x1e12c}, +{0x1e130, 0x1e13d}, +{0x1e140, 0x1e149}, +{0x1e14e, 0x1e14f}, +{0x1e290, 0x1e2ae}, +{0x1e2c0, 0x1e2f9}, +{0x1e2ff, 0x1e2ff}, +{0x1e4d0, 0x1e4f9}, +{0x1e5d0, 0x1e5fa}, +{0x1e5ff, 0x1e5ff}, +{0x1e7e0, 0x1e7e6}, +{0x1e7e8, 0x1e7eb}, +{0x1e7ed, 0x1e7ee}, +{0x1e7f0, 0x1e7fe}, +{0x1e800, 0x1e8c4}, +{0x1e8c7, 0x1e8d6}, +{0x1e900, 0x1e94b}, +{0x1e950, 0x1e959}, +{0x1e95e, 0x1e95f}, +{0x1ec71, 0x1ecb4}, +{0x1ed01, 0x1ed3d}, +{0x1ee00, 0x1ee03}, +{0x1ee05, 0x1ee1f}, +{0x1ee21, 0x1ee22}, +{0x1ee24, 0x1ee24}, +{0x1ee27, 0x1ee27}, +{0x1ee29, 0x1ee32}, +{0x1ee34, 0x1ee37}, +{0x1ee39, 0x1ee39}, +{0x1ee3b, 0x1ee3b}, +{0x1ee42, 0x1ee42}, +{0x1ee47, 0x1ee47}, +{0x1ee49, 0x1ee49}, +{0x1ee4b, 0x1ee4b}, +{0x1ee4d, 0x1ee4f}, +{0x1ee51, 0x1ee52}, +{0x1ee54, 0x1ee54}, +{0x1ee57, 0x1ee57}, +{0x1ee59, 0x1ee59}, +{0x1ee5b, 0x1ee5b}, +{0x1ee5d, 0x1ee5d}, +{0x1ee5f, 0x1ee5f}, +{0x1ee61, 0x1ee62}, +{0x1ee64, 0x1ee64}, +{0x1ee67, 0x1ee6a}, +{0x1ee6c, 0x1ee72}, +{0x1ee74, 0x1ee77}, +{0x1ee79, 0x1ee7c}, +{0x1ee7e, 0x1ee7e}, +{0x1ee80, 0x1ee89}, +{0x1ee8b, 0x1ee9b}, +{0x1eea1, 0x1eea3}, +{0x1eea5, 0x1eea9}, +{0x1eeab, 0x1eebb}, +{0x1eef0, 0x1eef1}, +{0x1f000, 0x1f02b}, +{0x1f030, 0x1f093}, +{0x1f0a0, 0x1f0ae}, +{0x1f0b1, 0x1f0bf}, +{0x1f0c1, 0x1f0cf}, +{0x1f0d1, 0x1f0f5}, +{0x1f100, 0x1f1ad}, +{0x1f1e6, 0x1f202}, +{0x1f210, 0x1f23b}, +{0x1f240, 0x1f248}, +{0x1f250, 0x1f251}, +{0x1f260, 0x1f265}, +{0x1f300, 0x1f6d7}, +{0x1f6dc, 0x1f6ec}, +{0x1f6f0, 0x1f6fc}, +{0x1f700, 0x1f776}, +{0x1f77b, 0x1f7d9}, +{0x1f7e0, 0x1f7eb}, +{0x1f7f0, 0x1f7f0}, +{0x1f800, 0x1f80b}, +{0x1f810, 0x1f847}, +{0x1f850, 0x1f859}, +{0x1f860, 0x1f887}, +{0x1f890, 0x1f8ad}, +{0x1f8b0, 0x1f8bb}, +{0x1f8c0, 0x1f8c1}, +{0x1f900, 0x1fa53}, +{0x1fa60, 0x1fa6d}, +{0x1fa70, 0x1fa7c}, +{0x1fa80, 0x1fa89}, +{0x1fa8f, 0x1fac6}, +{0x1face, 0x1fadc}, +{0x1fadf, 0x1fae9}, +{0x1faf0, 0x1faf8}, +{0x1fb00, 0x1fb92}, +{0x1fb94, 0x1fbf9}, +{0x20000, 0x2a6df}, +{0x2a700, 0x2b739}, +{0x2b740, 0x2b81d}, +{0x2b820, 0x2cea1}, +{0x2ceb0, 0x2ebe0}, +{0x2ebf0, 0x2ee5d}, +{0x2f800, 0x2fa1d}, +{0x30000, 0x3134a}, +{0x31350, 0x323af}, +{0xe0001, 0xe0001}, +{0xe0020, 0xe007f}, +{0xe0100, 0xe01ef}, +{0xf0000, 0xffffd}, +{0x100000, 0x10fffd}, diff --git a/unicode/nonspacing_chars.h b/unicode/nonspacing_chars.h new file mode 100644 index 00000000..77abfdf3 --- /dev/null +++ b/unicode/nonspacing_chars.h @@ -0,0 +1,377 @@ +/* + * Autogenerated by read_ucd.py from The Unicode Standard 16.0.0 + * + * Identify Unicode characters that occupy no character cells of a + * terminal. + * + * Used by utils/wcwidth.c. + */ + +{0x0300, 0x036f}, +{0x0483, 0x0489}, +{0x0591, 0x05bd}, +{0x05bf, 0x05bf}, +{0x05c1, 0x05c2}, +{0x05c4, 0x05c5}, +{0x05c7, 0x05c7}, +{0x0600, 0x0605}, +{0x0610, 0x061a}, +{0x061c, 0x061c}, +{0x064b, 0x065f}, +{0x0670, 0x0670}, +{0x06d6, 0x06dd}, +{0x06df, 0x06e4}, +{0x06e7, 0x06e8}, +{0x06ea, 0x06ed}, +{0x070f, 0x070f}, +{0x0711, 0x0711}, +{0x0730, 0x074a}, +{0x07a6, 0x07b0}, +{0x07eb, 0x07f3}, +{0x07fd, 0x07fd}, +{0x0816, 0x0819}, +{0x081b, 0x0823}, +{0x0825, 0x0827}, +{0x0829, 0x082d}, +{0x0859, 0x085b}, +{0x0890, 0x0891}, +{0x0897, 0x089f}, +{0x08ca, 0x0902}, +{0x093a, 0x093a}, +{0x093c, 0x093c}, +{0x0941, 0x0948}, +{0x094d, 0x094d}, +{0x0951, 0x0957}, +{0x0962, 0x0963}, +{0x0981, 0x0981}, +{0x09bc, 0x09bc}, +{0x09c1, 0x09c4}, +{0x09cd, 0x09cd}, +{0x09e2, 0x09e3}, +{0x09fe, 0x09fe}, +{0x0a01, 0x0a02}, +{0x0a3c, 0x0a3c}, +{0x0a41, 0x0a42}, +{0x0a47, 0x0a48}, +{0x0a4b, 0x0a4d}, +{0x0a51, 0x0a51}, +{0x0a70, 0x0a71}, +{0x0a75, 0x0a75}, +{0x0a81, 0x0a82}, +{0x0abc, 0x0abc}, +{0x0ac1, 0x0ac5}, +{0x0ac7, 0x0ac8}, +{0x0acd, 0x0acd}, +{0x0ae2, 0x0ae3}, +{0x0afa, 0x0aff}, +{0x0b01, 0x0b01}, +{0x0b3c, 0x0b3c}, +{0x0b3f, 0x0b3f}, +{0x0b41, 0x0b44}, +{0x0b4d, 0x0b4d}, +{0x0b55, 0x0b56}, +{0x0b62, 0x0b63}, +{0x0b82, 0x0b82}, +{0x0bc0, 0x0bc0}, +{0x0bcd, 0x0bcd}, +{0x0c00, 0x0c00}, +{0x0c04, 0x0c04}, +{0x0c3c, 0x0c3c}, +{0x0c3e, 0x0c40}, +{0x0c46, 0x0c48}, +{0x0c4a, 0x0c4d}, +{0x0c55, 0x0c56}, +{0x0c62, 0x0c63}, +{0x0c81, 0x0c81}, +{0x0cbc, 0x0cbc}, +{0x0cbf, 0x0cbf}, +{0x0cc6, 0x0cc6}, +{0x0ccc, 0x0ccd}, +{0x0ce2, 0x0ce3}, +{0x0d00, 0x0d01}, +{0x0d3b, 0x0d3c}, +{0x0d41, 0x0d44}, +{0x0d4d, 0x0d4d}, +{0x0d62, 0x0d63}, +{0x0d81, 0x0d81}, +{0x0dca, 0x0dca}, +{0x0dd2, 0x0dd4}, +{0x0dd6, 0x0dd6}, +{0x0e31, 0x0e31}, +{0x0e34, 0x0e3a}, +{0x0e47, 0x0e4e}, +{0x0eb1, 0x0eb1}, +{0x0eb4, 0x0ebc}, +{0x0ec8, 0x0ece}, +{0x0f18, 0x0f19}, +{0x0f35, 0x0f35}, +{0x0f37, 0x0f37}, +{0x0f39, 0x0f39}, +{0x0f71, 0x0f7e}, +{0x0f80, 0x0f84}, +{0x0f86, 0x0f87}, +{0x0f8d, 0x0f97}, +{0x0f99, 0x0fbc}, +{0x0fc6, 0x0fc6}, +{0x102d, 0x1030}, +{0x1032, 0x1037}, +{0x1039, 0x103a}, +{0x103d, 0x103e}, +{0x1058, 0x1059}, +{0x105e, 0x1060}, +{0x1071, 0x1074}, +{0x1082, 0x1082}, +{0x1085, 0x1086}, +{0x108d, 0x108d}, +{0x109d, 0x109d}, +{0x1160, 0x11ff}, +{0x135d, 0x135f}, +{0x1712, 0x1714}, +{0x1732, 0x1733}, +{0x1752, 0x1753}, +{0x1772, 0x1773}, +{0x17b4, 0x17b5}, +{0x17b7, 0x17bd}, +{0x17c6, 0x17c6}, +{0x17c9, 0x17d3}, +{0x17dd, 0x17dd}, +{0x180b, 0x180f}, +{0x1885, 0x1886}, +{0x18a9, 0x18a9}, +{0x1920, 0x1922}, +{0x1927, 0x1928}, +{0x1932, 0x1932}, +{0x1939, 0x193b}, +{0x1a17, 0x1a18}, +{0x1a1b, 0x1a1b}, +{0x1a56, 0x1a56}, +{0x1a58, 0x1a5e}, +{0x1a60, 0x1a60}, +{0x1a62, 0x1a62}, +{0x1a65, 0x1a6c}, +{0x1a73, 0x1a7c}, +{0x1a7f, 0x1a7f}, +{0x1ab0, 0x1ace}, +{0x1b00, 0x1b03}, +{0x1b34, 0x1b34}, +{0x1b36, 0x1b3a}, +{0x1b3c, 0x1b3c}, +{0x1b42, 0x1b42}, +{0x1b6b, 0x1b73}, +{0x1b80, 0x1b81}, +{0x1ba2, 0x1ba5}, +{0x1ba8, 0x1ba9}, +{0x1bab, 0x1bad}, +{0x1be6, 0x1be6}, +{0x1be8, 0x1be9}, +{0x1bed, 0x1bed}, +{0x1bef, 0x1bf1}, +{0x1c2c, 0x1c33}, +{0x1c36, 0x1c37}, +{0x1cd0, 0x1cd2}, +{0x1cd4, 0x1ce0}, +{0x1ce2, 0x1ce8}, +{0x1ced, 0x1ced}, +{0x1cf4, 0x1cf4}, +{0x1cf8, 0x1cf9}, +{0x1dc0, 0x1dff}, +{0x200b, 0x200f}, +{0x202a, 0x202e}, +{0x2060, 0x2064}, +{0x2066, 0x206f}, +{0x20d0, 0x20f0}, +{0x2cef, 0x2cf1}, +{0x2d7f, 0x2d7f}, +{0x2de0, 0x2dff}, +{0x302a, 0x302d}, +{0x3099, 0x309a}, +{0xa66f, 0xa672}, +{0xa674, 0xa67d}, +{0xa69e, 0xa69f}, +{0xa6f0, 0xa6f1}, +{0xa802, 0xa802}, +{0xa806, 0xa806}, +{0xa80b, 0xa80b}, +{0xa825, 0xa826}, +{0xa82c, 0xa82c}, +{0xa8c4, 0xa8c5}, +{0xa8e0, 0xa8f1}, +{0xa8ff, 0xa8ff}, +{0xa926, 0xa92d}, +{0xa947, 0xa951}, +{0xa980, 0xa982}, +{0xa9b3, 0xa9b3}, +{0xa9b6, 0xa9b9}, +{0xa9bc, 0xa9bd}, +{0xa9e5, 0xa9e5}, +{0xaa29, 0xaa2e}, +{0xaa31, 0xaa32}, +{0xaa35, 0xaa36}, +{0xaa43, 0xaa43}, +{0xaa4c, 0xaa4c}, +{0xaa7c, 0xaa7c}, +{0xaab0, 0xaab0}, +{0xaab2, 0xaab4}, +{0xaab7, 0xaab8}, +{0xaabe, 0xaabf}, +{0xaac1, 0xaac1}, +{0xaaec, 0xaaed}, +{0xaaf6, 0xaaf6}, +{0xabe5, 0xabe5}, +{0xabe8, 0xabe8}, +{0xabed, 0xabed}, +{0xfb1e, 0xfb1e}, +{0xfe00, 0xfe0f}, +{0xfe20, 0xfe2f}, +{0xfeff, 0xfeff}, +{0xfff9, 0xfffb}, +{0x101fd, 0x101fd}, +{0x102e0, 0x102e0}, +{0x10376, 0x1037a}, +{0x10a01, 0x10a03}, +{0x10a05, 0x10a06}, +{0x10a0c, 0x10a0f}, +{0x10a38, 0x10a3a}, +{0x10a3f, 0x10a3f}, +{0x10ae5, 0x10ae6}, +{0x10d24, 0x10d27}, +{0x10d69, 0x10d6d}, +{0x10eab, 0x10eac}, +{0x10efc, 0x10eff}, +{0x10f46, 0x10f50}, +{0x10f82, 0x10f85}, +{0x11001, 0x11001}, +{0x11038, 0x11046}, +{0x11070, 0x11070}, +{0x11073, 0x11074}, +{0x1107f, 0x11081}, +{0x110b3, 0x110b6}, +{0x110b9, 0x110ba}, +{0x110bd, 0x110bd}, +{0x110c2, 0x110c2}, +{0x110cd, 0x110cd}, +{0x11100, 0x11102}, +{0x11127, 0x1112b}, +{0x1112d, 0x11134}, +{0x11173, 0x11173}, +{0x11180, 0x11181}, +{0x111b6, 0x111be}, +{0x111c9, 0x111cc}, +{0x111cf, 0x111cf}, +{0x1122f, 0x11231}, +{0x11234, 0x11234}, +{0x11236, 0x11237}, +{0x1123e, 0x1123e}, +{0x11241, 0x11241}, +{0x112df, 0x112df}, +{0x112e3, 0x112ea}, +{0x11300, 0x11301}, +{0x1133b, 0x1133c}, +{0x11340, 0x11340}, +{0x11366, 0x1136c}, +{0x11370, 0x11374}, +{0x113bb, 0x113c0}, +{0x113ce, 0x113ce}, +{0x113d0, 0x113d0}, +{0x113d2, 0x113d2}, +{0x113e1, 0x113e2}, +{0x11438, 0x1143f}, +{0x11442, 0x11444}, +{0x11446, 0x11446}, +{0x1145e, 0x1145e}, +{0x114b3, 0x114b8}, +{0x114ba, 0x114ba}, +{0x114bf, 0x114c0}, +{0x114c2, 0x114c3}, +{0x115b2, 0x115b5}, +{0x115bc, 0x115bd}, +{0x115bf, 0x115c0}, +{0x115dc, 0x115dd}, +{0x11633, 0x1163a}, +{0x1163d, 0x1163d}, +{0x1163f, 0x11640}, +{0x116ab, 0x116ab}, +{0x116ad, 0x116ad}, +{0x116b0, 0x116b5}, +{0x116b7, 0x116b7}, +{0x1171d, 0x1171d}, +{0x1171f, 0x1171f}, +{0x11722, 0x11725}, +{0x11727, 0x1172b}, +{0x1182f, 0x11837}, +{0x11839, 0x1183a}, +{0x1193b, 0x1193c}, +{0x1193e, 0x1193e}, +{0x11943, 0x11943}, +{0x119d4, 0x119d7}, +{0x119da, 0x119db}, +{0x119e0, 0x119e0}, +{0x11a01, 0x11a0a}, +{0x11a33, 0x11a38}, +{0x11a3b, 0x11a3e}, +{0x11a47, 0x11a47}, +{0x11a51, 0x11a56}, +{0x11a59, 0x11a5b}, +{0x11a8a, 0x11a96}, +{0x11a98, 0x11a99}, +{0x11c30, 0x11c36}, +{0x11c38, 0x11c3d}, +{0x11c3f, 0x11c3f}, +{0x11c92, 0x11ca7}, +{0x11caa, 0x11cb0}, +{0x11cb2, 0x11cb3}, +{0x11cb5, 0x11cb6}, +{0x11d31, 0x11d36}, +{0x11d3a, 0x11d3a}, +{0x11d3c, 0x11d3d}, +{0x11d3f, 0x11d45}, +{0x11d47, 0x11d47}, +{0x11d90, 0x11d91}, +{0x11d95, 0x11d95}, +{0x11d97, 0x11d97}, +{0x11ef3, 0x11ef4}, +{0x11f00, 0x11f01}, +{0x11f36, 0x11f3a}, +{0x11f40, 0x11f40}, +{0x11f42, 0x11f42}, +{0x11f5a, 0x11f5a}, +{0x13430, 0x13440}, +{0x13447, 0x13455}, +{0x1611e, 0x16129}, +{0x1612d, 0x1612f}, +{0x16af0, 0x16af4}, +{0x16b30, 0x16b36}, +{0x16f4f, 0x16f4f}, +{0x16f8f, 0x16f92}, +{0x16fe4, 0x16fe4}, +{0x1bc9d, 0x1bc9e}, +{0x1bca0, 0x1bca3}, +{0x1cf00, 0x1cf2d}, +{0x1cf30, 0x1cf46}, +{0x1d167, 0x1d169}, +{0x1d173, 0x1d182}, +{0x1d185, 0x1d18b}, +{0x1d1aa, 0x1d1ad}, +{0x1d242, 0x1d244}, +{0x1da00, 0x1da36}, +{0x1da3b, 0x1da6c}, +{0x1da75, 0x1da75}, +{0x1da84, 0x1da84}, +{0x1da9b, 0x1da9f}, +{0x1daa1, 0x1daaf}, +{0x1e000, 0x1e006}, +{0x1e008, 0x1e018}, +{0x1e01b, 0x1e021}, +{0x1e023, 0x1e024}, +{0x1e026, 0x1e02a}, +{0x1e08f, 0x1e08f}, +{0x1e130, 0x1e136}, +{0x1e2ae, 0x1e2ae}, +{0x1e2ec, 0x1e2ef}, +{0x1e4ec, 0x1e4ef}, +{0x1e5ee, 0x1e5ef}, +{0x1e8d0, 0x1e8d6}, +{0x1e944, 0x1e94a}, +{0xe0001, 0xe0001}, +{0xe0020, 0xe007f}, +{0xe0100, 0xe01ef}, diff --git a/unicode/read_ucd.py b/unicode/read_ucd.py new file mode 100755 index 00000000..6fd0dd7d --- /dev/null +++ b/unicode/read_ucd.py @@ -0,0 +1,510 @@ +#!/usr/bin/env python3 + +# Tool to read various files from the Unicode character database and +# generate headers containing derived arrays and lookup tables needed +# by PuTTY. +# +# The aim is to have this be a single tool which you can easily re-run +# against a new version of Unicode, simply by pointing it at an +# appropriate UCD.zip or a directory containing the same files +# unpacked. + +import argparse +import collections +import io +import os +import string +import sys +import zipfile + +UCDRecord = collections.namedtuple('UCDRecord', [ + 'c', + 'General_Category', + 'Canonical_Combining_Class', + 'Bidi_Class', + 'Decomposition_Type', + 'Decomposition_Mapping', +]) + +def to_ranges(iterable): + """Collect together adjacent ranges in a list of (key, value) pairs. + + The input iterable should deliver a sequence of (key, value) pairs + in which the keys are integers in sorted order. The output is a + sequence of tuples with structure ((start, end), value), each + indicating that all the keys [start, start+1, ..., end] go with + that value. + """ + start = end = val = None + + for k, v in iterable: + if (k-1, v) == (end, val): + end = k + else: + if start is not None: + yield (start, end), val + start, end, val = k, k, v + + if start is not None: + yield (start, end), val + +def map_to_ranges(m): + """Convert an integer-keyed map into a list of (range, value) pairs.""" + yield from to_ranges(sorted(m.items())) + +def set_to_ranges(s): + """Convert a set into a list of ranges.""" + for r, _ in to_ranges((x, None) for x in sorted(s)): + yield r + +def lines(iterable, keep_comments=False): + """Deliver the lines of a Unicode data file. + + The input iterable should yield raw lines of the file: for + example, it can be the file handle itself. The output values have + their newlines removed, comments and trailing spaces deleted, and + blank lines discarded. + """ + for line in iter(iterable): + line = line.rstrip("\r\n") + if not keep_comments: + line = line.split("#", 1)[0] + line = line.rstrip(" \t") + if line == "": + continue + yield line + +class Main: + def run(self): + "Parse arguments and generate all the output files." + + parser = argparse.ArgumentParser( + description='Build UCD-derived source files.') + parser.add_argument("ucd", help="UCD to work from, either UCD.zip or " + "a directory full of unpacked files.") + args = parser.parse_args() + + if os.path.isdir(args.ucd): + ucd_dir = args.ucd + self.open_ucd_file = lambda filename: ( + open(os.path.join(ucd_dir, filename))) + else: + ucd_zip = zipfile.ZipFile(args.ucd) + self.open_ucd_file = lambda filename: ( + io.TextIOWrapper(ucd_zip.open(filename))) + + self.find_unicode_version() + + with open("version.h", "w") as fh: + self.write_version_header(fh) + with open("bidi_type.h", "w") as fh: + self.write_bidi_type_table(fh) + with open("bidi_mirror.h", "w") as fh: + self.write_bidi_mirroring_table(fh) + with open("bidi_brackets.h", "w") as fh: + self.write_bidi_brackets_table(fh) + with open("nonspacing_chars.h", "w") as fh: + self.write_nonspacing_chars_list(fh) + with open("wide_chars.h", "w") as fh: + self.write_wide_chars_list(fh) + with open("ambiguous_wide_chars.h", "w") as fh: + self.write_ambiguous_wide_chars_list(fh) + with open("known_chars.h", "w") as fh: + self.write_known_chars_table(fh) + with open("combining_classes.h", "w") as fh: + self.write_combining_class_table(fh) + with open("canonical_decomp.h", "w") as fh: + self.write_canonical_decomp_table(fh) + with open("canonical_comp.h", "w") as fh: + self.write_canonical_comp_table(fh) + + def find_unicode_version(self): + """Find out the version of Unicode. + + This is read from the top of NamesList.txt, which has the + closest thing to a machine-readable statement of the version + number that I found in the whole collection of files. + """ + with self.open_ucd_file("NamesList.txt") as fh: + for line in lines(fh): + if line.startswith("@@@\t"): + self.unicode_version_full = line[4:] + self.unicode_version_short = " ".join( + w for w in self.unicode_version_full.split(" ") + if any(c in string.digits for c in w)) + return + + @property + def UnicodeData(self): + """Records from UnicodeData.txt. + + Each yielded item is a UCDRecord tuple. + """ + with self.open_ucd_file("UnicodeData.txt") as fh: + for line in lines(fh): + # Split up the line into its raw fields. + ( + codepoint, name, category, cclass, bidiclass, decomp, + num6, num7, num8, bidimirrored, obsolete_unicode1_name, + obsolete_comment, uppercase, lowercase, titlecase, + ) = line.split(";") + + # By default, we expect that this record describes + # just one code point. + codepoints = [int(codepoint, 16)] + + # Spot the special markers where consecutive lines say + # and , indicating that the + # entire range of code points in between are treated + # the same. If so, we replace 'codepoints' with a + # range object. + if "<" in name: + assert name.startswith("<") and name.endswith(">"), ( + "Confusing < in character name: {!r}".format(line)) + name_pieces = [piece.strip(" \t") for piece in + name.lstrip("<").rstrip(">").split(",")] + if "First" in name_pieces: + assert isinstance(codepoints, list) + prev_line_was_first = True + prev_codepoint = codepoints[0] + continue + elif "Last" in name_pieces: + assert prev_line_was_first + codepoints = range(prev_codepoint, codepoints[0]+1) + del prev_codepoint + prev_line_was_first = False + + # Decode some of the raw fields into more cooked + # forms. + cclass = int(cclass) + + # Separate the decomposition field into decomposition + # type and mapping. + if decomp == "": + dtype = decomp = None + elif "<" not in decomp: + dtype = 'canonical' + else: + assert decomp.startswith("<") + dtype, decomp = decomp[1:].split(">", 1) + decomp = decomp.lstrip(" ") + # And decode the mapping part from hex strings to integers. + if decomp is not None: + decomp = [int(w, 16) for w in decomp.split(" ")] + + # And yield a UCDRecord for each code point in our + # range. + for codepoint in codepoints: + yield UCDRecord( + c=codepoint, + General_Category=category, + Canonical_Combining_Class=cclass, + Bidi_Class=bidiclass, + Decomposition_Type=dtype, + Decomposition_Mapping=decomp, + ) + + @property + def BidiMirroring(self): + """Parsed character pairs from BidiMirroring.txt. + + Each yielded tuple is a pair of Unicode code points. + """ + with self.open_ucd_file("BidiMirroring.txt") as fh: + for line in lines(fh): + cs1, cs2 = line.split(";") + c1 = int(cs1, 16) + c2 = int(cs2, 16) + yield c1, c2 + + @property + def BidiBrackets(self): + """Bracket pairs from BidiBrackets.txt. + + Each yielded tuple is a pair of Unicode code points, followed + by either 'o', 'c' or 'n' to indicate whether the first one is + an open or closing parenthesis or neither. + """ + with self.open_ucd_file("BidiBrackets.txt") as fh: + for line in lines(fh): + cs1, cs2, kind = line.split(";") + c1 = int(cs1, 16) + c2 = int(cs2, 16) + kind = kind.strip(" \t") + yield c1, c2, kind + + @property + def EastAsianWidth(self): + """East Asian width types from EastAsianWidth.txt. + + Each yielded tuple is (code point, width type). + """ + with self.open_ucd_file("EastAsianWidth.txt") as fh: + for line in lines(fh): + fields = line.split(";") + if ".." in fields[0]: + start, end = [int(s, 16) for s in fields[0].split("..")] + cs = range(start, end+1) + else: + cs = [int(fields[0], 16)] + for c in cs: + yield c, fields[1].strip() + + @property + def CompositionExclusions(self): + """Composition exclusions from CompositionExclusions.txt. + + Each yielded item is just a code point. + """ + with self.open_ucd_file("CompositionExclusions.txt") as fh: + for line in lines(fh): + yield int(line, 16) + + def write_file_header_comment(self, fh, description): + print("/*", file=fh) + print(" * Autogenerated by read_ucd.py from", + self.unicode_version_full, file=fh) + print(" *", file=fh) + for line in description.strip("\n").split("\n"): + print(" *" + (" " if line != "" else "") + line, file=fh) + print(" */", file=fh) + print(file=fh) + + def write_version_header(self, fh): + self.write_file_header_comment(fh, """ + +String literals giving the currently supported version of Unicode. +Useful for error messages and 'about' boxes. + +""") + assert all(0x20 <= ord(c) < 0x7F and c != '"' + for c in self.unicode_version_full) + + print("#define UNICODE_VERSION_FULL \"{}\"".format( + self.unicode_version_full), file=fh) + print("#define UNICODE_VERSION_SHORT \"{}\"".format( + self.unicode_version_short), file=fh) + + def write_bidi_type_table(self, fh): + self.write_file_header_comment(fh, """ + +Bidirectional type of every Unicode character, excluding those with +type ON. + +Used by terminal/bidi.c, whose associated lookup function returns ON +by default for anything not in this list. + +""") + types = {} + + for rec in self.UnicodeData: + if rec.Bidi_Class != "ON": + types[rec.c] = rec.Bidi_Class + + for (start, end), t in map_to_ranges(types): + print(f"{{0x{start:04x}, 0x{end:04x}, {t}}},", file=fh) + + def write_bidi_mirroring_table(self, fh): + self.write_file_header_comment(fh, """ + +Map each Unicode character to its mirrored form when printing right to +left. + +Used by terminal/bidi.c. + +""") + bidi_mirror = {} + for c1, c2 in self.BidiMirroring: + assert bidi_mirror.get(c1, c2) == c2, f"Clash at {c1:%04X}" + bidi_mirror[c1] = c2 + assert bidi_mirror.get(c2, c1) == c1, f"Clash at {c2:%04X}" + bidi_mirror[c2] = c1 + + for c1, c2 in sorted(bidi_mirror.items()): + print("{{0x{:04x}, 0x{:04x}}},".format(c1, c2), file=fh) + + def write_bidi_brackets_table(self, fh): + self.write_file_header_comment(fh, """ + +Identify Unicode characters that count as brackets for the purposes of +bidirectional text layout. For each one, indicate whether it's an open +or closed bracket, and identify up to two characters that can act as +its counterpart. + +Used by terminal/bidi.c. + +""") + bracket_map = {} + for c1, c2, kind in self.BidiBrackets: + bracket_map[c1] = kind, c2 + + equivalents = {} + for rec in self.UnicodeData: + if (rec.Decomposition_Type == 'canonical' and + len(rec.Decomposition_Mapping) == 1): + c = rec.c + c2 = rec.Decomposition_Mapping[0] + equivalents[c] = c2 + equivalents[c2] = c + + for src, (kind, dst) in sorted(bracket_map.items()): + dsteq = equivalents.get(dst, 0) + # UCD claims there's an 'n' kind possible, but as of UCD + # 14, no instances of it exist + enumval = {'o': 'BT_OPEN', 'c': 'BT_CLOSE'}[kind] + print("{{0x{:04x}, {{0x{:04x}, 0x{:04x}, {}}}}},".format( + src, dst, dsteq, enumval), file=fh) + + def write_nonspacing_chars_list(self, fh): + self.write_file_header_comment(fh, """ + +Identify Unicode characters that occupy no character cells of a +terminal. + +Used by utils/wcwidth.c. + +""") + cs = set() + + for rec in self.UnicodeData: + nonspacing = rec.General_Category in {"Me", "Mn", "Cf"} + if rec.c == 0xAD: + # In typography this is a SOFT HYPHEN and counts as + # discardable. But it's also an ISO 8859-1 printing + # character, and all of those occupy one character + # cell in a terminal. + nonspacing = False + if 0x1160 <= rec.c <= 0x11FF: + # Medial (vowel) and final (consonant) jamo for + # decomposed Hangul characters. These are regarded as + # non-spacing on the grounds that they compose with + # the preceding initial consonant. + nonspacing = True + if nonspacing: + cs.add(rec.c) + + for start, end in set_to_ranges(cs): + print(f"{{0x{start:04x}, 0x{end:04x}}},", file=fh) + + def write_width_table(self, fh, accept): + cs = set() + + for c, wid in self.EastAsianWidth: + if wid in accept: + cs.add(c) + + for start, end in set_to_ranges(cs): + print(f"{{0x{start:04x}, 0x{end:04x}}},", file=fh) + + def write_wide_chars_list(self, fh): + self.write_file_header_comment(fh, """ + +Identify Unicode characters that occupy two adjacent character cells +in a terminal. + +Used by utils/wcwidth.c. + +""") + self.write_width_table(fh, {'W', 'F'}) + + def write_ambiguous_wide_chars_list(self, fh): + self.write_file_header_comment(fh, """ + +Identify Unicode characters that are width-ambiguous: some regimes +regard them as occupying two adjacent character cells in a terminal, +and others do not. + +Used by utils/wcwidth.c. + +""") + self.write_width_table(fh, {'A'}) + + def write_known_chars_table(self, fh): + self.write_file_header_comment(fh, """ + +List the Unicode code points that are known to this version of the +standard at all. + +Used by utils/unicode-known.c. + +""") + chars = set(rec.c for rec in self.UnicodeData) + + for start, end in set_to_ranges(chars): + print(f"{{0x{start:04x}, 0x{end:04x}}},", file=fh) + + def write_combining_class_table(self, fh): + self.write_file_header_comment(fh, """ + +List the canonical combining class of each Unicode character, if it is +not zero. This controls how combining marks can be reordered by the +Unicode normalisation algorithms. + +Used by utils/unicode-norm.c. + +""") + cclasses = {} + + for rec in self.UnicodeData: + cc = rec.Canonical_Combining_Class + if cc != 0: + cclasses[rec.c] = cc + + for (start, end), cclass in map_to_ranges(cclasses): + print(f"{{0x{start:04x}, 0x{end:04x}, {cclass:d}}},", file=fh) + + def write_canonical_decomp_table(self, fh): + self.write_file_header_comment(fh, """ + +List the canonical decomposition of every Unicode character that has +one. This consists of up to two characters, but those may need +decomposition in turn. + +Used by utils/unicode-norm.c. + +""") + decomps = {} + + for rec in self.UnicodeData: + if rec.Decomposition_Type != 'canonical': + continue + # Fill in a zero code point as the second character, if + # it's only one character long + decomps[rec.c] = (rec.Decomposition_Mapping + [0])[:2] + + for c, (d1, d2) in sorted(decomps.items()): + d2s = f"0x{d2:04x}" if d2 else "0" + print(f"{{0x{c:04x}, 0x{d1:04x}, {d2s}}},", file=fh) + + def write_canonical_comp_table(self, fh): + self.write_file_header_comment(fh, """ + +List the pairs of Unicode characters that canonically recompose to a +single character in NFC. + +Used by utils/unicode-norm.c. + +""") + exclusions = set(self.CompositionExclusions) + nonstarters = set(rec.c for rec in self.UnicodeData + if rec.Canonical_Combining_Class != 0) + + decomps = {} + + for rec in self.UnicodeData: + if rec.Decomposition_Type != 'canonical': + continue # we don't want compatibility decompositions + if len(rec.Decomposition_Mapping) != 2: + continue # we don't want singletons either + if rec.c in exclusions: + continue # we don't want anything explicitly excluded + if (rec.c in nonstarters or + rec.Decomposition_Mapping[0] in nonstarters): + continue # we don't want non-starter decompositions + decomps[tuple(rec.Decomposition_Mapping)] = rec.c + + for (d0, d1), c in sorted(decomps.items()): + print(f"{{0x{d0:04x}, 0x{d1:04x}, 0x{c:04x}}},", file=fh) + +if __name__ == '__main__': + Main().run() diff --git a/unicode/version.h b/unicode/version.h new file mode 100644 index 00000000..2efe9d03 --- /dev/null +++ b/unicode/version.h @@ -0,0 +1,9 @@ +/* + * Autogenerated by read_ucd.py from The Unicode Standard 16.0.0 + * + * String literals giving the currently supported version of Unicode. + * Useful for error messages and 'about' boxes. + */ + +#define UNICODE_VERSION_FULL "The Unicode Standard 16.0.0" +#define UNICODE_VERSION_SHORT "16.0.0" diff --git a/unicode/wide_chars.h b/unicode/wide_chars.h new file mode 100644 index 00000000..935bef62 --- /dev/null +++ b/unicode/wide_chars.h @@ -0,0 +1,131 @@ +/* + * Autogenerated by read_ucd.py from The Unicode Standard 16.0.0 + * + * Identify Unicode characters that occupy two adjacent character cells + * in a terminal. + * + * Used by utils/wcwidth.c. + */ + +{0x1100, 0x115f}, +{0x231a, 0x231b}, +{0x2329, 0x232a}, +{0x23e9, 0x23ec}, +{0x23f0, 0x23f0}, +{0x23f3, 0x23f3}, +{0x25fd, 0x25fe}, +{0x2614, 0x2615}, +{0x2630, 0x2637}, +{0x2648, 0x2653}, +{0x267f, 0x267f}, +{0x268a, 0x268f}, +{0x2693, 0x2693}, +{0x26a1, 0x26a1}, +{0x26aa, 0x26ab}, +{0x26bd, 0x26be}, +{0x26c4, 0x26c5}, +{0x26ce, 0x26ce}, +{0x26d4, 0x26d4}, +{0x26ea, 0x26ea}, +{0x26f2, 0x26f3}, +{0x26f5, 0x26f5}, +{0x26fa, 0x26fa}, +{0x26fd, 0x26fd}, +{0x2705, 0x2705}, +{0x270a, 0x270b}, +{0x2728, 0x2728}, +{0x274c, 0x274c}, +{0x274e, 0x274e}, +{0x2753, 0x2755}, +{0x2757, 0x2757}, +{0x2795, 0x2797}, +{0x27b0, 0x27b0}, +{0x27bf, 0x27bf}, +{0x2b1b, 0x2b1c}, +{0x2b50, 0x2b50}, +{0x2b55, 0x2b55}, +{0x2e80, 0x2e99}, +{0x2e9b, 0x2ef3}, +{0x2f00, 0x2fd5}, +{0x2ff0, 0x303e}, +{0x3041, 0x3096}, +{0x3099, 0x30ff}, +{0x3105, 0x312f}, +{0x3131, 0x318e}, +{0x3190, 0x31e5}, +{0x31ef, 0x321e}, +{0x3220, 0x3247}, +{0x3250, 0xa48c}, +{0xa490, 0xa4c6}, +{0xa960, 0xa97c}, +{0xac00, 0xd7a3}, +{0xf900, 0xfaff}, +{0xfe10, 0xfe19}, +{0xfe30, 0xfe52}, +{0xfe54, 0xfe66}, +{0xfe68, 0xfe6b}, +{0xff01, 0xff60}, +{0xffe0, 0xffe6}, +{0x16fe0, 0x16fe4}, +{0x16ff0, 0x16ff1}, +{0x17000, 0x187f7}, +{0x18800, 0x18cd5}, +{0x18cff, 0x18d08}, +{0x1aff0, 0x1aff3}, +{0x1aff5, 0x1affb}, +{0x1affd, 0x1affe}, +{0x1b000, 0x1b122}, +{0x1b132, 0x1b132}, +{0x1b150, 0x1b152}, +{0x1b155, 0x1b155}, +{0x1b164, 0x1b167}, +{0x1b170, 0x1b2fb}, +{0x1d300, 0x1d356}, +{0x1d360, 0x1d376}, +{0x1f004, 0x1f004}, +{0x1f0cf, 0x1f0cf}, +{0x1f18e, 0x1f18e}, +{0x1f191, 0x1f19a}, +{0x1f200, 0x1f202}, +{0x1f210, 0x1f23b}, +{0x1f240, 0x1f248}, +{0x1f250, 0x1f251}, +{0x1f260, 0x1f265}, +{0x1f300, 0x1f320}, +{0x1f32d, 0x1f335}, +{0x1f337, 0x1f37c}, +{0x1f37e, 0x1f393}, +{0x1f3a0, 0x1f3ca}, +{0x1f3cf, 0x1f3d3}, +{0x1f3e0, 0x1f3f0}, +{0x1f3f4, 0x1f3f4}, +{0x1f3f8, 0x1f43e}, +{0x1f440, 0x1f440}, +{0x1f442, 0x1f4fc}, +{0x1f4ff, 0x1f53d}, +{0x1f54b, 0x1f54e}, +{0x1f550, 0x1f567}, +{0x1f57a, 0x1f57a}, +{0x1f595, 0x1f596}, +{0x1f5a4, 0x1f5a4}, +{0x1f5fb, 0x1f64f}, +{0x1f680, 0x1f6c5}, +{0x1f6cc, 0x1f6cc}, +{0x1f6d0, 0x1f6d2}, +{0x1f6d5, 0x1f6d7}, +{0x1f6dc, 0x1f6df}, +{0x1f6eb, 0x1f6ec}, +{0x1f6f4, 0x1f6fc}, +{0x1f7e0, 0x1f7eb}, +{0x1f7f0, 0x1f7f0}, +{0x1f90c, 0x1f93a}, +{0x1f93c, 0x1f945}, +{0x1f947, 0x1f9ff}, +{0x1fa70, 0x1fa7c}, +{0x1fa80, 0x1fa89}, +{0x1fa8f, 0x1fac6}, +{0x1face, 0x1fadc}, +{0x1fadf, 0x1fae9}, +{0x1faf0, 0x1faf8}, +{0x20000, 0x2fffd}, +{0x30000, 0x3fffd}, diff --git a/unix/CMakeLists.txt b/unix/CMakeLists.txt index ce02098c..4d8ef964 100644 --- a/unix/CMakeLists.txt +++ b/unix/CMakeLists.txt @@ -4,6 +4,7 @@ add_sources_from_current_dir(utils utils/arm_arch_queries.c utils/block_signal.c utils/cloexec.c + utils/cmdline_arg.c utils/dputs.c utils/filename.c utils/fontspec.c @@ -52,10 +53,10 @@ add_sources_from_current_dir(agent add_executable(fuzzterm ${CMAKE_SOURCE_DIR}/test/fuzzterm.c - ${CMAKE_SOURCE_DIR}/logging.c ${CMAKE_SOURCE_DIR}/stubs/no-print.c unicode.c - no-gtk.c) + no-gtk.c + $) be_list(fuzzterm FuZZterm) add_dependencies(fuzzterm generated_licence_h) target_link_libraries(fuzzterm @@ -64,15 +65,16 @@ target_link_libraries(fuzzterm add_executable(osxlaunch osxlaunch.c) -add_sources_from_current_dir(plink no-gtk.c) -add_sources_from_current_dir(pscp no-gtk.c) -add_sources_from_current_dir(psftp no-gtk.c) +add_sources_from_current_dir(plink unicode.c no-gtk.c) +add_sources_from_current_dir(pscp unicode.c no-gtk.c) +add_sources_from_current_dir(psftp unicode.c no-gtk.c) add_sources_from_current_dir(psocks no-gtk.c) add_executable(psusan psusan.c ${CMAKE_SOURCE_DIR}/stubs/no-gss.c ${CMAKE_SOURCE_DIR}/ssh/scpserver.c + unicode.c no-gtk.c pty.c) be_list(psusan psusan) @@ -120,6 +122,7 @@ add_executable(uppity ${CMAKE_SOURCE_DIR}/ssh/scpserver.c no-gtk.c pty.c + unicode.c ${CMAKE_SOURCE_DIR}/stubs/no-gss.c) be_list(uppity Uppity) target_link_libraries(uppity @@ -143,11 +146,12 @@ if(GTK_FOUND) main-gtk-simple.c ${CMAKE_SOURCE_DIR}/stubs/no-gss.c ${CMAKE_SOURCE_DIR}/stubs/no-ca-config.c + ${CMAKE_SOURCE_DIR}/stubs/no-console.c ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c pty.c) be_list(pterm pterm) target_link_libraries(pterm - guiterminal eventloop settings charset utils ptermxpms + guiterminal eventloop settings utils ptermxpms ${GTK_LIBRARIES} ${X11_LIBRARIES}) installed_program(pterm) @@ -158,21 +162,23 @@ if(GTK_FOUND) ${CMAKE_SOURCE_DIR}/stubs/no-cmdline.c ${CMAKE_SOURCE_DIR}/stubs/no-gss.c ${CMAKE_SOURCE_DIR}/stubs/no-ca-config.c + ${CMAKE_SOURCE_DIR}/stubs/no-console.c ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c pty.c) be_list(ptermapp pterm) target_link_libraries(ptermapp - guiterminal eventloop settings charset utils ptermxpms + guiterminal eventloop settings utils ptermxpms ${GTK_LIBRARIES} ${X11_LIBRARIES}) endif() add_executable(putty putty.c - main-gtk-simple.c) + main-gtk-simple.c + ${CMAKE_SOURCE_DIR}/stubs/no-console.c) be_list(putty PuTTY SSH SERIAL OTHERBACKENDS) target_link_libraries(putty guiterminal eventloop sshclient otherbackends settings - network crypto charset utils puttyxpms + network crypto utils puttyxpms ${GTK_LIBRARIES} ${X11_LIBRARIES}) set_target_properties(putty PROPERTIES LINK_INTERFACE_MULTIPLICITY 2) @@ -182,11 +188,12 @@ if(GTK_FOUND) add_executable(puttyapp putty.c main-gtk-application.c - ${CMAKE_SOURCE_DIR}/stubs/no-cmdline.c) + ${CMAKE_SOURCE_DIR}/stubs/no-cmdline.c + ${CMAKE_SOURCE_DIR}/stubs/no-console.c) be_list(puttyapp PuTTY SSH SERIAL OTHERBACKENDS) target_link_libraries(puttyapp guiterminal eventloop sshclient otherbackends settings - network crypto charset utils puttyxpms + network crypto utils puttyxpms ${GTK_LIBRARIES} ${X11_LIBRARIES}) endif() @@ -195,14 +202,33 @@ if(GTK_FOUND) main-gtk-simple.c ${CMAKE_SOURCE_DIR}/stubs/no-gss.c ${CMAKE_SOURCE_DIR}/stubs/no-ca-config.c + ${CMAKE_SOURCE_DIR}/stubs/no-console.c ${CMAKE_SOURCE_DIR}/stubs/no-rand.c ${CMAKE_SOURCE_DIR}/proxy/nocproxy.c ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c) be_list(puttytel PuTTYtel SERIAL OTHERBACKENDS) target_link_libraries(puttytel - guiterminal eventloop otherbackends settings network charset utils + guiterminal eventloop otherbackends settings network utils puttyxpms ${GTK_LIBRARIES} ${X11_LIBRARIES}) + + add_executable(test_lineedit + ${CMAKE_SOURCE_DIR}/test/test_lineedit.c + ${CMAKE_SOURCE_DIR}/stubs/no-gss.c + ${CMAKE_SOURCE_DIR}/stubs/no-logging.c + ${CMAKE_SOURCE_DIR}/stubs/no-printing.c + ${CMAKE_SOURCE_DIR}/stubs/no-storage.c + ${CMAKE_SOURCE_DIR}/stubs/no-timing.c) + target_link_libraries(test_lineedit + guiterminal settings eventloop utils ${platform_libraries}) + + add_executable(test_terminal + ${CMAKE_SOURCE_DIR}/test/test_terminal.c + ${CMAKE_SOURCE_DIR}/stubs/no-gss.c + ${CMAKE_SOURCE_DIR}/stubs/no-storage.c + ${CMAKE_SOURCE_DIR}/stubs/no-timing.c) + target_link_libraries(test_terminal + guiterminal settings eventloop utils ${platform_libraries}) endif() # Pageant is built whether we have GTK or not; in its absence we @@ -227,3 +253,5 @@ target_link_libraries(pageant eventloop console agent settings network crypto utils ${pageant_libs}) installed_program(pageant) + +add_sources_from_current_dir(test_conf unicode.c stubs/no-uxsel.c) diff --git a/unix/askpass.c b/unix/askpass.c index b45a1c23..a1143a8f 100644 --- a/unix/askpass.c +++ b/unix/askpass.c @@ -46,8 +46,7 @@ struct askpass_ctx { GdkColor cols[3]; #endif char *error_message; /* if we finish without a passphrase */ - char *passphrase; /* if we finish with one */ - int passlen, passsize; + strbuf *passphrase; /* if we finish with one */ #if GTK_CHECK_VERSION(3,20,0) GdkSeat *seat; /* for gdk_seat_grab */ #elif GTK_CHECK_VERSION(3,0,0) @@ -107,48 +106,30 @@ static void visually_acknowledge_keypress(struct askpass_ctx *ctx) ctx->active_area = new_active; } -static int last_char_len(struct askpass_ctx *ctx) +static size_t last_char_start(struct askpass_ctx *ctx) { /* * GTK always encodes in UTF-8, so we can do this in a fixed way. */ - int i; - assert(ctx->passlen > 0); - i = ctx->passlen - 1; - while ((unsigned)((unsigned char)ctx->passphrase[i] - 0x80) < 0x40) { + assert(ctx->passphrase->len > 0); + size_t i = ctx->passphrase->len - 1; + while ((unsigned)(ctx->passphrase->u[i] - 0x80) < 0x40) { if (i == 0) break; i--; } - return ctx->passlen - i; + return i; } static void add_text_to_passphrase(struct askpass_ctx *ctx, gchar *str) { - int len = strlen(str); - if (ctx->passlen + len >= ctx->passsize) { - /* Take some care with buffer expansion, because there are - * pieces of passphrase in the old buffer so we should ensure - * realloc doesn't leave a copy lying around in the address - * space. */ - int oldsize = ctx->passsize; - char *newbuf; - - ctx->passsize = (ctx->passlen + len) * 5 / 4 + 1024; - newbuf = snewn(ctx->passsize, char); - memcpy(newbuf, ctx->passphrase, oldsize); - smemclr(ctx->passphrase, oldsize); - sfree(ctx->passphrase); - ctx->passphrase = newbuf; - } - strcpy(ctx->passphrase + ctx->passlen, str); - ctx->passlen += len; + put_datapl(ctx->passphrase, ptrlen_from_asciz(str)); visually_acknowledge_keypress(ctx); } static void cancel_askpass(struct askpass_ctx *ctx, const char *msg) { - smemclr(ctx->passphrase, ctx->passsize); + strbuf_free(ctx->passphrase); ctx->passphrase = NULL; ctx->error_message = dupstr(msg); gtk_main_quit(); @@ -182,7 +163,7 @@ static gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) if (event->type == GDK_KEY_PRESS) { if (!strcmp(event->string, "\x15")) { /* Ctrl-U. Wipe out the whole line */ - ctx->passlen = 0; + strbuf_clear(ctx->passphrase); visually_acknowledge_keypress(ctx); } else if (!strcmp(event->string, "\x17")) { /* Ctrl-W. Delete back to the last space->nonspace @@ -190,20 +171,21 @@ static gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) * way (mimicking terminal drivers), and don't attempt * to second-guess exciting Unicode space * characters. */ - while (ctx->passlen > 0) { + while (ctx->passphrase->len > 0) { char deleted, prior; - ctx->passlen -= last_char_len(ctx); - deleted = ctx->passphrase[ctx->passlen]; - prior = (ctx->passlen == 0 ? ' ' : - ctx->passphrase[ctx->passlen-1]); + size_t newlen = last_char_start(ctx); + deleted = ctx->passphrase->s[newlen]; + strbuf_shrink_to(ctx->passphrase, newlen); + prior = (ctx->passphrase->len == 0 ? ' ' : + ctx->passphrase->s[ctx->passphrase->len-1]); if (!g_ascii_isspace(deleted) && g_ascii_isspace(prior)) break; } visually_acknowledge_keypress(ctx); } else if (event->keyval == GDK_KEY_BackSpace) { /* Backspace. Delete one character. */ - if (ctx->passlen > 0) - ctx->passlen -= last_char_len(ctx); + if (ctx->passphrase->len > 0) + strbuf_shrink_to(ctx->passphrase, last_char_start(ctx)); visually_acknowledge_keypress(ctx); #if !GTK_CHECK_VERSION(2,0,0) } else if (event->string[0]) { @@ -427,9 +409,7 @@ static const char *gtk_askpass_setup(struct askpass_ctx *ctx, int i; GtkBox *action_area; - ctx->passlen = 0; - ctx->passsize = 2048; - ctx->passphrase = snewn(ctx->passsize, char); + ctx->passphrase = strbuf_new_nm(); /* * Create widgets. @@ -553,11 +533,6 @@ static void gtk_askpass_cleanup(struct askpass_ctx *ctx) #endif gtk_grab_remove(ctx->promptlabel); - if (ctx->passphrase) { - assert(ctx->passlen < ctx->passsize); - ctx->passphrase[ctx->passlen] = '\0'; - } - gtk_widget_destroy(ctx->dialog); } @@ -612,7 +587,7 @@ char *gtk_askpass_main(const char *display, const char *wintitle, if (ctx->passphrase) { *success = true; - return ctx->passphrase; + return strbuf_to_str(ctx->passphrase); } else { *success = false; return ctx->error_message; diff --git a/unix/console.c b/unix/console.c index ba35ccea..f3d98309 100644 --- a/unix/console.c +++ b/unix/console.c @@ -207,7 +207,7 @@ SeatPromptResult console_confirm_ssh_host_key( if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n' && line[0] != 'q' && line[0] != 'Q') { if (line[0] == 'y' || line[0] == 'Y') - store_host_key(host, port, keytype, keystr); + store_host_key(seat, host, port, keytype, keystr); postmsg(&cf); return SPR_OK; } else { @@ -584,6 +584,20 @@ bool is_interactive(void) return isatty(0); } +bool console_set_stdio_prompts(bool newvalue) +{ + /* Sending prompts to stdio in place of /dev/tty is not supported + * in the Unix tools. It's only supported on Windows because of + * years of history making it likely someone was depending on it. */ + return false; +} + +bool set_legacy_charset_handling(bool newvalue) +{ + /* This probably _will_ need to be supported, but isn't yet. */ + return false; +} + /* * X11-forwarding-related things suitable for console. */ diff --git a/unix/dialog.c b/unix/dialog.c index 63a94545..835ad978 100644 --- a/unix/dialog.c +++ b/unix/dialog.c @@ -300,7 +300,7 @@ bool dlg_checkbox_get(dlgcontrol *ctrl, dlgparam *dp) return gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(uc->toplevel)); } -void dlg_editbox_set(dlgcontrol *ctrl, dlgparam *dp, char const *text) +void dlg_editbox_set_utf8(dlgcontrol *ctrl, dlgparam *dp, char const *text) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); GtkWidget *entry; @@ -338,7 +338,14 @@ void dlg_editbox_set(dlgcontrol *ctrl, dlgparam *dp, char const *text) sfree(tmpstring); } -char *dlg_editbox_get(dlgcontrol *ctrl, dlgparam *dp) +void dlg_editbox_set(dlgcontrol *ctrl, dlgparam *dp, char const *text) +{ + /* GTK specifies that its edit boxes are always in UTF-8 anyway, + * so legacy behaviour is to use those strings unmodified */ + dlg_editbox_set_utf8(ctrl, dp, text); +} + +char *dlg_editbox_get_utf8(dlgcontrol *ctrl, dlgparam *dp) { struct uctrl *uc = dlg_find_byctrl(dp, ctrl); assert(uc->ctrl->type == CTRL_EDITBOX); @@ -357,6 +364,13 @@ char *dlg_editbox_get(dlgcontrol *ctrl, dlgparam *dp) unreachable("bad control type in editbox_get"); } +char *dlg_editbox_get(dlgcontrol *ctrl, dlgparam *dp) +{ + /* GTK specifies that its edit boxes are always in UTF-8 anyway, + * so legacy behaviour is to use those strings unmodified */ + return dlg_editbox_get_utf8(ctrl, dp); +} + void dlg_editbox_select_range(dlgcontrol *ctrl, dlgparam *dp, size_t start, size_t len) { @@ -594,9 +608,7 @@ void dlg_listbox_addwithid(dlgcontrol *ctrl, dlgparam *dp, cols = cols ? cols : 1; for (i = 0; i < cols; i++) { int collen = strcspn(text, "\t"); - char *tmpstr = snewn(collen+1, char); - memcpy(tmpstr, text, collen); - tmpstr[collen] = '\0'; + char *tmpstr = mkstr(make_ptrlen(text, collen)); gtk_list_store_set(uc->listmodel, &iter, i+1, tmpstr, -1); sfree(tmpstr); text += collen; @@ -3540,7 +3552,8 @@ static void confirm_ssh_host_key_result_callback(void *vctx, int result) * doesn't care whether we saved the host key or not). */ if (result == 2) { - store_host_key(ctx->host, ctx->port, ctx->keytype, ctx->keystr); + store_host_key(ctx->seat, ctx->host, ctx->port, + ctx->keytype, ctx->keystr); logical_result = SPR_OK; } else if (result == 1) { logical_result = SPR_OK; diff --git a/unix/fd-socket.c b/unix/fd-socket.c index 9758a17b..0f1615ef 100644 --- a/unix/fd-socket.c +++ b/unix/fd-socket.c @@ -306,7 +306,7 @@ static void fdsocket_select_result_input_error(int fd, int event) retd = read(fd, buf, sizeof(buf)); if (retd > 0) { - log_proxy_stderr(fds->plug, &fds->psb, buf, retd); + log_proxy_stderr(fds->plug, &fds->sock, &fds->psb, buf, retd); } else { del234(fdsocket_by_inerrfd, fds); uxsel_del(fds->inerrfd); @@ -323,14 +323,14 @@ static const SocketVtable FdSocket_sockvt = { .write_eof = fdsocket_write_eof, .set_frozen = fdsocket_set_frozen, .socket_error = fdsocket_socket_error, - .peer_info = NULL, + .endpoint_info = nullsock_endpoint_info, }; static void fdsocket_connect_success_callback(void *ctx) { FdSocket *fds = (FdSocket *)ctx; - plug_log(fds->plug, PLUGLOG_CONNECT_SUCCESS, fds->addr, fds->port, - NULL, 0); + plug_log(fds->plug, &fds->sock, PLUGLOG_CONNECT_SUCCESS, + fds->addr, fds->port, NULL, 0); } void setup_fd_socket(Socket *s, int infd, int outfd, int inerrfd) diff --git a/unix/gtkcompat.h b/unix/gtkcompat.h index 6ab5c809..2e8e8b88 100644 --- a/unix/gtkcompat.h +++ b/unix/gtkcompat.h @@ -218,3 +218,7 @@ gdk_cursor_new_for_display(gdk_display_get_default(), cur) #endif /* 3.0 */ + +#if !HAVE_G_APPLICATION_DEFAULT_FLAGS +#define G_APPLICATION_DEFAULT_FLAGS G_APPLICATION_FLAGS_NONE +#endif diff --git a/unix/main-gtk-application.c b/unix/main-gtk-application.c index 963c93fc..3aaf6af7 100644 --- a/unix/main-gtk-application.c +++ b/unix/main-gtk-application.c @@ -79,6 +79,7 @@ I suppose I'll have to look into OS X code signing. #include "putty.h" #include "gtkmisc.h" +#include "gtkcompat.h" char *x_get_default(const char *key) { return NULL; } @@ -311,7 +312,7 @@ int main(int argc, char **argv) gtkcomm_setup(); app = gtk_application_new("org.tartarus.projects.putty.macputty", - G_APPLICATION_FLAGS_NONE); + G_APPLICATION_DEFAULT_FLAGS); g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); g_signal_connect(app, "startup", G_CALLBACK(startup), NULL); g_action_map_add_action_entries(G_ACTION_MAP(app), diff --git a/unix/main-gtk-simple.c b/unix/main-gtk-simple.c index 4cbb5a31..a9a897ca 100644 --- a/unix/main-gtk-simple.c +++ b/unix/main-gtk-simple.c @@ -254,7 +254,7 @@ int read_dupsession_data(Conf *conf, char *arg) } static void help(FILE *fp) { - if(fprintf(fp, + if (fprintf(fp, "pterm option summary:\n" "\n" " --display DISPLAY Specify X display to use (note '--')\n" @@ -282,8 +282,8 @@ static void help(FILE *fp) { static void version(FILE *fp) { char *buildinfo_text = buildinfo("\n"); - if(fprintf(fp, "%s: %s\n%s\n", appname, ver, buildinfo_text) < 0 || - fflush(fp) < 0) { + if (fprintf(fp, "%s: %s\n%s\n", appname, ver, buildinfo_text) < 0 || + fflush(fp) < 0) { perror("output error"); exit(1); } @@ -312,7 +312,6 @@ void window_setup_error(const char *errmsg) bool do_cmdline(int argc, char **argv, bool do_everything, Conf *conf) { bool err = false; - char *val; /* * Macros to make argument handling easier. @@ -323,20 +322,26 @@ bool do_cmdline(int argc, char **argv, bool do_everything, Conf *conf) * {...} else ((void)0). */ #define EXPECTS_ARG if (1) { \ - if (--argc <= 0) { \ + if (!nextarg) { \ err = true; \ fprintf(stderr, "%s: %s expects an argument\n", appname, p); \ continue; \ - } else \ - val = *++argv; \ + } else { \ + arglistpos++; \ + } \ } else ((void)0) #define SECOND_PASS_ONLY if (1) { \ if (!do_everything) \ continue; \ } else ((void)0) - while (--argc > 0) { - const char *p = *++argv; + CmdlineArgList *arglist = cmdline_arg_list_from_argv(argc, argv); + size_t arglistpos = 0; + while (arglist->args[arglistpos]) { + CmdlineArg *arg = arglist->args[arglistpos++]; + CmdlineArg *nextarg = arglist->args[arglistpos]; + const char *p = cmdline_arg_to_str(arg); + const char *val = cmdline_arg_to_str(nextarg); int ret; /* @@ -350,13 +355,13 @@ bool do_cmdline(int argc, char **argv, bool do_everything, Conf *conf) !strcmp(p, "-T")) p = "-title"; - ret = cmdline_process_param(p, (argc > 1 ? argv[1] : NULL), - do_everything ? 1 : -1, conf); + ret = cmdline_process_param( + arg, nextarg, do_everything ? 1 : -1, conf); if (ret == -2) { cmdline_error("option \"%s\" requires an argument", p); } else if (ret == 2) { - --argc, ++argv; /* skip next argument */ + arglistpos++; continue; } else if (ret == 1) { continue; @@ -458,13 +463,8 @@ bool do_cmdline(int argc, char **argv, bool do_everything, Conf *conf) if (!do_everything) break; - if (--argc > 0) { - int i; - pty_argv = snewn(argc+1, char *); - ++argv; - for (i = 0; i < argc; i++) - pty_argv[i] = argv[i]; - pty_argv[argc] = NULL; + if (nextarg) { + pty_argv = cmdline_arg_remainder(nextarg); break; /* finished command-line processing */ } else err = true, fprintf(stderr, "%s: -e expects an argument\n", @@ -476,10 +476,9 @@ bool do_cmdline(int argc, char **argv, bool do_everything, Conf *conf) conf_set_str(conf, CONF_wintitle, val); } else if (!strcmp(p, "-log")) { - Filename *fn; EXPECTS_ARG; SECOND_PASS_ONLY; - fn = filename_from_str(val); + Filename *fn = cmdline_arg_to_filename(nextarg); conf_set_filename(conf, CONF_logfilename, fn); conf_set_int(conf, CONF_logtype, LGTYP_DEBUG); filename_free(fn); @@ -520,17 +519,17 @@ bool do_cmdline(int argc, char **argv, bool do_everything, Conf *conf) EXPECTS_ARG; provide_xrm_string(val, appname); - } else if(!strcmp(p, "-help") || !strcmp(p, "--help")) { + } else if (!strcmp(p, "-help") || !strcmp(p, "--help")) { help(stdout); exit(0); - } else if(!strcmp(p, "-version") || !strcmp(p, "--version")) { + } else if (!strcmp(p, "-version") || !strcmp(p, "--version")) { version(stdout); exit(0); } else if (!strcmp(p, "-pgpfp")) { pgp_fingerprints(); - exit(1); + exit(0); } else if (has_ca_config_box && (!strcmp(p, "-host-ca") || !strcmp(p, "--host-ca") || diff --git a/unix/network.c b/unix/network.c index 31c754bd..2ebd76af 100644 --- a/unix/network.c +++ b/unix/network.c @@ -271,18 +271,18 @@ SockAddr *sk_namelookup(const char *host, char **canonicalname, SockAddr *sk_nonamelookup(const char *host) { - SockAddr *ret = snew(SockAddr); - ret->error = NULL; - ret->superfamily = UNRESOLVED; - strncpy(ret->hostname, host, lenof(ret->hostname)); - ret->hostname[lenof(ret->hostname)-1] = '\0'; + SockAddr *addr = snew(SockAddr); + addr->error = NULL; + addr->superfamily = UNRESOLVED; + strncpy(addr->hostname, host, lenof(addr->hostname)); + addr->hostname[lenof(addr->hostname)-1] = '\0'; #ifndef NO_IPV6 - ret->ais = NULL; + addr->ais = NULL; #else ret->addresses = NULL; #endif - ret->refcount = 1; - return ret; + addr->refcount = 1; + return addr; } static bool sk_nextaddr(SockAddr *addr, SockAddrStep *step) @@ -488,7 +488,7 @@ static size_t sk_net_write(Socket *s, const void *data, size_t len); static size_t sk_net_write_oob(Socket *s, const void *data, size_t len); static void sk_net_write_eof(Socket *s); static void sk_net_set_frozen(Socket *s, bool is_frozen); -static SocketPeerInfo *sk_net_peer_info(Socket *s); +static SocketEndpointInfo *sk_net_endpoint_info(Socket *s, bool peer); static const char *sk_net_socket_error(Socket *s); static const SocketVtable NetSocket_sockvt = { @@ -499,48 +499,48 @@ static const SocketVtable NetSocket_sockvt = { .write_eof = sk_net_write_eof, .set_frozen = sk_net_set_frozen, .socket_error = sk_net_socket_error, - .peer_info = sk_net_peer_info, + .endpoint_info = sk_net_endpoint_info, }; static Socket *sk_net_accept(accept_ctx_t ctx, Plug *plug) { int sockfd = ctx.i; - NetSocket *ret; + NetSocket *s; /* * Create NetSocket structure. */ - ret = snew(NetSocket); - ret->sock.vt = &NetSocket_sockvt; - ret->error = NULL; - ret->plug = plug; - bufchain_init(&ret->output_data); - ret->writable = true; /* to start with */ - ret->sending_oob = 0; - ret->frozen = true; - ret->localhost_only = false; /* unused, but best init anyway */ - ret->pending_error = 0; - ret->oobpending = false; - ret->outgoingeof = EOF_NO; - ret->incomingeof = false; - ret->listener = false; - ret->parent = ret->child = NULL; - ret->addr = NULL; - ret->connected = true; - - ret->s = sockfd; - - if (ret->s < 0) { - ret->error = strerror(errno); - return &ret->sock; + s = snew(NetSocket); + s->sock.vt = &NetSocket_sockvt; + s->error = NULL; + s->plug = plug; + bufchain_init(&s->output_data); + s->writable = true; /* to start with */ + s->sending_oob = 0; + s->frozen = true; + s->localhost_only = false; /* unused, but best init anyway */ + s->pending_error = 0; + s->oobpending = false; + s->outgoingeof = EOF_NO; + s->incomingeof = false; + s->listener = false; + s->parent = s->child = NULL; + s->addr = NULL; + s->connected = true; + + s->s = sockfd; + + if (s->s < 0) { + s->error = strerror(errno); + return &s->sock; } - ret->oobinline = false; + s->oobinline = false; - uxsel_tell(ret); - add234(sktree, ret); + uxsel_tell(s); + add234(sktree, s); - return &ret->sock; + return &s->sock; } static int try_connect(NetSocket *sock) @@ -566,7 +566,7 @@ static int try_connect(NetSocket *sock) { SockAddr thisaddr = sk_extractaddr_tmp( sock->addr, &sock->step); - plug_log(sock->plug, PLUGLOG_CONNECT_TRYING, + plug_log(sock->plug, &sock->sock, PLUGLOG_CONNECT_TRYING, &thisaddr, sock->port, NULL, 0); } @@ -725,7 +725,7 @@ static int try_connect(NetSocket *sock) sock->writable = true; SockAddr thisaddr = sk_extractaddr_tmp(sock->addr, &sock->step); - plug_log(sock->plug, PLUGLOG_CONNECT_SUCCESS, + plug_log(sock->plug, &sock->sock, PLUGLOG_CONNECT_SUCCESS, &thisaddr, sock->port, NULL, 0); } @@ -741,7 +741,7 @@ static int try_connect(NetSocket *sock) if (err) { SockAddr thisaddr = sk_extractaddr_tmp( sock->addr, &sock->step); - plug_log(sock->plug, PLUGLOG_CONNECT_FAILED, + plug_log(sock->plug, &sock->sock, PLUGLOG_CONNECT_FAILED, &thisaddr, sock->port, strerror(err), err); } return err; @@ -750,51 +750,51 @@ static int try_connect(NetSocket *sock) Socket *sk_new(SockAddr *addr, int port, bool privport, bool oobinline, bool nodelay, bool keepalive, Plug *plug) { - NetSocket *ret; + NetSocket *s; int err; /* * Create NetSocket structure. */ - ret = snew(NetSocket); - ret->sock.vt = &NetSocket_sockvt; - ret->error = NULL; - ret->plug = plug; - bufchain_init(&ret->output_data); - ret->connected = false; /* to start with */ - ret->writable = false; /* to start with */ - ret->sending_oob = 0; - ret->frozen = false; - ret->localhost_only = false; /* unused, but best init anyway */ - ret->pending_error = 0; - ret->parent = ret->child = NULL; - ret->oobpending = false; - ret->outgoingeof = EOF_NO; - ret->incomingeof = false; - ret->listener = false; - ret->addr = addr; - START_STEP(ret->addr, ret->step); - ret->s = -1; - ret->oobinline = oobinline; - ret->nodelay = nodelay; - ret->keepalive = keepalive; - ret->privport = privport; - ret->port = port; + s = snew(NetSocket); + s->sock.vt = &NetSocket_sockvt; + s->error = NULL; + s->plug = plug; + bufchain_init(&s->output_data); + s->connected = false; /* to start with */ + s->writable = false; /* to start with */ + s->sending_oob = 0; + s->frozen = false; + s->localhost_only = false; /* unused, but best init anyway */ + s->pending_error = 0; + s->parent = s->child = NULL; + s->oobpending = false; + s->outgoingeof = EOF_NO; + s->incomingeof = false; + s->listener = false; + s->addr = addr; + START_STEP(s->addr, s->step); + s->s = -1; + s->oobinline = oobinline; + s->nodelay = nodelay; + s->keepalive = keepalive; + s->privport = privport; + s->port = port; do { - err = try_connect(ret); - } while (err && sk_nextaddr(ret->addr, &ret->step)); + err = try_connect(s); + } while (err && sk_nextaddr(s->addr, &s->step)); if (err) - ret->error = strerror(err); + s->error = strerror(err); - return &ret->sock; + return &s->sock; } Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug, bool local_host_only, int orig_address_family) { - int s; + int fd; #ifndef NO_IPV6 struct addrinfo hints, *ai = NULL; char portstr[6]; @@ -802,7 +802,7 @@ Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug, union sockaddr_union u; union sockaddr_union *addr; int addrlen; - NetSocket *ret; + NetSocket *s; int retcode; int address_family; int on = 1; @@ -810,23 +810,23 @@ Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug, /* * Create NetSocket structure. */ - ret = snew(NetSocket); - ret->sock.vt = &NetSocket_sockvt; - ret->error = NULL; - ret->plug = plug; - bufchain_init(&ret->output_data); - ret->writable = false; /* to start with */ - ret->sending_oob = 0; - ret->frozen = false; - ret->localhost_only = local_host_only; - ret->pending_error = 0; - ret->parent = ret->child = NULL; - ret->oobpending = false; - ret->outgoingeof = EOF_NO; - ret->incomingeof = false; - ret->listener = true; - ret->addr = NULL; - ret->s = -1; + s = snew(NetSocket); + s->sock.vt = &NetSocket_sockvt; + s->error = NULL; + s->plug = plug; + bufchain_init(&s->output_data); + s->writable = false; /* to start with */ + s->sending_oob = 0; + s->frozen = false; + s->localhost_only = local_host_only; + s->pending_error = 0; + s->parent = s->child = NULL; + s->oobpending = false; + s->outgoingeof = EOF_NO; + s->incomingeof = false; + s->listener = true; + s->addr = NULL; + s->s = -1; /* * Translate address_family from platform-independent constants @@ -850,30 +850,30 @@ Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug, /* * Open socket. */ - s = socket(address_family, SOCK_STREAM, 0); + fd = socket(address_family, SOCK_STREAM, 0); #ifndef NO_IPV6 /* If the host doesn't support IPv6 try fallback to IPv4. */ - if (s < 0 && address_family == AF_INET6) { + if (fd < 0 && address_family == AF_INET6) { address_family = AF_INET; - s = socket(address_family, SOCK_STREAM, 0); + fd = socket(address_family, SOCK_STREAM, 0); } #endif - if (s < 0) { - ret->error = strerror(errno); - return &ret->sock; + if (fd < 0) { + s->error = strerror(errno); + return &s->sock; } - cloexec(s); + cloexec(fd); - ret->oobinline = false; + s->oobinline = false; - if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const char *)&on, sizeof(on)) < 0) { - ret->error = strerror(errno); - close(s); - return &ret->sock; + s->error = strerror(errno); + close(fd); + return &s->sock; } retcode = -1; @@ -941,7 +941,7 @@ Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug, } } - retcode = bind(s, &addr->sa, addrlen); + retcode = bind(fd, &addr->sa, addrlen); #ifndef NO_IPV6 if (ai) @@ -949,15 +949,15 @@ Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug, #endif if (retcode < 0) { - close(s); - ret->error = strerror(errno); - return &ret->sock; + close(fd); + s->error = strerror(errno); + return &s->sock; } - if (listen(s, SOMAXCONN) < 0) { - close(s); - ret->error = strerror(errno); - return &ret->sock; + if (listen(fd, SOMAXCONN) < 0) { + close(fd); + s->error = strerror(errno); + return &s->sock; } #ifndef NO_IPV6 @@ -975,25 +975,25 @@ Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug, if (other) { if (!other->error) { - other->parent = ret; - ret->child = other; + other->parent = s; + s->child = other; } else { /* If we couldn't create a listening socket on IPv4 as well * as IPv6, we must return an error overall. */ - close(s); - sfree(ret); + close(fd); + sfree(s); return &other->sock; } } } #endif - ret->s = s; + s->s = fd; - uxsel_tell(ret); - add234(sktree, ret); + uxsel_tell(s); + add234(sktree, s); - return &ret->sock; + return &s->sock; } static void sk_net_close(Socket *sock) @@ -1425,7 +1425,7 @@ static void net_select_result(int fd, int event) assert(s->addr); thisaddr = sk_extractaddr_tmp(s->addr, &s->step); - plug_log(s->plug, PLUGLOG_CONNECT_FAILED, + plug_log(s->plug, &s->sock, PLUGLOG_CONNECT_FAILED, &thisaddr, s->port, errmsg, err); while (err && s->addr && sk_nextaddr(s->addr, &s->step)) { @@ -1442,7 +1442,7 @@ static void net_select_result(int fd, int event) * The connection attempt succeeded. */ SockAddr thisaddr = sk_extractaddr_tmp(s->addr, &s->step); - plug_log(s->plug, PLUGLOG_CONNECT_SUCCESS, + plug_log(s->plug, &s->sock, PLUGLOG_CONNECT_SUCCESS, &thisaddr, s->port, NULL, 0); } } @@ -1494,7 +1494,7 @@ static void sk_net_set_frozen(Socket *sock, bool is_frozen) uxsel_tell(s); } -static SocketPeerInfo *sk_net_peer_info(Socket *sock) +static SocketEndpointInfo *sk_net_endpoint_info(Socket *sock, bool peer) { NetSocket *s = container_of(sock, NetSocket, sock); union sockaddr_union addr; @@ -1502,12 +1502,16 @@ static SocketPeerInfo *sk_net_peer_info(Socket *sock) #ifndef NO_IPV6 char buf[INET6_ADDRSTRLEN]; #endif - SocketPeerInfo *pi; + SocketEndpointInfo *pi; - if (getpeername(s->s, &addr.sa, &addrlen) < 0) - return NULL; + { + int retd = (peer ? getpeername(s->s, &addr.sa, &addrlen) : + getsockname(s->s, &addr.sa, &addrlen)); + if (retd < 0) + return NULL; + } - pi = snew(SocketPeerInfo); + pi = snew(SocketEndpointInfo); pi->addressfamily = ADDRTYPE_UNSPEC; pi->addr_text = NULL; pi->port = -1; @@ -1612,107 +1616,107 @@ char *get_hostname(void) SockAddr *platform_get_x11_unix_address(const char *sockpath, int displaynum) { - SockAddr *ret = snew(SockAddr); + SockAddr *addr = snew(SockAddr); int n; - memset(ret, 0, sizeof *ret); - ret->superfamily = UNIX; + memset(addr, 0, sizeof *addr); + addr->superfamily = UNIX; /* * In special circumstances (notably Mac OS X Leopard), we'll * have been passed an explicit Unix socket path. */ if (sockpath) { - n = snprintf(ret->hostname, sizeof ret->hostname, + n = snprintf(addr->hostname, sizeof addr->hostname, "%s", sockpath); } else { - n = snprintf(ret->hostname, sizeof ret->hostname, + n = snprintf(addr->hostname, sizeof addr->hostname, "%s%d", X11_UNIX_PATH, displaynum); } if (n < 0) - ret->error = "snprintf failed"; - else if (n >= sizeof ret->hostname) - ret->error = "X11 UNIX name too long"; + addr->error = "snprintf failed"; + else if (n >= sizeof addr->hostname) + addr->error = "X11 UNIX name too long"; #ifndef NO_IPV6 - ret->ais = NULL; + addr->ais = NULL; #else ret->addresses = NULL; ret->naddresses = 0; #endif - ret->refcount = 1; - return ret; + addr->refcount = 1; + return addr; } SockAddr *unix_sock_addr(const char *path) { - SockAddr *ret = snew(SockAddr); + SockAddr *addr = snew(SockAddr); int n; - memset(ret, 0, sizeof *ret); - ret->superfamily = UNIX; - n = snprintf(ret->hostname, sizeof ret->hostname, "%s", path); + memset(addr, 0, sizeof *addr); + addr->superfamily = UNIX; + n = snprintf(addr->hostname, sizeof addr->hostname, "%s", path); if (n < 0) - ret->error = "snprintf failed"; - else if (n >= sizeof ret->hostname || + addr->error = "snprintf failed"; + else if (n >= sizeof addr->hostname || n >= sizeof(((struct sockaddr_un *)0)->sun_path)) - ret->error = "socket pathname too long"; + addr->error = "socket pathname too long"; #ifndef NO_IPV6 - ret->ais = NULL; + addr->ais = NULL; #else ret->addresses = NULL; ret->naddresses = 0; #endif - ret->refcount = 1; - return ret; + addr->refcount = 1; + return addr; } Socket *new_unix_listener(SockAddr *listenaddr, Plug *plug) { - int s; + int fd; union sockaddr_union u; union sockaddr_union *addr; int addrlen; - NetSocket *ret; + NetSocket *s; int retcode; /* * Create NetSocket structure. */ - ret = snew(NetSocket); - ret->sock.vt = &NetSocket_sockvt; - ret->error = NULL; - ret->plug = plug; - bufchain_init(&ret->output_data); - ret->writable = false; /* to start with */ - ret->sending_oob = 0; - ret->frozen = false; - ret->localhost_only = true; - ret->pending_error = 0; - ret->parent = ret->child = NULL; - ret->oobpending = false; - ret->outgoingeof = EOF_NO; - ret->incomingeof = false; - ret->listener = true; - ret->addr = listenaddr; - ret->s = -1; + s = snew(NetSocket); + s->sock.vt = &NetSocket_sockvt; + s->error = NULL; + s->plug = plug; + bufchain_init(&s->output_data); + s->writable = false; /* to start with */ + s->sending_oob = 0; + s->frozen = false; + s->localhost_only = true; + s->pending_error = 0; + s->parent = s->child = NULL; + s->oobpending = false; + s->outgoingeof = EOF_NO; + s->incomingeof = false; + s->listener = true; + s->addr = listenaddr; + s->s = -1; assert(listenaddr->superfamily == UNIX); /* * Open socket. */ - s = socket(AF_UNIX, SOCK_STREAM, 0); - if (s < 0) { - ret->error = strerror(errno); - return &ret->sock; + fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) { + s->error = strerror(errno); + return &s->sock; } - cloexec(s); + cloexec(fd); - ret->oobinline = false; + s->oobinline = false; memset(&u, '\0', sizeof(u)); u.su.sun_family = AF_UNIX; @@ -1728,28 +1732,28 @@ Socket *new_unix_listener(SockAddr *listenaddr, Plug *plug) addrlen = sizeof(u.su); if (unlink(u.su.sun_path) < 0 && errno != ENOENT) { - close(s); - ret->error = strerror(errno); - return &ret->sock; + close(fd); + s->error = strerror(errno); + return &s->sock; } - retcode = bind(s, &addr->sa, addrlen); + retcode = bind(fd, &addr->sa, addrlen); if (retcode < 0) { - close(s); - ret->error = strerror(errno); - return &ret->sock; + close(fd); + s->error = strerror(errno); + return &s->sock; } - if (listen(s, SOMAXCONN) < 0) { - close(s); - ret->error = strerror(errno); - return &ret->sock; + if (listen(fd, SOMAXCONN) < 0) { + close(fd); + s->error = strerror(errno); + return &s->sock; } - ret->s = s; + s->s = fd; - uxsel_tell(ret); - add234(sktree, ret); + uxsel_tell(s); + add234(sktree, s); - return &ret->sock; + return &s->sock; } diff --git a/unix/pageant.c b/unix/pageant.c index 4558cd37..bd6d6d7f 100644 --- a/unix/pageant.c +++ b/unix/pageant.c @@ -172,7 +172,7 @@ void random_destroy_seed(void) {} char *platform_default_s(const char *name) { return NULL; } bool platform_default_b(const char *name, bool def) { return def; } int platform_default_i(const char *name, int def) { return def; } -FontSpec *platform_default_fontspec(const char *name) { return fontspec_new(""); } +FontSpec *platform_default_fontspec(const char *name) { return fontspec_new_default(); } Filename *platform_default_filename(const char *name) { return filename_from_str(""); } char *x_get_default(const char *key) { return NULL; } @@ -198,6 +198,7 @@ static void usage(void) printf(" -T run with the lifetime of the controlling tty\n"); printf(" --permanent run permanently\n"); printf(" --debug run in debugging mode, without forking\n"); + printf(" --foreground run permanently, without forking\n"); printf(" --exec run with the lifetime of that command\n"); printf("Client options, for talking to an existing agent:\n"); printf(" -a add key(s) to the existing agent\n"); @@ -217,7 +218,6 @@ static void usage(void) printf(" --tty-prompt force tty-based passphrase prompt\n"); printf(" --gui-prompt force GUI-based passphrase prompt\n"); printf(" --askpass behave like a standalone askpass program\n"); - exit(1); } static void version(void) @@ -237,18 +237,12 @@ void keylist_update(void) static bool time_to_die = false; -/* - * These functions are part of the plug for our connection to the X - * display, so they do get called. They needn't actually do anything, - * except that x11_closing has to signal back to the main loop that - * it's time to terminate. - */ -static void x11_log(Plug *p, PlugLogType type, SockAddr *addr, int port, - const char *error_msg, int error_code) {} -static void x11_receive(Plug *plug, int urgent, const char *data, size_t len) {} -static void x11_sent(Plug *plug, size_t bufsize) {} static void x11_closing(Plug *plug, PlugCloseType type, const char *error_msg) { + /* + * When the X connection closes, signal back to the main loop that + * it's time to terminate. + */ time_to_die = true; } struct X11Connection { @@ -431,7 +425,7 @@ bool have_controlling_tty(void) static char **exec_args = NULL; static enum { - LIFE_UNSPEC, LIFE_X11, LIFE_TTY, LIFE_DEBUG, LIFE_PERM, LIFE_EXEC + LIFE_UNSPEC, LIFE_X11, LIFE_TTY, LIFE_DEBUG, LIFE_PERM, LIFE_EXEC, LIFE_FOREGROUND } life = LIFE_UNSPEC; static const char *display = NULL; static enum { @@ -669,8 +663,7 @@ void key_find_callback(void *vctx, char **fingerprints, if ((ctx->match_comment && !strcmp(ctx->string, comment)) || (ctx->match_fp && match_fingerprint_string(ctx->string, fingerprints, - ctx))) - { + ctx))) { if (!ctx->found) ctx->found = pageant_pubkey_copy(key); ctx->nfound++; @@ -995,10 +988,10 @@ void run_client(void) } static const PlugVtable X11Connection_plugvt = { - .log = x11_log, + .log = nullplug_log, .closing = x11_closing, - .receive = x11_receive, - .sent = x11_sent, + .receive = nullplug_receive, + .sent = nullplug_sent, }; @@ -1229,7 +1222,17 @@ void run_agent(FILE *logfp, const char *symlink_path) pageant_fork_and_print_env(true); } else if (life == LIFE_PERM) { pageant_fork_and_print_env(false); + } else if (life == LIFE_FOREGROUND) { + pageant_print_env(getpid()); + /* Close stdout, so that a parent process at the other end of a pipe + * can do the simple thing of reading up to EOF */ + fclose(stdout); } else if (life == LIFE_DEBUG) { + /* Force stdout to be line-buffered in preference to unbuffered, so + * that if diagnostic output is being piped somewhere, it will arrive + * promptly at the other end of the pipe */ + setvbuf(stdout, NULL, _IOLBF, 0); + pageant_print_env(getpid()); upc->logfp = stdout; @@ -1362,12 +1365,14 @@ int main(int argc, char **argv) else if (curr_keyact == KEYACT_CLIENT_ADD) curr_keyact = KEYACT_CLIENT_ADD_ENCRYPTED; else { - fprintf(stderr, "pageant: unexpected -E while not adding " - "keys\n"); + fprintf(stderr, "pageant: unexpected %s while not adding " + "keys\n", p); exit(1); } } else if (!strcmp(p, "--debug")) { life = LIFE_DEBUG; + } else if (!strcmp(p, "--foreground")) { + life = LIFE_FOREGROUND; } else if (!strcmp(p, "--test-sign")) { curr_keyact = KEYACT_CLIENT_SIGN; sign_flags = 0; diff --git a/unix/platform.h b/unix/platform.h index a6bc7ad1..44c7986c 100644 --- a/unix/platform.h +++ b/unix/platform.h @@ -60,10 +60,12 @@ struct Filename { }; FILE *f_open(const struct Filename *, char const *, bool); +#ifndef SUPERSEDE_FONTSPEC_FOR_TESTING struct FontSpec { char *name; /* may be "" to indicate no selected font at all */ }; struct FontSpec *fontspec_new(const char *name); +#endif extern const struct BackendVtable pty_backend; @@ -81,6 +83,8 @@ extern const struct BackendVtable pty_backend; typedef void *HelpCtx; #define NULL_HELPCTX ((HelpCtx)NULL) #define HELPCTX(x) NULL + +typedef const char *FILESELECT_FILTER_TYPE; #define FILTER_KEY_FILES NULL /* FIXME */ #define FILTER_DYNLIB_FILES NULL /* FIXME */ @@ -332,6 +336,8 @@ void gtk_setup_config_box( */ #define DEFAULT_CODEPAGE 0xFFFF #define CP_UTF8 CS_UTF8 /* from libcharset */ +#define CP_437 CS_CP437 /* used for test suites */ +#define CP_ISO8859_1 CS_ISO8859_1 /* used for test suites */ #define strnicmp strncasecmp #define stricmp strcasecmp @@ -471,4 +477,8 @@ void plug_closing_errno(Plug *plug, int error); SeatPromptResult make_spr_sw_abort_errno(const char *prefix, int errno_value); +/* Unix-specific extra functions in cmdline_arg.c */ +CmdlineArgList *cmdline_arg_list_from_argv(int argc, char **argv); +char **cmdline_arg_remainder(CmdlineArg *argp); + #endif /* PUTTY_UNIX_PLATFORM_H */ diff --git a/unix/plink.c b/unix/plink.c index f0c73754..b4a9749f 100644 --- a/unix/plink.c +++ b/unix/plink.c @@ -64,7 +64,7 @@ int platform_default_i(const char *name, int def) FontSpec *platform_default_fontspec(const char *name) { - return fontspec_new(""); + return fontspec_new_default(); } Filename *platform_default_filename(const char *name) @@ -149,7 +149,7 @@ static char *plink_get_ttymode(Seat *seat, const char *mode) do { \ if (strcmp(mode, ourname) == 0) \ return get_ttychar(&orig_termios, uxname); \ - } while(0) + } while (0) #define GET_BOOL(ourname, uxname, uxmemb, transform) \ do { \ if (strcmp(mode, ourname) == 0) { \ @@ -394,8 +394,8 @@ static SeatPromptResult plink_get_userpass_input(Seat *seat, prompts_t *p) static bool plink_seat_interactive(Seat *seat) { - return (!*conf_get_str(conf, CONF_remote_cmd) && - !*conf_get_str(conf, CONF_remote_cmd2) && + return (!*conf_get_str_ambi(conf, CONF_remote_cmd, NULL) && + !*conf_get_str_ambi(conf, CONF_remote_cmd2, NULL) && !*conf_get_str(conf, CONF_ssh_nc_host)); } @@ -409,6 +409,7 @@ static const SeatVtable plink_seat_vt = { .notify_remote_exit = nullseat_notify_remote_exit, .notify_remote_disconnect = nullseat_notify_remote_disconnect, .connection_fatal = console_connection_fatal, + .nonfatal = console_nonfatal, .update_specials_menu = nullseat_update_specials_menu, .get_ttymode = plink_get_ttymode, .set_busy_status = nullseat_set_busy_status, @@ -572,7 +573,6 @@ static void usage(void) printf(" control what happens when a log file already exists\n"); printf(" -shareexists\n"); printf(" test whether a connection-sharing upstream exists\n"); - exit(1); } static void version(void) @@ -722,20 +722,21 @@ int main(int argc, char **argv) } } } - while (--argc) { - char *p = *++argv; - int ret = cmdline_process_param(p, (argc > 1 ? argv[1] : NULL), - 1, conf); + CmdlineArgList *arglist = cmdline_arg_list_from_argv(argc, argv); + size_t arglistpos = 0; + while (arglist->args[arglistpos]) { + CmdlineArg *arg = arglist->args[arglistpos++]; + CmdlineArg *nextarg = arglist->args[arglistpos]; + const char *p = cmdline_arg_to_str(arg); + int ret = cmdline_process_param(arg, nextarg, 1, conf); if (ret == -2) { fprintf(stderr, "plink: option \"%s\" requires an argument\n", p); errors = true; } else if (ret == 2) { - --argc, ++argv; + arglistpos++; } else if (ret == 1) { continue; - } else if (!strcmp(p, "-batch")) { - console_batch_mode = true; } else if (!strcmp(p, "-s")) { /* Save status to write to conf later. */ use_subsystem = true; @@ -746,7 +747,7 @@ int main(int argc, char **argv) exit(0); } else if (!strcmp(p, "-pgpfp")) { pgp_fingerprints(); - exit(1); + exit(0); } else if (!strcmp(p, "-o")) { if (argc <= 1) { fprintf(stderr, @@ -782,12 +783,11 @@ int main(int argc, char **argv) } else if (*p != '-') { strbuf *cmdbuf = strbuf_new(); - while (argc > 0) { + while (arg) { if (cmdbuf->len > 0) put_byte(cmdbuf, ' '); /* add space separator */ - put_dataz(cmdbuf, p); - if (--argc > 0) - p = *++argv; + put_dataz(cmdbuf, cmdline_arg_to_str(arg)); + arg = arglist->args[arglistpos++]; } conf_set_str(conf, CONF_remote_cmd, cmdbuf->s); @@ -806,7 +806,10 @@ int main(int argc, char **argv) return 1; if (!cmdline_host_ok(conf)) { - usage(); + fprintf(stderr, "plink: no valid host name provided\n" + "try \"plink --help\" for help\n"); + cmdline_arg_list_free(arglist); + return 1; } prepare_session(conf); @@ -816,11 +819,13 @@ int main(int argc, char **argv) */ cmdline_run_saved(conf); + cmdline_arg_list_free(arglist); + /* * If we have no better ideas for the remote username, use the local * one, as 'ssh' does. */ - if (conf_get_str(conf, CONF_username)[0] == '\0') { + if (conf_get_str_ambi(conf, CONF_username, NULL)[0] == '\0') { char *user = get_username(); if (user) { conf_set_str(conf, CONF_username, user); diff --git a/unix/printing.c b/unix/printing.c index 416db396..7f26ce00 100644 --- a/unix/printing.c +++ b/unix/printing.c @@ -12,17 +12,17 @@ struct printer_job_tag { printer_job *printer_start_job(char *printer) { - printer_job *ret = snew(printer_job); + printer_job *pj = snew(printer_job); /* * On Unix, we treat the printer string as the name of a * command to pipe to - typically lpr, of course. */ - ret->fp = popen(printer, "w"); - if (!ret->fp) { - sfree(ret); - ret = NULL; + pj->fp = popen(printer, "w"); + if (!pj->fp) { + sfree(pj); + pj = NULL; } - return ret; + return pj; } void printer_job_data(printer_job *pj, const void *data, size_t len) diff --git a/unix/psocks.c b/unix/psocks.c index 6b30dc16..8f1ddf46 100644 --- a/unix/psocks.c +++ b/unix/psocks.c @@ -82,7 +82,14 @@ static pid_t subcommand_pid = -1; static bool still_running = true; -static void start_subcommand(strbuf *args) +static char **exec_args = NULL; + +static void found_subcommand(CmdlineArg *arg) +{ + exec_args = cmdline_arg_remainder(arg); +} + +static void start_subcommand(void) { pid_t pid; @@ -95,24 +102,6 @@ static void start_subcommand(strbuf *args) } putty_signal(SIGCHLD, sigchld); - /* - * Make an array of argument pointers that execvp will like. - */ - size_t nargs = 0; - for (size_t i = 0; i < args->len; i++) - if (args->s[i] == '\0') - nargs++; - - char **exec_args = snewn(nargs + 1, char *); - char *p = args->s; - for (size_t a = 0; a < nargs; a++) { - exec_args[a] = p; - size_t len = strlen(p); - assert(len < args->len - (p - args->s)); - p += 1 + len; - } - exec_args[nargs] = NULL; - pid = fork(); if (pid < 0) { perror("fork"); @@ -123,12 +112,12 @@ static void start_subcommand(strbuf *args) _exit(127); } else { subcommand_pid = pid; - sfree(exec_args); } } static const PsocksPlatform platform = { open_pipes, + found_subcommand, start_subcommand, }; @@ -163,11 +152,13 @@ static bool psocks_continue(void *ctx, bool found_any_fd, int main(int argc, char **argv) { psocks_state *ps = psocks_new(&platform); - psocks_cmdline(ps, argc, argv); + CmdlineArgList *arglist = cmdline_arg_list_from_argv(argc, argv); + psocks_cmdline(ps, arglist); sk_init(); uxsel_init(); psocks_start(ps); + cmdline_arg_list_free(arglist); cli_main_loop(psocks_pw_setup, psocks_pw_check, psocks_continue, NULL); } diff --git a/unix/psusan.c b/unix/psusan.c index 1d5816d5..4e8693af 100644 --- a/unix/psusan.c +++ b/unix/psusan.c @@ -78,7 +78,7 @@ int platform_default_i(const char *name, int def) FontSpec *platform_default_fontspec(const char *name) { - return fontspec_new(""); + return fontspec_new_default(); } Filename *platform_default_filename(const char *name) @@ -265,8 +265,8 @@ static Plug *server_conn_plug( &inst->logpolicy, &unix_live_sftpserver_vt); } -static void server_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, - const char *error_msg, int error_code) +static void server_log(Plug *plug, Socket *s, PlugLogType type, SockAddr *addr, + int port, const char *error_msg, int error_code) { log_to_stderr(-1, error_msg); } @@ -299,12 +299,12 @@ static int server_accepting(Plug *p, accept_fn_t constructor, accept_ctx_t ctx) if ((err = sk_socket_error(s)) != NULL) return 1; - SocketPeerInfo *pi = sk_peer_info(s); + SocketEndpointInfo *pi = sk_peer_info(s); char *msg = dupprintf("new connection from %s", pi->log_text); log_to_stderr(inst->id, msg); sfree(msg); - sk_free_peer_info(pi); + sk_free_endpoint_info(pi); sk_set_frozen(s, false); ssh_server_start(plug, s); diff --git a/unix/pty.c b/unix/pty.c index 05c58929..c03328f7 100644 --- a/unix/pty.c +++ b/unix/pty.c @@ -1228,9 +1228,8 @@ Backend *pty_backend_create( char *shellname; if (conf_get_bool(conf, CONF_login_shell)) { const char *p = strrchr(shell, '/'); - shellname = snewn(2+strlen(shell), char); p = p ? p+1 : shell; - sprintf(shellname, "-%s", p); + shellname = dupprintf("-%s", p); } else shellname = (char *)shell; execl(shell, shellname, (void *)NULL); diff --git a/unix/sftp.c b/unix/sftp.c index 023f41ee..9fb0bf6e 100644 --- a/unix/sftp.c +++ b/unix/sftp.c @@ -52,7 +52,7 @@ int platform_default_i(const char *name, int def) FontSpec *platform_default_fontspec(const char *name) { - return fontspec_new(""); + return fontspec_new_default(); } Filename *platform_default_filename(const char *name) @@ -125,14 +125,14 @@ RFile *open_existing_file(const char *name, uint64_t *size, long *perms) { int fd; - RFile *ret; + RFile *f; fd = open(name, O_RDONLY); if (fd < 0) return NULL; - ret = snew(RFile); - ret->fd = fd; + f = snew(RFile); + f->fd = fd; if (size || mtime || atime || perms) { struct stat statbuf; @@ -154,7 +154,7 @@ RFile *open_existing_file(const char *name, uint64_t *size, *perms = statbuf.st_mode; } - return ret; + return f; } int read_from_file(RFile *f, void *buffer, int length) @@ -176,33 +176,33 @@ struct WFile { WFile *open_new_file(const char *name, long perms) { int fd; - WFile *ret; + WFile *f; fd = open(name, O_CREAT | O_TRUNC | O_WRONLY, (mode_t)(perms ? perms : 0666)); if (fd < 0) return NULL; - ret = snew(WFile); - ret->fd = fd; - ret->name = dupstr(name); + f = snew(WFile); + f->fd = fd; + f->name = dupstr(name); - return ret; + return f; } WFile *open_existing_wfile(const char *name, uint64_t *size) { int fd; - WFile *ret; + WFile *f; fd = open(name, O_APPEND | O_WRONLY); if (fd < 0) return NULL; - ret = snew(WFile); - ret->fd = fd; - ret->name = dupstr(name); + f = snew(WFile); + f->fd = fd; + f->name = dupstr(name); if (size) { struct stat statbuf; @@ -214,7 +214,7 @@ WFile *open_existing_wfile(const char *name, uint64_t *size) *size = statbuf.st_size; } - return ret; + return f; } int write_to_file(WFile *f, void *buffer, int length) @@ -311,18 +311,15 @@ struct DirHandle { DirHandle *open_directory(const char *name, const char **errmsg) { - DIR *dir; - DirHandle *ret; - - dir = opendir(name); - if (!dir) { + DIR *dp = opendir(name); + if (!dp) { *errmsg = strerror(errno); return NULL; } - ret = snew(DirHandle); - ret->dir = dir; - return ret; + DirHandle *dir = snew(DirHandle); + dir->dir = dp; + return dir; } char *read_filename(DirHandle *dir) @@ -388,16 +385,16 @@ struct WildcardMatcher { int i; }; WildcardMatcher *begin_wildcard_matching(const char *name) { - WildcardMatcher *ret = snew(WildcardMatcher); + WildcardMatcher *dir = snew(WildcardMatcher); - if (glob(name, 0, NULL, &ret->globbed) < 0) { - sfree(ret); + if (glob(name, 0, NULL, &dir->globbed) < 0) { + sfree(dir); return NULL; } - ret->i = 0; + dir->i = 0; - return ret; + return dir; } char *wildcard_get_filename(WildcardMatcher *dir) { if (dir->i < dir->globbed.gl_pathc) { @@ -580,5 +577,6 @@ const bool buildinfo_gtk_relevant = false; int main(int argc, char *argv[]) { uxsel_init(); - return psftp_main(argc, argv); + CmdlineArgList *arglist = cmdline_arg_list_from_argv(argc, argv); + return psftp_main(arglist); } diff --git a/unix/storage.c b/unix/storage.c index ca225732..042887f2 100644 --- a/unix/storage.c +++ b/unix/storage.c @@ -321,7 +321,6 @@ static int keycmp(void *av, void *bv) void provide_xrm_string(const char *string, const char *progname) { const char *p, *q; - char *key; struct skeyval *xrms, *ret; p = q = strchr(string, ':'); @@ -330,14 +329,13 @@ void provide_xrm_string(const char *string, const char *progname) " \"%s\"\n", progname, string); return; } - q++; + xrms = snew(struct skeyval); + while (p > string && p[-1] != '.' && p[-1] != '*') p--; - xrms = snew(struct skeyval); - key = snewn(q-p, char); - memcpy(key, p, q-p); - key[q-p-1] = '\0'; - xrms->key = key; + xrms->key = mkstr(make_ptrlen(p, q-p)); + + q++; while (*q && isspace((unsigned char)*q)) q++; xrms->value = dupstr(q); @@ -828,7 +826,7 @@ bool have_ssh_host_key(const char *hostname, int port, return check_stored_host_key(hostname, port, keytype, "") != 1; } -void store_host_key(const char *hostname, int port, +void store_host_key(Seat *seat, const char *hostname, int port, const char *keytype, const char *key) { FILE *rfp, *wfp; @@ -846,7 +844,7 @@ void store_host_key(const char *hostname, int port, dir = make_filename(INDEX_DIR, NULL); if ((errmsg = make_dir_path(dir, 0700)) != NULL) { - nonfatal("Unable to store host key: %s", errmsg); + seat_nonfatal(seat, "Unable to store host key: %s", errmsg); sfree(errmsg); sfree(dir); sfree(tmpfilename); @@ -857,8 +855,8 @@ void store_host_key(const char *hostname, int port, wfp = fopen(tmpfilename, "w"); } if (!wfp) { - nonfatal("Unable to store host key: open(\"%s\") " - "returned '%s'", tmpfilename, strerror(errno)); + seat_nonfatal(seat, "Unable to store host key: open(\"%s\") " + "returned '%s'", tmpfilename, strerror(errno)); sfree(tmpfilename); return; } @@ -889,9 +887,9 @@ void store_host_key(const char *hostname, int port, fclose(wfp); if (rename(tmpfilename, filename) < 0) { - nonfatal("Unable to store host key: rename(\"%s\",\"%s\")" - " returned '%s'", tmpfilename, filename, - strerror(errno)); + seat_nonfatal(seat, "Unable to store host key: rename(\"%s\",\"%s\")" + " returned '%s'", tmpfilename, filename, + strerror(errno)); } sfree(tmpfilename); diff --git a/unix/stubs/no-uxsel.c b/unix/stubs/no-uxsel.c new file mode 100644 index 00000000..310a8ca3 --- /dev/null +++ b/unix/stubs/no-uxsel.c @@ -0,0 +1,31 @@ +/* + * Stub version of uxsel.c, for test programs. + */ + +#include "putty.h" + +void uxsel_init(void) +{ +} + +void uxsel_set(int fd, int rwx, uxsel_callback_fn callback) +{ +} + +void uxsel_del(int fd) +{ +} + +int next_fd(int *state, int *rwx) +{ + return -1; +} + +int first_fd(int *state, int *rwx) +{ + return -1; +} + +void select_result(int fd, int event) +{ +} diff --git a/unix/unicode.c b/unix/unicode.c index a98c8d3b..44d523e9 100644 --- a/unix/unicode.c +++ b/unix/unicode.c @@ -21,81 +21,99 @@ bool is_dbcs_leadbyte(int codepage, char byte) return false; /* we don't do DBCS */ } -int mb_to_wc(int codepage, int flags, const char *mbstr, int mblen, - wchar_t *wcstr, int wclen) +bool BinarySink_put_mb_to_wc( + BinarySink *bs, int codepage, const char *mbstr, int mblen) { if (codepage == DEFAULT_CODEPAGE) { - int n = 0; mbstate_t state; memset(&state, 0, sizeof state); while (mblen > 0) { - if (n >= wclen) - return n; - size_t i = mbrtowc(wcstr+n, mbstr, (size_t)mblen, &state); + wchar_t wc; + size_t i = mbrtowc(&wc, mbstr, (size_t)mblen, &state); if (i == (size_t)-1 || i == (size_t)-2) break; - n++; + put_data(bs, &wc, sizeof(wc)); mbstr += i; mblen -= i; } - - return n; } else if (codepage == CS_NONE) { - int n = 0; - while (mblen > 0) { - if (n >= wclen) - return n; - wcstr[n] = 0xD800 | (mbstr[0] & 0xFF); - n++; + wchar_t wc = 0xD800 | (mbstr[0] & 0xFF); + put_data(bs, &wc, sizeof(wc)); mbstr++; mblen--; } + } else { + wchar_t wbuf[1024]; + while (mblen > 0) { + int wlen = charset_to_unicode(&mbstr, &mblen, wbuf, lenof(wbuf), + codepage, NULL, NULL, 0); + put_data(bs, wbuf, wlen * sizeof(wchar_t)); + } + } - return n; - } else - return charset_to_unicode(&mbstr, &mblen, wcstr, wclen, codepage, - NULL, NULL, 0); + /* We never expect to receive invalid charset values on Unix, + * because we're not dependent on an externally defined space of + * OS-provided code pages */ + return true; } -int wc_to_mb(int codepage, int flags, const wchar_t *wcstr, int wclen, - char *mbstr, int mblen, const char *defchr) +bool BinarySink_put_wc_to_mb( + BinarySink *bs, int codepage, const wchar_t *wcstr, int wclen, + const char *defchr) { + size_t defchr_len = 0; + bool defchr_len_known = false; + if (codepage == DEFAULT_CODEPAGE) { char output[MB_LEN_MAX]; mbstate_t state; - int n = 0; memset(&state, 0, sizeof state); while (wclen > 0) { size_t i = wcrtomb(output, wcstr[0], &state); - if (i == (size_t)-1 || i > n - mblen) - break; - memcpy(mbstr+n, output, i); - n += i; + if (i == (size_t)-1) { + if (!defchr_len_known) { + defchr_len = strlen(defchr); + defchr_len_known = true; + } + put_data(bs, defchr, defchr_len); + } else { + put_data(bs, output, i); + } wcstr++; wclen--; } - - return n; } else if (codepage == CS_NONE) { - int n = 0; - while (wclen > 0 && n < mblen) { - if (*wcstr >= 0xD800 && *wcstr < 0xD900) - mbstr[n++] = (*wcstr & 0xFF); - else if (defchr) - mbstr[n++] = *defchr; + while (wclen > 0) { + if (*wcstr >= 0xD800 && *wcstr < 0xD900) { + put_byte(bs, *wcstr & 0xFF); + } else { + if (!defchr_len_known) { + defchr_len = strlen(defchr); + defchr_len_known = true; + } + put_data(bs, defchr, defchr_len); + } wcstr++; wclen--; } - return n; } else { - return charset_from_unicode(&wcstr, &wclen, mbstr, mblen, codepage, - NULL, defchr?defchr:NULL, defchr?1:0); + char buf[2048]; + defchr_len = strlen(defchr); + + while (wclen > 0) { + int len = charset_from_unicode( + &wcstr, &wclen, buf, lenof(buf), codepage, + NULL, defchr, defchr_len); + put_data(bs, buf, len); + } } + + return true; } /* @@ -243,6 +261,13 @@ bool init_ucs(struct unicode_data *ucsdata, char *linecharset, return ret; } +void init_ucs_generic(Conf *conf, struct unicode_data *ucsdata) +{ + init_ucs(ucsdata, conf_get_str(conf, CONF_line_codepage), + conf_get_bool(conf, CONF_utf8_override), + CS_NONE, conf_get_int(conf, CONF_vtmode)); +} + const char *cp_name(int codepage) { if (codepage == CS_NONE) diff --git a/unix/unifont.c b/unix/unifont.c index e9f8623a..c8256794 100644 --- a/unix/unifont.c +++ b/unix/unifont.c @@ -598,14 +598,14 @@ static bool x11font_has_glyph(unifont *font, wchar_t glyph) * This X font has 8-bit indices, so we must convert to the * appropriate character set. */ - char sbstring[2]; - int sblen = wc_to_mb(xfont->real_charset, 0, &glyph, 1, - sbstring, 2, ""); - if (sblen == 0 || !sbstring[0]) + char c = '\0'; + buffer_sink bs[1]; + buffer_sink_init(bs, &c, 1); + put_wc_to_mb(bs, xfont->real_charset, &glyph, 1, ""); + if (!c) return false; /* not even in the charset */ - return x11_font_has_glyph(xfont->fonts[0].xfs, 0, - (unsigned char)sbstring[0]); + return x11_font_has_glyph(xfont->fonts[0].xfs, 0, (unsigned char)c); } } @@ -953,14 +953,13 @@ static void x11font_draw_text(unifont_drawctx *ctx, unifont *font, * This X font has 8-bit indices, so we must convert to the * appropriate character set. */ - char *sbstring = snewn(len+1, char); - int sblen = wc_to_mb(xfont->real_charset, 0, string, len, - sbstring, len+1, "."); + strbuf *sb = strbuf_new(); + put_wc_to_mb(sb, xfont->real_charset, string, len, "."); x11font_really_draw_text(x11font_drawfuncs + index + 0, ctx, &xfont->fonts[sfid], xfont->disp, x, y, - sbstring, sblen, shadowoffset, + sb->s, sb->len, shadowoffset, xfont->variable, cellwidth * mult); - sfree(sbstring); + strbuf_free(sb); } } @@ -1603,7 +1602,7 @@ static void pangofont_draw_internal(unifont_drawctx *ctx, unifont *font, PangoLayout *layout; PangoRectangle rect; char *utfstring, *utfptr; - int utflen; + size_t utflen; bool shadowbold = false; void (*draw_layout)(unifont_drawctx *ctx, gint x, gint y, PangoLayout *layout) = NULL; @@ -1642,12 +1641,11 @@ static void pangofont_draw_internal(unifont_drawctx *ctx, unifont *font, * Pango always expects UTF-8, so convert the input wide character * string to UTF-8. */ - utfstring = snewn(len*6+1, char); /* UTF-8 has max 6 bytes/char */ - utflen = wc_to_mb(CS_UTF8, 0, string, len, utfstring, len*6+1, "."); + utfstring = dup_wc_to_mb_c(CS_UTF8, string, len, "", &utflen); utfptr = utfstring; while (utflen > 0) { - int clen, n; + size_t clen, n; int desired = cellwidth * PANGO_SCALE; /* diff --git a/unix/uppity.c b/unix/uppity.c index 11fead95..30501af7 100644 --- a/unix/uppity.c +++ b/unix/uppity.c @@ -80,7 +80,7 @@ int platform_default_i(const char *name, int def) FontSpec *platform_default_fontspec(const char *name) { - return fontspec_new(""); + return fontspec_new_default(); } Filename *platform_default_filename(const char *name) @@ -478,8 +478,8 @@ static Plug *server_conn_plug( &inst->ap, &inst->logpolicy, &unix_live_sftpserver_vt); } -static void server_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, - const char *error_msg, int error_code) +static void server_log(Plug *plug, Socket *s, PlugLogType type, SockAddr *addr, + int port, const char *error_msg, int error_code) { log_to_stderr((unsigned)-1, error_msg); } @@ -514,13 +514,13 @@ static int server_accepting(Plug *p, accept_fn_t constructor, accept_ctx_t ctx) if ((err = sk_socket_error(s)) != NULL) return 1; - SocketPeerInfo *pi = sk_peer_info(s); + SocketEndpointInfo *pi = sk_peer_info(s); if (pi->addressfamily != ADDRTYPE_LOCAL && !sk_peer_trusted(s)) { fprintf(stderr, "rejected connection to serv#%u " "from %s (untrustworthy peer)\n", cfg->config_id, pi->log_text); - sk_free_peer_info(pi); + sk_free_endpoint_info(pi); sk_close(s); next_id = old_next_id; return 1; @@ -530,7 +530,7 @@ static int server_accepting(Plug *p, accept_fn_t constructor, accept_ctx_t ctx) cfg->config_id, pi->log_text); log_to_stderr(inst->id, msg); sfree(msg); - sk_free_peer_info(pi); + sk_free_endpoint_info(pi); sk_set_frozen(s, false); ssh_server_start(plug, s); @@ -924,6 +924,8 @@ int main(int argc, char **argv) ci->ssc.stunt_allow_trivial_ki_auth = true; } else if (!strcmp(arg, "--return-success-to-pubkey-offer")) { ci->ssc.stunt_return_success_to_pubkey_offer = true; + } else if (!strcmp(arg, "--close-after-banner")) { + ci->ssc.stunt_close_after_banner = true; } else { fprintf(stderr, "%s: unrecognised option '%s'\n", appname, arg); exit(1); diff --git a/unix/utils/block_signal.c b/unix/utils/block_signal.c index 918e63a4..8c6245ff 100644 --- a/unix/utils/block_signal.c +++ b/unix/utils/block_signal.c @@ -14,7 +14,7 @@ void block_signal(int sig, bool block_it) sigemptyset(&ss); sigaddset(&ss, sig); - if(sigprocmask(block_it ? SIG_BLOCK : SIG_UNBLOCK, &ss, 0) < 0) { + if (sigprocmask(block_it ? SIG_BLOCK : SIG_UNBLOCK, &ss, 0) < 0) { perror("sigprocmask"); exit(1); } diff --git a/unix/utils/cmdline_arg.c b/unix/utils/cmdline_arg.c new file mode 100644 index 00000000..e8a7a654 --- /dev/null +++ b/unix/utils/cmdline_arg.c @@ -0,0 +1,167 @@ +/* + * Implementation of the CmdlineArg abstraction for Unix + */ + +#include "putty.h" + +typedef struct CmdlineArgUnix CmdlineArgUnix; +struct CmdlineArgUnix { + /* + * This is a writable char *, because the arguments received by + * main() really are writable, and moreover, you _want_ to write + * over them in some circumstances, to manipulate how your program + * shows up in ps(1). Our example is wiping out the argument to + * the -pw option. This isn't robust - you need to not use that + * option at all if you want zero risk of password exposure + * through ps - but we do the best we can. + * + * Some CmdlineArg structures are invented after the program + * starts, in which case they don't correspond to real argv words + * at all, and this pointer is NULL. + */ + char *argv_word; + + /* + * A CmdlineArg invented later might need to store a string that + * will be freed when it goes away. This pointer is non-NULL if + * freeing needs to happen. + */ + char *to_free; + + /* + * This const char * is the real string value of the argument. + */ + const char *value; + + /* + * Our index in the CmdlineArgList, or (size_t)-1 if we don't have + * one and are an argument invented later. + */ + size_t index; + + /* + * Public part of the structure. + */ + CmdlineArg argp; +}; + +static CmdlineArgUnix *cmdline_arg_new_in_list(CmdlineArgList *list) +{ + CmdlineArgUnix *arg = snew(CmdlineArgUnix); + arg->argv_word = NULL; + arg->to_free = NULL; + arg->value = NULL; + arg->index = (size_t)-1; + arg->argp.list = list; + sgrowarray(list->args, list->argssize, list->nargs); + list->args[list->nargs++] = &arg->argp; + return arg; +} + +static CmdlineArg *cmdline_arg_from_argv_word(CmdlineArgList *list, char *word) +{ + CmdlineArgUnix *arg = cmdline_arg_new_in_list(list); + arg->argv_word = word; + arg->value = arg->argv_word; + return &arg->argp; +} + +CmdlineArgList *cmdline_arg_list_from_argv(int argc, char **argv) +{ + CmdlineArgList *list = snew(CmdlineArgList); + list->args = NULL; + list->nargs = list->argssize = 0; + for (int i = 1; i < argc; i++) { + CmdlineArg *argp = cmdline_arg_from_argv_word(list, argv[i]); + CmdlineArgUnix *arg = container_of(argp, CmdlineArgUnix, argp); + arg->index = i - 1; /* index in list->args[], not in argv[] */ + } + sgrowarray(list->args, list->argssize, list->nargs); + list->args[list->nargs++] = NULL; + return list; +} + +void cmdline_arg_free(CmdlineArg *argp) +{ + if (!argp) + return; + + CmdlineArgUnix *arg = container_of(argp, CmdlineArgUnix, argp); + if (arg->to_free) + burnstr(arg->to_free); + sfree(arg); +} + +void cmdline_arg_list_free(CmdlineArgList *list) +{ + for (size_t i = 0; i < list->nargs; i++) + cmdline_arg_free(list->args[i]); + sfree(list->args); + sfree(list); +} + +CmdlineArg *cmdline_arg_from_str(CmdlineArgList *list, const char *string) +{ + CmdlineArgUnix *arg = cmdline_arg_new_in_list(list); + arg->to_free = dupstr(string); + arg->value = arg->to_free; + return &arg->argp; +} + +const char *cmdline_arg_to_str(CmdlineArg *argp) +{ + if (!argp) + return NULL; + + CmdlineArgUnix *arg = container_of(argp, CmdlineArgUnix, argp); + return arg->value; +} + +const char *cmdline_arg_to_utf8(CmdlineArg *argp) +{ + /* For the moment, return NULL. But perhaps it makes sense to + * convert from the default locale into UTF-8? */ + return NULL; +} + +Filename *cmdline_arg_to_filename(CmdlineArg *argp) +{ + if (!argp) + return NULL; + + CmdlineArgUnix *arg = container_of(argp, CmdlineArgUnix, argp); + return filename_from_str(arg->value); +} + +void cmdline_arg_wipe(CmdlineArg *argp) +{ + if (!argp) + return; + + CmdlineArgUnix *arg = container_of(argp, CmdlineArgUnix, argp); + if (arg->argv_word) + smemclr(arg->argv_word, strlen(arg->argv_word)); +} + +char **cmdline_arg_remainder(CmdlineArg *argp) +{ + CmdlineArgUnix *arg = container_of(argp, CmdlineArgUnix, argp); + CmdlineArgList *list = argp->list; + + size_t index = arg->index; + assert(index != (size_t)-1); + + size_t nargs = 0; + while (list->args[index + nargs]) + nargs++; + + char **argv = snewn(nargs + 1, char *); + for (size_t i = 0; i < nargs; i++) { + CmdlineArg *ith_argp = list->args[index + i]; + CmdlineArgUnix *ith_arg = container_of(ith_argp, CmdlineArgUnix, argp); + argv[i] = ith_arg->argv_word; + } + argv[nargs] = NULL; + + return argv; +} diff --git a/unix/utils/filename.c b/unix/utils/filename.c index 208483d2..e786b222 100644 --- a/unix/utils/filename.c +++ b/unix/utils/filename.c @@ -9,9 +9,9 @@ Filename *filename_from_str(const char *str) { - Filename *ret = snew(Filename); - ret->path = dupstr(str); - return ret; + Filename *fn = snew(Filename); + fn->path = dupstr(str); + return fn; } Filename *filename_copy(const Filename *fn) diff --git a/unix/utils/fontspec.c b/unix/utils/fontspec.c index 7c5a0a2f..b9bf8195 100644 --- a/unix/utils/fontspec.c +++ b/unix/utils/fontspec.c @@ -13,6 +13,11 @@ FontSpec *fontspec_new(const char *name) return f; } +FontSpec *fontspec_new_default(void) +{ + return fontspec_new(""); +} + FontSpec *fontspec_copy(const FontSpec *f) { return fontspec_new(f->name); diff --git a/unix/utils/signal.c b/unix/utils/signal.c index a56fe6be..81234f97 100644 --- a/unix/utils/signal.c +++ b/unix/utils/signal.c @@ -21,10 +21,10 @@ void (*putty_signal(int sig, void (*func)(int)))(int) struct sigaction old; sa.sa_handler = func; - if(sigemptyset(&sa.sa_mask) < 0) + if (sigemptyset(&sa.sa_mask) < 0) return SIG_ERR; sa.sa_flags = SA_RESTART; - if(sigaction(sig, &sa, &old) < 0) + if (sigaction(sig, &sa, &old) < 0) return SIG_ERR; return old.sa_handler; } diff --git a/unix/window.c b/unix/window.c index 1ca52833..bb772d32 100644 --- a/unix/window.c +++ b/unix/window.c @@ -290,6 +290,12 @@ static void gtk_seat_connection_fatal(Seat *seat, const char *msg) queue_toplevel_callback(connection_fatal_callback, inst); } +static void gtk_seat_nonfatal(Seat *seat, const char *msg) +{ + GtkFrontend *inst = container_of(seat, GtkFrontend, seat); + nonfatal_message_box(inst->window, msg); +} + /* * Default settings that are specific to pterm. */ @@ -298,7 +304,7 @@ FontSpec *platform_default_fontspec(const char *name) if (!strcmp(name, "Font")) return fontspec_new(DEFAULT_GTK_FONT); else - return fontspec_new(""); + return fontspec_new_default(); } Filename *platform_default_filename(const char *name) @@ -423,6 +429,7 @@ static const SeatVtable gtk_seat_vt = { .notify_remote_exit = gtk_seat_notify_remote_exit, .notify_remote_disconnect = nullseat_notify_remote_disconnect, .connection_fatal = gtk_seat_connection_fatal, + .nonfatal = gtk_seat_nonfatal, .update_specials_menu = gtk_seat_update_specials_menu, .get_ttymode = gtk_seat_get_ttymode, .set_busy_status = gtk_seat_set_busy_status, @@ -1596,10 +1603,12 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) const wchar_t *wp; int wlen; int ulen; + buffer_sink bs[1]; - wlen = mb_to_wc(DEFAULT_CODEPAGE, 0, - event_string, strlen(event_string), - widedata, lenof(widedata)-1); + buffer_sink_init(bs, widedata, sizeof(widedata) - sizeof(wchar_t)); + put_mb_to_wc(bs, DEFAULT_CODEPAGE, + event_string, strlen(event_string)); + wlen = (wchar_t *)bs->out - widedata; #ifdef KEY_EVENT_DIAGNOSTICS { @@ -2947,16 +2956,12 @@ static void clipboard_text_received(GtkClipboard *clipboard, { GtkFrontend *inst = (GtkFrontend *)data; wchar_t *paste; - int paste_len; - int length; + size_t paste_len; if (!text) return; - length = strlen(text); - - paste = snewn(length, wchar_t); - paste_len = mb_to_wc(CS_UTF8, 0, text, length, paste, length); + paste = dup_mb_to_wc(CS_UTF8, text, length, &paste_len); term_do_paste(inst->term, paste, paste_len); @@ -3095,17 +3100,15 @@ static void gtkwin_clip_write( state->pasteout_data_ctext_len = 0; } - state->pasteout_data = snewn(len*6, char); - state->pasteout_data_len = len*6; - state->pasteout_data_len = wc_to_mb(inst->ucsdata.line_codepage, 0, - data, len, state->pasteout_data, - state->pasteout_data_len, NULL); - if (state->pasteout_data_len == 0) { - sfree(state->pasteout_data); - state->pasteout_data = NULL; - } else { - state->pasteout_data = - sresize(state->pasteout_data, state->pasteout_data_len, char); + { + size_t outlen; + state->pasteout_data = dup_wc_to_mb_c( + inst->ucsdata.line_codepage, data, len, "", &outlen); + /* We can't handle pastes larger than INT_MAX, because + * gtk_selection_data_set_text's length parameter is a gint */ + if (outlen > INT_MAX) + outlen = INT_MAX; + state->pasteout_data_len = outlen; } #ifndef NOT_X_WINDOWS @@ -3233,7 +3236,7 @@ static void selection_received(GtkWidget *widget, GtkSelectionData *seldata, const guchar *seldata_data = gtk_selection_data_get_data(seldata); gint seldata_length = gtk_selection_data_get_length(seldata); wchar_t *paste; - int paste_len; + size_t paste_len; struct clipboard_state *state = clipboard_from_atom( inst, gtk_selection_data_get_selection(seldata)); @@ -3326,11 +3329,8 @@ static void selection_received(GtkWidget *widget, GtkSelectionData *seldata, } } - paste = snewn(length, wchar_t); - paste_len = mb_to_wc(charset, 0, text, length, paste, length); - + paste = dup_mb_to_wc_c(charset, text, length, &paste_len); term_do_paste(inst->term, paste, paste_len); - sfree(paste); #ifndef NOT_X_WINDOWS @@ -3433,7 +3433,7 @@ static void gtkwin_set_title(TermWin *tw, const char *title, int codepage) GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); sfree(inst->wintitle); if (codepage != CP_UTF8) { - wchar_t *title_w = dup_mb_to_wc(codepage, 0, title); + wchar_t *title_w = dup_mb_to_wc(codepage, title); inst->wintitle = encode_wide_string_as_utf8(title_w); sfree(title_w); } else { @@ -3447,7 +3447,7 @@ static void gtkwin_set_icon_title(TermWin *tw, const char *title, int codepage) GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); sfree(inst->icontitle); if (codepage != CP_UTF8) { - wchar_t *title_w = dup_mb_to_wc(codepage, 0, title); + wchar_t *title_w = dup_mb_to_wc(codepage, title); inst->icontitle = encode_wide_string_as_utf8(title_w); sfree(title_w); } else { @@ -3889,11 +3889,11 @@ static void do_text_internal( truecolour.fg = truecolour.bg; truecolour.bg = trgb; } - if ((inst->bold_style & 2) && (attr & ATTR_BOLD)) { + if ((inst->bold_style & BOLD_STYLE_COLOUR) && (attr & ATTR_BOLD)) { if (nfg < 16) nfg |= 8; else if (nfg >= 256) nfg |= 1; } - if ((inst->bold_style & 2) && (attr & ATTR_BLINK)) { + if ((inst->bold_style & BOLD_STYLE_COLOUR) && (attr & ATTR_BLINK)) { if (nbg < 16) nbg |= 8; else if (nbg >= 256) nbg |= 1; } @@ -3913,7 +3913,7 @@ static void do_text_internal( widefactor = 1; } - if ((attr & ATTR_BOLD) && (inst->bold_style & 1)) { + if ((attr & ATTR_BOLD) && (inst->bold_style & BOLD_STYLE_FONT)) { bold = true; fontid |= 1; } else { @@ -4068,7 +4068,7 @@ static void gtkwin_draw_cursor( passive = true; } else passive = false; - if ((attr & TATTR_ACTCURS) && inst->cursor_type != 0) { + if ((attr & TATTR_ACTCURS) && inst->cursor_type != CURSOR_BLOCK) { attr &= ~TATTR_ACTCURS; active = true; } else @@ -4093,7 +4093,7 @@ static void gtkwin_draw_cursor( len *= 2; } - if (inst->cursor_type == 0) { + if (inst->cursor_type == CURSOR_BLOCK) { /* * An active block cursor will already have been done by * the above do_text call, so we only need to do anything @@ -4118,7 +4118,7 @@ static void gtkwin_draw_cursor( else char_width = inst->font_width; - if (inst->cursor_type == 1) { + if (inst->cursor_type == CURSOR_UNDERLINE) { uheight = inst->fonts[0]->ascent + 1; if (uheight >= inst->font_height) uheight = inst->font_height - 1; @@ -4128,7 +4128,7 @@ static void gtkwin_draw_cursor( dx = 1; dy = 0; length = len * widefactor * char_width; - } else { + } else /* inst->cursor_type == CURSOR_VERTICAL_LINE */ { int xadjust = 0; if (attr & TATTR_RIGHTCURS) xadjust = char_width - 1; diff --git a/unix/x11.c b/unix/x11.c index 710ff849..8ec2da07 100644 --- a/unix/x11.c +++ b/unix/x11.c @@ -33,9 +33,12 @@ void platform_get_x11_auth(struct X11Display *disp, Conf *conf) } if (xauthfile) { - x11_get_auth_from_authfile(disp, xauthfile); + Filename *xauthfn = filename_from_str(xauthfile); if (needs_free) sfree(xauthfile); + + x11_get_auth_from_authfile(disp, xauthfn); + filename_free(xauthfn); } } diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index 6ef33c8d..7accda5f 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -9,21 +9,26 @@ add_sources_from_current_dir(utils bufchain.c buildinfo.c burnstr.c + burnwcs.c cert-expr.c chomp.c cmdline_get_passwd_input_state_new.c conf.c + conf_data.c conf_dest.c + conf_debug.c conf_launchable.c ctrlparse.c ctrlset_normalise.c debug.c decode_utf8.c decode_utf8_to_wchar.c + decode_utf8_to_wide_string.c default_description.c dupcat.c dupprintf.c dupstr.c + dupwcs.c dup_mb_to_wc.c dup_wc_to_mb.c encode_utf8.c @@ -51,6 +56,7 @@ add_sources_from_current_dir(utils read_file_into.c seat_connection_fatal.c seat_dialog_text.c + seat_nonfatal.c sessprep.c sk_free_peer_info.c smemclr.c @@ -64,6 +70,8 @@ add_sources_from_current_dir(utils stripctrl.c tempseat.c tree234.c + unicode-known.c + unicode-norm.c validate_manual_hostkey.c version.c wcwidth.c diff --git a/utils/backend_socket_log.c b/utils/backend_socket_log.c index 783cca31..b8bc9905 100644 --- a/utils/backend_socket_log.c +++ b/utils/backend_socket_log.c @@ -4,7 +4,7 @@ #include "putty.h" #include "network.h" -void backend_socket_log(Seat *seat, LogContext *logctx, +void backend_socket_log(Seat *seat, LogContext *logctx, Socket *sock, PlugLogType type, SockAddr *addr, int port, const char *error_msg, int error_code, Conf *conf, bool session_started) @@ -30,6 +30,16 @@ void backend_socket_log(Seat *seat, LogContext *logctx, else /* fallback if address unavailable */ sprintf(addrbuf, "remote host"); msg = dupprintf("Connected to %s", addrbuf); + if (sock) { + SocketEndpointInfo *local_end = sk_endpoint_info(sock, false); + if (local_end) { + char *newmsg = dupprintf("%s (from %s)", msg, + local_end->log_text); + sfree(msg); + msg = newmsg; + sk_free_endpoint_info(local_end); + } + } break; case PLUGLOG_PROXY_MSG: { /* Proxy-related log messages have their own identifying diff --git a/utils/buildinfo.c b/utils/buildinfo.c index a69222fe..3f4505ca 100644 --- a/utils/buildinfo.c +++ b/utils/buildinfo.c @@ -44,6 +44,12 @@ char *buildinfo(const char *newline) * cases, two different compiler versions have the same _MSC_VER * value, and have to be distinguished by _MSC_FULL_VER. */ +#elif _MSC_VER == 1942 + put_fmt(buf, " 2022 (17.12)"); +#elif _MSC_VER == 1941 + put_fmt(buf, " 2022 (17.11)"); +#elif _MSC_VER == 1940 + put_fmt(buf, " 2022 (17.10)"); #elif _MSC_VER == 1939 put_fmt(buf, " 2022 (17.9)"); #elif _MSC_VER == 1938 diff --git a/utils/burnwcs.c b/utils/burnwcs.c new file mode 100644 index 00000000..15d325f1 --- /dev/null +++ b/utils/burnwcs.c @@ -0,0 +1,18 @@ +/* + * 'Burn' a dynamically allocated wide string, in the sense of + * destroying it beyond recovery: overwrite it with zeroes and then + * free it. + */ + +#include + +#include "defs.h" +#include "misc.h" + +void burnwcs(wchar_t *string) +{ + if (string) { + smemclr(string, sizeof(*string) * wcslen(string)); + sfree(string); + } +} diff --git a/utils/conf.c b/utils/conf.c index 53195180..dd51f79f 100644 --- a/utils/conf.c +++ b/utils/conf.c @@ -10,22 +10,6 @@ #include "tree234.h" #include "putty.h" -/* - * Enumeration of types used in keys and values. - */ -typedef enum { - TYPE_NONE, TYPE_BOOL, TYPE_INT, TYPE_STR, TYPE_FILENAME, TYPE_FONT -} Type; - -/* - * Arrays which allow us to look up the subkey and value types for a - * given primary key id. - */ -#define CONF_SUBKEYTYPE_DEF(valtype, keytype, keyword) TYPE_ ## keytype, -static int subkeytypes[] = { CONFIG_OPTIONS(CONF_SUBKEYTYPE_DEF) }; -#define CONF_VALUETYPE_DEF(valtype, keytype, keyword) TYPE_ ## valtype, -static int valuetypes[] = { CONFIG_OPTIONS(CONF_VALUETYPE_DEF) }; - /* * Configuration keys are primarily integers (big enum of all the * different configurable options); some keys have string-designated @@ -55,7 +39,10 @@ struct value { union { bool boolval; int intval; - char *stringval; + struct { + char *str; + bool utf8; + } stringval; Filename *fileval; FontSpec *fontval; } u; @@ -87,17 +74,20 @@ static int conf_cmp(void *av, void *bv) return -1; else if (a->primary > b->primary) return +1; - switch (subkeytypes[a->primary]) { - case TYPE_INT: + switch (conf_key_info[a->primary].subkey_type) { + case CONF_TYPE_INT: if (a->secondary.i < b->secondary.i) return -1; else if (a->secondary.i > b->secondary.i) return +1; return 0; - case TYPE_STR: + case CONF_TYPE_STR: + case CONF_TYPE_UTF8: return strcmp(a->secondary.s, b->secondary.s); - default: + case CONF_TYPE_NONE: return 0; + default: + unreachable("Unsupported subkey type"); } } @@ -110,17 +100,20 @@ static int conf_cmp_constkey(void *av, void *bv) return -1; else if (a->primary > b->primary) return +1; - switch (subkeytypes[a->primary]) { - case TYPE_INT: + switch (conf_key_info[a->primary].subkey_type) { + case CONF_TYPE_INT: if (a->secondary.i < b->secondary.i) return -1; else if (a->secondary.i > b->secondary.i) return +1; return 0; - case TYPE_STR: + case CONF_TYPE_STR: + case CONF_TYPE_UTF8: return strcmp(a->secondary.s, b->secondary.s); - default: + case CONF_TYPE_NONE: return 0; + default: + unreachable("Unsupported subkey type"); } } @@ -131,7 +124,8 @@ static int conf_cmp_constkey(void *av, void *bv) */ static void free_key(struct key *key) { - if (subkeytypes[key->primary] == TYPE_STR) + if (conf_key_info[key->primary].subkey_type == CONF_TYPE_STR || + conf_key_info[key->primary].subkey_type == CONF_TYPE_UTF8) sfree(key->secondary.s); } @@ -142,11 +136,12 @@ static void free_key(struct key *key) static void copy_key(struct key *to, struct key *from) { to->primary = from->primary; - switch (subkeytypes[to->primary]) { - case TYPE_INT: + switch (conf_key_info[to->primary].subkey_type) { + case CONF_TYPE_INT: to->secondary.i = from->secondary.i; break; - case TYPE_STR: + case CONF_TYPE_STR: + case CONF_TYPE_UTF8: to->secondary.s = dupstr(from->secondary.s); break; } @@ -159,11 +154,12 @@ static void copy_key(struct key *to, struct key *from) */ static void free_value(struct value *val, int type) { - if (type == TYPE_STR) - sfree(val->u.stringval); - else if (type == TYPE_FILENAME) + if (type == CONF_TYPE_STR || type == CONF_TYPE_UTF8 || + type == CONF_TYPE_STR_AMBI) + sfree(val->u.stringval.str); + else if (type == CONF_TYPE_FILENAME) filename_free(val->u.fileval); - else if (type == TYPE_FONT) + else if (type == CONF_TYPE_FONT) fontspec_free(val->u.fontval); } @@ -174,19 +170,22 @@ static void free_value(struct value *val, int type) static void copy_value(struct value *to, struct value *from, int type) { switch (type) { - case TYPE_BOOL: + case CONF_TYPE_BOOL: to->u.boolval = from->u.boolval; break; - case TYPE_INT: + case CONF_TYPE_INT: to->u.intval = from->u.intval; break; - case TYPE_STR: - to->u.stringval = dupstr(from->u.stringval); + case CONF_TYPE_STR: + case CONF_TYPE_UTF8: + case CONF_TYPE_STR_AMBI: + to->u.stringval.str = dupstr(from->u.stringval.str); + to->u.stringval.utf8 = from->u.stringval.utf8; break; - case TYPE_FILENAME: + case CONF_TYPE_FILENAME: to->u.fileval = filename_copy(from->u.fileval); break; - case TYPE_FONT: + case CONF_TYPE_FONT: to->u.fontval = fontspec_copy(from->u.fontval); break; } @@ -198,7 +197,7 @@ static void copy_value(struct value *to, struct value *from, int type) static void free_entry(struct conf_entry *entry) { free_key(&entry->key); - free_value(&entry->value, valuetypes[entry->key.primary]); + free_value(&entry->value, conf_key_info[entry->key.primary].value_type); sfree(entry); } @@ -211,7 +210,7 @@ Conf *conf_new(void) return conf; } -static void conf_clear(Conf *conf) +void conf_clear(Conf *conf) { struct conf_entry *entry; @@ -248,7 +247,7 @@ void conf_copy_into(Conf *newconf, Conf *oldconf) entry2 = snew(struct conf_entry); copy_key(&entry2->key, &entry->key); copy_value(&entry2->value, &entry->value, - valuetypes[entry->key.primary]); + conf_key_info[entry->key.primary].value_type); add234(newconf->tree, entry2); } } @@ -267,8 +266,8 @@ bool conf_get_bool(Conf *conf, int primary) struct key key; struct conf_entry *entry; - assert(subkeytypes[primary] == TYPE_NONE); - assert(valuetypes[primary] == TYPE_BOOL); + assert(conf_key_info[primary].subkey_type == CONF_TYPE_NONE); + assert(conf_key_info[primary].value_type == CONF_TYPE_BOOL); key.primary = primary; entry = find234(conf->tree, &key, NULL); assert(entry); @@ -280,8 +279,8 @@ int conf_get_int(Conf *conf, int primary) struct key key; struct conf_entry *entry; - assert(subkeytypes[primary] == TYPE_NONE); - assert(valuetypes[primary] == TYPE_INT); + assert(conf_key_info[primary].subkey_type == CONF_TYPE_NONE); + assert(conf_key_info[primary].value_type == CONF_TYPE_INT); key.primary = primary; entry = find234(conf->tree, &key, NULL); assert(entry); @@ -293,8 +292,8 @@ int conf_get_int_int(Conf *conf, int primary, int secondary) struct key key; struct conf_entry *entry; - assert(subkeytypes[primary] == TYPE_INT); - assert(valuetypes[primary] == TYPE_INT); + assert(conf_key_info[primary].subkey_type == CONF_TYPE_INT); + assert(conf_key_info[primary].value_type == CONF_TYPE_INT); key.primary = primary; key.secondary.i = secondary; entry = find234(conf->tree, &key, NULL); @@ -307,12 +306,42 @@ char *conf_get_str(Conf *conf, int primary) struct key key; struct conf_entry *entry; - assert(subkeytypes[primary] == TYPE_NONE); - assert(valuetypes[primary] == TYPE_STR); + assert(conf_key_info[primary].subkey_type == CONF_TYPE_NONE); + assert(conf_key_info[primary].value_type == CONF_TYPE_STR); + key.primary = primary; + entry = find234(conf->tree, &key, NULL); + assert(entry); + return entry->value.u.stringval.str; +} + +char *conf_get_utf8(Conf *conf, int primary) +{ + struct key key; + struct conf_entry *entry; + + assert(conf_key_info[primary].subkey_type == CONF_TYPE_NONE); + assert(conf_key_info[primary].value_type == CONF_TYPE_UTF8); + key.primary = primary; + entry = find234(conf->tree, &key, NULL); + assert(entry); + return entry->value.u.stringval.str; +} + +char *conf_get_str_ambi(Conf *conf, int primary, bool *utf8) +{ + struct key key; + struct conf_entry *entry; + + assert(conf_key_info[primary].subkey_type == CONF_TYPE_NONE); + assert(conf_key_info[primary].value_type == CONF_TYPE_STR || + conf_key_info[primary].value_type == CONF_TYPE_UTF8 || + conf_key_info[primary].value_type == CONF_TYPE_STR_AMBI); key.primary = primary; entry = find234(conf->tree, &key, NULL); assert(entry); - return entry->value.u.stringval; + if (utf8) + *utf8 = entry->value.u.stringval.utf8; + return entry->value.u.stringval.str; } char *conf_get_str_str_opt(Conf *conf, int primary, const char *secondary) @@ -320,12 +349,12 @@ char *conf_get_str_str_opt(Conf *conf, int primary, const char *secondary) struct key key; struct conf_entry *entry; - assert(subkeytypes[primary] == TYPE_STR); - assert(valuetypes[primary] == TYPE_STR); + assert(conf_key_info[primary].subkey_type == CONF_TYPE_STR); + assert(conf_key_info[primary].value_type == CONF_TYPE_STR); key.primary = primary; key.secondary.s = (char *)secondary; entry = find234(conf->tree, &key, NULL); - return entry ? entry->value.u.stringval : NULL; + return entry ? entry->value.u.stringval.str : NULL; } char *conf_get_str_str(Conf *conf, int primary, const char *secondary) @@ -341,8 +370,8 @@ char *conf_get_str_strs(Conf *conf, int primary, struct constkey key; struct conf_entry *entry; - assert(subkeytypes[primary] == TYPE_STR); - assert(valuetypes[primary] == TYPE_STR); + assert(conf_key_info[primary].subkey_type == CONF_TYPE_STR); + assert(conf_key_info[primary].value_type == CONF_TYPE_STR); key.primary = primary; if (subkeyin) { key.secondary.s = subkeyin; @@ -354,7 +383,7 @@ char *conf_get_str_strs(Conf *conf, int primary, if (!entry || entry->key.primary != primary) return NULL; *subkeyout = entry->key.secondary.s; - return entry->value.u.stringval; + return entry->value.u.stringval.str; } char *conf_get_str_nthstrkey(Conf *conf, int primary, int n) @@ -363,8 +392,8 @@ char *conf_get_str_nthstrkey(Conf *conf, int primary, int n) struct conf_entry *entry; int index; - assert(subkeytypes[primary] == TYPE_STR); - assert(valuetypes[primary] == TYPE_STR); + assert(conf_key_info[primary].subkey_type == CONF_TYPE_STR); + assert(conf_key_info[primary].value_type == CONF_TYPE_STR); key.primary = primary; key.secondary.s = ""; entry = findrelpos234(conf->tree, &key, conf_cmp_constkey, @@ -382,8 +411,8 @@ Filename *conf_get_filename(Conf *conf, int primary) struct key key; struct conf_entry *entry; - assert(subkeytypes[primary] == TYPE_NONE); - assert(valuetypes[primary] == TYPE_FILENAME); + assert(conf_key_info[primary].subkey_type == CONF_TYPE_NONE); + assert(conf_key_info[primary].value_type == CONF_TYPE_FILENAME); key.primary = primary; entry = find234(conf->tree, &key, NULL); assert(entry); @@ -395,8 +424,8 @@ FontSpec *conf_get_fontspec(Conf *conf, int primary) struct key key; struct conf_entry *entry; - assert(subkeytypes[primary] == TYPE_NONE); - assert(valuetypes[primary] == TYPE_FONT); + assert(conf_key_info[primary].subkey_type == CONF_TYPE_NONE); + assert(conf_key_info[primary].value_type == CONF_TYPE_FONT); key.primary = primary; entry = find234(conf->tree, &key, NULL); assert(entry); @@ -407,8 +436,8 @@ void conf_set_bool(Conf *conf, int primary, bool value) { struct conf_entry *entry = snew(struct conf_entry); - assert(subkeytypes[primary] == TYPE_NONE); - assert(valuetypes[primary] == TYPE_BOOL); + assert(conf_key_info[primary].subkey_type == CONF_TYPE_NONE); + assert(conf_key_info[primary].value_type == CONF_TYPE_BOOL); entry->key.primary = primary; entry->value.u.boolval = value; conf_insert(conf, entry); @@ -418,8 +447,8 @@ void conf_set_int(Conf *conf, int primary, int value) { struct conf_entry *entry = snew(struct conf_entry); - assert(subkeytypes[primary] == TYPE_NONE); - assert(valuetypes[primary] == TYPE_INT); + assert(conf_key_info[primary].subkey_type == CONF_TYPE_NONE); + assert(conf_key_info[primary].value_type == CONF_TYPE_INT); entry->key.primary = primary; entry->value.u.intval = value; conf_insert(conf, entry); @@ -430,23 +459,56 @@ void conf_set_int_int(Conf *conf, int primary, { struct conf_entry *entry = snew(struct conf_entry); - assert(subkeytypes[primary] == TYPE_INT); - assert(valuetypes[primary] == TYPE_INT); + assert(conf_key_info[primary].subkey_type == CONF_TYPE_INT); + assert(conf_key_info[primary].value_type == CONF_TYPE_INT); entry->key.primary = primary; entry->key.secondary.i = secondary; entry->value.u.intval = value; conf_insert(conf, entry); } -void conf_set_str(Conf *conf, int primary, const char *value) +bool conf_try_set_str(Conf *conf, int primary, const char *value) { + assert(conf_key_info[primary].subkey_type == CONF_TYPE_NONE); + if (conf_key_info[primary].value_type == CONF_TYPE_UTF8) + return false; + assert(conf_key_info[primary].value_type == CONF_TYPE_STR || + conf_key_info[primary].value_type == CONF_TYPE_STR_AMBI); + struct conf_entry *entry = snew(struct conf_entry); + entry->key.primary = primary; + entry->value.u.stringval.str = dupstr(value); + entry->value.u.stringval.utf8 = false; + conf_insert(conf, entry); + return true; +} - assert(subkeytypes[primary] == TYPE_NONE); - assert(valuetypes[primary] == TYPE_STR); +void conf_set_str(Conf *conf, int primary, const char *value) +{ + bool success = conf_try_set_str(conf, primary, value); + assert(success && "conf_set_str on CONF_TYPE_UTF8"); +} + +bool conf_try_set_utf8(Conf *conf, int primary, const char *value) +{ + assert(conf_key_info[primary].subkey_type == CONF_TYPE_NONE); + if (conf_key_info[primary].value_type == CONF_TYPE_STR) + return false; + assert(conf_key_info[primary].value_type == CONF_TYPE_UTF8 || + conf_key_info[primary].value_type == CONF_TYPE_STR_AMBI); + + struct conf_entry *entry = snew(struct conf_entry); entry->key.primary = primary; - entry->value.u.stringval = dupstr(value); + entry->value.u.stringval.str = dupstr(value); + entry->value.u.stringval.utf8 = true; conf_insert(conf, entry); + return true; +} + +void conf_set_utf8(Conf *conf, int primary, const char *value) +{ + bool success = conf_try_set_utf8(conf, primary, value); + assert(success && "conf_set_utf8 on CONF_TYPE_STR"); } void conf_set_str_str(Conf *conf, int primary, const char *secondary, @@ -454,11 +516,12 @@ void conf_set_str_str(Conf *conf, int primary, const char *secondary, { struct conf_entry *entry = snew(struct conf_entry); - assert(subkeytypes[primary] == TYPE_STR); - assert(valuetypes[primary] == TYPE_STR); + assert(conf_key_info[primary].subkey_type == CONF_TYPE_STR); + assert(conf_key_info[primary].value_type == CONF_TYPE_STR); entry->key.primary = primary; entry->key.secondary.s = dupstr(secondary); - entry->value.u.stringval = dupstr(value); + entry->value.u.stringval.str = dupstr(value); + entry->value.u.stringval.utf8 = false; conf_insert(conf, entry); } @@ -467,8 +530,8 @@ void conf_del_str_str(Conf *conf, int primary, const char *secondary) struct key key; struct conf_entry *entry; - assert(subkeytypes[primary] == TYPE_STR); - assert(valuetypes[primary] == TYPE_STR); + assert(conf_key_info[primary].subkey_type == CONF_TYPE_STR); + assert(conf_key_info[primary].value_type == CONF_TYPE_STR); key.primary = primary; key.secondary.s = (char *)secondary; entry = find234(conf->tree, &key, NULL); @@ -482,8 +545,8 @@ void conf_set_filename(Conf *conf, int primary, const Filename *value) { struct conf_entry *entry = snew(struct conf_entry); - assert(subkeytypes[primary] == TYPE_NONE); - assert(valuetypes[primary] == TYPE_FILENAME); + assert(conf_key_info[primary].subkey_type == CONF_TYPE_NONE); + assert(conf_key_info[primary].value_type == CONF_TYPE_FILENAME); entry->key.primary = primary; entry->value.u.fileval = filename_copy(value); conf_insert(conf, entry); @@ -493,8 +556,8 @@ void conf_set_fontspec(Conf *conf, int primary, const FontSpec *value) { struct conf_entry *entry = snew(struct conf_entry); - assert(subkeytypes[primary] == TYPE_NONE); - assert(valuetypes[primary] == TYPE_FONT); + assert(conf_key_info[primary].subkey_type == CONF_TYPE_NONE); + assert(conf_key_info[primary].value_type == CONF_TYPE_FONT); entry->key.primary = primary; entry->value.u.fontval = fontspec_copy(value); conf_insert(conf, entry); @@ -508,28 +571,33 @@ void conf_serialise(BinarySink *bs, Conf *conf) for (i = 0; (entry = index234(conf->tree, i)) != NULL; i++) { put_uint32(bs, entry->key.primary); - switch (subkeytypes[entry->key.primary]) { - case TYPE_INT: + switch (conf_key_info[entry->key.primary].subkey_type) { + case CONF_TYPE_INT: put_uint32(bs, entry->key.secondary.i); break; - case TYPE_STR: + case CONF_TYPE_STR: put_asciz(bs, entry->key.secondary.s); break; } - switch (valuetypes[entry->key.primary]) { - case TYPE_BOOL: + switch (conf_key_info[entry->key.primary].value_type) { + case CONF_TYPE_BOOL: put_bool(bs, entry->value.u.boolval); break; - case TYPE_INT: + case CONF_TYPE_INT: put_uint32(bs, entry->value.u.intval); break; - case TYPE_STR: - put_asciz(bs, entry->value.u.stringval); + case CONF_TYPE_STR: + case CONF_TYPE_UTF8: + put_asciz(bs, entry->value.u.stringval.str); + break; + case CONF_TYPE_STR_AMBI: + put_asciz(bs, entry->value.u.stringval.str); + put_bool(bs, entry->value.u.stringval.utf8); break; - case TYPE_FILENAME: + case CONF_TYPE_FILENAME: filename_serialise(bs, entry->value.u.fileval); break; - case TYPE_FONT: + case CONF_TYPE_FONT: fontspec_serialise(bs, entry->value.u.fontval); break; } @@ -556,29 +624,38 @@ bool conf_deserialise(Conf *conf, BinarySource *src) entry = snew(struct conf_entry); entry->key.primary = primary; - switch (subkeytypes[entry->key.primary]) { - case TYPE_INT: + switch (conf_key_info[entry->key.primary].subkey_type) { + case CONF_TYPE_INT: entry->key.secondary.i = toint(get_uint32(src)); break; - case TYPE_STR: + case CONF_TYPE_STR: entry->key.secondary.s = dupstr(get_asciz(src)); break; } - switch (valuetypes[entry->key.primary]) { - case TYPE_BOOL: + switch (conf_key_info[entry->key.primary].value_type) { + case CONF_TYPE_BOOL: entry->value.u.boolval = get_bool(src); break; - case TYPE_INT: + case CONF_TYPE_INT: entry->value.u.intval = toint(get_uint32(src)); break; - case TYPE_STR: - entry->value.u.stringval = dupstr(get_asciz(src)); + case CONF_TYPE_STR: + entry->value.u.stringval.str = dupstr(get_asciz(src)); + entry->value.u.stringval.utf8 = false; + break; + case CONF_TYPE_UTF8: + entry->value.u.stringval.str = dupstr(get_asciz(src)); + entry->value.u.stringval.utf8 = true; + break; + case CONF_TYPE_STR_AMBI: + entry->value.u.stringval.str = dupstr(get_asciz(src)); + entry->value.u.stringval.utf8 = get_bool(src); break; - case TYPE_FILENAME: + case CONF_TYPE_FILENAME: entry->value.u.fileval = filename_deserialise(src); break; - case TYPE_FONT: + case CONF_TYPE_FONT: entry->value.u.fontval = fontspec_deserialise(src); break; } diff --git a/utils/conf_data.c b/utils/conf_data.c new file mode 100644 index 00000000..74bf60cd --- /dev/null +++ b/utils/conf_data.c @@ -0,0 +1,53 @@ +#include "putty.h" + +#define CONF_ENUM(name, ...) \ + static const ConfSaveEnumValue conf_enum_values_##name[] = { \ + __VA_ARGS__ \ + }; const ConfSaveEnumType conf_enum_##name = { \ + .values = conf_enum_values_##name, \ + .nvalues = lenof(conf_enum_values_##name), \ + }; + +#define VALUE(eval, sval) { eval, sval, false } +#define VALUE_OBSOLETE(eval, sval) { eval, sval, true } + +#include "conf-enums.h" + +bool conf_enum_map_to_storage(const ConfSaveEnumType *etype, + int confval, int *storageval_out) +{ + for (size_t i = 0; i < etype->nvalues; i++) + if (!etype->values[i].obsolete && + etype->values[i].confval == confval) { + *storageval_out = etype->values[i].storageval; + return true; + } + return false; +} + +bool conf_enum_map_from_storage(const ConfSaveEnumType *etype, + int storageval, int *confval_out) +{ + for (size_t i = 0; i < etype->nvalues; i++) + if (etype->values[i].storageval == storageval) { + *confval_out = etype->values[i].confval; + return true; + } + return false; +} + +#define CONF_OPTION(id, ...) { __VA_ARGS__ }, +#define VALUE_TYPE(x) .value_type = CONF_TYPE_ ## x +#define SUBKEY_TYPE(x) .subkey_type = CONF_TYPE_ ## x +#define DEFAULT_INT(x) .default_value.ival = x +#define DEFAULT_STR(x) .default_value.sval = x +#define DEFAULT_BOOL(x) .default_value.bval = x +#define SAVE_KEYWORD(x) .save_keyword = x +#define STORAGE_ENUM(x) .storage_enum = &conf_enum_ ## x +#define SAVE_CUSTOM .save_custom = true +#define LOAD_CUSTOM .load_custom = true +#define NOT_SAVED .not_saved = true + +const ConfKeyInfo conf_key_info[] = { + #include "conf.h" +}; diff --git a/utils/conf_debug.c b/utils/conf_debug.c new file mode 100644 index 00000000..6f2f6c33 --- /dev/null +++ b/utils/conf_debug.c @@ -0,0 +1,15 @@ +#include "putty.h" + +#define CONF_OPTION(id, ...) "CONF_" #id, + +static const char *const conf_debug_identifiers[] = { + #include "conf.h" +}; + +const char *conf_id(int key) +{ + size_t i = key; + if (i < lenof(conf_debug_identifiers)) + return conf_debug_identifiers[i]; + return "CONF_!outofrange!"; +} diff --git a/utils/decode_utf8.c b/utils/decode_utf8.c index c8dbec79..38b2001a 100644 --- a/utils/decode_utf8.c +++ b/utils/decode_utf8.c @@ -5,15 +5,23 @@ #include "putty.h" #include "misc.h" -unsigned long decode_utf8(const char **utf8) +unsigned decode_utf8(BinarySource *src, DecodeUTF8Failure *err) { - unsigned char c = (unsigned char)*(*utf8)++; + /* Permit user to pass NULL as the err pointer */ + DecodeUTF8Failure dummy; + if (!err) err = &dummy; + + /* If the source has no byte available, this will return 0, which + * we'll return immediately and is a reasonable error return anyway */ + unsigned char c = get_byte(src); /* One-byte cases. */ if (c < 0x80) { + *err = DUTF8_SUCCESS; return c; } else if (c < 0xC0) { - return 0xFFFD; /* spurious continuation byte */ + *err = DUTF8_SPURIOUS_CONTINUATION; + return 0xFFFD; } unsigned long wc, min; @@ -29,49 +37,81 @@ unsigned long decode_utf8(const char **utf8) } else if (c < 0xFE) { wc = c & 0x01; ncont = 5; min = 0x4000000; } else { - return 0xFFFD; /* FE or FF illegal bytes */ + *err = DUTF8_ILLEGAL_BYTE; /* FE or FF */ + return 0xFFFD; } while (ncont-- > 0) { - unsigned char cont = (unsigned char)**utf8; - if (!(0x80 <= cont && cont < 0xC0)) - return 0xFFFD; /* short sequence */ - (*utf8)++; + if (!get_avail(src)) { + *err = DUTF8_E_OUT_OF_DATA; + return 0xFFFD; + } + unsigned char cont = get_byte(src); + if (!(0x80 <= cont && cont < 0xC0)) { + BinarySource_REWIND_TO(src, src->pos - 1); + *err = DUTF8_TRUNCATED_SEQUENCE; + return 0xFFFD; + } wc = (wc << 6) | (cont & 0x3F); } - if (wc < min) - return 0xFFFD; /* overlong encoding */ - if (0xD800 <= wc && wc < 0xE000) - return 0xFFFD; /* UTF-8 encoding of surrogate */ - if (wc > 0x10FFFF) + if (wc < min) { + *err = DUTF8_OVERLONG_ENCODING; + return 0xFFFD; + } + if (0xD800 <= wc && wc < 0xE000) { + *err = DUTF8_ENCODED_SURROGATE; + return 0xFFFD; + } + if (wc > 0x10FFFF) { + *err = DUTF8_CODE_POINT_TOO_BIG; return 0xFFFD; /* outside Unicode range */ + } + *err = DUTF8_SUCCESS; return wc; } +const char *const decode_utf8_error_strings[DUTF8_N_FAILURE_CODES] = { + #define MSG_ENTRY(sym, string) string, + DECODE_UTF8_FAILURE_LIST(MSG_ENTRY) + #undef MSG_ENTRY +}; + #ifdef TEST #include -bool dotest(const char *file, int line, const char *input, +void out_of_memory(void) +{ + fprintf(stderr, "out of memory!\n"); + exit(2); +} + +static const char *const decode_utf8_error_syms[DUTF8_N_FAILURE_CODES] = { + #define SYM_ENTRY(sym, string) #sym, + DECODE_UTF8_FAILURE_LIST(SYM_ENTRY) + #undef SYM_ENTRY +}; + +bool dotest(const char *file, int line, const char *input, size_t ninput, const unsigned long *chars, size_t nchars) { - const char *start = input; - const char *end = input + strlen(input) + 1; + BinarySource src[1]; + BinarySource_BARE_INIT(src, input, ninput); size_t noutput = 0; printf("%s:%d: test start\n", file, line); - while (input < end) { - const char *before = input; - unsigned long wc = decode_utf8(&input); + while (get_avail(src)) { + size_t before = src->pos; + DecodeUTF8Failure err; + unsigned long wc = decode_utf8(src, &err); - printf("%s:%d in+%"SIZEu" out+%"SIZEu":", - file, line, (size_t)(before-start), noutput); - while (before < input) - printf(" %02x", (unsigned)(unsigned char)(*before++)); - printf(" -> U-%08lx\n", wc); + printf("%s:%d in+%"SIZEu" out+%"SIZEu":", file, line, before, noutput); + while (before < src->pos) + printf(" %02x", (unsigned)(unsigned char)(input[before++])); + printf(" -> U-%08lx %s\n", wc, decode_utf8_error_syms[err]); if (noutput >= nchars) { printf("%s:%d: FAIL: expected no further output\n", file, line); @@ -85,6 +125,22 @@ bool dotest(const char *file, int line, const char *input, } noutput++; + + DecodeUTF8Failure expected_err; + if (wc == 0xFFFD) { + /* In the 'chars' array, any occurrence of 0xFFFD is followed + * by the expected error code */ + assert(noutput < nchars && "bad test data"); + expected_err = chars[noutput++]; + } else { + /* Expect success status to go with any non-FFFD character */ + expected_err = DUTF8_SUCCESS; + } + if (err != expected_err) { + printf("%s:%d: FAIL: expected %s\n", file, line, + decode_utf8_error_syms[expected_err]); + return false; + } } if (noutput < nchars) { @@ -97,9 +153,10 @@ bool dotest(const char *file, int line, const char *input, } #define DOTEST(input, ...) do { \ - static const unsigned long chars[] = { __VA_ARGS__, 0 }; \ + static const unsigned long chars[] = { __VA_ARGS__ }; \ ntest++; \ - if (dotest(__FILE__, __LINE__, input, chars, lenof(chars))) \ + if (dotest(__FILE__, __LINE__, input, sizeof(input)-1, \ + chars, lenof(chars))) \ npass++; \ } while (0) @@ -110,61 +167,90 @@ int main(void) DOTEST("\xCE\xBA\xE1\xBD\xB9\xCF\x83\xCE\xBC\xCE\xB5", 0x03BA, 0x1F79, 0x03C3, 0x03BC, 0x03B5); - /* First sequence of each length (not counting NUL, which is - * tested anyway by the string-termination handling in every test) */ + /* First sequence of each length */ + DOTEST("\x00", 0x0000); DOTEST("\xC2\x80", 0x0080); DOTEST("\xE0\xA0\x80", 0x0800); DOTEST("\xF0\x90\x80\x80", 0x00010000); - DOTEST("\xF8\x88\x80\x80\x80", 0xFFFD); /* would be 0x00200000 */ - DOTEST("\xFC\x84\x80\x80\x80\x80", 0xFFFD); /* would be 0x04000000 */ + DOTEST("\xF8\x88\x80\x80\x80", + 0xFFFD, DUTF8_CODE_POINT_TOO_BIG); /* would be 0x00200000 */ + DOTEST("\xFC\x84\x80\x80\x80\x80", + 0xFFFD, DUTF8_CODE_POINT_TOO_BIG); /* would be 0x04000000 */ /* Last sequence of each length */ DOTEST("\x7F", 0x007F); DOTEST("\xDF\xBF", 0x07FF); DOTEST("\xEF\xBF\xBF", 0xFFFF); - DOTEST("\xF7\xBF\xBF\xBF", 0xFFFD); /* would be 0x001FFFFF */ - DOTEST("\xFB\xBF\xBF\xBF\xBF", 0xFFFD); /* would be 0x03FFFFFF */ - DOTEST("\xFD\xBF\xBF\xBF\xBF\xBF", 0xFFFD); /* would be 0x7FFFFFFF */ + DOTEST("\xF7\xBF\xBF\xBF", + 0xFFFD, DUTF8_CODE_POINT_TOO_BIG); /* would be 0x001FFFFF */ + DOTEST("\xFB\xBF\xBF\xBF\xBF", + 0xFFFD, DUTF8_CODE_POINT_TOO_BIG); /* would be 0x03FFFFFF */ + DOTEST("\xFD\xBF\xBF\xBF\xBF\xBF", + 0xFFFD, DUTF8_CODE_POINT_TOO_BIG); /* would be 0x7FFFFFFF */ /* Endpoints of the surrogate range */ DOTEST("\xED\x9F\xBF", 0xD7FF); - DOTEST("\xED\xA0\x00", 0xFFFD); /* would be 0xD800 */ - DOTEST("\xED\xBF\xBF", 0xFFFD); /* would be 0xDFFF */ + DOTEST("\xED\xA0\x80", 0xFFFD, DUTF8_ENCODED_SURROGATE); /* 0xD800 */ + DOTEST("\xED\xBF\xBF", 0xFFFD, DUTF8_ENCODED_SURROGATE); /* 0xDFFF */ DOTEST("\xEE\x80\x80", 0xE000); /* REPLACEMENT CHARACTER itself */ - DOTEST("\xEF\xBF\xBD", 0xFFFD); + DOTEST("\xEF\xBF\xBD", 0xFFFD, DUTF8_SUCCESS); /* FFFD but no error! */ /* Endpoints of the legal Unicode range */ DOTEST("\xF4\x8F\xBF\xBF", 0x0010FFFF); - DOTEST("\xF4\x90\x80\x80", 0xFFFD); /* would be 0x00110000 */ + DOTEST("\xF4\x90\x80\x80", 0xFFFD, + DUTF8_CODE_POINT_TOO_BIG); /* would be 0x00110000 */ /* Spurious continuation bytes, each shown as a separate failure */ DOTEST("\x80 \x81\x82 \xBD\xBE\xBF", - 0xFFFD, 0x0020, 0xFFFD, 0xFFFD, 0x0020, 0xFFFD, 0xFFFD, 0xFFFD); - - /* Truncated sequences, each shown as just one failure */ + 0xFFFD, DUTF8_SPURIOUS_CONTINUATION, + 0x0020, + 0xFFFD, DUTF8_SPURIOUS_CONTINUATION, + 0xFFFD, DUTF8_SPURIOUS_CONTINUATION, + 0x0020, + 0xFFFD, DUTF8_SPURIOUS_CONTINUATION, + 0xFFFD, DUTF8_SPURIOUS_CONTINUATION, + 0xFFFD, DUTF8_SPURIOUS_CONTINUATION); + + /* Truncated sequences, each shown as just one failure. The last + * one gets a different error code because the sequence is + * interrupted by the end of the string instead of another + * character, so that if the string were a prefix of a longer + * chunk of data then that would not _necessarily_ indicate an + * error */ DOTEST("\xC2\xE0\xA0\xF0\x90\x80\xF8\x88\x80\x80\xFC\x84\x80\x80\x80", - 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD); + 0xFFFD, DUTF8_TRUNCATED_SEQUENCE, + 0xFFFD, DUTF8_TRUNCATED_SEQUENCE, + 0xFFFD, DUTF8_TRUNCATED_SEQUENCE, + 0xFFFD, DUTF8_TRUNCATED_SEQUENCE, + 0xFFFD, DUTF8_E_OUT_OF_DATA); DOTEST("\xC2 \xE0\xA0 \xF0\x90\x80 \xF8\x88\x80\x80 \xFC\x84\x80\x80\x80", - 0xFFFD, 0x0020, 0xFFFD, 0x0020, 0xFFFD, 0x0020, 0xFFFD, 0x0020, - 0xFFFD); + 0xFFFD, DUTF8_TRUNCATED_SEQUENCE, + 0x0020, + 0xFFFD, DUTF8_TRUNCATED_SEQUENCE, + 0x0020, + 0xFFFD, DUTF8_TRUNCATED_SEQUENCE, + 0x0020, + 0xFFFD, DUTF8_TRUNCATED_SEQUENCE, + 0x0020, + 0xFFFD, DUTF8_E_OUT_OF_DATA); /* Illegal bytes */ - DOTEST("\xFE\xFF", 0xFFFD, 0xFFFD); + DOTEST("\xFE\xFF", 0xFFFD, DUTF8_ILLEGAL_BYTE, 0xFFFD, DUTF8_ILLEGAL_BYTE); /* Overlong sequences */ - DOTEST("\xC1\xBF", 0xFFFD); - DOTEST("\xE0\x9F\xBF", 0xFFFD); - DOTEST("\xF0\x8F\xBF\xBF", 0xFFFD); - DOTEST("\xF8\x87\xBF\xBF\xBF", 0xFFFD); - DOTEST("\xFC\x83\xBF\xBF\xBF\xBF", 0xFFFD); - - DOTEST("\xC0\x80", 0xFFFD); - DOTEST("\xE0\x80\x80", 0xFFFD); - DOTEST("\xF0\x80\x80\x80", 0xFFFD); - DOTEST("\xF8\x80\x80\x80\x80", 0xFFFD); - DOTEST("\xFC\x80\x80\x80\x80\x80", 0xFFFD); + DOTEST("\xC1\xBF", 0xFFFD, DUTF8_OVERLONG_ENCODING); + DOTEST("\xE0\x9F\xBF", 0xFFFD, DUTF8_OVERLONG_ENCODING); + DOTEST("\xF0\x8F\xBF\xBF", 0xFFFD, DUTF8_OVERLONG_ENCODING); + DOTEST("\xF8\x87\xBF\xBF\xBF", 0xFFFD, DUTF8_OVERLONG_ENCODING); + DOTEST("\xFC\x83\xBF\xBF\xBF\xBF", 0xFFFD, DUTF8_OVERLONG_ENCODING); + + DOTEST("\xC0\x80", 0xFFFD, DUTF8_OVERLONG_ENCODING); + DOTEST("\xE0\x80\x80", 0xFFFD, DUTF8_OVERLONG_ENCODING); + DOTEST("\xF0\x80\x80\x80", 0xFFFD, DUTF8_OVERLONG_ENCODING); + DOTEST("\xF8\x80\x80\x80\x80", 0xFFFD, DUTF8_OVERLONG_ENCODING); + DOTEST("\xFC\x80\x80\x80\x80\x80", 0xFFFD, DUTF8_OVERLONG_ENCODING); printf("%d tests %d passed", ntest, npass); if (npass < ntest) { diff --git a/utils/decode_utf8_to_wchar.c b/utils/decode_utf8_to_wchar.c index 97a83218..b21cbb5f 100644 --- a/utils/decode_utf8_to_wchar.c +++ b/utils/decode_utf8_to_wchar.c @@ -5,10 +5,11 @@ #include "putty.h" #include "misc.h" -size_t decode_utf8_to_wchar(const char **utf8, wchar_t *out) +size_t decode_utf8_to_wchar(BinarySource *src, wchar_t *out, + DecodeUTF8Failure *err) { size_t outlen = 0; - unsigned wc = decode_utf8(utf8); + unsigned wc = decode_utf8(src, err); if (sizeof(wchar_t) > 2 || wc < 0x10000) { out[outlen++] = wc; } else { diff --git a/utils/decode_utf8_to_wide_string.c b/utils/decode_utf8_to_wide_string.c new file mode 100644 index 00000000..1ab9a8de --- /dev/null +++ b/utils/decode_utf8_to_wide_string.c @@ -0,0 +1,35 @@ +/* + * Decode a string of UTF-8 to a wchar_t string. + */ + +#include "misc.h" + +wchar_t *decode_utf8_to_wide_string(const char *s) +{ + wchar_t *ws = NULL; + size_t wlen = 0, wsize = 0; + + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, ptrlen_from_asciz(s)); + + while (get_avail(src) > 0) { + /* + * decode_utf8_to_wchar might emit up to 2 wchar_t if wchar_t + * is 16 bits (because of UTF-16 surrogates), but will emit at + * most one if wchar_t is 32-bit + */ + sgrowarrayn(ws, wsize, wlen, 1 + (sizeof(wchar_t) < 4)); + + /* We ignore 'err': if it is set, then the character decode + * function will have emitted U+FFFD REPLACEMENT CHARACTER, + * which is what we'd have done in response anyway. */ + DecodeUTF8Failure err; + wlen += decode_utf8_to_wchar(src, ws + wlen, &err); + } + + /* Reallocate to the final size and append the trailing NUL */ + ws = sresize(ws, wlen + 1, wchar_t); + ws[wlen] = L'\0'; + + return ws; +} diff --git a/utils/dup_mb_to_wc.c b/utils/dup_mb_to_wc.c index c3f17aba..0738ed27 100644 --- a/utils/dup_mb_to_wc.c +++ b/utils/dup_mb_to_wc.c @@ -2,28 +2,29 @@ * dup_mb_to_wc: memory-allocating wrapper on mb_to_wc. * * Also dup_mb_to_wc_c: same but you already know the length of the - * string. + * string, and you get told the length of the returned wide string. + * (But it's still NUL-terminated, for convenience.) */ #include "putty.h" #include "misc.h" -wchar_t *dup_mb_to_wc_c(int codepage, int flags, const char *string, int len) +wchar_t *dup_mb_to_wc_c(int codepage, const char *string, + size_t inlen, size_t *outlen_p) { - int mult; - for (mult = 1 ;; mult++) { - wchar_t *ret = snewn(mult*len + 2, wchar_t); - int outlen; - outlen = mb_to_wc(codepage, flags, string, len, ret, mult*len + 1); - if (outlen < mult*len+1) { - ret[outlen] = L'\0'; - return ret; - } - sfree(ret); - } + strbuf *sb = strbuf_new(); + put_mb_to_wc(sb, codepage, string, inlen); + if (outlen_p) + *outlen_p = sb->len / sizeof(wchar_t); + + /* Append a trailing L'\0'. For this we only need to write one + * byte _fewer_ than sizeof(wchar_t), because strbuf will append a + * byte '\0' for us. */ + put_padding(sb, sizeof(wchar_t) - 1, 0); + return (wchar_t *)strbuf_to_str(sb); } -wchar_t *dup_mb_to_wc(int codepage, int flags, const char *string) +wchar_t *dup_mb_to_wc(int codepage, const char *string) { - return dup_mb_to_wc_c(codepage, flags, string, strlen(string)); + return dup_mb_to_wc_c(codepage, string, strlen(string), NULL); } diff --git a/utils/dup_wc_to_mb.c b/utils/dup_wc_to_mb.c index 36088196..3259d22e 100644 --- a/utils/dup_wc_to_mb.c +++ b/utils/dup_wc_to_mb.c @@ -2,7 +2,8 @@ * dup_wc_to_mb: memory-allocating wrapper on wc_to_mb. * * Also dup_wc_to_mb_c: same but you already know the length of the - * string. + * wide string, and you get told the length of the returned string. + * (But it's still NUL-terminated, for convenience.). */ #include @@ -10,29 +11,18 @@ #include "putty.h" #include "misc.h" -char *dup_wc_to_mb_c(int codepage, int flags, const wchar_t *string, int len, - const char *defchr) +char *dup_wc_to_mb_c(int codepage, const wchar_t *string, + size_t inlen, const char *defchr, size_t *outlen_p) { - size_t outsize = len+1; - char *out = snewn(outsize, char); - - while (true) { - size_t outlen = wc_to_mb(codepage, flags, string, len, out, outsize, - defchr); - /* We can only be sure we've consumed the whole input if the - * output is not within a multibyte-character-length of the - * end of the buffer! */ - if (outlen < outsize && outsize - outlen > MB_LEN_MAX) { - out[outlen] = '\0'; - return out; - } - - sgrowarray(out, outsize, outsize); - } + strbuf *sb = strbuf_new(); + put_wc_to_mb(sb, codepage, string, inlen, defchr); + if (outlen_p) + *outlen_p = sb->len; + return strbuf_to_str(sb); } -char *dup_wc_to_mb(int codepage, int flags, const wchar_t *string, +char *dup_wc_to_mb(int codepage, const wchar_t *string, const char *defchr) { - return dup_wc_to_mb_c(codepage, flags, string, wcslen(string), defchr); + return dup_wc_to_mb_c(codepage, string, wcslen(string), defchr, NULL); } diff --git a/utils/dupwcs.c b/utils/dupwcs.c new file mode 100644 index 00000000..11fba330 --- /dev/null +++ b/utils/dupwcs.c @@ -0,0 +1,19 @@ +/* + * Allocate a duplicate of a NUL-terminated wchar_t string. + */ + +#include + +#include "defs.h" +#include "misc.h" + +wchar_t *dupwcs(const wchar_t *s) +{ + wchar_t *p = NULL; + if (s) { + int len = wcslen(s); + p = snewn(len + 1, wchar_t); + wcscpy(p, s); + } + return p; +} diff --git a/utils/encode_utf8.c b/utils/encode_utf8.c index 731ab925..d24f0951 100644 --- a/utils/encode_utf8.c +++ b/utils/encode_utf8.c @@ -5,25 +5,22 @@ #include "defs.h" #include "misc.h" -size_t encode_utf8(void *output, unsigned long ch) +void BinarySink_put_utf8_char(BinarySink *output, unsigned ch) { - unsigned char *start = (unsigned char *)output, *p = start; - if (ch < 0x80) { - *p++ = ch; + put_byte(output, ch); } else if (ch < 0x800) { - *p++ = 0xC0 | (ch >> 6); - *p++ = 0x80 | (ch & 0x3F); + put_byte(output, 0xC0 | (ch >> 6)); + put_byte(output, 0x80 | (ch & 0x3F)); } else if (ch < 0x10000) { - *p++ = 0xE0 | (ch >> 12); - *p++ = 0x80 | ((ch >> 6) & 0x3F); - *p++ = 0x80 | (ch & 0x3F); + put_byte(output, 0xE0 | (ch >> 12)); + put_byte(output, 0x80 | ((ch >> 6) & 0x3F)); + put_byte(output, 0x80 | (ch & 0x3F)); } else { assert(ch <= 0x10FFFF); - *p++ = 0xF0 | (ch >> 18); - *p++ = 0x80 | ((ch >> 12) & 0x3F); - *p++ = 0x80 | ((ch >> 6) & 0x3F); - *p++ = 0x80 | (ch & 0x3F); + put_byte(output, 0xF0 | (ch >> 18)); + put_byte(output, 0x80 | ((ch >> 12) & 0x3F)); + put_byte(output, 0x80 | ((ch >> 6) & 0x3F)); + put_byte(output, 0x80 | (ch & 0x3F)); } - return p - start; } diff --git a/utils/encode_wide_string_as_utf8.c b/utils/encode_wide_string_as_utf8.c index 870903d5..f5782888 100644 --- a/utils/encode_wide_string_as_utf8.c +++ b/utils/encode_wide_string_as_utf8.c @@ -17,9 +17,7 @@ char *encode_wide_string_as_utf8(const wchar_t *ws) } else if (IS_SURROGATE(ch)) { ch = 0xfffd; /* illegal UTF-16 -> REPLACEMENT CHARACTER */ } - char utf8[6]; - size_t size = encode_utf8(utf8, ch); - put_data(sb, utf8, size); + put_utf8_char(sb, ch); } return strbuf_to_str(sb); } diff --git a/utils/log_proxy_stderr.c b/utils/log_proxy_stderr.c index 2a84173a..74d806ee 100644 --- a/utils/log_proxy_stderr.c +++ b/utils/log_proxy_stderr.c @@ -15,7 +15,7 @@ void psb_set_prefix(ProxyStderrBuf *psb, const char *prefix) psb->prefix = prefix; } -void log_proxy_stderr(Plug *plug, ProxyStderrBuf *psb, +void log_proxy_stderr(Plug *plug, Socket *sock, ProxyStderrBuf *psb, const void *vdata, size_t len) { const char *data = (const char *)vdata; @@ -65,7 +65,7 @@ void log_proxy_stderr(Plug *plug, ProxyStderrBuf *psb, endpos--; char *msg = dupprintf( "%s: %.*s", psb->prefix, (int)(endpos - pos), psb->buf + pos); - plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, msg, 0); + plug_log(plug, sock, PLUGLOG_PROXY_MSG, NULL, 0, msg, 0); sfree(msg); pos = nlpos - psb->buf + 1; @@ -81,7 +81,7 @@ void log_proxy_stderr(Plug *plug, ProxyStderrBuf *psb, char *msg = dupprintf( "%s (partial line): %.*s", psb->prefix, (int)psb->size, psb->buf); - plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, msg, 0); + plug_log(plug, sock, PLUGLOG_PROXY_MSG, NULL, 0, msg, 0); sfree(msg); pos = psb->size = 0; diff --git a/utils/logeventf.c b/utils/logeventf.c new file mode 100644 index 00000000..ba162612 --- /dev/null +++ b/utils/logeventf.c @@ -0,0 +1,32 @@ +/* + * Helpful wrapper functions around the raw logevent(). + * + * This source file lives in 'utils' because it's conceptually a + * convenience utility rather than core functionality. But it can't + * live in the utils _library_, because then it might refer to + * logevent() in an earlier library after Unix ld had already finished + * searching that library, and cause a link failure. So it must live + * alongside logging.c. + */ + +#include "putty.h" + +void logevent_and_free(LogContext *ctx, char *event) +{ + logevent(ctx, event); + sfree(event); +} + +void logeventvf(LogContext *ctx, const char *fmt, va_list ap) +{ + logevent_and_free(ctx, dupvprintf(fmt, ap)); +} + +void logeventf(LogContext *ctx, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + logeventvf(ctx, fmt, ap); + va_end(ap); +} diff --git a/utils/marshal.c b/utils/marshal.c index 534ecf50..ec6a5806 100644 --- a/utils/marshal.c +++ b/utils/marshal.c @@ -336,3 +336,23 @@ void bufchain_sink_init(bufchain_sink *sink, bufchain *ch) sink->ch = ch; BinarySink_INIT(sink, bufchain_sink_write); } + +static void buffer_sink_write(BinarySink *bs, const void *data, size_t len) +{ + buffer_sink *sink = BinarySink_DOWNCAST(bs, buffer_sink); + if (len > sink->space) { + len = sink->space; + sink->overflowed = true; + } + memcpy(sink->out, data, len); + sink->space -= len; + sink->out += len; +} + +void buffer_sink_init(buffer_sink *sink, void *buffer, size_t len) +{ + sink->out = buffer; + sink->space = len; + sink->overflowed = false; + BinarySink_INIT(sink, buffer_sink_write); +} diff --git a/utils/prompts.c b/utils/prompts.c index d0823334..2b70133a 100644 --- a/utils/prompts.c +++ b/utils/prompts.c @@ -18,6 +18,7 @@ prompts_t *new_prompts(void) p->callback = NULL; p->callback_ctx = NULL; p->ldisc_ptr_to_us = NULL; + p->utf8 = false; return p; } diff --git a/utils/seat_nonfatal.c b/utils/seat_nonfatal.c new file mode 100644 index 00000000..d21e4c17 --- /dev/null +++ b/utils/seat_nonfatal.c @@ -0,0 +1,19 @@ +/* + * Wrapper function for the nonfatal() method of a Seat, + * providing printf-style formatting. + */ + +#include "putty.h" + +void seat_nonfatal(Seat *seat, const char *fmt, ...) +{ + va_list ap; + char *msg; + + va_start(ap, fmt); + msg = dupvprintf(fmt, ap); + va_end(ap); + + seat->vt->nonfatal(seat, msg); + sfree(msg); +} diff --git a/utils/sk_free_peer_info.c b/utils/sk_free_peer_info.c index a66e09d7..dab68590 100644 --- a/utils/sk_free_peer_info.c +++ b/utils/sk_free_peer_info.c @@ -1,14 +1,14 @@ /* - * Free a SocketPeerInfo, and everything that dangles off it. + * Free a SocketEndpointInfo, and everything that dangles off it. */ #include "putty.h" -void sk_free_peer_info(SocketPeerInfo *pi) +void sk_free_endpoint_info(SocketEndpointInfo *ei) { - if (pi) { - sfree((char *)pi->addr_text); - sfree((char *)pi->log_text); - sfree(pi); + if (ei) { + sfree((char *)ei->addr_text); + sfree((char *)ei->log_text); + sfree(ei); } } diff --git a/utils/stripctrl.c b/utils/stripctrl.c index d723a079..16a8dce2 100644 --- a/utils/stripctrl.c +++ b/utils/stripctrl.c @@ -217,9 +217,6 @@ static inline void stripctrl_term_put_wc( if (prefix.len) put_datapl(scc->bs_out, prefix); - char outbuf[6]; - size_t produced; - /* * The Terminal implementation encodes 7-bit ASCII characters in * UTF-8 mode, and all printing characters in non-UTF-8 (i.e. @@ -232,14 +229,10 @@ static inline void stripctrl_term_put_wc( wc &= 0xFF; if (in_utf(scc->term)) { - produced = encode_utf8(outbuf, wc); + put_utf8_char(scc->bs_out, wc); } else { - outbuf[0] = wc; - produced = 1; + put_byte(scc->bs_out, wc); } - - if (produced > 0) - put_data(scc->bs_out, outbuf, produced); } static inline size_t stripctrl_locale_try_consume( diff --git a/utils/tempseat.c b/utils/tempseat.c index 39e641f9..4e4e2b13 100644 --- a/utils/tempseat.c +++ b/utils/tempseat.c @@ -288,6 +288,17 @@ static void tempseat_connection_fatal(Seat *seat, const char *message) unreachable("connection_fatal should never be called on TempSeat"); } +static void tempseat_nonfatal(Seat *seat, const char *message) +{ + /* + * Non-fatal errors specific to a Seat should also not occur, + * because those will be for things like I/O errors writing the + * host key collection, and a backend's not _doing_ that when we + * haven't connected it to the host yet. + */ + unreachable("nonfatal should never be called on TempSeat"); +} + static bool tempseat_eof(Seat *seat) { /* @@ -327,6 +338,7 @@ static const struct SeatVtable tempseat_vt = { .notify_remote_exit = tempseat_notify_remote_exit, .notify_remote_disconnect = tempseat_notify_remote_disconnect, .connection_fatal = tempseat_connection_fatal, + .nonfatal = tempseat_nonfatal, .update_specials_menu = tempseat_update_specials_menu, .get_ttymode = tempseat_get_ttymode, .set_busy_status = tempseat_set_busy_status, diff --git a/utils/tree234.c b/utils/tree234.c index 6440f871..463f218c 100644 --- a/utils/tree234.c +++ b/utils/tree234.c @@ -63,11 +63,11 @@ struct node234_Tag { */ tree234 *newtree234(cmpfn234 cmp) { - tree234 *ret = snew(tree234); - LOG(("created tree %p\n", ret)); - ret->root = NULL; - ret->cmp = cmp; - return ret; + tree234 *t = snew(tree234); + LOG(("created tree %p\n", t)); + t->root = NULL; + t->cmp = cmp; + return t; } /* diff --git a/utils/unicode-known.c b/utils/unicode-known.c new file mode 100644 index 00000000..01b9b8a4 --- /dev/null +++ b/utils/unicode-known.c @@ -0,0 +1,59 @@ +/* + * Check a UTF-8 string to ensure every character in it is part of the + * version of Unicode that we understand. + * + * (If it isn't, then we don't know what combining properties it has, + * so we can't safely NFC it and rely on the result not changing when + * we later update our Unicode version.) + */ + +#include "misc.h" +#include "unicode/version.h" + +static bool known(unsigned c) +{ + struct range { + unsigned start, end; + }; + static const struct range ranges[] = { + #include "unicode/known_chars.h" + }; + + const struct range *start = ranges, *end = start + lenof(ranges); + + while (end > start) { + const struct range *curr = start + (end-start) / 2; + if (c < curr->start) + end = curr; + else if (c > curr->end) + start = curr + 1; + else + return true; + } + + return false; +}; + +char *utf8_unknown_char(ptrlen input) +{ + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, input); + + for (size_t nchars = 0; get_avail(src); nchars++) { + DecodeUTF8Failure err; + unsigned c = decode_utf8(src, &err); + if (err != DUTF8_SUCCESS) + return dupprintf( + "cannot normalise this string: UTF-8 decoding error " + "at character position %"SIZEu", byte position %"SIZEu": %s", + nchars, src->pos, decode_utf8_error_strings[err]); + if (!known(c)) + return dupprintf( + "cannot stably normalise this string: code point %04X " + "(at character position %"SIZEu", byte position %"SIZEu") " + "is not in Unicode %s", c, nchars, src->pos, + UNICODE_VERSION_SHORT); + } + + return NULL; +} diff --git a/utils/unicode-norm.c b/utils/unicode-norm.c new file mode 100644 index 00000000..af620fd3 --- /dev/null +++ b/utils/unicode-norm.c @@ -0,0 +1,453 @@ +#include +#include +#include "misc.h" + +typedef uint32_t uchar; +typedef int cclass_t; + +/* A local uchar-oriented analogue of strbuf */ +typedef struct ucharbuf { + uchar *buf; + size_t len, size; +} ucharbuf; + +static ucharbuf *ucharbuf_new(void) +{ + ucharbuf *ub = snew(ucharbuf); + ub->buf = NULL; + ub->len = ub->size = 0; + return ub; +} + +static void ucharbuf_append(ucharbuf *ub, uchar c) +{ + /* Use the _nm variant because this is used for passphrases */ + sgrowarray_nm(ub->buf, ub->size, ub->len); + ub->buf[ub->len++] = c; +} + +static void ucharbuf_free(ucharbuf *ub) +{ + if (ub->buf) { + memset(ub->buf, 0, ub->size * sizeof(*ub->buf)); + sfree(ub->buf); + } + sfree(ub); +} + +/* + * Constants relating to the arithmetic decomposition mapping of + * Hangul to jamo, from section 3.12 of Unicode 15.0.0. The following + * constant names match those in the spec. + */ +enum { + SBase = 0xAC00, /* base index for precomposed Hangul */ + LBase = 0x1100, /* base index for L (leading consonant) jamo */ + VBase = 0x1161, /* base index for V (vowel) jamo */ + TBase = 0x11A7, /* base index for T (trailing consonant) jamo */ + LCount = 19, /* number of L jamo */ + VCount = 21, /* number of V jamo */ + TCount = 28, /* number of T jamo, including not having one at all */ + NCount = VCount * TCount, /* number of Hangul for each L jamo */ + SCount = LCount * NCount, /* number of Hangul in total */ +}; + +static cclass_t combining_class(uchar c) +{ + struct range { + uchar start, end; + cclass_t cclass; + }; + static const struct range ranges[] = { + #include "unicode/combining_classes.h" + }; + + const struct range *start = ranges, *end = start + lenof(ranges); + + while (end > start) { + const struct range *curr = start + (end-start) / 2; + if (c < curr->start) + end = curr; + else if (c > curr->end) + start = curr + 1; + else + return curr->cclass; + } + + return 0; +}; + +static unsigned decompose_char(uchar c, uchar *out) +{ + struct decomp { + uchar composed, dec0, dec1; + }; + static const struct decomp decomps[] = { + #include "unicode/canonical_decomp.h" + }; + + if (c - SBase < SCount) { + /* Arithmetically decompose a Hangul character into jamo */ + uchar SIndex = c - SBase; + uchar LIndex = SIndex / NCount; + uchar VIndex = SIndex % NCount / TCount; + uchar TIndex = SIndex % TCount; + + unsigned n = 0; + out[n++] = LBase + LIndex; + out[n++] = VBase + VIndex; + if (TIndex) + out[n++] = TBase + TIndex; + return n; + } + + const struct decomp *start = decomps, *end = start + lenof(decomps); + + while (end > start) { + const struct decomp *curr = start + (end-start) / 2; + if (c < curr->composed) + end = curr; + else if (c > curr->composed) + start = curr + 1; + else { + out[0] = curr->dec0; + if (curr->dec1) { + out[1] = curr->dec1; + return 2; + } else { + return 1; + } + } + } + + return 0; +}; + +static uchar compose_chars(uchar S, uchar C) +{ + struct comp { + uchar dec0, dec1, composed; + }; + static const struct comp comps[] = { + #include "unicode/canonical_comp.h" + }; + + if (S - LBase < LCount && C - VBase < VCount) { + /* Arithmetically compose an L and V jamo into a Hangul LV + * character */ + return SBase + (S - LBase) * NCount + (C - VBase) * TCount; + } + + if (S - SBase < SCount && (S - SBase) % TCount == 0 && + C - TBase < TCount) { + /* Arithmetically compose an LV Hangul character and a T jamo + * into a Hangul LVT character */ + return S + C - TBase; + } + + const struct comp *start = comps, *end = start + lenof(comps); + + while (end > start) { + const struct comp *curr = start + (end-start) / 2; + if (S < curr->dec0) + end = curr; + else if (S > curr->dec0) + start = curr + 1; + else if (C < curr->dec1) + end = curr; + else if (C > curr->dec1) + start = curr + 1; + else + return curr->composed; + } + + return 0; +}; + +/* + * Recursively decompose a sequence of Unicode characters. The output + * is written to 'out', as a sequence of native-byte-order uchar. + */ +static void recursively_decompose(const uchar *str, size_t len, ucharbuf *out) +{ + uchar decomposed[3]; + + while (len-- > 0) { + uchar c = *str++; + unsigned n = decompose_char(c, decomposed); + if (n == 0) { + /* This character is indecomposable */ + ucharbuf_append(out, c); + } else { + /* This character has been decomposed into up to 3 + * characters, so we must now recursively decompose those */ + recursively_decompose(decomposed, n, out); + } + } +} + +/* + * Reorder combining marks according to the Canonical Ordering + * Algorithm (definition D109 in Unicode 15.0.0 section 3.11). + * + * The algorithm is phrased mechanistically, but the essence is: among + * any contiguous sequence of combining marks (that is, characters + * with cclass > 0), sort them by their cclass - but _stably_, i.e. + * breaking ties in cclass by preserving the original order of the + * characters in question. + */ +static void canonical_ordering(uchar *str, size_t len) +{ + for (size_t i = 1; i < len; i++) { + cclass_t cclass = combining_class(str[i]); + if (cclass == 0) + continue; + + size_t j = i; + while (j > 0 && combining_class(str[j-1]) > cclass) { + uchar tmp = str[j-1]; + str[j-1] = str[j]; + str[j] = tmp; + + j--; + } + } +} + +/* + * Canonically recompose characters according to the Canonical + * Composition Algorithm (definition D117 in Unicode 15.0.0 section + * 3.11). + */ +static size_t canonical_composition(uchar *str, size_t len) +{ + const uchar *in = str; + uchar *out = str; + uchar *last_starter = NULL; + cclass_t highest_cclass_between = -1; + + while (len > 0) { + len--; + uchar c = *in++; + cclass_t cclass = combining_class(c); + + if (last_starter && highest_cclass_between < cclass) { + uchar composed = compose_chars(*last_starter, c); + if (composed) { + *last_starter = composed; + continue; + } + } + + if (cclass == 0) { + last_starter = out; + highest_cclass_between = -1; + } else if (cclass > highest_cclass_between) { + highest_cclass_between = cclass; + } + + *out++ = c; + } + + return out - str; +} + +/* + * Render a string into NFD. + */ +static ucharbuf *nfd(ucharbuf *input) +{ + ucharbuf *output = ucharbuf_new(); + + /* + * Definition D118 in Unicode 15.0.0 section 3.11, referring to + * D68 in section 3.7: recursively decompose characters, then + * reorder combining marks. + */ + recursively_decompose(input->buf, input->len, output); + canonical_ordering(output->buf, output->len); + + return output; +} + +/* + * Render a string into NFC. + */ +static ucharbuf *nfc(ucharbuf *input) +{ + /* + * Definition D120 in Unicode 15.0.0 section 3.11: render the + * string into NFD, then apply the canonical composition algorithm. + */ + ucharbuf *output = nfd(input); + output->len = canonical_composition(output->buf, output->len); + + return output; +} + +/* + * Convert a UTF-8 string into NFC, returning it as UTF-8 again. + */ +strbuf *utf8_to_nfc(ptrlen input) +{ + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, input); + + ucharbuf *inbuf = ucharbuf_new(); + while (get_avail(src)) + ucharbuf_append(inbuf, decode_utf8(src, NULL)); + + ucharbuf *outbuf = nfc(inbuf); + + strbuf *output = strbuf_new_nm(); + for (size_t i = 0; i < outbuf->len; i++) + put_utf8_char(output, outbuf->buf[i]); + + ucharbuf_free(inbuf); + ucharbuf_free(outbuf); + + return output; +} + +#ifdef TEST +void out_of_memory(void) +{ + fprintf(stderr, "out of memory!\n"); + exit(2); +} + +static int pass, fail; + +static void subtest(const char *filename, int lineno, const char *subdesc, + char nftype, ucharbuf *input, ucharbuf *expected) +{ + /* + * Convert input into either NFC or NFD, and check it's equal to + * expected + */ + ucharbuf *nf; + switch (nftype) { + case 'C': + nf = nfc(input); + break; + case 'D': + nf = nfd(input); + break; + default: + unreachable("bad nftype"); + } + + if (nf->len == expected->len && !memcmp(nf->buf, expected->buf, nf->len)) { + pass++; + } else { + printf("%s:%d: failed %s: NF%c([", filename, lineno, subdesc, nftype); + for (size_t pos = 0; pos < input->len; pos += sizeof(uchar)) + printf("%s%04X", pos ? " " : "", (unsigned)input->buf[pos]); + printf("]) -> ["); + for (size_t pos = 0; pos < nf->len; pos += sizeof(uchar)) + printf("%s%04X", pos ? " " : "", (unsigned)nf->buf[pos]); + printf("] != ["); + for (size_t pos = 0; pos < expected->len; pos += sizeof(uchar)) + printf("%s%04X", pos ? " " : "", (unsigned)expected->buf[pos]); + printf("]\n"); + fail++; + } + + ucharbuf_free(nf); +} + +static void run_tests(const char *filename, FILE *fp) +{ + for (int lineno = 1;; lineno++) { + char *line = chomp(fgetline(fp)); + if (!line) + break; + + /* Strip section dividers which begin with @ */ + if (*line == '@') { + sfree(line); + continue; + } + + /* Strip comments, if any */ + ptrlen pl = ptrlen_from_asciz(line); + { + const char *p = memchr(pl.ptr, '#', pl.len); + if (p) + pl.len = p - (const char *)pl.ptr; + } + + /* Strip trailing space */ + while (pl.len > 0 && + (((char *)pl.ptr)[pl.len-1] == ' ' || + ((char *)pl.ptr)[pl.len-1] == '\t')) + pl.len--; + + /* Skip empty lines */ + if (!pl.len) { + sfree(line); + continue; + } + + /* Break up at semicolons, expecting five fields, each of + * which we decode into hex code points */ + ucharbuf *fields[5]; + for (size_t i = 0; i < lenof(fields); i++) { + ptrlen field = ptrlen_get_word(&pl, ";"); + fields[i] = ucharbuf_new(); + + ptrlen chr; + while ((chr = ptrlen_get_word(&field, " ")).len) { + char *chrstr = mkstr(chr); + uchar c = strtoul(chrstr, NULL, 16); + sfree(chrstr); + ucharbuf_append(fields[i], c); + } + } + + subtest(filename, lineno, "NFC(c1) = c2", 'C', fields[0], fields[1]); + subtest(filename, lineno, "NFC(c2) = c2", 'C', fields[1], fields[1]); + subtest(filename, lineno, "NFC(c3) = c2", 'C', fields[2], fields[1]); + subtest(filename, lineno, "NFC(c4) = c4", 'C', fields[3], fields[3]); + subtest(filename, lineno, "NFC(c5) = c4", 'C', fields[4], fields[3]); + subtest(filename, lineno, "NFD(c1) = c3", 'D', fields[0], fields[2]); + subtest(filename, lineno, "NFD(c2) = c3", 'D', fields[1], fields[2]); + subtest(filename, lineno, "NFD(c3) = c3", 'D', fields[2], fields[2]); + subtest(filename, lineno, "NFD(c4) = c5", 'D', fields[3], fields[4]); + subtest(filename, lineno, "NFD(c5) = c5", 'D', fields[4], fields[4]); + + for (size_t i = 0; i < lenof(fields); i++) + ucharbuf_free(fields[i]); + + sfree(line); + } +} + +int main(int argc, char **argv) +{ + if (argc != 2) { + fprintf(stderr, "test_unicode_norm: give an input file " + "of tests or '-'\n"); + return 1; + } + + const char *filename = argv[1]; + + if (!strcmp(filename, "-")) { + run_tests("", stdin); + } else { + FILE *fp = fopen(filename, "r"); + if (!fp) { + fprintf(stderr, "test_unicode_norm: unable to open '%s'\n", + filename); + return 1; + } + run_tests(filename, fp); + fclose(fp); + } + + printf("pass %d fail %d total %d\n", pass, fail, pass + fail); + + return fail != 0; +} +#endif diff --git a/utils/wcwidth.c b/utils/wcwidth.c index 7705d984..018e4c5b 100644 --- a/utils/wcwidth.c +++ b/utils/wcwidth.c @@ -124,509 +124,13 @@ static bool bisearch(unsigned int ucs, const struct interval *table, int max) { int mk_wcwidth(unsigned int ucs) { /* sorted list of non-overlapping intervals of non-spacing characters */ - /* generated by the following Perl - * from the Unicode 14.0.0 data files available at: - * https://www.unicode.org/Public/14.0.0/ucd/ - -open DATA, "<", "UnicodeData.txt" || die "$!"; -while () { - @fields = split /;/; - $chr = hex $fields[0]; - $cat = $fields[2]; - $include = ($cat eq "Me" || $cat eq "Mn" || $cat eq "Cf"); - $include = 0 if ($chr == 0x00AD); - $include = 1 if (0x1160 <= $chr && $chr <= 0x11FF); - $include = 1 if ($chr == 0x200B); - $chrs{$chr} = $include; -} -close DATA; -for ($chr = 0; $chr < 0x110000; $chr++) { - if ($chrs{$chr}) { - $start = $chr; - $chr++ while $chrs{$chr}; - printf " { 0x%04X, 0x%04X },\n", $start, $chr-1; - } -} - - */ static const struct interval combining[] = { - { 0x0300, 0x036F }, - { 0x0483, 0x0489 }, - { 0x0591, 0x05BD }, - { 0x05BF, 0x05BF }, - { 0x05C1, 0x05C2 }, - { 0x05C4, 0x05C5 }, - { 0x05C7, 0x05C7 }, - { 0x0600, 0x0605 }, - { 0x0610, 0x061A }, - { 0x061C, 0x061C }, - { 0x064B, 0x065F }, - { 0x0670, 0x0670 }, - { 0x06D6, 0x06DD }, - { 0x06DF, 0x06E4 }, - { 0x06E7, 0x06E8 }, - { 0x06EA, 0x06ED }, - { 0x070F, 0x070F }, - { 0x0711, 0x0711 }, - { 0x0730, 0x074A }, - { 0x07A6, 0x07B0 }, - { 0x07EB, 0x07F3 }, - { 0x07FD, 0x07FD }, - { 0x0816, 0x0819 }, - { 0x081B, 0x0823 }, - { 0x0825, 0x0827 }, - { 0x0829, 0x082D }, - { 0x0859, 0x085B }, - { 0x0890, 0x0891 }, - { 0x0898, 0x089F }, - { 0x08CA, 0x0902 }, - { 0x093A, 0x093A }, - { 0x093C, 0x093C }, - { 0x0941, 0x0948 }, - { 0x094D, 0x094D }, - { 0x0951, 0x0957 }, - { 0x0962, 0x0963 }, - { 0x0981, 0x0981 }, - { 0x09BC, 0x09BC }, - { 0x09C1, 0x09C4 }, - { 0x09CD, 0x09CD }, - { 0x09E2, 0x09E3 }, - { 0x09FE, 0x09FE }, - { 0x0A01, 0x0A02 }, - { 0x0A3C, 0x0A3C }, - { 0x0A41, 0x0A42 }, - { 0x0A47, 0x0A48 }, - { 0x0A4B, 0x0A4D }, - { 0x0A51, 0x0A51 }, - { 0x0A70, 0x0A71 }, - { 0x0A75, 0x0A75 }, - { 0x0A81, 0x0A82 }, - { 0x0ABC, 0x0ABC }, - { 0x0AC1, 0x0AC5 }, - { 0x0AC7, 0x0AC8 }, - { 0x0ACD, 0x0ACD }, - { 0x0AE2, 0x0AE3 }, - { 0x0AFA, 0x0AFF }, - { 0x0B01, 0x0B01 }, - { 0x0B3C, 0x0B3C }, - { 0x0B3F, 0x0B3F }, - { 0x0B41, 0x0B44 }, - { 0x0B4D, 0x0B4D }, - { 0x0B55, 0x0B56 }, - { 0x0B62, 0x0B63 }, - { 0x0B82, 0x0B82 }, - { 0x0BC0, 0x0BC0 }, - { 0x0BCD, 0x0BCD }, - { 0x0C00, 0x0C00 }, - { 0x0C04, 0x0C04 }, - { 0x0C3C, 0x0C3C }, - { 0x0C3E, 0x0C40 }, - { 0x0C46, 0x0C48 }, - { 0x0C4A, 0x0C4D }, - { 0x0C55, 0x0C56 }, - { 0x0C62, 0x0C63 }, - { 0x0C81, 0x0C81 }, - { 0x0CBC, 0x0CBC }, - { 0x0CBF, 0x0CBF }, - { 0x0CC6, 0x0CC6 }, - { 0x0CCC, 0x0CCD }, - { 0x0CE2, 0x0CE3 }, - { 0x0D00, 0x0D01 }, - { 0x0D3B, 0x0D3C }, - { 0x0D41, 0x0D44 }, - { 0x0D4D, 0x0D4D }, - { 0x0D62, 0x0D63 }, - { 0x0D81, 0x0D81 }, - { 0x0DCA, 0x0DCA }, - { 0x0DD2, 0x0DD4 }, - { 0x0DD6, 0x0DD6 }, - { 0x0E31, 0x0E31 }, - { 0x0E34, 0x0E3A }, - { 0x0E47, 0x0E4E }, - { 0x0EB1, 0x0EB1 }, - { 0x0EB4, 0x0EBC }, - { 0x0EC8, 0x0ECD }, - { 0x0F18, 0x0F19 }, - { 0x0F35, 0x0F35 }, - { 0x0F37, 0x0F37 }, - { 0x0F39, 0x0F39 }, - { 0x0F71, 0x0F7E }, - { 0x0F80, 0x0F84 }, - { 0x0F86, 0x0F87 }, - { 0x0F8D, 0x0F97 }, - { 0x0F99, 0x0FBC }, - { 0x0FC6, 0x0FC6 }, - { 0x102D, 0x1030 }, - { 0x1032, 0x1037 }, - { 0x1039, 0x103A }, - { 0x103D, 0x103E }, - { 0x1058, 0x1059 }, - { 0x105E, 0x1060 }, - { 0x1071, 0x1074 }, - { 0x1082, 0x1082 }, - { 0x1085, 0x1086 }, - { 0x108D, 0x108D }, - { 0x109D, 0x109D }, - { 0x1160, 0x11FF }, - { 0x135D, 0x135F }, - { 0x1712, 0x1714 }, - { 0x1732, 0x1733 }, - { 0x1752, 0x1753 }, - { 0x1772, 0x1773 }, - { 0x17B4, 0x17B5 }, - { 0x17B7, 0x17BD }, - { 0x17C6, 0x17C6 }, - { 0x17C9, 0x17D3 }, - { 0x17DD, 0x17DD }, - { 0x180B, 0x180F }, - { 0x1885, 0x1886 }, - { 0x18A9, 0x18A9 }, - { 0x1920, 0x1922 }, - { 0x1927, 0x1928 }, - { 0x1932, 0x1932 }, - { 0x1939, 0x193B }, - { 0x1A17, 0x1A18 }, - { 0x1A1B, 0x1A1B }, - { 0x1A56, 0x1A56 }, - { 0x1A58, 0x1A5E }, - { 0x1A60, 0x1A60 }, - { 0x1A62, 0x1A62 }, - { 0x1A65, 0x1A6C }, - { 0x1A73, 0x1A7C }, - { 0x1A7F, 0x1A7F }, - { 0x1AB0, 0x1ACE }, - { 0x1B00, 0x1B03 }, - { 0x1B34, 0x1B34 }, - { 0x1B36, 0x1B3A }, - { 0x1B3C, 0x1B3C }, - { 0x1B42, 0x1B42 }, - { 0x1B6B, 0x1B73 }, - { 0x1B80, 0x1B81 }, - { 0x1BA2, 0x1BA5 }, - { 0x1BA8, 0x1BA9 }, - { 0x1BAB, 0x1BAD }, - { 0x1BE6, 0x1BE6 }, - { 0x1BE8, 0x1BE9 }, - { 0x1BED, 0x1BED }, - { 0x1BEF, 0x1BF1 }, - { 0x1C2C, 0x1C33 }, - { 0x1C36, 0x1C37 }, - { 0x1CD0, 0x1CD2 }, - { 0x1CD4, 0x1CE0 }, - { 0x1CE2, 0x1CE8 }, - { 0x1CED, 0x1CED }, - { 0x1CF4, 0x1CF4 }, - { 0x1CF8, 0x1CF9 }, - { 0x1DC0, 0x1DFF }, - { 0x200B, 0x200F }, - { 0x202A, 0x202E }, - { 0x2060, 0x2064 }, - { 0x2066, 0x206F }, - { 0x20D0, 0x20F0 }, - { 0x2CEF, 0x2CF1 }, - { 0x2D7F, 0x2D7F }, - { 0x2DE0, 0x2DFF }, - { 0x302A, 0x302D }, - { 0x3099, 0x309A }, - { 0xA66F, 0xA672 }, - { 0xA674, 0xA67D }, - { 0xA69E, 0xA69F }, - { 0xA6F0, 0xA6F1 }, - { 0xA802, 0xA802 }, - { 0xA806, 0xA806 }, - { 0xA80B, 0xA80B }, - { 0xA825, 0xA826 }, - { 0xA82C, 0xA82C }, - { 0xA8C4, 0xA8C5 }, - { 0xA8E0, 0xA8F1 }, - { 0xA8FF, 0xA8FF }, - { 0xA926, 0xA92D }, - { 0xA947, 0xA951 }, - { 0xA980, 0xA982 }, - { 0xA9B3, 0xA9B3 }, - { 0xA9B6, 0xA9B9 }, - { 0xA9BC, 0xA9BD }, - { 0xA9E5, 0xA9E5 }, - { 0xAA29, 0xAA2E }, - { 0xAA31, 0xAA32 }, - { 0xAA35, 0xAA36 }, - { 0xAA43, 0xAA43 }, - { 0xAA4C, 0xAA4C }, - { 0xAA7C, 0xAA7C }, - { 0xAAB0, 0xAAB0 }, - { 0xAAB2, 0xAAB4 }, - { 0xAAB7, 0xAAB8 }, - { 0xAABE, 0xAABF }, - { 0xAAC1, 0xAAC1 }, - { 0xAAEC, 0xAAED }, - { 0xAAF6, 0xAAF6 }, - { 0xABE5, 0xABE5 }, - { 0xABE8, 0xABE8 }, - { 0xABED, 0xABED }, - { 0xFB1E, 0xFB1E }, - { 0xFE00, 0xFE0F }, - { 0xFE20, 0xFE2F }, - { 0xFEFF, 0xFEFF }, - { 0xFFF9, 0xFFFB }, - { 0x101FD, 0x101FD }, - { 0x102E0, 0x102E0 }, - { 0x10376, 0x1037A }, - { 0x10A01, 0x10A03 }, - { 0x10A05, 0x10A06 }, - { 0x10A0C, 0x10A0F }, - { 0x10A38, 0x10A3A }, - { 0x10A3F, 0x10A3F }, - { 0x10AE5, 0x10AE6 }, - { 0x10D24, 0x10D27 }, - { 0x10EAB, 0x10EAC }, - { 0x10F46, 0x10F50 }, - { 0x10F82, 0x10F85 }, - { 0x11001, 0x11001 }, - { 0x11038, 0x11046 }, - { 0x11070, 0x11070 }, - { 0x11073, 0x11074 }, - { 0x1107F, 0x11081 }, - { 0x110B3, 0x110B6 }, - { 0x110B9, 0x110BA }, - { 0x110BD, 0x110BD }, - { 0x110C2, 0x110C2 }, - { 0x110CD, 0x110CD }, - { 0x11100, 0x11102 }, - { 0x11127, 0x1112B }, - { 0x1112D, 0x11134 }, - { 0x11173, 0x11173 }, - { 0x11180, 0x11181 }, - { 0x111B6, 0x111BE }, - { 0x111C9, 0x111CC }, - { 0x111CF, 0x111CF }, - { 0x1122F, 0x11231 }, - { 0x11234, 0x11234 }, - { 0x11236, 0x11237 }, - { 0x1123E, 0x1123E }, - { 0x112DF, 0x112DF }, - { 0x112E3, 0x112EA }, - { 0x11300, 0x11301 }, - { 0x1133B, 0x1133C }, - { 0x11340, 0x11340 }, - { 0x11366, 0x1136C }, - { 0x11370, 0x11374 }, - { 0x11438, 0x1143F }, - { 0x11442, 0x11444 }, - { 0x11446, 0x11446 }, - { 0x1145E, 0x1145E }, - { 0x114B3, 0x114B8 }, - { 0x114BA, 0x114BA }, - { 0x114BF, 0x114C0 }, - { 0x114C2, 0x114C3 }, - { 0x115B2, 0x115B5 }, - { 0x115BC, 0x115BD }, - { 0x115BF, 0x115C0 }, - { 0x115DC, 0x115DD }, - { 0x11633, 0x1163A }, - { 0x1163D, 0x1163D }, - { 0x1163F, 0x11640 }, - { 0x116AB, 0x116AB }, - { 0x116AD, 0x116AD }, - { 0x116B0, 0x116B5 }, - { 0x116B7, 0x116B7 }, - { 0x1171D, 0x1171F }, - { 0x11722, 0x11725 }, - { 0x11727, 0x1172B }, - { 0x1182F, 0x11837 }, - { 0x11839, 0x1183A }, - { 0x1193B, 0x1193C }, - { 0x1193E, 0x1193E }, - { 0x11943, 0x11943 }, - { 0x119D4, 0x119D7 }, - { 0x119DA, 0x119DB }, - { 0x119E0, 0x119E0 }, - { 0x11A01, 0x11A0A }, - { 0x11A33, 0x11A38 }, - { 0x11A3B, 0x11A3E }, - { 0x11A47, 0x11A47 }, - { 0x11A51, 0x11A56 }, - { 0x11A59, 0x11A5B }, - { 0x11A8A, 0x11A96 }, - { 0x11A98, 0x11A99 }, - { 0x11C30, 0x11C36 }, - { 0x11C38, 0x11C3D }, - { 0x11C3F, 0x11C3F }, - { 0x11C92, 0x11CA7 }, - { 0x11CAA, 0x11CB0 }, - { 0x11CB2, 0x11CB3 }, - { 0x11CB5, 0x11CB6 }, - { 0x11D31, 0x11D36 }, - { 0x11D3A, 0x11D3A }, - { 0x11D3C, 0x11D3D }, - { 0x11D3F, 0x11D45 }, - { 0x11D47, 0x11D47 }, - { 0x11D90, 0x11D91 }, - { 0x11D95, 0x11D95 }, - { 0x11D97, 0x11D97 }, - { 0x11EF3, 0x11EF4 }, - { 0x13430, 0x13438 }, - { 0x16AF0, 0x16AF4 }, - { 0x16B30, 0x16B36 }, - { 0x16F4F, 0x16F4F }, - { 0x16F8F, 0x16F92 }, - { 0x16FE4, 0x16FE4 }, - { 0x1BC9D, 0x1BC9E }, - { 0x1BCA0, 0x1BCA3 }, - { 0x1CF00, 0x1CF2D }, - { 0x1CF30, 0x1CF46 }, - { 0x1D167, 0x1D169 }, - { 0x1D173, 0x1D182 }, - { 0x1D185, 0x1D18B }, - { 0x1D1AA, 0x1D1AD }, - { 0x1D242, 0x1D244 }, - { 0x1DA00, 0x1DA36 }, - { 0x1DA3B, 0x1DA6C }, - { 0x1DA75, 0x1DA75 }, - { 0x1DA84, 0x1DA84 }, - { 0x1DA9B, 0x1DA9F }, - { 0x1DAA1, 0x1DAAF }, - { 0x1E000, 0x1E006 }, - { 0x1E008, 0x1E018 }, - { 0x1E01B, 0x1E021 }, - { 0x1E023, 0x1E024 }, - { 0x1E026, 0x1E02A }, - { 0x1E130, 0x1E136 }, - { 0x1E2AE, 0x1E2AE }, - { 0x1E2EC, 0x1E2EF }, - { 0x1E8D0, 0x1E8D6 }, - { 0x1E944, 0x1E94A }, - { 0xE0001, 0xE0001 }, - { 0xE0020, 0xE007F }, - { 0xE0100, 0xE01EF }, + #include "unicode/nonspacing_chars.h" }; - /* A sorted list of intervals of double-width characters generated by: - * https://raw.githubusercontent.com/GNOME/glib/37d4c2941bd0326b8b6e6bb22c81bd424fcc040b/glib/gen-unicode-tables.pl - * from the Unicode 14.0.0 data files available at: - * https://www.unicode.org/Public/14.0.0/ucd/ - */ + /* A sorted list of intervals of double-width characters */ static const struct interval wide[] = { - {0x1100, 0x115F}, - {0x231A, 0x231B}, - {0x2329, 0x232A}, - {0x23E9, 0x23EC}, - {0x23F0, 0x23F0}, - {0x23F3, 0x23F3}, - {0x25FD, 0x25FE}, - {0x2614, 0x2615}, - {0x2648, 0x2653}, - {0x267F, 0x267F}, - {0x2693, 0x2693}, - {0x26A1, 0x26A1}, - {0x26AA, 0x26AB}, - {0x26BD, 0x26BE}, - {0x26C4, 0x26C5}, - {0x26CE, 0x26CE}, - {0x26D4, 0x26D4}, - {0x26EA, 0x26EA}, - {0x26F2, 0x26F3}, - {0x26F5, 0x26F5}, - {0x26FA, 0x26FA}, - {0x26FD, 0x26FD}, - {0x2705, 0x2705}, - {0x270A, 0x270B}, - {0x2728, 0x2728}, - {0x274C, 0x274C}, - {0x274E, 0x274E}, - {0x2753, 0x2755}, - {0x2757, 0x2757}, - {0x2795, 0x2797}, - {0x27B0, 0x27B0}, - {0x27BF, 0x27BF}, - {0x2B1B, 0x2B1C}, - {0x2B50, 0x2B50}, - {0x2B55, 0x2B55}, - {0x2E80, 0x2E99}, - {0x2E9B, 0x2EF3}, - {0x2F00, 0x2FD5}, - {0x2FF0, 0x2FFB}, - {0x3000, 0x303E}, - {0x3041, 0x3096}, - {0x3099, 0x30FF}, - {0x3105, 0x312F}, - {0x3131, 0x318E}, - {0x3190, 0x31E3}, - {0x31F0, 0x321E}, - {0x3220, 0x3247}, - {0x3250, 0x4DBF}, - {0x4E00, 0xA48C}, - {0xA490, 0xA4C6}, - {0xA960, 0xA97C}, - {0xAC00, 0xD7A3}, - {0xF900, 0xFAFF}, - {0xFE10, 0xFE19}, - {0xFE30, 0xFE52}, - {0xFE54, 0xFE66}, - {0xFE68, 0xFE6B}, - {0xFF01, 0xFF60}, - {0xFFE0, 0xFFE6}, - {0x16FE0, 0x16FE4}, - {0x16FF0, 0x16FF1}, - {0x17000, 0x187F7}, - {0x18800, 0x18CD5}, - {0x18D00, 0x18D08}, - {0x1AFF0, 0x1AFF3}, - {0x1AFF5, 0x1AFFB}, - {0x1AFFD, 0x1AFFE}, - {0x1B000, 0x1B122}, - {0x1B150, 0x1B152}, - {0x1B164, 0x1B167}, - {0x1B170, 0x1B2FB}, - {0x1F004, 0x1F004}, - {0x1F0CF, 0x1F0CF}, - {0x1F18E, 0x1F18E}, - {0x1F191, 0x1F19A}, - {0x1F200, 0x1F202}, - {0x1F210, 0x1F23B}, - {0x1F240, 0x1F248}, - {0x1F250, 0x1F251}, - {0x1F260, 0x1F265}, - {0x1F300, 0x1F320}, - {0x1F32D, 0x1F335}, - {0x1F337, 0x1F37C}, - {0x1F37E, 0x1F393}, - {0x1F3A0, 0x1F3CA}, - {0x1F3CF, 0x1F3D3}, - {0x1F3E0, 0x1F3F0}, - {0x1F3F4, 0x1F3F4}, - {0x1F3F8, 0x1F43E}, - {0x1F440, 0x1F440}, - {0x1F442, 0x1F4FC}, - {0x1F4FF, 0x1F53D}, - {0x1F54B, 0x1F54E}, - {0x1F550, 0x1F567}, - {0x1F57A, 0x1F57A}, - {0x1F595, 0x1F596}, - {0x1F5A4, 0x1F5A4}, - {0x1F5FB, 0x1F64F}, - {0x1F680, 0x1F6C5}, - {0x1F6CC, 0x1F6CC}, - {0x1F6D0, 0x1F6D2}, - {0x1F6D5, 0x1F6D7}, - {0x1F6DD, 0x1F6DF}, - {0x1F6EB, 0x1F6EC}, - {0x1F6F4, 0x1F6FC}, - {0x1F7E0, 0x1F7EB}, - {0x1F7F0, 0x1F7F0}, - {0x1F90C, 0x1F93A}, - {0x1F93C, 0x1F945}, - {0x1F947, 0x1F9FF}, - {0x1FA70, 0x1FA74}, - {0x1FA78, 0x1FA7C}, - {0x1FA80, 0x1FA86}, - {0x1FA90, 0x1FAAC}, - {0x1FAB0, 0x1FABA}, - {0x1FAC0, 0x1FAC5}, - {0x1FAD0, 0x1FAD9}, - {0x1FAE0, 0x1FAE7}, - {0x1FAF0, 0x1FAF6}, - {0x20000, 0x2FFFD}, - {0x30000, 0x3FFFD}, + #include "unicode/wide_chars.h" }; /* test for 8-bit control characters */ @@ -677,191 +181,9 @@ int mk_wcswidth(const unsigned int *pwcs, size_t n) */ int mk_wcwidth_cjk(unsigned int ucs) { - /* A sorted list of intervals of ambiguous width characters generated by: - * https://raw.githubusercontent.com/GNOME/glib/37d4c2941bd0326b8b6e6bb22c81bd424fcc040b/glib/gen-unicode-tables.pl - * from the Unicode 9.0.0 data files available at: - * http://www.unicode.org/Public/9.0.0/ucd/ - */ + /* A sorted list of intervals of ambiguous width characters */ static const struct interval ambiguous[] = { - {0x00A1, 0x00A1}, - {0x00A4, 0x00A4}, - {0x00A7, 0x00A8}, - {0x00AA, 0x00AA}, - {0x00AD, 0x00AE}, - {0x00B0, 0x00B4}, - {0x00B6, 0x00BA}, - {0x00BC, 0x00BF}, - {0x00C6, 0x00C6}, - {0x00D0, 0x00D0}, - {0x00D7, 0x00D8}, - {0x00DE, 0x00E1}, - {0x00E6, 0x00E6}, - {0x00E8, 0x00EA}, - {0x00EC, 0x00ED}, - {0x00F0, 0x00F0}, - {0x00F2, 0x00F3}, - {0x00F7, 0x00FA}, - {0x00FC, 0x00FC}, - {0x00FE, 0x00FE}, - {0x0101, 0x0101}, - {0x0111, 0x0111}, - {0x0113, 0x0113}, - {0x011B, 0x011B}, - {0x0126, 0x0127}, - {0x012B, 0x012B}, - {0x0131, 0x0133}, - {0x0138, 0x0138}, - {0x013F, 0x0142}, - {0x0144, 0x0144}, - {0x0148, 0x014B}, - {0x014D, 0x014D}, - {0x0152, 0x0153}, - {0x0166, 0x0167}, - {0x016B, 0x016B}, - {0x01CE, 0x01CE}, - {0x01D0, 0x01D0}, - {0x01D2, 0x01D2}, - {0x01D4, 0x01D4}, - {0x01D6, 0x01D6}, - {0x01D8, 0x01D8}, - {0x01DA, 0x01DA}, - {0x01DC, 0x01DC}, - {0x0251, 0x0251}, - {0x0261, 0x0261}, - {0x02C4, 0x02C4}, - {0x02C7, 0x02C7}, - {0x02C9, 0x02CB}, - {0x02CD, 0x02CD}, - {0x02D0, 0x02D0}, - {0x02D8, 0x02DB}, - {0x02DD, 0x02DD}, - {0x02DF, 0x02DF}, - {0x0300, 0x036F}, - {0x0391, 0x03A1}, - {0x03A3, 0x03A9}, - {0x03B1, 0x03C1}, - {0x03C3, 0x03C9}, - {0x0401, 0x0401}, - {0x0410, 0x044F}, - {0x0451, 0x0451}, - {0x2010, 0x2010}, - {0x2013, 0x2016}, - {0x2018, 0x2019}, - {0x201C, 0x201D}, - {0x2020, 0x2022}, - {0x2024, 0x2027}, - {0x2030, 0x2030}, - {0x2032, 0x2033}, - {0x2035, 0x2035}, - {0x203B, 0x203B}, - {0x203E, 0x203E}, - {0x2074, 0x2074}, - {0x207F, 0x207F}, - {0x2081, 0x2084}, - {0x20AC, 0x20AC}, - {0x2103, 0x2103}, - {0x2105, 0x2105}, - {0x2109, 0x2109}, - {0x2113, 0x2113}, - {0x2116, 0x2116}, - {0x2121, 0x2122}, - {0x2126, 0x2126}, - {0x212B, 0x212B}, - {0x2153, 0x2154}, - {0x215B, 0x215E}, - {0x2160, 0x216B}, - {0x2170, 0x2179}, - {0x2189, 0x2189}, - {0x2190, 0x2199}, - {0x21B8, 0x21B9}, - {0x21D2, 0x21D2}, - {0x21D4, 0x21D4}, - {0x21E7, 0x21E7}, - {0x2200, 0x2200}, - {0x2202, 0x2203}, - {0x2207, 0x2208}, - {0x220B, 0x220B}, - {0x220F, 0x220F}, - {0x2211, 0x2211}, - {0x2215, 0x2215}, - {0x221A, 0x221A}, - {0x221D, 0x2220}, - {0x2223, 0x2223}, - {0x2225, 0x2225}, - {0x2227, 0x222C}, - {0x222E, 0x222E}, - {0x2234, 0x2237}, - {0x223C, 0x223D}, - {0x2248, 0x2248}, - {0x224C, 0x224C}, - {0x2252, 0x2252}, - {0x2260, 0x2261}, - {0x2264, 0x2267}, - {0x226A, 0x226B}, - {0x226E, 0x226F}, - {0x2282, 0x2283}, - {0x2286, 0x2287}, - {0x2295, 0x2295}, - {0x2299, 0x2299}, - {0x22A5, 0x22A5}, - {0x22BF, 0x22BF}, - {0x2312, 0x2312}, - {0x2460, 0x24E9}, - {0x24EB, 0x254B}, - {0x2550, 0x2573}, - {0x2580, 0x258F}, - {0x2592, 0x2595}, - {0x25A0, 0x25A1}, - {0x25A3, 0x25A9}, - {0x25B2, 0x25B3}, - {0x25B6, 0x25B7}, - {0x25BC, 0x25BD}, - {0x25C0, 0x25C1}, - {0x25C6, 0x25C8}, - {0x25CB, 0x25CB}, - {0x25CE, 0x25D1}, - {0x25E2, 0x25E5}, - {0x25EF, 0x25EF}, - {0x2605, 0x2606}, - {0x2609, 0x2609}, - {0x260E, 0x260F}, - {0x261C, 0x261C}, - {0x261E, 0x261E}, - {0x2640, 0x2640}, - {0x2642, 0x2642}, - {0x2660, 0x2661}, - {0x2663, 0x2665}, - {0x2667, 0x266A}, - {0x266C, 0x266D}, - {0x266F, 0x266F}, - {0x269E, 0x269F}, - {0x26BF, 0x26BF}, - {0x26C6, 0x26CD}, - {0x26CF, 0x26D3}, - {0x26D5, 0x26E1}, - {0x26E3, 0x26E3}, - {0x26E8, 0x26E9}, - {0x26EB, 0x26F1}, - {0x26F4, 0x26F4}, - {0x26F6, 0x26F9}, - {0x26FB, 0x26FC}, - {0x26FE, 0x26FF}, - {0x273D, 0x273D}, - {0x2776, 0x277F}, - {0x2B56, 0x2B59}, - {0x3248, 0x324F}, - {0xE000, 0xF8FF}, - {0xFE00, 0xFE0F}, - {0xFFFD, 0xFFFD}, - {0x1F100, 0x1F10A}, - {0x1F110, 0x1F12D}, - {0x1F130, 0x1F169}, - {0x1F170, 0x1F18D}, - {0x1F18F, 0x1F190}, - {0x1F19B, 0x1F1AC}, - {0xE0100, 0xE01EF}, - {0xF0000, 0xFFFFD}, - {0x100000, 0x10FFFD}, + #include "unicode/ambiguous_wide_chars.h" }; /* binary search in table of non-spacing characters */ diff --git a/utils/x11authfile.c b/utils/x11authfile.c index e2358a9d..ffc91e9c 100644 --- a/utils/x11authfile.c +++ b/utils/x11authfile.c @@ -27,7 +27,7 @@ static void BinarySink_put_stringpl_xauth(BinarySink *bs, ptrlen pl) BinarySink_put_stringpl_xauth(BinarySink_UPCAST(bs),ptrlen) void x11_get_auth_from_authfile(struct X11Display *disp, - const char *authfilename) + Filename *authfilename) { FILE *authfp; char *buf; @@ -72,7 +72,7 @@ void x11_get_auth_from_authfile(struct X11Display *disp, */ bool localhost = !disp->unixdomain && sk_address_is_local(disp->addr); - authfp = fopen(authfilename, "rb"); + authfp = f_open(authfilename, "rb", false); if (!authfp) return; diff --git a/version.h b/version.h index 60f3ef22..f83e68b9 100644 --- a/version.h +++ b/version.h @@ -8,6 +8,6 @@ * default stuff used for local development runs of 'make'. */ -#define TEXTVER "发布版 0.81-cn1" -#define SSHVER "-Release-0-81-CN1" -#define BINARY_VERSION 0,81,0,1 +#define TEXTVER "发布版 0.82-cn1" +#define SSHVER "-Release-0-82-CN1" +#define BINARY_VERSION 0,82,0,1 diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index d34b4106..7e16b0f4 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -6,6 +6,7 @@ add_sources_from_current_dir(utils utils/arm_arch_queries.c utils/aux_match_opt.c utils/centre_window.c + utils/cmdline_arg.c utils/cryptoapi.c utils/defaults.c utils/dll_hijacking_protection.c @@ -16,6 +17,7 @@ add_sources_from_current_dir(utils utils/getdlgitemtext_alloc.c utils/get_system_dir.c utils/get_username.c + utils/gui-timing.c utils/interprocess_mutex.c utils/is_console_handle.c utils/load_system32_dll.c @@ -33,6 +35,7 @@ add_sources_from_current_dir(utils utils/security.c utils/shinydialogbox.c utils/split_into_argv.c + utils/split_into_argv_w.c utils/version.c utils/win_strerror.c unicode.c) @@ -100,6 +103,7 @@ add_executable(putty window.c putty.c help.c + ${CMAKE_SOURCE_DIR}/stubs/no-console.c putty.rc) be_list(putty PuTTY SSH SERIAL OTHERBACKENDS) add_dependencies(putty generated_licence_h) @@ -118,6 +122,7 @@ add_executable(puttytel help.c ${CMAKE_SOURCE_DIR}/stubs/no-gss.c ${CMAKE_SOURCE_DIR}/stubs/no-ca-config.c + ${CMAKE_SOURCE_DIR}/stubs/no-console.c ${CMAKE_SOURCE_DIR}/stubs/no-rand.c ${CMAKE_SOURCE_DIR}/proxy/nocproxy.c ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c @@ -160,6 +165,7 @@ if(HAVE_CONPTY) conpty.c ${CMAKE_SOURCE_DIR}/stubs/no-gss.c ${CMAKE_SOURCE_DIR}/stubs/no-ca-config.c + ${CMAKE_SOURCE_DIR}/stubs/no-console.c ${CMAKE_SOURCE_DIR}/stubs/no-rand.c ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c pterm.rc) @@ -177,10 +183,32 @@ else() endif() add_executable(test_split_into_argv - utils/split_into_argv.c) + test/test_split_into_argv.c) target_compile_definitions(test_split_into_argv PRIVATE TEST) target_link_libraries(test_split_into_argv utils ${platform_libraries}) add_executable(test_screenshot - test_screenshot.c) + test/test_screenshot.c) target_link_libraries(test_screenshot utils ${platform_libraries}) + +add_executable(test_lineedit + ${CMAKE_SOURCE_DIR}/test/test_lineedit.c + ${CMAKE_SOURCE_DIR}/stubs/no-gss.c + ${CMAKE_SOURCE_DIR}/stubs/no-logging.c + ${CMAKE_SOURCE_DIR}/stubs/no-printing.c + ${CMAKE_SOURCE_DIR}/stubs/no-storage.c + ${CMAKE_SOURCE_DIR}/stubs/no-timing.c + no-jump-list.c) +target_link_libraries(test_lineedit + guiterminal settings eventloop utils ${platform_libraries}) + +add_executable(test_terminal + ${CMAKE_SOURCE_DIR}/test/test_terminal.c + ${CMAKE_SOURCE_DIR}/stubs/no-gss.c + ${CMAKE_SOURCE_DIR}/stubs/no-storage.c + ${CMAKE_SOURCE_DIR}/stubs/no-timing.c + no-jump-list.c) +target_link_libraries(test_terminal + guiterminal settings eventloop utils ${platform_libraries}) + +add_sources_from_current_dir(test_conf no-jump-list.c handle-wait.c) diff --git a/windows/conpty.c b/windows/conpty.c index c23c81e1..27e6f552 100644 --- a/windows/conpty.c +++ b/windows/conpty.c @@ -176,7 +176,7 @@ static char *conpty_init(const BackendVtable *vt, Seat *seat, HPCON pcon; bool pcon_needs_cleanup = false; - STARTUPINFOEX si; + STARTUPINFOEXW si; memset(&si, 0, sizeof(si)); if (!init_conpty_api()) { @@ -238,16 +238,21 @@ static char *conpty_init(const BackendVtable *vt, Seat *seat, PROCESS_INFORMATION pi; memset(&pi, 0, sizeof(pi)); - char *command; - const char *conf_cmd = conf_get_str(conf, CONF_remote_cmd); - if (*conf_cmd) { - command = dupstr(conf_cmd); - } else { - command = dupcat(get_system_dir(), "\\cmd.exe"); + wchar_t *command; + { + bool utf8; + const char *conf_cmd = conf_get_str_ambi(conf, CONF_remote_cmd, &utf8); + if (*conf_cmd) { + command = dup_mb_to_wc(utf8 ? CP_UTF8 : CP_ACP, conf_cmd); + } else { + char *cmd = dupcat(get_system_dir(), "\\cmd.exe"); + command = dup_mb_to_wc(CP_ACP, cmd); + sfree(cmd); + } } - bool created_ok = CreateProcess(NULL, command, NULL, NULL, - false, EXTENDED_STARTUPINFO_PRESENT, - NULL, NULL, &si.StartupInfo, &pi); + bool created_ok = CreateProcessW(NULL, command, NULL, NULL, + false, EXTENDED_STARTUPINFO_PRESENT, + NULL, NULL, &si.StartupInfo, &pi); sfree(command); if (!created_ok) { err = dupprintf("CreateProcess: %s", diff --git a/windows/console.c b/windows/console.c index a1ede43d..a99097ba 100644 --- a/windows/console.c +++ b/windows/console.c @@ -32,37 +32,345 @@ void console_print_error_msg(const char *prefix, const char *msg) fflush(stderr); } +/* + * System for getting I/O handles to talk to the console for + * interactive prompts. + * + * In PuTTY 0.81 and before, these prompts used the standard I/O + * handles. But this means you can't redirect Plink's actual stdin + * from a sensible data channel without the responses to login prompts + * unwantedly being read from it too. Also, if you have a real + * console handle then you can read from it in Unicode mode, which is + * an option not available for any old file handle. + * + * However, many versions of PuTTY have worked the old way, so we need + * a method of falling back to it for the sake of whoever's workflow + * it turns out to break. So this structure equivocates between the + * two systems. + */ +static bool conio_use_standard_handles = false; +bool console_set_stdio_prompts(bool newvalue) +{ + conio_use_standard_handles = newvalue; + return true; +} + +static bool conio_use_utf8 = true; +bool set_legacy_charset_handling(bool newvalue) +{ + conio_use_utf8 = !newvalue; + return true; +} + +typedef struct ConsoleIO { + HANDLE hin, hout; + bool need_close_hin, need_close_hout; + bool hin_is_console, hout_is_console; + bool utf8; + BinarySink_IMPLEMENTATION; +} ConsoleIO; + +static void console_write(BinarySink *bs, const void *data, size_t len); + +static ConsoleIO *conio_setup(bool utf8, DWORD fallback_output) +{ + ConsoleIO *conio = snew(ConsoleIO); + + conio->hin = conio->hout = INVALID_HANDLE_VALUE; + conio->need_close_hin = conio->need_close_hout = false; + + init_winver(); + if (osPlatformId == VER_PLATFORM_WIN32_WINDOWS || + osPlatformId == VER_PLATFORM_WIN32s) + conio->utf8 = false; /* no Unicode support at all */ + else + conio->utf8 = utf8 && conio_use_utf8; + + /* + * First try opening the console itself, so that prompts will go + * there regardless of I/O redirection. We don't do this if the + * user has deliberately requested a fallback to the old + * behaviour. We also don't do it in batch mode, because in that + * situation, any need for an interactive prompt will instead + * noninteractively abort the connection, and in that situation, + * the 'prompt' becomes more in the nature of an error message, so + * it should go to standard error like everything else. + */ + if (!conio_use_standard_handles && !console_batch_mode) { + /* + * If we do open the console, it has to be done separately for + * input and output, with different magic file names. + * + * We need both read and write permission for both handles, + * because read permission is needed to read the console mode + * (in particular, to test if a file handle _is_ a console), + * and write permission to change it. + */ + conio->hin = CreateFile("CONIN$", GENERIC_READ | GENERIC_WRITE, + 0, NULL, OPEN_EXISTING, 0, NULL); + if (conio->hin != INVALID_HANDLE_VALUE) + conio->need_close_hin = true; + + conio->hout = CreateFile("CONOUT$", GENERIC_READ | GENERIC_WRITE, + 0, NULL, OPEN_EXISTING, 0, NULL); + if (conio->hout != INVALID_HANDLE_VALUE) + conio->need_close_hout = true; + } + + /* + * Fall back from that to using the standard handles. + * (For prompt output, some callers use STD_ERROR_HANDLE rather + * than STD_OUTPUT_HANDLE, because that has a better chance of + * separating them from session output.) + */ + if (conio->hin == INVALID_HANDLE_VALUE) + conio->hin = GetStdHandle(STD_INPUT_HANDLE); + if (conio->hout == INVALID_HANDLE_VALUE) + conio->hout = GetStdHandle(fallback_output); + + DWORD dummy; + conio->hin_is_console = GetConsoleMode(conio->hin, &dummy); + conio->hout_is_console = GetConsoleMode(conio->hout, &dummy); + + BinarySink_INIT(conio, console_write); + + return conio; +} + +static void conio_free(ConsoleIO *conio) +{ + if (conio->need_close_hin) + CloseHandle(conio->hin); + if (conio->need_close_hout) + CloseHandle(conio->hout); + sfree(conio); +} + +static void console_write(BinarySink *bs, const void *data, size_t len) +{ + ConsoleIO *conio = BinarySink_DOWNCAST(bs, ConsoleIO); + + if (conio->utf8) { + /* + * Convert the UTF-8 input into a wide string. + */ + size_t wlen; + wchar_t *wide = dup_mb_to_wc_c(CP_UTF8, data, len, &wlen); + if (conio->hout_is_console) { + /* + * To write UTF-8 to a console, use WriteConsoleW on the + * wide string we've just made. + */ + size_t pos = 0; + DWORD nwritten; + + while (pos < wlen && WriteConsoleW(conio->hout, wide+pos, wlen-pos, + &nwritten, NULL)) + pos += nwritten; + } else { + /* + * To write a string encoded in UTF-8 to any other file + * handle, the best we can do is to convert it into the + * system code page. This will lose some characters, but + * what else can you do? + */ + size_t clen; + char *sys_cp = dup_wc_to_mb_c(CP_ACP, wide, wlen, "?", &clen); + size_t pos = 0; + DWORD nwritten; + + while (pos < clen && WriteFile(conio->hout, sys_cp+pos, clen-pos, + &nwritten, NULL)) + pos += nwritten; + + burnstr(sys_cp); + } + + burnwcs(wide); + } else { + /* + * If we're in legacy non-UTF-8 mode, just send the bytes + * we're given to the file handle without trying to be clever. + */ + const char *cdata = (const char *)data; + size_t pos = 0; + DWORD nwritten; + + while (pos < len && WriteFile(conio->hout, cdata+pos, len-pos, + &nwritten, NULL)) + pos += nwritten; + } +} + +static bool console_read_line_to_strbuf(ConsoleIO *conio, bool echo, + strbuf *sb) +{ + DWORD savemode; + + if (conio->hin_is_console) { + GetConsoleMode(conio->hin, &savemode); + DWORD newmode = savemode | ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT; + if (!echo) + newmode &= ~ENABLE_ECHO_INPUT; + else + newmode |= ENABLE_ECHO_INPUT; + + SetConsoleMode(conio->hin, newmode); + } + + bool toret = false; + + while (true) { + if (ptrlen_endswith(ptrlen_from_strbuf(sb), + PTRLEN_LITERAL("\n"), NULL)) { + toret = true; + goto out; + } + + if (conio->utf8) { + wchar_t wbuf[4097]; + size_t wlen; + + if (conio->hin_is_console) { + /* + * To read UTF-8 from a console, read wide character data + * via ReadConsoleW, and convert it to UTF-8. + */ + DWORD nread; + if (!ReadConsoleW(conio->hin, wbuf, lenof(wbuf), &nread, NULL)) + goto out; + wlen = nread; + } else { + /* + * To read UTF-8 from an ordinary file handle, read it + * as normal bytes and then convert from CP_ACP to + * UTF-8, in the reverse of what we did above for + * output. + */ + char buf[4096]; + DWORD nread; + if (!ReadFile(conio->hin, buf, lenof(buf), &nread, NULL)) + goto out; + + buffer_sink bs[1]; + buffer_sink_init(bs, wbuf, sizeof(wbuf) - sizeof(wchar_t)); + put_mb_to_wc(bs, CP_ACP, buf, nread); + assert(!bs->overflowed); + wlen = (wchar_t *)bs->out - wbuf; + smemclr(buf, sizeof(buf)); + } + + put_wc_to_mb(sb, CP_UTF8, wbuf, wlen, ""); + smemclr(wbuf, sizeof(wbuf)); + } else { + /* + * If we're in legacy non-UTF-8 mode, just read bytes + * directly from the file handle into the output strbuf. + */ + char buf[4096]; + DWORD nread; + if (!ReadFile(conio->hin, buf, lenof(buf), &nread, NULL)) + goto out; + + put_data(sb, buf, nread); + smemclr(buf, sizeof(buf)); + } + } + + out: + if (!echo) + put_datalit(conio, "\r\n"); + if (conio->hin_is_console) + SetConsoleMode(conio->hin, savemode); + return toret; +} + +static char *console_read_line(ConsoleIO *conio, bool echo) +{ + strbuf *sb = strbuf_new_nm(); + if (!console_read_line_to_strbuf(conio, echo, sb)) { + strbuf_free(sb); + return NULL; + } else { + return strbuf_to_str(sb); + } +} + +typedef enum { + RESPONSE_ABANDON, + RESPONSE_YES, + RESPONSE_NO, + RESPONSE_INFO, + RESPONSE_UNRECOGNISED +} ResponseType; + +static ResponseType parse_and_free_response(char *line) +{ + if (!line) + return RESPONSE_ABANDON; + + ResponseType toret; + switch (line[0]) { + /* In case of misplaced reflexes from another program, + * recognise 'q' as 'abandon connection' as well as the + * advertised 'just press Return' */ + case 'q': + case 'Q': + case '\n': + case '\r': + case '\0': + toret = RESPONSE_ABANDON; + break; + case 'y': + case 'Y': + toret = RESPONSE_YES; + break; + case 'n': + case 'N': + toret = RESPONSE_NO; + break; + case 'i': + case 'I': + toret = RESPONSE_INFO; + break; + default: + toret = RESPONSE_UNRECOGNISED; + break; + } + + burnstr(line); + return toret; +} + /* * Helper function to print the message from a SeatDialogText. Returns * the final prompt to print on the input line, or NULL if a * batch-mode abort is needed. In the latter case it will have printed * the abort text already. */ -static const char *console_print_seatdialogtext(SeatDialogText *text) +static const char *console_print_seatdialogtext( + ConsoleIO *conio, SeatDialogText *text) { const char *prompt = NULL; - stdio_sink errsink[1]; - stdio_sink_init(errsink, stderr); for (SeatDialogTextItem *item = text->items, *end = item+text->nitems; item < end; item++) { switch (item->type) { case SDT_PARA: - wordwrap(BinarySink_UPCAST(errsink), + wordwrap(BinarySink_UPCAST(conio), ptrlen_from_asciz(item->text), 60); - fputc('\n', stderr); + put_byte(conio, '\n'); break; case SDT_DISPLAY: - fprintf(stderr, " %s\n", item->text); + put_fmt(conio, " %s\n", item->text); break; case SDT_SCARY_HEADING: /* Can't change font size or weight in this context */ - fprintf(stderr, "%s\n", item->text); + put_fmt(conio, "%s\n", item->text); break; case SDT_BATCH_ABORT: if (console_batch_mode) { - fprintf(stderr, "%s\n", item->text); - fflush(stderr); + put_fmt(conio, "%s\n", item->text); return NULL; } break; @@ -82,42 +390,35 @@ SeatPromptResult console_confirm_ssh_host_key( char *keystr, SeatDialogText *text, HelpCtx helpctx, void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { - HANDLE hin; - DWORD savemode, i; + ConsoleIO *conio = conio_setup(false, STD_ERROR_HANDLE); + SeatPromptResult result; - const char *prompt = console_print_seatdialogtext(text); - if (!prompt) - return SPR_SW_ABORT("Cannot confirm a host key in batch mode"); + const char *prompt = console_print_seatdialogtext(conio, text); + if (!prompt) { + result = SPR_SW_ABORT("Cannot confirm a host key in batch mode"); + goto out; + } - char line[32]; + ResponseType response; while (true) { - fprintf(stderr, - "%s (y/n, Return cancels connection, i for more info) ", + put_fmt(conio, "%s (y/n, Return cancels connection, i for more info) ", prompt); - fflush(stderr); - - line[0] = '\0'; /* fail safe if ReadFile returns no data */ - hin = GetStdHandle(STD_INPUT_HANDLE); - GetConsoleMode(hin, &savemode); - SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT | - ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT)); - ReadFile(hin, line, sizeof(line) - 1, &i, NULL); - SetConsoleMode(hin, savemode); + response = parse_and_free_response(console_read_line(conio, true)); - if (line[0] == 'i' || line[0] == 'I') { + if (response == RESPONSE_INFO) { for (SeatDialogTextItem *item = text->items, *end = item+text->nitems; item < end; item++) { switch (item->type) { case SDT_MORE_INFO_KEY: - fprintf(stderr, "%s", item->text); + put_dataz(conio, item->text); break; case SDT_MORE_INFO_VALUE_SHORT: - fprintf(stderr, ": %s\n", item->text); + put_fmt(conio, ": %s\n", item->text); break; case SDT_MORE_INFO_VALUE_BLOB: - fprintf(stderr, ":\n%s\n", item->text); + put_fmt(conio, ":\n%s\n", item->text); break; default: break; @@ -128,81 +429,75 @@ SeatPromptResult console_confirm_ssh_host_key( } } - /* In case of misplaced reflexes from another program, also recognise 'q' - * as 'abandon connection rather than trust this key' */ - if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n' && - line[0] != 'q' && line[0] != 'Q') { - if (line[0] == 'y' || line[0] == 'Y') - store_host_key(host, port, keytype, keystr); - return SPR_OK; + if (response == RESPONSE_YES || response == RESPONSE_NO) { + if (response == RESPONSE_YES) + store_host_key(seat, host, port, keytype, keystr); + result = SPR_OK; } else { - fputs(console_abandoned_msg, stderr); - return SPR_USER_ABORT; + put_dataz(conio, console_abandoned_msg); + result = SPR_USER_ABORT; } + out: + conio_free(conio); + return result; } SeatPromptResult console_confirm_weak_crypto_primitive( Seat *seat, SeatDialogText *text, void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { - HANDLE hin; - DWORD savemode, i; - - const char *prompt = console_print_seatdialogtext(text); - if (!prompt) - return SPR_SW_ABORT("Cannot confirm a weak crypto primitive " - "in batch mode"); - - char line[32]; + ConsoleIO *conio = conio_setup(false, STD_ERROR_HANDLE); + SeatPromptResult result; + + const char *prompt = console_print_seatdialogtext(conio, text); + if (!prompt) { + result = SPR_SW_ABORT("Cannot confirm a weak crypto primitive " + "in batch mode"); + goto out; + } - fprintf(stderr, "%s (y/n) ", prompt); - fflush(stderr); + put_fmt(conio, "%s (y/n) ", prompt); - hin = GetStdHandle(STD_INPUT_HANDLE); - GetConsoleMode(hin, &savemode); - SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT | - ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT)); - ReadFile(hin, line, sizeof(line) - 1, &i, NULL); - SetConsoleMode(hin, savemode); + ResponseType response = parse_and_free_response( + console_read_line(conio, true)); - if (line[0] == 'y' || line[0] == 'Y') { - return SPR_OK; + if (response == RESPONSE_YES) { + result = SPR_OK; } else { - fputs(console_abandoned_msg, stderr); - return SPR_USER_ABORT; + put_dataz(conio, console_abandoned_msg); + result = SPR_USER_ABORT; } + out: + conio_free(conio); + return result; } SeatPromptResult console_confirm_weak_cached_hostkey( Seat *seat, SeatDialogText *text, void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { - HANDLE hin; - DWORD savemode, i; + ConsoleIO *conio = conio_setup(false, STD_ERROR_HANDLE); + SeatPromptResult result; - const char *prompt = console_print_seatdialogtext(text); + const char *prompt = console_print_seatdialogtext(conio, text); if (!prompt) return SPR_SW_ABORT("Cannot confirm a weak cached host key " "in batch mode"); - char line[32]; + put_fmt(conio, "%s (y/n) ", prompt); - fprintf(stderr, "%s (y/n) ", prompt); - fflush(stderr); - - hin = GetStdHandle(STD_INPUT_HANDLE); - GetConsoleMode(hin, &savemode); - SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT | - ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT)); - ReadFile(hin, line, sizeof(line) - 1, &i, NULL); - SetConsoleMode(hin, savemode); + ResponseType response = parse_and_free_response( + console_read_line(conio, true)); - if (line[0] == 'y' || line[0] == 'Y') { - return SPR_OK; + if (response == RESPONSE_YES) { + result = SPR_OK; } else { - fputs(console_abandoned_msg, stderr); - return SPR_USER_ABORT; + put_dataz(conio, console_abandoned_msg); + result = SPR_USER_ABORT; } + + conio_free(conio); + return result; } bool is_interactive(void) @@ -258,9 +553,6 @@ bool console_has_mixed_input_stream(Seat *seat) int console_askappend(LogPolicy *lp, Filename *filename, void (*callback)(void *ctx, int result), void *ctx) { - HANDLE hin; - DWORD savemode, i; - static const char msgtemplate[] = "会话日志文件 \"%.*s\" 已经存在。\n" "可以使用新会话日志覆盖旧文件,\n" @@ -275,29 +567,28 @@ int console_askappend(LogPolicy *lp, Filename *filename, "会话日志文件 \"%.*s\" 已经存在。\n" "将不会启用日志记录。\n"; - char line[32]; + ConsoleIO *conio = conio_setup(true, STD_ERROR_HANDLE); + int result; if (console_batch_mode) { - fprintf(stderr, msgtemplate_batch, FILENAME_MAX, filename->path); - fflush(stderr); - return 0; + put_fmt(conio, msgtemplate_batch, FILENAME_MAX, filename->utf8path); + result = 0; + goto out; } - fprintf(stderr, msgtemplate, FILENAME_MAX, filename->path); - fflush(stderr); + put_fmt(conio, msgtemplate, FILENAME_MAX, filename->utf8path); + + ResponseType response = parse_and_free_response( + console_read_line(conio, true)); - hin = GetStdHandle(STD_INPUT_HANDLE); - GetConsoleMode(hin, &savemode); - SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT | - ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT)); - ReadFile(hin, line, sizeof(line) - 1, &i, NULL); - SetConsoleMode(hin, savemode); - - if (line[0] == 'y' || line[0] == 'Y') - return 2; - else if (line[0] == 'n' || line[0] == 'N') - return 1; + if (response == RESPONSE_YES) + result = 2; + else if (response == RESPONSE_NO) + result = 1; else - return 0; + result = 0; + out: + conio_free(conio); + return result; } /* @@ -364,15 +655,10 @@ StripCtrlChars *console_stripctrl_new( return stripctrl_new(bs_out, false, 0); } -static void console_write(HANDLE hout, ptrlen data) -{ - DWORD dummy; - WriteFile(hout, data.ptr, data.len, &dummy, NULL); -} - SeatPromptResult console_get_userpass_input(prompts_t *p) { - HANDLE hin = INVALID_HANDLE_VALUE, hout = INVALID_HANDLE_VALUE; + ConsoleIO *conio = conio_setup(p->utf8, STD_OUTPUT_HANDLE); + SeatPromptResult result; size_t curr_prompt; /* @@ -391,24 +677,10 @@ SeatPromptResult console_get_userpass_input(prompts_t *p) * need to ensure that we're able to get the answers. */ if (p->n_prompts) { - if (console_batch_mode) - return SPR_SW_ABORT("Cannot answer interactive prompts " - "in batch mode"); - hin = GetStdHandle(STD_INPUT_HANDLE); - if (hin == INVALID_HANDLE_VALUE) { - fprintf(stderr, "Cannot get standard input handle\n"); - cleanup_exit(1); - } - } - - /* - * And if we have anything to print, we need standard output. - */ - if ((p->name_reqd && p->name) || p->instruction || p->n_prompts) { - hout = GetStdHandle(STD_OUTPUT_HANDLE); - if (hout == INVALID_HANDLE_VALUE) { - fprintf(stderr, "Cannot get standard output handle\n"); - cleanup_exit(1); + if (console_batch_mode) { + result = SPR_SW_ABORT("Cannot answer interactive prompts " + "in batch mode"); + goto out; } } @@ -418,86 +690,40 @@ SeatPromptResult console_get_userpass_input(prompts_t *p) /* We only print the `name' caption if we have to... */ if (p->name_reqd && p->name) { ptrlen plname = ptrlen_from_asciz(p->name); - console_write(hout, plname); + put_datapl(conio, plname); if (!ptrlen_endswith(plname, PTRLEN_LITERAL("\n"), NULL)) - console_write(hout, PTRLEN_LITERAL("\n")); + put_datalit(conio, "\n"); } /* ...but we always print any `instruction'. */ if (p->instruction) { ptrlen plinst = ptrlen_from_asciz(p->instruction); - console_write(hout, plinst); + put_datapl(conio, plinst); if (!ptrlen_endswith(plinst, PTRLEN_LITERAL("\n"), NULL)) - console_write(hout, PTRLEN_LITERAL("\n")); + put_datalit(conio, "\n"); } for (curr_prompt = 0; curr_prompt < p->n_prompts; curr_prompt++) { - - DWORD savemode, newmode; prompt_t *pr = p->prompts[curr_prompt]; - GetConsoleMode(hin, &savemode); - newmode = savemode | ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT; - if (!pr->echo) - newmode &= ~ENABLE_ECHO_INPUT; - else - newmode |= ENABLE_ECHO_INPUT; - SetConsoleMode(hin, newmode); - - console_write(hout, ptrlen_from_asciz(pr->prompt)); + put_dataz(conio, pr->prompt); - bool failed = false; - SeatPromptResult spr; - while (1) { - /* - * Amount of data to try to read from the console in one - * go. This isn't completely arbitrary: a user reported - * that trying to read more than 31366 bytes at a time - * would fail with ERROR_NOT_ENOUGH_MEMORY on Windows 7, - * and Ruby's Win32 support module has evidence of a - * similar workaround: - * - * https://github.com/ruby/ruby/blob/0aa5195262d4193d3accf3e6b9bad236238b816b/win32/win32.c#L6842 - * - * To keep things simple, I stick with a nice round power - * of 2 rather than trying to go to the very limit of that - * bug. (We're typically reading user passphrases and the - * like here, so even this much is overkill really.) - */ - DWORD toread = 16384; - - size_t prev_result_len = pr->result->len; - void *ptr = strbuf_append(pr->result, toread); - - DWORD ret = 0; - if (!ReadFile(hin, ptr, toread, &ret, NULL)) { - /* An OS error when reading from the console is treated as an - * unexpected error and reported to the user. */ - failed = true; - spr = make_spr_sw_abort_winerror( - "Error reading from console", GetLastError()); - break; - } else if (ret == 0) { - /* Regard EOF on the terminal as a deliberate user-abort */ - failed = true; - spr = SPR_USER_ABORT; - break; - } - - strbuf_shrink_to(pr->result, prev_result_len + ret); + if (!console_read_line_to_strbuf(conio, pr->echo, pr->result)) { + result = make_spr_sw_abort_winerror( + "Error reading from console", GetLastError()); + goto out; + } else if (!pr->result->len) { + /* Regard EOF on the terminal as a deliberate user-abort */ + result = SPR_USER_ABORT; + goto out; + } else { if (strbuf_chomp(pr->result, '\n')) { strbuf_chomp(pr->result, '\r'); - break; } } - - SetConsoleMode(hin, savemode); - - if (!pr->echo) - console_write(hout, PTRLEN_LITERAL("\r\n")); - - if (failed) - return spr; } - return SPR_OK; + result = SPR_OK; + out: + conio_free(conio); + return result; } diff --git a/windows/controls.c b/windows/controls.c index 27f22be3..6fcca29e 100644 --- a/windows/controls.c +++ b/windows/controls.c @@ -393,13 +393,11 @@ char *staticwrap(struct ctlpos *cp, HWND hwnd, const char *text, int *lines) INT *pwidths, nfit; SIZE size; const char *p; - char *ret, *q; RECT r; HFONT oldfont, newfont; - ret = snewn(1+strlen(text), char); + strbuf *sb = strbuf_new(); p = text; - q = ret; pwidths = snewn(1+strlen(text), INT); /* @@ -432,7 +430,7 @@ char *staticwrap(struct ctlpos *cp, HWND hwnd, const char *text, int *lines) * Either way, we stop wrapping, copy the remainder of * the input string unchanged to the output, and leave. */ - strcpy(q, p); + put_datapl(sb, ptrlen_from_asciz(p)); break; } @@ -449,9 +447,8 @@ char *staticwrap(struct ctlpos *cp, HWND hwnd, const char *text, int *lines) } } - strncpy(q, p, nfit); - q[nfit] = '\n'; - q += nfit+1; + put_data(sb, p, nfit); + put_byte(sb, '\n'); p += nfit; while (*p && isspace((unsigned char)*p)) @@ -467,7 +464,7 @@ char *staticwrap(struct ctlpos *cp, HWND hwnd, const char *text, int *lines) sfree(pwidths); - return ret; + return strbuf_to_str(sb); } /* @@ -1181,30 +1178,24 @@ void progressbar(struct ctlpos *cp, int id) */ static char *shortcut_escape(const char *text, char shortcut) { - char *ret; - char const *p; - char *q; - if (!text) return NULL; /* sfree won't choke on this */ - ret = snewn(2*strlen(text)+1, char); /* size potentially doubles! */ + strbuf *sb = strbuf_new(); shortcut = tolower((unsigned char)shortcut); - p = text; - q = ret; + const char *p = text; while (*p) { if (shortcut != NO_SHORTCUT && tolower((unsigned char)*p) == shortcut) { - *q++ = '&'; + put_byte(sb, '&'); shortcut = NO_SHORTCUT; /* stop it happening twice */ } else if (*p == '&') { - *q++ = '&'; + put_byte(sb, '&'); } - *q++ = *p++; + put_byte(sb, *p++); } - *q = '\0'; - return ret; + return strbuf_to_str(sb); } void winctrl_add_shortcuts(struct dlgparam *dp, struct winctrl *c) @@ -1692,7 +1683,7 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, shortcuts[nshortcuts++] = ctrl->fontselect.shortcut; statictext(&pos, escaped, 1, base_id); staticbtn(&pos, "", base_id+1, "修改...", base_id+2); - data = fontspec_new("", false, 0, 0); + data = fontspec_new_default(); sfree(escaped); break; default: @@ -1997,32 +1988,36 @@ bool winctrl_handle_command(struct dlgparam *dp, UINT msg, (msg == WM_COMMAND && (HIWORD(wParam) == BN_CLICKED || HIWORD(wParam) == BN_DOUBLECLICKED))) { - OPENFILENAME of; - char filename[FILENAME_MAX]; + OPENFILENAMEW of; + wchar_t filename[FILENAME_MAX]; + + wchar_t *title_to_free = NULL; memset(&of, 0, sizeof(of)); of.hwndOwner = dp->hwnd; if (ctrl->fileselect.filter) of.lpstrFilter = ctrl->fileselect.filter; else - of.lpstrFilter = "所有文件 (*.*)\0*\0\0\0"; + of.lpstrFilter = L"所有文件 (*.*)\0*\0\0\0"; of.lpstrCustomFilter = NULL; of.nFilterIndex = 1; of.lpstrFile = filename; if (!ctrl->fileselect.just_button) { - GetDlgItemText(dp->hwnd, c->base_id+1, - filename, lenof(filename)); - filename[lenof(filename)-1] = '\0'; + GetDlgItemTextW(dp->hwnd, c->base_id+1, + filename, lenof(filename)); + filename[lenof(filename)-1] = L'\0'; } else { - *filename = '\0'; + *filename = L'\0'; } of.nMaxFile = lenof(filename); of.lpstrFileTitle = NULL; - of.lpstrTitle = ctrl->fileselect.title; + of.lpstrTitle = title_to_free = dup_mb_to_wc( + DEFAULT_CODEPAGE, ctrl->fileselect.title); of.Flags = 0; - if (request_file(NULL, &of, false, ctrl->fileselect.for_writing)) { + if (request_file_w(NULL, &of, false, + ctrl->fileselect.for_writing)) { if (!ctrl->fileselect.just_button) { - SetDlgItemText(dp->hwnd, c->base_id + 1, filename); + SetDlgItemTextW(dp->hwnd, c->base_id + 1, filename); ctrl->handler(ctrl, dp, dp->data, EVENT_VALCHANGE); } else { assert(!c->data); @@ -2031,6 +2026,8 @@ bool winctrl_handle_command(struct dlgparam *dp, UINT msg, c->data = NULL; } } + + sfree(title_to_free); } break; case CTRL_FONTSELECT: @@ -2213,6 +2210,15 @@ void dlg_editbox_set(dlgcontrol *ctrl, dlgparam *dp, char const *text) SetDlgItemText(dp->hwnd, c->base_id+1, text); } +void dlg_editbox_set_utf8(dlgcontrol *ctrl, dlgparam *dp, char const *text) +{ + struct winctrl *c = dlg_findbyctrl(dp, ctrl); + assert(c && c->ctrl->type == CTRL_EDITBOX); + wchar_t *wtext = dup_mb_to_wc(CP_UTF8, text); + SetDlgItemTextW(dp->hwnd, c->base_id+1, wtext); + sfree(wtext); +} + char *dlg_editbox_get(dlgcontrol *ctrl, dlgparam *dp) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); @@ -2220,6 +2226,16 @@ char *dlg_editbox_get(dlgcontrol *ctrl, dlgparam *dp) return GetDlgItemText_alloc(dp->hwnd, c->base_id+1); } +char *dlg_editbox_get_utf8(dlgcontrol *ctrl, dlgparam *dp) +{ + struct winctrl *c = dlg_findbyctrl(dp, ctrl); + assert(c && c->ctrl->type == CTRL_EDITBOX); + wchar_t *wtext = GetDlgItemTextW_alloc(dp->hwnd, c->base_id+1); + char *text = dup_wc_to_mb(CP_UTF8, wtext, ""); + sfree(wtext); + return text; +} + void dlg_editbox_select_range(dlgcontrol *ctrl, dlgparam *dp, size_t start, size_t len) { @@ -2403,19 +2419,17 @@ void dlg_filesel_set(dlgcontrol *ctrl, dlgparam *dp, Filename *fn) assert(c); assert(c->ctrl->type == CTRL_FILESELECT); assert(!c->ctrl->fileselect.just_button); - SetDlgItemText(dp->hwnd, c->base_id+1, fn->path); + SetDlgItemTextW(dp->hwnd, c->base_id+1, fn->wpath); } Filename *dlg_filesel_get(dlgcontrol *ctrl, dlgparam *dp) { struct winctrl *c = dlg_findbyctrl(dp, ctrl); - char *tmp; - Filename *ret; assert(c); assert(c->ctrl->type == CTRL_FILESELECT); if (!c->ctrl->fileselect.just_button) { - tmp = GetDlgItemText_alloc(dp->hwnd, c->base_id+1); - ret = filename_from_str(tmp); + wchar_t *tmp = GetDlgItemTextW_alloc(dp->hwnd, c->base_id+1); + Filename *ret = filename_from_wstr(tmp); sfree(tmp); return ret; } else { diff --git a/windows/dialog.c b/windows/dialog.c index bb108530..2e777430 100644 --- a/windows/dialog.c +++ b/windows/dialog.c @@ -300,35 +300,21 @@ static INT_PTR CALLBACK LogProc(HWND hwnd, UINT msg, LB_GETSELITEMS, selcount, (LPARAM) selitems); - int i; - int size; - char *clipdata; - static unsigned char sel_nl[] = SEL_NL; + static const unsigned char sel_nl[] = SEL_NL; if (count == 0) { /* can't copy zero stuff */ MessageBeep(0); break; } - size = 0; - for (i = 0; i < count; i++) - size += - strlen(getevent(selitems[i])) + sizeof(sel_nl); - - clipdata = snewn(size, char); - if (clipdata) { - char *p = clipdata; - for (i = 0; i < count; i++) { - char *q = getevent(selitems[i]); - int qlen = strlen(q); - memcpy(p, q, qlen); - p += qlen; - memcpy(p, sel_nl, sizeof(sel_nl)); - p += sizeof(sel_nl); - } - write_aclip(CLIP_SYSTEM, clipdata, size, true); - sfree(clipdata); + strbuf *sb = strbuf_new(); + for (int i = 0; i < count; i++) { + char *q = getevent(selitems[i]); + put_datapl(sb, ptrlen_from_asciz(q)); + put_data(sb, sel_nl, sizeof(sel_nl)); } + write_aclip(hwnd, CLIP_SYSTEM, sb->s, sb->len); + strbuf_free(sb); sfree(selitems); for (i = 0; i < (ninitial + ncircular); i++) @@ -473,7 +459,7 @@ static HTREEITEM treeview_insert(struct treeview_faff *faff, return newitem; } -const char *dialog_box_demo_screenshot_filename = NULL; +Filename *dialog_box_demo_screenshot_filename = NULL; /* ctrltrees indices for the main dialog box */ enum { @@ -1173,7 +1159,7 @@ SeatPromptResult win_seat_confirm_ssh_host_key( wgs->term_hwnd, HostKeyDialogProc, ctx); assert(mbret==IDC_HK_ACCEPT || mbret==IDC_HK_ONCE || mbret==IDCANCEL); if (mbret == IDC_HK_ACCEPT) { - store_host_key(host, port, keytype, keystr); + store_host_key(seat, host, port, keytype, keystr); return SPR_OK; } else if (mbret == IDC_HK_ONCE) { return SPR_OK; @@ -1241,11 +1227,12 @@ static int win_gui_askappend(LogPolicy *lp, Filename *filename, char *mbtitle; int mbret; - message = dupprintf(msgtemplate, FILENAME_MAX, filename->path); + message = dupprintf(msgtemplate, FILENAME_MAX, filename->utf8path); mbtitle = dupprintf("%s 日志记录到文件", appname); - mbret = MessageBox(NULL, message, mbtitle, - MB_ICONQUESTION | MB_YESNOCANCEL | MB_DEFBUTTON3); + mbret = message_box(NULL, message, mbtitle, + MB_ICONQUESTION | MB_YESNOCANCEL | MB_DEFBUTTON3, + true, 0); socket_reselect_all(); diff --git a/windows/gss.c b/windows/gss.c index 724eeec1..4f21400c 100644 --- a/windows/gss.c +++ b/windows/gss.c @@ -26,7 +26,7 @@ if (uli.QuadPart != 0) \ uli.QuadPart = uli.QuadPart / CNS_PERSEC - UNIX_EPOCH; \ (t) = (time_t) uli.QuadPart; \ -} while(0) +} while (0) /* Windows code to set up the GSSAPI library list. */ @@ -118,9 +118,7 @@ static void add_library_to_never_unload_tree(HMODULE module) struct ssh_gss_liblist *ssh_gss_setup(Conf *conf) { HMODULE module; - HKEY regkey; struct ssh_gss_liblist *list = snew(struct ssh_gss_liblist); - char *path; static HMODULE kernel32_module; if (!kernel32_module) { kernel32_module = load_system32_dll("kernel32.dll"); @@ -137,55 +135,47 @@ struct ssh_gss_liblist *ssh_gss_setup(Conf *conf) /* MIT Kerberos GSSAPI implementation */ module = NULL; - if (RegOpenKey(HKEY_LOCAL_MACHINE, "SOFTWARE\\MIT\\Kerberos", ®key) - == ERROR_SUCCESS) { - DWORD type, size; - LONG ret; - char *buffer; - - /* Find out the string length */ - ret = RegQueryValueEx(regkey, "InstallDir", NULL, &type, NULL, &size); - - if (ret == ERROR_SUCCESS && type == REG_SZ) { - buffer = snewn(size + 20, char); - ret = RegQueryValueEx(regkey, "InstallDir", NULL, - &type, (LPBYTE)buffer, &size); - if (ret == ERROR_SUCCESS && type == REG_SZ) { - strcat (buffer, "\\bin"); - if(p_AddDllDirectory) { - /* Add MIT Kerberos' path to the DLL search path, - * it loads its own DLLs further down the road */ - wchar_t *dllPath = - dup_mb_to_wc(DEFAULT_CODEPAGE, 0, buffer); - p_AddDllDirectory(dllPath); - sfree(dllPath); - } - strcat (buffer, "\\gssapi"MIT_KERB_SUFFIX".dll"); - module = LoadLibraryEx (buffer, NULL, - LOAD_LIBRARY_SEARCH_SYSTEM32 | - LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | - LOAD_LIBRARY_SEARCH_USER_DIRS); - - /* - * The MIT Kerberos DLL suffers an internal segfault - * for some reason if you unload and reload one within - * the same process. So, make sure that after we load - * this library, we never free it. - * - * Or rather: after we've loaded it once, if any - * _further_ load returns the same module handle, we - * immediately free it again (to prevent the Windows - * API's internal reference count growing without - * bound). But on the other hand we never free it in - * ssh_gss_cleanup. - */ - if (library_is_in_never_unload_tree(module)) - FreeLibrary(module); - add_library_to_never_unload_tree(module); + HKEY regkey = open_regkey_ro(HKEY_LOCAL_MACHINE, + "SOFTWARE\\MIT\\Kerberos"); + if (regkey) { + char *installdir = get_reg_sz(regkey, "InstallDir"); + if (installdir) { + char *bindir = dupcat(installdir, "\\bin"); + if (p_AddDllDirectory) { + /* Add MIT Kerberos' path to the DLL search path, + * it loads its own DLLs further down the road */ + wchar_t *dllPath = dup_mb_to_wc(DEFAULT_CODEPAGE, bindir); + p_AddDllDirectory(dllPath); + sfree(dllPath); } - sfree(buffer); + + char *dllfile = dupcat(bindir, "\\gssapi"MIT_KERB_SUFFIX".dll"); + module = LoadLibraryEx(dllfile, NULL, + LOAD_LIBRARY_SEARCH_SYSTEM32 | + LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | + LOAD_LIBRARY_SEARCH_USER_DIRS); + + /* + * The MIT Kerberos DLL suffers an internal segfault for + * some reason if you unload and reload one within the + * same process. So, make sure that after we load this + * library, we never free it. + * + * Or rather: after we've loaded it once, if any _further_ + * load returns the same module handle, we immediately + * free it again (to prevent the Windows API's internal + * reference count growing without bound). But on the + * other hand we never free it in ssh_gss_cleanup. + */ + if (library_is_in_never_unload_tree(module)) + FreeLibrary(module); + add_library_to_never_unload_tree(module); + + sfree(dllfile); + sfree(bindir); + sfree(installdir); } - RegCloseKey(regkey); + close_regkey(regkey); } if (module) { struct ssh_gss_library *lib = @@ -242,34 +232,36 @@ struct ssh_gss_liblist *ssh_gss_setup(Conf *conf) * Custom GSSAPI DLL. */ module = NULL; - path = conf_get_filename(conf, CONF_ssh_gss_custom)->path; - if (*path) { - if(p_AddDllDirectory) { + Filename *customlib = conf_get_filename(conf, CONF_ssh_gss_custom); + if (!filename_is_null(customlib)) { + const wchar_t *path = customlib->wpath; + if (p_AddDllDirectory) { + /* Add the custom directory as well in case it chainloads * some other DLLs (e.g a non-installed MIT Kerberos * instance) */ - int pathlen = strlen(path); + int pathlen = wcslen(path); - while (pathlen > 0 && path[pathlen-1] != ':' && - path[pathlen-1] != '\\') + while (pathlen > 0 && path[pathlen-1] != L':' && + path[pathlen-1] != L'\\') pathlen--; - if (pathlen > 0 && path[pathlen-1] != '\\') + if (pathlen > 0 && path[pathlen-1] != L'\\') pathlen--; if (pathlen > 0) { - char *dirpath = dupprintf("%.*s", pathlen, path); - wchar_t *dllPath = dup_mb_to_wc(DEFAULT_CODEPAGE, 0, dirpath); - p_AddDllDirectory(dllPath); - sfree(dllPath); + wchar_t *dirpath = snewn(pathlen + 1, wchar_t); + memcpy(dirpath, path, pathlen * sizeof(wchar_t)); + dirpath[pathlen] = L'\0'; + p_AddDllDirectory(dirpath); sfree(dirpath); } } - module = LoadLibraryEx(path, NULL, - LOAD_LIBRARY_SEARCH_SYSTEM32 | - LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | - LOAD_LIBRARY_SEARCH_USER_DIRS); + module = LoadLibraryExW(path, NULL, + LOAD_LIBRARY_SEARCH_SYSTEM32 | + LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | + LOAD_LIBRARY_SEARCH_USER_DIRS); } if (module) { struct ssh_gss_library *lib = @@ -277,7 +269,7 @@ struct ssh_gss_liblist *ssh_gss_setup(Conf *conf) lib->id = 2; lib->gsslogmsg = dupprintf("Using GSSAPI from user-specified" - " library '%s'", path); + " library '%s'", customlib->cpath); lib->handle = (void *)module; #define BIND_GSS_FN(name) \ @@ -460,8 +452,8 @@ static Ssh_gss_stat ssh_sspi_init_sec_context(struct ssh_gss_library *lib, winSsh_gss_ctx *winctx = (winSsh_gss_ctx *) *ctx; SecBuffer wsend_tok = {send_tok->length,SECBUFFER_TOKEN,send_tok->value}; SecBuffer wrecv_tok = {recv_tok->length,SECBUFFER_TOKEN,recv_tok->value}; - SecBufferDesc output_desc={SECBUFFER_VERSION,1,&wsend_tok}; - SecBufferDesc input_desc ={SECBUFFER_VERSION,1,&wrecv_tok}; + SecBufferDesc output_desc = {SECBUFFER_VERSION,1,&wsend_tok}; + SecBufferDesc input_desc = {SECBUFFER_VERSION,1,&wrecv_tok}; unsigned long flags=ISC_REQ_MUTUAL_AUTH|ISC_REQ_REPLAY_DETECT| ISC_REQ_CONFIDENTIALITY|ISC_REQ_ALLOCATE_MEMORY; ULONG ret_flags=0; @@ -512,7 +504,7 @@ static Ssh_gss_stat ssh_sspi_free_tok(struct ssh_gss_library *lib, static Ssh_gss_stat ssh_sspi_release_cred(struct ssh_gss_library *lib, Ssh_gss_ctx *ctx) { - winSsh_gss_ctx *winctx= (winSsh_gss_ctx *) *ctx; + winSsh_gss_ctx *winctx = (winSsh_gss_ctx *) *ctx; /* check input */ if (winctx == NULL) return SSH_GSS_FAILURE; @@ -532,7 +524,7 @@ static Ssh_gss_stat ssh_sspi_release_cred(struct ssh_gss_library *lib, static Ssh_gss_stat ssh_sspi_release_name(struct ssh_gss_library *lib, Ssh_gss_name *srv_name) { - char *pStr= (char *) *srv_name; + char *pStr = (char *) *srv_name; if (pStr == NULL) return SSH_GSS_FAILURE; sfree(pStr); @@ -596,7 +588,7 @@ static Ssh_gss_stat ssh_sspi_get_mic(struct ssh_gss_library *lib, Ssh_gss_ctx ctx, Ssh_gss_buf *buf, Ssh_gss_buf *hash) { - winSsh_gss_ctx *winctx= (winSsh_gss_ctx *) ctx; + winSsh_gss_ctx *winctx = (winSsh_gss_ctx *) ctx; SecPkgContext_Sizes ContextSizes; SecBufferDesc InputBufferDescriptor; SecBuffer InputSecurityToken[2]; @@ -643,7 +635,7 @@ static Ssh_gss_stat ssh_sspi_verify_mic(struct ssh_gss_library *lib, Ssh_gss_buf *buf, Ssh_gss_buf *mic) { - winSsh_gss_ctx *winctx= (winSsh_gss_ctx *) ctx; + winSsh_gss_ctx *winctx = (winSsh_gss_ctx *) ctx; SecBufferDesc InputBufferDescriptor; SecBuffer InputSecurityToken[2]; ULONG qop; diff --git a/windows/handle-socket.c b/windows/handle-socket.c index 2820975c..47fa4e29 100644 --- a/windows/handle-socket.c +++ b/windows/handle-socket.c @@ -107,7 +107,7 @@ static size_t handle_stderr( HandleSocket *hs = (HandleSocket *)handle_get_privdata(h); if (!err && len > 0) - log_proxy_stderr(hs->plug, &hs->psb, data, len); + log_proxy_stderr(hs->plug, &hs->sock, &hs->psb, data, len); return 0; } @@ -296,7 +296,7 @@ static const char *sk_handle_socket_error(Socket *s) return hs->error; } -static SocketPeerInfo *sk_handle_peer_info(Socket *s) +static SocketEndpointInfo *sk_handle_endpoint_info(Socket *s, bool peer) { HandleSocket *hs = container_of(s, HandleSocket, sock); ULONG pid; @@ -304,6 +304,9 @@ static SocketPeerInfo *sk_handle_peer_info(Socket *s) DECL_WINDOWS_FUNCTION(static, BOOL, GetNamedPipeClientProcessId, (HANDLE, PULONG)); + if (!peer) + return NULL; + if (!kernel32_module) { kernel32_module = load_system32_dll("kernel32.dll"); #if !HAVE_GETNAMEDPIPECLIENTPROCESSID @@ -326,7 +329,7 @@ static SocketPeerInfo *sk_handle_peer_info(Socket *s) */ if (p_GetNamedPipeClientProcessId && p_GetNamedPipeClientProcessId(hs->send_H, &pid)) { - SocketPeerInfo *pi = snew(SocketPeerInfo); + SocketEndpointInfo *pi = snew(SocketEndpointInfo); pi->addressfamily = ADDRTYPE_LOCAL; pi->addr_text = NULL; pi->port = -1; @@ -345,13 +348,14 @@ static const SocketVtable HandleSocket_sockvt = { .write_eof = sk_handle_write_eof, .set_frozen = sk_handle_set_frozen, .socket_error = sk_handle_socket_error, - .peer_info = sk_handle_peer_info, + .endpoint_info = sk_handle_endpoint_info, }; static void sk_handle_connect_success_callback(void *ctx) { HandleSocket *hs = (HandleSocket *)ctx; - plug_log(hs->plug, PLUGLOG_CONNECT_SUCCESS, hs->addr, hs->port, NULL, 0); + plug_log(hs->plug, &hs->sock, PLUGLOG_CONNECT_SUCCESS, hs->addr, hs->port, + NULL, 0); } Socket *make_handle_socket(HANDLE send_H, HANDLE recv_H, HANDLE stderr_H, @@ -431,7 +435,8 @@ static void sk_handle_deferred_set_frozen(Socket *s, bool is_frozen) hs->frozen = is_frozen; } -static SocketPeerInfo *sk_handle_deferred_peer_info(Socket *s) +static SocketEndpointInfo *sk_handle_deferred_endpoint_info( + Socket *s, bool peer) { return NULL; } @@ -444,7 +449,7 @@ static const SocketVtable HandleSocket_deferred_sockvt = { .write_eof = sk_handle_deferred_write_eof, .set_frozen = sk_handle_deferred_set_frozen, .socket_error = sk_handle_socket_error, - .peer_info = sk_handle_deferred_peer_info, + .endpoint_info = sk_handle_deferred_endpoint_info, }; Socket *make_deferred_handle_socket(DeferredSocketOpener *opener, diff --git a/windows/help.h b/windows/help.h index cfca4e1c..8430da58 100644 --- a/windows/help.h +++ b/windows/help.h @@ -45,6 +45,7 @@ typedef const char *HelpCtx; #define WINHELP_CTX_features_clearscroll "config-features-clearscroll" #define WINHELP_CTX_features_arabicshaping "config-features-shaping" #define WINHELP_CTX_features_bidi "config-features-bidi" +#define WINHELP_CTX_features_bracketed_paste "config-features-bracketed-paste" #define WINHELP_CTX_terminal_autowrap "config-autowrap" #define WINHELP_CTX_terminal_decom "config-decom" #define WINHELP_CTX_terminal_lfhascr "config-crlf" diff --git a/windows/msifixup.py b/windows/msifixup.py index 11606417..997ab04d 100755 --- a/windows/msifixup.py +++ b/windows/msifixup.py @@ -5,12 +5,12 @@ import tempfile import shutil import subprocess -import pipes +import shlex def run(command, verbose): if verbose: sys.stdout.write("$ {}\n".format(" ".join( - pipes.quote(word) for word in command))) + shlex.quote(word) for word in command))) out = subprocess.check_output(command) if verbose: sys.stdout.write("".join( diff --git a/windows/named-pipe-server.c b/windows/named-pipe-server.c index 87adb940..24f0c8d9 100644 --- a/windows/named-pipe-server.c +++ b/windows/named-pipe-server.c @@ -63,11 +63,6 @@ static const char *sk_namedpipeserver_socket_error(Socket *s) return ps->error; } -static SocketPeerInfo *sk_namedpipeserver_peer_info(Socket *s) -{ - return NULL; -} - static bool create_named_pipe(NamedPipeServerSocket *ps, bool first_instance) { SECURITY_ATTRIBUTES sa; @@ -175,7 +170,7 @@ static void named_pipe_accept_loop(NamedPipeServerSocket *ps, errmsg = dupprintf("Error while listening to named pipe: %s", win_strerror(error)); - plug_log(ps->plug, 1, sk_namedpipe_addr(ps->pipename), 0, + plug_log(ps->plug, &ps->sock, 1, sk_namedpipe_addr(ps->pipename), 0, errmsg, error); sfree(errmsg); break; @@ -196,40 +191,40 @@ static const SocketVtable NamedPipeServerSocket_sockvt = { .plug = sk_namedpipeserver_plug, .close = sk_namedpipeserver_close, .socket_error = sk_namedpipeserver_socket_error, - .peer_info = sk_namedpipeserver_peer_info, + .endpoint_info = nullsock_endpoint_info, }; Socket *new_named_pipe_listener(const char *pipename, Plug *plug) { - NamedPipeServerSocket *ret = snew(NamedPipeServerSocket); - ret->sock.vt = &NamedPipeServerSocket_sockvt; - ret->plug = plug; - ret->error = NULL; - ret->psd = NULL; - ret->pipename = dupstr(pipename); - ret->acl = NULL; - ret->callback_handle = NULL; + NamedPipeServerSocket *ps = snew(NamedPipeServerSocket); + ps->sock.vt = &NamedPipeServerSocket_sockvt; + ps->plug = plug; + ps->error = NULL; + ps->psd = NULL; + ps->pipename = dupstr(pipename); + ps->acl = NULL; + ps->callback_handle = NULL; assert(strncmp(pipename, "\\\\.\\pipe\\", 9) == 0); assert(strchr(pipename + 9, '\\') == NULL); if (!make_private_security_descriptor(GENERIC_READ | GENERIC_WRITE, - &ret->psd, &ret->acl, &ret->error)) { + &ps->psd, &ps->acl, &ps->error)) { goto cleanup; } - if (!create_named_pipe(ret, true)) { - ret->error = dupprintf("unable to create named pipe '%s': %s", + if (!create_named_pipe(ps, true)) { + ps->error = dupprintf("unable to create named pipe '%s': %s", pipename, win_strerror(GetLastError())); goto cleanup; } - memset(&ret->connect_ovl, 0, sizeof(ret->connect_ovl)); - ret->connect_ovl.hEvent = CreateEvent(NULL, true, false, NULL); - ret->callback_handle = add_handle_wait( - ret->connect_ovl.hEvent, named_pipe_connect_callback, ret); - named_pipe_accept_loop(ret, false); + memset(&ps->connect_ovl, 0, sizeof(ps->connect_ovl)); + ps->connect_ovl.hEvent = CreateEvent(NULL, true, false, NULL); + ps->callback_handle = add_handle_wait( + ps->connect_ovl.hEvent, named_pipe_connect_callback, ps); + named_pipe_accept_loop(ps, false); cleanup: - return &ret->sock; + return &ps->sock; } diff --git a/windows/network.c b/windows/network.c index 2c087b69..de32aefd 100644 --- a/windows/network.c +++ b/windows/network.c @@ -209,6 +209,8 @@ DECL_WINDOWS_FUNCTION(static, SOCKET, accept, (SOCKET, struct sockaddr FAR *, int FAR *)); DECL_WINDOWS_FUNCTION(static, int, getpeername, (SOCKET, struct sockaddr FAR *, int FAR *)); +DECL_WINDOWS_FUNCTION(static, int, getsockname, + (SOCKET, struct sockaddr FAR *, int FAR *)); DECL_WINDOWS_FUNCTION(static, int, recv, (SOCKET, char FAR *, int, int)); DECL_WINDOWS_FUNCTION(static, int, WSAIoctl, (SOCKET, DWORD, LPVOID, DWORD, LPVOID, DWORD, @@ -332,6 +334,7 @@ void sk_init(void) GET_WINDOWS_FUNCTION(winsock_module, ioctlsocket); GET_WINDOWS_FUNCTION(winsock_module, accept); GET_WINDOWS_FUNCTION(winsock_module, getpeername); + GET_WINDOWS_FUNCTION(winsock_module, getsockname); GET_WINDOWS_FUNCTION(winsock_module, recv); GET_WINDOWS_FUNCTION(winsock_module, WSAIoctl); @@ -547,18 +550,18 @@ SockAddr *sk_namelookup(const char *host, char **canonicalname, static SockAddr *sk_special_addr(SuperFamily superfamily, const char *name) { - SockAddr *ret = snew(SockAddr); - ret->error = NULL; - ret->superfamily = superfamily; + SockAddr *addr = snew(SockAddr); + addr->error = NULL; + addr->superfamily = superfamily; #ifndef NO_IPV6 - ret->ais = NULL; + addr->ais = NULL; #endif - ret->addresses = NULL; - ret->naddresses = 0; - ret->refcount = 1; - strncpy(ret->hostname, name, lenof(ret->hostname)); - ret->hostname[lenof(ret->hostname)-1] = '\0'; - return ret; + addr->addresses = NULL; + addr->naddresses = 0; + addr->refcount = 1; + strncpy(addr->hostname, name, lenof(addr->hostname)); + addr->hostname[lenof(addr->hostname)-1] = '\0'; + return addr; } SockAddr *sk_nonamelookup(const char *host) @@ -821,7 +824,7 @@ static size_t sk_net_write_oob(Socket *s, const void *data, size_t len); static void sk_net_write_eof(Socket *s); static void sk_net_set_frozen(Socket *s, bool is_frozen); static const char *sk_net_socket_error(Socket *s); -static SocketPeerInfo *sk_net_peer_info(Socket *s); +static SocketEndpointInfo *sk_net_endpoint_info(Socket *s, bool peer); static const SocketVtable NetSocket_sockvt = { .plug = sk_net_plug, @@ -831,54 +834,54 @@ static const SocketVtable NetSocket_sockvt = { .write_eof = sk_net_write_eof, .set_frozen = sk_net_set_frozen, .socket_error = sk_net_socket_error, - .peer_info = sk_net_peer_info, + .endpoint_info = sk_net_endpoint_info, }; static Socket *sk_net_accept(accept_ctx_t ctx, Plug *plug) { DWORD err; const char *errstr; - NetSocket *ret; + NetSocket *s; /* * Create NetSocket structure. */ - ret = snew(NetSocket); - ret->sock.vt = &NetSocket_sockvt; - ret->error = NULL; - ret->plug = plug; - bufchain_init(&ret->output_data); - ret->writable = true; /* to start with */ - ret->sending_oob = 0; - ret->outgoingeof = EOF_NO; - ret->frozen = true; - ret->frozen_readable = false; - ret->localhost_only = false; /* unused, but best init anyway */ - ret->pending_error = 0; - ret->parent = ret->child = NULL; - ret->addr = NULL; - - ret->s = (SOCKET)ctx.p; - - if (ret->s == INVALID_SOCKET) { + s = snew(NetSocket); + s->sock.vt = &NetSocket_sockvt; + s->error = NULL; + s->plug = plug; + bufchain_init(&s->output_data); + s->writable = true; /* to start with */ + s->sending_oob = 0; + s->outgoingeof = EOF_NO; + s->frozen = true; + s->frozen_readable = false; + s->localhost_only = false; /* unused, but best init anyway */ + s->pending_error = 0; + s->parent = s->child = NULL; + s->addr = NULL; + + s->s = (SOCKET)ctx.p; + + if (s->s == INVALID_SOCKET) { err = p_WSAGetLastError(); - ret->error = winsock_error_string(err); - return &ret->sock; + s->error = winsock_error_string(err); + return &s->sock; } - ret->oobinline = false; + s->oobinline = false; /* Set up a select mechanism. This could be an AsyncSelect on a * window, or an EventSelect on an event object. */ - errstr = do_select(ret->s, true); + errstr = do_select(s->s, true); if (errstr) { - ret->error = errstr; - return &ret->sock; + s->error = errstr; + return &s->sock; } - add234(sktree, ret); + add234(sktree, s); - return &ret->sock; + return &s->sock; } static DWORD try_connect(NetSocket *sock) @@ -901,7 +904,7 @@ static DWORD try_connect(NetSocket *sock) { SockAddr thisaddr = sk_extractaddr_tmp( sock->addr, &sock->step); - plug_log(sock->plug, PLUGLOG_CONNECT_TRYING, + plug_log(sock->plug, &sock->sock, PLUGLOG_CONNECT_TRYING, &thisaddr, sock->port, NULL, 0); } @@ -1062,7 +1065,7 @@ static DWORD try_connect(NetSocket *sock) */ sock->writable = true; SockAddr thisaddr = sk_extractaddr_tmp(sock->addr, &sock->step); - plug_log(sock->plug, PLUGLOG_CONNECT_SUCCESS, + plug_log(sock->plug, &sock->sock, PLUGLOG_CONNECT_SUCCESS, &thisaddr, sock->port, NULL, 0); } @@ -1078,7 +1081,7 @@ static DWORD try_connect(NetSocket *sock) if (err) { SockAddr thisaddr = sk_extractaddr_tmp( sock->addr, &sock->step); - plug_log(sock->plug, PLUGLOG_CONNECT_FAILED, + plug_log(sock->plug, &sock->sock, PLUGLOG_CONNECT_FAILED, &thisaddr, sock->port, sock->error, err); } return err; @@ -1087,48 +1090,48 @@ static DWORD try_connect(NetSocket *sock) Socket *sk_new(SockAddr *addr, int port, bool privport, bool oobinline, bool nodelay, bool keepalive, Plug *plug) { - NetSocket *ret; + NetSocket *s; DWORD err; /* * Create NetSocket structure. */ - ret = snew(NetSocket); - ret->sock.vt = &NetSocket_sockvt; - ret->error = NULL; - ret->plug = plug; - bufchain_init(&ret->output_data); - ret->connected = false; /* to start with */ - ret->writable = false; /* to start with */ - ret->sending_oob = 0; - ret->outgoingeof = EOF_NO; - ret->frozen = false; - ret->frozen_readable = false; - ret->localhost_only = false; /* unused, but best init anyway */ - ret->pending_error = 0; - ret->parent = ret->child = NULL; - ret->oobinline = oobinline; - ret->nodelay = nodelay; - ret->keepalive = keepalive; - ret->privport = privport; - ret->port = port; - ret->addr = addr; - START_STEP(ret->addr, ret->step); - ret->s = INVALID_SOCKET; + s = snew(NetSocket); + s->sock.vt = &NetSocket_sockvt; + s->error = NULL; + s->plug = plug; + bufchain_init(&s->output_data); + s->connected = false; /* to start with */ + s->writable = false; /* to start with */ + s->sending_oob = 0; + s->outgoingeof = EOF_NO; + s->frozen = false; + s->frozen_readable = false; + s->localhost_only = false; /* unused, but best init anyway */ + s->pending_error = 0; + s->parent = s->child = NULL; + s->oobinline = oobinline; + s->nodelay = nodelay; + s->keepalive = keepalive; + s->privport = privport; + s->port = port; + s->addr = addr; + START_STEP(s->addr, s->step); + s->s = INVALID_SOCKET; err = 0; do { - err = try_connect(ret); - } while (err && sk_nextaddr(ret->addr, &ret->step)); + err = try_connect(s); + } while (err && sk_nextaddr(s->addr, &s->step)); - return &ret->sock; + return &s->sock; } static Socket *sk_newlistener_internal( const char *srcaddr, int port, Plug *plug, bool local_host_only, int orig_address_family) { - SOCKET s; + SOCKET sk; SOCKADDR_IN a; #ifndef NO_IPV6 SOCKADDR_IN6 a6; @@ -1141,7 +1144,7 @@ static Socket *sk_newlistener_internal( DWORD err; const char *errstr; - NetSocket *ret; + NetSocket *s; int retcode; int address_family = orig_address_family; @@ -1149,20 +1152,20 @@ static Socket *sk_newlistener_internal( /* * Create NetSocket structure. */ - ret = snew(NetSocket); - ret->sock.vt = &NetSocket_sockvt; - ret->error = NULL; - ret->plug = plug; - bufchain_init(&ret->output_data); - ret->writable = false; /* to start with */ - ret->sending_oob = 0; - ret->outgoingeof = EOF_NO; - ret->frozen = false; - ret->frozen_readable = false; - ret->localhost_only = local_host_only; - ret->pending_error = 0; - ret->parent = ret->child = NULL; - ret->addr = NULL; + s = snew(NetSocket); + s->sock.vt = &NetSocket_sockvt; + s->error = NULL; + s->plug = plug; + bufchain_init(&s->output_data); + s->writable = false; /* to start with */ + s->sending_oob = 0; + s->outgoingeof = EOF_NO; + s->frozen = false; + s->frozen_readable = false; + s->localhost_only = local_host_only; + s->pending_error = 0; + s->parent = s->child = NULL; + s->addr = NULL; /* * Our default, if passed the `don't care' value @@ -1176,25 +1179,25 @@ static Socket *sk_newlistener_internal( /* * Open socket. */ - s = p_socket(address_family, SOCK_STREAM, 0); - ret->s = s; + sk = p_socket(address_family, SOCK_STREAM, 0); + s->s = sk; - if (s == INVALID_SOCKET) { + if (sk == INVALID_SOCKET) { err = p_WSAGetLastError(); - ret->error = winsock_error_string(err); - return &ret->sock; + s->error = winsock_error_string(err); + return &s->sock; } - SetHandleInformation((HANDLE)s, HANDLE_FLAG_INHERIT, 0); + SetHandleInformation((HANDLE)sk, HANDLE_FLAG_INHERIT, 0); - ret->oobinline = false; + s->oobinline = false; #if HAVE_AFUNIX_H if (address_family != AF_UNIX) #endif { BOOL on = true; - p_setsockopt(s, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, + p_setsockopt(sk, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (const char *)&on, sizeof(on)); } @@ -1244,7 +1247,7 @@ static Socket *sk_newlistener_internal( a.sin_addr.s_addr = p_inet_addr(srcaddr); if (a.sin_addr.s_addr != INADDR_NONE) { /* Override localhost_only with specified listen addr. */ - ret->localhost_only = ipv4_is_loopback(a.sin_addr); + s->localhost_only = ipv4_is_loopback(a.sin_addr); got_addr = true; } } @@ -1277,7 +1280,7 @@ static Socket *sk_newlistener_internal( unreachable("bad address family in sk_newlistener_internal"); } - retcode = p_bind(s, bindaddr, bindsize); + retcode = p_bind(sk, bindaddr, bindsize); if (retcode != SOCKET_ERROR) { err = 0; } else { @@ -1285,28 +1288,28 @@ static Socket *sk_newlistener_internal( } if (err) { - p_closesocket(s); - ret->error = winsock_error_string(err); - return &ret->sock; + p_closesocket(sk); + s->error = winsock_error_string(err); + return &s->sock; } - if (p_listen(s, SOMAXCONN) == SOCKET_ERROR) { - p_closesocket(s); - ret->error = winsock_error_string(p_WSAGetLastError()); - return &ret->sock; + if (p_listen(sk, SOMAXCONN) == SOCKET_ERROR) { + p_closesocket(sk); + s->error = winsock_error_string(p_WSAGetLastError()); + return &s->sock; } /* Set up a select mechanism. This could be an AsyncSelect on a * window, or an EventSelect on an event object. */ - errstr = do_select(s, true); + errstr = do_select(sk, true); if (errstr) { - p_closesocket(s); - ret->error = errstr; - return &ret->sock; + p_closesocket(sk); + s->error = errstr; + return &s->sock; } - add234(sktree, ret); + add234(sktree, s); #ifndef NO_IPV6 /* @@ -1320,8 +1323,8 @@ static Socket *sk_newlistener_internal( if (other) { NetSocket *ns = container_of(other, NetSocket, sock); if (!ns->error) { - ns->parent = ret; - ret->child = ns; + ns->parent = s; + s->child = ns; } else { sfree(ns); } @@ -1329,7 +1332,7 @@ static Socket *sk_newlistener_internal( } #endif - return &ret->sock; + return &s->sock; } Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug, @@ -1575,8 +1578,8 @@ void select_result(WPARAM wParam, LPARAM lParam) if (s->addr) { SockAddr thisaddr = sk_extractaddr_tmp( s->addr, &s->step); - plug_log(s->plug, PLUGLOG_CONNECT_FAILED, &thisaddr, s->port, - winsock_error_string(err), err); + plug_log(s->plug, &s->sock, PLUGLOG_CONNECT_FAILED, &thisaddr, + s->port, winsock_error_string(err), err); while (err && s->addr && sk_nextaddr(s->addr, &s->step)) { err = try_connect(s); } @@ -1601,7 +1604,7 @@ void select_result(WPARAM wParam, LPARAM lParam) if (s->addr) { SockAddr thisaddr = sk_extractaddr_tmp( s->addr, &s->step); - plug_log(s->plug, PLUGLOG_CONNECT_SUCCESS, + plug_log(s->plug, &s->sock, PLUGLOG_CONNECT_SUCCESS, &thisaddr, s->port, NULL, 0); sk_addr_free(s->addr); @@ -1707,8 +1710,7 @@ void select_result(WPARAM wParam, LPARAM lParam) memset(&isa, 0, sizeof(isa)); err = 0; t = p_accept(s->s,(struct sockaddr *)&isa,&addrlen); - if (t == INVALID_SOCKET) - { + if (t == INVALID_SOCKET) { err = p_WSAGetLastError(); if (err == WSATRY_AGAIN) break; @@ -1748,7 +1750,7 @@ static const char *sk_net_socket_error(Socket *sock) return s->error; } -static SocketPeerInfo *sk_net_peer_info(Socket *sock) +static SocketEndpointInfo *sk_net_endpoint_info(Socket *sock, bool peer) { NetSocket *s = container_of(sock, NetSocket, sock); #ifdef NO_IPV6 @@ -1758,12 +1760,17 @@ static SocketPeerInfo *sk_net_peer_info(Socket *sock) char buf[INET6_ADDRSTRLEN]; #endif int addrlen = sizeof(addr); - SocketPeerInfo *pi; + SocketEndpointInfo *pi; - if (p_getpeername(s->s, (struct sockaddr *)&addr, &addrlen) < 0) - return NULL; + { + int retd = (peer ? + p_getpeername(s->s, (struct sockaddr *)&addr, &addrlen) : + p_getsockname(s->s, (struct sockaddr *)&addr, &addrlen)); + if (retd < 0) + return NULL; + } - pi = snew(SocketPeerInfo); + pi = snew(SocketEndpointInfo); pi->addressfamily = ADDRTYPE_UNSPEC; pi->addr_text = NULL; pi->port = -1; @@ -1871,9 +1878,9 @@ char *get_hostname(void) SockAddr *platform_get_x11_unix_address(const char *display, int displaynum) { - SockAddr *ret = snew(SockAddr); - memset(ret, 0, sizeof(SockAddr)); - ret->error = "unix sockets for X11 not supported on this platform"; - ret->refcount = 1; - return ret; + SockAddr *addr = snew(SockAddr); + memset(addr, 0, sizeof(SockAddr)); + addr->error = "unix sockets for X11 not supported on this platform"; + addr->refcount = 1; + return addr; } diff --git a/windows/pageant.c b/windows/pageant.c index dc1c3493..679627e4 100644 --- a/windows/pageant.c +++ b/windows/pageant.c @@ -529,7 +529,7 @@ static void win_add_keyfile(Filename *filename, bool encrypted) } error: - message_box(traywindow, err, APPNAME, MB_OK | MB_ICONERROR, + message_box(traywindow, err, APPNAME, MB_OK | MB_ICONERROR, false, HELPCTXID(errors_cantloadkey)); done: sfree(err); @@ -547,7 +547,7 @@ static void prompt_add_keyfile(bool encrypted) if (!keypath) keypath = filereq_new(); memset(&of, 0, sizeof(of)); of.hwndOwner = traywindow; - of.lpstrFilter = FILTER_KEY_FILES; + of.lpstrFilter = FILTER_KEY_FILES_C; of.lpstrCustomFilter = NULL; of.nFilterIndex = 1; of.lpstrFile = filelist; @@ -557,7 +557,7 @@ static void prompt_add_keyfile(bool encrypted) of.lpstrTitle = "选择私钥文件"; of.Flags = OFN_ALLOWMULTISELECT | OFN_EXPLORER; if (request_file(keypath, &of, true, false)) { - if(strlen(filelist) > of.nFileOffset) { + if (strlen(filelist) > of.nFileOffset) { /* Only one filename returned? */ Filename *fn = filename_from_str(filelist); win_add_keyfile(fn, encrypted); @@ -802,7 +802,7 @@ static INT_PTR CALLBACK KeyListProc(HWND hwnd, UINT msg, /* do the same for the rsa keys */ for (i = rCount - 1; (itemNum >= 0) && (i >= 0); i--) { - if(selectedArray[itemNum] == i) { + if (selectedArray[itemNum] == i) { switch (LOWORD(wParam)) { case IDC_KEYLIST_REMOVE: pageant_delete_nth_ssh1_key(i); @@ -907,10 +907,10 @@ static void update_sessions(void) if (!putty_path) return; - if(ERROR_SUCCESS != RegOpenKey(HKEY_CURRENT_USER, PUTTY_REGKEY, &hkey)) + if (ERROR_SUCCESS != RegOpenKey(HKEY_CURRENT_USER, PUTTY_REGKEY, &hkey)) return; - for(num_entries = GetMenuItemCount(session_menu); + for (num_entries = GetMenuItemCount(session_menu); num_entries > initial_menuitems_count; num_entries--) RemoveMenu(session_menu, 0, MF_BYPOSITION); @@ -919,8 +919,8 @@ static void update_sessions(void) index_menu = 0; sb = strbuf_new(); - while(ERROR_SUCCESS == RegEnumKey(hkey, index_key, buf, MAX_PATH)) { - if(strcmp(buf, PUTTY_DEFAULT) != 0) { + while (ERROR_SUCCESS == RegEnumKey(hkey, index_key, buf, MAX_PATH)) { + if (strcmp(buf, PUTTY_DEFAULT) != 0) { strbuf_clear(sb); unescape_registry_key(buf, sb); @@ -940,7 +940,7 @@ static void update_sessions(void) RegCloseKey(hkey); - if(index_menu == 0) { + if (index_menu == 0) { mii.cbSize = sizeof(mii); mii.fMask = MIIM_TYPE | MIIM_STATE; mii.fType = MFT_STRING; @@ -1309,8 +1309,8 @@ static LRESULT CALLBACK TrayWndProc(HWND hwnd, UINT message, if (restrict_putty_acl) strcat(cmdline, "&R"); - if((INT_PTR)ShellExecute(hwnd, NULL, putty_path, cmdline, - _T(""), SW_SHOW) <= 32) { + if ((INT_PTR)ShellExecute(hwnd, NULL, putty_path, cmdline, + _T(""), SW_SHOW) <= 32) { MessageBox(NULL, "无法执行 PuTTY!", "错误", MB_OK | MB_ICONERROR); } @@ -1369,7 +1369,7 @@ static LRESULT CALLBACK TrayWndProc(HWND hwnd, UINT message, launch_help(hwnd, WINHELP_CTX_pageant_general); break; default: { - if(wParam >= IDM_SESSIONS_BASE && wParam <= IDM_SESSIONS_MAX) { + if (wParam >= IDM_SESSIONS_BASE && wParam <= IDM_SESSIONS_MAX) { MENUITEMINFO mii; TCHAR buf[MAX_PATH + 1]; TCHAR param[MAX_PATH + 1]; @@ -1384,8 +1384,8 @@ static LRESULT CALLBACK TrayWndProc(HWND hwnd, UINT message, strcat(param, "&R"); strcat(param, "@"); strcat(param, mii.dwTypeData); - if((INT_PTR)ShellExecute(hwnd, NULL, putty_path, param, - _T(""), SW_SHOW) <= 32) { + if ((INT_PTR)ShellExecute(hwnd, NULL, putty_path, param, + _T(""), SW_SHOW) <= 32) { MessageBox(NULL, "无法执行 PuTTY!", "错误", MB_OK | MB_ICONERROR); } @@ -1535,9 +1535,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) const char *command = NULL; const char *unixsocket = NULL; bool show_keylist_on_startup = false; - int argc; - char **argv, **argstart; - const char *openssh_config_file = NULL; + Filename *openssh_config_file = NULL; typedef struct CommandLineKey { Filename *fn; @@ -1596,28 +1594,27 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) * started up the main agent. Details of keys to be added are * stored in the 'clkeys' array. */ - split_into_argv(cmdline, &argc, &argv, &argstart); bool add_keys_encrypted = false; - AuxMatchOpt amo = aux_match_opt_init(argc, argv, 0, opt_error); + AuxMatchOpt amo = aux_match_opt_init(opt_error); while (!aux_match_done(&amo)) { - char *val; + CmdlineArg *valarg; #define match_opt(...) aux_match_opt( \ &amo, NULL, __VA_ARGS__, (const char *)NULL) #define match_optval(...) aux_match_opt( \ - &amo, &val, __VA_ARGS__, (const char *)NULL) + &amo, &valarg, __VA_ARGS__, (const char *)NULL) - if (aux_match_arg(&amo, &val)) { + if (aux_match_arg(&amo, &valarg)) { /* * Non-option arguments are expected to be key files, and * added to clkeys. */ sgrowarray(clkeys, clkeysize, nclkeys); CommandLineKey *clkey = &clkeys[nclkeys++]; - clkey->fn = filename_from_str(val); + clkey->fn = cmdline_arg_to_filename(valarg); clkey->add_encrypted = add_keys_encrypted; } else if (match_opt("-pgpfp")) { pgp_fingerprints_msgbox(NULL); - return 1; + return 0; } else if (match_opt("-restrict-acl", "-restrict_acl", "-restrictacl")) { restrict_process_acl(); @@ -1629,21 +1626,29 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) } else if (match_opt("-keylist")) { show_keylist_on_startup = true; } else if (match_optval("-openssh-config", "-openssh_config")) { - openssh_config_file = val; + openssh_config_file = cmdline_arg_to_filename(valarg); } else if (match_optval("-unix")) { - unixsocket = val; + /* UNICODE: should this be a Unicode filename? Is there a + * Unicode version of connect() that lets you give a + * Unicode pathname when making an AF_UNIX socket? */ + unixsocket = cmdline_arg_to_str(valarg); } else if (match_opt("-c")) { /* * If we see `-c', then the rest of the command line * should be treated as a command to be spawned. */ - if (amo.index < amo.argc) - command = argstart[amo.index]; - else + if (amo.arglist->args[amo.index]) { + /* UNICODE: should use the UTF-8 or wide version, and + * CreateProcessW, to pass through arbitrary command lines */ + command = cmdline_arg_remainder_acp( + amo.arglist->args[amo.index]); + } else { command = ""; + } break; } else { - opt_error("unrecognised option '%s'\n", amo.argv[amo.index]); + opt_error("unrecognised option '%s'\n", + cmdline_arg_to_str(amo.arglist->args[amo.index])); } } @@ -1731,10 +1736,11 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) * pointing at the named pipe, do so. */ if (openssh_config_file) { - FILE *fp = fopen(openssh_config_file, "w"); + FILE *fp = f_open(openssh_config_file, "w", true); if (!fp) { - char *err = dupprintf("Unable to write OpenSSH config " - "file to %s", openssh_config_file); + char *err = dupprintf( + "Unable to write OpenSSH config file to %s", + filename_to_str(openssh_config_file)); MessageBox(NULL, err, "Pageant 错误", MB_ICONERROR | MB_OK); return 1; @@ -1956,7 +1962,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) * Leave this file around, but empty it, so that it doesn't * refer to a pipe we aren't listening on any more. */ - FILE *fp = fopen(openssh_config_file, "w"); + FILE *fp = f_open(openssh_config_file, "w", true); if (fp) fclose(fp); } diff --git a/windows/platform.h b/windows/platform.h index 5e336707..7e7d8fbd 100644 --- a/windows/platform.h +++ b/windows/platform.h @@ -48,14 +48,27 @@ #define AGENT_COPYDATA_ID 0x804e50ba struct Filename { - char *path; + /* + * A Windows Filename stores a path in three formats: + * + * - wchar_t (in Windows UTF-16 encoding). The best format to use + * for actual file API functions, because all legal Windows + * file names are representable. + * + * - char, in the system default codepage. A fallback to use if + * necessary, e.g. in diagnostics written to somewhere that is + * unavoidably encoded _in_ the system codepage. + * + * - char, in UTF-8. An equally general representation to wpath, + * but suitable for keeping in char-typed strings. + */ + wchar_t *wpath; + char *cpath, *utf8path; }; -static inline FILE *f_open(const Filename *filename, const char *mode, - bool isprivate) -{ - return fopen(filename->path, mode); -} +Filename *filename_from_wstr(const wchar_t *str); +FILE *f_open(const Filename *filename, const char *mode, bool isprivate); +#ifndef SUPERSEDE_FONTSPEC_FOR_TESTING struct FontSpec { char *name; bool isbold; @@ -64,6 +77,7 @@ struct FontSpec { }; struct FontSpec *fontspec_new( const char *name, bool bold, int height, int charset); +#endif #ifndef CLEARTYPE_QUALITY #define CLEARTYPE_QUALITY 5 @@ -194,6 +208,9 @@ void centre_window(HWND hwnd); #define DEFAULT_CODEPAGE CP_ACP #define USES_VTLINE_HACK +#define CP_UTF8 65001 +#define CP_437 437 /* used for test suites */ +#define CP_ISO8859_1 0x10001 /* used for test suites */ #ifndef NO_GSSAPI /* @@ -249,7 +266,7 @@ const SeatDialogPromptDescriptions *win_seat_prompt_descriptions(Seat *seat); * which takes the data string in the system code page instead of * Unicode. */ -void write_aclip(int clipboard, char *, int, bool); +void write_aclip(HWND hwnd, int clipboard, char *, int); #define WM_NETEVENT (WM_APP + 5) @@ -283,12 +300,21 @@ void write_aclip(int clipboard, char *, int, bool); * these strings are of exactly the type needed to go in * `lpstrFilter' in an OPENFILENAME structure. */ -#define FILTER_KEY_FILES ("PuTTY 私钥文件 (*.ppk)\0*.ppk\0" \ - "所有文件 (*.*)\0*\0\0\0") -#define FILTER_WAVE_FILES ("Wav 音频文件 (*.wav)\0*.WAV\0" \ +typedef const wchar_t *FILESELECT_FILTER_TYPE; +#define FILTER_KEY_FILES (L"PuTTY 私钥文件 (*.ppk)\0*.ppk\0" \ + L"所有文件 (*.*)\0*\0\0\0") +#define FILTER_WAVE_FILES (L"音频文件 (*.wav)\0*.WAV\0" \ + L"所有文件 (*.*)\0*\0\0\0") +#define FILTER_DYNLIB_FILES (L"动态链接库文件 (*.dll)\0*.dll\0" \ + L"所有文件 (*.*)\0*\0\0\0") + +/* char-based versions of the above, for outlying uses of file selectors. */ +#define FILTER_KEY_FILES_C ("PuTTY 私钥文件 (*.ppk)\0*.ppk\0" \ + "所有文件 (*.*)\0*\0\0\0") +#define FILTER_WAVE_FILES_C ("音频文件 (*.wav)\0*.WAV\0" \ + "所有文件 (*.*)\0*\0\0\0") +#define FILTER_DYNLIB_FILES_C ("动态链接库文件 (*.dll)\0*.dll\0" \ "所有文件 (*.*)\0*\0\0\0") -#define FILTER_DYNLIB_FILES ("动态链接库文件 (*.dll)\0*.dll\0" \ - "所有文件 (*.*)\0*\0\0\0") /* * Exports from network.c. @@ -392,14 +418,53 @@ void init_common_controls(void); /* also does some DLL-loading */ */ typedef struct filereq_tag filereq; /* cwd for file requester */ bool request_file(filereq *state, OPENFILENAME *of, bool preserve, bool save); +bool request_file_w(filereq *state, OPENFILENAMEW *of, + bool preserve, bool save); filereq *filereq_new(void); void filereq_free(filereq *state); void pgp_fingerprints_msgbox(HWND owner); -int message_box(HWND owner, LPCTSTR text, LPCTSTR caption, - DWORD style, DWORD helpctxid); +int message_box(HWND owner, LPCTSTR text, LPCTSTR caption, DWORD style, + bool utf8, DWORD helpctxid); void MakeDlgItemBorderless(HWND parent, int id); char *GetDlgItemText_alloc(HWND hwnd, int id); -void split_into_argv(char *, int *, char ***, char ***); +wchar_t *GetDlgItemTextW_alloc(HWND hwnd, int id); +/* + * The split_into_argv functions take a single string 'cmdline' (char + * or wide) to split up into arguments. They return an argc and argv + * pair, and also 'argstart', an array of pointers into the original + * command line, pointing at the place where each output argument + * begins. (Useful for retrieving the tail of the original command + * line corresponding to a certain argument onwards, or identifying a + * section of the original command line to blank out for privacy.) + * + * If the command line includes the program name (e.g. if it was + * returned from GetCommandLine()), set includes_program_name=true. If + * it doesn't (e.g. it was the arguments string received by WinMain), + * set that flag to false. This affects the rules for argument + * splitting, which is done differently in the program name + * (specifically, \ isn't special, and won't escape "). + * + * Mutability: the argv[] words are in fresh dynamically allocated + * memory, so you can write into them safely. The original cmdline is + * passed in as a const pointer, and not modified in this function. + * But the pointers into that string written into argstart have the + * type of a mutable char *. Similarly to strchr, this is due to the + * limitation of C that you can't specify argstart as having the same + * constness as cmdline: the idea is that you either pass a + * non-mutable cmdline and promise not to write through the argstart + * pointers, of you pass a mutable one and are free to write through + * it. + * + * Allocation: argv and argstart are dynamically allocated. There's + * also a dynamically allocated string behind the scenes storing the + * actual strings. argv[0] guarantees to point at the first character + * of that. So to free all the memory allocated by this function, you + * must free argv[0], then argv, and also argstart. + */ +void split_into_argv(const char *cmdline, bool includes_program_name, + int *argc, char ***argv, char ***argstart); +void split_into_argv_w(const wchar_t *cmdline, bool includes_program_name, + int *argc, wchar_t ***argv, wchar_t ***argstart); /* * Private structure for prefslist state. Only in the header file @@ -783,18 +848,27 @@ void unlock_interprocess_mutex(HANDLE mutex); typedef void (*aux_opt_error_fn_t)(const char *, ...); typedef struct AuxMatchOpt { - int index, argc; - char **argv; + CmdlineArgList *arglist; + size_t index; bool doing_opts; aux_opt_error_fn_t error; } AuxMatchOpt; -AuxMatchOpt aux_match_opt_init(int argc, char **argv, int start_index, - aux_opt_error_fn_t opt_error); -bool aux_match_arg(AuxMatchOpt *amo, char **val); -bool aux_match_opt(AuxMatchOpt *amo, char **val, const char *optname, ...); +AuxMatchOpt aux_match_opt_init(aux_opt_error_fn_t opt_error); +bool aux_match_arg(AuxMatchOpt *amo, CmdlineArg **val); +bool aux_match_opt(AuxMatchOpt *amo, CmdlineArg **val, + const char *optname, ...); bool aux_match_done(AuxMatchOpt *amo); -char *save_screenshot(HWND hwnd, const char *outfile); +char *save_screenshot(HWND hwnd, Filename *outfile); void gui_terminal_ready(HWND hwnd, Seat *seat, Backend *backend); +void setup_gui_timing(void); + +/* Windows-specific extra functions in cmdline_arg.c */ +CmdlineArgList *cmdline_arg_list_from_GetCommandLineW(void); +const wchar_t *cmdline_arg_remainder_wide(CmdlineArg *); +char *cmdline_arg_remainder_acp(CmdlineArg *); +char *cmdline_arg_remainder_utf8(CmdlineArg *); +CmdlineArg *cmdline_arg_from_utf8(CmdlineArgList *list, const char *string); + #endif /* PUTTY_WINDOWS_PLATFORM_H */ diff --git a/windows/plink.c b/windows/plink.c index 2d45c51e..bf04344c 100644 --- a/windows/plink.c +++ b/windows/plink.c @@ -82,8 +82,8 @@ static SeatPromptResult plink_get_userpass_input(Seat *seat, prompts_t *p) static bool plink_seat_interactive(Seat *seat) { - return (!*conf_get_str(conf, CONF_remote_cmd) && - !*conf_get_str(conf, CONF_remote_cmd2) && + return (!*conf_get_str_ambi(conf, CONF_remote_cmd, NULL) && + !*conf_get_str_ambi(conf, CONF_remote_cmd2, NULL) && !*conf_get_str(conf, CONF_ssh_nc_host)); } @@ -97,6 +97,7 @@ static const SeatVtable plink_seat_vt = { .notify_remote_exit = nullseat_notify_remote_exit, .notify_remote_disconnect = nullseat_notify_remote_disconnect, .connection_fatal = console_connection_fatal, + .nonfatal = console_nonfatal, .update_specials_menu = nullseat_update_specials_menu, .get_ttymode = nullseat_get_ttymode, .set_busy_status = nullseat_set_busy_status, @@ -325,20 +326,21 @@ int main(int argc, char **argv) } } } - while (--argc) { - char *p = *++argv; - int ret = cmdline_process_param(p, (argc > 1 ? argv[1] : NULL), - 1, conf); + CmdlineArgList *arglist = cmdline_arg_list_from_GetCommandLineW(); + size_t arglistpos = 0; + while (arglist->args[arglistpos]) { + CmdlineArg *arg = arglist->args[arglistpos++]; + CmdlineArg *nextarg = arglist->args[arglistpos]; + const char *p = cmdline_arg_to_str(arg); + int ret = cmdline_process_param(arg, nextarg, 1, conf); if (ret == -2) { fprintf(stderr, "plink: option \"%s\" requires an argument\n", p); errors = true; } else if (ret == 2) { - --argc, ++argv; + arglistpos++; } else if (ret == 1) { continue; - } else if (!strcmp(p, "-batch")) { - console_batch_mode = true; } else if (!strcmp(p, "-s")) { /* Save status to write to conf later. */ use_subsystem = true; @@ -346,9 +348,10 @@ int main(int argc, char **argv) version(); } else if (!strcmp(p, "--help")) { usage(); + exit(0); } else if (!strcmp(p, "-pgpfp")) { pgp_fingerprints(); - exit(1); + exit(0); } else if (!strcmp(p, "-shareexists")) { just_test_share_exists = true; } else if (!strcmp(p, "-sanitise-stdout") || @@ -368,12 +371,11 @@ int main(int argc, char **argv) } else if (*p != '-') { strbuf *cmdbuf = strbuf_new(); - while (argc > 0) { + while (arg) { if (cmdbuf->len > 0) put_byte(cmdbuf, ' '); /* add space separator */ - put_dataz(cmdbuf, p); - if (--argc > 0) - p = *++argv; + put_dataz(cmdbuf, cmdline_arg_to_utf8(arg)); + arg = arglist->args[arglistpos++]; } conf_set_str(conf, CONF_remote_cmd, cmdbuf->s); @@ -392,7 +394,10 @@ int main(int argc, char **argv) return 1; if (!cmdline_host_ok(conf)) { - usage(); + fprintf(stderr, "plink: no valid host name provided\n" + "try \"plink --help\" for help\n"); + cmdline_arg_list_free(arglist); + return 1; } prepare_session(conf); diff --git a/windows/printing.c b/windows/printing.c index 2286c236..965b6384 100644 --- a/windows/printing.c +++ b/windows/printing.c @@ -93,7 +93,7 @@ static bool printer_add_enum(int param, DWORD level, char **buffer, printer_enum *printer_start_enum(int *nprinters_ptr) { - printer_enum *ret = snew(printer_enum); + printer_enum *pe = snew(printer_enum); char *buffer = NULL; *nprinters_ptr = 0; /* default return value */ @@ -110,30 +110,30 @@ printer_enum *printer_start_enum(int *nprinters_ptr) * Bletch. */ if (osPlatformId != VER_PLATFORM_WIN32_NT) { - ret->enum_level = 5; + pe->enum_level = 5; } else { - ret->enum_level = 4; + pe->enum_level = 4; } if (!printer_add_enum(PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS, - ret->enum_level, &buffer, 0, nprinters_ptr)) + pe->enum_level, &buffer, 0, nprinters_ptr)) goto error; - switch (ret->enum_level) { + switch (pe->enum_level) { case 4: - ret->info.i4 = (LPPRINTER_INFO_4)buffer; + pe->info.i4 = (LPPRINTER_INFO_4)buffer; break; case 5: - ret->info.i5 = (LPPRINTER_INFO_5)buffer; + pe->info.i5 = (LPPRINTER_INFO_5)buffer; break; } - ret->nprinters = *nprinters_ptr; + pe->nprinters = *nprinters_ptr; - return ret; + return pe; error: sfree(buffer); - sfree(ret); + sfree(pe); *nprinters_ptr = 0; return NULL; } @@ -171,38 +171,38 @@ void printer_finish_enum(printer_enum *pe) printer_job *printer_start_job(char *printer) { - printer_job *ret = snew(printer_job); + printer_job *pj = snew(printer_job); DOC_INFO_1 docinfo; bool jobstarted = false, pagestarted = false; init_winfuncs(); - ret->hprinter = NULL; - if (!p_OpenPrinter(printer, &ret->hprinter, NULL)) + pj->hprinter = NULL; + if (!p_OpenPrinter(printer, &pj->hprinter, NULL)) goto error; docinfo.pDocName = "PuTTY remote printer output"; docinfo.pOutputFile = NULL; docinfo.pDatatype = "RAW"; - if (!p_StartDocPrinter(ret->hprinter, 1, (LPBYTE)&docinfo)) + if (!p_StartDocPrinter(pj->hprinter, 1, (LPBYTE)&docinfo)) goto error; jobstarted = true; - if (!p_StartPagePrinter(ret->hprinter)) + if (!p_StartPagePrinter(pj->hprinter)) goto error; pagestarted = true; - return ret; + return pj; error: if (pagestarted) - p_EndPagePrinter(ret->hprinter); + p_EndPagePrinter(pj->hprinter); if (jobstarted) - p_EndDocPrinter(ret->hprinter); - if (ret->hprinter) - p_ClosePrinter(ret->hprinter); - sfree(ret); + p_EndDocPrinter(pj->hprinter); + if (pj->hprinter) + p_ClosePrinter(pj->hprinter); + sfree(pj); return NULL; } diff --git a/windows/psocks.c b/windows/psocks.c index 83ba364c..c0d8cb5b 100644 --- a/windows/psocks.c +++ b/windows/psocks.c @@ -8,13 +8,15 @@ static const PsocksPlatform platform = { NULL /* open_pipes */, + NULL /* found_subcommand */, NULL /* start_subcommand */, }; int main(int argc, char **argv) { psocks_state *ps = psocks_new(&platform); - psocks_cmdline(ps, argc, argv); + CmdlineArgList *arglist = cmdline_arg_list_from_GetCommandLineW(); + psocks_cmdline(ps, arglist); sk_init(); winselcli_setup(); diff --git a/windows/pterm.c b/windows/pterm.c index bb68245d..2b05fe08 100644 --- a/windows/pterm.c +++ b/windows/pterm.c @@ -15,33 +15,34 @@ void gui_term_process_cmdline(Conf *conf, char *cmdline) handle_special_filemapping_cmdline(cmdline, conf)) return; - int argc; - char **argv, **argstart; - split_into_argv(cmdline, &argc, &argv, &argstart); - - for (int i = 0; i < argc; i++) { - char *arg = argv[i]; - int retd = cmdline_process_param( - arg, i+1args[arglistpos]) { + CmdlineArg *arg = arglist->args[arglistpos++]; + CmdlineArg *nextarg = arglist->args[arglistpos]; + const char *argstr = cmdline_arg_to_str(arg); + int retd = cmdline_process_param(arg, nextarg, 1, conf); if (retd == -2) { - cmdline_error("option \"%s\" requires an argument", arg); + cmdline_error("option \"%s\" requires an argument", argstr); } else if (retd == 2) { - i++; /* skip next argument */ + arglistpos++; /* skip next argument */ } else if (retd == 1) { continue; /* nothing further needs doing */ - } else if (!strcmp(arg, "-e")) { - if (i+1 < argc) { + } else if (!strcmp(argstr, "-e")) { + if (nextarg) { /* The command to execute is taken to be the unparsed * version of the whole remainder of the command line. */ - conf_set_str(conf, CONF_remote_cmd, argstart[i+1]); + char *cmd = cmdline_arg_remainder_utf8(nextarg); + conf_set_utf8(conf, CONF_remote_cmd, cmd); + sfree(cmd); return; } else { - cmdline_error("option \"%s\" requires an argument", arg); + cmdline_error("option \"%s\" requires an argument", argstr); } - } else if (arg[0] == '-') { - cmdline_error("unrecognised option \"%s\"", arg); + } else if (argstr[0] == '-') { + cmdline_error("unrecognised option \"%s\"", argstr); } else { - cmdline_error("unexpected non-option argument \"%s\"", arg); + cmdline_error("unexpected non-option argument \"%s\"", argstr); } } diff --git a/windows/putty.c b/windows/putty.c index 3546d839..414a41e4 100644 --- a/windows/putty.c +++ b/windows/putty.c @@ -2,9 +2,9 @@ #include "storage.h" extern bool sesslist_demo_mode; -extern const char *dialog_box_demo_screenshot_filename; +extern Filename *dialog_box_demo_screenshot_filename; static strbuf *demo_terminal_data = NULL; -static const char *terminal_demo_screenshot_filename; +static Filename *terminal_demo_screenshot_filename; const unsigned cmdline_tooltype = TOOLTYPE_HOST_ARG | @@ -48,21 +48,17 @@ void gui_term_process_cmdline(Conf *conf, char *cmdline) * Otherwise, break up the command line and deal with * it sensibly. */ - int argc, i; - char **argv; - - split_into_argv(cmdline, &argc, &argv, NULL); - - for (i = 0; i < argc; i++) { - char *p = argv[i]; - int ret; - - ret = cmdline_process_param(p, i+1args[arglistpos]) { + CmdlineArg *arg = arglist->args[arglistpos++]; + CmdlineArg *nextarg = arglist->args[arglistpos]; + const char *p = cmdline_arg_to_str(arg); + int ret = cmdline_process_param(arg, nextarg, 1, conf); if (ret == -2) { cmdline_error("option \"%s\" requires an argument", p); } else if (ret == 2) { - i++; /* skip next argument */ + arglistpos++; /* skip next argument */ } else if (ret == 1) { continue; /* nothing further needs doing */ } else if (!strcmp(p, "-cleanup")) { @@ -83,7 +79,7 @@ void gui_term_process_cmdline(Conf *conf, char *cmdline) s2 = dupprintf("%s 警告", appname); if (message_box(NULL, s1, s2, MB_YESNO | MB_ICONWARNING | MB_DEFBUTTON2, - HELPCTXID(option_cleanup)) == IDYES) { + false, HELPCTXID(option_cleanup)) == IDYES) { cleanup_all(); } sfree(s1); @@ -91,25 +87,29 @@ void gui_term_process_cmdline(Conf *conf, char *cmdline) exit(0); } else if (!strcmp(p, "-pgpfp")) { pgp_fingerprints_msgbox(NULL); - exit(1); + exit(0); } else if (has_ca_config_box && (!strcmp(p, "-host-ca") || !strcmp(p, "--host-ca") || !strcmp(p, "-host_ca") || !strcmp(p, "--host_ca"))) { show_ca_config_box(NULL); exit(0); } else if (!strcmp(p, "-demo-config-box")) { - if (i+1 >= argc) { + if (!arglist->args[arglistpos]) { cmdline_error("%s expects an output filename", p); } else { demo_config_box = true; - dialog_box_demo_screenshot_filename = argv[++i]; + dialog_box_demo_screenshot_filename = + cmdline_arg_to_filename(arglist->args[arglistpos++]); } } else if (!strcmp(p, "-demo-terminal")) { - if (i+2 >= argc) { + if (!arglist->args[arglistpos] || + !arglist->args[arglistpos+1]) { cmdline_error("%s expects input and output filenames", p); } else { - const char *infile = argv[++i]; - terminal_demo_screenshot_filename = argv[++i]; + const char *infile = + cmdline_arg_to_str(arglist->args[arglistpos++]); + terminal_demo_screenshot_filename = + cmdline_arg_to_filename(arglist->args[arglistpos++]); FILE *fp = fopen(infile, "rb"); if (!fp) cmdline_error("can't open input file '%s'", infile); diff --git a/windows/puttygen.c b/windows/puttygen.c index a6eae2ad..93b6db42 100644 --- a/windows/puttygen.c +++ b/windows/puttygen.c @@ -26,9 +26,9 @@ #define DEFAULT_ECCURVE_INDEX 0 #define DEFAULT_EDCURVE_INDEX 0 -static char *cmdline_keyfile = NULL; +static Filename *cmdline_keyfile = NULL; static ptrlen cmdline_demo_keystr; -static const char *demo_screenshot_filename = NULL; +static Filename *demo_screenshot_filename = NULL; /* * Print a modal (Really Bad) message box and perform a fatal exit. @@ -1137,7 +1137,7 @@ void load_key_file(HWND hwnd, struct MainDlgState *state, char *msg = dupprintf("无法载入私钥 (%s)", key_type_to_str(type)); message_box(hwnd, msg, "PuTTYgen 错误", MB_OK | MB_ICONERROR, - HELPCTXID(errors_cantloadkey)); + false, HELPCTXID(errors_cantloadkey)); sfree(msg); return; } @@ -1200,7 +1200,7 @@ void load_key_file(HWND hwnd, struct MainDlgState *state, if (ret == 0) { char *msg = dupprintf("无法载入私钥 (%s)", errmsg); message_box(hwnd, msg, "PuTTYgen 错误", MB_OK | MB_ICONERROR, - HELPCTXID(errors_cantloadkey)); + false, HELPCTXID(errors_cantloadkey)); sfree(msg); } else if (ret == 1) { /* @@ -1238,7 +1238,7 @@ void add_certificate(HWND hwnd, struct MainDlgState *state, char *msg = dupprintf("无法载入证书 (%s)", key_type_to_str(type)); message_box(hwnd, msg, "PuTTYgen 错误", MB_OK | MB_ICONERROR, - HELPCTXID(errors_cantloadkey)); + false, HELPCTXID(errors_cantloadkey)); sfree(msg); return; } @@ -1251,7 +1251,7 @@ void add_certificate(HWND hwnd, struct MainDlgState *state, &error)) { char *msg = dupprintf("无法载入证书 (%s)", error); message_box(hwnd, msg, "PuTTYgen 错误", MB_OK | MB_ICONERROR, - HELPCTXID(errors_cantloadkey)); + false, HELPCTXID(errors_cantloadkey)); sfree(msg); strbuf_free(pub); return; @@ -1264,7 +1264,7 @@ void add_certificate(HWND hwnd, struct MainDlgState *state, char *msg = dupprintf("无法载入证书 (不支持的" "算法 '%s')", algname); message_box(hwnd, msg, "PuTTYgen 错误", MB_OK | MB_ICONERROR, - HELPCTXID(errors_cantloadkey)); + false, HELPCTXID(errors_cantloadkey)); sfree(msg); sfree(algname); strbuf_free(pub); @@ -1292,7 +1292,7 @@ void add_certificate(HWND hwnd, struct MainDlgState *state, if (!match) { char *msg = dupprintf("证书公钥不匹配"); message_box(hwnd, msg, "PuTTYgen 错误", MB_OK | MB_ICONERROR, - HELPCTXID(errors_cantloadkey)); + false, HELPCTXID(errors_cantloadkey)); sfree(msg); strbuf_free(pub); return; @@ -1308,7 +1308,7 @@ void add_certificate(HWND hwnd, struct MainDlgState *state, if (!newkey) { char *msg = dupprintf("无法合并证书密钥"); message_box(hwnd, msg, "PuTTYgen 错误", MB_OK | MB_ICONERROR, - HELPCTXID(errors_cantloadkey)); + false, HELPCTXID(errors_cantloadkey)); sfree(msg); return; } @@ -1734,9 +1734,7 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, * Load a key file if one was provided on the command line. */ if (cmdline_keyfile) { - Filename *fn = filename_from_str(cmdline_keyfile); - load_key_file(hwnd, state, fn, false); - filename_free(fn); + load_key_file(hwnd, state, cmdline_keyfile, false); } else if (cmdline_demo_keystr.ptr) { BinarySource src[1]; BinarySource_BARE_INIT_PL(src, cmdline_demo_keystr); @@ -2388,8 +2386,6 @@ static NORETURN void opt_error(const char *fmt, ...) int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) { - int argc; - char **argv; int ret; struct InitialParams params[1]; @@ -2413,35 +2409,35 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) save_params = ppk_save_default_parameters; - split_into_argv(cmdline, &argc, &argv, NULL); - int argbits = -1; - AuxMatchOpt amo = aux_match_opt_init(argc, argv, 0, opt_error); + AuxMatchOpt amo = aux_match_opt_init(opt_error); while (!aux_match_done(&amo)) { - char *val; + CmdlineArg *valarg; #define match_opt(...) aux_match_opt( \ &amo, NULL, __VA_ARGS__, (const char *)NULL) #define match_optval(...) aux_match_opt( \ - &amo, &val, __VA_ARGS__, (const char *)NULL) + &amo, &valarg, __VA_ARGS__, (const char *)NULL) - if (aux_match_arg(&amo, &val)) { + if (aux_match_arg(&amo, &valarg)) { if (!cmdline_keyfile) { /* * Assume the first argument to be a private key file, and * attempt to load it. */ - cmdline_keyfile = val; + cmdline_keyfile = cmdline_arg_to_filename(valarg); continue; } else { - opt_error("unexpected extra argument '%s'\n", val); + opt_error("unexpected extra argument '%s'\n", + cmdline_arg_to_str(valarg)); } } else if (match_opt("-pgpfp")) { pgp_fingerprints_msgbox(NULL); - return 1; + return 0; } else if (match_opt("-restrict-acl", "-restrict_acl", "-restrictacl")) { restrict_process_acl(); } else if (match_optval("-t")) { + const char *val = cmdline_arg_to_str(valarg); if (!strcmp(val, "rsa") || !strcmp(val, "rsa2")) { params->keybutton = IDC_KEYSSH2RSA; } else if (!strcmp(val, "rsa1")) { @@ -2462,8 +2458,9 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) opt_error("unknown key type '%s'\n", val); } } else if (match_optval("-b")) { - argbits = atoi(val); + argbits = atoi(cmdline_arg_to_str(valarg)); } else if (match_optval("-E")) { + const char *val = cmdline_arg_to_str(valarg); if (!strcmp(val, "md5")) params->fptype = SSH_FPTYPE_MD5; else if (!strcmp(val, "sha256")) @@ -2471,6 +2468,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) else opt_error("unknown fingerprint type '%s'\n", val); } else if (match_optval("-primes")) { + const char *val = cmdline_arg_to_str(valarg); if (!strcmp(val, "probable") || !strcmp(val, "probabilistic")) { params->primepolicybutton = IDC_PRIMEGEN_PROB; @@ -2491,6 +2489,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) } else if (match_opt("-strong-rsa")) { params->rsa_strong = true; } else if (match_optval("-ppk-param", "-ppk-params")) { + char *val = dupstr(cmdline_arg_to_str(valarg)); char *nextval; for (; val; val = nextval) { nextval = strchr(val, ','); @@ -2543,8 +2542,9 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) opt_error("unrecognised PPK parameter '%s'\n", val); } } + sfree(val); } else if (match_optval("-demo-screenshot")) { - demo_screenshot_filename = val; + demo_screenshot_filename = cmdline_arg_to_filename(valarg); cmdline_demo_keystr = PTRLEN_LITERAL( "PuTTY-User-Key-File-3: ssh-ed25519\n" "Encryption: none\n" @@ -2560,7 +2560,8 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) params->keybutton = IDC_KEYSSH2EDDSA; argbits = 255; } else { - opt_error("unrecognised option '%s'\n", amo.argv[amo.index]); + opt_error("unrecognised option '%s'\n", + cmdline_arg_to_str(amo.arglist->args[amo.index])); } } diff --git a/windows/sftp.c b/windows/sftp.c index a6546269..fb3658d7 100644 --- a/windows/sftp.c +++ b/windows/sftp.c @@ -86,14 +86,14 @@ static inline uint64_t uint64_from_words(uint32_t hi, uint32_t lo) uli.QuadPart = ((ULONGLONG)(t) + 11644473600ull) * 10000000ull; \ (ft).dwLowDateTime = uli.LowPart; \ (ft).dwHighDateTime = uli.HighPart; \ -} while(0) +} while (0) #define TIME_WIN_TO_POSIX(ft, t) do { \ ULARGE_INTEGER uli; \ uli.LowPart = (ft).dwLowDateTime; \ uli.HighPart = (ft).dwHighDateTime; \ uli.QuadPart = uli.QuadPart / 10000000ull - 11644473600ull; \ (t) = (unsigned long) uli.QuadPart; \ -} while(0) +} while (0) struct RFile { HANDLE h; @@ -104,15 +104,15 @@ RFile *open_existing_file(const char *name, uint64_t *size, long *perms) { HANDLE h; - RFile *ret; + RFile *f; h = CreateFile(name, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, 0); if (h == INVALID_HANDLE_VALUE) return NULL; - ret = snew(RFile); - ret->h = h; + f = snew(RFile); + f->h = h; if (size) { DWORD lo, hi; @@ -132,7 +132,7 @@ RFile *open_existing_file(const char *name, uint64_t *size, if (perms) *perms = -1; - return ret; + return f; } int read_from_file(RFile *f, void *buffer, int length) @@ -157,31 +157,31 @@ struct WFile { WFile *open_new_file(const char *name, long perms) { HANDLE h; - WFile *ret; + WFile *f; h = CreateFile(name, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); if (h == INVALID_HANDLE_VALUE) return NULL; - ret = snew(WFile); - ret->h = h; + f = snew(WFile); + f->h = h; - return ret; + return f; } WFile *open_existing_wfile(const char *name, uint64_t *size) { HANDLE h; - WFile *ret; + WFile *f; h = CreateFile(name, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, 0); if (h == INVALID_HANDLE_VALUE) return NULL; - ret = snew(WFile); - ret->h = h; + f = snew(WFile); + f->h = h; if (size) { DWORD lo, hi; @@ -189,7 +189,7 @@ WFile *open_existing_wfile(const char *name, uint64_t *size) *size = uint64_from_words(hi, lo); } - return ret; + return f; } int write_to_file(WFile *f, void *buffer, int length) @@ -277,7 +277,7 @@ DirHandle *open_directory(const char *name, const char **errmsg) HANDLE h; WIN32_FIND_DATA fdat; char *findfile; - DirHandle *ret; + DirHandle *dir; /* Enumerate files in dir `foo'. */ findfile = dupcat(name, "/*"); @@ -288,10 +288,10 @@ DirHandle *open_directory(const char *name, const char **errmsg) } sfree(findfile); - ret = snew(DirHandle); - ret->h = h; - ret->name = dupstr(fdat.cFileName); - return ret; + dir = snew(DirHandle); + dir->h = h; + dir->name = dupstr(fdat.cFileName); + return dir; } char *read_filename(DirHandle *dir) @@ -384,26 +384,26 @@ WildcardMatcher *begin_wildcard_matching(const char *name) { HANDLE h; WIN32_FIND_DATA fdat; - WildcardMatcher *ret; + WildcardMatcher *dir; char *last; h = FindFirstFile(name, &fdat); if (h == INVALID_HANDLE_VALUE) return NULL; - ret = snew(WildcardMatcher); - ret->h = h; - ret->srcpath = dupstr(name); - last = stripslashes(ret->srcpath, true); + dir = snew(WildcardMatcher); + dir->h = h; + dir->srcpath = dupstr(name); + last = stripslashes(dir->srcpath, true); *last = '\0'; if (fdat.cFileName[0] == '.' && (fdat.cFileName[1] == '\0' || (fdat.cFileName[1] == '.' && fdat.cFileName[2] == '\0'))) - ret->name = NULL; + dir->name = NULL; else - ret->name = dupcat(ret->srcpath, fdat.cFileName); + dir->name = dupcat(dir->srcpath, fdat.cFileName); - return ret; + return dir; } char *wildcard_get_filename(WildcardMatcher *dir) @@ -650,7 +650,8 @@ int main(int argc, char *argv[]) dll_hijacking_protection(); - ret = psftp_main(argc, argv); + CmdlineArgList *arglist = cmdline_arg_list_from_GetCommandLineW(); + ret = psftp_main(arglist); return ret; } diff --git a/windows/storage.c b/windows/storage.c index 2a544900..5554a217 100644 --- a/windows/storage.c +++ b/windows/storage.c @@ -51,9 +51,9 @@ settings_w *open_settings_w(const char *sessionname, char **errmsg) } strbuf_free(sb); - settings_w *toret = snew(settings_w); - toret->sesskey = sesskey; - return toret; + settings_w *handle = snew(settings_w); + handle->sesskey = sesskey; + return handle; } void write_setting_s(settings_w *handle, const char *key, const char *value) @@ -91,9 +91,9 @@ settings_r *open_settings_r(const char *sessionname) if (!sesskey) return NULL; - settings_r *toret = snew(settings_r); - toret->sesskey = sesskey; - return toret; + settings_r *handle = snew(settings_r); + handle->sesskey = sesskey; + return handle; } char *read_setting_s(settings_r *handle, const char *key) @@ -183,7 +183,23 @@ Filename *read_setting_filename(settings_r *handle, const char *name) void write_setting_filename(settings_w *handle, const char *name, Filename *result) { - write_setting_s(handle, name, result->path); + /* + * When saving a session involving a Filename, we use the 'cpath' + * member of the Filename structure, because otherwise we break + * backwards compatibility with existing saved sessions. + * + * This means that 'exotic' filenames - those including Unicode + * characters outside the host system's CP_ACP default code page - + * cannot be represented faithfully, and saving and reloading a + * Conf including one will break it. + * + * This can't be fixed without breaking backwards compatibility, + * and if we're going to break compatibility then we should break + * it good and hard (the Nanny Ogg principle), and devise a + * completely fresh storage representation that fixes as many + * other legacy problems as possible at the same time. + */ + write_setting_s(handle, name, result->cpath); /* FIXME */ } void close_settings_r(settings_r *handle) @@ -221,13 +237,13 @@ settings_e *enum_settings_start(void) if (!key) return NULL; - settings_e *ret = snew(settings_e); - if (ret) { - ret->key = key; - ret->i = 0; + settings_e *e = snew(settings_e); + if (e) { + e->key = key; + e->i = 0; } - return ret; + return e; } bool enum_settings_next(settings_e *e, strbuf *sb) @@ -357,7 +373,7 @@ bool have_ssh_host_key(const char *hostname, int port, return check_stored_host_key(hostname, port, keytype, "") != 1; } -void store_host_key(const char *hostname, int port, +void store_host_key(Seat *seat, const char *hostname, int port, const char *keytype, const char *key) { strbuf *regname = strbuf_new(); diff --git a/windows/test_screenshot.c b/windows/test/test_screenshot.c similarity index 69% rename from windows/test_screenshot.c rename to windows/test/test_screenshot.c index 1e3a20d7..01643f4e 100644 --- a/windows/test_screenshot.c +++ b/windows/test/test_screenshot.c @@ -15,22 +15,23 @@ void out_of_memory(void) { fatal_error("out of memory"); } int main(int argc, char **argv) { - const char *outfile = NULL; + Filename *outfile = NULL; - AuxMatchOpt amo = aux_match_opt_init(argc-1, argv+1, 0, fatal_error); + AuxMatchOpt amo = aux_match_opt_init(fatal_error); while (!aux_match_done(&amo)) { - char *val; + CmdlineArg *val; #define match_opt(...) aux_match_opt( \ &amo, NULL, __VA_ARGS__, (const char *)NULL) #define match_optval(...) aux_match_opt( \ &amo, &val, __VA_ARGS__, (const char *)NULL) if (aux_match_arg(&amo, &val)) { - fatal_error("unexpected argument '%s'", val); + fatal_error("unexpected argument '%s'", cmdline_arg_to_str(val)); } else if (match_optval("-o", "--output")) { - outfile = val; + outfile = cmdline_arg_to_filename(val); } else { - fatal_error("unrecognised option '%s'\n", amo.argv[amo.index]); + fatal_error("unrecognised option '%s'\n", + cmdline_arg_to_str(amo.arglist->args[amo.index])); } } @@ -40,6 +41,7 @@ int main(int argc, char **argv) char *err = save_screenshot(NULL, outfile); if (err) fatal_error("%s", err); + filename_free(outfile); return 0; } diff --git a/windows/test/test_split_into_argv.c b/windows/test/test_split_into_argv.c new file mode 100644 index 00000000..23734154 --- /dev/null +++ b/windows/test/test_split_into_argv.c @@ -0,0 +1,444 @@ +/* + * Test program for split_into_argv. + */ + +#include "putty.h" + +const struct argv_test { + const char *cmdline; + const char *argv[10]; + bool include_program_name; +} argv_tests[] = { + /* + * We generate this set of tests by invoking ourself with + * `-generate'. + */ +#if !MOD3 + /* Newer behaviour, with no weird mod-3 glitch. */ + {"ab c\" d", {"ab", "c d", NULL}}, + {"a\"b c\" d", {"ab c", "d", NULL}}, + {"a\"\"b c\" d", {"ab", "c d", NULL}}, + {"a\"\"\"b c\" d", {"a\"b c", "d", NULL}}, + {"a\"\"\"\"b c\" d", {"a\"b", "c d", NULL}}, + {"a\"\"\"\"\"b c\" d", {"a\"\"b c", "d", NULL}}, + {"a\"\"\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}}, + {"a\"\"\"\"\"\"\"b c\" d", {"a\"\"\"b c", "d", NULL}}, + {"a\"\"\"\"\"\"\"\"b c\" d", {"a\"\"\"b", "c d", NULL}}, + {"a\\b c\" d", {"a\\b", "c d", NULL}}, + {"a\\\"b c\" d", {"a\"b", "c d", NULL}}, + {"a\\\"\"b c\" d", {"a\"b c", "d", NULL}}, + {"a\\\"\"\"b c\" d", {"a\"b", "c d", NULL}}, + {"a\\\"\"\"\"b c\" d", {"a\"\"b c", "d", NULL}}, + {"a\\\"\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}}, + {"a\\\"\"\"\"\"\"b c\" d", {"a\"\"\"b c", "d", NULL}}, + {"a\\\"\"\"\"\"\"\"b c\" d", {"a\"\"\"b", "c d", NULL}}, + {"a\\\"\"\"\"\"\"\"\"b c\" d", {"a\"\"\"\"b c", "d", NULL}}, + {"a\\\\b c\" d", {"a\\\\b", "c d", NULL}}, + {"a\\\\\"b c\" d", {"a\\b c", "d", NULL}}, + {"a\\\\\"\"b c\" d", {"a\\b", "c d", NULL}}, + {"a\\\\\"\"\"b c\" d", {"a\\\"b c", "d", NULL}}, + {"a\\\\\"\"\"\"b c\" d", {"a\\\"b", "c d", NULL}}, + {"a\\\\\"\"\"\"\"b c\" d", {"a\\\"\"b c", "d", NULL}}, + {"a\\\\\"\"\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}}, + {"a\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b c", "d", NULL}}, + {"a\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b", "c d", NULL}}, + {"a\\\\\\b c\" d", {"a\\\\\\b", "c d", NULL}}, + {"a\\\\\\\"b c\" d", {"a\\\"b", "c d", NULL}}, + {"a\\\\\\\"\"b c\" d", {"a\\\"b c", "d", NULL}}, + {"a\\\\\\\"\"\"b c\" d", {"a\\\"b", "c d", NULL}}, + {"a\\\\\\\"\"\"\"b c\" d", {"a\\\"\"b c", "d", NULL}}, + {"a\\\\\\\"\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}}, + {"a\\\\\\\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b c", "d", NULL}}, + {"a\\\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b", "c d", NULL}}, + {"a\\\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"\"b c", "d", NULL}}, + {"a\\\\\\\\b c\" d", {"a\\\\\\\\b", "c d", NULL}}, + {"a\\\\\\\\\"b c\" d", {"a\\\\b c", "d", NULL}}, + {"a\\\\\\\\\"\"b c\" d", {"a\\\\b", "c d", NULL}}, + {"a\\\\\\\\\"\"\"b c\" d", {"a\\\\\"b c", "d", NULL}}, + {"a\\\\\\\\\"\"\"\"b c\" d", {"a\\\\\"b", "c d", NULL}}, + {"a\\\\\\\\\"\"\"\"\"b c\" d", {"a\\\\\"\"b c", "d", NULL}}, + {"a\\\\\\\\\"\"\"\"\"\"b c\" d", {"a\\\\\"\"b", "c d", NULL}}, + {"a\\\\\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\\\"\"\"b c", "d", NULL}}, + {"a\\\\\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\\\"\"\"b", "c d", NULL}}, + {"\"ab c\" d", {"ab c", "d", NULL}}, + {"\"a\"b c\" d", {"ab", "c d", NULL}}, + {"\"a\"\"b c\" d", {"a\"b c", "d", NULL}}, + {"\"a\"\"\"b c\" d", {"a\"b", "c d", NULL}}, + {"\"a\"\"\"\"b c\" d", {"a\"\"b c", "d", NULL}}, + {"\"a\"\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}}, + {"\"a\"\"\"\"\"\"b c\" d", {"a\"\"\"b c", "d", NULL}}, + {"\"a\"\"\"\"\"\"\"b c\" d", {"a\"\"\"b", "c d", NULL}}, + {"\"a\"\"\"\"\"\"\"\"b c\" d", {"a\"\"\"\"b c", "d", NULL}}, + {"\"a\\b c\" d", {"a\\b c", "d", NULL}}, + {"\"a\\\"b c\" d", {"a\"b c", "d", NULL}}, + {"\"a\\\"\"b c\" d", {"a\"b", "c d", NULL}}, + {"\"a\\\"\"\"b c\" d", {"a\"\"b c", "d", NULL}}, + {"\"a\\\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}}, + {"\"a\\\"\"\"\"\"b c\" d", {"a\"\"\"b c", "d", NULL}}, + {"\"a\\\"\"\"\"\"\"b c\" d", {"a\"\"\"b", "c d", NULL}}, + {"\"a\\\"\"\"\"\"\"\"b c\" d", {"a\"\"\"\"b c", "d", NULL}}, + {"\"a\\\"\"\"\"\"\"\"\"b c\" d", {"a\"\"\"\"b", "c d", NULL}}, + {"\"a\\\\b c\" d", {"a\\\\b c", "d", NULL}}, + {"\"a\\\\\"b c\" d", {"a\\b", "c d", NULL}}, + {"\"a\\\\\"\"b c\" d", {"a\\\"b c", "d", NULL}}, + {"\"a\\\\\"\"\"b c\" d", {"a\\\"b", "c d", NULL}}, + {"\"a\\\\\"\"\"\"b c\" d", {"a\\\"\"b c", "d", NULL}}, + {"\"a\\\\\"\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}}, + {"\"a\\\\\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b c", "d", NULL}}, + {"\"a\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b", "c d", NULL}}, + {"\"a\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"\"b c", "d", NULL}}, + {"\"a\\\\\\b c\" d", {"a\\\\\\b c", "d", NULL}}, + {"\"a\\\\\\\"b c\" d", {"a\\\"b c", "d", NULL}}, + {"\"a\\\\\\\"\"b c\" d", {"a\\\"b", "c d", NULL}}, + {"\"a\\\\\\\"\"\"b c\" d", {"a\\\"\"b c", "d", NULL}}, + {"\"a\\\\\\\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}}, + {"\"a\\\\\\\"\"\"\"\"b c\" d", {"a\\\"\"\"b c", "d", NULL}}, + {"\"a\\\\\\\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b", "c d", NULL}}, + {"\"a\\\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"\"b c", "d", NULL}}, + {"\"a\\\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"\"b", "c d", NULL}}, + {"\"a\\\\\\\\b c\" d", {"a\\\\\\\\b c", "d", NULL}}, + {"\"a\\\\\\\\\"b c\" d", {"a\\\\b", "c d", NULL}}, + {"\"a\\\\\\\\\"\"b c\" d", {"a\\\\\"b c", "d", NULL}}, + {"\"a\\\\\\\\\"\"\"b c\" d", {"a\\\\\"b", "c d", NULL}}, + {"\"a\\\\\\\\\"\"\"\"b c\" d", {"a\\\\\"\"b c", "d", NULL}}, + {"\"a\\\\\\\\\"\"\"\"\"b c\" d", {"a\\\\\"\"b", "c d", NULL}}, + {"\"a\\\\\\\\\"\"\"\"\"\"b c\" d", {"a\\\\\"\"\"b c", "d", NULL}}, + {"\"a\\\\\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\\\"\"\"b", "c d", NULL}}, + {"\"a\\\\\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\\\"\"\"\"b c", "d", NULL}}, +#else /* MOD3 */ + /* VS7 mod-3 behaviour. */ + {"ab c\" d", {"ab", "c d", NULL}}, + {"a\"b c\" d", {"ab c", "d", NULL}}, + {"a\"\"b c\" d", {"ab", "c d", NULL}}, + {"a\"\"\"b c\" d", {"a\"b", "c d", NULL}}, + {"a\"\"\"\"b c\" d", {"a\"b c", "d", NULL}}, + {"a\"\"\"\"\"b c\" d", {"a\"b", "c d", NULL}}, + {"a\"\"\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}}, + {"a\"\"\"\"\"\"\"b c\" d", {"a\"\"b c", "d", NULL}}, + {"a\"\"\"\"\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}}, + {"a\\b c\" d", {"a\\b", "c d", NULL}}, + {"a\\\"b c\" d", {"a\"b", "c d", NULL}}, + {"a\\\"\"b c\" d", {"a\"b c", "d", NULL}}, + {"a\\\"\"\"b c\" d", {"a\"b", "c d", NULL}}, + {"a\\\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}}, + {"a\\\"\"\"\"\"b c\" d", {"a\"\"b c", "d", NULL}}, + {"a\\\"\"\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}}, + {"a\\\"\"\"\"\"\"\"b c\" d", {"a\"\"\"b", "c d", NULL}}, + {"a\\\"\"\"\"\"\"\"\"b c\" d", {"a\"\"\"b c", "d", NULL}}, + {"a\\\\b c\" d", {"a\\\\b", "c d", NULL}}, + {"a\\\\\"b c\" d", {"a\\b c", "d", NULL}}, + {"a\\\\\"\"b c\" d", {"a\\b", "c d", NULL}}, + {"a\\\\\"\"\"b c\" d", {"a\\\"b", "c d", NULL}}, + {"a\\\\\"\"\"\"b c\" d", {"a\\\"b c", "d", NULL}}, + {"a\\\\\"\"\"\"\"b c\" d", {"a\\\"b", "c d", NULL}}, + {"a\\\\\"\"\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}}, + {"a\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\"\"b c", "d", NULL}}, + {"a\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}}, + {"a\\\\\\b c\" d", {"a\\\\\\b", "c d", NULL}}, + {"a\\\\\\\"b c\" d", {"a\\\"b", "c d", NULL}}, + {"a\\\\\\\"\"b c\" d", {"a\\\"b c", "d", NULL}}, + {"a\\\\\\\"\"\"b c\" d", {"a\\\"b", "c d", NULL}}, + {"a\\\\\\\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}}, + {"a\\\\\\\"\"\"\"\"b c\" d", {"a\\\"\"b c", "d", NULL}}, + {"a\\\\\\\"\"\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}}, + {"a\\\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b", "c d", NULL}}, + {"a\\\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b c", "d", NULL}}, + {"a\\\\\\\\b c\" d", {"a\\\\\\\\b", "c d", NULL}}, + {"a\\\\\\\\\"b c\" d", {"a\\\\b c", "d", NULL}}, + {"a\\\\\\\\\"\"b c\" d", {"a\\\\b", "c d", NULL}}, + {"a\\\\\\\\\"\"\"b c\" d", {"a\\\\\"b", "c d", NULL}}, + {"a\\\\\\\\\"\"\"\"b c\" d", {"a\\\\\"b c", "d", NULL}}, + {"a\\\\\\\\\"\"\"\"\"b c\" d", {"a\\\\\"b", "c d", NULL}}, + {"a\\\\\\\\\"\"\"\"\"\"b c\" d", {"a\\\\\"\"b", "c d", NULL}}, + {"a\\\\\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\\\"\"b c", "d", NULL}}, + {"a\\\\\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\\\"\"b", "c d", NULL}}, + {"\"ab c\" d", {"ab c", "d", NULL}}, + {"\"a\"b c\" d", {"ab", "c d", NULL}}, + {"\"a\"\"b c\" d", {"a\"b", "c d", NULL}}, + {"\"a\"\"\"b c\" d", {"a\"b c", "d", NULL}}, + {"\"a\"\"\"\"b c\" d", {"a\"b", "c d", NULL}}, + {"\"a\"\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}}, + {"\"a\"\"\"\"\"\"b c\" d", {"a\"\"b c", "d", NULL}}, + {"\"a\"\"\"\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}}, + {"\"a\"\"\"\"\"\"\"\"b c\" d", {"a\"\"\"b", "c d", NULL}}, + {"\"a\\b c\" d", {"a\\b c", "d", NULL}}, + {"\"a\\\"b c\" d", {"a\"b c", "d", NULL}}, + {"\"a\\\"\"b c\" d", {"a\"b", "c d", NULL}}, + {"\"a\\\"\"\"b c\" d", {"a\"\"b", "c d", NULL}}, + {"\"a\\\"\"\"\"b c\" d", {"a\"\"b c", "d", NULL}}, + {"\"a\\\"\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}}, + {"\"a\\\"\"\"\"\"\"b c\" d", {"a\"\"\"b", "c d", NULL}}, + {"\"a\\\"\"\"\"\"\"\"b c\" d", {"a\"\"\"b c", "d", NULL}}, + {"\"a\\\"\"\"\"\"\"\"\"b c\" d", {"a\"\"\"b", "c d", NULL}}, + {"\"a\\\\b c\" d", {"a\\\\b c", "d", NULL}}, + {"\"a\\\\\"b c\" d", {"a\\b", "c d", NULL}}, + {"\"a\\\\\"\"b c\" d", {"a\\\"b", "c d", NULL}}, + {"\"a\\\\\"\"\"b c\" d", {"a\\\"b c", "d", NULL}}, + {"\"a\\\\\"\"\"\"b c\" d", {"a\\\"b", "c d", NULL}}, + {"\"a\\\\\"\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}}, + {"\"a\\\\\"\"\"\"\"\"b c\" d", {"a\\\"\"b c", "d", NULL}}, + {"\"a\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}}, + {"\"a\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b", "c d", NULL}}, + {"\"a\\\\\\b c\" d", {"a\\\\\\b c", "d", NULL}}, + {"\"a\\\\\\\"b c\" d", {"a\\\"b c", "d", NULL}}, + {"\"a\\\\\\\"\"b c\" d", {"a\\\"b", "c d", NULL}}, + {"\"a\\\\\\\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}}, + {"\"a\\\\\\\"\"\"\"b c\" d", {"a\\\"\"b c", "d", NULL}}, + {"\"a\\\\\\\"\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}}, + {"\"a\\\\\\\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b", "c d", NULL}}, + {"\"a\\\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b c", "d", NULL}}, + {"\"a\\\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b", "c d", NULL}}, + {"\"a\\\\\\\\b c\" d", {"a\\\\\\\\b c", "d", NULL}}, + {"\"a\\\\\\\\\"b c\" d", {"a\\\\b", "c d", NULL}}, + {"\"a\\\\\\\\\"\"b c\" d", {"a\\\\\"b", "c d", NULL}}, + {"\"a\\\\\\\\\"\"\"b c\" d", {"a\\\\\"b c", "d", NULL}}, + {"\"a\\\\\\\\\"\"\"\"b c\" d", {"a\\\\\"b", "c d", NULL}}, + {"\"a\\\\\\\\\"\"\"\"\"b c\" d", {"a\\\\\"\"b", "c d", NULL}}, + {"\"a\\\\\\\\\"\"\"\"\"\"b c\" d", {"a\\\\\"\"b c", "d", NULL}}, + {"\"a\\\\\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\\\"\"b", "c d", NULL}}, + {"\"a\\\\\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\\\"\"\"b", "c d", NULL}}, +#endif /* MOD3 */ + /* Common tests that check the special program-name rule. */ + {"\"a b\\\"c \"d e\" \"f g\"", {"a b\\c", "d e", "f g", NULL}, true}, + {"\"a b\\\"c \"d e\" \"f g\"", {"a b\"c d", "e f", "g", NULL}, false}, +}; + +void out_of_memory(void) +{ + fprintf(stderr, "out of memory!\n"); + exit(2); +} + +int main(int argc, char **argv) +{ + int i, j; + + if (argc > 1) { + /* + * Generation of tests. + * + * Given `-splat ', we print out a C-style + * representation of each argument (in the form "a", "b", + * NULL), backslash-escaping each backslash and double + * quote. + * + * Given `-split ', we first doctor `string' by + * turning forward slashes into backslashes, single quotes + * into double quotes and underscores into spaces; and then + * we feed the resulting string to ourself with `-splat'. + * + * Given `-generate', we concoct a variety of fun test + * cases, encode them in quote-safe form (mapping \, " and + * space to /, ' and _ respectively) and feed each one to + * `-split'. + */ + if (!strcmp(argv[1], "-splat")) { + int i; + char *p; + for (i = 2; i < argc; i++) { + putchar('"'); + for (p = argv[i]; *p; p++) { + if (*p == '\\' || *p == '"') + putchar('\\'); + putchar(*p); + } + printf("\", "); + } + printf("NULL"); + return 0; + } + + if (!strcmp(argv[1], "-split") && argc > 2) { + strbuf *cmdline = strbuf_new(); + char *p; + + put_fmt(cmdline, "%s -splat ", argv[0]); + printf(" {\""); + size_t args_start = cmdline->len; + for (p = argv[2]; *p; p++) { + char c = (*p == '/' ? '\\' : + *p == '\'' ? '"' : + *p == '_' ? ' ' : + *p); + put_byte(cmdline, c); + } + write_c_string_literal(stdout, ptrlen_from_asciz( + cmdline->s + args_start)); + printf("\", {"); + fflush(stdout); + + system(cmdline->s); + + printf("}},\n"); + + strbuf_free(cmdline); + return 0; + } + + if (!strcmp(argv[1], "-generate")) { + char *teststr, *p; + int i, initialquote, backslashes, quotes; + + teststr = malloc(200 + strlen(argv[0])); + + for (initialquote = 0; initialquote <= 1; initialquote++) { + for (backslashes = 0; backslashes < 5; backslashes++) { + for (quotes = 0; quotes < 9; quotes++) { + p = teststr + sprintf(teststr, "%s -split ", argv[0]); + if (initialquote) *p++ = '\''; + *p++ = 'a'; + for (i = 0; i < backslashes; i++) *p++ = '/'; + for (i = 0; i < quotes; i++) *p++ = '\''; + *p++ = 'b'; + *p++ = '_'; + *p++ = 'c'; + *p++ = '\''; + *p++ = '_'; + *p++ = 'd'; + *p = '\0'; + + system(teststr); + } + } + } + return 0; + } + + if (!strcmp(argv[1], "-tabulate")) { + char table[] = "\ + * backslashes \n\ + * \n\ + * 0 1 2 3 4 \n\ + * \n\ + * 0 | \n\ + * --------+----------------------------- \n\ + * 1 | \n\ + * q 2 | \n\ + * u 3 | \n\ + * o 4 | \n\ + * t 5 | \n\ + * e 6 | \n\ + * s 7 | \n\ + * 8 | \n\ +"; + char *linestarts[14]; + char *p = table; + for (i = 0; i < lenof(linestarts); i++) { + linestarts[i] = p; + p += strcspn(p, "\n"); + if (*p) p++; + } + + for (i = 0; i < lenof(argv_tests); i++) { + const struct argv_test *test = &argv_tests[i]; + const char *q = test->cmdline; + + /* Skip tests that aren't telling us something about + * the behaviour _inside_ a quoted string */ + if (*q != '"') + continue; + + q++; + + assert(*q == 'a'); + q++; + int backslashes_in = 0, quotes_in = 0; + while (*q == '\\') { + q++; + backslashes_in++; + } + while (*q == '"') { + q++; + quotes_in++; + } + + q = test->argv[0]; + assert(*q == 'a'); + q++; + int backslashes_out = 0, quotes_out = 0; + while (*q == '\\') { + q++; + backslashes_out++; + } + while (*q == '"') { + q++; + quotes_out++; + } + assert(*q == 'b'); + q++; + bool in_quoted_string = (*q == ' '); + + int x = (backslashes_in == 0 ? 15 : 18 + 7 * backslashes_in); + int y = (quotes_in == 0 ? 4 : 5 + quotes_in); + char *buf = dupprintf("%d,%d,%c", + backslashes_out, quotes_out, + in_quoted_string ? 'y' : 'n'); + memcpy(linestarts[y] + x, buf, strlen(buf)); + sfree(buf); + } + + fputs(table, stdout); + return 0; + } + + fprintf(stderr, "unrecognised option: \"%s\"\n", argv[1]); + return 1; + } + + /* + * If we get here, we were invoked with no arguments, so just + * run the tests. + */ + int passes = 0, fails = 0; + + for (i = 0; i < lenof(argv_tests); i++) { + int ac; + char **av; + bool failed = false; + + split_into_argv((char *)argv_tests[i].cmdline, + argv_tests[i].include_program_name, &ac, &av, NULL); + + for (j = 0; j < ac && argv_tests[i].argv[j]; j++) { + if (strcmp(av[j], argv_tests[i].argv[j])) { + printf("failed test %d (|%s|) arg %d: |%s| should be |%s|\n", + i, argv_tests[i].cmdline, + j, av[j], argv_tests[i].argv[j]); + failed = true; + } +#ifdef VERBOSE + else { + printf("test %d (|%s|) arg %d: |%s| == |%s|\n", + i, argv_tests[i].cmdline, + j, av[j], argv_tests[i].argv[j]); + } +#endif + } + if (j < ac) { + printf("failed test %d (|%s|): %d args returned, should be %d\n", + i, argv_tests[i].cmdline, ac, j); + failed = true; + } + if (argv_tests[i].argv[j]) { + printf("failed test %d (|%s|): %d args returned, should be more\n", + i, argv_tests[i].cmdline, ac); + failed = true; + } + + if (failed) + fails++; + else + passes++; + } + + printf("passed %d failed %d (%s mode)\n", + passes, fails, +#if MOD3 + "mod 3" +#else + "mod 2" +#endif + ); + + return fails != 0; +} diff --git a/windows/unicode.c b/windows/unicode.c index 3b56f252..68b509ec 100644 --- a/windows/unicode.c +++ b/windows/unicode.c @@ -682,20 +682,25 @@ void init_ucs(Conf *conf, struct unicode_data *ucsdata) if (!DIRECT_FONT(ucsdata->unitab_xterm[i])) ucsdata->unitab_xterm[i] = (WCHAR) (CSET_ACP + poorman_vt100[i - 96]); - for(i=128;i<256;i++) + for (i = 128; i < 256; i++) if (!DIRECT_FONT(ucsdata->unitab_scoacs[i])) ucsdata->unitab_scoacs[i] = (WCHAR) (CSET_ACP + poorman_scoacs[i - 128]); } } +void init_ucs_generic(Conf *conf, struct unicode_data *ucsdata) +{ + init_ucs(conf, ucsdata); +} + static void link_font(WCHAR *line_tbl, WCHAR *font_tbl, WCHAR attr) { int font_index, line_index, i; for (line_index = 0; line_index < 256; line_index++) { if (DIRECT_FONT(line_tbl[line_index])) continue; - for(i = 0; i < 256; i++) { + for (i = 0; i < 256; i++) { font_index = ((32 + i) & 0xFF); if (line_tbl[line_index] == font_tbl[font_index]) { line_tbl[line_index] = (WCHAR) (attr + font_index); @@ -1227,8 +1232,7 @@ void get_unitab(int codepage, wchar_t *unitab, int ftype) for (i = 0; i < max; i++) { tbuf[0] = i; - if (mb_to_wc(codepage, flg, tbuf, 1, unitab + i, 1) - != 1) + if (MultiByteToWideChar(codepage, flg, tbuf, 1, unitab+i, 1) != 1) unitab[i] = 0xFFFD; } } else { @@ -1240,162 +1244,192 @@ void get_unitab(int codepage, wchar_t *unitab, int ftype) } } -int wc_to_mb(int codepage, int flags, const wchar_t *wcstr, int wclen, - char *mbstr, int mblen, const char *defchr) +bool BinarySink_put_wc_to_mb( + BinarySink *bs, int codepage, const wchar_t *wcstr, int wclen, + const char *defchr) { + if (!wclen) + return true; + reverse_mapping *rmap = get_reverse_mapping(codepage); if (rmap) { + size_t defchr_len = 0; + bool defchr_len_known = false; + /* Do this by array lookup if we can. */ - if (wclen < 0) { - for (wclen = 0; wcstr[wclen++] ;); /* will include the NUL */ - } - char *p; - int i; - for (p = mbstr, i = 0; i < wclen; i++) { + for (size_t i = 0; i < wclen; i++) { wchar_t ch = wcstr[i]; int by; - const char *p1; - - #define WRITECH(chr) do \ - { \ - assert(p - mbstr < mblen); \ - *p++ = (char)(chr); \ - } while (0) + const char *blk; - if ((p1 = rmap->blocks[(ch >> 8) & 0xFF]) != NULL && - (by = p1[ch & 0xFF]) != '\0') - WRITECH(by); + if ((blk = rmap->blocks[(ch >> 8) & 0xFF]) != NULL && + (by = blk[ch & 0xFF]) != '\0') + put_byte(bs, by); else if (ch < 0x80) - WRITECH(ch); - else if (defchr) - for (const char *q = defchr; *q; q++) - WRITECH(*q); -#if 1 - else - WRITECH('.'); -#endif + put_byte(bs, ch); + else if (defchr) { + if (!defchr_len_known) { + defchr_len = strlen(defchr); + defchr_len_known = true; + } + put_data(bs, defchr, defchr_len); + } + } + return true; + } - #undef WRITECH + { + char internalbuf[2048]; + char *allocbuf = NULL; + size_t allocsize = 0; + char *currbuf = internalbuf; + size_t currsize = lenof(internalbuf); + bool success; + + BOOL defused = false; + BOOL *defusedp = &defused; + + if (codepage == CP_UTF8 || !defchr[0]) { + /* + * The Win32 API spec says that defchr and defused must be + * NULL when doing a UTF-8 conversion, on pain of + * ERROR_INVALID_PARAMETER. + * + * Also, translate defchr="" on input to NULL in the Win32 + * API. + */ + defchr = NULL; + defusedp = NULL; } - return p - mbstr; - } else { - int defused, ret; - ret = WideCharToMultiByte(codepage, flags, wcstr, wclen, - mbstr, mblen, defchr, &defused); - if (ret) - return ret; -#ifdef LEGACY_WINDOWS - /* - * Fallback for legacy platforms too old to support UTF-8: if - * the codepage is UTF-8, we can do the translation ourselves. - */ - if (codepage == CP_UTF8 && mblen > 0 && wclen > 0) { - size_t remaining = mblen; - char *p = mbstr; - - while (wclen > 0) { - unsigned long wc = (wclen--, *wcstr++); - if (wclen > 0 && IS_SURROGATE_PAIR(wc, *wcstr)) { - wc = FROM_SURROGATES(wc, *wcstr); - wclen--, wcstr++; - } + while (true) { + int ret = WideCharToMultiByte( + codepage, 0, wcstr, wclen, currbuf, currsize, + defchr, defusedp); - char utfbuf[6]; - size_t utflen = encode_utf8(utfbuf, wc); - if (utflen <= remaining) { - memcpy(p, utfbuf, utflen); - p += utflen; - remaining -= utflen; - } else { - return p - mbstr; - } + if (ret) { + put_data(bs, currbuf, ret); + success = true; + break; + } else if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + success = false; + break; + } else { + sgrowarray_nm(allocbuf, allocsize, currsize); + currbuf = allocbuf; + currsize = allocsize; } + } + + smemclr(allocbuf, allocsize); + if (success) + return true; + } - return p - mbstr; +#ifdef LEGACY_WINDOWS + /* + * Fallback for legacy platforms too old to support UTF-8: if + * the codepage is UTF-8, we can do the translation ourselves. + */ + if (codepage == CP_UTF8 && wclen > 0) { + while (wclen > 0) { + unsigned long wc = (wclen--, *wcstr++); + if (wclen > 0 && IS_SURROGATE_PAIR(wc, *wcstr)) { + wc = FROM_SURROGATES(wc, *wcstr); + wclen--, wcstr++; + } + put_utf8_char(bs, wc); } -#endif - /* No other fallbacks are available */ - return 0; + return true; } +#endif + + /* No other fallbacks are available */ + return false; } -int mb_to_wc(int codepage, int flags, const char *mbstr, int mblen, - wchar_t *wcstr, int wclen) +bool BinarySink_put_mb_to_wc( + BinarySink *bs, int codepage, const char *mbstr, int mblen) { + if (!mblen) + return true; + if (codepage >= 65536) { /* Character set not known to Windows, so we'll have to * translate it ourself */ size_t index = codepage - 65536; if (index >= lenof(cp_list)) - return 0; + return false; const struct cp_list_item *cp = &cp_list[index]; if (!cp->cp_table) - return 0; + return false; - size_t remaining = wclen; - wchar_t *p = wcstr; unsigned tablebase = 256 - cp->cp_size; while (mblen > 0) { mblen--; unsigned c = 0xFF & *mbstr++; wchar_t wc = (c < tablebase ? c : cp->cp_table[c - tablebase]); - if (remaining > 0) { - remaining--; - *p++ = wc; + put_data(bs, &wc, sizeof(wc)); + } + + return true; + } + + { + wchar_t internalbuf[1024]; + wchar_t *allocbuf = NULL; + size_t allocsize = 0; + wchar_t *currbuf = internalbuf; + size_t currsize = lenof(internalbuf); + bool success; + + while (true) { + int ret = MultiByteToWideChar( + codepage, 0, mbstr, mblen, currbuf, currsize); + + if (ret > 0) { + put_data(bs, currbuf, ret * sizeof(wchar_t)); + success = true; + break; + } else if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + success = false; + break; } else { - return p - wcstr; + sgrowarray_nm(allocbuf, allocsize, currsize); + currbuf = allocbuf; + currsize = allocsize; } } - return p - wcstr; + smemclr(allocbuf, allocsize * sizeof(wchar_t)); + if (success) + return true; } - int ret = MultiByteToWideChar(codepage, flags, mbstr, mblen, wcstr, wclen); - if (ret) - return ret; - #ifdef LEGACY_WINDOWS /* * Fallback for legacy platforms too old to support UTF-8: if the * codepage is UTF-8, we can do the translation ourselves. */ - if (codepage == CP_UTF8 && mblen > 0 && wclen > 0) { - size_t remaining = wclen; - wchar_t *p = wcstr; + if (codepage == CP_UTF8 && mblen > 0) { + BinarySource src[1]; + BinarySource_BARE_INIT(src, mbstr, mblen); - while (mblen > 0) { - char utfbuf[7]; - int thissize = mblen < 6 ? mblen : 6; - memcpy(utfbuf, mbstr, thissize); - utfbuf[thissize] = '\0'; - - const char *utfptr = utfbuf; + while (get_avail(src)) { wchar_t wcbuf[2]; - size_t nwc = decode_utf8_to_wchar(&utfptr, wcbuf); - - for (size_t i = 0; i < nwc; i++) { - if (remaining > 0) { - remaining--; - *p++ = wcbuf[i]; - } else { - return p - wcstr; - } - } - - mbstr += (utfptr - utfbuf); - mblen -= (utfptr - utfbuf); + size_t nwc = decode_utf8_to_wchar(src, wcbuf, NULL); + put_data(bs, wcbuf, nwc * sizeof(wchar_t)); } - return p - wcstr; + return true; } #endif /* No other fallbacks are available */ - return 0; + return false; } bool is_dbcs_leadbyte(int codepage, char byte) diff --git a/windows/utils/aux_match_opt.c b/windows/utils/aux_match_opt.c index 190eddac..93080e7e 100644 --- a/windows/utils/aux_match_opt.c +++ b/windows/utils/aux_match_opt.c @@ -13,14 +13,12 @@ /* * Call this to initialise the state structure. */ -AuxMatchOpt aux_match_opt_init(int argc, char **argv, int start_index, - aux_opt_error_fn_t opt_error) +AuxMatchOpt aux_match_opt_init(aux_opt_error_fn_t opt_error) { AuxMatchOpt amo[1]; - amo->argc = argc; - amo->argv = argv; - amo->index = start_index; + amo->arglist = cmdline_arg_list_from_GetCommandLineW(); + amo->index = 0; amo->doing_opts = true; amo->error = opt_error; @@ -32,12 +30,14 @@ AuxMatchOpt aux_match_opt_init(int argc, char **argv, int start_index, * Point 'val' at a char * to receive the option argument, if one is * expected. Set 'val' to NULL if no argument is expected. */ -bool aux_match_opt(AuxMatchOpt *amo, char **val, const char *optname, ...) +bool aux_match_opt(AuxMatchOpt *amo, CmdlineArg **val, + const char *optname, ...) { - assert(amo->index < amo->argc); - /* Find the end of the option name */ - char *opt = amo->argv[amo->index]; + CmdlineArg *optarg = amo->arglist->args[amo->index]; + assert(optarg); + const char *opt = cmdline_arg_to_utf8(optarg); + ptrlen argopt; argopt.ptr = opt; argopt.len = strcspn(opt, "="); @@ -72,14 +72,14 @@ bool aux_match_opt(AuxMatchOpt *amo, char **val, const char *optname, ...) if (opt[argopt.len]) { if (!val) amo->error("option '%s' does not expect a value", opt); - *val = opt + argopt.len + 1; + *val = cmdline_arg_from_utf8(optarg->list, opt + argopt.len + 1); amo->index++; } else if (!val) { amo->index++; } else { - if (amo->index + 1 >= amo->argc) + if (!amo->arglist->args[amo->index + 1]) amo->error("option '%s' expects a value", opt); - *val = amo->argv[amo->index + 1]; + *val = amo->arglist->args[amo->index + 1]; amo->index += 2; } @@ -89,13 +89,14 @@ bool aux_match_opt(AuxMatchOpt *amo, char **val, const char *optname, ...) /* * Call this to return a non-option argument in *val. */ -bool aux_match_arg(AuxMatchOpt *amo, char **val) +bool aux_match_arg(AuxMatchOpt *amo, CmdlineArg **val) { - assert(amo->index < amo->argc); - char *opt = amo->argv[amo->index]; + CmdlineArg *optarg = amo->arglist->args[amo->index]; + assert(optarg); + const char *opt = cmdline_arg_to_utf8(optarg); if (!amo->doing_opts || opt[0] != '-' || !strcmp(opt, "-")) { - *val = opt; + *val = optarg; amo->index++; return true; } @@ -108,10 +109,12 @@ bool aux_match_arg(AuxMatchOpt *amo, char **val) */ bool aux_match_done(AuxMatchOpt *amo) { - if (amo->index < amo->argc && !strcmp(amo->argv[amo->index], "--")) { + CmdlineArg *optarg = amo->arglist->args[amo->index]; + const char *opt = cmdline_arg_to_utf8(optarg); + if (opt && !strcmp(opt, "--")) { amo->doing_opts = false; amo->index++; } - return amo->index >= amo->argc; + return amo->arglist->args[amo->index] == NULL; } diff --git a/windows/utils/cmdline_arg.c b/windows/utils/cmdline_arg.c new file mode 100644 index 00000000..cb232361 --- /dev/null +++ b/windows/utils/cmdline_arg.c @@ -0,0 +1,218 @@ +/* + * Implementation of the CmdlineArg abstraction for Windows + */ + +#include +#include "putty.h" + +typedef struct CmdlineArgWin CmdlineArgWin; +struct CmdlineArgWin { + /* + * The original wide-character argument. + */ + wchar_t *wide; + + /* + * Two translations of the wide-character argument into UTF-8 + * (maximally faithful to the original) and CP_ACP (the normal + * system code page). + */ + char *utf8, *acp; + + /* + * Our index in the CmdlineArgList, or (size_t)-1 if we don't have + * one and are an argument invented later. + */ + size_t index; + + /* + * Public part of the structure. + */ + CmdlineArg argp; +}; + +typedef struct CmdlineArgListWin CmdlineArgListWin; +struct CmdlineArgListWin { + /* + * Wide string pointer returned from GetCommandLineW. This points + * to the 'official' version of the command line, in the sense + * that overwriting it causes different text to show up in the + * Task Manager display of the process's command line. (So this is + * what we'll need to overwrite _on purpose_ for cmdline_arg_wipe.) + */ + wchar_t *cmdline; + + /* + * Data returned from split_into_argv_w. + */ + size_t argc; + wchar_t **argv, **argstart; + + /* + * Public part of the structure. + */ + CmdlineArgList listp; +}; + +static CmdlineArgWin *cmdline_arg_new_in_list(CmdlineArgList *listp) +{ + CmdlineArgWin *arg = snew(CmdlineArgWin); + arg->wide = NULL; + arg->utf8 = arg->acp = NULL; + arg->index = (size_t)-1; + arg->argp.list = listp; + sgrowarray(listp->args, listp->argssize, listp->nargs); + listp->args[listp->nargs++] = &arg->argp; + return arg; +} + +static CmdlineArg *cmdline_arg_from_wide_argv_word( + CmdlineArgList *list, wchar_t *word) +{ + CmdlineArgWin *arg = cmdline_arg_new_in_list(list); + arg->wide = dupwcs(word); + arg->utf8 = dup_wc_to_mb(CP_UTF8, word, ""); + arg->acp = dup_wc_to_mb(CP_ACP, word, ""); + return &arg->argp; +} + +CmdlineArgList *cmdline_arg_list_from_GetCommandLineW(void) +{ + CmdlineArgListWin *list = snew(CmdlineArgListWin); + CmdlineArgList *listp = &list->listp; + + list->cmdline = GetCommandLineW(); + + int argc; + split_into_argv_w(list->cmdline, true, + &argc, &list->argv, &list->argstart); + list->argc = (size_t)argc; + + listp->args = NULL; + listp->nargs = listp->argssize = 0; + for (size_t i = 1; i < list->argc; i++) { + CmdlineArg *argp = cmdline_arg_from_wide_argv_word( + listp, list->argv[i]); + CmdlineArgWin *arg = container_of(argp, CmdlineArgWin, argp); + arg->index = i - 1; /* index in list->args[], not in argv[] */ + } + sgrowarray(listp->args, listp->argssize, listp->nargs); + listp->args[listp->nargs++] = NULL; + return listp; +} + +void cmdline_arg_free(CmdlineArg *argp) +{ + if (!argp) + return; + + CmdlineArgWin *arg = container_of(argp, CmdlineArgWin, argp); + burnwcs(arg->wide); + burnstr(arg->utf8); + burnstr(arg->acp); + sfree(arg); +} + +void cmdline_arg_list_free(CmdlineArgList *listp) +{ + CmdlineArgListWin *list = container_of(listp, CmdlineArgListWin, listp); + for (size_t i = 0; i < listp->nargs; i++) + cmdline_arg_free(listp->args[i]); + /* list->argv[0] points at the start of the string allocated by + * split_into_argv_w */ + sfree(list->argv[0]); + sfree(list->argv); + sfree(list->argstart); + sfree(list); +} + +CmdlineArg *cmdline_arg_from_str(CmdlineArgList *listp, const char *string) +{ + CmdlineArgWin *arg = cmdline_arg_new_in_list(listp); + arg->acp = dupstr(string); + arg->wide = dup_mb_to_wc(CP_ACP, string); + arg->utf8 = dup_wc_to_mb(CP_UTF8, arg->wide, ""); + return &arg->argp; +} + +CmdlineArg *cmdline_arg_from_utf8(CmdlineArgList *listp, const char *string) +{ + CmdlineArgWin *arg = cmdline_arg_new_in_list(listp); + arg->acp = dupstr(string); + arg->wide = dup_mb_to_wc(CP_UTF8, string); + arg->utf8 = dup_wc_to_mb(CP_ACP, arg->wide, ""); + return &arg->argp; +} + +const char *cmdline_arg_to_str(CmdlineArg *argp) +{ + if (!argp) + return NULL; + + CmdlineArgWin *arg = container_of(argp, CmdlineArgWin, argp); + return arg->acp; +} + +const char *cmdline_arg_to_utf8(CmdlineArg *argp) +{ + if (!argp) + return NULL; + + CmdlineArgWin *arg = container_of(argp, CmdlineArgWin, argp); + return arg->utf8; +} + +Filename *cmdline_arg_to_filename(CmdlineArg *argp) +{ + if (!argp) + return NULL; + + CmdlineArgWin *arg = container_of(argp, CmdlineArgWin, argp); + return filename_from_wstr(arg->wide); +} + +void cmdline_arg_wipe(CmdlineArg *argp) +{ + if (!argp) + return; + + CmdlineArgWin *arg = container_of(argp, CmdlineArgWin, argp); + if (arg->index != (size_t)-1) { + CmdlineArgList *listp = argp->list; + CmdlineArgListWin *list = container_of( + listp, CmdlineArgListWin, listp); + + /* arg->index starts from the first argument _after_ program + * name, whereas argstart is indexed from argv[0] */ + wchar_t *p = list->argstart[arg->index + 1]; + wchar_t *end = (arg->index + 2 < list->argc ? + list->argstart[arg->index + 2] : + p + wcslen(p)); + while (p < end) + *p++ = L' '; + } +} + +const wchar_t *cmdline_arg_remainder_wide(CmdlineArg *argp) +{ + CmdlineArgWin *arg = container_of(argp, CmdlineArgWin, argp); + CmdlineArgList *listp = argp->list; + CmdlineArgListWin *list = container_of(listp, CmdlineArgListWin, listp); + + size_t index = arg->index; + assert(index != (size_t)-1); + + /* arg->index starts from the first argument _after_ program + * name, whereas argstart is indexed from argv[0] */ + return list->argstart[index + 1]; +} + +char *cmdline_arg_remainder_acp(CmdlineArg *argp) +{ + return dup_wc_to_mb(CP_ACP, cmdline_arg_remainder_wide(argp), ""); +} + +char *cmdline_arg_remainder_utf8(CmdlineArg *argp) +{ + return dup_wc_to_mb(CP_UTF8, cmdline_arg_remainder_wide(argp), ""); +} diff --git a/windows/utils/defaults.c b/windows/utils/defaults.c index 926b9769..ccb52641 100644 --- a/windows/utils/defaults.c +++ b/windows/utils/defaults.c @@ -11,7 +11,7 @@ FontSpec *platform_default_fontspec(const char *name) if (!strcmp(name, "Font")) return fontspec_new("新宋体", false, 12, GB2312_CHARSET); else - return fontspec_new("", false, 0, 0); + return fontspec_new_default(); } Filename *platform_default_filename(const char *name) diff --git a/windows/utils/filename.c b/windows/utils/filename.c index f4457ecb..8f4c53af 100644 --- a/windows/utils/filename.c +++ b/windows/utils/filename.c @@ -2,48 +2,79 @@ * Implementation of Filename for Windows. */ +#include + #include "putty.h" Filename *filename_from_str(const char *str) { - Filename *ret = snew(Filename); - ret->path = dupstr(str); - return ret; + Filename *fn = snew(Filename); + fn->cpath = dupstr(str); + fn->wpath = dup_mb_to_wc(DEFAULT_CODEPAGE, fn->cpath); + fn->utf8path = encode_wide_string_as_utf8(fn->wpath); + return fn; +} + +Filename *filename_from_wstr(const wchar_t *str) +{ + Filename *fn = snew(Filename); + fn->wpath = dupwcs(str); + fn->cpath = dup_wc_to_mb(DEFAULT_CODEPAGE, fn->wpath, "?"); + fn->utf8path = encode_wide_string_as_utf8(fn->wpath); + return fn; +} + +Filename *filename_from_utf8(const char *ustr) +{ + Filename *fn = snew(Filename); + fn->utf8path = dupstr(ustr); + fn->wpath = decode_utf8_to_wide_string(fn->utf8path); + fn->cpath = dup_wc_to_mb(DEFAULT_CODEPAGE, fn->wpath, "?"); + return fn; } Filename *filename_copy(const Filename *fn) { - return filename_from_str(fn->path); + Filename *newfn = snew(Filename); + newfn->cpath = dupstr(fn->cpath); + newfn->wpath = dupwcs(fn->wpath); + newfn->utf8path = dupstr(fn->utf8path); + return newfn; } const char *filename_to_str(const Filename *fn) { - return fn->path; + return fn->cpath; /* FIXME */ } bool filename_equal(const Filename *f1, const Filename *f2) { - return !strcmp(f1->path, f2->path); + /* wpath is primary: two filenames refer to the same file if they + * have the same wpath */ + return !wcscmp(f1->wpath, f2->wpath); } bool filename_is_null(const Filename *fn) { - return !*fn->path; + return !*fn->wpath; } void filename_free(Filename *fn) { - sfree(fn->path); + sfree(fn->wpath); + sfree(fn->cpath); + sfree(fn->utf8path); sfree(fn); } void filename_serialise(BinarySink *bs, const Filename *f) { - put_asciz(bs, f->path); + put_asciz(bs, f->utf8path); } Filename *filename_deserialise(BinarySource *src) { - return filename_from_str(get_asciz(src)); + const char *utf8 = get_asciz(src); + return filename_from_utf8(utf8); } char filename_char_sanitise(char c) @@ -52,3 +83,20 @@ char filename_char_sanitise(char c) return '.'; return c; } + +FILE *f_open(const Filename *fn, const char *mode, bool isprivate) +{ +#ifdef LEGACY_WINDOWS + /* Fallback for legacy pre-NT windows, where as far as I can see + * _wfopen just doesn't work at all */ + init_winver(); + if (osPlatformId == VER_PLATFORM_WIN32_WINDOWS || + osPlatformId == VER_PLATFORM_WIN32s) + return fopen(fn->cpath, mode); +#endif + + wchar_t *wmode = dup_mb_to_wc(DEFAULT_CODEPAGE, mode); + FILE *fp = _wfopen(fn->wpath, wmode); + sfree(wmode); + return fp; +} diff --git a/windows/utils/fontspec.c b/windows/utils/fontspec.c index 7e8d5175..7bdd3423 100644 --- a/windows/utils/fontspec.c +++ b/windows/utils/fontspec.c @@ -14,6 +14,11 @@ FontSpec *fontspec_new(const char *name, bool bold, int height, int charset) return f; } +FontSpec *fontspec_new_default(void) +{ + return fontspec_new("", false, 0, 0); +} + FontSpec *fontspec_copy(const FontSpec *f) { return fontspec_new(f->name, f->isbold, f->height, f->charset); diff --git a/windows/utils/getdlgitemtext_alloc.c b/windows/utils/getdlgitemtext_alloc.c index f0244d71..8db32901 100644 --- a/windows/utils/getdlgitemtext_alloc.c +++ b/windows/utils/getdlgitemtext_alloc.c @@ -1,7 +1,7 @@ /* - * Handy wrapper around GetDlgItemText which doesn't make you invent - * an arbitrary length limit on the output string. Returned string is - * dynamically allocated; caller must free. + * Handy wrappers around GetDlgItemText (A and W) which don't make you + * invent an arbitrary length limit on the output string. Returned + * string is dynamically allocated; caller must free. */ #include "putty.h" @@ -18,3 +18,16 @@ char *GetDlgItemText_alloc(HWND hwnd, int id) return ret; } + +wchar_t *GetDlgItemTextW_alloc(HWND hwnd, int id) +{ + wchar_t *ret = NULL; + size_t size = 0; + + do { + sgrowarray_nm(ret, size, size); + GetDlgItemTextW(hwnd, id, ret, size); + } while (!memchr(ret, '\0', size-1)); + + return ret; +} diff --git a/windows/utils/gui-timing.c b/windows/utils/gui-timing.c new file mode 100644 index 00000000..b0beec9c --- /dev/null +++ b/windows/utils/gui-timing.c @@ -0,0 +1,56 @@ +#include "putty.h" + +#define TIMING_CLASS_NAME "PuTTYTimerWindow" +#define TIMING_TIMER_ID 1234 +static long timing_next_time; +static HWND timing_hwnd; + +static LRESULT CALLBACK TimingWndProc(HWND hwnd, UINT message, + WPARAM wParam, LPARAM lParam) +{ + switch (message) { + case WM_TIMER: + if ((UINT_PTR)wParam == TIMING_TIMER_ID) { + unsigned long next; + + KillTimer(hwnd, TIMING_TIMER_ID); + if (run_timers(timing_next_time, &next)) { + timer_change_notify(next); + } else { + } + } + return 0; + } + return DefWindowProc(hwnd, message, wParam, lParam); +} + +void setup_gui_timing(void) +{ + WNDCLASS wndclass; + + memset(&wndclass, 0, sizeof(wndclass)); + wndclass.lpfnWndProc = TimingWndProc; + wndclass.hInstance = hinst; + wndclass.lpszClassName = TIMING_CLASS_NAME; + + RegisterClass(&wndclass); + + timing_hwnd = CreateWindow( + TIMING_CLASS_NAME, "PuTTY: hidden timing window", + WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, + 100, 100, NULL, NULL, hinst, NULL); + ShowWindow(timing_hwnd, SW_HIDE); +} + +void timer_change_notify(unsigned long next) +{ + unsigned long now = GETTICKCOUNT(); + long ticks; + if (now - next < INT_MAX) + ticks = 0; + else + ticks = next - now; + KillTimer(timing_hwnd, TIMING_TIMER_ID); + SetTimer(timing_hwnd, TIMING_TIMER_ID, ticks, NULL); + timing_next_time = next; +} diff --git a/windows/utils/message_box.c b/windows/utils/message_box.c index ae78de4a..37e22b0a 100644 --- a/windows/utils/message_box.c +++ b/windows/utils/message_box.c @@ -1,5 +1,9 @@ /* - * Message box with optional context help. + * Enhanced version of the MessageBox API function. Permits enabling a + * Help button by setting helpctxid to a context id in the help file + * relevant to this dialog box. Also permits setting the 'utf8' flag + * to indicate that the char strings given as 'text' and 'caption' are + * encoded in UTF-8 rather than the system code page. */ #include "putty.h" @@ -25,10 +29,10 @@ static VOID CALLBACK message_box_help_callback(LPHELPINFO lpHelpInfo) launch_help(message_box_owner, context); } -int message_box(HWND owner, LPCTSTR text, LPCTSTR caption, - DWORD style, DWORD helpctxid) +int message_box(HWND owner, LPCTSTR text, LPCTSTR caption, DWORD style, + bool utf8, DWORD helpctxid) { - MSGBOXPARAMS mbox; + MSGBOXPARAMSW mbox; /* * We use MessageBoxIndirect() because it allows us to specify a @@ -37,13 +41,31 @@ int message_box(HWND owner, LPCTSTR text, LPCTSTR caption, mbox.cbSize = sizeof(mbox); /* Assumes the globals `hinst' and `hwnd' have sensible values. */ mbox.hInstance = hinst; - mbox.hwndOwner = message_box_owner = owner; - mbox.lpfnMsgBoxCallback = &message_box_help_callback; mbox.dwLanguageId = LANG_NEUTRAL; - mbox.lpszText = text; - mbox.lpszCaption = caption; - mbox.dwContextHelpId = helpctxid; + + mbox.hwndOwner = message_box_owner = owner; + + wchar_t *wtext, *wcaption; + if (utf8) { + wtext = decode_utf8_to_wide_string(text); + wcaption = decode_utf8_to_wide_string(caption); + } else { + wtext = dup_mb_to_wc(DEFAULT_CODEPAGE, text); + wcaption = dup_mb_to_wc(DEFAULT_CODEPAGE, caption); + } + mbox.lpszText = wtext; + mbox.lpszCaption = wcaption; + mbox.dwStyle = style; + + mbox.dwContextHelpId = helpctxid; if (helpctxid != 0 && has_help()) mbox.dwStyle |= MB_HELP; - return MessageBoxIndirect(&mbox); + mbox.lpfnMsgBoxCallback = &message_box_help_callback; + + int toret = MessageBoxIndirectW(&mbox); + + sfree(wtext); + sfree(wcaption); + + return toret; } diff --git a/windows/utils/open_for_write_would_lose_data.c b/windows/utils/open_for_write_would_lose_data.c index 2aef5c5a..0645d7ac 100644 --- a/windows/utils/open_for_write_would_lose_data.c +++ b/windows/utils/open_for_write_would_lose_data.c @@ -39,17 +39,17 @@ static inline bool open_for_write_would_lose_data_impl( bool open_for_write_would_lose_data(const Filename *fn) { static HMODULE kernel32_module; - DECL_WINDOWS_FUNCTION(static, BOOL, GetFileAttributesExA, - (LPCSTR, GET_FILEEX_INFO_LEVELS, LPVOID)); + DECL_WINDOWS_FUNCTION(static, BOOL, GetFileAttributesExW, + (LPCWSTR, GET_FILEEX_INFO_LEVELS, LPVOID)); if (!kernel32_module) { kernel32_module = load_system32_dll("kernel32.dll"); - GET_WINDOWS_FUNCTION(kernel32_module, GetFileAttributesExA); + GET_WINDOWS_FUNCTION(kernel32_module, GetFileAttributesExW); } - if (p_GetFileAttributesExA) { + if (p_GetFileAttributesExW) { WIN32_FILE_ATTRIBUTE_DATA attrs; - if (!p_GetFileAttributesExA(fn->path, GetFileExInfoStandard, &attrs)) { + if (!p_GetFileAttributesExW(fn->wpath, GetFileExInfoStandard, &attrs)) { /* * Generally, if we don't identify a specific reason why we * should return true from this function, we return false, and @@ -61,8 +61,8 @@ bool open_for_write_would_lose_data(const Filename *fn) return open_for_write_would_lose_data_impl( attrs.dwFileAttributes, attrs.nFileSizeHigh, attrs.nFileSizeLow); } else { - WIN32_FIND_DATA fd; - HANDLE h = FindFirstFile(fn->path, &fd); + WIN32_FIND_DATAW fd; + HANDLE h = FindFirstFileW(fn->wpath, &fd); if (h == INVALID_HANDLE_VALUE) { /* * As above, if we can't find the file at all, return false. diff --git a/windows/utils/pgp_fingerprints_msgbox.c b/windows/utils/pgp_fingerprints_msgbox.c index 2de2ca0c..796bb112 100644 --- a/windows/utils/pgp_fingerprints_msgbox.c +++ b/windows/utils/pgp_fingerprints_msgbox.c @@ -21,5 +21,5 @@ void pgp_fingerprints_msgbox(HWND owner) ", " PGP_PREV_MASTER_KEY_DETAILS "):\n" " " PGP_PREV_MASTER_KEY_FP, "PGP 指纹", MB_ICONINFORMATION | MB_OK, - HELPCTXID(pgp_fingerprints)); + false, HELPCTXID(pgp_fingerprints)); } diff --git a/windows/utils/request_file.c b/windows/utils/request_file.c index dd2cab18..57363cad 100644 --- a/windows/utils/request_file.c +++ b/windows/utils/request_file.c @@ -9,6 +9,7 @@ struct filereq_tag { TCHAR cwd[MAX_PATH]; + WCHAR wcwd[MAX_PATH]; }; /* @@ -58,13 +59,55 @@ bool request_file(filereq *state, OPENFILENAME *of, bool preserve, bool save) return ret; } -filereq *filereq_new(void) +/* + * Here's the same one again, the wide-string version + */ +bool request_file_w(filereq *state, OPENFILENAMEW *of, + bool preserve, bool save) { - filereq *ret = snew(filereq); - ret->cwd[0] = '\0'; + WCHAR cwd[MAX_PATH]; /* process CWD */ + bool ret; + + /* Get process CWD */ + if (preserve) { + DWORD r = GetCurrentDirectoryW(lenof(cwd), cwd); + if (r == 0 || r >= lenof(cwd)) + /* Didn't work, oh well. Stop trying to be clever. */ + preserve = false; + } + + /* Open the file requester, maybe setting lpstrInitialDir */ + { + of->lStructSize = sizeof(*of); + of->lpstrInitialDir = (state && state->wcwd[0]) ? state->wcwd : NULL; + /* Actually put up the requester. */ + ret = save ? GetSaveFileNameW(of) : GetOpenFileNameW(of); + } + + /* Get CWD left by requester */ + if (state) { + DWORD r = GetCurrentDirectoryW(lenof(state->wcwd), state->wcwd); + if (r == 0 || r >= lenof(state->wcwd)) + /* Didn't work, oh well. */ + state->wcwd[0] = L'\0'; + } + + /* Restore process CWD */ + if (preserve) + /* If it fails, there's not much we can do. */ + (void) SetCurrentDirectoryW(cwd); + return ret; } +filereq *filereq_new(void) +{ + filereq *state = snew(filereq); + state->cwd[0] = '\0'; + state->wcwd[0] = L'\0'; + return state; +} + void filereq_free(filereq *state) { sfree(state); diff --git a/windows/utils/screenshot.c b/windows/utils/screenshot.c index 777520fd..6b53cd70 100644 --- a/windows/utils/screenshot.c +++ b/windows/utils/screenshot.c @@ -4,7 +4,7 @@ #include -char *save_screenshot(HWND hwnd, const char *outfile) +char *save_screenshot(HWND hwnd, Filename *outfile) { HDC dcWindow = NULL, dcSave = NULL; HBITMAP bmSave = NULL; @@ -89,9 +89,9 @@ char *save_screenshot(HWND hwnd, const char *outfile) err = dupprintf("GetDIBits (get data): %s", win_strerror(GetLastError())); - FILE *fp = fopen(outfile, "wb"); + FILE *fp = f_open(outfile, "wb", false); if (!fp) { - err = dupprintf("'%s': unable to open file", outfile); + err = dupprintf("'%s': unable to open file", filename_to_str(outfile)); goto out; } @@ -118,7 +118,7 @@ char *save_screenshot(HWND hwnd, const char *outfile) #else /* HAVE_DWMAPI_H */ /* Without we can't get the right window rectangle */ -char *save_screenshot(HWND hwnd, const char *outfile) +char *save_screenshot(HWND hwnd, Filename *outfile) { return dupstr("Demo screenshots not compiled in to this build"); } diff --git a/windows/utils/split_into_argv.c b/windows/utils/split_into_argv.c index e65ae0a2..3279e4ac 100644 --- a/windows/utils/split_into_argv.c +++ b/windows/utils/split_into_argv.c @@ -161,17 +161,27 @@ #define MOD3 0 #endif -static inline bool is_word_sep(char c) +#ifdef SPLIT_INTO_ARGV_W +#define FUNCTION split_into_argv_w +#define CHAR wchar_t +#define STRLEN wcslen +#else +#define FUNCTION split_into_argv +#define CHAR char +#define STRLEN strlen +#endif + +static inline bool is_word_sep(CHAR c) { return c == ' ' || c == '\t'; } -void split_into_argv(char *cmdline, int *argc, char ***argv, - char ***argstart) +void FUNCTION(const CHAR *cmdline, bool includes_program_name, + int *argc, CHAR ***argv, CHAR ***argstart) { - char *p; - char *outputline, *q; - char **outputargv, **outputargstart; + const CHAR *p; + CHAR *outputline, *q; + CHAR **outputargv, **outputargstart; int outputargc; /* @@ -190,9 +200,12 @@ void split_into_argv(char *cmdline, int *argc, char ***argv, * This will guaranteeably be big enough; we can realloc it * down later. */ - outputline = snewn(1+strlen(cmdline), char); - outputargv = snewn(strlen(cmdline)+1 / 2, char *); - outputargstart = snewn(strlen(cmdline)+1 / 2, char *); + { + size_t len = STRLEN(cmdline); + outputline = snewn(1+len, CHAR); + outputargv = snewn((len+1) / 2, CHAR *); + outputargstart = snewn((len+1) / 2, CHAR *); + } p = cmdline; q = outputline; outputargc = 0; @@ -203,9 +216,43 @@ void split_into_argv(char *cmdline, int *argc, char ***argv, while (*p && is_word_sep(*p)) p++; if (!*p) break; + /* + * Check if this argument is the program name. If so, + * different rules apply. + * + * In most arguments, the special characters are the double + * quote and the backslash. An exception is the program name + * at the start of the command line, in which backslashes are + * _not_ special - if one appears before a quote, it does not + * make the quote literal. + * + * The C library must implement this special rule, and we must + * follow suit here, in order to match the way CreateProcess + * scans the command line to determine the program name. It + * will consider that all these commands refer to the same + * file equally validly: + * + * "C:\Program Files\Foo"\bar.exe + * "C:\Program Files\Foo\"bar.exe + * "C:\Program Files\Foo\bar.exe" + * + * Each one contains a quoted section that protects the space + * in "Program Files", and the closing quote takes effect the + * same in all cases - even though, in the middle case, it's + * immediately preceded by one of the path separators in the + * name. For CreateProcess, backslashes aren't special. + * + * So, if our caller told us that the input command line + * includes the program name (which it does if it came from + * GetCommandLine, but not if it was passed in to WinMain), + * then we must treat the 0th output argument specially, by + * not considering backslashes to affect the quoting. + */ + bool backslash_special = !(outputargc == 0 && includes_program_name); + /* We have an argument; start it. */ outputargv[outputargc] = q; - outputargstart[outputargc] = p; + outputargstart[outputargc] = (CHAR *)p; outputargc++; quote = false; @@ -214,7 +261,7 @@ void split_into_argv(char *cmdline, int *argc, char ***argv, if (!quote && is_word_sep(*p)) break; /* argument is finished */ - if (*p == '"' || *p == '\\') { + if (*p == '"' || (*p == '\\' && backslash_special)) { /* * We have a sequence of zero or more backslashes * followed by a sequence of zero or more quotes. @@ -265,448 +312,10 @@ void split_into_argv(char *cmdline, int *argc, char ***argv, *q++ = '\0'; } - outputargv = sresize(outputargv, outputargc, char *); - outputargstart = sresize(outputargstart, outputargc, char *); + outputargv = sresize(outputargv, outputargc, CHAR *); + outputargstart = sresize(outputargstart, outputargc, CHAR *); if (argc) *argc = outputargc; if (argv) *argv = outputargv; else sfree(outputargv); if (argstart) *argstart = outputargstart; else sfree(outputargstart); } - -#ifdef TEST - -const struct argv_test { - const char *cmdline; - const char *argv[10]; -} argv_tests[] = { - /* - * We generate this set of tests by invoking ourself with - * `-generate'. - */ -#if !MOD3 - /* Newer behaviour, with no weird mod-3 glitch. */ - {"ab c\" d", {"ab", "c d", NULL}}, - {"a\"b c\" d", {"ab c", "d", NULL}}, - {"a\"\"b c\" d", {"ab", "c d", NULL}}, - {"a\"\"\"b c\" d", {"a\"b c", "d", NULL}}, - {"a\"\"\"\"b c\" d", {"a\"b", "c d", NULL}}, - {"a\"\"\"\"\"b c\" d", {"a\"\"b c", "d", NULL}}, - {"a\"\"\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}}, - {"a\"\"\"\"\"\"\"b c\" d", {"a\"\"\"b c", "d", NULL}}, - {"a\"\"\"\"\"\"\"\"b c\" d", {"a\"\"\"b", "c d", NULL}}, - {"a\\b c\" d", {"a\\b", "c d", NULL}}, - {"a\\\"b c\" d", {"a\"b", "c d", NULL}}, - {"a\\\"\"b c\" d", {"a\"b c", "d", NULL}}, - {"a\\\"\"\"b c\" d", {"a\"b", "c d", NULL}}, - {"a\\\"\"\"\"b c\" d", {"a\"\"b c", "d", NULL}}, - {"a\\\"\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}}, - {"a\\\"\"\"\"\"\"b c\" d", {"a\"\"\"b c", "d", NULL}}, - {"a\\\"\"\"\"\"\"\"b c\" d", {"a\"\"\"b", "c d", NULL}}, - {"a\\\"\"\"\"\"\"\"\"b c\" d", {"a\"\"\"\"b c", "d", NULL}}, - {"a\\\\b c\" d", {"a\\\\b", "c d", NULL}}, - {"a\\\\\"b c\" d", {"a\\b c", "d", NULL}}, - {"a\\\\\"\"b c\" d", {"a\\b", "c d", NULL}}, - {"a\\\\\"\"\"b c\" d", {"a\\\"b c", "d", NULL}}, - {"a\\\\\"\"\"\"b c\" d", {"a\\\"b", "c d", NULL}}, - {"a\\\\\"\"\"\"\"b c\" d", {"a\\\"\"b c", "d", NULL}}, - {"a\\\\\"\"\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}}, - {"a\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b c", "d", NULL}}, - {"a\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b", "c d", NULL}}, - {"a\\\\\\b c\" d", {"a\\\\\\b", "c d", NULL}}, - {"a\\\\\\\"b c\" d", {"a\\\"b", "c d", NULL}}, - {"a\\\\\\\"\"b c\" d", {"a\\\"b c", "d", NULL}}, - {"a\\\\\\\"\"\"b c\" d", {"a\\\"b", "c d", NULL}}, - {"a\\\\\\\"\"\"\"b c\" d", {"a\\\"\"b c", "d", NULL}}, - {"a\\\\\\\"\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}}, - {"a\\\\\\\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b c", "d", NULL}}, - {"a\\\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b", "c d", NULL}}, - {"a\\\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"\"b c", "d", NULL}}, - {"a\\\\\\\\b c\" d", {"a\\\\\\\\b", "c d", NULL}}, - {"a\\\\\\\\\"b c\" d", {"a\\\\b c", "d", NULL}}, - {"a\\\\\\\\\"\"b c\" d", {"a\\\\b", "c d", NULL}}, - {"a\\\\\\\\\"\"\"b c\" d", {"a\\\\\"b c", "d", NULL}}, - {"a\\\\\\\\\"\"\"\"b c\" d", {"a\\\\\"b", "c d", NULL}}, - {"a\\\\\\\\\"\"\"\"\"b c\" d", {"a\\\\\"\"b c", "d", NULL}}, - {"a\\\\\\\\\"\"\"\"\"\"b c\" d", {"a\\\\\"\"b", "c d", NULL}}, - {"a\\\\\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\\\"\"\"b c", "d", NULL}}, - {"a\\\\\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\\\"\"\"b", "c d", NULL}}, - {"\"ab c\" d", {"ab c", "d", NULL}}, - {"\"a\"b c\" d", {"ab", "c d", NULL}}, - {"\"a\"\"b c\" d", {"a\"b c", "d", NULL}}, - {"\"a\"\"\"b c\" d", {"a\"b", "c d", NULL}}, - {"\"a\"\"\"\"b c\" d", {"a\"\"b c", "d", NULL}}, - {"\"a\"\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}}, - {"\"a\"\"\"\"\"\"b c\" d", {"a\"\"\"b c", "d", NULL}}, - {"\"a\"\"\"\"\"\"\"b c\" d", {"a\"\"\"b", "c d", NULL}}, - {"\"a\"\"\"\"\"\"\"\"b c\" d", {"a\"\"\"\"b c", "d", NULL}}, - {"\"a\\b c\" d", {"a\\b c", "d", NULL}}, - {"\"a\\\"b c\" d", {"a\"b c", "d", NULL}}, - {"\"a\\\"\"b c\" d", {"a\"b", "c d", NULL}}, - {"\"a\\\"\"\"b c\" d", {"a\"\"b c", "d", NULL}}, - {"\"a\\\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}}, - {"\"a\\\"\"\"\"\"b c\" d", {"a\"\"\"b c", "d", NULL}}, - {"\"a\\\"\"\"\"\"\"b c\" d", {"a\"\"\"b", "c d", NULL}}, - {"\"a\\\"\"\"\"\"\"\"b c\" d", {"a\"\"\"\"b c", "d", NULL}}, - {"\"a\\\"\"\"\"\"\"\"\"b c\" d", {"a\"\"\"\"b", "c d", NULL}}, - {"\"a\\\\b c\" d", {"a\\\\b c", "d", NULL}}, - {"\"a\\\\\"b c\" d", {"a\\b", "c d", NULL}}, - {"\"a\\\\\"\"b c\" d", {"a\\\"b c", "d", NULL}}, - {"\"a\\\\\"\"\"b c\" d", {"a\\\"b", "c d", NULL}}, - {"\"a\\\\\"\"\"\"b c\" d", {"a\\\"\"b c", "d", NULL}}, - {"\"a\\\\\"\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}}, - {"\"a\\\\\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b c", "d", NULL}}, - {"\"a\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b", "c d", NULL}}, - {"\"a\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"\"b c", "d", NULL}}, - {"\"a\\\\\\b c\" d", {"a\\\\\\b c", "d", NULL}}, - {"\"a\\\\\\\"b c\" d", {"a\\\"b c", "d", NULL}}, - {"\"a\\\\\\\"\"b c\" d", {"a\\\"b", "c d", NULL}}, - {"\"a\\\\\\\"\"\"b c\" d", {"a\\\"\"b c", "d", NULL}}, - {"\"a\\\\\\\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}}, - {"\"a\\\\\\\"\"\"\"\"b c\" d", {"a\\\"\"\"b c", "d", NULL}}, - {"\"a\\\\\\\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b", "c d", NULL}}, - {"\"a\\\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"\"b c", "d", NULL}}, - {"\"a\\\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"\"b", "c d", NULL}}, - {"\"a\\\\\\\\b c\" d", {"a\\\\\\\\b c", "d", NULL}}, - {"\"a\\\\\\\\\"b c\" d", {"a\\\\b", "c d", NULL}}, - {"\"a\\\\\\\\\"\"b c\" d", {"a\\\\\"b c", "d", NULL}}, - {"\"a\\\\\\\\\"\"\"b c\" d", {"a\\\\\"b", "c d", NULL}}, - {"\"a\\\\\\\\\"\"\"\"b c\" d", {"a\\\\\"\"b c", "d", NULL}}, - {"\"a\\\\\\\\\"\"\"\"\"b c\" d", {"a\\\\\"\"b", "c d", NULL}}, - {"\"a\\\\\\\\\"\"\"\"\"\"b c\" d", {"a\\\\\"\"\"b c", "d", NULL}}, - {"\"a\\\\\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\\\"\"\"b", "c d", NULL}}, - {"\"a\\\\\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\\\"\"\"\"b c", "d", NULL}}, -#else /* MOD3 */ - /* VS7 mod-3 behaviour. */ - {"ab c\" d", {"ab", "c d", NULL}}, - {"a\"b c\" d", {"ab c", "d", NULL}}, - {"a\"\"b c\" d", {"ab", "c d", NULL}}, - {"a\"\"\"b c\" d", {"a\"b", "c d", NULL}}, - {"a\"\"\"\"b c\" d", {"a\"b c", "d", NULL}}, - {"a\"\"\"\"\"b c\" d", {"a\"b", "c d", NULL}}, - {"a\"\"\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}}, - {"a\"\"\"\"\"\"\"b c\" d", {"a\"\"b c", "d", NULL}}, - {"a\"\"\"\"\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}}, - {"a\\b c\" d", {"a\\b", "c d", NULL}}, - {"a\\\"b c\" d", {"a\"b", "c d", NULL}}, - {"a\\\"\"b c\" d", {"a\"b c", "d", NULL}}, - {"a\\\"\"\"b c\" d", {"a\"b", "c d", NULL}}, - {"a\\\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}}, - {"a\\\"\"\"\"\"b c\" d", {"a\"\"b c", "d", NULL}}, - {"a\\\"\"\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}}, - {"a\\\"\"\"\"\"\"\"b c\" d", {"a\"\"\"b", "c d", NULL}}, - {"a\\\"\"\"\"\"\"\"\"b c\" d", {"a\"\"\"b c", "d", NULL}}, - {"a\\\\b c\" d", {"a\\\\b", "c d", NULL}}, - {"a\\\\\"b c\" d", {"a\\b c", "d", NULL}}, - {"a\\\\\"\"b c\" d", {"a\\b", "c d", NULL}}, - {"a\\\\\"\"\"b c\" d", {"a\\\"b", "c d", NULL}}, - {"a\\\\\"\"\"\"b c\" d", {"a\\\"b c", "d", NULL}}, - {"a\\\\\"\"\"\"\"b c\" d", {"a\\\"b", "c d", NULL}}, - {"a\\\\\"\"\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}}, - {"a\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\"\"b c", "d", NULL}}, - {"a\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}}, - {"a\\\\\\b c\" d", {"a\\\\\\b", "c d", NULL}}, - {"a\\\\\\\"b c\" d", {"a\\\"b", "c d", NULL}}, - {"a\\\\\\\"\"b c\" d", {"a\\\"b c", "d", NULL}}, - {"a\\\\\\\"\"\"b c\" d", {"a\\\"b", "c d", NULL}}, - {"a\\\\\\\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}}, - {"a\\\\\\\"\"\"\"\"b c\" d", {"a\\\"\"b c", "d", NULL}}, - {"a\\\\\\\"\"\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}}, - {"a\\\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b", "c d", NULL}}, - {"a\\\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b c", "d", NULL}}, - {"a\\\\\\\\b c\" d", {"a\\\\\\\\b", "c d", NULL}}, - {"a\\\\\\\\\"b c\" d", {"a\\\\b c", "d", NULL}}, - {"a\\\\\\\\\"\"b c\" d", {"a\\\\b", "c d", NULL}}, - {"a\\\\\\\\\"\"\"b c\" d", {"a\\\\\"b", "c d", NULL}}, - {"a\\\\\\\\\"\"\"\"b c\" d", {"a\\\\\"b c", "d", NULL}}, - {"a\\\\\\\\\"\"\"\"\"b c\" d", {"a\\\\\"b", "c d", NULL}}, - {"a\\\\\\\\\"\"\"\"\"\"b c\" d", {"a\\\\\"\"b", "c d", NULL}}, - {"a\\\\\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\\\"\"b c", "d", NULL}}, - {"a\\\\\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\\\"\"b", "c d", NULL}}, - {"\"ab c\" d", {"ab c", "d", NULL}}, - {"\"a\"b c\" d", {"ab", "c d", NULL}}, - {"\"a\"\"b c\" d", {"a\"b", "c d", NULL}}, - {"\"a\"\"\"b c\" d", {"a\"b c", "d", NULL}}, - {"\"a\"\"\"\"b c\" d", {"a\"b", "c d", NULL}}, - {"\"a\"\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}}, - {"\"a\"\"\"\"\"\"b c\" d", {"a\"\"b c", "d", NULL}}, - {"\"a\"\"\"\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}}, - {"\"a\"\"\"\"\"\"\"\"b c\" d", {"a\"\"\"b", "c d", NULL}}, - {"\"a\\b c\" d", {"a\\b c", "d", NULL}}, - {"\"a\\\"b c\" d", {"a\"b c", "d", NULL}}, - {"\"a\\\"\"b c\" d", {"a\"b", "c d", NULL}}, - {"\"a\\\"\"\"b c\" d", {"a\"\"b", "c d", NULL}}, - {"\"a\\\"\"\"\"b c\" d", {"a\"\"b c", "d", NULL}}, - {"\"a\\\"\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}}, - {"\"a\\\"\"\"\"\"\"b c\" d", {"a\"\"\"b", "c d", NULL}}, - {"\"a\\\"\"\"\"\"\"\"b c\" d", {"a\"\"\"b c", "d", NULL}}, - {"\"a\\\"\"\"\"\"\"\"\"b c\" d", {"a\"\"\"b", "c d", NULL}}, - {"\"a\\\\b c\" d", {"a\\\\b c", "d", NULL}}, - {"\"a\\\\\"b c\" d", {"a\\b", "c d", NULL}}, - {"\"a\\\\\"\"b c\" d", {"a\\\"b", "c d", NULL}}, - {"\"a\\\\\"\"\"b c\" d", {"a\\\"b c", "d", NULL}}, - {"\"a\\\\\"\"\"\"b c\" d", {"a\\\"b", "c d", NULL}}, - {"\"a\\\\\"\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}}, - {"\"a\\\\\"\"\"\"\"\"b c\" d", {"a\\\"\"b c", "d", NULL}}, - {"\"a\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}}, - {"\"a\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b", "c d", NULL}}, - {"\"a\\\\\\b c\" d", {"a\\\\\\b c", "d", NULL}}, - {"\"a\\\\\\\"b c\" d", {"a\\\"b c", "d", NULL}}, - {"\"a\\\\\\\"\"b c\" d", {"a\\\"b", "c d", NULL}}, - {"\"a\\\\\\\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}}, - {"\"a\\\\\\\"\"\"\"b c\" d", {"a\\\"\"b c", "d", NULL}}, - {"\"a\\\\\\\"\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}}, - {"\"a\\\\\\\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b", "c d", NULL}}, - {"\"a\\\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b c", "d", NULL}}, - {"\"a\\\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b", "c d", NULL}}, - {"\"a\\\\\\\\b c\" d", {"a\\\\\\\\b c", "d", NULL}}, - {"\"a\\\\\\\\\"b c\" d", {"a\\\\b", "c d", NULL}}, - {"\"a\\\\\\\\\"\"b c\" d", {"a\\\\\"b", "c d", NULL}}, - {"\"a\\\\\\\\\"\"\"b c\" d", {"a\\\\\"b c", "d", NULL}}, - {"\"a\\\\\\\\\"\"\"\"b c\" d", {"a\\\\\"b", "c d", NULL}}, - {"\"a\\\\\\\\\"\"\"\"\"b c\" d", {"a\\\\\"\"b", "c d", NULL}}, - {"\"a\\\\\\\\\"\"\"\"\"\"b c\" d", {"a\\\\\"\"b c", "d", NULL}}, - {"\"a\\\\\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\\\"\"b", "c d", NULL}}, - {"\"a\\\\\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\\\"\"\"b", "c d", NULL}}, -#endif /* MOD3 */ -}; - -void out_of_memory(void) -{ - fprintf(stderr, "out of memory!\n"); - exit(2); -} - -int main(int argc, char **argv) -{ - int i, j; - - if (argc > 1) { - /* - * Generation of tests. - * - * Given `-splat ', we print out a C-style - * representation of each argument (in the form "a", "b", - * NULL), backslash-escaping each backslash and double - * quote. - * - * Given `-split ', we first doctor `string' by - * turning forward slashes into backslashes, single quotes - * into double quotes and underscores into spaces; and then - * we feed the resulting string to ourself with `-splat'. - * - * Given `-generate', we concoct a variety of fun test - * cases, encode them in quote-safe form (mapping \, " and - * space to /, ' and _ respectively) and feed each one to - * `-split'. - */ - if (!strcmp(argv[1], "-splat")) { - int i; - char *p; - for (i = 2; i < argc; i++) { - putchar('"'); - for (p = argv[i]; *p; p++) { - if (*p == '\\' || *p == '"') - putchar('\\'); - putchar(*p); - } - printf("\", "); - } - printf("NULL"); - return 0; - } - - if (!strcmp(argv[1], "-split") && argc > 2) { - strbuf *cmdline = strbuf_new(); - char *p; - - put_fmt(cmdline, "%s -splat ", argv[0]); - printf(" {\""); - size_t args_start = cmdline->len; - for (p = argv[2]; *p; p++) { - char c = (*p == '/' ? '\\' : - *p == '\'' ? '"' : - *p == '_' ? ' ' : - *p); - put_byte(cmdline, c); - } - write_c_string_literal(stdout, ptrlen_from_asciz( - cmdline->s + args_start)); - printf("\", {"); - fflush(stdout); - - system(cmdline->s); - - printf("}},\n"); - - strbuf_free(cmdline); - return 0; - } - - if (!strcmp(argv[1], "-generate")) { - char *teststr, *p; - int i, initialquote, backslashes, quotes; - - teststr = malloc(200 + strlen(argv[0])); - - for (initialquote = 0; initialquote <= 1; initialquote++) { - for (backslashes = 0; backslashes < 5; backslashes++) { - for (quotes = 0; quotes < 9; quotes++) { - p = teststr + sprintf(teststr, "%s -split ", argv[0]); - if (initialquote) *p++ = '\''; - *p++ = 'a'; - for (i = 0; i < backslashes; i++) *p++ = '/'; - for (i = 0; i < quotes; i++) *p++ = '\''; - *p++ = 'b'; - *p++ = '_'; - *p++ = 'c'; - *p++ = '\''; - *p++ = '_'; - *p++ = 'd'; - *p = '\0'; - - system(teststr); - } - } - } - return 0; - } - - if (!strcmp(argv[1], "-tabulate")) { - char table[] = "\ - * backslashes \n\ - * \n\ - * 0 1 2 3 4 \n\ - * \n\ - * 0 | \n\ - * --------+----------------------------- \n\ - * 1 | \n\ - * q 2 | \n\ - * u 3 | \n\ - * o 4 | \n\ - * t 5 | \n\ - * e 6 | \n\ - * s 7 | \n\ - * 8 | \n\ -"; - char *linestarts[14]; - char *p = table; - for (i = 0; i < lenof(linestarts); i++) { - linestarts[i] = p; - p += strcspn(p, "\n"); - if (*p) p++; - } - - for (i = 0; i < lenof(argv_tests); i++) { - const struct argv_test *test = &argv_tests[i]; - const char *q = test->cmdline; - - /* Skip tests that aren't telling us something about - * the behaviour _inside_ a quoted string */ - if (*q != '"') - continue; - - q++; - - assert(*q == 'a'); - q++; - int backslashes_in = 0, quotes_in = 0; - while (*q == '\\') { - q++; - backslashes_in++; - } - while (*q == '"') { - q++; - quotes_in++; - } - - q = test->argv[0]; - assert(*q == 'a'); - q++; - int backslashes_out = 0, quotes_out = 0; - while (*q == '\\') { - q++; - backslashes_out++; - } - while (*q == '"') { - q++; - quotes_out++; - } - assert(*q == 'b'); - q++; - bool in_quoted_string = (*q == ' '); - - int x = (backslashes_in == 0 ? 15 : 18 + 7 * backslashes_in); - int y = (quotes_in == 0 ? 4 : 5 + quotes_in); - char *buf = dupprintf("%d,%d,%c", - backslashes_out, quotes_out, - in_quoted_string ? 'y' : 'n'); - memcpy(linestarts[y] + x, buf, strlen(buf)); - sfree(buf); - } - - fputs(table, stdout); - return 0; - } - - fprintf(stderr, "unrecognised option: \"%s\"\n", argv[1]); - return 1; - } - - /* - * If we get here, we were invoked with no arguments, so just - * run the tests. - */ - int passes = 0, fails = 0; - - for (i = 0; i < lenof(argv_tests); i++) { - int ac; - char **av; - bool failed = false; - - split_into_argv((char *)argv_tests[i].cmdline, &ac, &av, NULL); - - for (j = 0; j < ac && argv_tests[i].argv[j]; j++) { - if (strcmp(av[j], argv_tests[i].argv[j])) { - printf("failed test %d (|%s|) arg %d: |%s| should be |%s|\n", - i, argv_tests[i].cmdline, - j, av[j], argv_tests[i].argv[j]); - failed = true; - } -#ifdef VERBOSE - else { - printf("test %d (|%s|) arg %d: |%s| == |%s|\n", - i, argv_tests[i].cmdline, - j, av[j], argv_tests[i].argv[j]); - } -#endif - } - if (j < ac) { - printf("failed test %d (|%s|): %d args returned, should be %d\n", - i, argv_tests[i].cmdline, ac, j); - failed = true; - } - if (argv_tests[i].argv[j]) { - printf("failed test %d (|%s|): %d args returned, should be more\n", - i, argv_tests[i].cmdline, ac); - failed = true; - } - - if (failed) - fails++; - else - passes++; - } - - printf("passed %d failed %d (%s mode)\n", - passes, fails, -#if MOD3 - "mod 3" -#else - "mod 2" -#endif - ); - - return fails != 0; -} - -#endif /* TEST */ diff --git a/windows/utils/split_into_argv_w.c b/windows/utils/split_into_argv_w.c new file mode 100644 index 00000000..a125e54d --- /dev/null +++ b/windows/utils/split_into_argv_w.c @@ -0,0 +1,6 @@ +/* + * Unicode version of split_into_argv. + */ + +#define SPLIT_INTO_ARGV_W +#include "split_into_argv.c" diff --git a/windows/win-gui-seat.h b/windows/win-gui-seat.h index 19c5cbea..36f25f2d 100644 --- a/windows/win-gui-seat.h +++ b/windows/win-gui-seat.h @@ -1,14 +1,165 @@ /* - * Small implementation of Seat and LogPolicy shared between window.c - * and dialog.c. + * Main state structure for an instance of the Windows PuTTY front + * end, containing all the PuTTY objects and all the Windows API + * resources like the window handle. */ typedef struct WinGuiSeat WinGuiSeat; +struct PopupMenu { + HMENU menu; +}; +enum { SYSMENU, CTXMENU }; /* indices into popup_menus field */ + +#define FONT_NORMAL 0 +#define FONT_BOLD 1 +#define FONT_UNDERLINE 2 +#define FONT_BOLDUND 3 +#define FONT_WIDE 0x04 +#define FONT_HIGH 0x08 +#define FONT_NARROW 0x10 + +#define FONT_OEM 0x20 +#define FONT_OEMBOLD 0x21 +#define FONT_OEMUND 0x22 +#define FONT_OEMBOLDUND 0x23 + +#define FONT_MAXNO 0x40 +#define FONT_SHIFT 5 + +enum BoldMode { + BOLD_NONE, BOLD_SHADOW, BOLD_FONT +}; +enum UnderlineMode { + UND_LINE, UND_FONT +}; + +struct _dpi_info { + POINT cur_dpi; + RECT new_wnd_rect; +}; + +/* + * Against the future possibility of having more than one of these + * in a process (and the current possibility of having zero), we + * keep a linked list of all live WinGuiSeats, so that cleanups + * can be done to any that exist. + */ +struct WinGuiSeatListNode { + struct WinGuiSeatListNode *next, *prev; +}; +extern struct WinGuiSeatListNode wgslisthead; /* static end pointer */ + struct WinGuiSeat { - HWND term_hwnd; + struct WinGuiSeatListNode wgslistnode; + Seat seat; + TermWin termwin; LogPolicy logpolicy; + + HWND term_hwnd; + + int extra_width, extra_height; + int font_width, font_height; + bool font_dualwidth, font_varpitch; + int offset_width, offset_height; + bool was_zoomed; + int prev_rows, prev_cols; // FIXME I don't think these are even used + + HBITMAP caretbm; + int caret_x, caret_y; + + int kbd_codepage; + + Ldisc *ldisc; + Backend *backend; + + cmdline_get_passwd_input_state cmdline_get_passwd_state; + + struct unicode_data ucsdata; + bool session_closed; + bool reconfiguring; + + const SessionSpecial *specials; + HMENU specials_menu; + int n_specials; + + struct PopupMenu popup_menus[2]; + HMENU savedsess_menu; + + Conf *conf; + LogContext *logctx; + Terminal *term; + + int cursor_type; + int vtmode; + + HFONT fonts[FONT_MAXNO]; + LOGFONT lfont; + bool fontflag[FONT_MAXNO]; + enum BoldMode bold_font_mode; + + bool bold_colours; + enum UnderlineMode und_mode; + int descent, font_strikethrough_y; + + COLORREF colours[OSC4_NCOLOURS]; + HPALETTE pal; + LPLOGPALETTE logpal; + bool tried_pal; + COLORREF colorref_modifier; + + struct _dpi_info dpi_info; + + int dbltime, lasttime, lastact; + Mouse_Button lastbtn; + + bool send_raw_mouse; + int wheel_accumulator; + + bool pointer_indicates_raw_mouse; + + BusyStatus busy_status; + + wchar_t *window_name, *icon_name; + + int alt_numberpad_accumulator; + int compose_state; + int compose_char; + WPARAM compose_keycode; + + HDC wintw_hdc; + + bool resizing; + bool need_backend_resize; + + long next_flash; + bool flashing; + + long last_beep_time; + + bool ignore_clip; + bool fullscr_on_max; + bool processed_resize; + bool in_scrollbar_loop; + UINT last_mousemove; + WPARAM last_wm_mousemove_wParam, last_wm_ncmousemove_wParam; + LPARAM last_wm_mousemove_lParam, last_wm_ncmousemove_lParam; + wchar_t pending_surrogate; }; extern const LogPolicyVtable win_gui_logpolicy_vt; /* in dialog.c */ + +static inline void wgs_link(WinGuiSeat *wgs) +{ + wgs->wgslistnode.prev = wgslisthead.prev; + wgs->wgslistnode.next = &wgslisthead; + wgs->wgslistnode.prev->next = &wgs->wgslistnode; + wgs->wgslistnode.next->prev = &wgs->wgslistnode; +} + +static inline void wgs_unlink(WinGuiSeat *wgs) +{ + wgs->wgslistnode.prev->next = wgs->wgslistnode.next; + wgs->wgslistnode.next->prev = wgs->wgslistnode.prev; +} diff --git a/windows/window.c b/windows/window.c index 4f058f73..6bf960a6 100644 --- a/windows/window.c +++ b/windows/window.c @@ -94,109 +94,39 @@ #define VK_PACKET 0xE7 #endif -static Mouse_Button translate_button(Mouse_Button button); -static void show_mouseptr(bool show); +static Mouse_Button translate_button(WinGuiSeat *wgs, Mouse_Button button); +static void show_mouseptr(WinGuiSeat *wgs, bool show); static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); -static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, - unsigned char *output); -static void init_palette(void); -static void init_fonts(int, int); -static void init_dpi_info(void); -static void another_font(int); -static void deinit_fonts(void); -static void set_input_locale(HKL); -static void update_savedsess_menu(void); +static int TranslateKey(WinGuiSeat *wgs, UINT message, WPARAM wParam, + LPARAM lParam, unsigned char *output); +static void init_palette(WinGuiSeat *wgs); +static void init_fonts(WinGuiSeat *wgs, int, int); +static void init_dpi_info(WinGuiSeat *wgs); +static void another_font(WinGuiSeat *wgs, int); +static void deinit_fonts(WinGuiSeat *wgs); +static void set_input_locale(WinGuiSeat *wgs, HKL); +static void update_savedsess_menu(WinGuiSeat *wgs); static void init_winfuncs(void); -static bool is_full_screen(void); -static void make_full_screen(void); -static void clear_full_screen(void); -static void flip_full_screen(void); -static void process_clipdata(HGLOBAL clipdata, bool unicode); +static bool is_full_screen(WinGuiSeat *wgs); +static void make_full_screen(WinGuiSeat *wgs); +static void clear_full_screen(WinGuiSeat *wgs); +static void flip_full_screen(WinGuiSeat *wgs); +static void process_clipdata(WinGuiSeat *wgs, HGLOBAL clipdata, bool unicode); static void setup_clipboards(Terminal *, Conf *); /* Window layout information */ -static void reset_window(int); -static int extra_width, extra_height; -static int font_width, font_height; -static bool font_dualwidth, font_varpitch; -static int offset_width, offset_height; -static bool was_zoomed = false; -static int prev_rows, prev_cols; +static void reset_window(WinGuiSeat *wgs, int reinit); -static void flash_window(int mode); -static void sys_cursor_update(void); -static bool get_fullscreen_rect(RECT *ss); +static void flash_window(WinGuiSeat *wgs, int mode); +static void sys_cursor_update(WinGuiSeat *wgs); +static bool get_fullscreen_rect(WinGuiSeat *wgs, RECT *ss); +static bool get_workingarea_rect(WinGuiSeat *wgs, RECT *ss); -static int caret_x = -1, caret_y = -1; - -static int kbd_codepage; - -static Ldisc *ldisc; -static Backend *backend; - -static cmdline_get_passwd_input_state cmdline_get_passwd_state; - -static struct unicode_data ucsdata; -static bool session_closed; -static bool reconfiguring = false; - -static const SessionSpecial *specials = NULL; -static HMENU specials_menu = NULL; -static int n_specials = 0; - -#define TIMING_TIMER_ID 1234 -static long timing_next_time; - -static struct { - HMENU menu; -} popup_menus[2]; -enum { SYSMENU, CTXMENU }; -static HMENU savedsess_menu; - -static Conf *conf; -static LogContext *logctx; -static Terminal *term; - -static void conf_cache_data(void); -static int cursor_type; -static int vtmode; +static void conf_cache_data(WinGuiSeat *wgs); static struct sesslist sesslist; /* for saved-session menu */ -#define FONT_NORMAL 0 -#define FONT_BOLD 1 -#define FONT_UNDERLINE 2 -#define FONT_BOLDUND 3 -#define FONT_WIDE 0x04 -#define FONT_HIGH 0x08 -#define FONT_NARROW 0x10 - -#define FONT_OEM 0x20 -#define FONT_OEMBOLD 0x21 -#define FONT_OEMUND 0x22 -#define FONT_OEMBOLDUND 0x23 - -#define FONT_MAXNO 0x40 -#define FONT_SHIFT 5 -static HFONT fonts[FONT_MAXNO]; -static LOGFONT lfont; -static bool fontflag[FONT_MAXNO]; -static enum { - BOLD_NONE, BOLD_SHADOW, BOLD_FONT -} bold_font_mode; -static bool bold_colours; -static enum { - UND_LINE, UND_FONT -} und_mode; -static int descent, font_strikethrough_y; - -static COLORREF colours[OSC4_NCOLOURS]; -static HPALETTE pal; -static LPLOGPALETTE logpal; -bool tried_pal = false; -COLORREF colorref_modifier = 0; - enum MONITOR_DPI_TYPE { MDT_EFFECTIVE_DPI, MDT_ANGULAR_DPI, MDT_RAW_DPI, MDT_DEFAULT }; DECL_WINDOWS_FUNCTION(static, BOOL, GetMonitorInfoA, (HMONITOR, LPMONITORINFO)); DECL_WINDOWS_FUNCTION(static, HMONITOR, MonitorFromPoint, (POINT, DWORD)); @@ -205,30 +135,12 @@ DECL_WINDOWS_FUNCTION(static, HRESULT, GetDpiForMonitor, (HMONITOR hmonitor, enu DECL_WINDOWS_FUNCTION(static, HRESULT, GetSystemMetricsForDpi, (int nIndex, UINT dpi)); DECL_WINDOWS_FUNCTION(static, HRESULT, AdjustWindowRectExForDpi, (LPRECT lpRect, DWORD dwStyle, BOOL bMenu, DWORD dwExStyle, UINT dpi)); -static struct _dpi_info { - POINT cur_dpi; - RECT new_wnd_rect; -} dpi_info; - -static HBITMAP caretbm; - -static int dbltime, lasttime, lastact; -static Mouse_Button lastbtn; - -/* this allows xterm-style mouse handling. */ -static bool send_raw_mouse = false; -static int wheel_accumulator = 0; - -static bool pointer_indicates_raw_mouse = false; - -static BusyStatus busy_status = BUSY_NOT; - -static wchar_t *window_name, *icon_name; - -static int compose_state = 0; - static UINT wm_mousewheel = WM_MOUSEWHEEL; +struct WinGuiSeatListNode wgslisthead = { + .next = &wgslisthead, .prev = &wgslisthead, +}; + #define IS_HIGH_VARSEL(wch1, wch2) \ ((wch1) == 0xDB40 && ((wch2) >= 0xDD00 && (wch2) <= 0xDDEF)) #define IS_LOW_VARSEL(wch) \ @@ -292,33 +204,33 @@ static const TermWinVtable windows_termwin_vt = { .unthrottle = wintw_unthrottle, }; -static TermWin wintw[1]; -static HDC wintw_hdc; - static HICON trust_icon = INVALID_HANDLE_VALUE; const bool share_can_be_downstream = true; const bool share_can_be_upstream = true; -static bool is_utf8(void) +static bool is_utf8(WinGuiSeat *wgs) { - return ucsdata.line_codepage == CP_UTF8; + return wgs->ucsdata.line_codepage == CP_UTF8; } static bool win_seat_is_utf8(Seat *seat) { - return is_utf8(); + WinGuiSeat *wgs = container_of(seat, WinGuiSeat, seat); + return is_utf8(wgs); } static char *win_seat_get_ttymode(Seat *seat, const char *mode) { - return term_get_ttymode(term, mode); + WinGuiSeat *wgs = container_of(seat, WinGuiSeat, seat); + return term_get_ttymode(wgs->term, mode); } static StripCtrlChars *win_seat_stripctrl_new( Seat *seat, BinarySink *bs_out, SeatInteractionContext sic) { - return stripctrl_new_term(bs_out, false, 0, term); + WinGuiSeat *wgs = container_of(seat, WinGuiSeat, seat); + return stripctrl_new_term(bs_out, false, 0, wgs->term); } static size_t win_seat_output( @@ -327,6 +239,7 @@ static bool win_seat_eof(Seat *seat); static SeatPromptResult win_seat_get_userpass_input(Seat *seat, prompts_t *p); static void win_seat_notify_remote_exit(Seat *seat); static void win_seat_connection_fatal(Seat *seat, const char *msg); +static void win_seat_nonfatal(Seat *seat, const char *msg); static void win_seat_update_specials_menu(Seat *seat); static void win_seat_set_busy_status(Seat *seat, BusyStatus status); static void win_seat_set_trust_status(Seat *seat, bool trusted); @@ -344,6 +257,7 @@ static const SeatVtable win_seat_vt = { .notify_remote_exit = win_seat_notify_remote_exit, .notify_remote_disconnect = nullseat_notify_remote_disconnect, .connection_fatal = win_seat_connection_fatal, + .nonfatal = win_seat_nonfatal, .update_specials_menu = win_seat_update_specials_menu, .get_ttymode = win_seat_get_ttymode, .set_busy_status = win_seat_set_busy_status, @@ -364,26 +278,24 @@ static const SeatVtable win_seat_vt = { .interactive = nullseat_interactive_yes, .get_cursor_position = win_seat_get_cursor_position, }; -static WinGuiSeat wgs = { .seat.vt = &win_seat_vt, - .logpolicy.vt = &win_gui_logpolicy_vt }; -static void start_backend(void) +static void start_backend(WinGuiSeat *wgs) { const struct BackendVtable *vt; char *error, *realhost; int i; - cmdline_get_passwd_state = cmdline_get_passwd_input_state_new; + wgs->cmdline_get_passwd_state = cmdline_get_passwd_input_state_new; - vt = backend_vt_from_conf(conf); + vt = backend_vt_from_conf(wgs->conf); - seat_set_trust_status(&wgs.seat, true); - error = backend_init(vt, &wgs.seat, &backend, logctx, conf, - conf_get_str(conf, CONF_host), - conf_get_int(conf, CONF_port), + seat_set_trust_status(&wgs->seat, true); + error = backend_init(vt, &wgs->seat, &wgs->backend, wgs->logctx, wgs->conf, + conf_get_str(wgs->conf, CONF_host), + conf_get_int(wgs->conf, CONF_port), &realhost, - conf_get_bool(conf, CONF_tcp_nodelay), - conf_get_bool(conf, CONF_tcp_keepalives)); + conf_get_bool(wgs->conf, CONF_tcp_nodelay), + conf_get_bool(wgs->conf, CONF_tcp_keepalives)); if (error) { char *str = dupprintf("%s 错误", appname); char *msg; @@ -392,7 +304,7 @@ static void start_backend(void) msg = dupprintf("无法打开终端:\n%s", error); } else { msg = dupprintf("无法打开到\n%s 的连接\n%s", - conf_dest(conf), error); + conf_dest(wgs->conf), error); } sfree(error); MessageBox(NULL, msg, str, MB_ICONERROR | MB_OK); @@ -400,18 +312,18 @@ static void start_backend(void) sfree(msg); exit(0); } - term_setup_window_titles(term, realhost); + term_setup_window_titles(wgs->term, realhost); sfree(realhost); /* * Connect the terminal to the backend for resize purposes. */ - term_provide_backend(term, backend); + term_provide_backend(wgs->term, wgs->backend); /* * Set up a line discipline. */ - ldisc = ldisc_create(conf, term, backend, &wgs.seat); + wgs->ldisc = ldisc_create(wgs->conf, wgs->term, wgs->backend, &wgs->seat); /* * Destroy the Restart Session menu item. (This will return @@ -420,43 +332,44 @@ static void start_backend(void) * as the menu item ends up not being there, we don't care * whether it was us who removed it or not!) */ - for (i = 0; i < lenof(popup_menus); i++) { - DeleteMenu(popup_menus[i].menu, IDM_RESTART, MF_BYCOMMAND); + for (i = 0; i < lenof(wgs->popup_menus); i++) { + DeleteMenu(wgs->popup_menus[i].menu, IDM_RESTART, MF_BYCOMMAND); } - session_closed = false; + wgs->session_closed = false; } -static void close_session(void *ignored_context) +static void close_session(void *vctx) { + WinGuiSeat *wgs = (WinGuiSeat *)vctx; char *newtitle; int i; - session_closed = true; + wgs->session_closed = true; newtitle = dupprintf("%s (不活动的)", appname); - win_set_icon_title(wintw, newtitle, DEFAULT_CODEPAGE); - win_set_title(wintw, newtitle, DEFAULT_CODEPAGE); + win_set_icon_title(&wgs->termwin, newtitle, DEFAULT_CODEPAGE); + win_set_title(&wgs->termwin, newtitle, DEFAULT_CODEPAGE); sfree(newtitle); - if (ldisc) { - ldisc_free(ldisc); - ldisc = NULL; + if (wgs->ldisc) { + ldisc_free(wgs->ldisc); + wgs->ldisc = NULL; } - if (backend) { - backend_free(backend); - backend = NULL; - term_provide_backend(term, NULL); - seat_update_specials_menu(&wgs.seat); + if (wgs->backend) { + backend_free(wgs->backend); + wgs->backend = NULL; + term_provide_backend(wgs->term, NULL); + seat_update_specials_menu(&wgs->seat); } /* * Show the Restart Session menu item. Do a precautionary * delete first to ensure we never end up with more than one. */ - for (i = 0; i < lenof(popup_menus); i++) { - DeleteMenu(popup_menus[i].menu, IDM_RESTART, MF_BYCOMMAND); - InsertMenu(popup_menus[i].menu, IDM_DUPSESS, MF_BYCOMMAND | MF_ENABLED, - IDM_RESTART, "重启会话(&R)"); + for (i = 0; i < lenof(wgs->popup_menus); i++) { + DeleteMenu(wgs->popup_menus[i].menu, IDM_RESTART, MF_BYCOMMAND); + InsertMenu(wgs->popup_menus[i].menu, IDM_DUPSESS, + MF_BYCOMMAND | MF_ENABLED, IDM_RESTART, "重启会话(&R)"); } } @@ -478,7 +391,7 @@ static void sw_SetWindowText(HWND hwnd, wchar_t *text) if (unicode_window) { SetWindowTextW(hwnd, text); } else { - char *mb = dup_wc_to_mb(DEFAULT_CODEPAGE, 0, text, "?"); + char *mb = dup_wc_to_mb(DEFAULT_CODEPAGE, text, "?"); SetWindowTextA(hwnd, mb); sfree(mb); } @@ -505,7 +418,7 @@ wchar_t *terminal_window_class_w(void) { static wchar_t *classname = NULL; if (!classname) - classname = dup_mb_to_wc(DEFAULT_CODEPAGE, 0, appname); + classname = dup_mb_to_wc(DEFAULT_CODEPAGE, appname); if (!hprev) { WNDCLASSW wndclassw; SETUP_WNDCLASS(wndclassw, classname); @@ -567,7 +480,20 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) init_winfuncs(); - conf = conf_new(); + setup_gui_timing(); + + WinGuiSeat *wgs = snew(WinGuiSeat); + memset(wgs, 0, sizeof(*wgs)); + wgs_link(wgs); + + wgs->seat.vt = &win_seat_vt; + wgs->logpolicy.vt = &win_gui_logpolicy_vt; + wgs->termwin.vt = &windows_termwin_vt; + + wgs->caret_x = wgs->caret_y = -1; + wgs->busy_status = BUSY_NOT; + + wgs->conf = conf_new(); /* * Initialize COM. @@ -583,12 +509,14 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) /* * Process the command line. + * (If the command line doesn't provide enough info to start a + * session, this will detour via the config box.) */ - gui_term_process_cmdline(conf, cmdline); + gui_term_process_cmdline(wgs->conf, cmdline); - memset(&ucsdata, 0, sizeof(ucsdata)); + memset(&wgs->ucsdata, 0, sizeof(wgs->ucsdata)); - conf_cache_data(); + conf_cache_data(wgs); /* * Guess some defaults for the window size. This all gets @@ -597,15 +525,17 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) * large font rather than a small one... */ - font_width = 10; - font_height = 20; - extra_width = 25; - extra_height = 28; - guess_width = extra_width + font_width * conf_get_int(conf, CONF_width); - guess_height = extra_height + font_height*conf_get_int(conf, CONF_height); + wgs->font_width = 10; + wgs->font_height = 20; + wgs->extra_width = 25; + wgs->extra_height = 28; + guess_width = wgs->extra_width + wgs->font_width * conf_get_int( + wgs->conf, CONF_width); + guess_height = wgs->extra_height + wgs->font_height * conf_get_int( + wgs->conf, CONF_height); { RECT r; - get_fullscreen_rect(&r); + get_fullscreen_rect(wgs, &r); if (guess_width > r.right - r.left) guess_width = r.right - r.left; if (guess_height > r.bottom - r.top) @@ -620,69 +550,71 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) bool resize_forbidden = false; if (vt && vt->flags & BACKEND_RESIZE_FORBIDDEN) resize_forbidden = true; - wchar_t *uappname = dup_mb_to_wc(DEFAULT_CODEPAGE, 0, appname); - window_name = dup_mb_to_wc(DEFAULT_CODEPAGE, 0, appname); - icon_name = dup_mb_to_wc(DEFAULT_CODEPAGE, 0, appname); - if (!conf_get_bool(conf, CONF_scrollbar)) + wchar_t *uappname = dup_mb_to_wc(DEFAULT_CODEPAGE, appname); + wgs->window_name = dup_mb_to_wc(DEFAULT_CODEPAGE, appname); + wgs->icon_name = dup_mb_to_wc(DEFAULT_CODEPAGE, appname); + if (!conf_get_bool(wgs->conf, CONF_scrollbar)) winmode &= ~(WS_VSCROLL); - if (conf_get_int(conf, CONF_resize_action) == RESIZE_DISABLED || + if (conf_get_int(wgs->conf, CONF_resize_action) == RESIZE_DISABLED || resize_forbidden) winmode &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX); - if (conf_get_bool(conf, CONF_alwaysontop)) + if (conf_get_bool(wgs->conf, CONF_alwaysontop)) exwinmode |= WS_EX_TOPMOST; - if (conf_get_bool(conf, CONF_sunken_edge)) + if (conf_get_bool(wgs->conf, CONF_sunken_edge)) exwinmode |= WS_EX_CLIENTEDGE; #ifdef TEST_ANSI_WINDOW /* For developer testing of ANSI window support, pretend * CreateWindowExW failed */ - wgs.term_hwnd = NULL; + wgs->term_hwnd = NULL; SetLastError(ERROR_CALL_NOT_IMPLEMENTED); #else unicode_window = true; sw_PeekMessage = PeekMessageW; sw_DispatchMessage = DispatchMessageW; sw_DefWindowProc = DefWindowProcW; - wgs.term_hwnd = CreateWindowExW( + wgs->term_hwnd = CreateWindowExW( exwinmode, terminal_window_class_w(), uappname, winmode, CW_USEDEFAULT, CW_USEDEFAULT, guess_width, guess_height, NULL, NULL, inst, NULL); #endif #if defined LEGACY_WINDOWS || defined TEST_ANSI_WINDOW - if (!wgs.term_hwnd && GetLastError() == ERROR_CALL_NOT_IMPLEMENTED) { + if (!wgs->term_hwnd && GetLastError() == ERROR_CALL_NOT_IMPLEMENTED) { /* Fall back to an ANSI window, swapping in all the ANSI * window message handling functions */ unicode_window = false; sw_PeekMessage = PeekMessageA; sw_DispatchMessage = DispatchMessageA; sw_DefWindowProc = DefWindowProcA; - wgs.term_hwnd = CreateWindowExA( + wgs->term_hwnd = CreateWindowExA( exwinmode, terminal_window_class_a(), appname, winmode, CW_USEDEFAULT, CW_USEDEFAULT, guess_width, guess_height, NULL, NULL, inst, NULL); } #endif - if (!wgs.term_hwnd) { + if (!wgs->term_hwnd) { modalfatalbox("Unable to create terminal window: %s", win_strerror(GetLastError())); } - memset(&dpi_info, 0, sizeof(struct _dpi_info)); - init_dpi_info(); + memset(&wgs->dpi_info, 0, sizeof(struct _dpi_info)); + init_dpi_info(wgs); sfree(uappname); } + SetWindowLongPtr(wgs->term_hwnd, GWLP_USERDATA, (LONG_PTR)wgs); + /* * Initialise the fonts, simultaneously correcting the guesses * for font_{width,height}. */ - init_fonts(0,0); + init_fonts(wgs, 0, 0); /* * Prepare a logical palette. */ - init_palette(); + init_palette(wgs); /* * Initialise the terminal. (We have to do this _after_ @@ -690,48 +622,90 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) * which will call schedule_timer(), which will in turn call * timer_change_notify() which will expect hwnd to exist.) */ - wintw->vt = &windows_termwin_vt; - term = term_init(conf, &ucsdata, wintw); - setup_clipboards(term, conf); - logctx = log_init(&wgs.logpolicy, conf); - term_provide_logctx(term, logctx); - term_size(term, conf_get_int(conf, CONF_height), - conf_get_int(conf, CONF_width), - conf_get_int(conf, CONF_savelines)); + wgs->term = term_init(wgs->conf, &wgs->ucsdata, &wgs->termwin); + setup_clipboards(wgs->term, wgs->conf); + wgs->logctx = log_init(&wgs->logpolicy, wgs->conf); + term_provide_logctx(wgs->term, wgs->logctx); + term_size(wgs->term, conf_get_int(wgs->conf, CONF_height), + conf_get_int(wgs->conf, CONF_width), + conf_get_int(wgs->conf, CONF_savelines)); /* * Correct the guesses for extra_{width,height}. */ { RECT cr, wr; - GetWindowRect(wgs.term_hwnd, &wr); - GetClientRect(wgs.term_hwnd, &cr); - offset_width = offset_height = conf_get_int(conf, CONF_window_border); - extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2; - extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2; + GetWindowRect(wgs->term_hwnd, &wr); + GetClientRect(wgs->term_hwnd, &cr); + wgs->offset_width = wgs->offset_height = + conf_get_int(wgs->conf, CONF_window_border); + wgs->extra_width = + wr.right - wr.left - cr.right + cr.left + wgs->offset_width*2; + wgs->extra_height = + wr.bottom - wr.top - cr.bottom + cr.top +wgs->offset_height*2; } /* - * Resize the window, now we know what size we _really_ want it - * to be. + * Compute what size we _really_ want the window to be. */ - guess_width = extra_width + font_width * term->cols; - guess_height = extra_height + font_height * term->rows; - SetWindowPos(wgs.term_hwnd, NULL, 0, 0, guess_width, guess_height, - SWP_NOMOVE | SWP_NOREDRAW | SWP_NOZORDER); + guess_width = wgs->extra_width + wgs->font_width * wgs->term->cols; + guess_height = wgs->extra_height + wgs->font_height * wgs->term->rows; + + /* + * Resize the window to that size, also repositioning it if it's extended + * off the edge of a monitor. + */ + { + /* Find the previous coordinates of the window */ + RECT winr; + GetWindowRect(wgs->term_hwnd, &winr); + + int x = winr.left; + int y = winr.top; + + /* Adjust them if necessary */ + RECT war; + if (get_workingarea_rect(wgs, &war)) { + /* + * Try to ensure the window is entirely within the monitor's + * working area, by adjusting its position if not. + * + * We first move it left, if it overlaps off the right side. Then + * we move it right if it overlaps off the left side. This means + * that if it's wider than the working area (so that some overlap + * is unavoidable), we prefer to get its left edge in bounds than + * its right edge. Similarly, we do the y checks in the same + * order, privileging the top edge over the bottom. + */ + if (x + guess_width > war.right) + x = war.right - guess_width; + if (x < war.left) + x = war.left; + if (y + guess_height > war.bottom) + y = war.bottom - guess_height; + if (y < war.top) + y = war.top; + } + + /* And set the window to the final size and position we've chosen */ + SetWindowPos(wgs->term_hwnd, NULL, x, y, guess_width, guess_height, + SWP_NOREDRAW | SWP_NOZORDER); + } /* * Set up a caret bitmap, with no content. */ { char *bits; - int size = (font_width + 15) / 16 * 2 * font_height; + int size = (wgs->font_width + 15) / 16 * 2 * wgs->font_height; bits = snewn(size, char); memset(bits, 0, size); - caretbm = CreateBitmap(font_width, font_height, 1, 1, bits); + wgs->caretbm = CreateBitmap(wgs->font_width, wgs->font_height, + 1, 1, bits); sfree(bits); } - CreateCaret(wgs.term_hwnd, caretbm, font_width, font_height); + CreateCaret(wgs->term_hwnd, wgs->caretbm, + wgs->font_width, wgs->font_height); /* * Initialise the scroll bar. @@ -742,18 +716,18 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) si.cbSize = sizeof(si); si.fMask = SIF_ALL | SIF_DISABLENOSCROLL; si.nMin = 0; - si.nMax = term->rows - 1; - si.nPage = term->rows; + si.nMax = wgs->term->rows - 1; + si.nPage = wgs->term->rows; si.nPos = 0; - SetScrollInfo(wgs.term_hwnd, SB_VERT, &si, false); + SetScrollInfo(wgs->term_hwnd, SB_VERT, &si, false); } /* * Prepare the mouse handler. */ - lastact = MA_NOTHING; - lastbtn = MBT_NOTHING; - dbltime = GetDoubleClickTime(); + wgs->lastact = MA_NOTHING; + wgs->lastbtn = MBT_NOTHING; + wgs->dbltime = GetDoubleClickTime(); /* * Set up the session-control options on the system menu. @@ -763,24 +737,28 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) int j; char *str; - popup_menus[SYSMENU].menu = GetSystemMenu(wgs.term_hwnd, false); - popup_menus[CTXMENU].menu = CreatePopupMenu(); - AppendMenu(popup_menus[CTXMENU].menu, MF_ENABLED, IDM_COPY, "复制(&C)"); - AppendMenu(popup_menus[CTXMENU].menu, MF_ENABLED, IDM_PASTE, "粘贴(&P)"); + wgs->popup_menus[SYSMENU].menu = GetSystemMenu(wgs->term_hwnd, false); + wgs->popup_menus[CTXMENU].menu = CreatePopupMenu(); - savedsess_menu = CreateMenu(); + for (j = 0; j < lenof(wgs->popup_menus); j++) { + m = wgs->popup_menus[j].menu; + AppendMenu(m, MF_ENABLED, IDM_COPY, "复制(&C)"); + AppendMenu(m, MF_ENABLED, IDM_PASTE, "粘贴(&P)"); + } + + wgs->savedsess_menu = CreateMenu(); get_sesslist(&sesslist, true); - update_savedsess_menu(); + update_savedsess_menu(wgs); - for (j = 0; j < lenof(popup_menus); j++) { - m = popup_menus[j].menu; + for (j = 0; j < lenof(wgs->popup_menus); j++) { + m = wgs->popup_menus[j].menu; AppendMenu(m, MF_SEPARATOR, 0, 0); AppendMenu(m, MF_ENABLED, IDM_SHOWLOG, "事件日志记录(&E)"); AppendMenu(m, MF_SEPARATOR, 0, 0); AppendMenu(m, MF_ENABLED, IDM_NEWSESS, "新会话(&W)..."); AppendMenu(m, MF_ENABLED, IDM_DUPSESS, "复制会话(&D)"); - AppendMenu(m, MF_POPUP | MF_ENABLED, (UINT_PTR) savedsess_menu, + AppendMenu(m, MF_POPUP | MF_ENABLED, (UINT_PTR)wgs->savedsess_menu, "保存的会话(&V)"); AppendMenu(m, MF_ENABLED, IDM_RECONF, "修改设置(&G)..."); AppendMenu(m, MF_SEPARATOR, 0, 0); @@ -788,7 +766,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) AppendMenu(m, MF_ENABLED, IDM_CLRSB, "清除滚动条(&L)"); AppendMenu(m, MF_ENABLED, IDM_RESET, "重置终端(&T)"); AppendMenu(m, MF_SEPARATOR, 0, 0); - AppendMenu(m, (conf_get_int(conf, CONF_resize_action) + AppendMenu(m, (conf_get_int(wgs->conf, CONF_resize_action) == RESIZE_DISABLED) ? MF_GRAYED : MF_ENABLED, IDM_FULLSCREEN, "全屏(&F)"); AppendMenu(m, MF_SEPARATOR, 0, 0); @@ -801,27 +779,27 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) } if (restricted_acl()) { - lp_eventlog(&wgs.logpolicy, "使用受限进程 ACL 运行"); + lp_eventlog(&wgs->logpolicy, "使用受限进程 ACL 运行"); } - winselgui_set_hwnd(wgs.term_hwnd); - start_backend(); + winselgui_set_hwnd(wgs->term_hwnd); + start_backend(wgs); /* * Set up the initial input locale. */ - set_input_locale(GetKeyboardLayout(0)); + set_input_locale(wgs, GetKeyboardLayout(0)); /* * Finally show the window! */ - ShowWindow(wgs.term_hwnd, show); - SetForegroundWindow(wgs.term_hwnd); + ShowWindow(wgs->term_hwnd, show); + SetForegroundWindow(wgs->term_hwnd); - term_set_focus(term, GetForegroundWindow() == wgs.term_hwnd); - UpdateWindow(wgs.term_hwnd); + term_set_focus(wgs->term, GetForegroundWindow() == wgs->term_hwnd); + UpdateWindow(wgs->term_hwnd); - gui_terminal_ready(wgs.term_hwnd, &wgs.seat, backend); + gui_terminal_ready(wgs->term_hwnd, &wgs->seat, wgs->backend); while (1) { int n; @@ -850,7 +828,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) } else { timeout = INFINITE; /* The messages seem unreliable; especially if we're being tricky */ - term_set_focus(term, GetForegroundWindow() == wgs.term_hwnd); + term_set_focus(wgs->term, GetForegroundWindow() == wgs->term_hwnd); } HandleWaitList *hwl = get_handle_wait_list(); @@ -901,6 +879,16 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) return msg.wParam; /* ... but optimiser doesn't know */ } +static void wgs_cleanup(WinGuiSeat *wgs) +{ + deinit_fonts(wgs); + sfree(wgs->logpal); + if (wgs->pal) + DeleteObject(wgs->pal); + wgs_unlink(wgs); + sfree(wgs); +} + char *handle_restrict_acl_cmdline_prefix(char *p) { /* @@ -1015,10 +1003,11 @@ void cleanup_exit(int code) /* * Clean up. */ - deinit_fonts(); - sfree(logpal); - if (pal) - DeleteObject(pal); + while (wgslisthead.next != &wgslisthead) { + WinGuiSeat *wgs = container_of( + wgslisthead.next, WinGuiSeat, wgslistnode); + wgs_cleanup(wgs); + } sk_cleanup(); random_save_seed(); @@ -1033,20 +1022,21 @@ void cleanup_exit(int code) /* * Refresh the saved-session submenu from `sesslist'. */ -static void update_savedsess_menu(void) +static void update_savedsess_menu(WinGuiSeat *wgs) { int i; - while (DeleteMenu(savedsess_menu, 0, MF_BYPOSITION)) ; + while (DeleteMenu(wgs->savedsess_menu, 0, MF_BYPOSITION)) ; /* skip sesslist.sessions[0] == Default Settings */ for (i = 1; i < ((sesslist.nsessions <= MENU_SAVED_MAX+1) ? sesslist.nsessions : MENU_SAVED_MAX+1); i++) - AppendMenu(savedsess_menu, MF_ENABLED, + AppendMenu(wgs->savedsess_menu, MF_ENABLED, IDM_SAVED_MIN + (i-1)*MENU_SAVED_STEP, sesslist.sessions[i]); if (sesslist.nsessions <= 1) - AppendMenu(savedsess_menu, MF_GRAYED, IDM_SAVED_MIN, "(没有会话)"); + AppendMenu(wgs->savedsess_menu, MF_GRAYED, IDM_SAVED_MIN, + "(没有会话)"); } /* @@ -1054,15 +1044,16 @@ static void update_savedsess_menu(void) */ static void win_seat_update_specials_menu(Seat *seat) { + WinGuiSeat *wgs = container_of(seat, WinGuiSeat, seat); HMENU new_menu; int i, j; - if (backend) - specials = backend_get_specials(backend); + if (wgs->backend) + wgs->specials = backend_get_specials(wgs->backend); else - specials = NULL; + wgs->specials = NULL; - if (specials) { + if (wgs->specials) { /* We can't use Windows to provide a stack for submenus, so * here's a lame "stack" that will do for now. */ HMENU saved_menu = NULL; @@ -1070,7 +1061,7 @@ static void win_seat_update_specials_menu(Seat *seat) new_menu = CreatePopupMenu(); for (i = 0; nesting > 0; i++) { assert(IDM_SPECIAL_MIN + 0x10 * i < IDM_SPECIAL_MAX); - switch (specials[i].code) { + switch (wgs->specials[i].code) { case SS_SEP: AppendMenu(new_menu, MF_SEPARATOR, 0, 0); break; @@ -1080,7 +1071,7 @@ static void win_seat_update_specials_menu(Seat *seat) saved_menu = new_menu; /* XXX lame stacking */ new_menu = CreatePopupMenu(); AppendMenu(saved_menu, MF_POPUP | MF_ENABLED, - (UINT_PTR) new_menu, specials[i].name); + (UINT_PTR) new_menu, wgs->specials[i].name); break; case SS_EXITMENU: nesting--; @@ -1091,43 +1082,43 @@ static void win_seat_update_specials_menu(Seat *seat) break; default: AppendMenu(new_menu, MF_ENABLED, IDM_SPECIAL_MIN + 0x10 * i, - specials[i].name); + wgs->specials[i].name); break; } } /* Squirrel the highest special. */ - n_specials = i - 1; + wgs->n_specials = i - 1; } else { new_menu = NULL; - n_specials = 0; + wgs->n_specials = 0; } - for (j = 0; j < lenof(popup_menus); j++) { - if (specials_menu) { + for (j = 0; j < lenof(wgs->popup_menus); j++) { + if (wgs->specials_menu) { /* XXX does this free up all submenus? */ - DeleteMenu(popup_menus[j].menu, (UINT_PTR)specials_menu, + DeleteMenu(wgs->popup_menus[j].menu, (UINT_PTR)wgs->specials_menu, MF_BYCOMMAND); - DeleteMenu(popup_menus[j].menu, IDM_SPECIALSEP, MF_BYCOMMAND); + DeleteMenu(wgs->popup_menus[j].menu, IDM_SPECIALSEP, MF_BYCOMMAND); } if (new_menu) { - InsertMenu(popup_menus[j].menu, IDM_SHOWLOG, + InsertMenu(wgs->popup_menus[j].menu, IDM_SHOWLOG, MF_BYCOMMAND | MF_POPUP | MF_ENABLED, (UINT_PTR) new_menu, "指定命令(&P)"); - InsertMenu(popup_menus[j].menu, IDM_SHOWLOG, + InsertMenu(wgs->popup_menus[j].menu, IDM_SHOWLOG, MF_BYCOMMAND | MF_SEPARATOR, IDM_SPECIALSEP, 0); } } - specials_menu = new_menu; + wgs->specials_menu = new_menu; } -static void update_mouse_pointer(void) +static void update_mouse_pointer(WinGuiSeat *wgs) { LPTSTR curstype = NULL; bool force_visible = false; static bool forced_visible = false; - switch (busy_status) { + switch (wgs->busy_status) { case BUSY_NOT: - if (pointer_indicates_raw_mouse) + if (wgs->pointer_indicates_raw_mouse) curstype = IDC_ARROW; else curstype = IDC_IBEAM; @@ -1145,7 +1136,7 @@ static void update_mouse_pointer(void) } { HCURSOR cursor = LoadCursor(NULL, curstype); - SetClassLongPtr(wgs.term_hwnd, GCLP_HCURSOR, (LONG_PTR)cursor); + SetClassLongPtr(wgs->term_hwnd, GCLP_HCURSOR, (LONG_PTR)cursor); SetCursor(cursor); /* force redraw of cursor at current posn */ } if (force_visible != forced_visible) { @@ -1160,19 +1151,22 @@ static void update_mouse_pointer(void) static void win_seat_set_busy_status(Seat *seat, BusyStatus status) { - busy_status = status; - update_mouse_pointer(); + WinGuiSeat *wgs = container_of(seat, WinGuiSeat, seat); + wgs->busy_status = status; + update_mouse_pointer(wgs); } static void wintw_set_raw_mouse_mode(TermWin *tw, bool activate) { - send_raw_mouse = activate; + WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin); + wgs->send_raw_mouse = activate; } static void wintw_set_raw_mouse_mode_pointer(TermWin *tw, bool activate) { - pointer_indicates_raw_mouse = activate; - update_mouse_pointer(); + WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin); + wgs->pointer_indicates_raw_mouse = activate; + update_mouse_pointer(wgs); } /* @@ -1180,16 +1174,39 @@ static void wintw_set_raw_mouse_mode_pointer(TermWin *tw, bool activate) */ static void win_seat_connection_fatal(Seat *seat, const char *msg) { + WinGuiSeat *wgs = container_of(seat, WinGuiSeat, seat); char *title = dupprintf("%s 致命错误", appname); - show_mouseptr(true); - MessageBox(wgs.term_hwnd, msg, title, MB_ICONERROR | MB_OK); + show_mouseptr(wgs, true); + MessageBox(wgs->term_hwnd, msg, title, MB_ICONERROR | MB_OK); sfree(title); - if (conf_get_int(conf, CONF_close_on_exit) == FORCE_ON) + if (conf_get_int(wgs->conf, CONF_close_on_exit) == FORCE_ON) PostQuitMessage(1); else { - queue_toplevel_callback(close_session, NULL); + queue_toplevel_callback(close_session, wgs); + } +} + +/* + * Print a message box and don't close the connection. + */ +static void win_seat_nonfatal(Seat *seat, const char *msg) +{ + WinGuiSeat *wgs = container_of(seat, WinGuiSeat, seat); + char *title = dupprintf("%s Error", appname); + show_mouseptr(wgs, true); + MessageBox(wgs->term_hwnd, msg, title, MB_ICONERROR | MB_OK); + sfree(title); +} + +static HWND find_window_for_msgbox(void) +{ + if (wgslisthead.next != &wgslisthead) { + WinGuiSeat *wgs = container_of( + wgslisthead.next, WinGuiSeat, wgslistnode); + return wgs->term_hwnd; } + return NULL; } /* @@ -1204,7 +1221,7 @@ void cmdline_error(const char *fmt, ...) message = dupvprintf(fmt, ap); va_end(ap); title = dupprintf("%s 命令行错误", appname); - MessageBox(wgs.term_hwnd, message, title, MB_ICONERROR | MB_OK); + MessageBox(find_window_for_msgbox(), message, title, MB_ICONERROR | MB_OK); sfree(message); sfree(title); exit(1); @@ -1221,7 +1238,8 @@ static inline rgb rgb_from_colorref(COLORREF cr) static void wintw_palette_get_overrides(TermWin *tw, Terminal *term) { - if (conf_get_bool(conf, CONF_system_colour)) { + WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin); + if (conf_get_bool(wgs->conf, CONF_system_colour)) { rgb rgb; rgb = rgb_from_colorref(GetSysColor(COLOR_WINDOWTEXT)); @@ -1288,9 +1306,9 @@ static void exact_textout(HDC hdc, int x, int y, CONST RECT *lprc, * and for everything else we use a simple ExtTextOut as we did * before exact_textout() was introduced. */ -static void general_textout(HDC hdc, int x, int y, CONST RECT *lprc, - unsigned short *lpString, UINT cbCount, - CONST INT *lpDx, bool opaque) +static void general_textout( + WinGuiSeat *wgs, HDC hdc, int x, int y, CONST RECT *lprc, + unsigned short *lpString, UINT cbCount, CONST INT *lpDx, bool opaque) { int i, j, xp, xn; int bkmode = 0; @@ -1316,11 +1334,11 @@ static void general_textout(HDC hdc, int x, int y, CONST RECT *lprc, */ if (rtl) { exact_textout(hdc, xp, y, lprc, lpString+i, j-i, - font_varpitch ? NULL : lpDx+i, opaque); + wgs->font_varpitch ? NULL : lpDx+i, opaque); } else { ExtTextOutW(hdc, xp, y, ETO_CLIPPED | (opaque ? ETO_OPAQUE : 0), lprc, lpString+i, j-i, - font_varpitch ? NULL : lpDx+i); + wgs->font_varpitch ? NULL : lpDx+i); } i = j; @@ -1336,7 +1354,7 @@ static void general_textout(HDC hdc, int x, int y, CONST RECT *lprc, SetBkMode(hdc, bkmode); } -static int get_font_width(HDC hdc, const TEXTMETRIC *tm) +static int get_font_width(WinGuiSeat *wgs, HDC hdc, const TEXTMETRIC *tm) { int ret; /* Note that the TMPF_FIXED_PITCH bit is defined upside down :-( */ @@ -1348,8 +1366,8 @@ static int get_font_width(HDC hdc, const TEXTMETRIC *tm) ABCFLOAT widths[LAST-FIRST + 1]; int j; - font_varpitch = true; - font_dualwidth = true; + wgs->font_varpitch = true; + wgs->font_dualwidth = true; if (GetCharABCWidthsFloat(hdc, FIRST, LAST, widths)) { ret = 0; for (j = 0; j < lenof(widths); j++) { @@ -1367,26 +1385,26 @@ static int get_font_width(HDC hdc, const TEXTMETRIC *tm) return ret; } -static void init_dpi_info(void) +static void init_dpi_info(WinGuiSeat *wgs) { - if (dpi_info.cur_dpi.x == 0 || dpi_info.cur_dpi.y == 0) { + if (wgs->dpi_info.cur_dpi.x == 0 || wgs->dpi_info.cur_dpi.y == 0) { if (p_GetDpiForMonitor && p_MonitorFromWindow) { UINT dpiX, dpiY; HMONITOR currentMonitor = p_MonitorFromWindow( - wgs.term_hwnd, MONITOR_DEFAULTTOPRIMARY); + wgs->term_hwnd, MONITOR_DEFAULTTOPRIMARY); if (p_GetDpiForMonitor(currentMonitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY) == S_OK) { - dpi_info.cur_dpi.x = (int)dpiX; - dpi_info.cur_dpi.y = (int)dpiY; + wgs->dpi_info.cur_dpi.x = (int)dpiX; + wgs->dpi_info.cur_dpi.y = (int)dpiY; } } /* Fall back to system DPI */ - if (dpi_info.cur_dpi.x == 0 || dpi_info.cur_dpi.y == 0) { - HDC hdc = GetDC(wgs.term_hwnd); - dpi_info.cur_dpi.x = GetDeviceCaps(hdc, LOGPIXELSX); - dpi_info.cur_dpi.y = GetDeviceCaps(hdc, LOGPIXELSY); - ReleaseDC(wgs.term_hwnd, hdc); + if (wgs->dpi_info.cur_dpi.x == 0 || wgs->dpi_info.cur_dpi.y == 0) { + HDC hdc = GetDC(wgs->term_hwnd); + wgs->dpi_info.cur_dpi.x = GetDeviceCaps(hdc, LOGPIXELSX); + wgs->dpi_info.cur_dpi.y = GetDeviceCaps(hdc, LOGPIXELSY); + ReleaseDC(wgs->term_hwnd, hdc); } } } @@ -1410,7 +1428,7 @@ static void init_dpi_info(void) * * - find a trust sigil icon that will look OK with the chosen font. */ -static void init_fonts(int pick_width, int pick_height) +static void init_fonts(WinGuiSeat *wgs, int pick_width, int pick_height) { TEXTMETRIC tm; OUTLINETEXTMETRIC otm; @@ -1423,14 +1441,17 @@ static void init_fonts(int pick_width, int pick_height) int fw_dontcare, fw_bold; for (i = 0; i < FONT_MAXNO; i++) - fonts[i] = NULL; + wgs->fonts[i] = NULL; - bold_font_mode = conf_get_int(conf, CONF_bold_style) & 1 ? + wgs->bold_font_mode = + conf_get_int(wgs->conf, CONF_bold_style) & BOLD_STYLE_FONT ? BOLD_FONT : BOLD_NONE; - bold_colours = conf_get_int(conf, CONF_bold_style) & 2 ? true : false; - und_mode = UND_FONT; + wgs->bold_colours = + conf_get_int(wgs->conf, CONF_bold_style) & BOLD_STYLE_COLOUR ? + true : false; + wgs->und_mode = UND_FONT; - font = conf_get_fontspec(conf, CONF_font); + font = conf_get_fontspec(wgs->conf, CONF_font); if (font->isbold) { fw_dontcare = FW_BOLD; fw_bold = FW_HEAVY; @@ -1439,48 +1460,48 @@ static void init_fonts(int pick_width, int pick_height) fw_bold = FW_BOLD; } - hdc = GetDC(wgs.term_hwnd); + hdc = GetDC(wgs->term_hwnd); if (pick_height) - font_height = pick_height; + wgs->font_height = pick_height; else { - font_height = font->height; - if (font_height > 0) { - font_height = - -MulDiv(font_height, dpi_info.cur_dpi.y, 72); + wgs->font_height = font->height; + if (wgs->font_height > 0) { + wgs->font_height = -MulDiv( + wgs->font_height, wgs->dpi_info.cur_dpi.y, 72); } } - font_width = pick_width; + wgs->font_width = pick_width; - quality = conf_get_int(conf, CONF_font_quality); -#define f(i,c,w,u) \ - fonts[i] = CreateFont (font_height, font_width, 0, 0, w, false, u, false, \ - c, OUT_DEFAULT_PRECIS, \ - CLIP_DEFAULT_PRECIS, FONT_QUALITY(quality), \ - FIXED_PITCH | FF_DONTCARE, font->name) + quality = conf_get_int(wgs->conf, CONF_font_quality); +#define f(i,c,w,u) \ + wgs->fonts[i] = CreateFont( \ + wgs->font_height, wgs->font_width, 0, 0, w, false, u, false, c, \ + OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, FONT_QUALITY(quality), \ + FIXED_PITCH | FF_DONTCARE, font->name) f(FONT_NORMAL, font->charset, fw_dontcare, false); - SelectObject(hdc, fonts[FONT_NORMAL]); + SelectObject(hdc, wgs->fonts[FONT_NORMAL]); GetTextMetrics(hdc, &tm); if (GetOutlineTextMetrics(hdc, sizeof(otm), &otm)) - font_strikethrough_y = tm.tmAscent - otm.otmsStrikeoutPosition; + wgs->font_strikethrough_y = tm.tmAscent - otm.otmsStrikeoutPosition; else - font_strikethrough_y = tm.tmAscent - (tm.tmAscent * 3 / 8); + wgs->font_strikethrough_y = tm.tmAscent - (tm.tmAscent * 3 / 8); - GetObject(fonts[FONT_NORMAL], sizeof(LOGFONT), &lfont); + GetObject(wgs->fonts[FONT_NORMAL], sizeof(LOGFONT), &wgs->lfont); /* Note that the TMPF_FIXED_PITCH bit is defined upside down :-( */ if (!(tm.tmPitchAndFamily & TMPF_FIXED_PITCH)) { - font_varpitch = false; - font_dualwidth = (tm.tmAveCharWidth != tm.tmMaxCharWidth); + wgs->font_varpitch = false; + wgs->font_dualwidth = (tm.tmAveCharWidth != tm.tmMaxCharWidth); } else { - font_varpitch = true; - font_dualwidth = true; + wgs->font_varpitch = true; + wgs->font_dualwidth = true; } if (pick_width == 0 || pick_height == 0) { - font_height = tm.tmHeight; - font_width = get_font_width(hdc, &tm); + wgs->font_height = tm.tmHeight; + wgs->font_width = get_font_width(wgs, hdc, &tm); } #ifdef RDB_DEBUG_PATCH @@ -1495,15 +1516,15 @@ static void init_fonts(int pick_width, int pick_height) /* !!! Yes the next line is right */ if (cset == OEM_CHARSET) - ucsdata.font_codepage = GetOEMCP(); + wgs->ucsdata.font_codepage = GetOEMCP(); else if (TranslateCharsetInfo ((DWORD *)(ULONG_PTR)cset, &info, TCI_SRCCHARSET)) - ucsdata.font_codepage = info.ciACP; + wgs->ucsdata.font_codepage = info.ciACP; else - ucsdata.font_codepage = -1; + wgs->ucsdata.font_codepage = -1; - GetCPInfo(ucsdata.font_codepage, &cpinfo); - ucsdata.dbcs_screenfont = (cpinfo.MaxCharSize > 1); + GetCPInfo(wgs->ucsdata.font_codepage, &cpinfo); + wgs->ucsdata.dbcs_screenfont = (cpinfo.MaxCharSize > 1); } f(FONT_UNDERLINE, font->charset, fw_dontcare, true); @@ -1533,17 +1554,18 @@ static void init_fonts(int pick_width, int pick_height) COLORREF c; und_dc = CreateCompatibleDC(hdc); - und_bm = CreateCompatibleBitmap(hdc, font_width, font_height); + und_bm = CreateCompatibleBitmap( + hdc, wgs->font_width, wgs->font_height); und_oldbm = SelectObject(und_dc, und_bm); - SelectObject(und_dc, fonts[FONT_UNDERLINE]); + SelectObject(und_dc, wgs->fonts[FONT_UNDERLINE]); SetTextAlign(und_dc, TA_TOP | TA_LEFT | TA_NOUPDATECP); SetTextColor(und_dc, RGB(255, 255, 255)); SetBkColor(und_dc, RGB(0, 0, 0)); SetBkMode(und_dc, OPAQUE); ExtTextOut(und_dc, 0, 0, ETO_OPAQUE, NULL, " ", 1, NULL); gotit = false; - for (i = 0; i < font_height; i++) { - c = GetPixel(und_dc, font_width / 2, i); + for (i = 0; i < wgs->font_height; i++) { + c = GetPixel(und_dc, wgs->font_width / 2, i); if (c != RGB(0, 0, 0)) gotit = true; } @@ -1551,60 +1573,61 @@ static void init_fonts(int pick_width, int pick_height) DeleteObject(und_bm); DeleteDC(und_dc); if (!gotit) { - und_mode = UND_LINE; - DeleteObject(fonts[FONT_UNDERLINE]); - fonts[FONT_UNDERLINE] = 0; + wgs->und_mode = UND_LINE; + DeleteObject(wgs->fonts[FONT_UNDERLINE]); + wgs->fonts[FONT_UNDERLINE] = 0; } } - if (bold_font_mode == BOLD_FONT) { + if (wgs->bold_font_mode == BOLD_FONT) { f(FONT_BOLD, font->charset, fw_bold, false); } #undef f - descent = tm.tmAscent + 1; - if (descent >= font_height) - descent = font_height - 1; + wgs->descent = tm.tmAscent + 1; + if (wgs->descent >= wgs->font_height) + wgs->descent = wgs->font_height - 1; for (i = 0; i < 3; i++) { - if (fonts[i]) { - if (SelectObject(hdc, fonts[i]) && GetTextMetrics(hdc, &tm)) - fontsize[i] = get_font_width(hdc, &tm) + 256 * tm.tmHeight; + if (wgs->fonts[i]) { + if (SelectObject(hdc, wgs->fonts[i]) && GetTextMetrics(hdc, &tm)) + fontsize[i] = (get_font_width(wgs, hdc, &tm) + + 256 * tm.tmHeight); else fontsize[i] = -i; } else fontsize[i] = -i; } - ReleaseDC(wgs.term_hwnd, hdc); + ReleaseDC(wgs->term_hwnd, hdc); if (trust_icon != INVALID_HANDLE_VALUE) { DestroyIcon(trust_icon); } trust_icon = LoadImage(hinst, MAKEINTRESOURCE(IDI_MAINICON), - IMAGE_ICON, font_width*2, font_height, + IMAGE_ICON, wgs->font_width*2, wgs->font_height, LR_DEFAULTCOLOR); if (fontsize[FONT_UNDERLINE] != fontsize[FONT_NORMAL]) { - und_mode = UND_LINE; - DeleteObject(fonts[FONT_UNDERLINE]); - fonts[FONT_UNDERLINE] = 0; + wgs->und_mode = UND_LINE; + DeleteObject(wgs->fonts[FONT_UNDERLINE]); + wgs->fonts[FONT_UNDERLINE] = 0; } - if (bold_font_mode == BOLD_FONT && + if (wgs->bold_font_mode == BOLD_FONT && fontsize[FONT_BOLD] != fontsize[FONT_NORMAL]) { - bold_font_mode = BOLD_SHADOW; - DeleteObject(fonts[FONT_BOLD]); - fonts[FONT_BOLD] = 0; + wgs->bold_font_mode = BOLD_SHADOW; + DeleteObject(wgs->fonts[FONT_BOLD]); + wgs->fonts[FONT_BOLD] = 0; } - fontflag[0] = true; - fontflag[1] = true; - fontflag[2] = true; + wgs->fontflag[0] = true; + wgs->fontflag[1] = true; + wgs->fontflag[2] = true; - init_ucs(conf, &ucsdata); + init_ucs(wgs->conf, &wgs->ucsdata); } -static void another_font(int fontno) +static void another_font(WinGuiSeat *wgs, int fontno) { int basefont; int fw_dontcare, fw_bold, quality; @@ -1613,14 +1636,14 @@ static void another_font(int fontno) char *s; FontSpec *font; - if (fontno < 0 || fontno >= FONT_MAXNO || fontflag[fontno]) + if (fontno < 0 || fontno >= FONT_MAXNO || wgs->fontflag[fontno]) return; basefont = (fontno & ~(FONT_BOLDUND)); - if (basefont != fontno && !fontflag[basefont]) - another_font(basefont); + if (basefont != fontno && !wgs->fontflag[basefont]) + another_font(wgs, basefont); - font = conf_get_fontspec(conf, CONF_font); + font = conf_get_fontspec(wgs->conf, CONF_font); if (font->isbold) { fw_dontcare = FW_BOLD; @@ -1634,7 +1657,7 @@ static void another_font(int fontno) w = fw_dontcare; u = false; s = font->name; - x = font_width; + x = wgs->font_width; if (fontno & FONT_WIDE) x *= 2; @@ -1647,25 +1670,25 @@ static void another_font(int fontno) if (fontno & FONT_UNDERLINE) u = true; - quality = conf_get_int(conf, CONF_font_quality); + quality = conf_get_int(wgs->conf, CONF_font_quality); - fonts[fontno] = - CreateFont(font_height * (1 + !!(fontno & FONT_HIGH)), x, 0, 0, w, + wgs->fonts[fontno] = + CreateFont(wgs->font_height * (1 + !!(fontno & FONT_HIGH)), x, 0, 0, w, false, u, false, c, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, FONT_QUALITY(quality), DEFAULT_PITCH | FF_DONTCARE, s); - fontflag[fontno] = true; + wgs->fontflag[fontno] = true; } -static void deinit_fonts(void) +static void deinit_fonts(WinGuiSeat *wgs) { int i; for (i = 0; i < FONT_MAXNO; i++) { - if (fonts[i]) - DeleteObject(fonts[i]); - fonts[i] = 0; - fontflag[i] = false; + if (wgs->fonts[i]) + DeleteObject(wgs->fonts[i]); + wgs->fonts[i] = 0; + wgs->fontflag[i] = false; } if (trust_icon != INVALID_HANDLE_VALUE) { @@ -1676,40 +1699,41 @@ static void deinit_fonts(void) static void wintw_request_resize(TermWin *tw, int w, int h) { + WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin); const struct BackendVtable *vt; int width, height; - int resize_action = conf_get_int(conf, CONF_resize_action); + int resize_action = conf_get_int(wgs->conf, CONF_resize_action); bool deny_resize = false; /* Suppress server-originated resizing attempts if local resizing * is disabled entirely, or if it's supposed to change * rows/columns but the window is maximised. */ if (resize_action == RESIZE_DISABLED - || (resize_action == RESIZE_TERM && IsZoomed(wgs.term_hwnd))) { + || (resize_action == RESIZE_TERM && IsZoomed(wgs->term_hwnd))) { deny_resize = true; } vt = backend_vt_from_proto(be_default_protocol); if (vt && vt->flags & BACKEND_RESIZE_FORBIDDEN) deny_resize = true; - if (h == term->rows && w == term->cols) deny_resize = true; + if (h == wgs->term->rows && w == wgs->term->cols) deny_resize = true; /* We still need to acknowledge a suppressed resize attempt. */ if (deny_resize) { - term_resize_request_completed(term); + term_resize_request_completed(wgs->term); return; } /* Sanity checks ... */ { RECT ss; - if (get_fullscreen_rect(&ss)) { + if (get_fullscreen_rect(wgs, &ss)) { /* Make sure the values aren't too big */ - width = (ss.right - ss.left - extra_width) / 4; - height = (ss.bottom - ss.top - extra_height) / 6; + width = (ss.right - ss.left - wgs->extra_width) / 4; + height = (ss.bottom - ss.top - wgs->extra_height) / 6; if (w > width || h > height) { - term_resize_request_completed(term); + term_resize_request_completed(wgs->term); return; } if (w < 15) @@ -1719,11 +1743,11 @@ static void wintw_request_resize(TermWin *tw, int w, int h) } } - if (resize_action != RESIZE_FONT && !IsZoomed(wgs.term_hwnd)) { - width = extra_width + font_width * w; - height = extra_height + font_height * h; + if (resize_action != RESIZE_FONT && !IsZoomed(wgs->term_hwnd)) { + width = wgs->extra_width + wgs->font_width * w; + height = wgs->extra_height + wgs->font_height * h; - SetWindowPos(wgs.term_hwnd, NULL, 0, 0, width, height, + SetWindowPos(wgs->term_hwnd, NULL, 0, 0, width, height, SWP_NOACTIVATE | SWP_NOCOPYBITS | SWP_NOMOVE | SWP_NOZORDER); } else { @@ -1732,34 +1756,35 @@ static void wintw_request_resize(TermWin *tw, int w, int h) * terminal the new size immediately, so that reset_window * will know what to do. */ - term_size(term, h, w, conf_get_int(conf, CONF_savelines)); - reset_window(0); + term_size(wgs->term, h, w, conf_get_int(wgs->conf, CONF_savelines)); + reset_window(wgs, 0); } - term_resize_request_completed(term); - InvalidateRect(wgs.term_hwnd, NULL, true); + term_resize_request_completed(wgs->term); + InvalidateRect(wgs->term_hwnd, NULL, true); } -static void recompute_window_offset(void) +static void recompute_window_offset(WinGuiSeat *wgs) { RECT cr; - GetClientRect(wgs.term_hwnd, &cr); + GetClientRect(wgs->term_hwnd, &cr); int win_width = cr.right - cr.left; int win_height = cr.bottom - cr.top; - int new_offset_width = (win_width-font_width*term->cols)/2; - int new_offset_height = (win_height-font_height*term->rows)/2; + int new_offset_width = (win_width-wgs->font_width*wgs->term->cols)/2; + int new_offset_height = (win_height-wgs->font_height*wgs->term->rows)/2; - if (offset_width != new_offset_width || - offset_height != new_offset_height) { - offset_width = new_offset_width; - offset_height = new_offset_height; - InvalidateRect(wgs.term_hwnd, NULL, true); + if (wgs->offset_width != new_offset_width || + wgs->offset_height != new_offset_height) { + wgs->offset_width = new_offset_width; + wgs->offset_height = new_offset_height; + InvalidateRect(wgs->term_hwnd, NULL, true); } } -static void reset_window(int reinit) { +static void reset_window(WinGuiSeat *wgs, int reinit) +{ /* * This function decides how to resize or redraw when the * user changes something. @@ -1775,14 +1800,14 @@ static void reset_window(int reinit) { #endif /* Current window sizes ... */ - GetWindowRect(wgs.term_hwnd, &wr); - GetClientRect(wgs.term_hwnd, &cr); + GetWindowRect(wgs->term_hwnd, &wr); + GetClientRect(wgs->term_hwnd, &cr); win_width = cr.right - cr.left; win_height = cr.bottom - cr.top; - resize_action = conf_get_int(conf, CONF_resize_action); - window_border = conf_get_int(conf, CONF_window_border); + resize_action = conf_get_int(wgs->conf, CONF_resize_action); + window_border = conf_get_int(wgs->conf, CONF_window_border); if (resize_action == RESIZE_DISABLED) reinit = 2; @@ -1792,8 +1817,8 @@ static void reset_window(int reinit) { #ifdef RDB_DEBUG_PATCH debug("reset_window() -- Forced deinit\n"); #endif - deinit_fonts(); - init_fonts(0,0); + deinit_fonts(wgs); + init_fonts(wgs, 0, 0); } /* Oh, looks like we're minimised */ @@ -1802,44 +1827,56 @@ static void reset_window(int reinit) { /* Is the window out of position ? */ if (!reinit) { - recompute_window_offset(); + recompute_window_offset(wgs); #ifdef RDB_DEBUG_PATCH debug("reset_window() -> Reposition terminal\n"); #endif } - if (IsZoomed(wgs.term_hwnd)) { + if (IsZoomed(wgs->term_hwnd)) { /* We're fullscreen, this means we must not change the size of * the window so it's the font size or the terminal itself. */ - extra_width = wr.right - wr.left - cr.right + cr.left; - extra_height = wr.bottom - wr.top - cr.bottom + cr.top; + wgs->extra_width = wr.right - wr.left - cr.right + cr.left; + wgs->extra_height = wr.bottom - wr.top - cr.bottom + cr.top; if (resize_action != RESIZE_TERM) { - if (font_width != win_width/term->cols || - font_height != win_height/term->rows) { - deinit_fonts(); - init_fonts(win_width/term->cols, win_height/term->rows); - offset_width = (win_width-font_width*term->cols)/2; - offset_height = (win_height-font_height*term->rows)/2; - InvalidateRect(wgs.term_hwnd, NULL, true); + if (wgs->font_width != win_width/wgs->term->cols || + wgs->font_height != win_height/wgs->term->rows) { + int fw = (win_width - 2*window_border) / wgs->term->cols; + int fh = (win_height - 2*window_border) / wgs->term->rows; + /* In case that subtraction made the font size go + * negative in an edge case, bound it below by 1 */ + if (fw < 1) fw = 1; + if (fh < 1) fh = 1; + deinit_fonts(wgs); + init_fonts(wgs, fw, fh); + wgs->offset_width = + (win_width - wgs->font_width*wgs->term->cols) / 2; + wgs->offset_height = + (win_height - wgs->font_height*wgs->term->rows) / 2; + InvalidateRect(wgs->term_hwnd, NULL, true); #ifdef RDB_DEBUG_PATCH debug("reset_window() -> Z font resize to (%d, %d)\n", - font_width, font_height); + wgs->font_width, wgs->font_height); #endif } } else { - if (font_width * term->cols != win_width || - font_height * term->rows != win_height) { + if (wgs->font_width * wgs->term->cols != win_width || + wgs->font_height * wgs->term->rows != win_height) { /* Our only choice at this point is to change the * size of the terminal; Oh well. */ - term_size(term, win_height/font_height, win_width/font_width, - conf_get_int(conf, CONF_savelines)); - offset_width = (win_width-font_width*term->cols)/2; - offset_height = (win_height-font_height*term->rows)/2; - InvalidateRect(wgs.term_hwnd, NULL, true); + term_size(wgs->term, + (win_height - 2*window_border) / wgs->font_height, + (win_width - 2*window_border) / wgs->font_width, + conf_get_int(wgs->conf, CONF_savelines)); + wgs->offset_width = + (win_width - window_border - wgs->font_width*wgs->term->cols) / 2; + wgs->offset_height = + (win_height - window_border - wgs->font_height*wgs->term->rows) / 2; + InvalidateRect(wgs->term_hwnd, NULL, true); #ifdef RDB_DEBUG_PATCH debug("reset_window() -> Zoomed term_size\n"); #endif @@ -1852,28 +1889,31 @@ static void reset_window(int reinit) { if (reinit == 3 && p_GetSystemMetricsForDpi && p_AdjustWindowRectExForDpi) { RECT rect; rect.left = rect.top = 0; - rect.right = (font_width * term->cols); - if (conf_get_bool(conf, CONF_scrollbar)) + rect.right = (wgs->font_width * wgs->term->cols); + if (conf_get_bool(wgs->conf, CONF_scrollbar)) rect.right += p_GetSystemMetricsForDpi(SM_CXVSCROLL, - dpi_info.cur_dpi.x); - rect.bottom = (font_height * term->rows); + wgs->dpi_info.cur_dpi.x); + rect.bottom = (wgs->font_height * wgs->term->rows); p_AdjustWindowRectExForDpi( - &rect, GetWindowLongPtr(wgs.term_hwnd, GWL_STYLE), - FALSE, GetWindowLongPtr(wgs.term_hwnd, GWL_EXSTYLE), - dpi_info.cur_dpi.x); + &rect, GetWindowLongPtr(wgs->term_hwnd, GWL_STYLE), + FALSE, GetWindowLongPtr(wgs->term_hwnd, GWL_EXSTYLE), + wgs->dpi_info.cur_dpi.x); rect.right += (window_border * 2); rect.bottom += (window_border * 2); - OffsetRect(&dpi_info.new_wnd_rect, - ((dpi_info.new_wnd_rect.right - dpi_info.new_wnd_rect.left) - + OffsetRect(&wgs->dpi_info.new_wnd_rect, + ((wgs->dpi_info.new_wnd_rect.right - + wgs->dpi_info.new_wnd_rect.left) - (rect.right - rect.left)) / 2, - ((dpi_info.new_wnd_rect.bottom - dpi_info.new_wnd_rect.top) - + ((wgs->dpi_info.new_wnd_rect.bottom - + wgs->dpi_info.new_wnd_rect.top) - (rect.bottom - rect.top)) / 2); - SetWindowPos(wgs.term_hwnd, NULL, - dpi_info.new_wnd_rect.left, dpi_info.new_wnd_rect.top, + SetWindowPos(wgs->term_hwnd, NULL, + wgs->dpi_info.new_wnd_rect.left, + wgs->dpi_info.new_wnd_rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOZORDER); - InvalidateRect(wgs.term_hwnd, NULL, true); + InvalidateRect(wgs->term_hwnd, NULL, true); return; } @@ -1885,24 +1925,28 @@ static void reset_window(int reinit) { debug("reset_window() -> Forced re-init\n"); #endif - offset_width = offset_height = window_border; - extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2; - extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2; + wgs->offset_width = wgs->offset_height = window_border; + wgs->extra_width = + wr.right - wr.left - cr.right + cr.left + wgs->offset_width*2; + wgs->extra_height = + wr.bottom - wr.top - cr.bottom + cr.top + wgs->offset_height*2; - if (win_width != font_width*term->cols + offset_width*2 || - win_height != font_height*term->rows + offset_height*2) { + if (win_width != (wgs->font_width*wgs->term->cols + + wgs->offset_width*2) || + win_height != (wgs->font_height*wgs->term->rows + + wgs->offset_height*2)) { /* If this is too large windows will resize it to the maximum * allowed window size, we will then be back in here and resize * the font or terminal to fit. */ - SetWindowPos(wgs.term_hwnd, NULL, 0, 0, - font_width*term->cols + extra_width, - font_height*term->rows + extra_height, + SetWindowPos(wgs->term_hwnd, NULL, 0, 0, + wgs->font_width*wgs->term->cols + wgs->extra_width, + wgs->font_height*wgs->term->rows + wgs->extra_height, SWP_NOMOVE | SWP_NOZORDER); } - InvalidateRect(wgs.term_hwnd, NULL, true); + InvalidateRect(wgs->term_hwnd, NULL, true); return; } @@ -1913,42 +1957,50 @@ static void reset_window(int reinit) { if ((resize_action == RESIZE_TERM && reinit<=0) || (resize_action == RESIZE_EITHER && reinit<0) || reinit>0) { - offset_width = offset_height = window_border; - extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2; - extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2; - - if (win_width != font_width*term->cols + offset_width*2 || - win_height != font_height*term->rows + offset_height*2) { - - static RECT ss; + wgs->offset_width = wgs->offset_height = window_border; + wgs->extra_width = + wr.right - wr.left - cr.right + cr.left + wgs->offset_width*2; + wgs->extra_height = + wr.bottom - wr.top - cr.bottom + cr.top + wgs->offset_height*2; + + if (win_width != (wgs->font_width*wgs->term->cols + + wgs->offset_width*2) || + win_height != (wgs->font_height*wgs->term->rows + + wgs->offset_height*2)) { + + RECT ss; int width, height; - get_fullscreen_rect(&ss); + get_fullscreen_rect(wgs, &ss); - width = (ss.right - ss.left - extra_width) / font_width; - height = (ss.bottom - ss.top - extra_height) / font_height; + width = (ss.right - ss.left - wgs->extra_width) / wgs->font_width; + height = (ss.bottom - ss.top - wgs->extra_height)/wgs->font_height; /* Grrr too big */ - if ( term->rows > height || term->cols > width ) { + if ( wgs->term->rows > height || wgs->term->cols > width ) { if (resize_action == RESIZE_EITHER) { /* Make the font the biggest we can */ - if (term->cols > width) - font_width = (ss.right - ss.left - extra_width) - / term->cols; - if (term->rows > height) - font_height = (ss.bottom - ss.top - extra_height) - / term->rows; - - deinit_fonts(); - init_fonts(font_width, font_height); - - width = (ss.right - ss.left - extra_width) / font_width; - height = (ss.bottom - ss.top - extra_height) / font_height; + if (wgs->term->cols > width) + wgs->font_width = + (ss.right - ss.left - wgs->extra_width) / + wgs->term->cols; + if (wgs->term->rows > height) + wgs->font_height = + (ss.bottom - ss.top - wgs->extra_height) / + wgs->term->rows; + + deinit_fonts(wgs); + init_fonts(wgs, wgs->font_width, wgs->font_height); + + width = (ss.right - ss.left - wgs->extra_width) / + wgs->font_width; + height = (ss.bottom - ss.top - wgs->extra_height) / + wgs->font_height; } else { - if ( height > term->rows ) height = term->rows; - if ( width > term->cols ) width = term->cols; - term_size(term, height, width, - conf_get_int(conf, CONF_savelines)); + if ( height > wgs->term->rows ) height = wgs->term->rows; + if ( width > wgs->term->cols ) width = wgs->term->cols; + term_size(wgs->term, height, width, + conf_get_int(wgs->conf, CONF_savelines)); #ifdef RDB_DEBUG_PATCH debug("reset_window() -> term resize to (%d,%d)\n", height, width); @@ -1956,16 +2008,16 @@ static void reset_window(int reinit) { } } - SetWindowPos(wgs.term_hwnd, NULL, 0, 0, - font_width*term->cols + extra_width, - font_height*term->rows + extra_height, + SetWindowPos(wgs->term_hwnd, NULL, 0, 0, + wgs->font_width*wgs->term->cols + wgs->extra_width, + wgs->font_height*wgs->term->rows + wgs->extra_height, SWP_NOMOVE | SWP_NOZORDER); - InvalidateRect(wgs.term_hwnd, NULL, true); + InvalidateRect(wgs->term_hwnd, NULL, true); #ifdef RDB_DEBUG_PATCH debug("reset_window() -> window resize to (%d,%d)\n", - font_width*term->cols + extra_width, - font_height*term->rows + extra_height); + wgs->font_width*term->cols + wgs->extra_width, + wgs->font_height*term->rows + wgs->extra_height); #endif } return; @@ -1973,87 +2025,97 @@ static void reset_window(int reinit) { /* We're allowed to or must change the font but do we want to ? */ - if (font_width != (win_width-window_border*2)/term->cols || - font_height != (win_height-window_border*2)/term->rows) { + if (wgs->font_width != (win_width-window_border*2)/wgs->term->cols || + wgs->font_height != (win_height-window_border*2)/wgs->term->rows) { - deinit_fonts(); - init_fonts((win_width-window_border*2)/term->cols, - (win_height-window_border*2)/term->rows); - offset_width = (win_width-font_width*term->cols)/2; - offset_height = (win_height-font_height*term->rows)/2; + deinit_fonts(wgs); + init_fonts(wgs, (win_width-window_border*2)/wgs->term->cols, + (win_height-window_border*2)/wgs->term->rows); + wgs->offset_width = (win_width-wgs->font_width*wgs->term->cols)/2; + wgs->offset_height = (win_height-wgs->font_height*wgs->term->rows)/2; - extra_width = wr.right - wr.left - cr.right + cr.left +offset_width*2; - extra_height = wr.bottom - wr.top - cr.bottom + cr.top+offset_height*2; + wgs->extra_width = + wr.right - wr.left - cr.right + cr.left + wgs->offset_width*2; + wgs->extra_height = + wr.bottom - wr.top - cr.bottom + cr.top + wgs->offset_height*2; - InvalidateRect(wgs.term_hwnd, NULL, true); + InvalidateRect(wgs->term_hwnd, NULL, true); #ifdef RDB_DEBUG_PATCH debug("reset_window() -> font resize to (%d,%d)\n", - font_width, font_height); + wgs->font_width, wgs->font_height); #endif } } -static void set_input_locale(HKL kl) +static void set_input_locale(WinGuiSeat *wgs, HKL kl) { char lbuf[20]; GetLocaleInfo(LOWORD(kl), LOCALE_IDEFAULTANSICODEPAGE, lbuf, sizeof(lbuf)); - kbd_codepage = atoi(lbuf); + wgs->kbd_codepage = atoi(lbuf); } -static void click(Mouse_Button b, int x, int y, +static void click(WinGuiSeat *wgs, Mouse_Button b, int x, int y, bool shift, bool ctrl, bool alt) { int thistime = GetMessageTime(); - if (send_raw_mouse && - !(shift && conf_get_bool(conf, CONF_mouse_override))) { - lastbtn = MBT_NOTHING; - term_mouse(term, b, translate_button(b), MA_CLICK, + if (wgs->send_raw_mouse && + !(shift && conf_get_bool(wgs->conf, CONF_mouse_override))) { + wgs->lastbtn = MBT_NOTHING; + term_mouse(wgs->term, b, translate_button(wgs, b), MA_CLICK, x, y, shift, ctrl, alt); return; } - if (lastbtn == b && thistime - lasttime < dbltime) { - lastact = (lastact == MA_CLICK ? MA_2CLK : - lastact == MA_2CLK ? MA_3CLK : - lastact == MA_3CLK ? MA_CLICK : MA_NOTHING); + if (wgs->lastbtn == b && thistime - wgs->lasttime < wgs->dbltime) { + wgs->lastact = (wgs->lastact == MA_CLICK ? MA_2CLK : + wgs->lastact == MA_2CLK ? MA_3CLK : + wgs->lastact == MA_3CLK ? MA_CLICK : MA_NOTHING); } else { - lastbtn = b; - lastact = MA_CLICK; + wgs->lastbtn = b; + wgs->lastact = MA_CLICK; } - if (lastact != MA_NOTHING) - term_mouse(term, b, translate_button(b), lastact, + if (wgs->lastact != MA_NOTHING) + term_mouse(wgs->term, b, translate_button(wgs, b), wgs->lastact, x, y, shift, ctrl, alt); - lasttime = thistime; + wgs->lasttime = thistime; } /* * Translate a raw mouse button designation (LEFT, MIDDLE, RIGHT) * into a cooked one (SELECT, EXTEND, PASTE). */ -static Mouse_Button translate_button(Mouse_Button button) +static Mouse_Button translate_button(WinGuiSeat *wgs, Mouse_Button button) { if (button == MBT_LEFT) return MBT_SELECT; if (button == MBT_MIDDLE) - return conf_get_int(conf, CONF_mouse_is_xterm) == 1 ? + return conf_get_int(wgs->conf, CONF_mouse_is_xterm) == MOUSE_XTERM ? MBT_PASTE : MBT_EXTEND; if (button == MBT_RIGHT) - return conf_get_int(conf, CONF_mouse_is_xterm) == 1 ? + return conf_get_int(wgs->conf, CONF_mouse_is_xterm) == MOUSE_XTERM ? MBT_EXTEND : MBT_PASTE; return 0; /* shouldn't happen */ } -static void show_mouseptr(bool show) +static void show_mouseptr(WinGuiSeat *wgs, bool show) { /* NB that the counter in ShowCursor() is also frobbed by * update_mouse_pointer() */ static bool cursor_visible = true; - if (!conf_get_bool(conf, CONF_hide_mouseptr)) - show = true; /* override if this feature disabled */ + if (wgs) { + if (!conf_get_bool(wgs->conf, CONF_hide_mouseptr)) + show = true; /* hiding mouse pointer disabled in Conf */ + } else { + /* + * You can pass wgs==NULL if you want to _show_ the pointer + * rather than hiding it, because that's never disallowed. + */ + assert(show); + } if (cursor_visible && !show) ShowCursor(false); else if (!cursor_visible && show) @@ -2074,29 +2136,28 @@ static bool is_alt_pressed(void) return false; } -static bool resizing; - static void exit_callback(void *vctx) { + WinGuiSeat *wgs = (WinGuiSeat *)vctx; int exitcode, close_on_exit; - if (!session_closed && - (exitcode = backend_exitcode(backend)) >= 0) { - close_on_exit = conf_get_int(conf, CONF_close_on_exit); + if (!wgs->session_closed && + (exitcode = backend_exitcode(wgs->backend)) >= 0) { + close_on_exit = conf_get_int(wgs->conf, CONF_close_on_exit); /* Abnormal exits will already have set session_closed and taken * appropriate action. */ if (close_on_exit == FORCE_ON || (close_on_exit == AUTO && exitcode != INT_MAX)) { PostQuitMessage(0); } else { - queue_toplevel_callback(close_session, NULL); - session_closed = true; + queue_toplevel_callback(close_session, wgs); + wgs->session_closed = true; /* exitcode == INT_MAX indicates that the connection was closed * by a fatal error, so an error box will be coming our way and * we should not generate this informational one. */ if (exitcode != INT_MAX) { - show_mouseptr(true); - MessageBox(wgs.term_hwnd, "Connection closed by remote host", + show_mouseptr(wgs, true); + MessageBox(wgs->term_hwnd, "Connection closed by remote host", appname, MB_OK | MB_ICONINFORMATION); } } @@ -2105,68 +2166,54 @@ static void exit_callback(void *vctx) static void win_seat_notify_remote_exit(Seat *seat) { - queue_toplevel_callback(exit_callback, NULL); -} - -void timer_change_notify(unsigned long next) -{ - unsigned long now = GETTICKCOUNT(); - long ticks; - if (now - next < INT_MAX) - ticks = 0; - else - ticks = next - now; - KillTimer(wgs.term_hwnd, TIMING_TIMER_ID); - SetTimer(wgs.term_hwnd, TIMING_TIMER_ID, ticks, NULL); - timing_next_time = next; + WinGuiSeat *wgs = container_of(seat, WinGuiSeat, seat); + queue_toplevel_callback(exit_callback, wgs); } -static void conf_cache_data(void) +static void conf_cache_data(WinGuiSeat *wgs) { /* Cache some items from conf to speed lookups in very hot code */ - cursor_type = conf_get_int(conf, CONF_cursor_type); - vtmode = conf_get_int(conf, CONF_vtmode); + wgs->cursor_type = conf_get_int(wgs->conf, CONF_cursor_type); + wgs->vtmode = conf_get_int(wgs->conf, CONF_vtmode); } static const int clips_system[] = { CLIP_SYSTEM }; -static HDC make_hdc(void) +static HDC make_hdc(WinGuiSeat *wgs) { HDC hdc; - if (!wgs.term_hwnd) + if (!wgs->term_hwnd) return NULL; - hdc = GetDC(wgs.term_hwnd); + hdc = GetDC(wgs->term_hwnd); if (!hdc) return NULL; - SelectPalette(hdc, pal, false); + SelectPalette(hdc, wgs->pal, false); return hdc; } -static void free_hdc(HDC hdc) +static void free_hdc(WinGuiSeat *wgs, HDC hdc) { - assert(wgs.term_hwnd); + assert(wgs->term_hwnd); SelectPalette(hdc, GetStockObject(DEFAULT_PALETTE), false); - ReleaseDC(wgs.term_hwnd, hdc); + ReleaseDC(wgs->term_hwnd, hdc); } -static bool need_backend_resize = false; - -static void wm_size_resize_term(LPARAM lParam, bool border) +static void wm_size_resize_term(WinGuiSeat *wgs, LPARAM lParam) { int width = LOWORD(lParam); int height = HIWORD(lParam); - int border_size = border ? conf_get_int(conf, CONF_window_border) : 0; + int border_size = conf_get_int(wgs->conf, CONF_window_border); - int w = (width - border_size*2) / font_width; - int h = (height - border_size*2) / font_height; + int w = (width - border_size*2) / wgs->font_width; + int h = (height - border_size*2) / wgs->font_height; if (w < 1) w = 1; if (h < 1) h = 1; - if (resizing) { + if (wgs->resizing) { /* * If we're in the middle of an interactive resize, we don't * call term_size. This means that, firstly, the user can drag @@ -2176,12 +2223,12 @@ static void wm_size_resize_term(LPARAM lParam, bool border) * resizing drag, so we don't spam the server with huge * numbers of resize events. */ - need_backend_resize = true; - conf_set_int(conf, CONF_height, h); - conf_set_int(conf, CONF_width, w); + wgs->need_backend_resize = true; + conf_set_int(wgs->conf, CONF_height, h); + conf_set_int(wgs->conf, CONF_width, w); } else { - term_size(term, h, w, - conf_get_int(conf, CONF_savelines)); + term_size(wgs->term, h, w, + conf_get_int(wgs->conf, CONF_savelines)); } } @@ -2189,38 +2236,24 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { HDC hdc; - static bool ignore_clip = false; - static bool fullscr_on_max = false; - static bool processed_resize = false; - static bool in_scrollbar_loop = false; - static UINT last_mousemove = 0; int resize_action; + WinGuiSeat *wgs = (WinGuiSeat *)GetWindowLongPtr(hwnd, GWLP_USERDATA); switch (message) { - case WM_TIMER: - if ((UINT_PTR)wParam == TIMING_TIMER_ID) { - unsigned long next; - - KillTimer(hwnd, TIMING_TIMER_ID); - if (run_timers(timing_next_time, &next)) { - timer_change_notify(next); - } else { - } - } - return 0; case WM_CREATE: break; case WM_CLOSE: { char *title, *msg, *additional = NULL; - show_mouseptr(true); + show_mouseptr(wgs, true); title = dupprintf("%s 退出确认", appname); - if (backend && backend->vt->close_warn_text) { - additional = backend->vt->close_warn_text(backend); + if (wgs->backend && wgs->backend->vt->close_warn_text) { + additional = wgs->backend->vt->close_warn_text(wgs->backend); } msg = dupprintf("确定要关闭本会话么?%s%s", additional ? "\n" : "", additional ? additional : ""); - if (session_closed || !conf_get_bool(conf, CONF_warn_on_close) || + if (wgs->session_closed || + !conf_get_bool(wgs->conf, CONF_warn_on_close) || MessageBox(hwnd, msg, title, MB_ICONWARNING | MB_OKCANCEL | MB_DEFBUTTON1) == IDOK) @@ -2231,16 +2264,16 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, return 0; } case WM_DESTROY: - show_mouseptr(true); + show_mouseptr(wgs, true); PostQuitMessage(0); return 0; case WM_INITMENUPOPUP: - if ((HMENU)wParam == savedsess_menu) { + if ((HMENU)wParam == wgs->savedsess_menu) { /* About to pop up Saved Sessions sub-menu. * Refresh the session list. */ get_sesslist(&sesslist, false); /* free */ get_sesslist(&sesslist, true); - update_savedsess_menu(); + update_savedsess_menu(wgs); return 0; } break; @@ -2256,10 +2289,10 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, * when we're re-entered from that loop, scroll events * within an interactive scrollbar-drag can be handled * differently. */ - in_scrollbar_loop = true; + wgs->in_scrollbar_loop = true; LRESULT result = sw_DefWindowProc( hwnd, message, wParam, lParam); - in_scrollbar_loop = false; + wgs->in_scrollbar_loop = false; return result; } break; @@ -2293,7 +2326,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, int size; serbuf = strbuf_new(); - conf_serialise(BinarySink_UPCAST(serbuf), conf); + conf_serialise(BinarySink_UPCAST(serbuf), wgs->conf); size = serbuf->len; sa.nLength = sizeof(sa); @@ -2350,10 +2383,10 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, break; } case IDM_RESTART: - if (!backend) { - lp_eventlog(&wgs.logpolicy, "----- Session restarted -----"); - term_pwron(term, false); - start_backend(); + if (!wgs->backend) { + lp_eventlog(&wgs->logpolicy, "----- Session restarted -----"); + term_pwron(wgs->term, false); + start_backend(wgs); } break; @@ -2362,30 +2395,31 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, int init_lvl = 1; bool reconfig_result; - if (reconfiguring) + if (wgs->reconfiguring) break; else - reconfiguring = true; + wgs->reconfiguring = true; - term_pre_reconfig(term, conf); - prev_conf = conf_copy(conf); + term_pre_reconfig(wgs->term, wgs->conf); + prev_conf = conf_copy(wgs->conf); reconfig_result = do_reconfig( - hwnd, conf, backend ? backend_cfg_info(backend) : 0); - reconfiguring = false; + hwnd, wgs->conf, + wgs->backend ? backend_cfg_info(wgs->backend) : 0); + wgs->reconfiguring = false; if (!reconfig_result) { conf_free(prev_conf); break; } - conf_cache_data(); + conf_cache_data(wgs); - resize_action = conf_get_int(conf, CONF_resize_action); + resize_action = conf_get_int(wgs->conf, CONF_resize_action); { /* Disable full-screen if resizing forbidden */ int i; - for (i = 0; i < lenof(popup_menus); i++) - EnableMenuItem(popup_menus[i].menu, IDM_FULLSCREEN, + for (i = 0; i < lenof(wgs->popup_menus); i++) + EnableMenuItem(wgs->popup_menus[i].menu, IDM_FULLSCREEN, MF_BYCOMMAND | (resize_action == RESIZE_DISABLED ? MF_GRAYED : MF_ENABLED)); @@ -2395,51 +2429,51 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, } /* Pass new config data to the logging module */ - log_reconfig(logctx, conf); + log_reconfig(wgs->logctx, wgs->conf); - sfree(logpal); + sfree(wgs->logpal); /* * Flush the line discipline's edit buffer in the * case where local editing has just been disabled. */ - if (ldisc) { - ldisc_configure(ldisc, conf); - ldisc_echoedit_update(ldisc); + if (wgs->ldisc) { + ldisc_configure(wgs->ldisc, wgs->conf); + ldisc_echoedit_update(wgs->ldisc); } - if (conf_get_bool(conf, CONF_system_colour) != + if (conf_get_bool(wgs->conf, CONF_system_colour) != conf_get_bool(prev_conf, CONF_system_colour)) - term_notify_palette_changed(term); + term_notify_palette_changed(wgs->term); /* Pass new config data to the terminal */ - term_reconfig(term, conf); - setup_clipboards(term, conf); + term_reconfig(wgs->term, wgs->conf); + setup_clipboards(wgs->term, wgs->conf); /* Reinitialise the colour palette, in case the terminal * just read new settings out of Conf */ - if (pal) - DeleteObject(pal); - logpal = NULL; - pal = NULL; - init_palette(); + if (wgs->pal) + DeleteObject(wgs->pal); + wgs->logpal = NULL; + wgs->pal = NULL; + init_palette(wgs); /* Pass new config data to the back end */ - if (backend) - backend_reconfig(backend, conf); + if (wgs->backend) + backend_reconfig(wgs->backend, wgs->conf); /* Screen size changed ? */ - if (conf_get_int(conf, CONF_height) != + if (conf_get_int(wgs->conf, CONF_height) != conf_get_int(prev_conf, CONF_height) || - conf_get_int(conf, CONF_width) != + conf_get_int(wgs->conf, CONF_width) != conf_get_int(prev_conf, CONF_width) || - conf_get_int(conf, CONF_savelines) != + conf_get_int(wgs->conf, CONF_savelines) != conf_get_int(prev_conf, CONF_savelines) || resize_action == RESIZE_FONT || (resize_action == RESIZE_EITHER && IsZoomed(hwnd)) || resize_action == RESIZE_DISABLED) - term_size(term, conf_get_int(conf, CONF_height), - conf_get_int(conf, CONF_width), - conf_get_int(conf, CONF_savelines)); + term_size(wgs->term, conf_get_int(wgs->conf, CONF_height), + conf_get_int(wgs->conf, CONF_width), + conf_get_int(wgs->conf, CONF_savelines)); /* Enable or disable the scroll bar, etc */ { @@ -2448,9 +2482,9 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, GetWindowLongPtr(hwnd, GWL_EXSTYLE); nexflag = exflag; - if (conf_get_bool(conf, CONF_alwaysontop) != + if (conf_get_bool(wgs->conf, CONF_alwaysontop) != conf_get_bool(prev_conf, CONF_alwaysontop)) { - if (conf_get_bool(conf, CONF_alwaysontop)) { + if (conf_get_bool(wgs->conf, CONF_alwaysontop)) { nexflag |= WS_EX_TOPMOST; SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); @@ -2460,13 +2494,13 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, SWP_NOMOVE | SWP_NOSIZE); } } - if (conf_get_bool(conf, CONF_sunken_edge)) + if (conf_get_bool(wgs->conf, CONF_sunken_edge)) nexflag |= WS_EX_CLIENTEDGE; else nexflag &= ~(WS_EX_CLIENTEDGE); nflg = flag; - if (conf_get_bool(conf, is_full_screen() ? + if (conf_get_bool(wgs->conf, is_full_screen(wgs) ? CONF_scrollbar_in_fullscreen : CONF_scrollbar)) nflg |= WS_VSCROLL; @@ -2474,7 +2508,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, nflg &= ~WS_VSCROLL; if (resize_action == RESIZE_DISABLED || - is_full_screen()) + is_full_screen(wgs)) nflg &= ~WS_THICKFRAME; else nflg |= WS_THICKFRAME; @@ -2506,21 +2540,21 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, } { - FontSpec *font = conf_get_fontspec(conf, CONF_font); + FontSpec *font = conf_get_fontspec(wgs->conf, CONF_font); FontSpec *prev_font = conf_get_fontspec(prev_conf, CONF_font); if (!strcmp(font->name, prev_font->name) || - !strcmp(conf_get_str(conf, CONF_line_codepage), + !strcmp(conf_get_str(wgs->conf, CONF_line_codepage), conf_get_str(prev_conf, CONF_line_codepage)) || font->isbold != prev_font->isbold || font->height != prev_font->height || font->charset != prev_font->charset || - conf_get_int(conf, CONF_font_quality) != + conf_get_int(wgs->conf, CONF_font_quality) != conf_get_int(prev_conf, CONF_font_quality) || - conf_get_int(conf, CONF_vtmode) != + conf_get_int(wgs->conf, CONF_vtmode) != conf_get_int(prev_conf, CONF_vtmode) || - conf_get_int(conf, CONF_bold_style) != + conf_get_int(wgs->conf, CONF_bold_style) != conf_get_int(prev_conf, CONF_bold_style) || resize_action == RESIZE_DISABLED || resize_action == RESIZE_EITHER || @@ -2530,27 +2564,27 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, } InvalidateRect(hwnd, NULL, true); - reset_window(init_lvl); + reset_window(wgs, init_lvl); conf_free(prev_conf); break; } case IDM_COPYALL: - term_copyall(term, clips_system, lenof(clips_system)); + term_copyall(wgs->term, clips_system, lenof(clips_system)); break; case IDM_COPY: - term_request_copy(term, clips_system, lenof(clips_system)); + term_request_copy(wgs->term, clips_system, lenof(clips_system)); break; case IDM_PASTE: - term_request_paste(term, CLIP_SYSTEM); + term_request_paste(wgs->term, CLIP_SYSTEM); break; case IDM_CLRSB: - term_clrsb(term); + term_clrsb(wgs->term); break; case IDM_RESET: - term_pwron(term, true); - if (ldisc) - ldisc_echoedit_update(ldisc); + term_pwron(wgs->term, true); + if (wgs->ldisc) + ldisc_echoedit_update(wgs->ldisc); break; case IDM_ABOUT: showabout(hwnd); @@ -2563,7 +2597,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, * We get this if the System menu has been activated * using the mouse. */ - show_mouseptr(true); + show_mouseptr(wgs, true); break; case SC_KEYMENU: /* @@ -2574,12 +2608,12 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, * the menu up_ rather than just sitting there in * `ready to appear' state. */ - show_mouseptr(true); /* make sure pointer is visible */ - if( lParam == 0 ) + show_mouseptr(wgs, true); /* make sure pointer is visible */ + if (lParam == 0) PostMessage(hwnd, WM_CHAR, ' ', 0); break; case IDM_FULLSCREEN: - flip_full_screen(); + flip_full_screen(wgs); break; default: if (wParam >= IDM_SAVED_MIN && wParam < IDM_SAVED_MAX) { @@ -2592,11 +2626,11 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, * which would cause us to reference invalid memory * and crash. Perhaps I'm just too paranoid here. */ - if (i >= n_specials) + if (i >= wgs->n_specials) break; - if (backend) - backend_special( - backend, specials[i].code, specials[i].arg); + if (wgs->backend) + backend_special(wgs->backend, wgs->specials[i].code, + wgs->specials[i].arg); } } break; @@ -2604,8 +2638,10 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, #define X_POS(l) ((int)(short)LOWORD(l)) #define Y_POS(l) ((int)(short)HIWORD(l)) -#define TO_CHR_X(x) ((((x)<0 ? (x)-font_width+1 : (x))-offset_width) / font_width) -#define TO_CHR_Y(y) ((((y)<0 ? (y)-font_height+1: (y))-offset_height) / font_height) +#define TO_CHR_X(x) ((((x)<0 ? (x)-wgs->font_width+1 : \ + (x))-wgs->offset_width) / wgs->font_width) +#define TO_CHR_Y(y) ((((y)<0 ? (y)-wgs->font_height+1 : \ + (y))-wgs->offset_height) / wgs->font_height) case WM_LBUTTONDOWN: case WM_MBUTTONDOWN: case WM_RBUTTONDOWN: @@ -2614,15 +2650,15 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, case WM_RBUTTONUP: if (message == WM_RBUTTONDOWN && ((wParam & MK_CONTROL) || - (conf_get_int(conf, CONF_mouse_is_xterm) == 2))) { + (conf_get_int(wgs->conf, CONF_mouse_is_xterm) == MOUSE_WINDOWS))) { POINT cursorpos; /* Just in case this happened in mid-select */ - term_cancel_selection_drag(term); + term_cancel_selection_drag(wgs->term); - show_mouseptr(true); /* make sure pointer is visible */ + show_mouseptr(wgs, true); /* make sure pointer is visible */ GetCursorPos(&cursorpos); - TrackPopupMenu(popup_menus[CTXMENU].menu, + TrackPopupMenu(wgs->popup_menus[CTXMENU].menu, TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RIGHTBUTTON, cursorpos.x, cursorpos.y, 0, hwnd, NULL); @@ -2667,7 +2703,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, button = 0; press = false; } - show_mouseptr(true); + show_mouseptr(wgs, true); /* * Special case: in full-screen mode, if the left * button is clicked in the very top left corner of the @@ -2700,7 +2736,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, if (pt.x == 0 && pt.y == 0) { mouse_on_hotspot = true; } - if (is_full_screen() && press && + if (is_full_screen(wgs) && press && button == MBT_LEFT && mouse_on_hotspot) { SendMessage(hwnd, WM_SYSCOMMAND, SC_MOUSEMENU, MAKELPARAM(pt.x, pt.y)); @@ -2709,14 +2745,14 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, } if (press) { - click(button, + click(wgs, button, TO_CHR_X(X_POS(lParam)), TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT, wParam & MK_CONTROL, is_alt_pressed()); SetCapture(hwnd); } else { - term_mouse(term, button, translate_button(button), MA_RELEASE, - TO_CHR_X(X_POS(lParam)), + term_mouse(wgs->term, button, translate_button(wgs, button), + MA_RELEASE, TO_CHR_X(X_POS(lParam)), TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT, wParam & MK_CONTROL, is_alt_pressed()); if (!(wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON))) @@ -2724,19 +2760,19 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, } } return 0; - case WM_MOUSEMOVE: { + case WM_MOUSEMOVE: /* * Windows seems to like to occasionally send MOUSEMOVE * events even if the mouse hasn't moved. Don't unhide * the mouse pointer in this case. */ - static WPARAM wp = 0; - static LPARAM lp = 0; - if (wParam != wp || lParam != lp || - last_mousemove != WM_MOUSEMOVE) { - show_mouseptr(true); - wp = wParam; lp = lParam; - last_mousemove = WM_MOUSEMOVE; + if (wgs->last_mousemove != WM_MOUSEMOVE || + wParam != wgs->last_wm_mousemove_wParam || + lParam != wgs->last_wm_mousemove_lParam) { + show_mouseptr(wgs, true); + wgs->last_mousemove = WM_MOUSEMOVE; + wgs->last_wm_mousemove_wParam = wParam; + wgs->last_wm_mousemove_lParam = lParam; } /* * Add the mouse position and message time to the random @@ -2753,45 +2789,43 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, b = MBT_MIDDLE; else b = MBT_RIGHT; - term_mouse(term, b, translate_button(b), MA_DRAG, + term_mouse(wgs->term, b, translate_button(wgs, b), MA_DRAG, TO_CHR_X(X_POS(lParam)), TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT, wParam & MK_CONTROL, is_alt_pressed()); } else { - term_mouse(term, MBT_NOTHING, MBT_NOTHING, MA_MOVE, + term_mouse(wgs->term, MBT_NOTHING, MBT_NOTHING, MA_MOVE, TO_CHR_X(X_POS(lParam)), TO_CHR_Y(Y_POS(lParam)), false, false, false); } return 0; - } - case WM_NCMOUSEMOVE: { - static WPARAM wp = 0; - static LPARAM lp = 0; - if (wParam != wp || lParam != lp || - last_mousemove != WM_NCMOUSEMOVE) { - show_mouseptr(true); - wp = wParam; lp = lParam; - last_mousemove = WM_NCMOUSEMOVE; + case WM_NCMOUSEMOVE: + if (wgs->last_mousemove != WM_NCMOUSEMOVE || + wParam != wgs->last_wm_ncmousemove_wParam || + lParam != wgs->last_wm_ncmousemove_lParam) { + show_mouseptr(wgs, true); + wgs->last_mousemove = WM_NCMOUSEMOVE; + wgs->last_wm_ncmousemove_wParam = wParam; + wgs->last_wm_ncmousemove_lParam = lParam; } noise_ultralight(NOISE_SOURCE_MOUSEPOS, lParam); break; - } case WM_IGNORE_CLIP: - ignore_clip = wParam; /* don't panic on DESTROYCLIPBOARD */ + wgs->ignore_clip = wParam; /* don't panic on DESTROYCLIPBOARD */ break; case WM_DESTROYCLIPBOARD: - if (!ignore_clip) - term_lost_clipboard_ownership(term, CLIP_SYSTEM); - ignore_clip = false; + if (!wgs->ignore_clip) + term_lost_clipboard_ownership(wgs->term, CLIP_SYSTEM); + wgs->ignore_clip = false; return 0; case WM_PAINT: { PAINTSTRUCT p; HideCaret(hwnd); hdc = BeginPaint(hwnd, &p); - if (pal) { - SelectPalette(hdc, pal, true); + if (wgs->pal) { + SelectPalette(hdc, wgs->pal, true); RealizePalette(hdc); } @@ -2827,29 +2861,30 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, * current terminal appearance so that WM_PAINT becomes * completely trivial. However, this should do for now. */ - assert(!wintw_hdc); - wintw_hdc = hdc; - term_paint(term, - (p.rcPaint.left-offset_width)/font_width, - (p.rcPaint.top-offset_height)/font_height, - (p.rcPaint.right-offset_width-1)/font_width, - (p.rcPaint.bottom-offset_height-1)/font_height, - !term->window_update_pending); - wintw_hdc = NULL; + assert(!wgs->wintw_hdc); + wgs->wintw_hdc = hdc; + term_paint(wgs->term, + (p.rcPaint.left-wgs->offset_width)/wgs->font_width, + (p.rcPaint.top-wgs->offset_height)/wgs->font_height, + (p.rcPaint.right-wgs->offset_width-1)/wgs->font_width, + (p.rcPaint.bottom-wgs->offset_height-1)/wgs->font_height, + !wgs->term->window_update_pending); + wgs->wintw_hdc = NULL; if (p.fErase || - p.rcPaint.left < offset_width || - p.rcPaint.top < offset_height || - p.rcPaint.right >= offset_width + font_width*term->cols || - p.rcPaint.bottom>= offset_height + font_height*term->rows) - { + p.rcPaint.left < wgs->offset_width || + p.rcPaint.top < wgs->offset_height || + p.rcPaint.right >= (wgs->offset_width + + wgs->font_width*wgs->term->cols) || + p.rcPaint.bottom>= (wgs->offset_height + + wgs->font_height*wgs->term->rows)) { HBRUSH fillcolour, oldbrush; HPEN edge, oldpen; fillcolour = CreateSolidBrush ( - colours[ATTR_DEFBG>>ATTR_BGSHIFT]); + wgs->colours[ATTR_DEFBG>>ATTR_BGSHIFT]); oldbrush = SelectObject(hdc, fillcolour); edge = CreatePen(PS_SOLID, 0, - colours[ATTR_DEFBG>>ATTR_BGSHIFT]); + wgs->colours[ATTR_DEFBG>>ATTR_BGSHIFT]); oldpen = SelectObject(hdc, edge); /* @@ -2863,10 +2898,10 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, p.rcPaint.left, p.rcPaint.top, p.rcPaint.right, p.rcPaint.bottom); - ExcludeClipRect(hdc, - offset_width, offset_height, - offset_width+font_width*term->cols, - offset_height+font_height*term->rows); + ExcludeClipRect( + hdc, wgs->offset_width, wgs->offset_height, + wgs->offset_width+wgs->font_width*wgs->term->cols, + wgs->offset_height+wgs->font_height*wgs->term->rows); Rectangle(hdc, p.rcPaint.left, p.rcPaint.top, p.rcPaint.right, p.rcPaint.bottom); @@ -2888,41 +2923,41 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, winselgui_response(wParam, lParam); return 0; case WM_SETFOCUS: - term_set_focus(term, true); - CreateCaret(hwnd, caretbm, font_width, font_height); + term_set_focus(wgs->term, true); + CreateCaret(hwnd, wgs->caretbm, wgs->font_width, wgs->font_height); ShowCaret(hwnd); - flash_window(0); /* stop */ - compose_state = 0; - term_update(term); + flash_window(wgs, 0); /* stop */ + wgs->compose_state = 0; + term_update(wgs->term); break; case WM_KILLFOCUS: - show_mouseptr(true); - term_set_focus(term, false); + show_mouseptr(wgs, true); + term_set_focus(wgs->term, false); DestroyCaret(); - caret_x = caret_y = -1; /* ensure caret is replaced next time */ - term_update(term); + wgs->caret_x = wgs->caret_y = -1; /* ensure caret replaced next time */ + term_update(wgs->term); break; case WM_ENTERSIZEMOVE: #ifdef RDB_DEBUG_PATCH debug("WM_ENTERSIZEMOVE\n"); #endif EnableSizeTip(true); - resizing = true; - need_backend_resize = false; + wgs->resizing = true; + wgs->need_backend_resize = false; break; case WM_EXITSIZEMOVE: EnableSizeTip(false); - resizing = false; + wgs->resizing = false; #ifdef RDB_DEBUG_PATCH debug("WM_EXITSIZEMOVE\n"); #endif - if (need_backend_resize) { - term_size(term, conf_get_int(conf, CONF_height), - conf_get_int(conf, CONF_width), - conf_get_int(conf, CONF_savelines)); + if (wgs->need_backend_resize) { + term_size(wgs->term, conf_get_int(wgs->conf, CONF_height), + conf_get_int(wgs->conf, CONF_width), + conf_get_int(wgs->conf, CONF_savelines)); InvalidateRect(hwnd, NULL, true); } - recompute_window_offset(); + recompute_window_offset(wgs); break; case WM_SIZING: /* @@ -2930,15 +2965,15 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, * 1) Keep the sizetip uptodate * 2) Make sure the window size is _stepped_ in units of the font size. */ - resize_action = conf_get_int(conf, CONF_resize_action); + resize_action = conf_get_int(wgs->conf, CONF_resize_action); if (resize_action == RESIZE_TERM || (resize_action == RESIZE_EITHER && !is_alt_pressed())) { int width, height, w, h, ew, eh; LPRECT r = (LPRECT) lParam; - if (!need_backend_resize && resize_action == RESIZE_EITHER && - (conf_get_int(conf, CONF_height) != term->rows || - conf_get_int(conf, CONF_width) != term->cols)) { + if (!wgs->need_backend_resize && resize_action == RESIZE_EITHER && + (conf_get_int(wgs->conf, CONF_height) != wgs->term->rows || + conf_get_int(wgs->conf, CONF_width) != wgs->term->cols)) { /* * Great! It seems that both the terminal size and the * font size have been changed and the user is now dragging. @@ -2948,24 +2983,24 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, * * This would be easier but it seems to be too confusing. */ - conf_set_int(conf, CONF_height, term->rows); - conf_set_int(conf, CONF_width, term->cols); + conf_set_int(wgs->conf, CONF_height, wgs->term->rows); + conf_set_int(wgs->conf, CONF_width, wgs->term->cols); InvalidateRect(hwnd, NULL, true); - need_backend_resize = true; + wgs->need_backend_resize = true; } - width = r->right - r->left - extra_width; - height = r->bottom - r->top - extra_height; - w = (width + font_width / 2) / font_width; + width = r->right - r->left - wgs->extra_width; + height = r->bottom - r->top - wgs->extra_height; + w = (width + wgs->font_width / 2) / wgs->font_width; if (w < 1) w = 1; - h = (height + font_height / 2) / font_height; + h = (height + wgs->font_height / 2) / wgs->font_height; if (h < 1) h = 1; UpdateSizeTip(hwnd, w, h); - ew = width - w * font_width; - eh = height - h * font_height; + ew = width - w * wgs->font_width; + eh = height - h * wgs->font_height; if (ew != 0) { if (wParam == WMSZ_LEFT || wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_TOPLEFT) @@ -2986,46 +3021,48 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, return 0; } else { int width, height, w, h, rv = 0; - int window_border = conf_get_int(conf, CONF_window_border); - int ex_width = extra_width + (window_border - offset_width) * 2; - int ex_height = extra_height + (window_border - offset_height) * 2; + int window_border = conf_get_int(wgs->conf, CONF_window_border); + int ex_width = wgs->extra_width + + (window_border - wgs->offset_width) * 2; + int ex_height = wgs->extra_height + + (window_border - wgs->offset_height) * 2; LPRECT r = (LPRECT) lParam; width = r->right - r->left - ex_width; height = r->bottom - r->top - ex_height; - w = (width + term->cols/2)/term->cols; - h = (height + term->rows/2)/term->rows; - if ( r->right != r->left + w*term->cols + ex_width) + w = (width + wgs->term->cols/2)/wgs->term->cols; + h = (height + wgs->term->rows/2)/wgs->term->rows; + if ( r->right != r->left + w*wgs->term->cols + ex_width) rv = 1; if (wParam == WMSZ_LEFT || wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_TOPLEFT) - r->left = r->right - w*term->cols - ex_width; + r->left = r->right - w*wgs->term->cols - ex_width; else - r->right = r->left + w*term->cols + ex_width; + r->right = r->left + w*wgs->term->cols + ex_width; - if (r->bottom != r->top + h*term->rows + ex_height) + if (r->bottom != r->top + h*wgs->term->rows + ex_height) rv = 1; if (wParam == WMSZ_TOP || wParam == WMSZ_TOPRIGHT || wParam == WMSZ_TOPLEFT) - r->top = r->bottom - h*term->rows - ex_height; + r->top = r->bottom - h*wgs->term->rows - ex_height; else - r->bottom = r->top + h*term->rows + ex_height; + r->bottom = r->top + h*wgs->term->rows + ex_height; return rv; } /* break; (never reached) */ case WM_FULLSCR_ON_MAX: - fullscr_on_max = true; + wgs->fullscr_on_max = true; break; case WM_MOVE: - term_notify_window_pos(term, LOWORD(lParam), HIWORD(lParam)); - sys_cursor_update(); + term_notify_window_pos(wgs->term, LOWORD(lParam), HIWORD(lParam)); + sys_cursor_update(wgs); break; case WM_SIZE: - resize_action = conf_get_int(conf, CONF_resize_action); + resize_action = conf_get_int(wgs->conf, CONF_resize_action); #ifdef RDB_DEBUG_PATCH debug("WM_SIZE %s (%d,%d)\n", (wParam == SIZE_MINIMIZED) ? "SIZE_MINIMIZED": @@ -3035,7 +3072,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, "...", LOWORD(lParam), HIWORD(lParam)); #endif - term_notify_minimised(term, wParam == SIZE_MINIMIZED); + term_notify_minimised(wgs->term, wParam == SIZE_MINIMIZED); { /* * WM_SIZE's lParam tells us the size of the client area. @@ -3045,18 +3082,18 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, RECT r; GetWindowRect(hwnd, &r); term_notify_window_size_pixels( - term, r.right - r.left, r.bottom - r.top); + wgs->term, r.right - r.left, r.bottom - r.top); } if (wParam == SIZE_MINIMIZED) sw_SetWindowText(hwnd, - conf_get_bool(conf, CONF_win_name_always) ? - window_name : icon_name); + conf_get_bool(wgs->conf, CONF_win_name_always) ? + wgs->window_name : wgs->icon_name); if (wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED) - sw_SetWindowText(hwnd, window_name); + sw_SetWindowText(hwnd, wgs->window_name); if (wParam == SIZE_RESTORED) { - processed_resize = false; - clear_full_screen(); - if (processed_resize) { + wgs->processed_resize = false; + clear_full_screen(wgs); + if (wgs->processed_resize) { /* * Inhibit normal processing of this WM_SIZE; a * secondary one was triggered just now by @@ -3066,11 +3103,11 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, return 0; } } - if (wParam == SIZE_MAXIMIZED && fullscr_on_max) { - fullscr_on_max = false; - processed_resize = false; - make_full_screen(); - if (processed_resize) { + if (wParam == SIZE_MAXIMIZED && wgs->fullscr_on_max) { + wgs->fullscr_on_max = false; + wgs->processed_resize = false; + make_full_screen(wgs); + if (wgs->processed_resize) { /* * Inhibit normal processing of this WM_SIZE; a * secondary one was triggered just now by @@ -3081,34 +3118,34 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, } } - processed_resize = true; + wgs->processed_resize = true; if (resize_action == RESIZE_DISABLED) { /* A resize, well it better be a minimize. */ - reset_window(-1); + reset_window(wgs, -1); } else { if (wParam == SIZE_MAXIMIZED) { - was_zoomed = true; - prev_rows = term->rows; - prev_cols = term->cols; + wgs->was_zoomed = true; + wgs->prev_rows = wgs->term->rows; + wgs->prev_cols = wgs->term->cols; if (resize_action == RESIZE_TERM) - wm_size_resize_term(lParam, false); - reset_window(0); - } else if (wParam == SIZE_RESTORED && was_zoomed) { - was_zoomed = false; + wm_size_resize_term(wgs, lParam); + reset_window(wgs, 0); + } else if (wParam == SIZE_RESTORED && wgs->was_zoomed) { + wgs->was_zoomed = false; if (resize_action == RESIZE_TERM) { - wm_size_resize_term(lParam, true); - reset_window(2); + wm_size_resize_term(wgs, lParam); + reset_window(wgs, 2); } else if (resize_action != RESIZE_FONT) - reset_window(2); + reset_window(wgs, 2); else - reset_window(0); + reset_window(wgs, 0); } else if (wParam == SIZE_MINIMIZED) { /* do nothing */ } else if (resize_action == RESIZE_TERM || (resize_action == RESIZE_EITHER && !is_alt_pressed())) { - wm_size_resize_term(lParam, true); + wm_size_resize_term(wgs, lParam); /* * Sometimes, we can get a spontaneous resize event @@ -3123,39 +3160,39 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, * recompute the border around the window and do a * full redraw to clear the new border. */ - if (!resizing) - recompute_window_offset(); + if (!wgs->resizing) + recompute_window_offset(wgs); } else { - reset_window(0); + reset_window(wgs, 0); } } - sys_cursor_update(); + sys_cursor_update(wgs); return 0; case WM_DPICHANGED: - dpi_info.cur_dpi.x = LOWORD(wParam); - dpi_info.cur_dpi.y = HIWORD(wParam); - dpi_info.new_wnd_rect = *(RECT*)(lParam); - reset_window(3); + wgs->dpi_info.cur_dpi.x = LOWORD(wParam); + wgs->dpi_info.cur_dpi.y = HIWORD(wParam); + wgs->dpi_info.new_wnd_rect = *(RECT*)(lParam); + reset_window(wgs, 3); return 0; case WM_VSCROLL: switch (LOWORD(wParam)) { case SB_BOTTOM: - term_scroll(term, -1, 0); + term_scroll(wgs->term, -1, 0); break; case SB_TOP: - term_scroll(term, +1, 0); + term_scroll(wgs->term, +1, 0); break; case SB_LINEDOWN: - term_scroll(term, 0, +1); + term_scroll(wgs->term, 0, +1); break; case SB_LINEUP: - term_scroll(term, 0, -1); + term_scroll(wgs->term, 0, -1); break; case SB_PAGEDOWN: - term_scroll(term, 0, +term->rows / 2); + term_scroll(wgs->term, 0, +wgs->term->rows / 2); break; case SB_PAGEUP: - term_scroll(term, 0, -term->rows / 2); + term_scroll(wgs->term, 0, -wgs->term->rows / 2); break; case SB_THUMBPOSITION: case SB_THUMBTRACK: { @@ -3169,12 +3206,12 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, si.fMask = SIF_TRACKPOS; if (GetScrollInfo(hwnd, SB_VERT, &si) == 0) si.nTrackPos = HIWORD(wParam); - term_scroll(term, 1, si.nTrackPos); + term_scroll(wgs->term, 1, si.nTrackPos); break; } } - if (in_scrollbar_loop) { + if (wgs->in_scrollbar_loop) { /* * Allow window updates to happen during interactive * scroll. @@ -3216,26 +3253,26 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, * way round. Many people on the Internet have noticed * this, e.g. https://stackoverflow.com/q/55528397 */ - term_update(term); + term_update(wgs->term); } break; case WM_PALETTECHANGED: - if ((HWND) wParam != hwnd && pal != NULL) { - HDC hdc = make_hdc(); + if ((HWND) wParam != hwnd && wgs->pal != NULL) { + HDC hdc = make_hdc(wgs); if (hdc) { if (RealizePalette(hdc) > 0) UpdateColors(hdc); - free_hdc(hdc); + free_hdc(wgs, hdc); } } break; case WM_QUERYNEWPALETTE: - if (pal != NULL) { - HDC hdc = make_hdc(); + if (wgs->pal != NULL) { + HDC hdc = make_hdc(wgs); if (hdc) { if (RealizePalette(hdc) > 0) UpdateColors(hdc); - free_hdc(hdc); + free_hdc(wgs, hdc); return true; } } @@ -3272,7 +3309,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, TranslateMessage(&m); } else break; /* pass to Windows for default processing */ } else { - len = TranslateKey(message, wParam, lParam, buf); + len = TranslateKey(wgs, message, wParam, lParam, buf); if (len == -1) return sw_DefWindowProc(hwnd, message, wParam, lParam); @@ -3285,8 +3322,8 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, * messages. We _have_ to buffer everything * we're sent. */ - term_keyinput(term, -1, buf, len); - show_mouseptr(false); + term_keyinput(wgs->term, -1, buf, len); + show_mouseptr(wgs, false); } } } @@ -3294,12 +3331,12 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, case WM_INPUTLANGCHANGE: /* wParam == Font number */ /* lParam == Locale */ - set_input_locale((HKL)lParam); - sys_cursor_update(); + set_input_locale(wgs, (HKL)lParam); + sys_cursor_update(wgs); break; case WM_IME_STARTCOMPOSITION: { HIMC hImc = ImmGetContext(hwnd); - ImmSetCompositionFont(hImc, &lfont); + ImmSetCompositionFont(hImc, &wgs->lfont); ImmReleaseContext(hwnd, hImc); break; } @@ -3329,20 +3366,20 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, * instead we send the characters one by one. */ /* don't divide SURROGATE PAIR */ - if (ldisc) { + if (wgs->ldisc) { for (i = 0; i < n; i += 2) { WCHAR hs = *(unsigned short *)(buff+i); if (IS_HIGH_SURROGATE(hs) && i+2 < n) { WCHAR ls = *(unsigned short *)(buff+i+2); if (IS_LOW_SURROGATE(ls)) { term_keyinputw( - term, (unsigned short *)(buff+i), 2); + wgs->term, (unsigned short *)(buff+i), 2); i += 2; continue; } } term_keyinputw( - term, (unsigned short *)(buff+i), 1); + wgs->term, (unsigned short *)(buff+i), 1); } } free(buff); @@ -3357,11 +3394,11 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, buf[1] = wParam; buf[0] = wParam >> 8; - term_keyinput(term, kbd_codepage, buf, 2); + term_keyinput(wgs->term, wgs->kbd_codepage, buf, 2); } else { char c = (unsigned char) wParam; - term_seen_key_event(term); - term_keyinput(term, kbd_codepage, &c, 1); + term_seen_key_event(wgs->term); + term_keyinput(wgs->term, wgs->kbd_codepage, &c, 1); } return (0); case WM_CHAR: @@ -3373,37 +3410,36 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, * we're ready to cope. */ if (unicode_window) { - static wchar_t pending_surrogate = 0; wchar_t c = wParam; if (IS_HIGH_SURROGATE(c)) { - pending_surrogate = c; - } else if (IS_SURROGATE_PAIR(pending_surrogate, c)) { + wgs->pending_surrogate = c; + } else if (IS_SURROGATE_PAIR(wgs->pending_surrogate, c)) { wchar_t pair[2]; - pair[0] = pending_surrogate; + pair[0] = wgs->pending_surrogate; pair[1] = c; - term_keyinputw(term, pair, 2); + term_keyinputw(wgs->term, pair, 2); } else if (!IS_SURROGATE(c)) { - term_keyinputw(term, &c, 1); + term_keyinputw(wgs->term, &c, 1); } } else { char c = (unsigned char)wParam; - term_seen_key_event(term); - if (ldisc) - term_keyinput(term, CP_ACP, &c, 1); + term_seen_key_event(wgs->term); + if (wgs->ldisc) + term_keyinput(wgs->term, CP_ACP, &c, 1); } return 0; case WM_SYSCOLORCHANGE: - if (conf_get_bool(conf, CONF_system_colour)) { + if (conf_get_bool(wgs->conf, CONF_system_colour)) { /* Refresh palette from system colours. */ - term_notify_palette_changed(term); - init_palette(); + term_notify_palette_changed(wgs->term); + init_palette(wgs); /* Force a repaint of the terminal window. */ - term_invalidate(term); + term_invalidate(wgs->term); } break; case WM_GOT_CLIPDATA: - process_clipdata((HGLOBAL)lParam, wParam); + process_clipdata(wgs, (HGLOBAL)lParam, wParam); return 0; default: if (message == wm_mousewheel || message == WM_MOUSEWHEEL @@ -3411,12 +3447,12 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, bool shift_pressed = false, control_pressed = false; if (message == WM_MOUSEWHEEL || message == WM_MOUSEHWHEEL) { - wheel_accumulator += (short)HIWORD(wParam); + wgs->wheel_accumulator += (short)HIWORD(wParam); shift_pressed=LOWORD(wParam) & MK_SHIFT; control_pressed=LOWORD(wParam) & MK_CONTROL; } else { BYTE keys[256]; - wheel_accumulator += (int)wParam; + wgs->wheel_accumulator += (int)wParam; if (GetKeyboardState(keys)!=0) { shift_pressed=keys[VK_SHIFT]&0x80; control_pressed=keys[VK_CONTROL]&0x80; @@ -3424,21 +3460,21 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, } /* process events when the threshold is reached */ - while (abs(wheel_accumulator) >= WHEEL_DELTA) { + while (abs(wgs->wheel_accumulator) >= WHEEL_DELTA) { int b; /* reduce amount for next time */ - if (wheel_accumulator > 0) { + if (wgs->wheel_accumulator > 0) { b = message == WM_MOUSEHWHEEL ? MBT_WHEEL_RIGHT : MBT_WHEEL_UP; - wheel_accumulator -= WHEEL_DELTA; - } else if (wheel_accumulator < 0) { + wgs->wheel_accumulator -= WHEEL_DELTA; + } else if (wgs->wheel_accumulator < 0) { b = message == WM_MOUSEHWHEEL ? MBT_WHEEL_LEFT : MBT_WHEEL_DOWN; - wheel_accumulator += WHEEL_DELTA; + wgs->wheel_accumulator += WHEEL_DELTA; } else break; - if (send_raw_mouse && - !(conf_get_bool(conf, CONF_mouse_override) && + if (wgs->send_raw_mouse && + !(conf_get_bool(wgs->conf, CONF_mouse_override) && shift_pressed)) { /* Mouse wheel position is in screen coordinates for * some reason */ @@ -3446,7 +3482,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, p.x = X_POS(lParam); p.y = Y_POS(lParam); if (ScreenToClient(hwnd, &p)) { /* send a mouse-down followed by a mouse up */ - term_mouse(term, b, translate_button(b), + term_mouse(wgs->term, b, translate_button(wgs, b), MA_CLICK, TO_CHR_X(p.x), TO_CHR_Y(p.y), shift_pressed, @@ -3454,9 +3490,9 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, } /* else: not sure when this can fail */ } else if (message != WM_MOUSEHWHEEL) { /* trigger a scroll */ - term_scroll(term, 0, + term_scroll(wgs->term, 0, b == MBT_WHEEL_UP ? - -term->rows / 2 : term->rows / 2); + -wgs->term->rows / 2 : wgs->term->rows / 2); } } return 0; @@ -3478,35 +3514,36 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, */ static void wintw_set_cursor_pos(TermWin *tw, int x, int y) { + WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin); int cx, cy; - if (!term->has_focus) return; + if (!wgs->term->has_focus) return; /* * Avoid gratuitously re-updating the cursor position and IMM * window if there's no actual change required. */ - cx = x * font_width + offset_width; - cy = y * font_height + offset_height; - if (cx == caret_x && cy == caret_y) + cx = x * wgs->font_width + wgs->offset_width; + cy = y * wgs->font_height + wgs->offset_height; + if (cx == wgs->caret_x && cy == wgs->caret_y) return; - caret_x = cx; - caret_y = cy; + wgs->caret_x = cx; + wgs->caret_y = cy; - sys_cursor_update(); + sys_cursor_update(wgs); } -static void sys_cursor_update(void) +static void sys_cursor_update(WinGuiSeat *wgs) { COMPOSITIONFORM cf; HIMC hIMC; - if (!term->has_focus) return; + if (!wgs->term->has_focus) return; - if (caret_x < 0 || caret_y < 0) + if (wgs->caret_x < 0 || wgs->caret_y < 0) return; - SetCaretPos(caret_x, caret_y); + SetCaretPos(wgs->caret_x, wgs->caret_y); /* IMM calls on Win98 and beyond only */ if (osPlatformId == VER_PLATFORM_WIN32s) return; /* 3.11 */ @@ -3515,31 +3552,31 @@ static void sys_cursor_update(void) osMinorVersion == 0) return; /* 95 */ /* we should have the IMM functions */ - hIMC = ImmGetContext(wgs.term_hwnd); + hIMC = ImmGetContext(wgs->term_hwnd); cf.dwStyle = CFS_POINT; - cf.ptCurrentPos.x = caret_x; - cf.ptCurrentPos.y = caret_y; + cf.ptCurrentPos.x = wgs->caret_x; + cf.ptCurrentPos.y = wgs->caret_y; ImmSetCompositionWindow(hIMC, &cf); - ImmReleaseContext(wgs.term_hwnd, hIMC); + ImmReleaseContext(wgs->term_hwnd, hIMC); } -static void draw_horizontal_line_on_text(int y, int lattr, RECT line_box, - COLORREF colour) +static void draw_horizontal_line_on_text( + WinGuiSeat *wgs, int y, int lattr, RECT line_box, COLORREF colour) { if (lattr == LATTR_TOP || lattr == LATTR_BOT) { y *= 2; if (lattr == LATTR_BOT) - y -= font_height; + y -= wgs->font_height; } - if (!(0 <= y && y < font_height)) + if (!(0 <= y && y < wgs->font_height)) return; - HPEN oldpen = SelectObject(wintw_hdc, CreatePen(PS_SOLID, 0, colour)); - MoveToEx(wintw_hdc, line_box.left, line_box.top + y, NULL); - LineTo(wintw_hdc, line_box.right, line_box.top + y); - oldpen = SelectObject(wintw_hdc, oldpen); + HPEN oldpen = SelectObject(wgs->wintw_hdc, CreatePen(PS_SOLID, 0, colour)); + MoveToEx(wgs->wintw_hdc, line_box.left, line_box.top + y, NULL); + LineTo(wgs->wintw_hdc, line_box.right, line_box.top + y); + oldpen = SelectObject(wgs->wintw_hdc, oldpen); DeleteObject(oldpen); } @@ -3550,7 +3587,7 @@ static void draw_horizontal_line_on_text(int y, int lattr, RECT line_box, * We are allowed to fiddle with the contents of `text'. */ static void do_text_internal( - int x, int y, wchar_t *text, int len, + WinGuiSeat *wgs, int x, int y, wchar_t *text, int len, unsigned long attr, int lattr, truecolour truecolour) { COLORREF fg, bg, t; @@ -3563,28 +3600,32 @@ static void do_text_internal( int maxlen, remaining; bool opaque; bool is_cursor = false; - static int *lpDx = NULL; - static size_t lpDx_len = 0; - int *lpDx_maybe; + int *lpDx = NULL; + size_t lpDx_len = 0; + bool use_lpDx; + wchar_t *wbuf = NULL; + char *cbuf = NULL; + size_t wbuflen = 0, cbuflen = 0; int len2; /* for SURROGATE PAIR */ lattr &= LATTR_MODE; - char_width = fnt_width = font_width * (1 + (lattr != LATTR_NORM)); + char_width = fnt_width = wgs->font_width * (1 + (lattr != LATTR_NORM)); if (attr & ATTR_WIDE) char_width *= 2; /* Only want the left half of double width lines */ - if (lattr != LATTR_NORM && x*2 >= term->cols) + if (lattr != LATTR_NORM && x*2 >= wgs->term->cols) return; x *= fnt_width; - y *= font_height; - x += offset_width; - y += offset_height; + y *= wgs->font_height; + x += wgs->offset_width; + y += wgs->offset_height; - if ((attr & TATTR_ACTCURS) && (cursor_type == 0 || term->big_cursor)) { + if ((attr & TATTR_ACTCURS) && + (wgs->cursor_type == CURSOR_BLOCK || wgs->term->big_cursor)) { truecolour.fg = truecolour.bg = optionalrgb_none; attr &= ~(ATTR_REVERSE|ATTR_BLINK|ATTR_COLOURS|ATTR_DIM); /* cursor fg and bg */ @@ -3593,7 +3634,7 @@ static void do_text_internal( } nfont = 0; - if (vtmode == VT_POORMAN && lattr != LATTR_NORM) { + if (wgs->vtmode == VT_POORMAN && lattr != LATTR_NORM) { /* Assume a poorman font is borken in other ways too. */ lattr = LATTR_WIDE; } else @@ -3615,21 +3656,21 @@ static void do_text_internal( if (text[0] >= 0x23BA && text[0] <= 0x23BD) { switch ((unsigned char) (text[0])) { case 0xBA: - text_adjust = -2 * font_height / 5; + text_adjust = -2 * wgs->font_height / 5; break; case 0xBB: - text_adjust = -1 * font_height / 5; + text_adjust = -1 * wgs->font_height / 5; break; case 0xBC: - text_adjust = font_height / 5; + text_adjust = wgs->font_height / 5; break; case 0xBD: - text_adjust = 2 * font_height / 5; + text_adjust = 2 * wgs->font_height / 5; break; } if (lattr == LATTR_TOP || lattr == LATTR_BOT) text_adjust *= 2; - text[0] = ucsdata.unitab_xterm['q']; + text[0] = wgs->ucsdata.unitab_xterm['q']; if (attr & ATTR_UNDER) { attr &= ~ATTR_UNDER; force_manual_underline = true; @@ -3651,20 +3692,20 @@ static void do_text_internal( nfg = ((attr & ATTR_FGMASK) >> ATTR_FGSHIFT); nbg = ((attr & ATTR_BGMASK) >> ATTR_BGSHIFT); - if (bold_font_mode == BOLD_FONT && (attr & ATTR_BOLD)) + if (wgs->bold_font_mode == BOLD_FONT && (attr & ATTR_BOLD)) nfont |= FONT_BOLD; - if (und_mode == UND_FONT && (attr & ATTR_UNDER)) + if (wgs->und_mode == UND_FONT && (attr & ATTR_UNDER)) nfont |= FONT_UNDERLINE; - another_font(nfont); - if (!fonts[nfont]) { + another_font(wgs, nfont); + if (!wgs->fonts[nfont]) { if (nfont & FONT_UNDERLINE) force_manual_underline = true; /* Don't do the same for manual bold, it could be bad news. */ nfont &= ~(FONT_BOLD | FONT_UNDERLINE); } - another_font(nfont); - if (!fonts[nfont]) + another_font(wgs, nfont); + if (!wgs->fonts[nfont]) nfont = FONT_NORMAL; if (attr & ATTR_REVERSE) { struct optionalrgb trgb; @@ -3677,41 +3718,41 @@ static void do_text_internal( truecolour.fg = truecolour.bg; truecolour.bg = trgb; } - if (bold_colours && (attr & ATTR_BOLD) && !is_cursor) { + if (wgs->bold_colours && (attr & ATTR_BOLD) && !is_cursor) { if (nfg < 16) nfg |= 8; else if (nfg >= 256) nfg |= 1; } - if (bold_colours && (attr & ATTR_BLINK)) { + if (wgs->bold_colours && (attr & ATTR_BLINK)) { if (nbg < 16) nbg |= 8; else if (nbg >= 256) nbg |= 1; } - if (!pal && truecolour.fg.enabled) + if (!wgs->pal && truecolour.fg.enabled) fg = RGB(truecolour.fg.r, truecolour.fg.g, truecolour.fg.b); else - fg = colours[nfg]; + fg = wgs->colours[nfg]; - if (!pal && truecolour.bg.enabled) + if (!wgs->pal && truecolour.bg.enabled) bg = RGB(truecolour.bg.r, truecolour.bg.g, truecolour.bg.b); else - bg = colours[nbg]; + bg = wgs->colours[nbg]; - if (!pal && (attr & ATTR_DIM)) { + if (!wgs->pal && (attr & ATTR_DIM)) { fg = RGB(GetRValue(fg) * 2 / 3, GetGValue(fg) * 2 / 3, GetBValue(fg) * 2 / 3); } - SelectObject(wintw_hdc, fonts[nfont]); - SetTextColor(wintw_hdc, fg); - SetBkColor(wintw_hdc, bg); + SelectObject(wgs->wintw_hdc, wgs->fonts[nfont]); + SetTextColor(wgs->wintw_hdc, fg); + SetBkColor(wgs->wintw_hdc, bg); if (attr & TATTR_COMBINING) - SetBkMode(wintw_hdc, TRANSPARENT); + SetBkMode(wgs->wintw_hdc, TRANSPARENT); else - SetBkMode(wintw_hdc, OPAQUE); + SetBkMode(wgs->wintw_hdc, OPAQUE); line_box.left = x; line_box.top = y; line_box.right = x + char_width * len; - line_box.bottom = y + font_height; + line_box.bottom = y + wgs->font_height; /* adjust line_box.right for SURROGATE PAIR & VARIATION SELECTOR */ { int i; @@ -3732,10 +3773,10 @@ static void do_text_internal( } /* Only want the left half of double width lines */ - if (line_box.right > font_width*term->cols+offset_width) - line_box.right = font_width*term->cols+offset_width; + if (line_box.right > wgs->font_width*wgs->term->cols+wgs->offset_width) + line_box.right = wgs->font_width*wgs->term->cols+wgs->offset_width; - if (font_varpitch) { + if (wgs->font_varpitch) { /* * If we're using a variable-pitch font, we unconditionally * draw the glyphs one at a time and centre them in their @@ -3744,8 +3785,8 @@ static void do_text_internal( * generally reasonable results. */ xoffset = char_width / 2; - SetTextAlign(wintw_hdc, TA_TOP | TA_CENTER | TA_NOUPDATECP); - lpDx_maybe = NULL; + SetTextAlign(wgs->wintw_hdc, TA_TOP | TA_CENTER | TA_NOUPDATECP); + use_lpDx = false; maxlen = 1; } else { /* @@ -3753,8 +3794,8 @@ static void do_text_internal( * in the normal way. */ xoffset = 0; - SetTextAlign(wintw_hdc, TA_TOP | TA_LEFT | TA_NOUPDATECP); - lpDx_maybe = lpDx; + SetTextAlign(wgs->wintw_hdc, TA_TOP | TA_LEFT | TA_NOUPDATECP); + use_lpDx = true; maxlen = len; } @@ -3774,10 +3815,8 @@ static void do_text_internal( len += 2; } - if (len > lpDx_len) { + if (len > lpDx_len) sgrowarray(lpDx, lpDx_len, len); - if (lpDx_maybe) lpDx_maybe = lpDx; - } { int i; @@ -3802,68 +3841,64 @@ static void do_text_internal( } /* We're using a private area for direct to font. (512 chars.) */ - if (ucsdata.dbcs_screenfont && (text[0] & CSET_MASK) == CSET_ACP) { + if (wgs->ucsdata.dbcs_screenfont && + (text[0] & CSET_MASK) == CSET_ACP) { /* Ho Hum, dbcs fonts are a PITA! */ /* To display on W9x I have to convert to UCS */ - static wchar_t *uni_buf = 0; - static int uni_len = 0; int nlen, mptr; - if (len > uni_len) { - sfree(uni_buf); - uni_len = len; - uni_buf = snewn(uni_len, wchar_t); - } - for(nlen = mptr = 0; mptrucsdata.font_codepage, (BYTE) text[mptr])) { char dbcstext[2]; dbcstext[0] = text[mptr] & 0xFF; dbcstext[1] = text[mptr+1] & 0xFF; lpDx[nlen] += char_width; - MultiByteToWideChar(ucsdata.font_codepage, MB_USEGLYPHCHARS, - dbcstext, 2, uni_buf+nlen, 1); + MultiByteToWideChar( + wgs->ucsdata.font_codepage, MB_USEGLYPHCHARS, + dbcstext, 2, wbuf+nlen, 1); mptr++; } else { char dbcstext[1]; dbcstext[0] = text[mptr] & 0xFF; - MultiByteToWideChar(ucsdata.font_codepage, MB_USEGLYPHCHARS, - dbcstext, 1, uni_buf+nlen, 1); + MultiByteToWideChar( + wgs->ucsdata.font_codepage, MB_USEGLYPHCHARS, + dbcstext, 1, wbuf+nlen, 1); } nlen++; } if (nlen <= 0) - return; /* Eeek! */ - - ExtTextOutW(wintw_hdc, x + xoffset, - y - font_height * (lattr == LATTR_BOT) + text_adjust, - ETO_CLIPPED | (opaque ? ETO_OPAQUE : 0), - &line_box, uni_buf, nlen, - lpDx_maybe); - if (bold_font_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) { - SetBkMode(wintw_hdc, TRANSPARENT); - ExtTextOutW(wintw_hdc, x + xoffset - 1, - y - font_height * (lattr == - LATTR_BOT) + text_adjust, - ETO_CLIPPED, &line_box, uni_buf, nlen, lpDx_maybe); + goto out; /* Eeek! */ + + ExtTextOutW( + wgs->wintw_hdc, x + xoffset, + y - wgs->font_height * (lattr == LATTR_BOT) + text_adjust, + ETO_CLIPPED | (opaque ? ETO_OPAQUE : 0), + &line_box, wbuf, nlen, (use_lpDx ? lpDx : NULL)); + if (wgs->bold_font_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) { + SetBkMode(wgs->wintw_hdc, TRANSPARENT); + ExtTextOutW( + wgs->wintw_hdc, x + xoffset - 1, + y - wgs->font_height * (lattr == LATTR_BOT) + text_adjust, + ETO_CLIPPED, &line_box, wbuf, nlen, + (use_lpDx ? lpDx : NULL)); } lpDx[0] = -1; } else if (DIRECT_FONT(text[0])) { - static char *directbuf = NULL; - static size_t directlen = 0; - - sgrowarray(directbuf, directlen, len); + sgrowarray(cbuf, cbuflen, len); for (size_t i = 0; i < len; i++) - directbuf[i] = text[i] & 0xFF; + cbuf[i] = text[i] & 0xFF; - ExtTextOut(wintw_hdc, x + xoffset, - y - font_height * (lattr == LATTR_BOT) + text_adjust, - ETO_CLIPPED | (opaque ? ETO_OPAQUE : 0), - &line_box, directbuf, len, lpDx_maybe); - if (bold_font_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) { - SetBkMode(wintw_hdc, TRANSPARENT); + ExtTextOut( + wgs->wintw_hdc, x + xoffset, + y - wgs->font_height * (lattr == LATTR_BOT) + text_adjust, + ETO_CLIPPED | (opaque ? ETO_OPAQUE : 0), + &line_box, cbuf, len, (use_lpDx ? lpDx : NULL)); + if (wgs->bold_font_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) { + SetBkMode(wgs->wintw_hdc, TRANSPARENT); /* GRR: This draws the character outside its box and * can leave 'droppings' even with the clip box! I @@ -3874,39 +3909,33 @@ static void do_text_internal( * or -1 for this shift depending on if the leftmost * column is blank... */ - ExtTextOut(wintw_hdc, x + xoffset - 1, - y - font_height * (lattr == - LATTR_BOT) + text_adjust, - ETO_CLIPPED, &line_box, directbuf, len, lpDx_maybe); + ExtTextOut( + wgs->wintw_hdc, x + xoffset - 1, + y - wgs->font_height * (lattr == LATTR_BOT) + text_adjust, + ETO_CLIPPED, &line_box, cbuf, len, + (use_lpDx ? lpDx : NULL)); } } else { /* And 'normal' unicode characters */ - static WCHAR *wbuf = NULL; - static int wlen = 0; - int i; - - if (wlen < len) { - sfree(wbuf); - wlen = len; - wbuf = snewn(wlen, WCHAR); - } - - for (i = 0; i < len; i++) + sgrowarray(wbuf, wbuflen, len); + for (int i = 0; i < len; i++) wbuf[i] = text[i]; /* print Glyphs as they are, without Windows' Shaping*/ - general_textout(wintw_hdc, x + xoffset, - y - font_height * (lattr==LATTR_BOT) + text_adjust, - &line_box, wbuf, len, lpDx, - opaque && !(attr & TATTR_COMBINING)); + general_textout( + wgs, wgs->wintw_hdc, x + xoffset, + y - wgs->font_height * (lattr==LATTR_BOT) + text_adjust, + &line_box, wbuf, len, lpDx, + opaque && !(attr & TATTR_COMBINING)); /* And the shadow bold hack. */ - if (bold_font_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) { - SetBkMode(wintw_hdc, TRANSPARENT); - ExtTextOutW(wintw_hdc, x + xoffset - 1, - y - font_height * (lattr == - LATTR_BOT) + text_adjust, - ETO_CLIPPED, &line_box, wbuf, len, lpDx_maybe); + if (wgs->bold_font_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) { + SetBkMode(wgs->wintw_hdc, TRANSPARENT); + ExtTextOutW( + wgs->wintw_hdc, x + xoffset - 1, + y - wgs->font_height * (lattr == LATTR_BOT) + text_adjust, + ETO_CLIPPED, &line_box, wbuf, len, + (use_lpDx ? lpDx : NULL)); } } @@ -3914,16 +3943,23 @@ static void do_text_internal( * If we're looping round again, stop erasing the background * rectangle. */ - SetBkMode(wintw_hdc, TRANSPARENT); + SetBkMode(wgs->wintw_hdc, TRANSPARENT); opaque = false; } - if (lattr != LATTR_TOP && (force_manual_underline || - (und_mode == UND_LINE && (attr & ATTR_UNDER)))) - draw_horizontal_line_on_text(descent, lattr, line_box, fg); + if (lattr != LATTR_TOP && + (force_manual_underline || (wgs->und_mode == UND_LINE && + (attr & ATTR_UNDER)))) + draw_horizontal_line_on_text(wgs, wgs->descent, lattr, line_box, fg); if (attr & ATTR_STRIKE) - draw_horizontal_line_on_text(font_strikethrough_y, lattr, line_box, fg); + draw_horizontal_line_on_text(wgs, wgs->font_strikethrough_y, lattr, + line_box, fg); + + out: + sfree(lpDx); + sfree(wbuf); + sfree(cbuf); } /* @@ -3933,6 +3969,7 @@ static void wintw_draw_text( TermWin *tw, int x, int y, wchar_t *text, int len, unsigned long attr, int lattr, truecolour truecolour) { + WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin); if (attr & TATTR_COMBINING) { unsigned long a = 0; int len0 = 1; @@ -3941,13 +3978,13 @@ static void wintw_draw_text( len0 = 2; if (len-len0 >= 1 && IS_LOW_VARSEL(text[len0])) { attr &= ~TATTR_COMBINING; - do_text_internal(x, y, text, len0+1, attr, lattr, truecolour); + do_text_internal(wgs, x, y, text, len0+1, attr, lattr, truecolour); text += len0+1; len -= len0+1; a = TATTR_COMBINING; } else if (len-len0 >= 2 && IS_HIGH_VARSEL(text[len0], text[len0+1])) { attr &= ~TATTR_COMBINING; - do_text_internal(x, y, text, len0+2, attr, lattr, truecolour); + do_text_internal(wgs, x, y, text, len0+2, attr, lattr, truecolour); text += len0+2; len -= len0+2; a = TATTR_COMBINING; @@ -3957,66 +3994,73 @@ static void wintw_draw_text( while (len--) { if (len >= 1 && IS_SURROGATE_PAIR(text[0], text[1])) { - do_text_internal(x, y, text, 2, attr | a, lattr, truecolour); + do_text_internal(wgs, x, y, text, 2, attr | a, lattr, + truecolour); len--; text++; } else - do_text_internal(x, y, text, 1, attr | a, lattr, truecolour); + do_text_internal(wgs, x, y, text, 1, attr | a, lattr, + truecolour); text++; a = TATTR_COMBINING; } } else - do_text_internal(x, y, text, len, attr, lattr, truecolour); + do_text_internal(wgs, x, y, text, len, attr, lattr, truecolour); } static void wintw_draw_cursor( TermWin *tw, int x, int y, wchar_t *text, int len, unsigned long attr, int lattr, truecolour truecolour) { + WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin); int fnt_width; int char_width; - int ctype = cursor_type; + int ctype = wgs->cursor_type; lattr &= LATTR_MODE; - if ((attr & TATTR_ACTCURS) && (ctype == 0 || term->big_cursor)) { + if ((attr & TATTR_ACTCURS) && + (ctype == CURSOR_BLOCK || wgs->term->big_cursor)) { if (*text != UCSWIDE) { win_draw_text(tw, x, y, text, len, attr, lattr, truecolour); return; } - ctype = 2; + ctype = CURSOR_VERTICAL_LINE; attr |= TATTR_RIGHTCURS; } - fnt_width = char_width = font_width * (1 + (lattr != LATTR_NORM)); + fnt_width = char_width = wgs->font_width * (1 + (lattr != LATTR_NORM)); if (attr & ATTR_WIDE) char_width *= 2; x *= fnt_width; - y *= font_height; - x += offset_width; - y += offset_height; + y *= wgs->font_height; + x += wgs->offset_width; + y += wgs->offset_height; - if ((attr & TATTR_PASCURS) && (ctype == 0 || term->big_cursor)) { + if ((attr & TATTR_PASCURS) && + (ctype == CURSOR_BLOCK || wgs->term->big_cursor)) { POINT pts[5]; HPEN oldpen; pts[0].x = pts[1].x = pts[4].x = x; pts[2].x = pts[3].x = x + char_width - 1; pts[0].y = pts[3].y = pts[4].y = y; - pts[1].y = pts[2].y = y + font_height - 1; - oldpen = SelectObject(wintw_hdc, CreatePen(PS_SOLID, 0, colours[261])); - Polyline(wintw_hdc, pts, 5); - oldpen = SelectObject(wintw_hdc, oldpen); + pts[1].y = pts[2].y = y + wgs->font_height - 1; + oldpen = SelectObject(wgs->wintw_hdc, + CreatePen(PS_SOLID, 0, wgs->colours[261])); + Polyline(wgs->wintw_hdc, pts, 5); + oldpen = SelectObject(wgs->wintw_hdc, oldpen); DeleteObject(oldpen); - } else if ((attr & (TATTR_ACTCURS | TATTR_PASCURS)) && ctype != 0) { + } else if ((attr & (TATTR_ACTCURS | TATTR_PASCURS)) && + ctype != CURSOR_BLOCK) { int startx, starty, dx, dy, length, i; - if (ctype == 1) { + if (ctype == CURSOR_UNDERLINE) { startx = x; - starty = y + descent; + starty = y + wgs->descent; dx = 1; dy = 0; length = char_width; - } else { + } else /* ctype == CURSOR_VERTICAL_LINE */ { int xadjust = 0; if (attr & TATTR_RIGHTCURS) xadjust = char_width - 1; @@ -4024,20 +4068,22 @@ static void wintw_draw_cursor( starty = y; dx = 0; dy = 1; - length = font_height; + length = wgs->font_height; } if (attr & TATTR_ACTCURS) { HPEN oldpen; oldpen = - SelectObject(wintw_hdc, CreatePen(PS_SOLID, 0, colours[261])); - MoveToEx(wintw_hdc, startx, starty, NULL); - LineTo(wintw_hdc, startx + dx * length, starty + dy * length); - oldpen = SelectObject(wintw_hdc, oldpen); + SelectObject(wgs->wintw_hdc, + CreatePen(PS_SOLID, 0, wgs->colours[261])); + MoveToEx(wgs->wintw_hdc, startx, starty, NULL); + LineTo(wgs->wintw_hdc, startx + dx * length, starty + dy * length); + oldpen = SelectObject(wgs->wintw_hdc, oldpen); DeleteObject(oldpen); } else { for (i = 0; i < length; i++) { if (i % 2 == 0) { - SetPixel(wintw_hdc, startx, starty, colours[261]); + SetPixel(wgs->wintw_hdc, startx, starty, + wgs->colours[261]); } startx += dx; starty += dy; @@ -4048,74 +4094,77 @@ static void wintw_draw_cursor( static void wintw_draw_trust_sigil(TermWin *tw, int x, int y) { - x *= font_width; - y *= font_height; - x += offset_width; - y += offset_height; + WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin); + + x *= wgs->font_width; + y *= wgs->font_height; + x += wgs->offset_width; + y += wgs->offset_height; - DrawIconEx(wintw_hdc, x, y, trust_icon, font_width * 2, font_height, - 0, NULL, DI_NORMAL); + DrawIconEx(wgs->wintw_hdc, x, y, trust_icon, + wgs->font_width * 2, wgs->font_height, 0, NULL, DI_NORMAL); } /* This function gets the actual width of a character in the normal font. */ static int wintw_char_width(TermWin *tw, int uc) { + WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin); int ibuf = 0; /* If the font max is the same as the font ave width then this * function is a no-op. */ - if (!font_dualwidth) return 1; + if (!wgs->font_dualwidth) return 1; switch (uc & CSET_MASK) { case CSET_ASCII: - uc = ucsdata.unitab_line[uc & 0xFF]; + uc = wgs->ucsdata.unitab_line[uc & 0xFF]; break; case CSET_LINEDRW: - uc = ucsdata.unitab_xterm[uc & 0xFF]; + uc = wgs->ucsdata.unitab_xterm[uc & 0xFF]; break; case CSET_SCOACS: - uc = ucsdata.unitab_scoacs[uc & 0xFF]; + uc = wgs->ucsdata.unitab_scoacs[uc & 0xFF]; break; } if (DIRECT_FONT(uc)) { - if (ucsdata.dbcs_screenfont) return 1; + if (wgs->ucsdata.dbcs_screenfont) return 1; /* Speedup, I know of no font where ascii is the wrong width */ if ((uc&~CSET_MASK) >= ' ' && (uc&~CSET_MASK)<= '~') return 1; if ( (uc & CSET_MASK) == CSET_ACP ) { - SelectObject(wintw_hdc, fonts[FONT_NORMAL]); + SelectObject(wgs->wintw_hdc, wgs->fonts[FONT_NORMAL]); } else if ( (uc & CSET_MASK) == CSET_OEMCP ) { - another_font(FONT_OEM); - if (!fonts[FONT_OEM]) return 0; + another_font(wgs, FONT_OEM); + if (!wgs->fonts[FONT_OEM]) return 0; - SelectObject(wintw_hdc, fonts[FONT_OEM]); + SelectObject(wgs->wintw_hdc, wgs->fonts[FONT_OEM]); } else return 0; - if (GetCharWidth32(wintw_hdc, uc & ~CSET_MASK, + if (GetCharWidth32(wgs->wintw_hdc, uc & ~CSET_MASK, uc & ~CSET_MASK, &ibuf) != 1 && - GetCharWidth(wintw_hdc, uc & ~CSET_MASK, + GetCharWidth(wgs->wintw_hdc, uc & ~CSET_MASK, uc & ~CSET_MASK, &ibuf) != 1) return 0; } else { /* Speedup, I know of no font where ascii is the wrong width */ if (uc >= ' ' && uc <= '~') return 1; - SelectObject(wintw_hdc, fonts[FONT_NORMAL]); - if (GetCharWidth32W(wintw_hdc, uc, uc, &ibuf) == 1) + SelectObject(wgs->wintw_hdc, wgs->fonts[FONT_NORMAL]); + if (GetCharWidth32W(wgs->wintw_hdc, uc, uc, &ibuf) == 1) /* Okay that one worked */ ; - else if (GetCharWidthW(wintw_hdc, uc, uc, &ibuf) == 1) + else if (GetCharWidthW(wgs->wintw_hdc, uc, uc, &ibuf) == 1) /* This should work on 9x too, but it's "less accurate" */ ; else return 0; } - ibuf += font_width / 2 -1; - ibuf /= font_width; + ibuf += wgs->font_width / 2 -1; + ibuf /= wgs->font_width; return ibuf; } @@ -4123,7 +4172,8 @@ static int wintw_char_width(TermWin *tw, int uc) DECL_WINDOWS_FUNCTION(static, BOOL, FlashWindowEx, (PFLASHWINFO)); DECL_WINDOWS_FUNCTION(static, BOOL, ToUnicodeEx, (UINT, UINT, const BYTE *, LPWSTR, int, UINT, HKL)); -DECL_WINDOWS_FUNCTION(static, BOOL, PlaySound, (LPCTSTR, HMODULE, DWORD)); +DECL_WINDOWS_FUNCTION(static, BOOL, PlaySoundW, (LPCWSTR, HMODULE, DWORD)); +DECL_WINDOWS_FUNCTION(static, BOOL, PlaySoundA, (LPCSTR, HMODULE, DWORD)); static void init_winfuncs(void) { @@ -4132,7 +4182,8 @@ static void init_winfuncs(void) HMODULE shcore_module = load_system32_dll("shcore.dll"); GET_WINDOWS_FUNCTION(user32_module, FlashWindowEx); GET_WINDOWS_FUNCTION(user32_module, ToUnicodeEx); - GET_WINDOWS_FUNCTION_PP(winmm_module, PlaySound); + GET_WINDOWS_FUNCTION(winmm_module, PlaySoundW); + GET_WINDOWS_FUNCTION(winmm_module, PlaySoundA); GET_WINDOWS_FUNCTION_NO_TYPECHECK(user32_module, GetMonitorInfoA); GET_WINDOWS_FUNCTION_NO_TYPECHECK(user32_module, MonitorFromPoint); GET_WINDOWS_FUNCTION_NO_TYPECHECK(user32_module, MonitorFromWindow); @@ -4147,27 +4198,22 @@ static void init_winfuncs(void) * -1 to forward the message to Windows, or another negative number * to indicate a NUL-terminated "special" string. */ -static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, - unsigned char *output) +static int TranslateKey(WinGuiSeat *wgs, UINT message, WPARAM wParam, + LPARAM lParam, unsigned char *output) { BYTE keystate[256]; int scan, shift_state; bool left_alt = false, key_down; int r, i; unsigned char *p = output; - static int alt_sum = 0; - int funky_type = conf_get_int(conf, CONF_funky_type); - bool no_applic_k = conf_get_bool(conf, CONF_no_applic_k); - bool ctrlaltkeys = conf_get_bool(conf, CONF_ctrlaltkeys); - bool nethack_keypad = conf_get_bool(conf, CONF_nethack_keypad); + int funky_type = conf_get_int(wgs->conf, CONF_funky_type); + bool no_applic_k = conf_get_bool(wgs->conf, CONF_no_applic_k); + bool ctrlaltkeys = conf_get_bool(wgs->conf, CONF_ctrlaltkeys); + bool nethack_keypad = conf_get_bool(wgs->conf, CONF_nethack_keypad); char keypad_key = '\0'; HKL kbd_layout = GetKeyboardLayout(0); - static wchar_t keys_unicode[3]; - static int compose_char = 0; - static WPARAM compose_keycode = 0; - r = GetKeyboardState(keystate); if (!r) memset(keystate, 0, sizeof(keystate)); @@ -4216,13 +4262,6 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, else if (ch) debug(", $%02x", ch); - if (keys_unicode[0]) - debug(", KB0=%04x", keys_unicode[0]); - if (keys_unicode[1]) - debug(", KB1=%04x", keys_unicode[1]); - if (keys_unicode[2]) - debug(", KB2=%04x", keys_unicode[2]); - if ((keystate[VK_SHIFT] & 0x80) != 0) debug(", S"); if ((keystate[VK_CONTROL] & 0x80) != 0) @@ -4254,7 +4293,7 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, /* Nastiness with NUMLock - Shift-NUMLock is left alone though */ if ((funky_type == FUNKY_VT400 || - (funky_type <= FUNKY_LINUX && term->app_keypad_keys && + (funky_type <= FUNKY_LINUX && wgs->term->app_keypad_keys && !no_applic_k)) && wParam == VK_NUMLOCK && !(keystate[VK_SHIFT] & 0x80)) { @@ -4270,7 +4309,7 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, } /* Disable Auto repeat if required */ - if (term->repeat_off && + if (wgs->term->repeat_off && (HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT) return 0; @@ -4294,34 +4333,34 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, + ((keystate[VK_CONTROL] & 0x80) != 0) * 2; /* Note if AltGr was pressed and if it was used as a compose key */ - if (!compose_state) { - compose_keycode = 0x100; - if (conf_get_bool(conf, CONF_compose_key)) { + if (!wgs->compose_state) { + wgs->compose_keycode = 0x100; + if (conf_get_bool(wgs->conf, CONF_compose_key)) { if (wParam == VK_MENU && (HIWORD(lParam) & KF_EXTENDED)) - compose_keycode = wParam; + wgs->compose_keycode = wParam; } if (wParam == VK_APPS) - compose_keycode = wParam; + wgs->compose_keycode = wParam; } - if (wParam == compose_keycode) { - if (compose_state == 0 && + if (wParam == wgs->compose_keycode) { + if (wgs->compose_state == 0 && (HIWORD(lParam) & (KF_UP | KF_REPEAT)) == 0) - compose_state = 1; - else if (compose_state == 1 && (HIWORD(lParam) & KF_UP)) - compose_state = 2; + wgs->compose_state = 1; + else if (wgs->compose_state == 1 && (HIWORD(lParam) & KF_UP)) + wgs->compose_state = 2; else - compose_state = 0; - } else if (compose_state == 1 && wParam != VK_CONTROL) - compose_state = 0; + wgs->compose_state = 0; + } else if (wgs->compose_state == 1 && wParam != VK_CONTROL) + wgs->compose_state = 0; - if (compose_state > 1 && left_alt) - compose_state = 0; + if (wgs->compose_state > 1 && left_alt) + wgs->compose_state = 0; /* Sanitize the number pad if not using a PC NumPad */ - if (left_alt || (term->app_keypad_keys && !no_applic_k - && funky_type != FUNKY_XTERM) - || funky_type == FUNKY_VT400 || nethack_keypad || compose_state) { + if (left_alt || (wgs->term->app_keypad_keys && !no_applic_k + && funky_type != FUNKY_XTERM) || + funky_type == FUNKY_VT400 || nethack_keypad || wgs->compose_state) { if ((HIWORD(lParam) & KF_EXTENDED) == 0) { int nParam = 0; switch (wParam) { @@ -4368,47 +4407,48 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, } /* If a key is pressed and AltGr is not active */ - if (key_down && (keystate[VK_RMENU] & 0x80) == 0 && !compose_state) { + if (key_down && (keystate[VK_RMENU] & 0x80) == 0 && !wgs->compose_state) { /* Okay, prepare for most alts then ... */ if (left_alt) *p++ = '\033'; /* Lets see if it's a pattern we know all about ... */ if (wParam == VK_PRIOR && shift_state == 1) { - SendMessage(wgs.term_hwnd, WM_VSCROLL, SB_PAGEUP, 0); + SendMessage(wgs->term_hwnd, WM_VSCROLL, SB_PAGEUP, 0); return 0; } if (wParam == VK_PRIOR && shift_state == 3) { /* ctrl-shift-pageup */ - SendMessage(wgs.term_hwnd, WM_VSCROLL, SB_TOP, 0); + SendMessage(wgs->term_hwnd, WM_VSCROLL, SB_TOP, 0); return 0; } if (wParam == VK_NEXT && shift_state == 3) { /* ctrl-shift-pagedown */ - SendMessage(wgs.term_hwnd, WM_VSCROLL, SB_BOTTOM, 0); + SendMessage(wgs->term_hwnd, WM_VSCROLL, SB_BOTTOM, 0); return 0; } if (wParam == VK_PRIOR && shift_state == 2) { - SendMessage(wgs.term_hwnd, WM_VSCROLL, SB_LINEUP, 0); + SendMessage(wgs->term_hwnd, WM_VSCROLL, SB_LINEUP, 0); return 0; } if (wParam == VK_NEXT && shift_state == 1) { - SendMessage(wgs.term_hwnd, WM_VSCROLL, SB_PAGEDOWN, 0); + SendMessage(wgs->term_hwnd, WM_VSCROLL, SB_PAGEDOWN, 0); return 0; } if (wParam == VK_NEXT && shift_state == 2) { - SendMessage(wgs.term_hwnd, WM_VSCROLL, SB_LINEDOWN, 0); + SendMessage(wgs->term_hwnd, WM_VSCROLL, SB_LINEDOWN, 0); return 0; } if ((wParam == VK_PRIOR || wParam == VK_NEXT) && shift_state == 3) { - term_scroll_to_selection(term, (wParam == VK_PRIOR ? 0 : 1)); + term_scroll_to_selection(wgs->term, (wParam == VK_PRIOR ? 0 : 1)); return 0; } if (wParam == VK_INSERT && shift_state == 2) { - switch (conf_get_int(conf, CONF_ctrlshiftins)) { + switch (conf_get_int(wgs->conf, CONF_ctrlshiftins)) { case CLIPUI_IMPLICIT: break; /* no need to re-copy to CLIP_LOCAL */ case CLIPUI_EXPLICIT: - term_request_copy(term, clips_system, lenof(clips_system)); + term_request_copy(wgs->term, clips_system, + lenof(clips_system)); break; default: break; @@ -4416,12 +4456,12 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, return 0; } if (wParam == VK_INSERT && shift_state == 1) { - switch (conf_get_int(conf, CONF_ctrlshiftins)) { + switch (conf_get_int(wgs->conf, CONF_ctrlshiftins)) { case CLIPUI_IMPLICIT: - term_request_paste(term, CLIP_LOCAL); + term_request_paste(wgs->term, CLIP_LOCAL); break; case CLIPUI_EXPLICIT: - term_request_paste(term, CLIP_SYSTEM); + term_request_paste(wgs->term, CLIP_SYSTEM); break; default: break; @@ -4429,11 +4469,12 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, return 0; } if (wParam == 'C' && shift_state == 3) { - switch (conf_get_int(conf, CONF_ctrlshiftcv)) { + switch (conf_get_int(wgs->conf, CONF_ctrlshiftcv)) { case CLIPUI_IMPLICIT: break; /* no need to re-copy to CLIP_LOCAL */ case CLIPUI_EXPLICIT: - term_request_copy(term, clips_system, lenof(clips_system)); + term_request_copy(wgs->term, clips_system, + lenof(clips_system)); break; default: break; @@ -4441,47 +4482,50 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, return 0; } if (wParam == 'V' && shift_state == 3) { - switch (conf_get_int(conf, CONF_ctrlshiftcv)) { + switch (conf_get_int(wgs->conf, CONF_ctrlshiftcv)) { case CLIPUI_IMPLICIT: - term_request_paste(term, CLIP_LOCAL); + term_request_paste(wgs->term, CLIP_LOCAL); break; case CLIPUI_EXPLICIT: - term_request_paste(term, CLIP_SYSTEM); + term_request_paste(wgs->term, CLIP_SYSTEM); break; default: break; } return 0; } - if (left_alt && wParam == VK_F4 && conf_get_bool(conf, CONF_alt_f4)) { + if (left_alt && wParam == VK_F4 && + conf_get_bool(wgs->conf, CONF_alt_f4)) { return -1; } - if (left_alt && wParam == VK_SPACE && conf_get_bool(conf, - CONF_alt_space)) { - SendMessage(wgs.term_hwnd, WM_SYSCOMMAND, SC_KEYMENU, 0); + if (left_alt && wParam == VK_SPACE && + conf_get_bool(wgs->conf, CONF_alt_space)) { + SendMessage(wgs->term_hwnd, WM_SYSCOMMAND, SC_KEYMENU, 0); return -1; } if (left_alt && wParam == VK_RETURN && - conf_get_bool(conf, CONF_fullscreenonaltenter) && - (conf_get_int(conf, CONF_resize_action) != RESIZE_DISABLED)) { + conf_get_bool(wgs->conf, CONF_fullscreenonaltenter) && + (conf_get_int(wgs->conf, CONF_resize_action) != RESIZE_DISABLED)) { if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) != KF_REPEAT) - flip_full_screen(); + flip_full_screen(wgs); return -1; } /* Control-Numlock for app-keypad mode switch */ if (wParam == VK_PAUSE && shift_state == 2) { - term->app_keypad_keys = !term->app_keypad_keys; + wgs->term->app_keypad_keys = !wgs->term->app_keypad_keys; return 0; } if (wParam == VK_BACK && shift_state == 0) { /* Backspace */ - *p++ = (conf_get_bool(conf, CONF_bksp_is_delete) ? 0x7F : 0x08); + *p++ = (conf_get_bool(wgs->conf, CONF_bksp_is_delete) ? + 0x7F : 0x08); *p++ = 0; return -2; } if (wParam == VK_BACK && shift_state == 1) { /* Shift Backspace */ /* We do the opposite of what is configured */ - *p++ = (conf_get_bool(conf, CONF_bksp_is_delete) ? 0x08 : 0x7F); + *p++ = (conf_get_bool(wgs->conf, CONF_bksp_is_delete) ? + 0x08 : 0x7F); *p++ = 0; return -2; } @@ -4500,8 +4544,8 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, return p - output; } if (wParam == VK_CANCEL && shift_state == 2) { /* Ctrl-Break */ - if (backend) - backend_special(backend, SS_BRK, 0); + if (wgs->backend) + backend_special(wgs->backend, SS_BRK, 0); return 0; } if (wParam == VK_PAUSE) { /* Break/Pause */ @@ -4552,15 +4596,16 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, * numeric character code input */ if (left_alt) { if (keypad_key >= '0' && keypad_key <= '9') - alt_sum = alt_sum * 10 + keypad_key - '0'; + wgs->alt_numberpad_accumulator = + wgs->alt_numberpad_accumulator * 10 + keypad_key - '0'; else - alt_sum = 0; + wgs->alt_numberpad_accumulator = 0; break; } { int nchars = format_numeric_keypad_key( - (char *)p, term, keypad_key, + (char *)p, wgs->term, keypad_key, shift_state & 1, shift_state & 2); if (!nchars) { /* @@ -4612,11 +4657,14 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, case VK_F20: fkey_number = 20; goto numbered_function_key; numbered_function_key: consumed_alt = false; - p += format_function_key((char *)p, term, fkey_number, + p += format_function_key((char *)p, wgs->term, fkey_number, shift_state & 1, shift_state & 2, left_alt, &consumed_alt); - if (consumed_alt) - left_alt = false; /* supersedes the usual prefixing of Esc */ + if (consumed_alt) { + /* supersedes the usual prefixing of Esc */ + p -= 1; + memmove(output, output + 1, p - output); + } return p - output; SmallKeypadKey sk_key; @@ -4631,11 +4679,15 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, if (shift_state & 2) break; - p += format_small_keypad_key((char *)p, term, sk_key, + consumed_alt = false; + p += format_small_keypad_key((char *)p, wgs->term, sk_key, shift_state & 1, shift_state & 2, left_alt, &consumed_alt); - if (consumed_alt) - left_alt = false; /* supersedes the usual prefixing of Esc */ + if (consumed_alt) { + /* supersedes the usual prefixing of Esc */ + p -= 1; + memmove(output, output + 1, p - output); + } return p - output; char xkey; @@ -4646,10 +4698,13 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, case VK_CLEAR: xkey = 'G'; goto arrow_key; /* close enough */ arrow_key: consumed_alt = false; - p += format_arrow_key((char *)p, term, xkey, shift_state & 1, + p += format_arrow_key((char *)p, wgs->term, xkey, shift_state & 1, shift_state & 2, left_alt, &consumed_alt); - if (consumed_alt) - left_alt = false; /* supersedes the usual prefixing of Esc */ + if (consumed_alt) { + /* supersedes the usual prefixing of Esc */ + p -= 1; + memmove(output, output + 1, p - output); + } return p - output; case VK_RETURN: @@ -4658,7 +4713,7 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, goto numeric_keypad; } ordinary_return_key: - if (shift_state == 0 && term->cr_lf_return) { + if (shift_state == 0 && wgs->term->cr_lf_return) { *p++ = '\r'; *p++ = '\n'; return p - output; @@ -4676,12 +4731,14 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, bool capsOn = false; /* helg: clear CAPS LOCK state if caps lock switches to cyrillic */ - if(keystate[VK_CAPITAL] != 0 && - conf_get_bool(conf, CONF_xlat_capslockcyr)) { - capsOn= !left_alt; + if (keystate[VK_CAPITAL] != 0 && + conf_get_bool(wgs->conf, CONF_xlat_capslockcyr)) { + capsOn = !left_alt; keystate[VK_CAPITAL] = 0; } + wchar_t keys_unicode[3]; + /* XXX how do we know what the max size of the keys array should * be is? There's indication on MS' website of an Inquire/InquireEx * functioning returning a KBINFO structure which tells us. */ @@ -4700,8 +4757,8 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, * See wishlist item `win-dead-keys' for more horrible detail * and speculations. */ int i; - static WORD keys[3]; - static BYTE keysb[3]; + WORD keys[3]; + BYTE keysb[3]; r = ToAsciiEx(wParam, scan, keystate, keys, 0, kbd_layout); if (r > 0) { for (i = 0; i < r; i++) { @@ -4713,11 +4770,11 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, } #ifdef SHOW_TOASCII_RESULT if (r == 1 && !key_down) { - if (alt_sum) { + if (wgs->alt_numberpad_accumulator) { if (in_utf(term) || ucsdata.dbcs_screenfont) - debug(", (U+%04x)", alt_sum); + debug(", (U+%04x)", wgs->alt_numberpad_accumulator); else - debug(", LCH(%d)", alt_sum); + debug(", LCH(%d)", wgs->alt_numberpad_accumulator); } else { debug(", ACH(%d)", keys_unicode[0]); } @@ -4737,33 +4794,34 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, for (i = 0; i < r; i++) { wchar_t wch = keys_unicode[i]; - if (compose_state == 2 && wch >= ' ' && wch < 0x80) { - compose_char = wch; - compose_state++; + if (wgs->compose_state == 2 && wch >= ' ' && wch < 0x80) { + wgs->compose_char = wch; + wgs->compose_state++; continue; } - if (compose_state == 3 && wch >= ' ' && wch < 0x80) { + if (wgs->compose_state == 3 && wch >= ' ' && wch < 0x80) { int nc; - compose_state = 0; + wgs->compose_state = 0; - if ((nc = check_compose(compose_char, wch)) == -1) { + if ((nc = check_compose(wgs->compose_char, wch)) == -1) { MessageBeep(MB_ICONHAND); return 0; } keybuf = nc; - term_keyinputw(term, &keybuf, 1); + term_keyinputw(wgs->term, &keybuf, 1); continue; } - compose_state = 0; + wgs->compose_state = 0; if (!key_down) { - if (alt_sum) { - if (in_utf(term) || ucsdata.dbcs_screenfont) { - keybuf = alt_sum; - term_keyinputw(term, &keybuf, 1); + if (wgs->alt_numberpad_accumulator) { + if (in_utf(wgs->term) || + wgs->ucsdata.dbcs_screenfont) { + keybuf = wgs->alt_numberpad_accumulator; + term_keyinputw(wgs->term, &keybuf, 1); } else { - char ch = (char) alt_sum; + char ch = (char) wgs->alt_numberpad_accumulator; /* * We need not bother about stdin * backlogs here, because in GUI PuTTY @@ -4773,39 +4831,32 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, * messages. We _have_ to buffer * everything we're sent. */ - term_keyinput(term, -1, &ch, 1); + term_keyinput(wgs->term, -1, &ch, 1); } - alt_sum = 0; + wgs->alt_numberpad_accumulator = 0; } else { - term_keyinputw(term, &wch, 1); + term_keyinputw(wgs->term, &wch, 1); } } else { - if(capsOn && wch < 0x80) { + if (capsOn && wch < 0x80) { WCHAR cbuf[2]; cbuf[0] = 27; cbuf[1] = xlat_uskbd2cyrllic(wch); - term_keyinputw(term, cbuf+!left_alt, 1+!!left_alt); + term_keyinputw( + wgs->term, cbuf+!left_alt, 1+!!left_alt); } else { WCHAR cbuf[2]; cbuf[0] = '\033'; cbuf[1] = wch; - term_keyinputw(term, cbuf +!left_alt, 1+!!left_alt); + term_keyinputw( + wgs->term, cbuf +!left_alt, 1+!!left_alt); } } - show_mouseptr(false); + show_mouseptr(wgs, false); } - /* This is so the ALT-Numpad and dead keys work correctly. */ - keys_unicode[0] = 0; - return p - output; } - /* If we're definitely not building up an ALT-54321 then clear it */ - if (!left_alt) - keys_unicode[0] = 0; - /* If we will be using alt_sum fix the 256s */ - else if (keys_unicode[0] && (in_utf(term) || ucsdata.dbcs_screenfont)) - keys_unicode[0] = 10; } /* @@ -4815,7 +4866,7 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, * we return -1, which means Windows will give the keystroke * its default handling (i.e. bring up the System menu). */ - if (wParam == VK_MENU && !conf_get_bool(conf, CONF_alt_only)) + if (wParam == VK_MENU && !conf_get_bool(wgs->conf, CONF_alt_only)) return 0; return -1; @@ -4823,35 +4874,40 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, static void wintw_set_title(TermWin *tw, const char *title, int codepage) { - wchar_t *new_window_name = dup_mb_to_wc(codepage, 0, title); - if (!wcscmp(new_window_name, window_name)) { + WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin); + wchar_t *new_window_name = dup_mb_to_wc(codepage, title); + if (!wcscmp(new_window_name, wgs->window_name)) { sfree(new_window_name); return; } - sfree(window_name); - window_name = new_window_name; - if (conf_get_bool(conf, CONF_win_name_always) || !IsIconic(wgs.term_hwnd)) - sw_SetWindowText(wgs.term_hwnd, window_name); + sfree(wgs->window_name); + wgs->window_name = new_window_name; + if (conf_get_bool(wgs->conf, CONF_win_name_always) || + !IsIconic(wgs->term_hwnd)) + sw_SetWindowText(wgs->term_hwnd, wgs->window_name); } static void wintw_set_icon_title(TermWin *tw, const char *title, int codepage) { - wchar_t *new_icon_name = dup_mb_to_wc(codepage, 0, title); - if (!wcscmp(new_icon_name, icon_name)) { + WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin); + wchar_t *new_icon_name = dup_mb_to_wc(codepage, title); + if (!wcscmp(new_icon_name, wgs->icon_name)) { sfree(new_icon_name); return; } - sfree(icon_name); - icon_name = new_icon_name; - if (!conf_get_bool(conf, CONF_win_name_always) && IsIconic(wgs.term_hwnd)) - sw_SetWindowText(wgs.term_hwnd, icon_name); + sfree(wgs->icon_name); + wgs->icon_name = new_icon_name; + if (!conf_get_bool(wgs->conf, CONF_win_name_always) && + IsIconic(wgs->term_hwnd)) + sw_SetWindowText(wgs->term_hwnd, wgs->icon_name); } static void wintw_set_scrollbar(TermWin *tw, int total, int start, int page) { + WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin); SCROLLINFO si; - if (!conf_get_bool(conf, is_full_screen() ? + if (!conf_get_bool(wgs->conf, is_full_screen(wgs) ? CONF_scrollbar_in_fullscreen : CONF_scrollbar)) return; @@ -4861,96 +4917,102 @@ static void wintw_set_scrollbar(TermWin *tw, int total, int start, int page) si.nMax = total - 1; si.nPage = page; si.nPos = start; - if (wgs.term_hwnd) - SetScrollInfo(wgs.term_hwnd, SB_VERT, &si, true); + if (wgs->term_hwnd) + SetScrollInfo(wgs->term_hwnd, SB_VERT, &si, true); } static bool wintw_setup_draw_ctx(TermWin *tw) { - assert(!wintw_hdc); - wintw_hdc = make_hdc(); - return wintw_hdc != NULL; + WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin); + assert(!wgs->wintw_hdc); + wgs->wintw_hdc = make_hdc(wgs); + return wgs->wintw_hdc != NULL; } static void wintw_free_draw_ctx(TermWin *tw) { - assert(wintw_hdc); - free_hdc(wintw_hdc); - wintw_hdc = NULL; + WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin); + assert(wgs->wintw_hdc); + free_hdc(wgs, wgs->wintw_hdc); + wgs->wintw_hdc = NULL; } /* * Set up the colour palette. */ -static void init_palette(void) +static void init_palette(WinGuiSeat *wgs) { - pal = NULL; - logpal = snew_plus(LOGPALETTE, (OSC4_NCOLOURS - 1) * sizeof(PALETTEENTRY)); - logpal->palVersion = 0x300; - logpal->palNumEntries = OSC4_NCOLOURS; + wgs->pal = NULL; + wgs->logpal = snew_plus( + LOGPALETTE, (OSC4_NCOLOURS - 1) * sizeof(PALETTEENTRY)); + wgs->logpal->palVersion = 0x300; + wgs->logpal->palNumEntries = OSC4_NCOLOURS; for (unsigned i = 0; i < OSC4_NCOLOURS; i++) - logpal->palPalEntry[i].peFlags = PC_NOCOLLAPSE; + wgs->logpal->palPalEntry[i].peFlags = PC_NOCOLLAPSE; } -static void wintw_palette_set(TermWin *win, unsigned start, +static void wintw_palette_set(TermWin *tw, unsigned start, unsigned ncolours, const rgb *colours_in) { + WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin); assert(start <= OSC4_NCOLOURS); assert(ncolours <= OSC4_NCOLOURS - start); for (unsigned i = 0; i < ncolours; i++) { const rgb *in = &colours_in[i]; - PALETTEENTRY *out = &logpal->palPalEntry[i + start]; + PALETTEENTRY *out = &wgs->logpal->palPalEntry[i + start]; out->peRed = in->r; out->peGreen = in->g; out->peBlue = in->b; - colours[i + start] = RGB(in->r, in->g, in->b) ^ colorref_modifier; + wgs->colours[i + start] = + RGB(in->r, in->g, in->b) ^ wgs->colorref_modifier; } bool got_new_palette = false; - if (!tried_pal && conf_get_bool(conf, CONF_try_palette)) { - HDC hdc = GetDC(wgs.term_hwnd); + if (!wgs->tried_pal && conf_get_bool(wgs->conf, CONF_try_palette)) { + HDC hdc = GetDC(wgs->term_hwnd); if (GetDeviceCaps(hdc, RASTERCAPS) & RC_PALETTE) { - pal = CreatePalette(logpal); - if (pal) { - SelectPalette(hdc, pal, false); + wgs->pal = CreatePalette(wgs->logpal); + if (wgs->pal) { + SelectPalette(hdc, wgs->pal, false); RealizePalette(hdc); SelectPalette(hdc, GetStockObject(DEFAULT_PALETTE), false); /* Convert all RGB() values in colours[] into PALETTERGB(), * and ensure we stick to that later */ - colorref_modifier = PALETTERGB(0, 0, 0) ^ RGB(0, 0, 0); + wgs->colorref_modifier = PALETTERGB(0, 0, 0) ^ RGB(0, 0, 0); for (unsigned i = 0; i < OSC4_NCOLOURS; i++) - colours[i] ^= colorref_modifier; + wgs->colours[i] ^= wgs->colorref_modifier; /* Inhibit the SetPaletteEntries call below */ got_new_palette = true; } } - ReleaseDC(wgs.term_hwnd, hdc); - tried_pal = true; + ReleaseDC(wgs->term_hwnd, hdc); + wgs->tried_pal = true; } - if (pal && !got_new_palette) { + if (wgs->pal && !got_new_palette) { /* We already had a palette, so replace the changed colours in the * existing one. */ - SetPaletteEntries(pal, start, ncolours, logpal->palPalEntry + start); + SetPaletteEntries(wgs->pal, start, ncolours, + wgs->logpal->palPalEntry + start); - HDC hdc = make_hdc(); - UnrealizeObject(pal); + HDC hdc = make_hdc(wgs); + UnrealizeObject(wgs->pal); RealizePalette(hdc); - free_hdc(hdc); + free_hdc(wgs, hdc); } if (start <= OSC4_COLOUR_bg && OSC4_COLOUR_bg < start + ncolours) { /* If Default Background changes, we need to ensure any space between * the text area and the window border is redrawn. */ - InvalidateRect(wgs.term_hwnd, NULL, true); + InvalidateRect(wgs->term_hwnd, NULL, true); } } -void write_aclip(int clipboard, char *data, int len, bool must_deselect) +void write_aclip(HWND hwnd, int clipboard, char *data, int len) { HGLOBAL clipdata; void *lock; @@ -4968,18 +5030,12 @@ void write_aclip(int clipboard, char *data, int len, bool must_deselect) ((unsigned char *) lock)[len] = 0; GlobalUnlock(clipdata); - if (!must_deselect) - SendMessage(wgs.term_hwnd, WM_IGNORE_CLIP, true, 0); - - if (OpenClipboard(wgs.term_hwnd)) { + if (OpenClipboard(hwnd)) { EmptyClipboard(); SetClipboardData(CF_TEXT, clipdata); CloseClipboard(); } else GlobalFree(clipdata); - - if (!must_deselect) - SendMessage(wgs.term_hwnd, WM_IGNORE_CLIP, false, 0); } typedef struct _rgbindex { @@ -5001,6 +5057,7 @@ static void wintw_clip_write( TermWin *tw, int clipboard, wchar_t *data, int *attr, truecolour *truecolour, int len, bool must_deselect) { + WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin); HGLOBAL clipdata, clipdata2, clipdata3; int len2; void *lock, *lock2, *lock3; @@ -5036,7 +5093,7 @@ static void wintw_clip_write( memcpy(lock, data, len * sizeof(wchar_t)); WideCharToMultiByte(CP_ACP, 0, data, len, lock2, len2, NULL, NULL); - if (conf_get_bool(conf, CONF_rtf_paste)) { + if (conf_get_bool(wgs->conf, CONF_rtf_paste)) { wchar_t unitab[256]; strbuf *rtf = strbuf_new(); unsigned char *tdata = (unsigned char *)lock2; @@ -5053,7 +5110,7 @@ static void wintw_clip_write( int palette[OSC4_NCOLOURS]; int numcolours; tree234 *rgbtree = NULL; - FontSpec *font = conf_get_fontspec(conf, CONF_font); + FontSpec *font = conf_get_fontspec(wgs->conf, CONF_font); get_unitab(CP_ACP, unitab, 0); @@ -5082,7 +5139,7 @@ static void wintw_clip_write( bgcolour = tmpcolour; } - if (bold_colours && (attr[i] & ATTR_BOLD)) { + if (wgs->bold_colours && (attr[i] & ATTR_BOLD)) { if (fgcolour < 8) /* ANSI colours */ fgcolour += 8; else if (fgcolour >= 256) /* Default colours */ @@ -5144,7 +5201,7 @@ static void wintw_clip_write( for (i = 0; i < OSC4_NCOLOURS; i++) { if (palette[i] != 0) { - const PALETTEENTRY *pe = &logpal->palPalEntry[i]; + const PALETTEENTRY *pe = &wgs->logpal->palPalEntry[i]; put_fmt(rtf, "\\red%d\\green%d\\blue%d;", pe->peRed, pe->peGreen, pe->peBlue); } @@ -5223,7 +5280,8 @@ static void wintw_clip_write( bg = tmpref; } - if (bold_colours && (attr[tindex] & ATTR_BOLD) && (fgcolour >= 0)) { + if (wgs->bold_colours && (attr[tindex] & ATTR_BOLD) && + (fgcolour >= 0)) { if (fgcolour < 8) /* ANSI colours */ fgcolour += 8; else if (fgcolour >= 256) /* Default colours */ @@ -5240,7 +5298,7 @@ static void wintw_clip_write( /* * Collect other attributes */ - if (bold_font_mode != BOLD_NONE) + if (wgs->bold_font_mode != BOLD_NONE) attrBold = attr[tindex] & ATTR_BOLD; else attrBold = 0; @@ -5259,7 +5317,8 @@ static void wintw_clip_write( bgcolour = -1; /* No coloring */ if (fgcolour >= 256) { /* Default colour */ - if (bold_colours && (fgcolour & 1) && bgcolour == -1) + if (wgs->bold_colours && (fgcolour & 1) && + bgcolour == -1) attrBold = ATTR_BOLD; /* Emphasize text with bold attribute */ fgcolour = -1; /* No coloring */ @@ -5374,9 +5433,9 @@ static void wintw_clip_write( GlobalUnlock(clipdata2); if (!must_deselect) - SendMessage(wgs.term_hwnd, WM_IGNORE_CLIP, true, 0); + SendMessage(wgs->term_hwnd, WM_IGNORE_CLIP, true, 0); - if (OpenClipboard(wgs.term_hwnd)) { + if (OpenClipboard(wgs->term_hwnd)) { EmptyClipboard(); SetClipboardData(CF_UNICODETEXT, clipdata); SetClipboardData(CF_TEXT, clipdata2); @@ -5389,7 +5448,7 @@ static void wintw_clip_write( } if (!must_deselect) - SendMessage(wgs.term_hwnd, WM_IGNORE_CLIP, false, 0); + SendMessage(wgs->term_hwnd, WM_IGNORE_CLIP, false, 0); } static DWORD WINAPI clipboard_read_threadfunc(void *param) @@ -5411,7 +5470,7 @@ static DWORD WINAPI clipboard_read_threadfunc(void *param) return 0; } -static void process_clipdata(HGLOBAL clipdata, bool unicode) +static void process_clipdata(WinGuiSeat *wgs, HGLOBAL clipdata, bool unicode) { wchar_t *clipboard_contents = NULL; size_t clipboard_length = 0; @@ -5427,7 +5486,7 @@ static void process_clipdata(HGLOBAL clipdata, bool unicode) clipboard_contents = snewn(clipboard_length + 1, wchar_t); memcpy(clipboard_contents, p, clipboard_length * sizeof(wchar_t)); clipboard_contents[clipboard_length] = L'\0'; - term_do_paste(term, clipboard_contents, clipboard_length); + term_do_paste(wgs->term, clipboard_contents, clipboard_length); } } else { char *s = GlobalLock(clipdata); @@ -5440,7 +5499,7 @@ static void process_clipdata(HGLOBAL clipdata, bool unicode) clipboard_contents, i); clipboard_length = i - 1; clipboard_contents[clipboard_length] = L'\0'; - term_do_paste(term, clipboard_contents, clipboard_length); + term_do_paste(wgs->term, clipboard_contents, clipboard_length); } } @@ -5449,6 +5508,7 @@ static void process_clipdata(HGLOBAL clipdata, bool unicode) static void wintw_clip_request_paste(TermWin *tw, int clipboard) { + WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin); assert(clipboard == CLIP_SYSTEM); /* @@ -5469,7 +5529,7 @@ static void wintw_clip_request_paste(TermWin *tw, int clipboard) */ DWORD in_threadid; /* required for Win9x */ HANDLE hThread = CreateThread(NULL, 0, clipboard_read_threadfunc, - wgs.term_hwnd, 0, &in_threadid); + wgs->term_hwnd, 0, &in_threadid); if (hThread) CloseHandle(hThread); /* we don't need the thread handle */ } @@ -5485,9 +5545,9 @@ void modalfatalbox(const char *fmt, ...) va_start(ap, fmt); message = dupvprintf(fmt, ap); va_end(ap); - show_mouseptr(true); + show_mouseptr(NULL, true); title = dupprintf("%s 致命错误", appname); - MessageBox(wgs.term_hwnd, message, title, + MessageBox(find_window_for_msgbox(), message, title, MB_SYSTEMMODAL | MB_ICONERROR | MB_OK); sfree(message); sfree(title); @@ -5505,19 +5565,20 @@ void nonfatal(const char *fmt, ...) va_start(ap, fmt); message = dupvprintf(fmt, ap); va_end(ap); - show_mouseptr(true); + show_mouseptr(NULL, true); title = dupprintf("%s 错误", appname); - MessageBox(wgs.term_hwnd, message, title, MB_ICONERROR | MB_OK); + MessageBox(find_window_for_msgbox(), message, title, MB_ICONERROR | MB_OK); sfree(message); sfree(title); } -static bool flash_window_ex(DWORD dwFlags, UINT uCount, DWORD dwTimeout) +static bool flash_window_ex(WinGuiSeat *wgs, DWORD dwFlags, + UINT uCount, DWORD dwTimeout) { if (p_FlashWindowEx) { FLASHWINFO fi; fi.cbSize = sizeof(fi); - fi.hwnd = wgs.term_hwnd; + fi.hwnd = wgs->term_hwnd; fi.dwFlags = dwFlags; fi.uCount = uCount; fi.dwTimeout = dwTimeout; @@ -5527,18 +5588,15 @@ static bool flash_window_ex(DWORD dwFlags, UINT uCount, DWORD dwTimeout) return false; /* shrug */ } -static void flash_window(int mode); -static long next_flash; -static bool flashing = false; - /* * Timer for platforms where we must maintain window flashing manually * (e.g., Win95). */ -static void flash_window_timer(void *ctx, unsigned long now) +static void flash_window_timer(void *vctx, unsigned long now) { - if (flashing && now == next_flash) { - flash_window(1); + WinGuiSeat *wgs = (WinGuiSeat *)vctx; + if (wgs->flashing && now == wgs->next_flash) { + flash_window(wgs, 1); } } @@ -5546,23 +5604,23 @@ static void flash_window_timer(void *ctx, unsigned long now) * Manage window caption / taskbar flashing, if enabled. * 0 = stop, 1 = maintain, 2 = start */ -static void flash_window(int mode) +static void flash_window(WinGuiSeat *wgs, int mode) { - int beep_ind = conf_get_int(conf, CONF_beep_ind); + int beep_ind = conf_get_int(wgs->conf, CONF_beep_ind); if ((mode == 0) || (beep_ind == B_IND_DISABLED)) { /* stop */ - if (flashing) { - flashing = false; + if (wgs->flashing) { + wgs->flashing = false; if (p_FlashWindowEx) - flash_window_ex(FLASHW_STOP, 0, 0); + flash_window_ex(wgs, FLASHW_STOP, 0, 0); else - FlashWindow(wgs.term_hwnd, false); + FlashWindow(wgs->term_hwnd, false); } } else if (mode == 2) { /* start */ - if (!flashing) { - flashing = true; + if (!wgs->flashing) { + wgs->flashing = true; if (p_FlashWindowEx) { /* For so-called "steady" mode, we use uCount=2, which * seems to be the traditional number of flashes used @@ -5570,23 +5628,21 @@ static void flash_window(int mode) * uCount=0 appears to enable continuous flashing, per * "flashing" mode, although I haven't seen this * documented. */ - flash_window_ex(FLASHW_ALL | FLASHW_TIMER, + flash_window_ex(wgs, FLASHW_ALL | FLASHW_TIMER, (beep_ind == B_IND_FLASH ? 0 : 2), 0 /* system cursor blink rate */); /* No need to schedule timer */ } else { - FlashWindow(wgs.term_hwnd, true); - next_flash = schedule_timer(450, flash_window_timer, - wgs.term_hwnd); + FlashWindow(wgs->term_hwnd, true); + wgs->next_flash = schedule_timer(450, flash_window_timer, wgs); } } } else if ((mode == 1) && (beep_ind == B_IND_FLASH)) { /* maintain */ - if (flashing && !p_FlashWindowEx) { - FlashWindow(wgs.term_hwnd, true); /* toggle */ - next_flash = schedule_timer(450, flash_window_timer, - wgs.term_hwnd); + if (wgs->flashing && !p_FlashWindowEx) { + FlashWindow(wgs->term_hwnd, true); /* toggle */ + wgs->next_flash = schedule_timer(450, flash_window_timer, wgs); } } } @@ -5596,6 +5652,7 @@ static void flash_window(int mode) */ static void wintw_bell(TermWin *tw, int mode) { + WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin); if (mode == BELL_DEFAULT) { /* * For MessageBeep style bells, we want to be careful of @@ -5603,10 +5660,9 @@ static void wintw_bell(TermWin *tw, int mode) * PlaySound bells that each one cancels the previous * active one. So we limit the rate to one per 50ms or so. */ - static long lastbeep = 0; long beepdiff; - beepdiff = GetTickCount() - lastbeep; + beepdiff = GetTickCount() - wgs->last_beep_time; if (beepdiff >= 0 && beepdiff < 50) return; MessageBeep(MB_OK); @@ -5614,28 +5670,32 @@ static void wintw_bell(TermWin *tw, int mode) * The above MessageBeep call takes time, so we record the * time _after_ it finishes rather than before it starts. */ - lastbeep = GetTickCount(); + wgs->last_beep_time = GetTickCount(); } else if (mode == BELL_WAVEFILE) { - Filename *bell_wavefile = conf_get_filename(conf, CONF_bell_wavefile); - if (!p_PlaySound || !p_PlaySound(bell_wavefile->path, NULL, - SND_ASYNC | SND_FILENAME)) { + Filename *bell_wavefile = conf_get_filename( + wgs->conf, CONF_bell_wavefile); + bool success = ( + p_PlaySoundW ? p_PlaySoundW(bell_wavefile->wpath, NULL, + SND_ASYNC | SND_FILENAME) : + p_PlaySoundA ? p_PlaySoundA(bell_wavefile->cpath, NULL, + SND_ASYNC | SND_FILENAME) : false); + if (!success) { char *buf, *otherbuf; - show_mouseptr(true); + show_mouseptr(wgs, true); buf = dupprintf( "Unable to play sound file\n%s\nUsing default sound instead", - bell_wavefile->path); + bell_wavefile->utf8path); otherbuf = dupprintf("%s Sound Error", appname); - MessageBox(wgs.term_hwnd, buf, otherbuf, - MB_OK | MB_ICONEXCLAMATION); + message_box(wgs->term_hwnd, buf, otherbuf, + MB_OK | MB_ICONEXCLAMATION, true, 0); sfree(buf); sfree(otherbuf); - conf_set_int(conf, CONF_beep, BELL_DEFAULT); + conf_set_int(wgs->conf, CONF_beep, BELL_DEFAULT); } } else if (mode == BELL_PCSPEAKER) { - static long lastbeep = 0; long beepdiff; - beepdiff = GetTickCount() - lastbeep; + beepdiff = GetTickCount() - wgs->last_beep_time; if (beepdiff >= 0 && beepdiff < 50) return; @@ -5647,11 +5707,11 @@ static void wintw_bell(TermWin *tw, int mode) Beep(800, 100); else MessageBeep(-1); - lastbeep = GetTickCount(); + wgs->last_beep_time = GetTickCount(); } /* Otherwise, either visual bell or disabled; do nothing here */ - if (!term->has_focus) { - flash_window(2); /* start */ + if (!wgs->term->has_focus) { + flash_window(wgs, 2); /* start */ } } @@ -5661,12 +5721,13 @@ static void wintw_bell(TermWin *tw, int mode) */ static void wintw_set_minimised(TermWin *tw, bool minimised) { - if (IsIconic(wgs.term_hwnd)) { + WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin); + if (IsIconic(wgs->term_hwnd)) { if (!minimised) - ShowWindow(wgs.term_hwnd, SW_RESTORE); + ShowWindow(wgs->term_hwnd, SW_RESTORE); } else { if (minimised) - ShowWindow(wgs.term_hwnd, SW_MINIMIZE); + ShowWindow(wgs->term_hwnd, SW_MINIMIZE); } } @@ -5675,13 +5736,14 @@ static void wintw_set_minimised(TermWin *tw, bool minimised) */ static void wintw_move(TermWin *tw, int x, int y) { - int resize_action = conf_get_int(conf, CONF_resize_action); + WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin); + int resize_action = conf_get_int(wgs->conf, CONF_resize_action); if (resize_action == RESIZE_DISABLED || resize_action == RESIZE_FONT || - IsZoomed(wgs.term_hwnd)) + IsZoomed(wgs->term_hwnd)) return; - SetWindowPos(wgs.term_hwnd, NULL, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER); + SetWindowPos(wgs->term_hwnd, NULL, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER); } /* @@ -5690,9 +5752,10 @@ static void wintw_move(TermWin *tw, int x, int y) */ static void wintw_set_zorder(TermWin *tw, bool top) { - if (conf_get_bool(conf, CONF_alwaysontop)) + WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin); + if (conf_get_bool(wgs->conf, CONF_alwaysontop)) return; /* ignore */ - SetWindowPos(wgs.term_hwnd, top ? HWND_TOP : HWND_BOTTOM, 0, 0, 0, 0, + SetWindowPos(wgs->term_hwnd, top ? HWND_TOP : HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); } @@ -5701,7 +5764,8 @@ static void wintw_set_zorder(TermWin *tw, bool top) */ static void wintw_refresh(TermWin *tw) { - InvalidateRect(wgs.term_hwnd, NULL, true); + WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin); + InvalidateRect(wgs->term_hwnd, NULL, true); } /* @@ -5710,40 +5774,54 @@ static void wintw_refresh(TermWin *tw) */ static void wintw_set_maximised(TermWin *tw, bool maximised) { - if (IsZoomed(wgs.term_hwnd)) { + WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin); + if (IsZoomed(wgs->term_hwnd)) { if (!maximised) - ShowWindow(wgs.term_hwnd, SW_RESTORE); + ShowWindow(wgs->term_hwnd, SW_RESTORE); } else { if (maximised) - ShowWindow(wgs.term_hwnd, SW_MAXIMIZE); + ShowWindow(wgs->term_hwnd, SW_MAXIMIZE); } } /* * See if we're in full-screen mode. */ -static bool is_full_screen() +static bool is_full_screen(WinGuiSeat *wgs) { - if (!IsZoomed(wgs.term_hwnd)) + if (!IsZoomed(wgs->term_hwnd)) return false; - if (GetWindowLongPtr(wgs.term_hwnd, GWL_STYLE) & WS_CAPTION) + if (GetWindowLongPtr(wgs->term_hwnd, GWL_STYLE) & WS_CAPTION) return false; return true; } -/* Get the rect/size of a full screen window using the nearest available - * monitor in multimon systems; default to something sensible if only - * one monitor is present. */ -static bool get_fullscreen_rect(RECT *ss) +/* Get a MONITORINFO structure for the nearest available monitor, if the + * multimon API is available and returns success. Shared subroutine between + * get_fullscreen_rect() and get_workingarea_rect(). */ +static bool get_monitor_info(WinGuiSeat *wgs, MONITORINFO *mi) { #if defined(MONITOR_DEFAULTTONEAREST) && !defined(NO_MULTIMON) if (p_GetMonitorInfoA && p_MonitorFromWindow) { HMONITOR mon; - MONITORINFO mi; - mon = p_MonitorFromWindow(wgs.term_hwnd, MONITOR_DEFAULTTONEAREST); - mi.cbSize = sizeof(mi); - p_GetMonitorInfoA(mon, &mi); + mon = p_MonitorFromWindow(wgs->term_hwnd, MONITOR_DEFAULTTONEAREST); + mi->cbSize = sizeof(*mi); + p_GetMonitorInfoA(mon, mi); + return true; + } +#endif + return false; +} + +/* Get the rect/size of a full-screen window on the nearest available + * monitor in multimon systems; default to something sensible if only + * one monitor is present. */ +static bool get_fullscreen_rect(WinGuiSeat *wgs, RECT *ss) +{ +#if defined(MONITOR_DEFAULTTONEAREST) && !defined(NO_MULTIMON) + MONITORINFO mi; + if (get_monitor_info(wgs, &mi)) { /* structure copy */ *ss = mi.rcMonitor; return true; @@ -5758,67 +5836,86 @@ static bool get_fullscreen_rect(RECT *ss) } +/* Similar to get_fullscreen_rect, but retrieves the working area of the + * monitor (minus the taskbar) instead of its full extent. */ +static bool get_workingarea_rect(WinGuiSeat *wgs, RECT *ss) +{ +#if defined(MONITOR_DEFAULTTONEAREST) && !defined(NO_MULTIMON) + MONITORINFO mi; + if (get_monitor_info(wgs, &mi)) { + /* structure copy */ + *ss = mi.rcWork; + return true; + } +#endif + /* Fallback is the same as get_monitor_rect, which is good _enough_: + * if the window overlaps the taskbar, that's not too bad a failure. */ + return GetClientRect(GetDesktopWindow(), ss); +} + + /* * Go full-screen. This should only be called when we are already * maximised. */ -static void make_full_screen() +static void make_full_screen(WinGuiSeat *wgs) { DWORD style; RECT ss; - assert(IsZoomed(wgs.term_hwnd)); + assert(IsZoomed(wgs->term_hwnd)); - if (is_full_screen()) + if (is_full_screen(wgs)) return; /* Remove the window furniture. */ - style = GetWindowLongPtr(wgs.term_hwnd, GWL_STYLE); + style = GetWindowLongPtr(wgs->term_hwnd, GWL_STYLE); style &= ~(WS_CAPTION | WS_BORDER | WS_THICKFRAME); - if (conf_get_bool(conf, CONF_scrollbar_in_fullscreen)) + if (conf_get_bool(wgs->conf, CONF_scrollbar_in_fullscreen)) style |= WS_VSCROLL; else style &= ~WS_VSCROLL; - SetWindowLongPtr(wgs.term_hwnd, GWL_STYLE, style); + SetWindowLongPtr(wgs->term_hwnd, GWL_STYLE, style); /* Resize ourselves to exactly cover the nearest monitor. */ - get_fullscreen_rect(&ss); - SetWindowPos(wgs.term_hwnd, HWND_TOP, ss.left, ss.top, + get_fullscreen_rect(wgs, &ss); + SetWindowPos(wgs->term_hwnd, HWND_TOP, ss.left, ss.top, ss.right - ss.left, ss.bottom - ss.top, SWP_FRAMECHANGED); /* We may have changed size as a result */ - reset_window(0); + reset_window(wgs, 0); /* Tick the menu item in the System and context menus. */ { int i; - for (i = 0; i < lenof(popup_menus); i++) - CheckMenuItem(popup_menus[i].menu, IDM_FULLSCREEN, MF_CHECKED); + for (i = 0; i < lenof(wgs->popup_menus); i++) + CheckMenuItem(wgs->popup_menus[i].menu, + IDM_FULLSCREEN, MF_CHECKED); } } /* * Clear the full-screen attributes. */ -static void clear_full_screen() +static void clear_full_screen(WinGuiSeat *wgs) { DWORD oldstyle, style; /* Reinstate the window furniture. */ - style = oldstyle = GetWindowLongPtr(wgs.term_hwnd, GWL_STYLE); + style = oldstyle = GetWindowLongPtr(wgs->term_hwnd, GWL_STYLE); style |= WS_CAPTION | WS_BORDER; - if (conf_get_int(conf, CONF_resize_action) == RESIZE_DISABLED) + if (conf_get_int(wgs->conf, CONF_resize_action) == RESIZE_DISABLED) style &= ~WS_THICKFRAME; else style |= WS_THICKFRAME; - if (conf_get_bool(conf, CONF_scrollbar)) + if (conf_get_bool(wgs->conf, CONF_scrollbar)) style |= WS_VSCROLL; else style &= ~WS_VSCROLL; if (style != oldstyle) { - SetWindowLongPtr(wgs.term_hwnd, GWL_STYLE, style); - SetWindowPos(wgs.term_hwnd, NULL, 0, 0, 0, 0, + SetWindowLongPtr(wgs->term_hwnd, GWL_STYLE, style); + SetWindowPos(wgs->term_hwnd, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); } @@ -5826,36 +5923,39 @@ static void clear_full_screen() /* Untick the menu item in the System and context menus. */ { int i; - for (i = 0; i < lenof(popup_menus); i++) - CheckMenuItem(popup_menus[i].menu, IDM_FULLSCREEN, MF_UNCHECKED); + for (i = 0; i < lenof(wgs->popup_menus); i++) + CheckMenuItem(wgs->popup_menus[i].menu, + IDM_FULLSCREEN, MF_UNCHECKED); } } /* * Toggle full-screen mode. */ -static void flip_full_screen() +static void flip_full_screen(WinGuiSeat *wgs) { - if (is_full_screen()) { - ShowWindow(wgs.term_hwnd, SW_RESTORE); - } else if (IsZoomed(wgs.term_hwnd)) { - make_full_screen(); + if (is_full_screen(wgs)) { + ShowWindow(wgs->term_hwnd, SW_RESTORE); + } else if (IsZoomed(wgs->term_hwnd)) { + make_full_screen(wgs); } else { - SendMessage(wgs.term_hwnd, WM_FULLSCR_ON_MAX, 0, 0); - ShowWindow(wgs.term_hwnd, SW_MAXIMIZE); + SendMessage(wgs->term_hwnd, WM_FULLSCR_ON_MAX, 0, 0); + ShowWindow(wgs->term_hwnd, SW_MAXIMIZE); } } static size_t win_seat_output(Seat *seat, SeatOutputType type, const void *data, size_t len) { - return term_data(term, data, len); + WinGuiSeat *wgs = container_of(seat, WinGuiSeat, seat); + return term_data(wgs->term, data, len); } -static void wintw_unthrottle(TermWin *win, size_t bufsize) +static void wintw_unthrottle(TermWin *tw, size_t bufsize) { - if (backend) - backend_unthrottle(backend, bufsize); + WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin); + if (wgs->backend) + backend_unthrottle(wgs->backend, bufsize); } static bool win_seat_eof(Seat *seat) @@ -5865,16 +5965,18 @@ static bool win_seat_eof(Seat *seat) static SeatPromptResult win_seat_get_userpass_input(Seat *seat, prompts_t *p) { + WinGuiSeat *wgs = container_of(seat, WinGuiSeat, seat); SeatPromptResult spr; - spr = cmdline_get_passwd_input(p, &cmdline_get_passwd_state, true); + spr = cmdline_get_passwd_input(p, &wgs->cmdline_get_passwd_state, true); if (spr.kind == SPRK_INCOMPLETE) - spr = term_get_userpass_input(term, p); + spr = term_get_userpass_input(wgs->term, p); return spr; } static void win_seat_set_trust_status(Seat *seat, bool trusted) { - term_set_trust_status(term, trusted); + WinGuiSeat *wgs = container_of(seat, WinGuiSeat, seat); + term_set_trust_status(wgs->term, trusted); } static bool win_seat_can_set_trust_status(Seat *seat) @@ -5884,14 +5986,16 @@ static bool win_seat_can_set_trust_status(Seat *seat) static bool win_seat_get_cursor_position(Seat *seat, int *x, int *y) { - term_get_cursor_position(term, x, y); + WinGuiSeat *wgs = container_of(seat, WinGuiSeat, seat); + term_get_cursor_position(wgs->term, x, y); return true; } static bool win_seat_get_window_pixel_size(Seat *seat, int *x, int *y) { + WinGuiSeat *wgs = container_of(seat, WinGuiSeat, seat); RECT r; - GetWindowRect(wgs.term_hwnd, &r); + GetWindowRect(wgs->term_hwnd, &r); *x = r.right - r.left; *y = r.bottom - r.top; return true; diff --git a/windows/x11.c b/windows/x11.c index 98bbb627..a6f2676c 100644 --- a/windows/x11.c +++ b/windows/x11.c @@ -11,9 +11,9 @@ void platform_get_x11_auth(struct X11Display *disp, Conf *conf) { - char *xauthpath = conf_get_filename(conf, CONF_xauthfile)->path; - if (xauthpath[0]) - x11_get_auth_from_authfile(disp, xauthpath); + Filename *xauthfn = conf_get_filename(conf, CONF_xauthfile); + if (!filename_is_null(xauthfn)) + x11_get_auth_from_authfile(disp, xauthfn); } const bool platform_uses_x11_unix_by_default = false;