GUACAMOLE-478: Merge add clipboard line ending normalization option for RDP.
diff --git a/src/common/common/iconv.h b/src/common/common/iconv.h
index 6381b0a..16a2a63 100644
--- a/src/common/common/iconv.h
+++ b/src/common/common/iconv.h
@@ -77,6 +77,30 @@
guac_iconv_read GUAC_READ_ISO8859_1;
/**
+ * Read function for UTF-8 which normalizes newline character sequences like
+ * "\r\n" to Unix-style newlines ('\n').
+ */
+guac_iconv_read GUAC_READ_UTF8_NORMALIZED;
+
+/**
+ * Read function for UTF-16 which normalizes newline character sequences like
+ * "\r\n" to Unix-style newlines ('\n').
+ */
+guac_iconv_read GUAC_READ_UTF16_NORMALIZED;
+
+/**
+ * Read function for CP-1252 which normalizes newline character sequences like
+ * "\r\n" to Unix-style newlines ('\n').
+ */
+guac_iconv_read GUAC_READ_CP1252_NORMALIZED;
+
+/**
+ * Read function for ISO 8859-1 which normalizes newline character sequences
+ * like "\r\n" to Unix-style newlines ('\n').
+ */
+guac_iconv_read GUAC_READ_ISO8859_1_NORMALIZED;
+
+/**
* Write function for UTF8.
*/
guac_iconv_write GUAC_WRITE_UTF8;
@@ -96,5 +120,29 @@
*/
guac_iconv_write GUAC_WRITE_ISO8859_1;
+/**
+ * Write function for UTF-8 which writes newline characters ('\n') as
+ * Windows-style newlines ("\r\n").
+ */
+guac_iconv_write GUAC_WRITE_UTF8_CRLF;
+
+/**
+ * Write function for UTF-16 which writes newline characters ('\n') as
+ * Windows-style newlines ("\r\n").
+ */
+guac_iconv_write GUAC_WRITE_UTF16_CRLF;
+
+/**
+ * Write function for CP-1252 which writes newline characters ('\n') as
+ * Windows-style newlines ("\r\n").
+ */
+guac_iconv_write GUAC_WRITE_CP1252_CRLF;
+
+/**
+ * Write function for ISO 8859-1 which writes newline characters ('\n') as
+ * Windows-style newlines ("\r\n").
+ */
+guac_iconv_write GUAC_WRITE_ISO8859_1_CRLF;
+
#endif
diff --git a/src/common/iconv.c b/src/common/iconv.c
index f4cc6c5..3bd9f5d 100644
--- a/src/common/iconv.c
+++ b/src/common/iconv.c
@@ -138,6 +138,70 @@
}
+/**
+ * Invokes the given reader function, automatically normalizing newline
+ * sequences as Unix-style newline characters ('\n'). All other charaters are
+ * read verbatim.
+ *
+ * @param reader
+ * The reader to use to read the given character.
+ *
+ * @param input
+ * Pointer to the location within the input buffer that the next character
+ * should be read from.
+ *
+ * @param remaining
+ * The number of bytes remaining in the input buffer.
+ *
+ * @return
+ * The codepoint that was read, or zero if the end of the input string has
+ * been reached.
+ */
+static int guac_iconv_read_normalized(guac_iconv_read* reader,
+ const char** input, int remaining) {
+
+ /* Read requested character */
+ const char* input_start = *input;
+ int value = reader(input, remaining);
+
+ /* Automatically translate CRLF pairs to simple newlines */
+ if (value == '\r') {
+
+ /* Peek ahead by one character, adjusting remaining bytes relative to
+ * last read */
+ int peek_remaining = remaining - (*input - input_start);
+ const char* peek_input = *input;
+ int peek_value = reader(&peek_input, peek_remaining);
+
+ /* Consider read value to be a newline if we have encountered a "\r\n"
+ * (CRLF) pair */
+ if (peek_value == '\n') {
+ value = '\n';
+ *input = peek_input;
+ }
+
+ }
+
+ return value;
+
+}
+
+int GUAC_READ_UTF8_NORMALIZED(const char** input, int remaining) {
+ return guac_iconv_read_normalized(GUAC_READ_UTF8, input, remaining);
+}
+
+int GUAC_READ_UTF16_NORMALIZED(const char** input, int remaining) {
+ return guac_iconv_read_normalized(GUAC_READ_UTF16, input, remaining);
+}
+
+int GUAC_READ_CP1252_NORMALIZED(const char** input, int remaining) {
+ return guac_iconv_read_normalized(GUAC_READ_CP1252, input, remaining);
+}
+
+int GUAC_READ_ISO8859_1_NORMALIZED(const char** input, int remaining) {
+ return guac_iconv_read_normalized(GUAC_READ_ISO8859_1, input, remaining);
+}
+
void GUAC_WRITE_UTF8(char** output, int remaining, int value) {
*output += guac_utf8_write(value, *output, remaining);
}
@@ -190,3 +254,53 @@
(*output)++;
}
+/**
+ * Invokes the given writer function, automatically writing newline characters
+ * ('\n') as CRLF ("\r\n"). All other charaters are written verbatim.
+ *
+ * @param writer
+ * The writer to use to write the given character.
+ *
+ * @param output
+ * Pointer to the location within the output buffer that the next character
+ * should be written.
+ *
+ * @param remaining
+ * The number of bytes remaining in the output buffer.
+ *
+ * @param value
+ * The codepoint of the character to write.
+ */
+static void guac_iconv_write_crlf(guac_iconv_write* writer, char** output,
+ int remaining, int value) {
+
+ if (value != '\n') {
+ writer(output, remaining, value);
+ return;
+ }
+
+ char* output_start = *output;
+ writer(output, remaining, '\r');
+
+ remaining -= *output - output_start;
+ if (remaining > 0)
+ writer(output, remaining, '\n');
+
+}
+
+void GUAC_WRITE_UTF8_CRLF(char** output, int remaining, int value) {
+ guac_iconv_write_crlf(GUAC_WRITE_UTF8, output, remaining, value);
+}
+
+void GUAC_WRITE_UTF16_CRLF(char** output, int remaining, int value) {
+ guac_iconv_write_crlf(GUAC_WRITE_UTF16, output, remaining, value);
+}
+
+void GUAC_WRITE_CP1252_CRLF(char** output, int remaining, int value) {
+ guac_iconv_write_crlf(GUAC_WRITE_CP1252, output, remaining, value);
+}
+
+void GUAC_WRITE_ISO8859_1_CRLF(char** output, int remaining, int value) {
+ guac_iconv_write_crlf(GUAC_WRITE_ISO8859_1, output, remaining, value);
+}
+
diff --git a/src/common/tests/Makefile.am b/src/common/tests/Makefile.am
index a9c1b55..526577b 100644
--- a/src/common/tests/Makefile.am
+++ b/src/common/tests/Makefile.am
@@ -33,8 +33,12 @@
check_PROGRAMS = test_common
TESTS = $(check_PROGRAMS)
+noinst_HEADERS = \
+ iconv/convert-test-data.h
+
test_common_SOURCES = \
iconv/convert.c \
+ iconv/convert-test-data.c \
rect/clip_and_split.c \
rect/constrain.c \
rect/expand_to_grid.c \
diff --git a/src/common/tests/iconv/convert-test-data.c b/src/common/tests/iconv/convert-test-data.c
new file mode 100644
index 0000000..2032e9d
--- /dev/null
+++ b/src/common/tests/iconv/convert-test-data.c
@@ -0,0 +1,153 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "common/iconv.h"
+#include "convert-test-data.h"
+
+encoding_test_parameters test_params[NUM_SUPPORTED_ENCODINGS] = {
+
+ /*
+ * UTF-8
+ */
+
+ {
+ "UTF-8",
+ GUAC_READ_UTF8, GUAC_READ_UTF8_NORMALIZED,
+ GUAC_WRITE_UTF8, GUAC_WRITE_UTF8_CRLF,
+ .test_mixed = TEST_STRING(
+ "pap\xC3\xA0 \xC3\xA8 bello\n"
+ "pap\xC3\xA0 \xC3\xA8 bello\r\n"
+ "pap\xC3\xA0 \xC3\xA8 bello\n"
+ "pap\xC3\xA0 \xC3\xA8 bello\r\n"
+ "pap\xC3\xA0 \xC3\xA8 bello"
+ ),
+ .test_unix = TEST_STRING(
+ "pap\xC3\xA0 \xC3\xA8 bello\n"
+ "pap\xC3\xA0 \xC3\xA8 bello\n"
+ "pap\xC3\xA0 \xC3\xA8 bello\n"
+ "pap\xC3\xA0 \xC3\xA8 bello\n"
+ "pap\xC3\xA0 \xC3\xA8 bello"
+ ),
+ .test_windows = TEST_STRING(
+ "pap\xC3\xA0 \xC3\xA8 bello\r\n"
+ "pap\xC3\xA0 \xC3\xA8 bello\r\n"
+ "pap\xC3\xA0 \xC3\xA8 bello\r\n"
+ "pap\xC3\xA0 \xC3\xA8 bello\r\n"
+ "pap\xC3\xA0 \xC3\xA8 bello"
+ )
+ },
+
+ /*
+ * UTF-16
+ */
+
+ {
+ "UTF-16",
+ GUAC_READ_UTF16, GUAC_READ_UTF16_NORMALIZED,
+ GUAC_WRITE_UTF16, GUAC_WRITE_UTF16_CRLF,
+ .test_mixed = TEST_STRING(
+ "p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00" "\n\x00"
+ "p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00" "\r\x00" "\n\x00"
+ "p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00" "\n\x00"
+ "p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00" "\r\x00" "\n\x00"
+ "p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00"
+ "\x00"
+ ),
+ .test_unix = TEST_STRING(
+ "p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00" "\n\x00"
+ "p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00" "\n\x00"
+ "p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00" "\n\x00"
+ "p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00" "\n\x00"
+ "p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00"
+ "\x00"
+ ),
+ .test_windows = TEST_STRING(
+ "p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00" "\r\x00" "\n\x00"
+ "p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00" "\r\x00" "\n\x00"
+ "p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00" "\r\x00" "\n\x00"
+ "p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00" "\r\x00" "\n\x00"
+ "p\x00" "a\x00" "p\x00" "\xE0\x00" " \x00" "\xE8\x00" " \x00" "b\x00" "e\x00" "l\x00" "l\x00" "o\x00"
+ "\x00"
+ )
+ },
+
+ /*
+ * ISO 8859-1
+ */
+
+ {
+ "ISO 8859-1",
+ GUAC_READ_ISO8859_1, GUAC_READ_ISO8859_1_NORMALIZED,
+ GUAC_WRITE_ISO8859_1, GUAC_WRITE_ISO8859_1_CRLF,
+ .test_mixed = TEST_STRING(
+ "pap\xE0 \xE8 bello\n"
+ "pap\xE0 \xE8 bello\r\n"
+ "pap\xE0 \xE8 bello\n"
+ "pap\xE0 \xE8 bello\r\n"
+ "pap\xE0 \xE8 bello"
+ ),
+ .test_unix = TEST_STRING(
+ "pap\xE0 \xE8 bello\n"
+ "pap\xE0 \xE8 bello\n"
+ "pap\xE0 \xE8 bello\n"
+ "pap\xE0 \xE8 bello\n"
+ "pap\xE0 \xE8 bello"
+ ),
+ .test_windows = TEST_STRING(
+ "pap\xE0 \xE8 bello\r\n"
+ "pap\xE0 \xE8 bello\r\n"
+ "pap\xE0 \xE8 bello\r\n"
+ "pap\xE0 \xE8 bello\r\n"
+ "pap\xE0 \xE8 bello"
+ )
+ },
+
+ /*
+ * CP-1252
+ */
+
+ {
+ "CP-1252",
+ GUAC_READ_CP1252, GUAC_READ_CP1252_NORMALIZED,
+ GUAC_WRITE_CP1252, GUAC_WRITE_CP1252_CRLF,
+ .test_mixed = TEST_STRING(
+ "pap\xE0 \xE8 bello\n"
+ "pap\xE0 \xE8 bello\r\n"
+ "pap\xE0 \xE8 bello\n"
+ "pap\xE0 \xE8 bello\r\n"
+ "pap\xE0 \xE8 bello"
+ ),
+ .test_unix = TEST_STRING(
+ "pap\xE0 \xE8 bello\n"
+ "pap\xE0 \xE8 bello\n"
+ "pap\xE0 \xE8 bello\n"
+ "pap\xE0 \xE8 bello\n"
+ "pap\xE0 \xE8 bello"
+ ),
+ .test_windows = TEST_STRING(
+ "pap\xE0 \xE8 bello\r\n"
+ "pap\xE0 \xE8 bello\r\n"
+ "pap\xE0 \xE8 bello\r\n"
+ "pap\xE0 \xE8 bello\r\n"
+ "pap\xE0 \xE8 bello"
+ )
+ }
+
+};
+
diff --git a/src/common/tests/iconv/convert-test-data.h b/src/common/tests/iconv/convert-test-data.h
new file mode 100644
index 0000000..f682cfb
--- /dev/null
+++ b/src/common/tests/iconv/convert-test-data.h
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "common/iconv.h"
+
+/**
+ * Representation of test string data and its length in bytes.
+ */
+typedef struct test_string {
+
+ /**
+ * The raw content of the test string.
+ */
+ unsigned char* buffer;
+
+ /**
+ * The number of bytes within the test string, including null terminator.
+ */
+ int size;
+
+} test_string;
+
+/**
+ * Convenience macro which statically-initializes a test_string with the given
+ * string value, automatically calculating its size in bytes.
+ *
+ * @param value
+ * The string value.
+ */
+#define TEST_STRING(value) { \
+ .buffer = (unsigned char*) (value), \
+ .size = sizeof(value) \
+}
+
+/**
+ * The parameters applicable to a unit test for a particular encoding supported
+ * by guac_iconv().
+ */
+typedef struct encoding_test_parameters {
+
+ /**
+ * The human-readable name of this encoding. This will be logged to the
+ * test suite log to assist with debugging test failures.
+ */
+ const char* name;
+
+ /**
+ * Reader function which reads using this encoding and does not perform any
+ * transformation on newline characters.
+ */
+ guac_iconv_read* reader;
+
+ /**
+ * Reader function which reads using this encoding and automatically
+ * normalizes newline sequences to Unix-style newline characters.
+ */
+ guac_iconv_read* reader_normalized;
+
+ /**
+ * Writer function which writes using this encoding and does not perform
+ * any transformation on newline characters.
+ */
+ guac_iconv_write* writer;
+
+ /**
+ * Writer function which writes using this encoding, but writes newline
+ * characters as CRLF sequences.
+ */
+ guac_iconv_write* writer_crlf;
+
+ /**
+ * A test string having both Windows- and Unix-style line endings. Except
+ * for the line endings, the characters represented within this test string
+ * must be identical to all other test strings.
+ */
+ test_string test_mixed;
+
+ /**
+ * A test string having only Unix-style line endings. Except for the line
+ * endings, the characters represented within this test string must be
+ * identical to all other test strings.
+ */
+ test_string test_unix;
+
+ /**
+ * A test string having only Windows-style line endings. Except for the
+ * line endings, the characters represented within this test string must be
+ * identical to all other test strings.
+ */
+ test_string test_windows;
+
+} encoding_test_parameters;
+
+/**
+ * The total number of encodings supported by guac_iconv().
+ */
+#define NUM_SUPPORTED_ENCODINGS 4
+
+/**
+ * Test parameters for each supported encoding. The test strings included each
+ * consist of five repeated lines of "papà è bello", omitting the line ending
+ * of the final line.
+ */
+extern encoding_test_parameters test_params[NUM_SUPPORTED_ENCODINGS];
+
diff --git a/src/common/tests/iconv/convert.c b/src/common/tests/iconv/convert.c
index 1a00994..d177ba0 100644
--- a/src/common/tests/iconv/convert.c
+++ b/src/common/tests/iconv/convert.c
@@ -18,48 +18,10 @@
*/
#include "common/iconv.h"
+#include "convert-test-data.h"
#include <CUnit/CUnit.h>
-
-/**
- * UTF8 for "papà è bello".
- */
-unsigned char test_string_utf8[] = {
- 'p', 'a', 'p', 0xC3, 0xA0, ' ',
- 0xC3, 0xA8, ' ',
- 'b', 'e', 'l', 'l', 'o',
- 0x00
-};
-
-/**
- * UTF16 for "papà è bello".
- */
-unsigned char test_string_utf16[] = {
- 'p', 0x00, 'a', 0x00, 'p', 0x00, 0xE0, 0x00, ' ', 0x00,
- 0xE8, 0x00, ' ', 0x00,
- 'b', 0x00, 'e', 0x00, 'l', 0x00, 'l', 0x00, 'o', 0x00,
- 0x00, 0x00
-};
-
-/**
- * ISO-8859-1 for "papà è bello".
- */
-unsigned char test_string_iso8859_1[] = {
- 'p', 'a', 'p', 0xE0, ' ',
- 0xE8, ' ',
- 'b', 'e', 'l', 'l', 'o',
- 0x00
-};
-
-/**
- * CP1252 for "papà è bello".
- */
-unsigned char test_string_cp1252[] = {
- 'p', 'a', 'p', 0xE0, ' ',
- 0xE8, ' ',
- 'b', 'e', 'l', 'l', 'o',
- 0x00
-};
+#include <stdio.h>
/**
* Tests that conversion between character sets using the given guac_iconv_read
@@ -69,25 +31,20 @@
* The guac_iconv_read implementation to use to read the input string.
*
* @param in_string
- * A pointer to the beginning of the input string.
- *
- * @param in_length
- * The size of the input string in bytes.
+ * A pointer to the test_string structure describing the input string being
+ * tested.
*
* @param writer
* The guac_iconv_write implementation to use to write the output string
* (the converted input string).
*
* @param out_string
- * A pointer to the beginning of a string which contains the expected
- * result of the conversion.
- *
- * @param out_length
- * The size of the expected result in bytes.
+ * A pointer to the test_string structure describing the expected result of
+ * the conversion.
*/
static void verify_conversion(
- guac_iconv_read* reader, unsigned char* in_string, int in_length,
- guac_iconv_write* writer, unsigned char* out_string, int out_length) {
+ guac_iconv_read* reader, test_string* in_string,
+ guac_iconv_write* writer, test_string* out_string) {
char output[4096];
char input[4096];
@@ -95,91 +52,78 @@
const char* current_input = input;
char* current_output = output;
- memcpy(input, in_string, in_length);
+ memcpy(input, in_string->buffer, in_string->size);
guac_iconv(reader, ¤t_input, sizeof(input),
writer, ¤t_output, sizeof(output));
/* Verify output length */
- CU_ASSERT_EQUAL(out_length, current_output - output);
+ CU_ASSERT_EQUAL(out_string->size, current_output - output);
/* Verify entire input read */
- CU_ASSERT_EQUAL(in_length, current_input - input);
+ CU_ASSERT_EQUAL(in_string->size, current_input - input);
/* Verify output content */
- CU_ASSERT_EQUAL(0, memcmp(output, out_string, out_length));
+ CU_ASSERT_EQUAL(0, memcmp(output, out_string->buffer, out_string->size));
}
/**
- * Tests which verifies conversion of UTF-8 to itself.
+ * Test which verifies that every supported encoding can be correctly converted
+ * to every other supported encoding, with all line endings preserved verbatim
+ * (not normalized).
*/
-void test_iconv__utf8_to_utf8() {
- verify_conversion(
- GUAC_READ_UTF8, test_string_utf8, sizeof(test_string_utf8),
- GUAC_WRITE_UTF8, test_string_utf8, sizeof(test_string_utf8));
+void test_iconv__preserve() {
+ for (int i = 0; i < NUM_SUPPORTED_ENCODINGS; i++) {
+ for (int j = 0; j < NUM_SUPPORTED_ENCODINGS; j++) {
+
+ encoding_test_parameters* from = &test_params[i];
+ encoding_test_parameters* to = &test_params[j];
+
+ printf("# \"%s\" -> \"%s\" ...\n", from->name, to->name);
+ verify_conversion(from->reader, &from->test_mixed,
+ to->writer, &to->test_mixed);
+
+ }
+ }
}
/**
- * Tests which verifies conversion of UTF-16 to UTF-8.
+ * Test which verifies that every supported encoding can be correctly converted
+ * to every other supported encoding, normalizing all line endings to
+ * Unix-style line endings.
*/
-void test_iconv__utf8_to_utf16() {
- verify_conversion(
- GUAC_READ_UTF8, test_string_utf8, sizeof(test_string_utf8),
- GUAC_WRITE_UTF16, test_string_utf16, sizeof(test_string_utf16));
+void test_iconv__normalize_unix() {
+ for (int i = 0; i < NUM_SUPPORTED_ENCODINGS; i++) {
+ for (int j = 0; j < NUM_SUPPORTED_ENCODINGS; j++) {
+
+ encoding_test_parameters* from = &test_params[i];
+ encoding_test_parameters* to = &test_params[j];
+
+ printf("# \"%s\" -> \"%s\" ...\n", from->name, to->name);
+ verify_conversion(from->reader_normalized, &from->test_mixed,
+ to->writer, &to->test_unix);
+
+ }
+ }
}
/**
- * Tests which verifies conversion of UTF-16 to itself.
+ * Test which verifies that every supported encoding can be correctly converted
+ * to every other supported encoding, normalizing all line endings to
+ * Windows-style line endings.
*/
-void test_iconv__utf16_to_utf16() {
- verify_conversion(
- GUAC_READ_UTF16, test_string_utf16, sizeof(test_string_utf16),
- GUAC_WRITE_UTF16, test_string_utf16, sizeof(test_string_utf16));
-}
+void test_iconv__normalize_crlf() {
+ for (int i = 0; i < NUM_SUPPORTED_ENCODINGS; i++) {
+ for (int j = 0; j < NUM_SUPPORTED_ENCODINGS; j++) {
-/**
- * Tests which verifies conversion of UTF-8 to UTF-16.
- */
-void test_iconv__utf16_to_utf8() {
- verify_conversion(
- GUAC_READ_UTF16, test_string_utf16, sizeof(test_string_utf16),
- GUAC_WRITE_UTF8, test_string_utf8, sizeof(test_string_utf8));
-}
+ encoding_test_parameters* from = &test_params[i];
+ encoding_test_parameters* to = &test_params[j];
-/**
- * Tests which verifies conversion of UTF-16 to ISO 8859-1.
- */
-void test_iconv__utf16_to_iso8859_1() {
- verify_conversion(
- GUAC_READ_UTF16, test_string_utf16, sizeof(test_string_utf16),
- GUAC_WRITE_ISO8859_1, test_string_iso8859_1, sizeof(test_string_iso8859_1));
-}
+ printf("# \"%s\" -> \"%s\" ...\n", from->name, to->name);
+ verify_conversion(from->reader_normalized, &from->test_mixed,
+ to->writer_crlf, &to->test_windows);
-/**
- * Tests which verifies conversion of UTF-16 to CP1252.
- */
-void test_iconv__utf16_to_cp1252() {
- verify_conversion(
- GUAC_READ_UTF16, test_string_utf16, sizeof(test_string_utf16),
- GUAC_WRITE_CP1252, test_string_cp1252, sizeof(test_string_cp1252));
-}
-
-/**
- * Tests which verifies conversion of CP1252 to UTF-8.
- */
-void test_iconv__cp1252_to_utf8() {
- verify_conversion(
- GUAC_READ_CP1252, test_string_cp1252, sizeof(test_string_cp1252),
- GUAC_WRITE_UTF8, test_string_utf8, sizeof(test_string_utf8));
-}
-
-/**
- * Tests which verifies conversion of ISO 8859-1 to UTF-8.
- */
-void test_iconv__iso8859_1_to_utf8() {
- verify_conversion(
- GUAC_READ_ISO8859_1, test_string_iso8859_1, sizeof(test_string_iso8859_1),
- GUAC_WRITE_UTF8, test_string_utf8, sizeof(test_string_utf8));
-
+ }
+ }
}
diff --git a/src/protocols/rdp/channels/cliprdr.c b/src/protocols/rdp/channels/cliprdr.c
index 261f0c9..8f9d92a 100644
--- a/src/protocols/rdp/channels/cliprdr.c
+++ b/src/protocols/rdp/channels/cliprdr.c
@@ -352,10 +352,11 @@
guac_client* client = clipboard->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
+ guac_rdp_settings* settings = rdp_client->settings;
guac_client_log(client, GUAC_LOG_TRACE, "CLIPRDR: Received format data request.");
- guac_iconv_write* writer;
+ guac_iconv_write* remote_writer;
const char* input = clipboard->clipboard->buffer;
char* output = malloc(GUAC_RDP_CLIPBOARD_MAX_LENGTH);
@@ -363,11 +364,11 @@
switch (format_data_request->requestedFormatId) {
case CF_TEXT:
- writer = GUAC_WRITE_CP1252;
+ remote_writer = settings->clipboard_crlf ? GUAC_WRITE_CP1252_CRLF : GUAC_WRITE_CP1252;
break;
case CF_UNICODETEXT:
- writer = GUAC_WRITE_UTF16;
+ remote_writer = settings->clipboard_crlf ? GUAC_WRITE_UTF16_CRLF : GUAC_WRITE_UTF16;
break;
/* Warn if clipboard data cannot be sent as intended due to a violation
@@ -386,8 +387,9 @@
/* Send received clipboard data to the RDP server in the format
* requested */
BYTE* start = (BYTE*) output;
- guac_iconv(GUAC_READ_UTF8, &input, clipboard->clipboard->length,
- writer, &output, GUAC_RDP_CLIPBOARD_MAX_LENGTH);
+ guac_iconv_read* local_reader = settings->normalize_clipboard ? GUAC_READ_UTF8_NORMALIZED : GUAC_READ_UTF8;
+ guac_iconv(local_reader, &input, clipboard->clipboard->length,
+ remote_writer, &output, GUAC_RDP_CLIPBOARD_MAX_LENGTH);
CLIPRDR_FORMAT_DATA_RESPONSE data_response = {
.requestedFormatData = (BYTE*) start,
@@ -449,7 +451,7 @@
char received_data[GUAC_RDP_CLIPBOARD_MAX_LENGTH];
- guac_iconv_read* reader;
+ guac_iconv_read* remote_reader;
const char* input = (char*) format_data_response->requestedFormatData;
char* output = received_data;
@@ -458,12 +460,12 @@
/* Non-Unicode (Windows CP-1252) */
case CF_TEXT:
- reader = GUAC_READ_CP1252;
+ remote_reader = settings->normalize_clipboard ? GUAC_READ_CP1252_NORMALIZED : GUAC_READ_CP1252;
break;
/* Unicode (UTF-16) */
case CF_UNICODETEXT:
- reader = GUAC_READ_UTF16;
+ remote_reader = settings->normalize_clipboard ? GUAC_READ_UTF16_NORMALIZED : GUAC_READ_UTF16;
break;
/* If the format ID stored within the guac_rdp_clipboard structure is actually
@@ -481,7 +483,7 @@
/* Convert, store, and forward the clipboard data received from RDP
* server */
- if (guac_iconv(reader, &input, format_data_response->dataLen,
+ if (guac_iconv(remote_reader, &input, format_data_response->dataLen,
GUAC_WRITE_UTF8, &output, sizeof(received_data))) {
int length = strnlen(received_data, sizeof(received_data));
guac_common_clipboard_reset(clipboard->clipboard, "text/plain");
diff --git a/src/protocols/rdp/settings.c b/src/protocols/rdp/settings.c
index 5a1d48e..81dfedf 100644
--- a/src/protocols/rdp/settings.c
+++ b/src/protocols/rdp/settings.c
@@ -130,6 +130,7 @@
"wol-wait-time",
"force-lossless",
+ "normalize-clipboard",
NULL
};
@@ -647,6 +648,16 @@
*/
IDX_FORCE_LOSSLESS,
+ /**
+ * Controls whether the text content of the clipboard should be
+ * automatically normalized to use a particular line ending format. Valid
+ * values are "preserve", to preserve line endings verbatim, "windows" to
+ * transform all line endings to Windows-style CRLF sequences, or "unix" to
+ * transform all line endings to Unix-style newline characters ('\n'). By
+ * default, line endings within the clipboard are preserved.
+ */
+ IDX_NORMALIZE_CLIPBOARD,
+
RDP_ARGS_COUNT
};
@@ -1167,7 +1178,36 @@
settings->disable_paste =
guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv,
IDX_DISABLE_PASTE, 0);
-
+
+ /* Normalize clipboard line endings to Unix format */
+ if (strcmp(argv[IDX_NORMALIZE_CLIPBOARD], "unix") == 0) {
+ guac_user_log(user, GUAC_LOG_INFO, "Clipboard line ending normalization: Unix (LF)");
+ settings->normalize_clipboard = 1;
+ settings->clipboard_crlf = 0;
+ }
+
+ /* Normalize clipboard line endings to Windows format */
+ else if (strcmp(argv[IDX_NORMALIZE_CLIPBOARD], "windows") == 0) {
+ guac_user_log(user, GUAC_LOG_INFO, "Clipboard line ending normalization: Windows (CRLF)");
+ settings->normalize_clipboard = 1;
+ settings->clipboard_crlf = 1;
+ }
+
+ /* Preserve clipboard line ending format */
+ else if (strcmp(argv[IDX_NORMALIZE_CLIPBOARD], "preserve") == 0) {
+ guac_user_log(user, GUAC_LOG_INFO, "Clipboard line ending normalization: Preserve (none)");
+ settings->normalize_clipboard = 0;
+ settings->clipboard_crlf = 0;
+ }
+
+ /* If nothing given, default to preserving line endings */
+ else {
+ guac_user_log(user, GUAC_LOG_INFO, "No clipboard line-ending normalization specified. Defaulting to preserving the format of all line endings.");
+ settings->normalize_clipboard = 0;
+ settings->clipboard_crlf = 0;
+ }
+
+
/* Parse Wake-on-LAN (WoL) settings */
settings->wol_send_packet =
guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv,
diff --git a/src/protocols/rdp/settings.h b/src/protocols/rdp/settings.h
index f80cde4..daaf3e9 100644
--- a/src/protocols/rdp/settings.h
+++ b/src/protocols/rdp/settings.h
@@ -324,6 +324,19 @@
int disable_paste;
/**
+ * Whether line endings within the clipboard should be automatically
+ * normalized to Unix-style newline characters.
+ */
+ int normalize_clipboard;
+
+ /**
+ * Whether Unix-style newline characters within the clipboard should be
+ * automatically translated to CRLF sequences before transmission to the
+ * RDP server.
+ */
+ int clipboard_crlf;
+
+ /**
* Whether the desktop wallpaper should be visible. If unset, the desktop
* wallpaper will be hidden, reducing the amount of bandwidth required.
*/