Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Aiden Luo 2019-11-29 17:43:26 +08:00
commit dcd85b6640
182 changed files with 10017 additions and 2530 deletions

19
.gitignore vendored
View File

@ -10,6 +10,10 @@
*.gcov
*.gcno
# Test suite output
*.log
*.trs
# Backup files
*~
@ -28,27 +32,16 @@ Makefile
Makefile.in
aclocal.m4
autom4te.cache/
build-aux/
libtool
m4/*
!README
compile
config.guess
config.h
config.h.in
config.log
config.status
config.sub
configure
depcomp
install-sh
libtool
ltmain.sh
missing
stamp-h1
test-driver
# Test binaries
tests/test_*
!tests/test_*.[ch]
# Generated docs
doc/doxygen-output

View File

@ -49,6 +49,7 @@ ARG BUILD_DEPENDENCIES=" \
libtelnet-dev \
libtool \
libvncserver-dev \
libwebsockets-dev \
libwebp-dev \
make"
@ -74,7 +75,7 @@ RUN ${PREFIX_DIR}/bin/list-dependencies.sh \
> ${PREFIX_DIR}/DEPENDENCIES
# Use same Debian as the base for the runtime image
FROM debian:${DEBIAN_VERSION}
FROM debian:${DEBIAN_VERSION}-slim
# Base directory for installed build artifacts.
# Due to limitations of the Docker image build process, this value is
@ -89,6 +90,7 @@ ENV LD_LIBRARY_PATH=${PREFIX_DIR}/lib
ENV GUACD_LOG_LEVEL=info
ARG RUNTIME_DEPENDENCIES=" \
ca-certificates \
ghostscript \
libfreerdp-plugins-standard \
fonts-liberation \

View File

@ -16,29 +16,34 @@
# specific language governing permissions and limitations
# under the License.
#
# NOTE: Parts of this file (Makefile.am) are automatically transcluded verbatim
# into Makefile.in. Though the build system (GNU Autotools) automatically adds
# its own license boilerplate to the generated Makefile.in, that boilerplate
# does not apply to the transcluded portions of Makefile.am which are licensed
# to you by the ASF under the Apache License, Version 2.0, as described above.
#
ACLOCAL_AMFLAGS = -I m4
# Subprojects
DIST_SUBDIRS = \
src/libguac \
src/common \
src/common-ssh \
src/terminal \
src/guacd \
src/guacenc \
src/guaclog \
src/pulse \
src/protocols/rdp \
src/protocols/ssh \
src/protocols/telnet \
src/protocols/vnc \
tests
DIST_SUBDIRS = \
src/libguac \
src/common \
src/common-ssh \
src/terminal \
src/guacd \
src/guacenc \
src/guaclog \
src/pulse \
src/protocols/kubernetes \
src/protocols/rdp \
src/protocols/ssh \
src/protocols/telnet \
src/protocols/vnc
SUBDIRS = \
src/libguac \
src/common \
tests
src/common
if ENABLE_COMMON_SSH
SUBDIRS += src/common-ssh
@ -52,6 +57,10 @@ if ENABLE_PULSE
SUBDIRS += src/pulse
endif
if ENABLE_KUBERNETES
SUBDIRS += src/protocols/kubernetes
endif
if ENABLE_RDP
SUBDIRS += src/protocols/rdp
endif
@ -80,13 +89,14 @@ if ENABLE_GUACLOG
SUBDIRS += src/guaclog
endif
EXTRA_DIST = \
.dockerignore \
CONTRIBUTING \
Dockerfile \
LICENSE \
NOTICE \
bin/guacctl \
doc/Doxyfile.in \
src/guacd-docker
EXTRA_DIST = \
.dockerignore \
CONTRIBUTING \
Dockerfile \
LICENSE \
NOTICE \
bin/guacctl \
doc/Doxyfile.in \
src/guacd-docker \
util/generate-test-runner.pl

2
NOTICE
View File

@ -1,5 +1,5 @@
Apache Guacamole
Copyright 2018 The Apache Software Foundation
Copyright 2019 The Apache Software Foundation
This product includes software developed at
The Apache Software Foundation (http://www.apache.org/).

89
README-unit-testing.md Normal file
View File

@ -0,0 +1,89 @@
Unit testing and guacamole-server
=================================
Unit tests within guacamole-server are implemented using the following:
* automake, which allows arbitrary tests to be declared within `Makefile.am`
and uses `make check` to run those tests.
* CUnit (libcunit), a unit testing framework.
* `util/generate-test-runner.pl`, a Perl script which generates a test runner
written in C which leverages CUnit, running the unit tests declared in each
of the given `.c` files. The generated test runner produces output in [TAP
format](https://testanything.org/) which is consumed by the TAP test driver
provided by automake.
Writing unit tests
------------------
All unit tests should be within reasonably-isolated C source files, with each
logical test having its own function of the form:
void test_SUITENAME__TESTNAME() {
...
}
where `TESTNAME` is the arbitrary name of the test and `SUITENAME` is the
arbitrary name of the test suite that this test belongs to.
**This naming convention is required by `generate-test-runner.pl`.** Absolutely
all tests MUST follow the above convention if they are to be picked up and
organized by the test runner generation script. Functions which are not tests
MUST NOT follow the above convention so that they are _not_ picked up mistakenly
by the test runner generator as if they were tests.
The `Makefile.am` for a subproject which contains such tests is typically
modified to contain a sections like the following:
#
# Unit tests for myproj
#
check_PROGRAMS = test_myproj
TESTS = $(check_PROGRAMS)
test_myproj_SOURCES = \
...all source files...
test_myproj_CFLAGS = \
-Werror -Wall -pedantic \
...other flags...
test_myproj_LDADD = \
...libraries...
#
# Autogenerate test runner
#
GEN_RUNNER = $(top_srcdir)/util/generate-test-runner.pl
CLEANFILES = _generated_runner.c
_generated_runner.c: $(test_myproj_SOURCES)
$(AM_V_GEN) $(GEN_RUNNER) $(test_myproj_SOURCES) > $@
nodist_test_libguac_SOURCES = \
_generated_runner.c
# Use automake's TAP test driver for running any tests
LOG_DRIVER = \
env AM_TAP_AWK='$(AWK)' \
$(SHELL) $(top_srcdir)/build-aux/tap-driver.sh
The above declares ...
* ... that a binary, `test_myproj` should be built from the given sources.
Note that `test_myproj_SOURCES` contains only the source which was actually
written by hand while `nodist_test_myproj_SOURCES` contains only the source
which was generated by `generate-test-runner.pl`.
* ... that this `test_myproj` binary should be run to test this project when
`make check` is run, and that automake's TAP driver should be used to
consume its output.
* ... that the `_generated_runner.c` source file is generated dynamically
(through running `generate-test-runner.pl` on all non-generated test source)
and should not be distributed as part of the source archive.
With tests following the above naming convention in place, and with the
necessary changes made to the applicable `Makefile.am`, all tests will be
run automatically when `make check` is run.

View File

@ -117,7 +117,7 @@ error() {
##
usage() {
cat >&2 <<END
guacctl 1.0.0, Apache Guacamole terminal session control utility.
guacctl 1.1.0, Apache Guacamole terminal session control utility.
Usage: guacctl [OPTION] [FILE or NAME]...
-d, --download download each of the files listed.

View File

@ -18,7 +18,8 @@
#
AC_PREREQ([2.61])
AC_INIT([guacamole-server], [1.0.0])
AC_INIT([guacamole-server], [1.1.0])
AC_CONFIG_AUX_DIR([build-aux])
AM_INIT_AUTOMAKE([-Wall -Werror foreign subdir-objects])
AM_SILENT_RULES([yes])
@ -28,6 +29,9 @@ LT_INIT([dlopen])
AC_CONFIG_HEADER([config.h])
AC_CONFIG_MACRO_DIR([m4])
# Use TAP test driver for tests (part of automake)
AC_REQUIRE_AUX_FILE([tap-driver.sh])
# Programs
AC_PROG_CC
AC_PROG_CC_C99
@ -38,6 +42,7 @@ AC_CHECK_HEADERS([fcntl.h stdlib.h string.h sys/socket.h time.h sys/time.h syslo
# Source characteristics
AC_DEFINE([_XOPEN_SOURCE], [700], [Uses X/Open and POSIX APIs])
AC_DEFINE([__BSD_VISIBLE], [1], [Uses BSD-specific APIs (if available)])
# Check for whether math library is required
AC_CHECK_LIB([m], [cos],
@ -116,6 +121,16 @@ AC_CHECK_DECL([poll],
[Whether poll() is defined])],,
[#include <poll.h>])
AC_CHECK_DECL([strlcpy],
[AC_DEFINE([HAVE_STRLCPY],,
[Whether strlcpy() is defined])],,
[#include <string.h>])
AC_CHECK_DECL([strlcat],
[AC_DEFINE([HAVE_STRLCAT],,
[Whether strlcat() is defined])],,
[#include <string.h>])
# Typedefs
AC_TYPE_SIZE_T
AC_TYPE_SSIZE_T
@ -136,6 +151,10 @@ AC_SUBST([PULSE_INCLUDE], '-I$(top_srcdir)/src/pulse')
AC_SUBST([COMMON_SSH_LTLIB], '$(top_builddir)/src/common-ssh/libguac_common_ssh.la')
AC_SUBST([COMMON_SSH_INCLUDE], '-I$(top_srcdir)/src/common-ssh')
# RDP support
AC_SUBST([LIBGUAC_CLIENT_RDP_LTLIB], '$(top_builddir)/src/protocols/rdp/libguac-client-rdp.la')
AC_SUBST([LIBGUAC_CLIENT_RDP_INCLUDE], '-I$(top_srcdir)/src/protocols/rdp')
# Terminal emulator
AC_SUBST([TERMINAL_LTLIB], '$(top_builddir)/src/terminal/libguac_terminal.la')
AC_SUBST([TERMINAL_INCLUDE], '-I$(top_srcdir)/src/terminal $(PANGO_CFLAGS) $(PANGOCAIRO_CFLAGS) $(COMMON_INCLUDE)')
@ -508,12 +527,40 @@ then
fi
#
# TLS Locking Support within libVNCServer
#
if test "x${have_libvncserver}" = "xyes"
then
have_vnc_tls_locking=yes
AC_CHECK_MEMBERS([rfbClient.LockWriteToTLS, rfbClient.UnlockWriteToTLS],
[], [have_vnc_tls_locking=no],
[[#include <rfb/rfbclient.h>]])
if test "x${have_vnc_tls_locking}" = "xno"
then
AC_MSG_WARN([
--------------------------------------------
This version of libvncclient lacks support
for TLS locking. VNC connections that use
TLS may experience instability as documented
in GUACAMOLE-414])
else
AC_DEFINE([ENABLE_VNC_TLS_LOCKING],,
[Whether support for TLS locking within VNC is enabled.])
fi
fi
#
# FreeRDP
#
have_freerdp=disabled
RDP_LIBS=
WINPR_LIBS=
AC_ARG_WITH([rdp],
[AS_HELP_STRING([--with-rdp],
[support RDP @<:@default=check@:>@])],
@ -715,6 +762,21 @@ then
[#include <freerdp/utils/stream.h>])])
fi
# Find location of Stream_New and Stream_free
if test "x${have_freerdp}" = "xyes" -a "x${have_winpr}" = "xyes"
then
AC_CHECK_LIB([winpr], [Stream_New, Stream_Free],
[WINPR_LIBS="$WINPR_LIBS -lwinpr"],
[AC_CHECK_LIB([winpr-utils], [Stream_New, Stream_Free],
[WINPR_LIBS="$WINPR_LIBS -lwinpr-utils"],
[AC_MSG_WARN([
------------------------------------------
Unable to locate stream functions in winpr
libraries. RDP will be disabled.
------------------------------------------])
have_freerdp=no])])
fi
# Check for types in WinPR
if test "x${have_freerdp}" = "xyes"
then
@ -1023,6 +1085,7 @@ AM_CONDITIONAL([ENABLE_WINPR], [test "x${have_winpr}" = "xyes"])
AM_CONDITIONAL([ENABLE_RDP], [test "x${have_freerdp}" = "xyes"])
AC_SUBST(RDP_LIBS)
AC_SUBST(WINPR_LIBS)
#
# libssh2
@ -1172,6 +1235,88 @@ fi
AM_CONDITIONAL([ENABLE_WEBP], [test "x${have_webp}" = "xyes"])
AC_SUBST(WEBP_LIBS)
#
# libwebsockets
#
have_libwebsockets=disabled
WEBSOCKETS_LIBS=
AC_ARG_WITH([websockets],
[AS_HELP_STRING([--with-websockets],
[support WebSockets @<:@default=check@:>@])],
[],
[with_websockets=check])
if test "x$with_websockets" != "xno"
then
have_libwebsockets=yes
AC_CHECK_LIB([websockets],
[lws_create_context],
[WEBSOCKETS_LIBS="$WEBSOCKETS_LIBS -lwebsockets"],
[AC_MSG_WARN([
--------------------------------------------
Unable to find libwebsockets.
Support for Kubernetes will be disabled.
--------------------------------------------])
have_libwebsockets=no])
fi
if test "x$with_websockets" != "xno"
then
# Check for client-specific closed event, which must be used in favor of
# the generic closed event if libwebsockets is recent enough to provide
# this
AC_CHECK_DECL([LWS_CALLBACK_CLIENT_CLOSED],
[AC_DEFINE([HAVE_LWS_CALLBACK_CLIENT_CLOSED],,
[Whether LWS_CALLBACK_CLIENT_CLOSED is defined])],,
[#include <libwebsockets.h>])
# Older versions of libwebsockets may not define a flag for requesting
# global initialization of OpenSSL, instead performing that initialization
# by default
AC_CHECK_DECL([LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT],
[AC_DEFINE([HAVE_LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT],,
[Whether LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT is defined])],,
[#include <libwebsockets.h>])
# Older versions of libwebsockets do not define special macros for SSL
# connection flags, instead relying on documented integer values
AC_CHECK_DECL([LCCSCF_USE_SSL],
[AC_DEFINE([HAVE_LCCSCF_USE_SSL],,
[Whether LCCSCF_USE_SSL is defined])],,
[#include <libwebsockets.h>])
# Older versions of libwebsockets do not define a dummy callback which
# must be invoked after the main event callback is invoked; the main event
# callback must instead manually return zero
AC_CHECK_DECL([lws_callback_http_dummy],
[AC_DEFINE([HAVE_LWS_CALLBACK_HTTP_DUMMY],,
[Whether lws_callback_http_dummy() is defined])],,
[#include <libwebsockets.h>])
fi
AM_CONDITIONAL([ENABLE_WEBSOCKETS],
[test "x${have_libwebsockets}" = "xyes"])
AC_SUBST(WEBSOCKETS_LIBS)
#
# Kubernetes
#
AC_ARG_ENABLE([kubernetes],
[AS_HELP_STRING([--disable-kubernetes],
[do not build support for attaching to Kubernetes pods])],
[],
[enable_kubernetes=yes])
AM_CONDITIONAL([ENABLE_KUBERNETES], [test "x${enable_kubernetes}" = "xyes" \
-a "x${have_libwebsockets}" = "xyes" \
-a "x${have_ssl}" = "xyes" \
-a "x${have_terminal}" = "xyes"])
#
# guacd
#
@ -1217,11 +1362,13 @@ AM_CONDITIONAL([ENABLE_GUACLOG], [test "x${enable_guaclog}" = "xyes"])
AC_CONFIG_FILES([Makefile
doc/Doxyfile
tests/Makefile
src/common/Makefile
src/common/tests/Makefile
src/common-ssh/Makefile
src/common-ssh/tests/Makefile
src/terminal/Makefile
src/libguac/Makefile
src/libguac/tests/Makefile
src/guacd/Makefile
src/guacd/man/guacd.8
src/guacd/man/guacd.conf.5
@ -1230,7 +1377,9 @@ AC_CONFIG_FILES([Makefile
src/guaclog/Makefile
src/guaclog/man/guaclog.1
src/pulse/Makefile
src/protocols/kubernetes/Makefile
src/protocols/rdp/Makefile
src/protocols/rdp/tests/Makefile
src/protocols/ssh/Makefile
src/protocols/telnet/Makefile
src/protocols/vnc/Makefile])
@ -1240,10 +1389,11 @@ AC_OUTPUT
# Protocol build status
#
AM_COND_IF([ENABLE_RDP], [build_rdp=yes], [build_rdp=no])
AM_COND_IF([ENABLE_SSH], [build_ssh=yes], [build_ssh=no])
AM_COND_IF([ENABLE_TELNET], [build_telnet=yes], [build_telnet=no])
AM_COND_IF([ENABLE_VNC], [build_vnc=yes], [build_vnc=no])
AM_COND_IF([ENABLE_KUBERNETES], [build_kubernetes=yes], [build_kubernetes=no])
AM_COND_IF([ENABLE_RDP], [build_rdp=yes], [build_rdp=no])
AM_COND_IF([ENABLE_SSH], [build_ssh=yes], [build_ssh=no])
AM_COND_IF([ENABLE_TELNET], [build_telnet=yes], [build_telnet=no])
AM_COND_IF([ENABLE_VNC], [build_vnc=yes], [build_vnc=no])
#
# Service / tool build status
@ -1287,15 +1437,17 @@ $PACKAGE_NAME version $PACKAGE_VERSION
libVNCServer ........ ${have_libvncserver}
libvorbis ........... ${have_vorbis}
libpulse ............ ${have_pulse}
libwebsockets ....... ${have_libwebsockets}
libwebp ............. ${have_webp}
wsock32 ............. ${have_winsock}
Protocol support:
RDP ....... ${build_rdp}
SSH ....... ${build_ssh}
Telnet .... ${build_telnet}
VNC ....... ${build_vnc}
Kubernetes .... ${build_kubernetes}
RDP ........... ${build_rdp}
SSH ........... ${build_ssh}
Telnet ........ ${build_telnet}
VNC ........... ${build_vnc}
Services / tools:

5
src/common-ssh/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
# Auto-generated test runner and binary
_generated_runner.c
test_common_ssh

View File

@ -16,11 +16,18 @@
# specific language governing permissions and limitations
# under the License.
#
# NOTE: Parts of this file (Makefile.am) are automatically transcluded verbatim
# into Makefile.in. Though the build system (GNU Autotools) automatically adds
# its own license boilerplate to the generated Makefile.in, that boilerplate
# does not apply to the transcluded portions of Makefile.am which are licensed
# to you by the ASF under the Apache License, Version 2.0, as described above.
#
AUTOMAKE_OPTIONS = foreign
ACLOCAL_AMFLAGS = -I m4
noinst_LTLIBRARIES = libguac_common_ssh.la
SUBDIRS = . tests
libguac_common_ssh_la_SOURCES = \
buffer.c \

View File

@ -255,5 +255,30 @@ int guac_common_ssh_sftp_handle_file_stream(
void guac_common_ssh_sftp_set_upload_path(
guac_common_ssh_sftp_filesystem* filesystem, const char* path);
/**
* Given an arbitrary absolute path, which may contain "..", ".", and
* backslashes, creates an equivalent absolute path which does NOT contain
* relative path components (".." or "."), backslashes, or empty path
* components. With the exception of paths referring to the root directory, the
* resulting path is guaranteed to not contain trailing slashes.
*
* Normalization will fail if the given path is not absolute, is too long, or
* contains more than GUAC_COMMON_SSH_SFTP_MAX_DEPTH path components.
*
* @param fullpath
* The buffer to populate with the normalized path. The normalized path
* will not contain relative path components like ".." or ".", nor will it
* contain backslashes. This buffer MUST be at least
* GUAC_COMMON_SSH_SFTP_MAX_PATH bytes in size.
*
* @param path
* The absolute path to normalize.
*
* @return
* Non-zero if normalization succeeded, zero otherwise.
*/
int guac_common_ssh_sftp_normalize_path(char* fullpath,
const char* path);
#endif

View File

@ -25,6 +25,23 @@
#include <guacamole/client.h>
#include <libssh2.h>
/**
* Handler for retrieving additional credentials.
*
* @param client
* The Guacamole Client associated with this need for additional
* credentials.
*
* @param cred_name
* The name of the credential being requested, which will be shared
* with the client in order to generate a meaningful prompt.
*
* @return
* A newly-allocated string containing the credentials provided by
* the user, which must be freed by a call to free().
*/
typedef char* guac_ssh_credential_handler(guac_client* client, char* cred_name);
/**
* An SSH session, backed by libssh2 and associated with a particular
* Guacamole client.
@ -50,6 +67,11 @@ typedef struct guac_common_ssh_session {
* The file descriptor of the socket being used for the SSH connection.
*/
int fd;
/**
* Callback function to retrieve credentials.
*/
guac_ssh_credential_handler* credential_handler;
} guac_common_ssh_session;
@ -92,14 +114,32 @@ void guac_common_ssh_uninit();
*
* @param user
* The user to authenticate as, once connected.
*
* @param keepalive
* How frequently the connection should send keepalive packets, in
* seconds. Zero disables keepalive packets, and 2 is the minimum
* configurable value.
*
* @param host_key
* The known public host key of the server, as provided by the client. If
* provided the identity of the server will be checked against this key,
* and a mis-match between this and the server identity will cause the
* connection to fail. If not provided, no checks will be done and the
* connection will proceed.
*
* @param credential_handler
* The handler function for retrieving additional credentials from the user
* as required by the SSH server, or NULL if the user will not be asked
* for additional credentials.
*
* @return
* A new SSH session if the connection and authentication succeed, or NULL
* if the connection or authentication were not successful.
*/
guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client,
const char* hostname, const char* port, guac_common_ssh_user* user, int keepalive,
const char* host_key);
const char* hostname, const char* port, guac_common_ssh_user* user,
int keepalive, const char* host_key,
guac_ssh_credential_handler* credential_handler);
/**
* Disconnects and destroys the given SSH session, freeing all associated

View File

@ -24,6 +24,7 @@
#include <guacamole/object.h>
#include <guacamole/protocol.h>
#include <guacamole/socket.h>
#include <guacamole/string.h>
#include <guacamole/user.h>
#include <libssh2.h>
@ -32,107 +33,70 @@
#include <stdlib.h>
#include <string.h>
/**
* Given an arbitrary absolute path, which may contain "..", ".", and
* backslashes, creates an equivalent absolute path which does NOT contain
* relative path components (".." or "."), backslashes, or empty path
* components. With the exception of paths referring to the root directory, the
* resulting path is guaranteed to not contain trailing slashes.
*
* Normalization will fail if the given path is not absolute, is too long, or
* contains more than GUAC_COMMON_SSH_SFTP_MAX_DEPTH path components.
*
* @param fullpath
* The buffer to populate with the normalized path. The normalized path
* will not contain relative path components like ".." or ".", nor will it
* contain backslashes. This buffer MUST be at least
* GUAC_COMMON_SSH_SFTP_MAX_PATH bytes in size.
*
* @param path
* The absolute path to normalize.
*
* @return
* Non-zero if normalization succeeded, zero otherwise.
*/
static int guac_common_ssh_sftp_normalize_path(char* fullpath,
int guac_common_ssh_sftp_normalize_path(char* fullpath,
const char* path) {
int i;
int path_depth = 0;
char path_component_data[GUAC_COMMON_SSH_SFTP_MAX_PATH];
const char* path_components[GUAC_COMMON_SSH_SFTP_MAX_DEPTH];
const char** current_path_component = &(path_components[0]);
const char* current_path_component_data = &(path_component_data[0]);
/* If original path is not absolute, normalization fails */
if (path[0] != '\\' && path[0] != '/')
return 1;
return 0;
/* Skip past leading slash */
path++;
/* Create scratch copy of path excluding leading slash (we will be
* replacing path separators with null terminators and referencing those
* substrings directly as path components) */
char path_scratch[GUAC_COMMON_SSH_SFTP_MAX_PATH - 1];
int length = guac_strlcpy(path_scratch, path + 1,
sizeof(path_scratch));
/* Copy path into component data for parsing */
strncpy(path_component_data, path, sizeof(path_component_data) - 1);
/* Fail if provided path is too long */
if (length >= sizeof(path_scratch))
return 0;
/* Find path components within path */
for (i = 0; i < sizeof(path_component_data) - 1; i++) {
/* Locate all path components within path */
const char* current_path_component = &(path_scratch[0]);
for (int i = 0; i <= length; i++) {
/* If current character is a path separator, parse as component */
char c = path_component_data[i];
char c = path_scratch[i];
if (c == '/' || c == '\\' || c == '\0') {
/* Terminate current component */
path_component_data[i] = '\0';
path_scratch[i] = '\0';
/* If component refers to parent, just move up in depth */
if (strcmp(current_path_component_data, "..") == 0) {
if (strcmp(current_path_component, "..") == 0) {
if (path_depth > 0)
path_depth--;
}
/* Otherwise, if component not current directory, add to list */
else if (strcmp(current_path_component_data, ".") != 0
&& strcmp(current_path_component_data, "") != 0)
path_components[path_depth++] = current_path_component_data;
else if (strcmp(current_path_component, ".") != 0
&& strcmp(current_path_component, "") != 0) {
/* If end of string, stop */
if (c == '\0')
break;
/* Fail normalization if path is too deep */
if (path_depth >= GUAC_COMMON_SSH_SFTP_MAX_DEPTH)
return 0;
path_components[path_depth++] = current_path_component;
}
/* Update start of next component */
current_path_component_data = &(path_component_data[i+1]);
current_path_component = &(path_scratch[i+1]);
} /* end if separator */
} /* end for each character */
/* If no components, the path is simply root */
if (path_depth == 0) {
strcpy(fullpath, "/");
return 1;
}
/* Add leading slash for resulting absolute path */
fullpath[0] = '/';
/* Ensure last component is null-terminated */
path_component_data[i] = 0;
/* Append normalized components to path, separated by slashes */
guac_strljoin(fullpath + 1, path_components, path_depth,
"/", GUAC_COMMON_SSH_SFTP_MAX_PATH - 1);
/* Convert components back into path */
for (; path_depth > 0; path_depth--) {
const char* filename = *(current_path_component++);
/* Add separator */
*(fullpath++) = '/';
/* Copy string */
while (*filename != 0)
*(fullpath++) = *(filename++);
}
/* Terminate absolute path */
*(fullpath++) = 0;
return 1;
}
@ -229,7 +193,7 @@ static guac_protocol_status guac_sftp_get_status(
static int guac_ssh_append_filename(char* fullpath, const char* path,
const char* filename) {
int i;
int length;
/* Disallow "." as a filename */
if (strcmp(filename, ".") == 0)
@ -239,49 +203,29 @@ static int guac_ssh_append_filename(char* fullpath, const char* path,
if (strcmp(filename, "..") == 0)
return 0;
/* Copy path, append trailing slash */
for (i=0; i<GUAC_COMMON_SSH_SFTP_MAX_PATH; i++) {
/*
* Append trailing slash only if:
* 1) Trailing slash is not already present
* 2) Path is non-empty
*/
char c = path[i];
if (c == '\0') {
if (i > 0 && path[i-1] != '/')
fullpath[i++] = '/';
break;
}
/* Copy character if not end of string */
fullpath[i] = c;
}
/* Append filename */
for (; i<GUAC_COMMON_SSH_SFTP_MAX_PATH; i++) {
char c = *(filename++);
if (c == '\0')
break;
/* Filenames may not contain slashes */
if (c == '\\' || c == '/')
return 0;
/* Append each character within filename */
fullpath[i] = c;
}
/* Verify path length is within maximum */
if (i == GUAC_COMMON_SSH_SFTP_MAX_PATH)
/* Filenames may not contain slashes */
if (strchr(filename, '/') != NULL)
return 0;
/* Terminate path string */
fullpath[i] = '\0';
/* Copy base path */
length = guac_strlcpy(fullpath, path, GUAC_COMMON_SSH_SFTP_MAX_PATH);
/*
* Append trailing slash only if:
* 1) Trailing slash is not already present
* 2) Path is non-empty
*/
if (length > 0 && fullpath[length - 1] != '/')
length += guac_strlcpy(fullpath + length, "/",
GUAC_COMMON_SSH_SFTP_MAX_PATH - length);
/* Append filename */
length += guac_strlcpy(fullpath + length, filename,
GUAC_COMMON_SSH_SFTP_MAX_PATH - length);
/* Verify path length is within maximum */
if (length >= GUAC_COMMON_SSH_SFTP_MAX_PATH)
return 0;
/* Append was successful */
return 1;
@ -310,46 +254,30 @@ static int guac_ssh_append_filename(char* fullpath, const char* path,
static int guac_ssh_append_path(char* fullpath, const char* path_a,
const char* path_b) {
int i;
int length;
/* Copy path, appending a trailing slash */
for (i = 0; i < GUAC_COMMON_SSH_SFTP_MAX_PATH; i++) {
/* Copy first half of path */
length = guac_strlcpy(fullpath, path_a, GUAC_COMMON_SSH_SFTP_MAX_PATH);
if (length >= GUAC_COMMON_SSH_SFTP_MAX_PATH)
return 0;
char c = path_a[i];
if (c == '\0') {
if (i > 0 && path_a[i-1] != '/')
fullpath[i++] = '/';
break;
}
/* Copy character if not end of string */
fullpath[i] = c;
}
/* Ensure path ends with trailing slash */
if (length == 0 || fullpath[length - 1] != '/')
length += guac_strlcpy(fullpath + length, "/",
GUAC_COMMON_SSH_SFTP_MAX_PATH - length);
/* Skip past leading slashes in second path */
while (*path_b == '/')
path_b++;
/* Append path */
for (; i < GUAC_COMMON_SSH_SFTP_MAX_PATH; i++) {
char c = *(path_b++);
if (c == '\0')
break;
/* Append each character within path */
fullpath[i] = c;
}
/* Append final half of path */
length += guac_strlcpy(fullpath + length, path_b,
GUAC_COMMON_SSH_SFTP_MAX_PATH - length);
/* Verify path length is within maximum */
if (i == GUAC_COMMON_SSH_SFTP_MAX_PATH)
if (length >= GUAC_COMMON_SSH_SFTP_MAX_PATH)
return 0;
/* Terminate path string */
fullpath[i] = '\0';
/* Append was successful */
return 1;
@ -830,8 +758,17 @@ static int guac_common_ssh_sftp_get_handler(guac_user* user,
list_state->directory = dir;
list_state->filesystem = filesystem;
strncpy(list_state->directory_name, name,
sizeof(list_state->directory_name) - 1);
int length = guac_strlcpy(list_state->directory_name, name,
sizeof(list_state->directory_name));
/* Bail out if directory name is too long to store */
if (length >= sizeof(list_state->directory_name)) {
guac_user_log(user, GUAC_LOG_INFO, "Unable to read directory "
"\"%s\": Path too long", fullpath);
free(list_state);
return 0;
}
/* Allocate stream for body */
guac_stream* stream = guac_user_alloc_stream(user);

View File

@ -304,20 +304,26 @@ static int guac_common_ssh_authenticate(guac_common_ssh_session* common_session)
LIBSSH2_SESSION* session = common_session->session;
/* Get user credentials */
char* username = user->username;
char* password = user->password;
guac_common_ssh_key* key = user->private_key;
/* Validate username provided */
if (username == NULL) {
if (user->username == NULL) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED,
"SSH authentication requires a username.");
return 1;
}
/* Get list of supported authentication methods */
char* user_authlist = libssh2_userauth_list(session, username,
strlen(username));
char* user_authlist = libssh2_userauth_list(session, user->username,
strlen(user->username));
/* If auth list is NULL, then authentication has succeeded with NONE */
if (user_authlist == NULL) {
guac_client_log(client, GUAC_LOG_DEBUG,
"SSH NONE authentication succeeded.");
return 0;
}
guac_client_log(client, GUAC_LOG_DEBUG,
"Supported authentication methods: %s", user_authlist);
@ -333,7 +339,7 @@ static int guac_common_ssh_authenticate(guac_common_ssh_session* common_session)
}
/* Attempt public key auth */
if (libssh2_userauth_publickey(session, username,
if (libssh2_userauth_publickey(session, user->username,
(unsigned char*) key->public_key, key->public_key_length,
guac_common_ssh_sign_callback, (void**) key)) {
@ -352,14 +358,18 @@ static int guac_common_ssh_authenticate(guac_common_ssh_session* common_session)
}
/* Attempt authentication with username + password. */
if (user->password == NULL && common_session->credential_handler)
user->password = common_session->credential_handler(client, "Password: ");
/* Authenticate with password, if provided */
else if (password != NULL) {
if (user->password != NULL) {
/* Check if password auth is supported on the server */
if (strstr(user_authlist, "password") != NULL) {
/* Attempt password authentication */
if (libssh2_userauth_password(session, username, password)) {
if (libssh2_userauth_password(session, user->username, user->password)) {
/* Abort on failure */
char* error_message;
@ -380,7 +390,7 @@ static int guac_common_ssh_authenticate(guac_common_ssh_session* common_session)
if (strstr(user_authlist, "keyboard-interactive") != NULL) {
/* Attempt keyboard-interactive auth using provided password */
if (libssh2_userauth_keyboard_interactive(session, username,
if (libssh2_userauth_keyboard_interactive(session, user->username,
&guac_common_ssh_kbd_callback)) {
/* Abort on failure */
@ -415,8 +425,9 @@ static int guac_common_ssh_authenticate(guac_common_ssh_session* common_session)
}
guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client,
const char* hostname, const char* port, guac_common_ssh_user* user, int keepalive,
const char* host_key) {
const char* hostname, const char* port, guac_common_ssh_user* user,
int keepalive, const char* host_key,
guac_ssh_credential_handler* credential_handler) {
int retval;
@ -561,6 +572,7 @@ guac_common_ssh_session* guac_common_ssh_create_session(guac_client* client,
common_session->user = user;
common_session->session = session;
common_session->fd = fd;
common_session->credential_handler = credential_handler;
/* Attempt authentication */
if (guac_common_ssh_authenticate(common_session)) {

View File

@ -0,0 +1,67 @@
#
# 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.
#
# NOTE: Parts of this file (Makefile.am) are automatically transcluded verbatim
# into Makefile.in. Though the build system (GNU Autotools) automatically adds
# its own license boilerplate to the generated Makefile.in, that boilerplate
# does not apply to the transcluded portions of Makefile.am which are licensed
# to you by the ASF under the Apache License, Version 2.0, as described above.
#
AUTOMAKE_OPTIONS = foreign
ACLOCAL_AMFLAGS = -I m4
#
# Unit tests for common SSH support
#
check_PROGRAMS = test_common_ssh
TESTS = $(check_PROGRAMS)
test_common_ssh_SOURCES = \
sftp/normalize_path.c
test_common_ssh_CFLAGS = \
-Werror -Wall -pedantic \
@COMMON_INCLUDE@ \
@COMMON_SSH_INCLUDE@ \
@LIBGUAC_INCLUDE@
test_common_ssh_LDADD = \
@CUNIT_LIBS@ \
@COMMON_SSH_LTLIB@ \
@COMMON_LTLIB@
#
# Autogenerate test runner
#
GEN_RUNNER = $(top_srcdir)/util/generate-test-runner.pl
CLEANFILES = _generated_runner.c
_generated_runner.c: $(test_common_ssh_SOURCES)
$(AM_V_GEN) $(GEN_RUNNER) $(test_common_ssh_SOURCES) > $@
nodist_test_common_ssh_SOURCES = \
_generated_runner.c
# Use automake's TAP test driver for running any tests
LOG_DRIVER = \
env AM_TAP_AWK='$(AWK)' \
$(SHELL) $(top_srcdir)/build-aux/tap-driver.sh

View File

@ -0,0 +1,263 @@
/*
* 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-ssh/sftp.h"
#include <CUnit/CUnit.h>
#include <stdlib.h>
/**
* Test which verifies absolute Windows-style paths are correctly normalized to
* absolute paths with UNIX separators and no relative components.
*/
void test_sftp__normalize_absolute_windows() {
char normalized[GUAC_COMMON_SSH_SFTP_MAX_PATH];
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "\\"), 0);
CU_ASSERT_NSTRING_EQUAL(normalized, "/", sizeof(normalized));
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "\\foo\\bar\\baz"), 0);
CU_ASSERT_NSTRING_EQUAL(normalized, "/foo/bar/baz", sizeof(normalized));
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "\\foo\\bar\\..\\baz\\"), 0);
CU_ASSERT_NSTRING_EQUAL(normalized, "/foo/baz", sizeof(normalized));
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "\\foo\\bar\\..\\..\\baz\\a\\..\\b"), 0);
CU_ASSERT_NSTRING_EQUAL(normalized, "/baz/b", sizeof(normalized));
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "\\foo\\.\\bar\\baz"), 0);
CU_ASSERT_NSTRING_EQUAL(normalized, "/foo/bar/baz", sizeof(normalized));
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "\\foo\\bar\\..\\..\\..\\..\\..\\..\\baz"), 0);
CU_ASSERT_NSTRING_EQUAL(normalized, "/baz", sizeof(normalized));
}
/**
* Test which verifies absolute UNIX-style paths are correctly normalized to
* absolute paths with UNIX separators and no relative components.
*/
void test_sftp__normalize_absolute_unix() {
char normalized[GUAC_COMMON_SSH_SFTP_MAX_PATH];
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "/"), 0);
CU_ASSERT_NSTRING_EQUAL(normalized, "/", sizeof(normalized));
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "/foo/bar/baz"), 0);
CU_ASSERT_NSTRING_EQUAL(normalized, "/foo/bar/baz", sizeof(normalized));
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "/foo/bar/../baz/"), 0);
CU_ASSERT_NSTRING_EQUAL(normalized, "/foo/baz", sizeof(normalized));
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "/foo/bar/../../baz/a/../b"), 0);
CU_ASSERT_NSTRING_EQUAL(normalized, "/baz/b", sizeof(normalized));
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "/foo/./bar/baz"), 0);
CU_ASSERT_NSTRING_EQUAL(normalized, "/foo/bar/baz", sizeof(normalized));
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "/foo/bar/../../../../../../baz"), 0);
CU_ASSERT_NSTRING_EQUAL(normalized, "/baz", sizeof(normalized));
}
/**
* Test which verifies absolute paths consisting of mixed Windows and UNIX path
* separators are correctly normalized to absolute paths with UNIX separators
* and no relative components.
*/
void test_sftp__normalize_absolute_mixed() {
char normalized[GUAC_COMMON_SSH_SFTP_MAX_PATH];
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "\\foo/bar\\baz"), 0);
CU_ASSERT_NSTRING_EQUAL(normalized, "/foo/bar/baz", sizeof(normalized));
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "/foo\\bar/..\\baz/"), 0);
CU_ASSERT_NSTRING_EQUAL(normalized, "/foo/baz", sizeof(normalized));
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "\\foo/bar\\../../baz\\a\\..\\b"), 0);
CU_ASSERT_NSTRING_EQUAL(normalized, "/baz/b", sizeof(normalized));
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "\\foo\\.\\bar/baz"), 0);
CU_ASSERT_NSTRING_EQUAL(normalized, "/foo/bar/baz", sizeof(normalized));
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "\\foo/bar\\../..\\..\\..\\../..\\baz"), 0);
CU_ASSERT_NSTRING_EQUAL(normalized, "/baz", sizeof(normalized));
}
/**
* Test which verifies relative Windows-style paths are always rejected.
*/
void test_sftp__normalize_relative_windows() {
char normalized[GUAC_COMMON_SSH_SFTP_MAX_PATH];
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, ""), 0);
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "."), 0);
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, ".."), 0);
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "foo"), 0);
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, ".\\foo"), 0);
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "..\\foo"), 0);
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "foo\\bar\\baz"), 0);
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, ".\\foo\\bar\\baz"), 0);
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "..\\foo\\bar\\baz"), 0);
}
/**
* Test which verifies relative UNIX-style paths are always rejected.
*/
void test_sftp__normalize_relative_unix() {
char normalized[GUAC_COMMON_SSH_SFTP_MAX_PATH];
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, ""), 0);
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "."), 0);
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, ".."), 0);
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "foo"), 0);
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "./foo"), 0);
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "../foo"), 0);
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "foo/bar/baz"), 0);
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "./foo/bar/baz"), 0);
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "../foo/bar/baz"), 0);
}
/**
* Test which verifies relative paths consisting of mixed Windows and UNIX path
* separators are always rejected.
*/
void test_sftp__normalize_relative_mixed() {
char normalized[GUAC_COMMON_SSH_SFTP_MAX_PATH];
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "foo\\bar/baz"), 0);
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, ".\\foo/bar/baz"), 0);
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, "../foo\\bar\\baz"), 0);
}
/**
* Generates a dynamically-allocated path having the given number of bytes, not
* counting the null-terminator. The path will contain only UNIX-style path
* separators. The returned path must eventually be freed with a call to
* free().
*
* @param length
* The number of bytes to include in the generated path, not counting the
* null-terminator. If -1, the length of the path will be automatically
* determined from the provided max_depth.
*
* @param max_depth
* The maximum number of path components to include within the generated
* path.
*
* @return
* A dynamically-allocated path containing the given number of bytes, not
* counting the null-terminator. This path must eventually be freed with a
* call to free().
*/
static char* generate_path(int length, int max_depth) {
/* If no length given, calculate space required from max_depth */
if (length == -1)
length = max_depth * 2;
int i;
char* input = malloc(length + 1);
/* Fill path with /x/x/x/x/x/x/x/x/x/x/.../xxxxxxxxx... */
for (i = 0; i < length; i++) {
if (max_depth > 0 && i % 2 == 0) {
input[i] = '/';
max_depth--;
}
else
input[i] = 'x';
}
/* Add null terminator */
input[length] = '\0';
return input;
}
/**
* Test which verifies that paths exceeding the maximum path length are
* rejected.
*/
void test_sftp__normalize_long() {
char* input;
char normalized[GUAC_COMMON_SSH_SFTP_MAX_PATH];
/* Exceeds maximum length by a factor of 2 */
input = generate_path(GUAC_COMMON_SSH_SFTP_MAX_PATH * 2, GUAC_COMMON_SSH_SFTP_MAX_DEPTH);
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, input), 0);
free(input);
/* Exceeds maximum length by one byte */
input = generate_path(GUAC_COMMON_SSH_SFTP_MAX_PATH, GUAC_COMMON_SSH_SFTP_MAX_DEPTH);
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, input), 0);
free(input);
/* Exactly maximum length */
input = generate_path(GUAC_COMMON_SSH_SFTP_MAX_PATH - 1, GUAC_COMMON_SSH_SFTP_MAX_DEPTH);
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, input), 0);
free(input);
}
/**
* Test which verifies that paths exceeding the maximum path depth are
* rejected.
*/
void test_sftp__normalize_deep() {
char* input;
char normalized[GUAC_COMMON_SSH_SFTP_MAX_PATH];
/* Exceeds maximum depth by a factor of 2 */
input = generate_path(-1, GUAC_COMMON_SSH_SFTP_MAX_DEPTH * 2);
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, input), 0);
free(input);
/* Exceeds maximum depth by one component */
input = generate_path(-1, GUAC_COMMON_SSH_SFTP_MAX_DEPTH + 1);
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, input), 0);
free(input);
/* Exactly maximum depth (should still be rejected as SFTP depth limits are
* set such that a path with the maximum depth will exceed the maximum
* length) */
input = generate_path(-1, GUAC_COMMON_SSH_SFTP_MAX_DEPTH);
CU_ASSERT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, input), 0);
free(input);
/* Less than maximum depth */
input = generate_path(-1, GUAC_COMMON_SSH_SFTP_MAX_DEPTH - 1);
CU_ASSERT_NOT_EQUAL(guac_common_ssh_sftp_normalize_path(normalized, input), 0);
free(input);
}

5
src/common/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
# Auto-generated test runner and binary
_generated_runner.c
test_common

View File

@ -16,11 +16,18 @@
# specific language governing permissions and limitations
# under the License.
#
# NOTE: Parts of this file (Makefile.am) are automatically transcluded verbatim
# into Makefile.in. Though the build system (GNU Autotools) automatically adds
# its own license boilerplate to the generated Makefile.in, that boilerplate
# does not apply to the transcluded portions of Makefile.am which are licensed
# to you by the ASF under the Apache License, Version 2.0, as described above.
#
AUTOMAKE_OPTIONS = foreign
ACLOCAL_AMFLAGS = -I m4
noinst_LTLIBRARIES = libguac_common.la
SUBDIRS = . tests
noinst_HEADERS = \
common/io.h \

View File

@ -23,6 +23,7 @@
#include <guacamole/client.h>
#include <guacamole/protocol.h>
#include <guacamole/stream.h>
#include <guacamole/string.h>
#include <guacamole/user.h>
#include <pthread.h>
#include <string.h>
@ -131,8 +132,7 @@ void guac_common_clipboard_reset(guac_common_clipboard* clipboard,
clipboard->length = 0;
/* Assign given mimetype */
strncpy(clipboard->mimetype, mimetype, sizeof(clipboard->mimetype) - 1);
clipboard->mimetype[sizeof(clipboard->mimetype) - 1] = '\0';
guac_strlcpy(clipboard->mimetype, mimetype, sizeof(clipboard->mimetype));
pthread_mutex_unlock(&(clipboard->lock));

View File

@ -78,13 +78,6 @@
#define cairo_format_stride_for_width(format, width) (width*4)
#endif
/**
* The JPEG image quality ('quantization') setting to use. Range 0-100 where
* 100 is the highest quality/largest file size, and 0 is the lowest
* quality/smallest file size.
*/
#define GUAC_SURFACE_JPEG_IMAGE_QUALITY 90
/**
* The framerate which, if exceeded, indicates that JPEG is preferred.
*/
@ -96,13 +89,6 @@
*/
#define GUAC_SURFACE_JPEG_MIN_BITMAP_SIZE 4096
/**
* The WebP image quality ('quantization') setting to use. Range 0-100 where
* 100 is the highest quality/largest file size, and 0 is the lowest
* quality/smallest file size.
*/
#define GUAC_SURFACE_WEBP_IMAGE_QUALITY 90
/**
* The JPEG compression min block size. This defines the optimal rectangle block
* size factor for JPEG compression. Usually 8x8 would suffice, but use 16 to
@ -1666,6 +1652,36 @@ static void __guac_common_surface_flush_to_png(guac_common_surface* surface,
}
/**
* Returns an appropriate quality between 0 and 100 for lossy encoding
* depending on the current processing lag calculated for the given client.
*
* @param client
* The client for which the lossy quality is being calculated.
*
* @return
* A value between 0 and 100 inclusive which seems appropriate for the
* client based on lag measurements.
*/
static int guac_common_surface_suggest_quality(guac_client* client) {
int lag = guac_client_get_processing_lag(client);
/* Scale quality linearly from 90 to 30 as lag varies from 20ms to 80ms */
int quality = 90 - (lag - 20);
/* Do not exceed 90 for quality */
if (quality > 90)
return 90;
/* Do not go below 30 for quality */
if (quality < 30)
return 30;
return quality;
}
/**
* Flushes the bitmap update currently described by the dirty rectangle within
* the given surface directly via an "img" instruction as JPEG data. The
@ -1702,7 +1718,7 @@ static void __guac_common_surface_flush_to_jpeg(guac_common_surface* surface) {
/* Send JPEG for rect */
guac_client_stream_jpeg(surface->client, socket, GUAC_COMP_OVER, layer,
surface->dirty_rect.x, surface->dirty_rect.y, rect,
GUAC_SURFACE_JPEG_IMAGE_QUALITY);
guac_common_surface_suggest_quality(surface->client));
cairo_surface_destroy(rect);
surface->realized = 1;
@ -1764,7 +1780,7 @@ static void __guac_common_surface_flush_to_webp(guac_common_surface* surface,
/* Send WebP for rect */
guac_client_stream_webp(surface->client, socket, GUAC_COMP_OVER, layer,
surface->dirty_rect.x, surface->dirty_rect.y, rect,
GUAC_SURFACE_WEBP_IMAGE_QUALITY, 0);
guac_common_surface_suggest_quality(surface->client), 0);
cairo_surface_destroy(rect);
surface->realized = 1;

View File

@ -0,0 +1,72 @@
#
# 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.
#
# NOTE: Parts of this file (Makefile.am) are automatically transcluded verbatim
# into Makefile.in. Though the build system (GNU Autotools) automatically adds
# its own license boilerplate to the generated Makefile.in, that boilerplate
# does not apply to the transcluded portions of Makefile.am which are licensed
# to you by the ASF under the Apache License, Version 2.0, as described above.
#
AUTOMAKE_OPTIONS = foreign
ACLOCAL_AMFLAGS = -I m4
#
# Unit tests for libguac_common
#
check_PROGRAMS = test_common
TESTS = $(check_PROGRAMS)
test_common_SOURCES = \
iconv/convert.c \
rect/clip_and_split.c \
rect/constrain.c \
rect/expand_to_grid.c \
rect/extend.c \
rect/init.c \
rect/intersects.c \
string/count_occurrences.c \
string/split.c
test_common_CFLAGS = \
-Werror -Wall -pedantic \
@COMMON_INCLUDE@
test_common_LDADD = \
@COMMON_LTLIB@ \
@CUNIT_LIBS@
#
# Autogenerate test runner
#
GEN_RUNNER = $(top_srcdir)/util/generate-test-runner.pl
CLEANFILES = _generated_runner.c
_generated_runner.c: $(test_common_SOURCES)
$(AM_V_GEN) $(GEN_RUNNER) $(test_common_SOURCES) > $@
nodist_test_common_SOURCES = \
_generated_runner.c
# Use automake's TAP test driver for running any tests
LOG_DRIVER = \
env AM_TAP_AWK='$(AWK)' \
$(SHELL) $(top_srcdir)/build-aux/tap-driver.sh

View File

@ -17,15 +17,75 @@
* under the License.
*/
#include "config.h"
#include "common_suite.h"
#include "common/iconv.h"
#include <stdlib.h>
#include <CUnit/Basic.h>
#include <CUnit/CUnit.h>
static void test_conversion(
/**
* 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
};
/**
* Tests that conversion between character sets using the given guac_iconv_read
* and guac_iconv_write implementations matches expectations.
*
* @param reader
* 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.
*
* @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.
*/
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) {
@ -50,77 +110,74 @@ static void test_conversion(
}
void test_guac_iconv() {
/* 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
};
/* UTF8 identity */
test_conversion(
/**
* Tests which verifies conversion of UTF-8 to itself.
*/
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));
}
/* UTF16 identity */
test_conversion(
GUAC_READ_UTF16, test_string_utf16, sizeof(test_string_utf16),
GUAC_WRITE_UTF16, test_string_utf16, sizeof(test_string_utf16));
/* UTF8 to UTF16 */
test_conversion(
/**
* Tests which verifies conversion of UTF-16 to UTF-8.
*/
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));
}
/* UTF16 to UTF8 */
test_conversion(
/**
* Tests which verifies conversion of UTF-16 to itself.
*/
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));
}
/**
* 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));
}
/* UTF16 to ISO-8859-1 */
test_conversion(
/**
* 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));
}
/* UTF16 to CP1252 */
test_conversion(
/**
* 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));
}
/* CP1252 to UTF8 */
test_conversion(
/**
* 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));
}
/* ISO-8859-1 to UTF8 */
test_conversion(
/**
* 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));

View File

@ -0,0 +1,156 @@
/*
* 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/rect.h"
#include <CUnit/CUnit.h>
/**
* Test which verifies that guac_common_rect_clip_and_split() divides a
* rectangle into subrectangles after removing a "hole" rectangle.
*/
void test_rect__clip_and_split() {
int res;
guac_common_rect cut;
guac_common_rect min;
guac_common_rect rect;
guac_common_rect_init(&min, 10, 10, 10, 10);
/* Clip top */
guac_common_rect_init(&rect, 10, 5, 10, 10);
res = guac_common_rect_clip_and_split(&rect, &min, &cut);
CU_ASSERT_EQUAL(1, res);
CU_ASSERT_EQUAL(10, cut.x);
CU_ASSERT_EQUAL(5, cut.y);
CU_ASSERT_EQUAL(10, cut.width);
CU_ASSERT_EQUAL(5, cut.height);
CU_ASSERT_EQUAL(10, rect.x);
CU_ASSERT_EQUAL(10, rect.y);
CU_ASSERT_EQUAL(10, rect.width);
CU_ASSERT_EQUAL(5, rect.height);
/* Clip bottom */
guac_common_rect_init(&rect, 10, 15, 10, 10);
res = guac_common_rect_clip_and_split(&rect, &min, &cut);
CU_ASSERT_EQUAL(1, res);
CU_ASSERT_EQUAL(10, cut.x);
CU_ASSERT_EQUAL(20, cut.y);
CU_ASSERT_EQUAL(10, cut.width);
CU_ASSERT_EQUAL(5, cut.height);
CU_ASSERT_EQUAL(10, rect.x);
CU_ASSERT_EQUAL(15, rect.y);
CU_ASSERT_EQUAL(10, rect.width);
CU_ASSERT_EQUAL(5, rect.height);
/* Clip left */
guac_common_rect_init(&rect, 5, 10, 10, 10);
res = guac_common_rect_clip_and_split(&rect, &min, &cut);
CU_ASSERT_EQUAL(1, res);
CU_ASSERT_EQUAL(5, cut.x);
CU_ASSERT_EQUAL(10, cut.y);
CU_ASSERT_EQUAL(5, cut.width);
CU_ASSERT_EQUAL(10, cut.height);
CU_ASSERT_EQUAL(10, rect.x);
CU_ASSERT_EQUAL(10, rect.y);
CU_ASSERT_EQUAL(5, rect.width);
CU_ASSERT_EQUAL(10, rect.height);
/* Clip right */
guac_common_rect_init(&rect, 15, 10, 10, 10);
res = guac_common_rect_clip_and_split(&rect, &min, &cut);
CU_ASSERT_EQUAL(1, res);
CU_ASSERT_EQUAL(20, cut.x);
CU_ASSERT_EQUAL(10, cut.y);
CU_ASSERT_EQUAL(5, cut.width);
CU_ASSERT_EQUAL(10, cut.height);
CU_ASSERT_EQUAL(15, rect.x);
CU_ASSERT_EQUAL(10, rect.y);
CU_ASSERT_EQUAL(5, rect.width);
CU_ASSERT_EQUAL(10, rect.height);
/*
* Test a rectangle which completely covers the hole.
* Clip and split until done.
*/
guac_common_rect_init(&rect, 5, 5, 20, 20);
/* Clip top */
res = guac_common_rect_clip_and_split(&rect, &min, &cut);
CU_ASSERT_EQUAL(1, res);
CU_ASSERT_EQUAL(5, cut.x);
CU_ASSERT_EQUAL(5, cut.y);
CU_ASSERT_EQUAL(20, cut.width);
CU_ASSERT_EQUAL(5, cut.height);
CU_ASSERT_EQUAL(5, rect.x);
CU_ASSERT_EQUAL(10, rect.y);
CU_ASSERT_EQUAL(20, rect.width);
CU_ASSERT_EQUAL(15, rect.height);
/* Clip left */
res = guac_common_rect_clip_and_split(&rect, &min, &cut);
CU_ASSERT_EQUAL(1, res);
CU_ASSERT_EQUAL(5, cut.x);
CU_ASSERT_EQUAL(10, cut.y);
CU_ASSERT_EQUAL(5, cut.width);
CU_ASSERT_EQUAL(15, cut.height);
CU_ASSERT_EQUAL(10, rect.x);
CU_ASSERT_EQUAL(10, rect.y);
CU_ASSERT_EQUAL(15, rect.width);
CU_ASSERT_EQUAL(15, rect.height);
/* Clip bottom */
res = guac_common_rect_clip_and_split(&rect, &min, &cut);
CU_ASSERT_EQUAL(1, res);
CU_ASSERT_EQUAL(10, cut.x);
CU_ASSERT_EQUAL(20, cut.y);
CU_ASSERT_EQUAL(15, cut.width);
CU_ASSERT_EQUAL(5, cut.height);
CU_ASSERT_EQUAL(10, rect.x);
CU_ASSERT_EQUAL(10, rect.y);
CU_ASSERT_EQUAL(15, rect.width);
CU_ASSERT_EQUAL(10, rect.height);
/* Clip right */
res = guac_common_rect_clip_and_split(&rect, &min, &cut);
CU_ASSERT_EQUAL(20, cut.x);
CU_ASSERT_EQUAL(10, cut.y);
CU_ASSERT_EQUAL(5, cut.width);
CU_ASSERT_EQUAL(10, cut.height);
CU_ASSERT_EQUAL(10, rect.x);
CU_ASSERT_EQUAL(10, rect.y);
CU_ASSERT_EQUAL(10, rect.width);
CU_ASSERT_EQUAL(10, rect.height);
/* Make sure nothing is left to do */
res = guac_common_rect_clip_and_split(&rect, &min, &cut);
CU_ASSERT_EQUAL(0, res);
}

View File

@ -0,0 +1,43 @@
/*
* 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/rect.h"
#include <CUnit/CUnit.h>
/**
* Test which verifies that guac_common_rect_constrain() restricts a given
* rectangle to arbitrary bounds.
*/
void test_rect__constrain() {
guac_common_rect max;
guac_common_rect rect;
guac_common_rect_init(&rect, -10, -10, 110, 110);
guac_common_rect_init(&max, 0, 0, 100, 100);
guac_common_rect_constrain(&rect, &max);
CU_ASSERT_EQUAL(0, rect.x);
CU_ASSERT_EQUAL(0, rect.y);
CU_ASSERT_EQUAL(100, rect.width);
CU_ASSERT_EQUAL(100, rect.height);
}

View File

@ -0,0 +1,71 @@
/*
* 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/rect.h"
#include <CUnit/CUnit.h>
/**
* Test which verifies guac_common_rect_expand_to_grid() properly shifts and
* resizes rectangles to fit an NxN grid.
*/
void test_rect__expand_to_grid() {
int cell_size = 16;
guac_common_rect max;
guac_common_rect rect;
/* Simple adjustment */
guac_common_rect_init(&rect, 0, 0, 25, 25);
guac_common_rect_init(&max, 0, 0, 100, 100);
guac_common_rect_expand_to_grid(cell_size, &rect, &max);
CU_ASSERT_EQUAL(0, rect.x);
CU_ASSERT_EQUAL(0, rect.y);
CU_ASSERT_EQUAL(32, rect.width);
CU_ASSERT_EQUAL(32, rect.height);
/* Adjustment with moving of rect */
guac_common_rect_init(&rect, 75, 75, 25, 25);
guac_common_rect_init(&max, 0, 0, 100, 100);
guac_common_rect_expand_to_grid(cell_size, &rect, &max);
CU_ASSERT_EQUAL(max.width - 32, rect.x);
CU_ASSERT_EQUAL(max.height - 32, rect.y);
CU_ASSERT_EQUAL(32, rect.width);
CU_ASSERT_EQUAL(32, rect.height);
guac_common_rect_init(&rect, -5, -5, 25, 25);
guac_common_rect_init(&max, 0, 0, 100, 100);
guac_common_rect_expand_to_grid(cell_size, &rect, &max);
CU_ASSERT_EQUAL(0, rect.x);
CU_ASSERT_EQUAL(0, rect.y);
CU_ASSERT_EQUAL(32, rect.width);
CU_ASSERT_EQUAL(32, rect.height);
/* Adjustment with moving and clamping of rect */
guac_common_rect_init(&rect, 0, 0, 25, 15);
guac_common_rect_init(&max, 0, 5, 32, 15);
guac_common_rect_expand_to_grid(cell_size, &rect, &max);
CU_ASSERT_EQUAL(max.x, rect.x);
CU_ASSERT_EQUAL(max.y, rect.y);
CU_ASSERT_EQUAL(max.width, rect.width);
CU_ASSERT_EQUAL(max.height, rect.height);
}

View File

@ -0,0 +1,42 @@
/*
* 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/rect.h"
#include <CUnit/CUnit.h>
/**
* Test which verifies that guac_common_rect_extend() expands the given
* rectangle as necessary to contain at least the given bounds.
*/
void test_rect__extend() {
guac_common_rect max;
guac_common_rect rect;
guac_common_rect_init(&rect, 10, 10, 90, 90);
guac_common_rect_init(&max, 0, 0, 100, 100);
guac_common_rect_extend(&rect, &max);
CU_ASSERT_EQUAL(0, rect.x);
CU_ASSERT_EQUAL(0, rect.y);
CU_ASSERT_EQUAL(100, rect.width);
CU_ASSERT_EQUAL(100, rect.height);
}

View File

@ -17,31 +17,23 @@
* under the License.
*/
#include "config.h"
#include "common/rect.h"
#include "client/client_suite.h"
#include "common/common_suite.h"
#include "protocol/suite.h"
#include "util/util_suite.h"
#include <CUnit/CUnit.h>
#include <CUnit/Basic.h>
/**
* Test which verifies rectangle initialization via guac_common_rect_init().
*/
void test_rect__init() {
int main() {
guac_common_rect max;
/* Init registry */
if (CU_initialize_registry() != CUE_SUCCESS)
return CU_get_error();
guac_common_rect_init(&max, 0, 0, 100, 100);
/* Register suites */
register_protocol_suite();
register_client_suite();
register_util_suite();
/* Run tests */
CU_basic_set_mode(CU_BRM_VERBOSE);
CU_basic_run_tests();
CU_cleanup_registry();
return CU_get_error();
CU_ASSERT_EQUAL(0, max.x);
CU_ASSERT_EQUAL(0, max.y);
CU_ASSERT_EQUAL(100, max.width);
CU_ASSERT_EQUAL(100, max.height);
}

View File

@ -0,0 +1,91 @@
/*
* 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/rect.h"
#include <CUnit/CUnit.h>
/**
* Test which verifies intersection testing via guac_common_rect_intersects().
*/
void test_rect__intersects() {
int res;
guac_common_rect min;
guac_common_rect rect;
guac_common_rect_init(&min, 10, 10, 10, 10);
/* Rectangle intersection - empty
* rectangle is outside */
guac_common_rect_init(&rect, 25, 25, 5, 5);
res = guac_common_rect_intersects(&rect, &min);
CU_ASSERT_EQUAL(0, res);
/* Rectangle intersection - complete
* rectangle is completely inside */
guac_common_rect_init(&rect, 11, 11, 5, 5);
res = guac_common_rect_intersects(&rect, &min);
CU_ASSERT_EQUAL(2, res);
/* Rectangle intersection - partial
* rectangle intersects UL */
guac_common_rect_init(&rect, 8, 8, 5, 5);
res = guac_common_rect_intersects(&rect, &min);
CU_ASSERT_EQUAL(1, res);
/* Rectangle intersection - partial
* rectangle intersects LR */
guac_common_rect_init(&rect, 18, 18, 5, 5);
res = guac_common_rect_intersects(&rect, &min);
CU_ASSERT_EQUAL(1, res);
/* Rectangle intersection - complete
* rect intersects along UL but inside */
guac_common_rect_init(&rect, 10, 10, 5, 5);
res = guac_common_rect_intersects(&rect, &min);
CU_ASSERT_EQUAL(2, res);
/* Rectangle intersection - partial
* rectangle intersects along L but outside */
guac_common_rect_init(&rect, 5, 10, 5, 5);
res = guac_common_rect_intersects(&rect, &min);
CU_ASSERT_EQUAL(1, res);
/* Rectangle intersection - complete
* rectangle intersects along LR but rest is inside */
guac_common_rect_init(&rect, 15, 15, 5, 5);
res = guac_common_rect_intersects(&rect, &min);
CU_ASSERT_EQUAL(2, res);
/* Rectangle intersection - partial
* rectangle intersects along R but rest is outside */
guac_common_rect_init(&rect, 20, 10, 5, 5);
res = guac_common_rect_intersects(&rect, &min);
CU_ASSERT_EQUAL(1, res);
/* Rectangle intersection - partial
* rectangle encloses min; which is a partial intersection */
guac_common_rect_init(&rect, 5, 5, 20, 20);
res = guac_common_rect_intersects(&rect, &min);
CU_ASSERT_EQUAL(1, res);
}

View File

@ -0,0 +1,33 @@
/*
* 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/string.h"
#include <CUnit/CUnit.h>
/**
* Test which verifies that guac_count_occurrences() counts the number of
* occurrences of an arbitrary character within a given string.
*/
void test_string__guac_count_occurrences() {
CU_ASSERT_EQUAL(4, guac_count_occurrences("this is a test string", 's'));
CU_ASSERT_EQUAL(3, guac_count_occurrences("this is a test string", 'i'));
CU_ASSERT_EQUAL(0, guac_count_occurrences("", 's'));
}

View File

@ -17,26 +17,20 @@
* under the License.
*/
#include "config.h"
#include "common_suite.h"
#include "common/string.h"
#include <CUnit/CUnit.h>
#include <stdlib.h>
#include <CUnit/Basic.h>
void test_guac_string() {
char** tokens;
/* Test occurrence counting */
CU_ASSERT_EQUAL(4, guac_count_occurrences("this is a test string", 's'));
CU_ASSERT_EQUAL(3, guac_count_occurrences("this is a test string", 'i'));
CU_ASSERT_EQUAL(0, guac_count_occurrences("", 's'));
/**
* Test which verifies that guac_split() splits a string on occurrences of a
* given character.
*/
void test_string__split() {
/* Split test string */
tokens = guac_split("this is a test string", ' ');
char** tokens = guac_split("this is a test string", ' ');
CU_ASSERT_PTR_NOT_NULL(tokens);
/* Check resulting tokens */
@ -57,7 +51,6 @@ void test_guac_string() {
CU_ASSERT_PTR_NULL(tokens[5]);
/* Clean up */
free(tokens[0]);
free(tokens[1]);

35
src/guacd/.gitignore vendored
View File

@ -13,38 +13,3 @@ guacd.exe
man/guacd.8
man/guacd.conf.5
# Object code
*.o
*.so
*.lo
*.la
# Backup files
*~
# Release files
*.tar.gz
# Files currently being edited by vim or vi
*.swp
# automake/autoconf
.deps/
.libs/
Makefile
Makefile.in
aclocal.m4
autom4te.cache/
m4/
config.guess
config.log
config.status
config.sub
configure
depcomp
install-sh
libtool
ltmain.sh
missing

View File

@ -16,6 +16,12 @@
# specific language governing permissions and limitations
# under the License.
#
# NOTE: Parts of this file (Makefile.am) are automatically transcluded verbatim
# into Makefile.in. Though the build system (GNU Autotools) automatically adds
# its own license boilerplate to the generated Makefile.in, that boilerplate
# does not apply to the transcluded portions of Makefile.am which are licensed
# to you by the ASF under the Apache License, Version 2.0, as described above.
#
AUTOMAKE_OPTIONS = foreign

View File

@ -16,6 +16,12 @@
# specific language governing permissions and limitations
# under the License.
#
# NOTE: Parts of this file (Makefile.am) are automatically transcluded verbatim
# into Makefile.in. Though the build system (GNU Autotools) automatically adds
# its own license boilerplate to the generated Makefile.in, that boilerplate
# does not apply to the transcluded portions of Makefile.am which are licensed
# to you by the ASF under the Apache License, Version 2.0, as described above.
#
AUTOMAKE_OPTIONS = foreign

View File

@ -75,8 +75,10 @@ int main(int argc, char* argv[]) {
guacenc_log(GUAC_LOG_INFO, "Guacamole video encoder (guacenc) "
"version " VERSION);
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 10, 100)
/* Prepare libavcodec */
avcodec_register_all();
#endif
/* Track number of overall failures */
int total_files = argc - optind;

View File

@ -16,6 +16,12 @@
# specific language governing permissions and limitations
# under the License.
#
# NOTE: Parts of this file (Makefile.am) are automatically transcluded verbatim
# into Makefile.in. Though the build system (GNU Autotools) automatically adds
# its own license boilerplate to the generated Makefile.in, that boilerplate
# does not apply to the transcluded portions of Makefile.am which are licensed
# to you by the ASF under the Apache License, Version 2.0, as described above.
#
AUTOMAKE_OPTIONS = foreign

View File

@ -1,41 +1,5 @@
# Object code
*.o
*.so
*.lo
*.la
# gcov files
*.gcda
*.gcov
*.gcno
# Backup files
*~
# Release files
*.tar.gz
# Files currently being edited by vim or vi
*.swp
# automake/autoconf
.deps/
.libs/
Makefile
Makefile.in
aclocal.m4
autom4te.cache/
m4/*
!README
config.guess
config.log
config.status
config.sub
configure
depcomp
install-sh
libtool
ltmain.sh
missing
# Auto-generated test runner and binary
_generated_runner.c
test_libguac

View File

@ -16,11 +16,18 @@
# specific language governing permissions and limitations
# under the License.
#
# NOTE: Parts of this file (Makefile.am) are automatically transcluded verbatim
# into Makefile.in. Though the build system (GNU Autotools) automatically adds
# its own license boilerplate to the generated Makefile.in, that boilerplate
# does not apply to the transcluded portions of Makefile.am which are licensed
# to you by the ASF under the Apache License, Version 2.0, as described above.
#
AUTOMAKE_OPTIONS = foreign
ACLOCAL_AMFLAGS = -I m4
lib_LTLIBRARIES = libguac.la
SUBDIRS = . tests
libguacincdir = $(includedir)/guacamole
@ -47,6 +54,7 @@ libguacinc_HEADERS = \
guacamole/pool.h \
guacamole/pool-types.h \
guacamole/protocol.h \
guacamole/protocol-constants.h \
guacamole/protocol-types.h \
guacamole/socket-constants.h \
guacamole/socket.h \
@ -54,6 +62,7 @@ libguacinc_HEADERS = \
guacamole/socket-types.h \
guacamole/stream.h \
guacamole/stream-types.h \
guacamole/string.h \
guacamole/timestamp.h \
guacamole/timestamp-types.h \
guacamole/unicode.h \
@ -89,6 +98,7 @@ libguac_la_SOURCES = \
socket-fd.c \
socket-nest.c \
socket-tee.c \
string.c \
timestamp.c \
unicode.c \
user.c \
@ -115,10 +125,10 @@ libguacinc_HEADERS += guacamole/socket-wsa.h
endif
libguac_la_CFLAGS = \
-Werror -Wall -pedantic -I$(srcdir)/guacamole
-Werror -Wall -pedantic
libguac_la_LDFLAGS = \
-version-info 16:0:0 \
-version-info 17:0:0 \
-no-undefined \
@CAIRO_LIBS@ \
@DL_LIBS@ \

View File

@ -19,14 +19,13 @@
#include "config.h"
#include "guacamole/audio.h"
#include "guacamole/client.h"
#include "guacamole/protocol.h"
#include "guacamole/stream.h"
#include "guacamole/user.h"
#include "raw_encoder.h"
#include <guacamole/audio.h>
#include <guacamole/client.h>
#include <guacamole/protocol.h>
#include <guacamole/stream.h>
#include <guacamole/user.h>
#include <stdlib.h>
#include <string.h>

View File

@ -19,20 +19,21 @@
#include "config.h"
#include "client.h"
#include "encode-jpeg.h"
#include "encode-png.h"
#include "encode-webp.h"
#include "error.h"
#include "guacamole/client.h"
#include "guacamole/error.h"
#include "guacamole/layer.h"
#include "guacamole/plugin.h"
#include "guacamole/pool.h"
#include "guacamole/protocol.h"
#include "guacamole/socket.h"
#include "guacamole/stream.h"
#include "guacamole/string.h"
#include "guacamole/timestamp.h"
#include "guacamole/user.h"
#include "id.h"
#include "layer.h"
#include "pool.h"
#include "plugin.h"
#include "protocol.h"
#include "socket.h"
#include "stream.h"
#include "timestamp.h"
#include "user.h"
#include <dlfcn.h>
#include <inttypes.h>
@ -441,8 +442,13 @@ int guac_client_load_plugin(guac_client* client, const char* protocol) {
} alias;
/* Add protocol and .so suffix to protocol_lib */
strncat(protocol_lib, protocol, GUAC_PROTOCOL_NAME_LIMIT-1);
strcat(protocol_lib, GUAC_PROTOCOL_LIBRARY_SUFFIX);
guac_strlcat(protocol_lib, protocol, sizeof(protocol_lib));
if (guac_strlcat(protocol_lib, GUAC_PROTOCOL_LIBRARY_SUFFIX,
sizeof(protocol_lib)) >= sizeof(protocol_lib)) {
guac_error = GUAC_STATUS_NO_MEMORY;
guac_error_message = "Protocol name is too long";
return -1;
}
/* Load client plugin */
client_plugin_handle = dlopen(protocol_lib, RTLD_LAZY);
@ -510,6 +516,26 @@ int guac_client_get_processing_lag(guac_client* client) {
}
void guac_client_stream_argv(guac_client* client, guac_socket* socket,
const char* mimetype, const char* name, const char* value) {
/* Allocate new stream for argument value */
guac_stream* stream = guac_client_alloc_stream(client);
/* Declare stream as containing connection parameter data */
guac_protocol_send_argv(socket, stream, mimetype, name);
/* Write parameter data */
guac_protocol_send_blobs(socket, stream, value, strlen(value));
/* Terminate stream */
guac_protocol_send_end(socket, stream);
/* Free allocated stream */
guac_client_free_stream(client, stream);
}
void guac_client_stream_png(guac_client* client, guac_socket* socket,
guac_composite_mode mode, const guac_layer* layer, int x, int y,
cairo_surface_t* surface) {

View File

@ -20,10 +20,10 @@
#include "config.h"
#include "encode-jpeg.h"
#include "error.h"
#include "guacamole/error.h"
#include "guacamole/protocol.h"
#include "guacamole/stream.h"
#include "palette.h"
#include "protocol.h"
#include "stream.h"
#include <cairo/cairo.h>
#include <jpeglib.h>
@ -59,7 +59,7 @@ typedef struct guac_jpeg_destination_mgr {
/**
* The output buffer.
*/
unsigned char buffer[6048];
unsigned char buffer[GUAC_PROTOCOL_BLOB_MAX_LENGTH];
} guac_jpeg_destination_mgr;

View File

@ -22,8 +22,8 @@
#include "config.h"
#include "socket.h"
#include "stream.h"
#include "guacamole/socket.h"
#include "guacamole/stream.h"
#include <cairo/cairo.h>

View File

@ -20,10 +20,10 @@
#include "config.h"
#include "encode-png.h"
#include "error.h"
#include "guacamole/error.h"
#include "guacamole/protocol.h"
#include "guacamole/stream.h"
#include "palette.h"
#include "protocol.h"
#include "stream.h"
#include <png.h>
#include <cairo/cairo.h>
@ -56,7 +56,7 @@ typedef struct guac_png_write_state {
/**
* Buffer of pending PNG data.
*/
char buffer[6048];
char buffer[GUAC_PROTOCOL_BLOB_MAX_LENGTH];
/**
* The number of bytes currently stored in the buffer.

View File

@ -22,8 +22,8 @@
#include "config.h"
#include "socket.h"
#include "stream.h"
#include "guacamole/socket.h"
#include "guacamole/stream.h"
#include <cairo/cairo.h>

View File

@ -20,10 +20,10 @@
#include "config.h"
#include "encode-webp.h"
#include "error.h"
#include "guacamole/error.h"
#include "guacamole/protocol.h"
#include "guacamole/stream.h"
#include "palette.h"
#include "protocol.h"
#include "stream.h"
#include <cairo/cairo.h>
#include <webp/encode.h>
@ -52,7 +52,7 @@ typedef struct guac_webp_stream_writer {
/**
* Buffer of pending WebP data.
*/
char buffer[6048];
char buffer[GUAC_PROTOCOL_BLOB_MAX_LENGTH];
/**
* The number of bytes currently stored in the buffer.

View File

@ -22,8 +22,8 @@
#include "config.h"
#include "socket.h"
#include "stream.h"
#include "guacamole/socket.h"
#include "guacamole/stream.h"
#include <cairo/cairo.h>

View File

@ -19,7 +19,7 @@
#include "config.h"
#include "error.h"
#include "guacamole/error.h"
#include <errno.h>
#include <stdlib.h>

View File

@ -548,6 +548,33 @@ int guac_client_load_plugin(guac_client* client, const char* protocol);
*/
int guac_client_get_processing_lag(guac_client* client);
/**
* Streams the given connection parameter value over an argument value stream
* ("argv" instruction), exposing the current value of the named connection
* parameter to all users of the given client. The argument value stream will
* be automatically allocated and freed.
*
* @param client
* The Guacamole client for which the argument value stream should be
* allocated.
*
* @param socket
* The socket over which instructions associated with the argument value
* stream should be sent.
*
* @param mimetype
* The mimetype of the data within the connection parameter value being
* sent.
*
* @param name
* The name of the connection parameter being sent.
*
* @param value
* The current value of the connection parameter being sent.
*/
void guac_client_stream_argv(guac_client* client, guac_socket* socket,
const char* mimetype, const char* name, const char* value);
/**
* Streams the image data of the given surface over an image stream ("img"
* instruction) as PNG-encoded data. The image stream will be automatically

View File

@ -0,0 +1,53 @@
/*
* 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.
*/
#ifndef GUAC_PROTOCOL_CONSTANTS_H
#define GUAC_PROTOCOL_CONSTANTS_H
/**
* Constants related to the Guacamole protocol.
*
* @file protocol-constants.h
*/
/**
* This defines the overall protocol version that this build of libguac
* supports. The protocol version is used to provide compatibility between
* potentially different versions of Guacamole server and clients. The
* version number is a MAJOR_MINOR_PATCH version that matches the versioning
* used throughout the components of the Guacamole project. This version
* will not necessarily increment with the other components, unless additional
* functionality is introduced that affects compatibility.
*
* This version is passed by the __guac_protocol_send_args() function from the
* server to the client during the client/server handshake.
*/
#define GUACAMOLE_PROTOCOL_VERSION "VERSION_1_1_0"
/**
* The maximum number of bytes that should be sent in any one blob instruction
* to ensure the instruction does not exceed the maximum allowed instruction
* size.
*
* @see GUAC_INSTRUCTION_MAX_LENGTH
*/
#define GUAC_PROTOCOL_BLOB_MAX_LENGTH 6048
#endif

View File

@ -30,6 +30,7 @@
#include "layer-types.h"
#include "object-types.h"
#include "protocol-constants.h"
#include "protocol-types.h"
#include "socket-types.h"
#include "stream-types.h"
@ -214,6 +215,13 @@ int guac_protocol_send_mouse(guac_socket* socket, int x, int y,
* If an error occurs sending the instruction, a non-zero value is
* returned, and guac_error is set appropriately.
*
* @deprecated
* The "nest" instruction and the corresponding guac_socket
* implementation are no longer necessary, having been replaced by
* the streaming instructions ("blob", "ack", "end"). Code using nested
* sockets or the "nest" instruction should instead write to a normal
* socket directly.
*
* @param socket The guac_socket connection to use.
* @param index The integer index of the stram to send the protocol
* data over.
@ -427,6 +435,37 @@ int guac_protocol_send_pipe(guac_socket* socket, const guac_stream* stream,
int guac_protocol_send_blob(guac_socket* socket, const guac_stream* stream,
const void* data, int count);
/**
* Sends a series of blob instructions, splitting the given data across the
* number of instructions required to ensure the size of each blob does not
* exceed GUAC_PROTOCOL_BLOB_MAX_LENGTH. If the size of data provided is zero,
* no blob instructions are sent.
*
* If an error occurs sending any blob instruction, a non-zero value is
* returned, guac_error is set appropriately, and no further blobs are sent.
*
* @see GUAC_PROTOCOL_BLOB_MAX_LENGTH
*
* @param socket
* The guac_socket connection to use to send the blob instructions.
*
* @param stream
* The stream to associate with each blob sent.
*
* @param data
* The data which should be sent using the required number of blob
* instructions.
*
* @param count
* The number of bytes within the given buffer of data that must be
* written.
*
* @return
* Zero on success, non-zero on error.
*/
int guac_protocol_send_blobs(guac_socket* socket, const guac_stream* stream,
const void* data, int count);
/**
* Sends an end instruction over the given guac_socket connection.
*
@ -911,6 +950,31 @@ int guac_protocol_send_size(guac_socket* socket, const guac_layer* layer,
/* TEXT INSTRUCTIONS */
/**
* Sends an argv instruction over the given guac_socket connection.
*
* If an error occurs sending the instruction, a non-zero value is
* returned, and guac_error is set appropriately.
*
* @param socket
* The guac_socket connection to use to send the connection parameter
* value.
*
* @param stream
* The stream to use to send the connection parameter value.
*
* @param mimetype
* The mimetype of the connection parameter value being sent.
*
* @param name
* The name of the connection parameter whose current value is being sent.
*
* @return
* Zero on success, non-zero on error.
*/
int guac_protocol_send_argv(guac_socket* socket, guac_stream* stream,
const char* mimetype, const char* name);
/**
* Sends a clipboard instruction over the given guac_socket connection.
*

View File

@ -180,6 +180,13 @@ guac_socket* guac_socket_open(int fd);
* If an error occurs while allocating the guac_socket object, NULL is returned,
* and guac_error is set appropriately.
*
* @deprecated
* The "nest" instruction and the corresponding guac_socket
* implementation are no longer necessary, having been replaced by
* the streaming instructions ("blob", "ack", "end"). Code using nested
* sockets or the "nest" instruction should instead write to a normal
* socket directly.
*
* @param parent The guac_socket this new guac_socket should write nest
* instructions to.
* @param index The stream index to use for the written nest instructions.

View File

@ -0,0 +1,161 @@
/*
* 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.
*/
#ifndef GUAC_STRING_H
#define GUAC_STRING_H
/**
* Provides convenience functions for manipulating strings.
*
* @file string.h
*/
#include <stddef.h>
#include <string.h>
/**
* Copies a limited number of bytes from the given source string to the given
* destination buffer. The resulting buffer will always be null-terminated,
* even if doing so means that the intended string is truncated, unless the
* destination buffer has no space available at all. As this function always
* returns the length of the string it tried to create (the length of the
* source string), whether truncation has occurred can be detected by comparing
* the return value against the size of the destination buffer. If the value
* returned is greater than or equal to the size of the destination buffer, then
* the string has been truncated.
*
* The source and destination buffers MAY NOT overlap.
*
* @param dest
* The buffer which should receive the contents of the source string. This
* buffer will always be null terminated unless zero bytes are available
* within the buffer.
*
* @param src
* The source string to copy into the destination buffer. This string MUST
* be null terminated.
*
* @param n
* The number of bytes available within the destination buffer. If this
* value is zero, no bytes will be written to the destination buffer, and
* the destination buffer may not be null terminated. In all other cases,
* the destination buffer will always be null terminated, even if doing
* so means that the copied data from the source string will be truncated.
*
* @return
* The length of the copied string (the source string) in bytes, excluding
* the null terminator.
*/
size_t guac_strlcpy(char* restrict dest, const char* restrict src, size_t n);
/**
* Appends the given source string after the end of the given destination
* string, writing at most the given number of bytes. Both the source and
* destination strings MUST be null-terminated. The resulting buffer will
* always be null-terminated, even if doing so means that the intended string
* is truncated, unless the destination buffer has no space available at all.
* As this function always returns the length of the string it tried to create
* (the length of destination and source strings added together), whether
* truncation has occurred can be detected by comparing the return value
* against the size of the destination buffer. If the value returned is greater
* than or equal to the size of the destination buffer, then the string has
* been truncated.
*
* The source and destination buffers MAY NOT overlap.
*
* @param dest
* The buffer which should be appended with the contents of the source
* string. This buffer MUST already be null-terminated and will always be
* null-terminated unless zero bytes are available within the buffer.
*
* As a safeguard against incorrectly-written code, in the event that the
* destination buffer is not null-terminated, this function will still stop
* before overrunning the buffer, instead behaving as if the length of the
* string in the buffer is exactly the size of the buffer. The destination
* buffer will remain untouched (and unterminated) in this case.
*
* @param src
* The source string to append to the the destination buffer. This string
* MUST be null-terminated.
*
* @param n
* The number of bytes available within the destination buffer. If this
* value is not greater than zero, no bytes will be written to the
* destination buffer, and the destination buffer may not be
* null-terminated. In all other cases, the destination buffer will always
* be null-terminated, even if doing so means that the copied data from the
* source string will be truncated.
*
* @return
* The length of the string this function tried to create (the lengths of
* the source and destination strings added together) in bytes, excluding
* the null terminator.
*/
size_t guac_strlcat(char* restrict dest, const char* restrict src, size_t n);
/**
* Concatenates each of the given strings, separated by the given delimiter,
* storing the result within a destination buffer. The number of bytes written
* will be no more than the given number of bytes, and the destination buffer
* is guaranteed to be null-terminated, even if doing so means that one or more
* of the intended strings are truncated or omitted from the end of the result,
* unless the destination buffer has no space available at all. As this
* function always returns the length of the string it tried to create (the
* length of all source strings and all delimiters added together), whether
* truncation has occurred can be detected by comparing the return value
* against the size of the destination buffer. If the value returned is greater
* than or equal to the size of the destination buffer, then the string has
* been truncated.
*
* The source strings, delimiter string, and destination buffer MAY NOT
* overlap.
*
* @param dest
* The buffer which should receive the result of joining the given strings.
* This buffer will always be null terminated unless zero bytes are
* available within the buffer.
*
* @param elements
* The elements to concatenate together, separated by the given delimiter.
* Each element MUST be null-terminated.
*
* @param nmemb
* The number of elements within the elements array.
*
* @param delim
* The delimiter to include between each pair of elements.
*
* @param n
* The number of bytes available within the destination buffer. If this
* value is not greater than zero, no bytes will be written to the
* destination buffer, and the destination buffer may not be null
* terminated. In all other cases, the destination buffer will always be
* null terminated, even if doing so means that the result will be
* truncated.
*
* @return
* The length of the string this function tried to create (the length of
* all source strings and all delimiters added together) in bytes,
* excluding the null terminator.
*/
size_t guac_strljoin(char* restrict dest, const char* restrict const* elements,
int nmemb, const char* restrict delim, size_t n);
#endif

View File

@ -237,6 +237,36 @@ typedef int guac_user_file_handler(guac_user* user, guac_stream* stream,
typedef int guac_user_pipe_handler(guac_user* user, guac_stream* stream,
char* mimetype, char* name);
/**
* Handler for Guacamole argument value (argv) streams received from a user.
* Argument value streams are real-time revisions to the connection parameters
* of an in-progress connection. Each such argument value stream begins when
* the user sends a "argv" instruction. To handle received data along this
* stream, implementations of this handler must assign blob and end handlers to
* the given stream object.
*
* @param user
* The user that opened the argument value stream.
*
* @param stream
* The stream object allocated by libguac to represent the argument value
* stream opened by the user.
*
* @param mimetype
* The mimetype of the data that will be sent along the stream.
*
* @param name
* The name of the connection parameter being updated. It is up to the
* implementation of this handler to decide whether and how to update a
* connection parameter.
*
* @return
* Zero if the opening of the argument value stream has been handled
* successfully, or non-zero if an error occurs.
*/
typedef int guac_user_argv_handler(guac_user* user, guac_stream* stream,
char* mimetype, char* name);
/**
* Handler for Guacamole stream blobs. Each blob originates from a "blob"
* instruction which was associated with a previously-created stream.

View File

@ -88,6 +88,13 @@ struct guac_user_info {
* stated resolution of the display size request is recommended.
*/
int optimal_resolution;
/**
* The timezone of the remote system. If the client does not provide
* a specific timezone then this will be NULL. The format of the timezone
* is the standard tzdata naming convention.
*/
const char* timezone;
};
@ -475,6 +482,27 @@ struct guac_user {
*/
guac_user_audio_handler* audio_handler;
/**
* Handler for argv events (updates to the connection parameters of an
* in-progress connection) sent by the Guacamole web-client.
*
* The handler takes a guac_stream which contains the stream index and
* will persist through the duration of the transfer, the mimetype of
* the data being transferred, and the argument (connection parameter)
* name.
*
* Example:
* @code
* int argv_handler(guac_user* user, guac_stream* stream,
* char* mimetype, char* name);
*
* int guac_user_init(guac_user* user, int argc, char** argv) {
* user->argv_handler = argv_handler;
* }
* @endcode
*/
guac_user_argv_handler* argv_handler;
};
/**
@ -519,7 +547,7 @@ int guac_user_handle_connection(guac_user* user, int usec_timeout);
/**
* Call the appropriate handler defined by the given user for the given
* instruction. A comparison is made between the instruction opcode and the
* initial handler lookup table defined in user-handlers.c. The intial handlers
* initial handler lookup table defined in user-handlers.c. The initial handlers
* will in turn call the user's handler (if defined).
*
* @param user
@ -648,6 +676,32 @@ guac_object* guac_user_alloc_object(guac_user* user);
*/
void guac_user_free_object(guac_user* user, guac_object* object);
/**
* Streams the given connection parameter value over an argument value stream
* ("argv" instruction), exposing the current value of the named connection
* parameter to the given user. The argument value stream will be automatically
* allocated and freed.
*
* @param user
* The Guacamole user who should receive the connection parameter value.
*
* @param socket
* The socket over which instructions associated with the argument value
* stream should be sent.
*
* @param mimetype
* The mimetype of the data within the connection parameter value being
* sent.
*
* @param name
* The name of the connection parameter being sent.
*
* @param value
* The current value of the connection parameter being sent.
*/
void guac_user_stream_argv(guac_user* user, guac_socket* socket,
const char* mimetype, const char* name, const char* value);
/**
* Streams the image data of the given surface over an image stream ("img"
* instruction) as PNG-encoded data. The image stream will be automatically

View File

@ -19,8 +19,8 @@
#include "config.h"
#include "guacamole/error.h"
#include "id.h"
#include "error.h"
#ifdef HAVE_OSSP_UUID_H
#include <ossp/uuid.h>

View File

@ -19,10 +19,10 @@
#include "config.h"
#include "error.h"
#include "parser.h"
#include "socket.h"
#include "unicode.h"
#include "guacamole/error.h"
#include "guacamole/parser.h"
#include "guacamole/socket.h"
#include "guacamole/unicode.h"
#include <stdlib.h>
#include <stdio.h>

View File

@ -19,7 +19,7 @@
#include "config.h"
#include "pool.h"
#include "guacamole/pool.h"
#include <stdlib.h>

View File

@ -19,14 +19,14 @@
#include "config.h"
#include "error.h"
#include "layer.h"
#include "object.h"
#include "guacamole/error.h"
#include "guacamole/layer.h"
#include "guacamole/object.h"
#include "guacamole/protocol.h"
#include "guacamole/socket.h"
#include "guacamole/stream.h"
#include "guacamole/unicode.h"
#include "palette.h"
#include "protocol.h"
#include "socket.h"
#include "stream.h"
#include "unicode.h"
#include <cairo/cairo.h>
@ -93,6 +93,11 @@ static int __guac_protocol_send_args(guac_socket* socket, const char** args) {
int i;
if (guac_socket_write_string(socket, "4.args")) return -1;
/* Send protocol version ahead of other args. */
if (guac_socket_write_string(socket, ",")
|| __guac_socket_write_length_string(socket, GUACAMOLE_PROTOCOL_VERSION))
return -1;
for (i=0; args[i] != NULL; i++) {
@ -120,6 +125,26 @@ int guac_protocol_send_args(guac_socket* socket, const char** args) {
}
int guac_protocol_send_argv(guac_socket* socket, guac_stream* stream,
const char* mimetype, const char* name) {
int ret_val;
guac_socket_instruction_begin(socket);
ret_val =
guac_socket_write_string(socket, "4.argv,")
|| __guac_socket_write_length_int(socket, stream->index)
|| guac_socket_write_string(socket, ",")
|| __guac_socket_write_length_string(socket, mimetype)
|| guac_socket_write_string(socket, ",")
|| __guac_socket_write_length_string(socket, name)
|| guac_socket_write_string(socket, ";");
guac_socket_instruction_end(socket);
return ret_val;
}
int guac_protocol_send_arc(guac_socket* socket, const guac_layer* layer,
int x, int y, int radius, double startAngle, double endAngle,
int negative) {
@ -190,6 +215,33 @@ int guac_protocol_send_blob(guac_socket* socket, const guac_stream* stream,
}
int guac_protocol_send_blobs(guac_socket* socket, const guac_stream* stream,
const void* data, int count) {
int ret_val = 0;
/* Send blob instructions while data remains and instructions are being
* sent successfully */
while (count > 0 && ret_val == 0) {
/* Limit blob size to maximum allowed */
int blob_size = count;
if (blob_size > GUAC_PROTOCOL_BLOB_MAX_LENGTH)
blob_size = GUAC_PROTOCOL_BLOB_MAX_LENGTH;
/* Send next blob of data */
ret_val = guac_protocol_send_blob(socket, stream, data, blob_size);
/* Advance to next blob */
data = (const char*) data + blob_size;
count -= blob_size;
}
return ret_val;
}
int guac_protocol_send_body(guac_socket* socket, const guac_object* object,
const guac_stream* stream, const char* mimetype, const char* name) {

View File

@ -19,15 +19,13 @@
#include "config.h"
#include "audio.h"
#include "guacamole/audio.h"
#include "guacamole/client.h"
#include "guacamole/protocol.h"
#include "guacamole/socket.h"
#include "guacamole/user.h"
#include "raw_encoder.h"
#include <guacamole/audio.h>
#include <guacamole/client.h>
#include <guacamole/protocol.h>
#include <guacamole/socket.h>
#include <guacamole/user.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
@ -123,25 +121,8 @@ static void raw_encoder_flush_handler(guac_audio_stream* audio) {
guac_socket* socket = audio->client->socket;
guac_stream* stream = audio->stream;
unsigned char* current = state->buffer;
int remaining = state->written;
/* Flush all data in buffer as blobs */
while (remaining > 0) {
/* Determine size of blob to be written */
int chunk_size = remaining;
if (chunk_size > 6048)
chunk_size = 6048;
/* Send audio data */
guac_protocol_send_blob(socket, stream, current, chunk_size);
/* Advance to next blob */
current += chunk_size;
remaining -= chunk_size;
}
guac_protocol_send_blobs(socket, stream, state->buffer, state->written);
/* All data has been flushed */
state->written = 0;

View File

@ -23,7 +23,7 @@
#include "config.h"
#include "audio.h"
#include "guacamole/audio.h"
/**
* The number of bytes to send in each audio blob.

View File

@ -19,10 +19,10 @@
#include "config.h"
#include "client.h"
#include "error.h"
#include "socket.h"
#include "user.h"
#include "guacamole/client.h"
#include "guacamole/error.h"
#include "guacamole/socket.h"
#include "guacamole/user.h"
#include <pthread.h>
#include <stdlib.h>

View File

@ -19,8 +19,8 @@
#include "config.h"
#include "error.h"
#include "socket.h"
#include "guacamole/error.h"
#include "guacamole/socket.h"
#include "wait-fd.h"
#include <pthread.h>

View File

@ -19,86 +19,243 @@
#include "config.h"
#include "protocol.h"
#include "socket.h"
#include "unicode.h"
#include "guacamole/protocol.h"
#include "guacamole/socket.h"
#include "guacamole/unicode.h"
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define GUAC_SOCKET_NEST_BUFFER_SIZE 8192
/**
* The maximum number of bytes to buffer before sending a "nest" instruction.
* As some of the 8 KB space available for each instruction will be taken up by
* the "nest" opcode and other parameters, and 1 KB will be more than enough
* space for that extra data, this space is reduced to an even 7 KB.
*/
#define GUAC_SOCKET_NEST_BUFFER_SIZE 7168
typedef struct __guac_socket_nest_data {
/**
* Internal data associated with an open socket which writes via a series of
* "nest" instructions to some underlying, parent socket.
*/
typedef struct guac_socket_nest_data {
/**
* The underlying socket which should be used to write "nest" instructions.
*/
guac_socket* parent;
char buffer[GUAC_SOCKET_NEST_BUFFER_SIZE];
/**
* The arbitrary index of the nested socket, assigned at time of
* allocation.
*/
int index;
} __guac_socket_nest_data;
/**
* The number of bytes currently in the main write buffer.
*/
int written;
ssize_t __guac_socket_nest_write_handler(guac_socket* socket,
const void* buf, size_t count) {
/**
* The main write buffer. Bytes written go here before being flushed
* as nest instructions. Space is included for the null terminator
* required by guac_protocol_send_nest().
*/
char buffer[GUAC_SOCKET_NEST_BUFFER_SIZE];
__guac_socket_nest_data* data = (__guac_socket_nest_data*) socket->data;
unsigned char* source = (unsigned char*) buf;
/**
* Lock which is acquired when an instruction is being written, and
* released when the instruction is finished being written.
*/
pthread_mutex_t socket_lock;
/* Current location in destination buffer during copy */
char* current = data->buffer;
/**
* Lock which protects access to the internal buffer of this socket,
* guaranteeing atomicity of writes and flushes.
*/
pthread_mutex_t buffer_lock;
/* Number of bytes remaining in source buffer */
int remaining = count;
} guac_socket_nest_data;
/* If we can't actually store that many bytes, reduce number of bytes
* expected to be written */
if (remaining > GUAC_SOCKET_NEST_BUFFER_SIZE)
remaining = GUAC_SOCKET_NEST_BUFFER_SIZE;
/**
* Flushes the contents of the output buffer of the given socket immediately,
* without first locking access to the output buffer. This function must ONLY
* be called if the buffer lock has already been acquired.
*
* @param socket
* The guac_socket to flush.
*
* @return
* Zero if the flush operation was successful, non-zero otherwise.
*/
static ssize_t guac_socket_nest_flush(guac_socket* socket) {
/* Current offset within destination buffer */
int offset;
guac_socket_nest_data* data = (guac_socket_nest_data*) socket->data;
/* Number of characters before start of next character */
int skip = 0;
/* Flush remaining bytes in buffer */
if (data->written > 0) {
/* Copy UTF-8 characters into buffer */
for (offset = 0; offset < GUAC_SOCKET_NEST_BUFFER_SIZE; offset++) {
/* Determine length of buffer containing complete UTF-8 characters
* (buffer may end with a partial, multi-byte character) */
int length = 0;
while (length < data->written)
length += guac_utf8_charsize(*(data->buffer + length));
/* Get next byte */
unsigned char c = *source;
remaining--;
/* Add null terminator, preserving overwritten character for later
* restoration (guac_protocol_send_nest() requires null-terminated
* strings) */
char overwritten = data->buffer[length];
data->buffer[length] = '\0';
/* If skipping, then skip */
if (skip > 0) skip--;
/* Write ALL bytes in buffer as nest instruction */
int retval = guac_protocol_send_nest(data->parent, data->index, data->buffer);
/* Otherwise, determine next skip value, and increment length */
else {
/* Restore original value overwritten by null terminator */
data->buffer[length] = overwritten;
/* Determine skip value (size in bytes of rest of character) */
skip = guac_utf8_charsize(c) - 1;
if (retval)
return 1;
/* If not enough bytes to complete character, break */
if (skip > remaining)
break;
}
/* Store byte */
*current = c;
/* Advance to next character */
source++;
current++;
/* Shift any remaining data to beginning of buffer */
memcpy(data->buffer, data->buffer + length, data->written - length);
data->written -= length;
}
/* Append null-terminator */
*current = 0;
return 0;
/* Send nest instruction containing read UTF-8 segment */
guac_protocol_send_nest(data->parent, data->index, data->buffer);
}
/* Return number of bytes actually written */
return offset;
/**
* Flushes the internal buffer of the given guac_socket, writing all data
* to the underlying socket using "nest" instructions.
*
* @param socket
* The guac_socket to flush.
*
* @return
* Zero if the flush operation was successful, non-zero otherwise.
*/
static ssize_t guac_socket_nest_flush_handler(guac_socket* socket) {
int retval;
guac_socket_nest_data* data = (guac_socket_nest_data*) socket->data;
/* Acquire exclusive access to buffer */
pthread_mutex_lock(&(data->buffer_lock));
/* Flush contents of buffer */
retval = guac_socket_nest_flush(socket);
/* Relinquish exclusive access to buffer */
pthread_mutex_unlock(&(data->buffer_lock));
return retval;
}
/**
* Writes the contents of the buffer to the output buffer of the given socket,
* flushing the output buffer as necessary, without first locking access to the
* output buffer. This function must ONLY be called if the buffer lock has
* already been acquired.
*
* @param socket
* The guac_socket to write the given buffer to.
*
* @param buf
* The buffer to write to the given socket.
*
* @param count
* The number of bytes in the given buffer.
*
* @return
* The number of bytes written, or a negative value if an error occurs
* during write.
*/
static ssize_t guac_socket_nest_write_buffered(guac_socket* socket,
const void* buf, size_t count) {
size_t original_count = count;
const char* current = buf;
guac_socket_nest_data* data = (guac_socket_nest_data*) socket->data;
/* Append to buffer, flush if necessary */
while (count > 0) {
int chunk_size;
/* Calculate space remaining, including one extra byte for the null
* terminator added upon flush */
int remaining = sizeof(data->buffer) - data->written - 1;
/* If no space left in buffer, flush and retry */
if (remaining == 0) {
/* Abort if error occurs during flush */
if (guac_socket_nest_flush(socket))
return -1;
/* Retry buffer append */
continue;
}
/* Calculate size of chunk to be written to buffer */
chunk_size = count;
if (chunk_size > remaining)
chunk_size = remaining;
/* Update output buffer */
memcpy(data->buffer + data->written, current, chunk_size);
data->written += chunk_size;
/* Update provided buffer */
current += chunk_size;
count -= chunk_size;
}
/* All bytes have been written, possibly some to the internal buffer */
return original_count;
}
/**
* Appends the provided data to the internal buffer for future writing. The
* actual write attempt will occur only upon flush, or when the internal buffer
* is full.
*
* @param socket
* The guac_socket being write to.
*
* @param buf
* The arbitrary buffer containing the data to be written.
*
* @param count
* The number of bytes contained within the buffer.
*
* @return
* The number of bytes written, or -1 if an error occurs.
*/
static ssize_t guac_socket_nest_write_handler(guac_socket* socket,
const void* buf, size_t count) {
int retval;
guac_socket_nest_data* data = (guac_socket_nest_data*) socket->data;
/* Acquire exclusive access to buffer */
pthread_mutex_lock(&(data->buffer_lock));
/* Write provided data to buffer */
retval = guac_socket_nest_write_buffered(socket, buf, count);
/* Relinquish exclusive access to buffer */
pthread_mutex_unlock(&(data->buffer_lock));
return retval;
}
@ -113,29 +270,63 @@ ssize_t __guac_socket_nest_write_handler(guac_socket* socket,
* Zero if the data was successfully freed, non-zero otherwise. This
* implementation always succeeds, and will always return zero.
*/
static int __guac_socket_nest_free_handler(guac_socket* socket) {
static int guac_socket_nest_free_handler(guac_socket* socket) {
/* Free associated data */
__guac_socket_nest_data* data = (__guac_socket_nest_data*) socket->data;
guac_socket_nest_data* data = (guac_socket_nest_data*) socket->data;
free(data);
return 0;
}
/**
* Acquires exclusive access to the given socket.
*
* @param socket
* The guac_socket to which exclusive access is required.
*/
static void guac_socket_nest_lock_handler(guac_socket* socket) {
guac_socket_nest_data* data = (guac_socket_nest_data*) socket->data;
/* Acquire exclusive access to socket */
pthread_mutex_lock(&(data->socket_lock));
}
/**
* Relinquishes exclusive access to the given socket.
*
* @param socket
* The guac_socket to which exclusive access is no longer required.
*/
static void guac_socket_nest_unlock_handler(guac_socket* socket) {
guac_socket_nest_data* data = (guac_socket_nest_data*) socket->data;
/* Relinquish exclusive access to socket */
pthread_mutex_unlock(&(data->socket_lock));
}
guac_socket* guac_socket_nest(guac_socket* parent, int index) {
/* Allocate socket and associated data */
guac_socket* socket = guac_socket_alloc();
__guac_socket_nest_data* data = malloc(sizeof(__guac_socket_nest_data));
guac_socket_nest_data* data = malloc(sizeof(guac_socket_nest_data));
/* Store file descriptor as socket data */
/* Store nested socket details as socket data */
data->parent = parent;
data->index = index;
socket->data = data;
/* Set write and free handlers */
socket->write_handler = __guac_socket_nest_write_handler;
socket->free_handler = __guac_socket_nest_free_handler;
/* Set relevant handlers */
socket->write_handler = guac_socket_nest_write_handler;
socket->lock_handler = guac_socket_nest_lock_handler;
socket->unlock_handler = guac_socket_nest_unlock_handler;
socket->flush_handler = guac_socket_nest_flush_handler;
socket->free_handler = guac_socket_nest_free_handler;
return socket;

View File

@ -19,9 +19,9 @@
#include "config.h"
#include "error.h"
#include "socket-ssl.h"
#include "socket.h"
#include "guacamole/error.h"
#include "guacamole/socket-ssl.h"
#include "guacamole/socket.h"
#include "wait-fd.h"
#include <stdlib.h>

View File

@ -19,7 +19,7 @@
#include "config.h"
#include "socket.h"
#include "guacamole/socket.h"
#include <stdlib.h>

View File

@ -17,8 +17,8 @@
* under the License.
*/
#include "error.h"
#include "socket.h"
#include "guacamole/error.h"
#include "guacamole/socket.h"
#include <pthread.h>
#include <stddef.h>

View File

@ -19,10 +19,10 @@
#include "config.h"
#include "error.h"
#include "protocol.h"
#include "socket.h"
#include "timestamp.h"
#include "guacamole/error.h"
#include "guacamole/protocol.h"
#include "guacamole/socket.h"
#include "guacamole/timestamp.h"
#include <inttypes.h>
#include <pthread.h>

107
src/libguac/string.c Normal file
View File

@ -0,0 +1,107 @@
/*
* 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 "config.h"
#include <stddef.h>
#include <string.h>
/**
* Returns the space remaining in a buffer assuming that the given number of
* bytes have already been written. If the number of bytes exceeds the size
* of the buffer, zero is returned.
*
* @param n
* The size of the buffer in bytes.
*
* @param length
* The number of bytes which have been written to the buffer so far. If
* the routine writing the bytes will automatically truncate its writes,
* this value may exceed the size of the buffer.
*
* @return
* The number of bytes remaining in the buffer. This value will always
* be non-negative. If the number of bytes written already exceeds the
* size of the buffer, zero will be returned.
*/
#define REMAINING(n, length) (((n) < (length)) ? 0 : ((n) - (length)))
size_t guac_strlcpy(char* restrict dest, const char* restrict src, size_t n) {
#ifdef HAVE_STRLCPY
return strlcpy(dest, src, n);
#else
/* Calculate actual length of desired string */
size_t length = strlen(src);
/* Copy nothing if there is no space */
if (n == 0)
return length;
/* Calculate length of the string which will be copied */
size_t copy_length = length;
if (copy_length >= n)
copy_length = n - 1;
/* Copy only as much of string as possible, manually adding a null
* terminator */
memcpy(dest, src, copy_length);
dest[copy_length] = '\0';
/* Return the overall length of the desired string */
return length;
#endif
}
size_t guac_strlcat(char* restrict dest, const char* restrict src, size_t n) {
#ifdef HAVE_STRLCPY
return strlcat(dest, src, n);
#else
size_t length = strnlen(dest, n);
return length + guac_strlcpy(dest + length, src, REMAINING(n, length));
#endif
}
size_t guac_strljoin(char* restrict dest, const char* restrict const* elements,
int nmemb, const char* restrict delim, size_t n) {
size_t length = 0;
const char* restrict const* current = elements;
/* If no elements are provided, nothing to do but ensure the destination
* buffer is null terminated */
if (nmemb <= 0)
return guac_strlcpy(dest, "", n);
/* Initialize destination buffer with first element */
length += guac_strlcpy(dest, *current, n);
/* Copy all remaining elements, separated by delimiter */
for (current++; nmemb > 1; current++, nmemb--) {
length += guac_strlcat(dest + length, delim, REMAINING(n, length));
length += guac_strlcat(dest + length, *current, REMAINING(n, length));
}
return length;
}

View File

@ -0,0 +1,79 @@
#
# 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.
#
# NOTE: Parts of this file (Makefile.am) are automatically transcluded verbatim
# into Makefile.in. Though the build system (GNU Autotools) automatically adds
# its own license boilerplate to the generated Makefile.in, that boilerplate
# does not apply to the transcluded portions of Makefile.am which are licensed
# to you by the ASF under the Apache License, Version 2.0, as described above.
#
AUTOMAKE_OPTIONS = foreign
ACLOCAL_AMFLAGS = -I m4
#
# Unit tests for libguac
#
check_PROGRAMS = test_libguac
TESTS = $(check_PROGRAMS)
test_libguac_SOURCES = \
client/buffer_pool.c \
client/layer_pool.c \
parser/append.c \
parser/read.c \
pool/next_free.c \
protocol/base64_decode.c \
socket/fd_send_instruction.c \
socket/nested_send_instruction.c \
string/strlcat.c \
string/strlcpy.c \
string/strljoin.c \
unicode/charsize.c \
unicode/read.c \
unicode/strlen.c \
unicode/write.c
test_libguac_CFLAGS = \
-Werror -Wall -pedantic \
@LIBGUAC_INCLUDE@
test_libguac_LDADD = \
@CUNIT_LIBS@ \
@LIBGUAC_LTLIB@
#
# Autogenerate test runner
#
GEN_RUNNER = $(top_srcdir)/util/generate-test-runner.pl
CLEANFILES = _generated_runner.c
_generated_runner.c: $(test_libguac_SOURCES)
$(AM_V_GEN) $(GEN_RUNNER) $(test_libguac_SOURCES) > $@
nodist_test_libguac_SOURCES = \
_generated_runner.c
# Use automake's TAP test driver for running any tests
LOG_DRIVER = \
env AM_TAP_AWK='$(AWK)' \
$(SHELL) $(top_srcdir)/build-aux/tap-driver.sh

View File

@ -17,20 +17,23 @@
* under the License.
*/
#include "config.h"
#include "client_suite.h"
#include <CUnit/Basic.h>
#include <CUnit/CUnit.h>
#include <guacamole/client.h>
#include <guacamole/layer.h>
void test_buffer_pool() {
#include <stdbool.h>
/**
* Test which verifies that buffers can be allocated and freed using the pool
* of buffers available to each guac_client, and that doing so does not disturb
* the similar pool of layers.
*/
void test_client__buffer_pool() {
guac_client* client;
int i;
int seen[GUAC_BUFFER_POOL_INITIAL_SIZE] = {0};
bool seen[GUAC_BUFFER_POOL_INITIAL_SIZE] = { 0 };
guac_layer* layer;
@ -53,7 +56,7 @@ void test_buffer_pool() {
/* This should be a layer we have not seen yet */
CU_ASSERT_FALSE(seen[-layer->index - 1]);
seen[-layer->index - 1] = 1;
seen[-layer->index - 1] = true;
guac_client_free_buffer(client, layer);

View File

@ -17,20 +17,23 @@
* under the License.
*/
#include "config.h"
#include "client_suite.h"
#include <CUnit/Basic.h>
#include <CUnit/CUnit.h>
#include <guacamole/client.h>
#include <guacamole/layer.h>
void test_layer_pool() {
#include <stdbool.h>
/**
* Test which verifies that layers can be allocated and freed using the pool
* of layers available to each guac_client, and that doing so does not disturb
* the similar pool of buffers.
*/
void test_client__layer_pool() {
guac_client* client;
int i;
int seen[GUAC_BUFFER_POOL_INITIAL_SIZE] = {0};
bool seen[GUAC_BUFFER_POOL_INITIAL_SIZE] = {0};
guac_layer* layer;
@ -53,7 +56,7 @@ void test_layer_pool() {
/* This should be a layer we have not seen yet */
CU_ASSERT_FALSE(seen[layer->index - 1]);
seen[layer->index - 1] = 1;
seen[layer->index - 1] = true;
guac_client_free_layer(client, layer);

View File

@ -17,18 +17,18 @@
* under the License.
*/
#include "config.h"
#include "suite.h"
#include <CUnit/CUnit.h>
#include <guacamole/parser.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <CUnit/Basic.h>
#include <guacamole/parser.h>
void test_instruction_parse() {
/**
* Test which verifies that guac_parser correctly parses Guacamole instructions
* from arbitrary blocks of data passed to guac_parser_append().
*/
void test_parser__append() {
/* Allocate parser */
guac_parser* parser = guac_parser_alloc();
@ -52,6 +52,7 @@ void test_instruction_parse() {
}
/* Parse of instruction should be complete */
CU_ASSERT_EQUAL(remaining, 18);
CU_ASSERT_EQUAL(parser->state, GUAC_PARSE_COMPLETE);

View File

@ -0,0 +1,145 @@
/*
* 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 <CUnit/CUnit.h>
#include <guacamole/error.h>
#include <guacamole/parser.h>
#include <guacamole/protocol.h>
#include <guacamole/socket.h>
#include <stdlib.h>
#include <unistd.h>
/**
* Test string which contains exactly four Unicode characters encoded in UTF-8.
* This particular test string uses several characters which encode to multiple
* bytes in UTF-8.
*/
#define UTF8_4 "\xe7\x8a\xac\xf0\x90\xac\x80z\xc3\xa1"
/**
* Writes a series of Guacamole instructions as raw bytes to the given file
* descriptor. The instructions written correspond to the instructions verified
* by read_expected_instructions(). The given file descriptor is automatically
* closed as a result of calling this function.
*
* @param fd
* The file descriptor to write instructions to.
*/
static void write_instructions(int fd) {
char test_string[] = "4.test,6.a" UTF8_4 "b,"
"5.12345,10.a" UTF8_4 UTF8_4 "c;"
"5.test2,10.hellohello,15.worldworldworld;";
char* current = test_string;
int remaining = sizeof(test_string) - 1;
/* Write all bytes in test string */
while (remaining > 0) {
/* Bail out immediately if write fails (test will fail in parent
* process due to failure to read) */
int written = write(fd, current, remaining);
if (written <= 0)
break;
current += written;
remaining -= written;
}
/* Done writing */
close(fd);
}
/**
* Reads and parses instructions from the given file descriptor using a
* guac_socket and guac_parser, verfying that those instructions match the
* series of Guacamole instructions expected to be written by
* write_instructions(). The given file descriptor is automatically closed as a
* result of calling this function.
*
* @param fd
* The file descriptor to read data from.
*/
static void read_expected_instructions(int fd) {
/* Open guac socket */
guac_socket* socket = guac_socket_open(fd);
CU_ASSERT_PTR_NOT_NULL_FATAL(socket);
/* Allocate parser */
guac_parser* parser = guac_parser_alloc();
CU_ASSERT_PTR_NOT_NULL_FATAL(parser);
/* Read and validate first instruction */
CU_ASSERT_EQUAL_FATAL(guac_parser_read(parser, socket, 1000000), 0);
CU_ASSERT_STRING_EQUAL(parser->opcode, "test");
CU_ASSERT_EQUAL_FATAL(parser->argc, 3);
CU_ASSERT_STRING_EQUAL(parser->argv[0], "a" UTF8_4 "b");
CU_ASSERT_STRING_EQUAL(parser->argv[1], "12345");
CU_ASSERT_STRING_EQUAL(parser->argv[2], "a" UTF8_4 UTF8_4 "c");
/* Read and validate second instruction */
CU_ASSERT_EQUAL_FATAL(guac_parser_read(parser, socket, 1000000), 0);
CU_ASSERT_STRING_EQUAL(parser->opcode, "test2");
CU_ASSERT_EQUAL_FATAL(parser->argc, 2);
CU_ASSERT_STRING_EQUAL(parser->argv[0], "hellohello");
CU_ASSERT_STRING_EQUAL(parser->argv[1], "worldworldworld");
/* Done */
guac_parser_free(parser);
guac_socket_free(socket);
}
/**
* Tests that guac_parser_read() correctly reads and parses instructions
* received over a guac_socket. A child process is forked to write a series of
* instructions which are read and verified by the parent process.
*/
void test_parser__read() {
int fd[2];
/* Create pipe */
CU_ASSERT_EQUAL_FATAL(pipe(fd), 0);
int read_fd = fd[0];
int write_fd = fd[1];
/* Fork into writer process (child) and reader process (parent) */
int childpid;
CU_ASSERT_NOT_EQUAL_FATAL((childpid = fork()), -1);
/* Attempt to write a series of instructions within the child process */
if (childpid == 0) {
close(read_fd);
write_instructions(write_fd);
exit(0);
}
/* Read and verify the expected instructions within the parent process */
close(write_fd);
read_expected_instructions(read_fd);
}

View File

@ -17,20 +17,21 @@
* under the License.
*/
#include "config.h"
#include "util_suite.h"
#include <CUnit/Basic.h>
#include <CUnit/CUnit.h>
#include <guacamole/pool.h>
#define UNSEEN 0
#define SEEN_PHASE_1 1
#define SEEN_PHASE_2 2
/**
* The number of unique integers to provide through the guac_pool instance
* being tested.
*/
#define POOL_SIZE 128
void test_guac_pool() {
/**
* Test which verifies that guac_pool provides access to a given number of
* unique integers, never repeating a retrieved integer until that integer
* is returned to the pool.
*/
void test_pool__next_free() {
guac_pool* pool;
@ -53,8 +54,8 @@ void test_guac_pool() {
CU_ASSERT_FATAL(value < POOL_SIZE);
/* This should be an integer we have not seen yet */
CU_ASSERT_EQUAL(UNSEEN, seen[value]);
seen[value] = SEEN_PHASE_1;
CU_ASSERT_EQUAL(0, seen[value]);
seen[value]++;
/* Return value to pool */
guac_pool_free_int(pool, value);
@ -71,9 +72,9 @@ void test_guac_pool() {
CU_ASSERT_FATAL(value >= 0);
CU_ASSERT_FATAL(value < POOL_SIZE);
/* This should be an integer we have seen already */
CU_ASSERT_EQUAL(SEEN_PHASE_1, seen[value]);
seen[value] = SEEN_PHASE_2;
/* This should be an integer we have seen only once */
CU_ASSERT_EQUAL(1, seen[value]);
seen[value]++;
}

View File

@ -17,18 +17,14 @@
* under the License.
*/
#include "config.h"
#include "suite.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <CUnit/Basic.h>
#include <CUnit/CUnit.h>
#include <guacamole/protocol.h>
void test_base64_decode() {
/**
* Tests that libguac's in-place base64 decoding function properly decodes
* valid base64 and fails for invalid base64.
*/
void test_protocol__decode_base64() {
/* Test strings */
char test_HELLO[] = "SEVMTE8=";

View File

@ -0,0 +1,136 @@
/*
* 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 <CUnit/CUnit.h>
#include <guacamole/protocol.h>
#include <guacamole/socket.h>
#include <stdlib.h>
#include <unistd.h>
/**
* Test string which contains exactly four Unicode characters encoded in UTF-8.
* This particular test string uses several characters which encode to multiple
* bytes in UTF-8.
*/
#define UTF8_4 "\xe7\x8a\xac\xf0\x90\xac\x80z\xc3\xa1"
/**
* Writes a series of Guacamole instructions using a normal guac_socket
* wrapping the given file descriptor. The instructions written correspond to
* the instructions verified by read_expected_instructions(). The given file
* descriptor is automatically closed as a result of calling this function.
*
* @param fd
* The file descriptor to write instructions to.
*/
static void write_instructions(int fd) {
/* Open guac socket */
guac_socket* socket = guac_socket_open(fd);
/* Write nothing if socket cannot be allocated (test will fail in parent
* process due to failure to read) */
if (socket == NULL) {
close(fd);
return;
}
/* Write instructions */
guac_protocol_send_name(socket, "a" UTF8_4 "b" UTF8_4 "c");
guac_protocol_send_sync(socket, 12345);
guac_socket_flush(socket);
/* Close and free socket */
guac_socket_free(socket);
}
/**
* Reads raw bytes from the given file descriptor until no further bytes
* remain, verfying that those bytes represent the series of Guacamole
* instructions expected to be written by write_instructions(). The given
* file descriptor is automatically closed as a result of calling this
* function.
*
* @param fd
* The file descriptor to read data from.
*/
static void read_expected_instructions(int fd) {
char expected[] =
"4.name,11.a" UTF8_4 "b" UTF8_4 "c;"
"4.sync,5.12345;";
int numread;
char buffer[1024];
int offset = 0;
/* Read everything available into buffer */
while ((numread = read(fd, &(buffer[offset]),
sizeof(buffer) - offset)) > 0) {
offset += numread;
}
/* Verify length of read data */
CU_ASSERT_EQUAL(offset, strlen(expected));
/* Add NULL terminator */
buffer[offset] = '\0';
/* Read value should be equal to expected value */
CU_ASSERT_STRING_EQUAL(buffer, expected);
/* File descriptor is no longer needed */
close(fd);
}
/**
* Tests that the file descriptor implementation of guac_socket properly
* implements writing of instructions. A child process is forked to write a
* series of instructions which are read and verified by the parent process.
*/
void test_socket__fd_send_instruction() {
int fd[2];
/* Create pipe */
CU_ASSERT_EQUAL_FATAL(pipe(fd), 0);
int read_fd = fd[0];
int write_fd = fd[1];
/* Fork into writer process (child) and reader process (parent) */
int childpid;
CU_ASSERT_NOT_EQUAL_FATAL((childpid = fork()), -1);
/* Attempt to write a series of instructions within the child process */
if (childpid == 0) {
close(read_fd);
write_instructions(write_fd);
exit(0);
}
/* Read and verify the expected instructions within the parent process */
close(write_fd);
read_expected_instructions(read_fd);
}

View File

@ -0,0 +1,149 @@
/*
* 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 <CUnit/CUnit.h>
#include <guacamole/protocol.h>
#include <guacamole/socket.h>
#include <stdlib.h>
#include <unistd.h>
/**
* Test string which contains exactly four Unicode characters encoded in UTF-8.
* This particular test string uses several characters which encode to multiple
* bytes in UTF-8.
*/
#define UTF8_4 "\xe7\x8a\xac\xf0\x90\xac\x80z\xc3\xa1"
/**
* Writes a series of Guacamole instructions using a nested guac_socket
* wrapping another guac_socket which writes to the given file descriptor. The
* instructions written correspond to the instructions verified by
* read_expected_instructions(). The given file descriptor is automatically
* closed as a result of calling this function.
*
* @param fd
* The file descriptor to write instructions to.
*/
static void write_instructions(int fd) {
/* Open guac socket */
guac_socket* socket = guac_socket_open(fd);
/* Write nothing if socket cannot be allocated (test will fail in parent
* process due to failure to read) */
if (socket == NULL) {
close(fd);
return;
}
/* Nest socket */
guac_socket* nested_socket = guac_socket_nest(socket, 123);
/* Write nothing if nested socket cannot be allocated (test will fail in
* parent process due to failure to read) */
if (socket == NULL) {
guac_socket_free(socket);
return;
}
/* Write instructions */
guac_protocol_send_name(nested_socket, "a" UTF8_4 "b" UTF8_4 "c");
guac_protocol_send_sync(nested_socket, 12345);
/* Close and free sockets */
guac_socket_free(nested_socket);
guac_socket_free(socket);
}
/**
* Reads raw bytes from the given file descriptor until no further bytes
* remain, verfying that those bytes represent the series of Guacamole
* instructions expected to be written by write_instructions(). The given
* file descriptor is automatically closed as a result of calling this
* function.
*
* @param fd
* The file descriptor to read data from.
*/
static void read_expected_instructions(int fd) {
char expected[] =
"4.nest,3.123,37."
"4.name,11.a" UTF8_4 "b" UTF8_4 "c;"
"4.sync,5.12345;"
";";
int numread;
char buffer[1024];
int offset = 0;
/* Read everything available into buffer */
while ((numread = read(fd, &(buffer[offset]),
sizeof(buffer) - offset)) > 0) {
offset += numread;
}
/* Verify length of read data */
CU_ASSERT_EQUAL(offset, strlen(expected));
/* Add NULL terminator */
buffer[offset] = '\0';
/* Read value should be equal to expected value */
CU_ASSERT_STRING_EQUAL(buffer, expected);
/* File descriptor is no longer needed */
close(fd);
}
/**
* Tests that the nested socket implementation of guac_socket properly
* implements writing of instructions. A child process is forked to write a
* series of instructions which are read and verified by the parent process.
*/
void test_socket__nested_send_instruction() {
int fd[2];
/* Create pipe */
CU_ASSERT_EQUAL_FATAL(pipe(fd), 0);
int read_fd = fd[0];
int write_fd = fd[1];
/* Fork into writer process (child) and reader process (parent) */
int childpid;
CU_ASSERT_NOT_EQUAL_FATAL((childpid = fork()), -1);
/* Attempt to write a series of instructions within the child process */
if (childpid == 0) {
close(read_fd);
write_instructions(write_fd);
exit(0);
}
/* Read and verify the expected instructions within the parent process */
close(write_fd);
read_expected_instructions(read_fd);
}

View File

@ -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 <CUnit/CUnit.h>
#include <guacamole/string.h>
#include <stdlib.h>
#include <string.h>
/**
* Verify guac_strlcat() behavior when the string fits the buffer without
* truncation. The return value of each call should be the length of the
* resulting string. Each resulting string should contain the full result of
* the concatenation, including null terminator.
*/
void test_string__strlcat() {
char buffer[1024];
memset(buffer, 0xFF, sizeof(buffer));
strcpy(buffer, "Apache ");
CU_ASSERT_EQUAL(guac_strlcat(buffer, "Guacamole", sizeof(buffer)), 16);
CU_ASSERT_STRING_EQUAL(buffer, "Apache Guacamole");
CU_ASSERT_EQUAL(buffer[17], '\xFF');
memset(buffer, 0xFF, sizeof(buffer));
strcpy(buffer, "");
CU_ASSERT_EQUAL(guac_strlcat(buffer, "This is a test", sizeof(buffer)), 14);
CU_ASSERT_STRING_EQUAL(buffer, "This is a test");
CU_ASSERT_EQUAL(buffer[15], '\xFF');
memset(buffer, 0xFF, sizeof(buffer));
strcpy(buffer, "AB");
CU_ASSERT_EQUAL(guac_strlcat(buffer, "X", sizeof(buffer)), 3);
CU_ASSERT_STRING_EQUAL(buffer, "ABX");
CU_ASSERT_EQUAL(buffer[4], '\xFF');
memset(buffer, 0xFF, sizeof(buffer));
strcpy(buffer, "X");
CU_ASSERT_EQUAL(guac_strlcat(buffer, "", sizeof(buffer)), 1);
CU_ASSERT_STRING_EQUAL(buffer, "X");
CU_ASSERT_EQUAL(buffer[2], '\xFF');
memset(buffer, 0xFF, sizeof(buffer));
strcpy(buffer, "");
CU_ASSERT_EQUAL(guac_strlcat(buffer, "", sizeof(buffer)), 0);
CU_ASSERT_STRING_EQUAL(buffer, "");
CU_ASSERT_EQUAL(buffer[1], '\xFF');
}
/**
* Verify guac_strlcat() behavior when the string must be truncated to fit the
* buffer. The return value of each call should be the length that would result
* from concatenating the strings given an infinite buffer, however only as
* many characters as can fit should be appended to the string within the
* buffer, and the buffer should be null-terminated.
*/
void test_string__strlcat_truncate() {
char buffer[1024];
memset(buffer, 0xFF, sizeof(buffer));
strcpy(buffer, "Apache ");
CU_ASSERT_EQUAL(guac_strlcat(buffer, "Guacamole", 9), 16);
CU_ASSERT_STRING_EQUAL(buffer, "Apache G");
CU_ASSERT_EQUAL(buffer[9], '\xFF');
memset(buffer, 0xFF, sizeof(buffer));
strcpy(buffer, "");
CU_ASSERT_EQUAL(guac_strlcat(buffer, "This is a test", 10), 14);
CU_ASSERT_STRING_EQUAL(buffer, "This is a");
CU_ASSERT_EQUAL(buffer[10], '\xFF');
memset(buffer, 0xFF, sizeof(buffer));
strcpy(buffer, "This ");
CU_ASSERT_EQUAL(guac_strlcat(buffer, "is ANOTHER test", 6), 20);
CU_ASSERT_STRING_EQUAL(buffer, "This ");
CU_ASSERT_EQUAL(buffer[6], '\xFF');
}
/**
* Verify guac_strlcat() behavior with zero buffer sizes. The return value of
* each call should be the size of the input string, while the buffer remains
* untouched.
*/
void test_string__strlcat_nospace() {
/* 0-byte buffer plus 1 guard byte (to test overrun) */
char buffer[1] = { '\xFF' };
CU_ASSERT_EQUAL(guac_strlcat(buffer, "Guacamole", 0), 9);
CU_ASSERT_EQUAL(buffer[0], '\xFF');
CU_ASSERT_EQUAL(guac_strlcat(buffer, "This is a test", 0), 14);
CU_ASSERT_EQUAL(buffer[0], '\xFF');
CU_ASSERT_EQUAL(guac_strlcat(buffer, "X", 0), 1);
CU_ASSERT_EQUAL(buffer[0], '\xFF');
CU_ASSERT_EQUAL(guac_strlcat(buffer, "", 0), 0);
CU_ASSERT_EQUAL(buffer[0], '\xFF');
}
/**
* Verify guac_strlcat() behavior with unterminated buffers. With respect to
* the return value, the length of the string in the buffer should be
* considered equal to the size of the buffer, however the resulting buffer
* should not be null-terminated.
*/
void test_string__strlcat_nonull() {
char expected[1024];
memset(expected, 0xFF, sizeof(expected));
char buffer[1024];
memset(buffer, 0xFF, sizeof(buffer));
CU_ASSERT_EQUAL(guac_strlcat(buffer, "Guacamole", 256), 265);
CU_ASSERT_NSTRING_EQUAL(buffer, expected, sizeof(expected));
memset(buffer, 0xFF, sizeof(buffer));
CU_ASSERT_EQUAL(guac_strlcat(buffer, "This is a test", 37), 51);
CU_ASSERT_NSTRING_EQUAL(buffer, expected, sizeof(expected));
memset(buffer, 0xFF, sizeof(buffer));
CU_ASSERT_EQUAL(guac_strlcat(buffer, "X", 12), 13);
CU_ASSERT_NSTRING_EQUAL(buffer, expected, sizeof(expected));
memset(buffer, 0xFF, sizeof(buffer));
CU_ASSERT_EQUAL(guac_strlcat(buffer, "", 100), 100);
CU_ASSERT_NSTRING_EQUAL(buffer, expected, sizeof(expected));
}

View File

@ -0,0 +1,102 @@
/*
* 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 <CUnit/CUnit.h>
#include <guacamole/string.h>
#include <stdlib.h>
#include <string.h>
/**
* Verify guac_strlcpy() behavior when the string fits the buffer without
* truncation.
*/
void test_string__strlcpy() {
char buffer[1024];
memset(buffer, 0xFF, sizeof(buffer));
CU_ASSERT_EQUAL(guac_strlcpy(buffer, "Guacamole", sizeof(buffer)), 9);
CU_ASSERT_STRING_EQUAL(buffer, "Guacamole");
CU_ASSERT_EQUAL(buffer[10], '\xFF');
memset(buffer, 0xFF, sizeof(buffer));
CU_ASSERT_EQUAL(guac_strlcpy(buffer, "This is a test", sizeof(buffer)), 14);
CU_ASSERT_STRING_EQUAL(buffer, "This is a test");
CU_ASSERT_EQUAL(buffer[15], '\xFF');
memset(buffer, 0xFF, sizeof(buffer));
CU_ASSERT_EQUAL(guac_strlcpy(buffer, "X", sizeof(buffer)), 1);
CU_ASSERT_STRING_EQUAL(buffer, "X");
CU_ASSERT_EQUAL(buffer[2], '\xFF');
memset(buffer, 0xFF, sizeof(buffer));
CU_ASSERT_EQUAL(guac_strlcpy(buffer, "", sizeof(buffer)), 0);
CU_ASSERT_STRING_EQUAL(buffer, "");
CU_ASSERT_EQUAL(buffer[1], '\xFF');
}
/**
* Verify guac_strlcpy() behavior when the string must be truncated to fit the
* buffer.
*/
void test_string__strlcpy_truncate() {
char buffer[1024];
memset(buffer, 0xFF, sizeof(buffer));
CU_ASSERT_EQUAL(guac_strlcpy(buffer, "Guacamole", 6), 9);
CU_ASSERT_STRING_EQUAL(buffer, "Guaca");
CU_ASSERT_EQUAL(buffer[6], '\xFF');
memset(buffer, 0xFF, sizeof(buffer));
CU_ASSERT_EQUAL(guac_strlcpy(buffer, "This is a test", 10), 14);
CU_ASSERT_STRING_EQUAL(buffer, "This is a");
CU_ASSERT_EQUAL(buffer[10], '\xFF');
memset(buffer, 0xFF, sizeof(buffer));
CU_ASSERT_EQUAL(guac_strlcpy(buffer, "This is ANOTHER test", 2), 20);
CU_ASSERT_STRING_EQUAL(buffer, "T");
CU_ASSERT_EQUAL(buffer[2], '\xFF');
}
/**
* Verify guac_strlcpy() behavior with zero buffer sizes.
*/
void test_string__strlcpy_nospace() {
/* 0-byte buffer plus 1 guard byte (to test overrun) */
char buffer[1] = { '\xFF' };
CU_ASSERT_EQUAL(guac_strlcpy(buffer, "Guacamole", 0), 9);
CU_ASSERT_EQUAL(buffer[0], '\xFF');
CU_ASSERT_EQUAL(guac_strlcpy(buffer, "This is a test", 0), 14);
CU_ASSERT_EQUAL(buffer[0], '\xFF');
CU_ASSERT_EQUAL(guac_strlcpy(buffer, "X", 0), 1);
CU_ASSERT_EQUAL(buffer[0], '\xFF');
CU_ASSERT_EQUAL(guac_strlcpy(buffer, "", 0), 0);
CU_ASSERT_EQUAL(buffer[0], '\xFF');
}

View File

@ -0,0 +1,148 @@
/*
* 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 <CUnit/CUnit.h>
#include <guacamole/string.h>
#include <stdlib.h>
#include <string.h>
/**
* Array of test elements containing the strings "Apache" and "Guacamole".
*/
const char* const apache_guacamole[] = { "Apache", "Guacamole" };
/**
* Array of test elements containing the strings "This", "is", "a", and "test".
*/
const char* const this_is_a_test[] = { "This", "is", "a", "test" };
/**
* Array of four test elements containing the strings "A" and "B", each
* preceded by an empty string ("").
*/
const char* const empty_a_empty_b[] = { "", "A", "", "B" };
/**
* Array of test elements containing ten empty strings.
*/
const char* const empty_x10[] = { "", "", "", "", "", "", "", "", "", "" };
/**
* Verify guac_strljoin() behavior when the string fits the buffer without
* truncation. The return value of each call should be the length of the
* resulting string. Each resulting string should contain the full result of
* the join operation, including null terminator.
*/
void test_string__strljoin() {
char buffer[1024];
memset(buffer, 0xFF, sizeof(buffer));
CU_ASSERT_EQUAL(guac_strljoin(buffer, apache_guacamole, 2, " ", sizeof(buffer)), 16);
CU_ASSERT_STRING_EQUAL(buffer, "Apache Guacamole");
CU_ASSERT_EQUAL(buffer[17], '\xFF');
memset(buffer, 0xFF, sizeof(buffer));
CU_ASSERT_EQUAL(guac_strljoin(buffer, this_is_a_test, 4, "", sizeof(buffer)), 11);
CU_ASSERT_STRING_EQUAL(buffer, "Thisisatest");
CU_ASSERT_EQUAL(buffer[12], '\xFF');
memset(buffer, 0xFF, sizeof(buffer));
CU_ASSERT_EQUAL(guac_strljoin(buffer, this_is_a_test, 4, "-/-", sizeof(buffer)), 20);
CU_ASSERT_STRING_EQUAL(buffer, "This-/-is-/-a-/-test");
CU_ASSERT_EQUAL(buffer[21], '\xFF');
memset(buffer, 0xFF, sizeof(buffer));
CU_ASSERT_EQUAL(guac_strljoin(buffer, empty_a_empty_b, 4, "/", sizeof(buffer)), 5);
CU_ASSERT_STRING_EQUAL(buffer, "/A//B");
CU_ASSERT_EQUAL(buffer[6], '\xFF');
memset(buffer, 0xFF, sizeof(buffer));
CU_ASSERT_EQUAL(guac_strljoin(buffer, empty_x10, 10, "/", sizeof(buffer)), 9);
CU_ASSERT_STRING_EQUAL(buffer, "/////////");
CU_ASSERT_EQUAL(buffer[10], '\xFF');
}
/**
* Verify guac_strljoin() behavior when the string must be truncated to fit the
* buffer. The return value of each call should be the length that would result
* from joining the strings given an infinite buffer, however only as many
* characters as can fit should be appended to the string within the buffer,
* and the buffer should be null-terminated.
*/
void test_string__strljoin_truncate() {
char buffer[1024];
memset(buffer, 0xFF, sizeof(buffer));
CU_ASSERT_EQUAL(guac_strljoin(buffer, apache_guacamole, 2, " ", 9), 16);
CU_ASSERT_STRING_EQUAL(buffer, "Apache G");
CU_ASSERT_EQUAL(buffer[9], '\xFF');
memset(buffer, 0xFF, sizeof(buffer));
CU_ASSERT_EQUAL(guac_strljoin(buffer, this_is_a_test, 4, "", 8), 11);
CU_ASSERT_STRING_EQUAL(buffer, "Thisisa");
CU_ASSERT_EQUAL(buffer[8], '\xFF');
memset(buffer, 0xFF, sizeof(buffer));
CU_ASSERT_EQUAL(guac_strljoin(buffer, this_is_a_test, 4, "-/-", 12), 20);
CU_ASSERT_STRING_EQUAL(buffer, "This-/-is-/");
CU_ASSERT_EQUAL(buffer[12], '\xFF');
memset(buffer, 0xFF, sizeof(buffer));
CU_ASSERT_EQUAL(guac_strljoin(buffer, empty_a_empty_b, 4, "/", 2), 5);
CU_ASSERT_STRING_EQUAL(buffer, "/");
CU_ASSERT_EQUAL(buffer[2], '\xFF');
memset(buffer, 0xFF, sizeof(buffer));
CU_ASSERT_EQUAL(guac_strljoin(buffer, empty_x10, 10, "/", 7), 9);
CU_ASSERT_STRING_EQUAL(buffer, "//////");
CU_ASSERT_EQUAL(buffer[7], '\xFF');
}
/**
* Verify guac_strljoin() behavior with zero buffer sizes. The return value of
* each call should be the size of the input string, while the buffer remains
* untouched.
*/
void test_string__strljoin_nospace() {
/* 0-byte buffer plus 1 guard byte (to test overrun) */
char buffer[1] = { '\xFF' };
CU_ASSERT_EQUAL(guac_strljoin(buffer, apache_guacamole, 2, " ", 0), 16);
CU_ASSERT_EQUAL(buffer[0], '\xFF');
CU_ASSERT_EQUAL(guac_strljoin(buffer, this_is_a_test, 4, "", 0), 11);
CU_ASSERT_EQUAL(buffer[0], '\xFF');
CU_ASSERT_EQUAL(guac_strljoin(buffer, this_is_a_test, 4, "-/-", 0), 20);
CU_ASSERT_EQUAL(buffer[0], '\xFF');
CU_ASSERT_EQUAL(guac_strljoin(buffer, empty_a_empty_b, 4, "/", 0), 5);
CU_ASSERT_EQUAL(buffer[0], '\xFF');
CU_ASSERT_EQUAL(guac_strljoin(buffer, empty_x10, 10, "/", 0), 9);
CU_ASSERT_EQUAL(buffer[0], '\xFF');
}

View File

@ -0,0 +1,33 @@
/*
* 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 <CUnit/CUnit.h>
#include <guacamole/unicode.h>
/**
* Test which verifies that guac_utf8_charsize() correctly determines the
* length of UTF-8 characters from the leading byte of that character.
*/
void test_unicode__utf8_charsize() {
CU_ASSERT_EQUAL(1, guac_utf8_charsize('g'));
CU_ASSERT_EQUAL(2, guac_utf8_charsize('\xC4'));
CU_ASSERT_EQUAL(3, guac_utf8_charsize('\xE7'));
CU_ASSERT_EQUAL(4, guac_utf8_charsize('\xF0'));
}

View File

@ -0,0 +1,52 @@
/*
* 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 <CUnit/CUnit.h>
#include <guacamole/unicode.h>
/**
* Test which verifies that guac_utf8_read() properly parses UTF-8.
*/
void test_unicode__utf8_read() {
int codepoint;
char buffer[16] =
/* U+0065 */ "\x65"
/* U+0654 */ "\xD9\x94"
/* U+0876 */ "\xE0\xA1\xB6"
/* U+12345 */ "\xF0\x92\x8D\x85";
CU_ASSERT_EQUAL(1, guac_utf8_read(&(buffer[0]), 10, &codepoint));
CU_ASSERT_EQUAL(0x0065, codepoint);
CU_ASSERT_EQUAL(2, guac_utf8_read(&(buffer[1]), 9, &codepoint));
CU_ASSERT_EQUAL(0x0654, codepoint);
CU_ASSERT_EQUAL(3, guac_utf8_read(&(buffer[3]), 7, &codepoint));
CU_ASSERT_EQUAL(0x0876, codepoint);
CU_ASSERT_EQUAL(4, guac_utf8_read(&(buffer[6]), 4, &codepoint));
CU_ASSERT_EQUAL(0x12345, codepoint);
CU_ASSERT_EQUAL(0, guac_utf8_read(&(buffer[10]), 0, &codepoint));
CU_ASSERT_EQUAL(0x12345, codepoint);
}

View File

@ -17,19 +17,8 @@
* under the License.
*/
#ifndef _GUAC_TEST_UTIL_SUITE_H
#define _GUAC_TEST_UTIL_SUITE_H
/**
* Test suite containing unit tests for utility functions built into libguac.
* These utility functions are included for convenience rather as integral
* requirements of the core.
*
* @file util_suite.h
*/
#include "config.h"
#include <CUnit/CUnit.h>
#include <guacamole/unicode.h>
/**
* A single Unicode character encoded as one byte with UTF-8.
@ -52,24 +41,19 @@
#define UTF8_4b "\xf0\x90\x84\xa3"
/**
* Registers the utility test suite with CUnit.
* Test which verifies that guac_utf8_strlen() properly calculates the length
* of UTF-8 strings.
*/
int register_util_suite();
/**
* Unit test for the guac_pool structure and related functions. The guac_pool
* structure provides a consistent source of pooled integers. This unit test
* checks that the associated functions behave as documented (returning
* integers in the proper order, allocating new integers as necessary, etc.).
*/
void test_guac_pool();
/**
* Unit test for libguac's Unicode convenience functions. This test checks that
* the functions provided for determining string length, character length, and
* for reading and writing UTF-8 behave as specified in the documentation.
*/
void test_guac_unicode();
#endif
void test_unicode__utf8_strlen() {
CU_ASSERT_EQUAL(0, guac_utf8_strlen(""));
CU_ASSERT_EQUAL(1, guac_utf8_strlen(UTF8_4b));
CU_ASSERT_EQUAL(2, guac_utf8_strlen(UTF8_4b UTF8_1b));
CU_ASSERT_EQUAL(2, guac_utf8_strlen(UTF8_2b UTF8_3b));
CU_ASSERT_EQUAL(3, guac_utf8_strlen(UTF8_1b UTF8_3b UTF8_4b));
CU_ASSERT_EQUAL(3, guac_utf8_strlen(UTF8_2b UTF8_1b UTF8_3b));
CU_ASSERT_EQUAL(3, guac_utf8_strlen(UTF8_4b UTF8_2b UTF8_1b));
CU_ASSERT_EQUAL(3, guac_utf8_strlen(UTF8_3b UTF8_4b UTF8_2b));
CU_ASSERT_EQUAL(5, guac_utf8_strlen("hello"));
CU_ASSERT_EQUAL(9, guac_utf8_strlen("guacamole"));
}

View File

@ -17,36 +17,17 @@
* under the License.
*/
#include "config.h"
#include "util_suite.h"
#include <CUnit/Basic.h>
#include <CUnit/CUnit.h>
#include <guacamole/unicode.h>
void test_guac_unicode() {
/**
* Test which verifies that guac_utf8_write() properly encodes Unicode
* codepoints as UTF-8.
*/
void test_unicode__utf8_write() {
int codepoint;
char buffer[16];
/* Test character length */
CU_ASSERT_EQUAL(1, guac_utf8_charsize(UTF8_1b[0]));
CU_ASSERT_EQUAL(2, guac_utf8_charsize(UTF8_2b[0]));
CU_ASSERT_EQUAL(3, guac_utf8_charsize(UTF8_3b[0]));
CU_ASSERT_EQUAL(4, guac_utf8_charsize(UTF8_4b[0]));
/* Test string length */
CU_ASSERT_EQUAL(0, guac_utf8_strlen(""));
CU_ASSERT_EQUAL(1, guac_utf8_strlen(UTF8_4b));
CU_ASSERT_EQUAL(2, guac_utf8_strlen(UTF8_4b UTF8_1b));
CU_ASSERT_EQUAL(2, guac_utf8_strlen(UTF8_2b UTF8_3b));
CU_ASSERT_EQUAL(3, guac_utf8_strlen(UTF8_1b UTF8_3b UTF8_4b));
CU_ASSERT_EQUAL(3, guac_utf8_strlen(UTF8_2b UTF8_1b UTF8_3b));
CU_ASSERT_EQUAL(3, guac_utf8_strlen(UTF8_4b UTF8_2b UTF8_1b));
CU_ASSERT_EQUAL(3, guac_utf8_strlen(UTF8_3b UTF8_4b UTF8_2b));
CU_ASSERT_EQUAL(5, guac_utf8_strlen("hello"));
CU_ASSERT_EQUAL(9, guac_utf8_strlen("guacamole"));
/* Test writes */
CU_ASSERT_EQUAL(1, guac_utf8_write(0x00065, &(buffer[0]), 10));
CU_ASSERT_EQUAL(2, guac_utf8_write(0x00654, &(buffer[1]), 9));
@ -60,22 +41,5 @@ void test_guac_unicode() {
CU_ASSERT(memcmp("\xE0\xA1\xB6", &(buffer[3]), 3) == 0); /* U+0876 */
CU_ASSERT(memcmp("\xF0\x92\x8D\x85", &(buffer[6]), 4) == 0); /* U+12345 */
/* Test reads */
CU_ASSERT_EQUAL(1, guac_utf8_read(&(buffer[0]), 10, &codepoint));
CU_ASSERT_EQUAL(0x0065, codepoint);
CU_ASSERT_EQUAL(2, guac_utf8_read(&(buffer[1]), 9, &codepoint));
CU_ASSERT_EQUAL(0x0654, codepoint);
CU_ASSERT_EQUAL(3, guac_utf8_read(&(buffer[3]), 7, &codepoint));
CU_ASSERT_EQUAL(0x0876, codepoint);
CU_ASSERT_EQUAL(4, guac_utf8_read(&(buffer[6]), 4, &codepoint));
CU_ASSERT_EQUAL(0x12345, codepoint);
CU_ASSERT_EQUAL(0, guac_utf8_read(&(buffer[10]), 0, &codepoint));
CU_ASSERT_EQUAL(0x12345, codepoint);
}

View File

@ -19,7 +19,7 @@
#include "config.h"
#include "timestamp.h"
#include "guacamole/timestamp.h"
#include <sys/time.h>

View File

@ -19,7 +19,7 @@
#include "config.h"
#include "unicode.h"
#include "guacamole/unicode.h"
#include <stddef.h>

View File

@ -19,18 +19,19 @@
#include "config.h"
#include "client.h"
#include "object.h"
#include "protocol.h"
#include "stream.h"
#include "timestamp.h"
#include "user.h"
#include "guacamole/client.h"
#include "guacamole/object.h"
#include "guacamole/protocol.h"
#include "guacamole/stream.h"
#include "guacamole/timestamp.h"
#include "guacamole/user.h"
#include "user-handlers.h"
#include <inttypes.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
/* Guacamole instruction handler map */
@ -49,9 +50,22 @@ __guac_instruction_handler_mapping __guac_instruction_handler_map[] = {
{"get", __guac_handle_get},
{"put", __guac_handle_put},
{"audio", __guac_handle_audio},
{"argv", __guac_handle_argv},
{"nop", __guac_handle_nop},
{NULL, NULL}
};
/* Guacamole handshake handler map */
__guac_instruction_handler_mapping __guac_handshake_handler_map[] = {
{"size", __guac_handshake_size_handler},
{"audio", __guac_handshake_audio_handler},
{"video", __guac_handshake_video_handler},
{"image", __guac_handshake_image_handler},
{"timezone", __guac_handshake_timezone_handler},
{NULL, NULL}
};
/**
* Parses a 64-bit integer from the given string. It is assumed that the string
* will contain only decimal digits, with an optional leading minus sign.
@ -382,6 +396,30 @@ int __guac_handle_pipe(guac_user* user, int argc, char** argv) {
return 0;
}
int __guac_handle_argv(guac_user* user, int argc, char** argv) {
/* Pull corresponding stream */
int stream_index = atoi(argv[0]);
guac_stream* stream = __init_input_stream(user, stream_index);
if (stream == NULL)
return 0;
/* If supported, call handler */
if (user->argv_handler)
return user->argv_handler(
user,
stream,
argv[1], /* mimetype */
argv[2] /* name */
);
/* Otherwise, abort */
guac_protocol_send_ack(user->socket, stream,
"Reconfiguring in-progress connections unsupported",
GUAC_PROTOCOL_STATUS_UNSUPPORTED);
return 0;
}
int __guac_handle_ack(guac_user* user, int argc, char** argv) {
guac_stream* stream;
@ -551,8 +589,147 @@ int __guac_handle_put(guac_user* user, int argc, char** argv) {
return 0;
}
int __guac_handle_nop(guac_user* user, int argc, char** argv) {
guac_user_log(user, GUAC_LOG_TRACE,
"Received nop instruction");
return 0;
}
int __guac_handle_disconnect(guac_user* user, int argc, char** argv) {
guac_user_stop(user);
return 0;
}
/* Guacamole handshake handler functions. */
int __guac_handshake_size_handler(guac_user* user, int argc, char** argv) {
/* Validate size of instruction. */
if (argc < 2) {
guac_user_log(user, GUAC_LOG_ERROR, "Received \"size\" "
"instruction lacked required arguments.");
return 1;
}
/* Parse optimal screen dimensions from size instruction */
user->info.optimal_width = atoi(argv[0]);
user->info.optimal_height = atoi(argv[1]);
/* If DPI given, set the user resolution */
if (argc >= 3)
user->info.optimal_resolution = atoi(argv[2]);
/* Otherwise, use a safe default for rough backwards compatibility */
else
user->info.optimal_resolution = 96;
return 0;
}
int __guac_handshake_audio_handler(guac_user* user, int argc, char** argv) {
guac_free_mimetypes((char **) user->info.audio_mimetypes);
/* Store audio mimetypes */
user->info.audio_mimetypes = (const char**) guac_copy_mimetypes(argv, argc);
return 0;
}
int __guac_handshake_video_handler(guac_user* user, int argc, char** argv) {
guac_free_mimetypes((char **) user->info.video_mimetypes);
/* Store video mimetypes */
user->info.video_mimetypes = (const char**) guac_copy_mimetypes(argv, argc);
return 0;
}
int __guac_handshake_image_handler(guac_user* user, int argc, char** argv) {
guac_free_mimetypes((char **) user->info.image_mimetypes);
/* Store image mimetypes */
user->info.image_mimetypes = (const char**) guac_copy_mimetypes(argv, argc);
return 0;
}
int __guac_handshake_timezone_handler(guac_user* user, int argc, char** argv) {
/* Free any past value */
free((char *) user->info.timezone);
/* Store timezone, if present */
if (argc > 0 && strcmp(argv[0], ""))
user->info.timezone = (const char*) strdup(argv[0]);
else
user->info.timezone = NULL;
return 0;
}
char** guac_copy_mimetypes(char** mimetypes, int count) {
int i;
/* Allocate sufficient space for NULL-terminated array of mimetypes */
char** mimetypes_copy = malloc(sizeof(char*) * (count+1));
/* Copy each provided mimetype */
for (i = 0; i < count; i++)
mimetypes_copy[i] = strdup(mimetypes[i]);
/* Terminate with NULL */
mimetypes_copy[count] = NULL;
return mimetypes_copy;
}
void guac_free_mimetypes(char** mimetypes) {
if (mimetypes == NULL)
return;
char** current_mimetype = mimetypes;
/* Free all strings within NULL-terminated mimetype array */
while (*current_mimetype != NULL) {
free(*current_mimetype);
current_mimetype++;
}
/* Free the array itself, now that its contents have been freed */
free(mimetypes);
}
int __guac_user_call_opcode_handler(__guac_instruction_handler_mapping* map,
guac_user* user, const char* opcode, int argc, char** argv) {
/* For each defined instruction */
__guac_instruction_handler_mapping* current = map;
while (current->opcode != NULL) {
/* If recognized, call handler */
if (strcmp(opcode, current->opcode) == 0)
return current->handler(user, argc, argv);
current++;
}
/* If unrecognized, log and ignore */
guac_user_log(user, GUAC_LOG_DEBUG, "Handler not found for \"%s\"",
opcode);
return 0;
}

View File

@ -31,8 +31,8 @@
#include "config.h"
#include "client.h"
#include "timestamp.h"
#include "guacamole/client.h"
#include "guacamole/timestamp.h"
/**
* Internal handler for Guacamole instructions. Instruction handlers will be
@ -120,6 +120,13 @@ __guac_instruction_handler __guac_handle_file;
*/
__guac_instruction_handler __guac_handle_pipe;
/**
* Internal initial handler for the argv instruction. When a argv instruction
* is received, this handler will be called. The client's argv handler will
* be invoked if defined.
*/
__guac_instruction_handler __guac_handle_argv;
/**
* Internal initial handler for the ack instruction. When a ack instruction
* is received, this handler will be called. The client's ack handler will
@ -170,6 +177,47 @@ __guac_instruction_handler __guac_handle_size;
*/
__guac_instruction_handler __guac_handle_disconnect;
/**
* Internal handler for the nop instruction. This handler will be called when
* the nop instruction is received, and will do nothing more than a TRACE level
* log of the instruction.
*/
__guac_instruction_handler __guac_handle_nop;
/**
* Internal handler function that is called when the size instruction is
* received during the handshake process.
*/
__guac_instruction_handler __guac_handshake_size_handler;
/**
* Internal handler function that is called when the audio instruction is
* received during the handshake process, specifying the audio mimetypes
* available to the client.
*/
__guac_instruction_handler __guac_handshake_audio_handler;
/**
* Internal handler function that is called when the video instruction is
* received during the handshake process, specifying the video mimetypes
* available to the client.
*/
__guac_instruction_handler __guac_handshake_video_handler;
/**
* Internal handler function that is called when the image instruction is
* received during the handshake process, specifying the image mimetypes
* available to the client.
*/
__guac_instruction_handler __guac_handshake_image_handler;
/**
* Internal handler function that is called when the timezone instruction is
* received during the handshake process, specifying the timezone of the
* client.
*/
__guac_instruction_handler __guac_handshake_timezone_handler;
/**
* Instruction handler mapping table. This is a NULL-terminated array of
* __guac_instruction_handler_mapping structures, each mapping an opcode
@ -179,4 +227,72 @@ __guac_instruction_handler __guac_handle_disconnect;
*/
extern __guac_instruction_handler_mapping __guac_instruction_handler_map[];
/**
* Handler mapping table for instructions (opcodes) specifically for the
* handshake portion of the connection. Each
* __guac_instruction_handler_mapping structure within this NULL-terminated
* array maps an opcode to a __guac_instruction_handler. The end of the array
* must be marked with a mapping with the opcode set to NULL.
*/
extern __guac_instruction_handler_mapping __guac_handshake_handler_map[];
/**
* Frees the given array of mimetypes, including the space allocated to each
* mimetype string within the array. The provided array of mimetypes MUST have
* been allocated with guac_copy_mimetypes().
*
* @param mimetypes
* The NULL-terminated array of mimetypes to free. This array MUST have
* been previously allocated with guac_copy_mimetypes().
*/
void guac_free_mimetypes(char** mimetypes);
/**
* Copies the given array of mimetypes (strings) into a newly-allocated NULL-
* terminated array of strings. Both the array and the strings within the array
* are newly-allocated and must be later freed via guac_free_mimetypes().
*
* @param mimetypes
* The array of mimetypes to copy.
*
* @param count
* The number of mimetypes in the given array.
*
* @return
* A newly-allocated, NULL-terminated array containing newly-allocated
* copies of each of the mimetypes provided in the original mimetypes
* array.
*/
char** guac_copy_mimetypes(char** mimetypes, int count);
/**
* Call the appropriate handler defined by the given user for the given
* instruction. A comparison is made between the instruction opcode and the
* initial handler lookup table defined in the map that is provided to this
* function. If an entry for the instruction is found in the provided map,
* the handler defined in that map will be called and the value returned. If
* no match is found, it is silently ignored.
*
* @param map
* The array that holds the opcode to handler mappings.
*
* @param user
* The user whose handlers should be called.
*
* @param opcode
* The opcode of the instruction to pass to the user via the appropriate
* handler.
*
* @param argc
* The number of arguments which are part of the instruction.
*
* @param argv
* An array of all arguments which are part of the instruction.
*
* @return
* Zero if the instruction was handled successfully, or non-zero otherwise.
*/
int __guac_user_call_opcode_handler(__guac_instruction_handler_mapping* map,
guac_user* user, const char* opcode, int argc, char** argv);
#endif

View File

@ -19,12 +19,13 @@
#include "config.h"
#include "client.h"
#include "error.h"
#include "parser.h"
#include "protocol.h"
#include "socket.h"
#include "user.h"
#include "guacamole/client.h"
#include "guacamole/error.h"
#include "guacamole/parser.h"
#include "guacamole/protocol.h"
#include "guacamole/socket.h"
#include "guacamole/user.h"
#include "user-handlers.h"
#include <pthread.h>
#include <stdlib.h>
@ -113,64 +114,6 @@ static void guac_user_log_handshake_failure(guac_user* user) {
}
/**
* Copies the given array of mimetypes (strings) into a newly-allocated NULL-
* terminated array of strings. Both the array and the strings within the array
* are newly-allocated and must be later freed via guac_free_mimetypes().
*
* @param mimetypes
* The array of mimetypes to copy.
*
* @param count
* The number of mimetypes in the given array.
*
* @return
* A newly-allocated, NULL-terminated array containing newly-allocated
* copies of each of the mimetypes provided in the original mimetypes
* array.
*/
static char** guac_copy_mimetypes(char** mimetypes, int count) {
int i;
/* Allocate sufficient space for NULL-terminated array of mimetypes */
char** mimetypes_copy = malloc(sizeof(char*) * (count+1));
/* Copy each provided mimetype */
for (i = 0; i < count; i++)
mimetypes_copy[i] = strdup(mimetypes[i]);
/* Terminate with NULL */
mimetypes_copy[count] = NULL;
return mimetypes_copy;
}
/**
* Frees the given array of mimetypes, including the space allocated to each
* mimetype string within the array. The provided array of mimetypes MUST have
* been allocated with guac_copy_mimetypes().
*
* @param mimetypes
* The NULL-terminated array of mimetypes to free. This array MUST have
* been previously allocated with guac_copy_mimetypes().
*/
static void guac_free_mimetypes(char** mimetypes) {
char** current_mimetype = mimetypes;
/* Free all strings within NULL-terminated mimetype array */
while (*current_mimetype != NULL) {
free(*current_mimetype);
current_mimetype++;
}
/* Free the array itself, now that its contents have been freed */
free(mimetypes);
}
/**
* The thread which handles all user input, calling event handlers for received
* instructions.
@ -219,7 +162,8 @@ static void* guac_user_input_thread(void* data) {
guac_error_message = NULL;
/* Call handler, stop on error */
if (guac_user_handle_instruction(user, parser->opcode, parser->argc, parser->argv) < 0) {
if (__guac_user_call_opcode_handler(__guac_instruction_handler_map,
user, parser->opcode, parser->argc, parser->argv)) {
/* Log error */
guac_user_log_guac_error(user, GUAC_LOG_WARNING,
@ -286,11 +230,78 @@ static int guac_user_start(guac_parser* parser, guac_user* user,
}
/**
* This function loops through the received instructions during the handshake
* with the client attempting to join the connection, and runs the handlers
* for each of the opcodes, ending when the connect instruction is received.
* Returns zero if the handshake completes successfully with the connect opcode,
* or a non-zero value if an error occurs.
*
* @param user
* The guac_user attempting to join the connection.
*
* @param parser
* The parser used to examine the received data.
*
* @param usec_timeout
* The timeout, in microseconds, for reading the instructions.
*
* @return
* Zero if the handshake completes successfully with the connect opcode,
* or non-zero if an error occurs.
*/
static int __guac_user_handshake(guac_user* user, guac_parser* parser,
int usec_timeout) {
guac_socket* socket = user->socket;
/* Handle each of the opcodes. */
while (guac_parser_read(parser, socket, usec_timeout) == 0) {
/* If we receive the connect opcode, we're done. */
if (strcmp(parser->opcode, "connect") == 0)
return 0;
guac_user_log(user, GUAC_LOG_DEBUG, "Processing instruction: %s",
parser->opcode);
/* Run instruction handler for opcode with arguments. */
if (__guac_user_call_opcode_handler(__guac_handshake_handler_map, user,
parser->opcode, parser->argc, parser->argv)) {
guac_user_log_handshake_failure(user);
guac_user_log_guac_error(user, GUAC_LOG_DEBUG,
"Error handling instruction during handshake.");
guac_user_log(user, GUAC_LOG_DEBUG, "Failed opcode: %s",
parser->opcode);
guac_parser_free(parser);
return 1;
}
}
/* If we get here it's because we never got the connect instruction. */
guac_user_log(user, GUAC_LOG_ERROR,
"Handshake failed, \"connect\" instruction was not received.");
return 1;
}
int guac_user_handle_connection(guac_user* user, int usec_timeout) {
guac_socket* socket = user->socket;
guac_client* client = user->client;
user->info.audio_mimetypes = NULL;
user->info.image_mimetypes = NULL;
user->info.video_mimetypes = NULL;
user->info.timezone = NULL;
/* Count number of arguments. */
int num_args;
for (num_args = 0; client->args[num_args] != NULL; num_args++);
/* Send args */
if (guac_protocol_send_args(socket, client->args)
|| guac_socket_flush(socket)) {
@ -305,94 +316,8 @@ int guac_user_handle_connection(guac_user* user, int usec_timeout) {
guac_parser* parser = guac_parser_alloc();
/* Get optimal screen size */
if (guac_parser_expect(parser, socket, usec_timeout, "size")) {
/* Log error */
guac_user_log_handshake_failure(user);
guac_user_log_guac_error(user, GUAC_LOG_DEBUG,
"Error reading \"size\"");
guac_parser_free(parser);
return 1;
}
/* Validate content of size instruction */
if (parser->argc < 2) {
guac_user_log(user, GUAC_LOG_ERROR, "Received \"size\" "
"instruction lacked required arguments.");
guac_parser_free(parser);
return 1;
}
/* Parse optimal screen dimensions from size instruction */
user->info.optimal_width = atoi(parser->argv[0]);
user->info.optimal_height = atoi(parser->argv[1]);
/* If DPI given, set the user resolution */
if (parser->argc >= 3)
user->info.optimal_resolution = atoi(parser->argv[2]);
/* Otherwise, use a safe default for rough backwards compatibility */
else
user->info.optimal_resolution = 96;
/* Get supported audio formats */
if (guac_parser_expect(parser, socket, usec_timeout, "audio")) {
/* Log error */
guac_user_log_handshake_failure(user);
guac_user_log_guac_error(user, GUAC_LOG_DEBUG,
"Error reading \"audio\"");
guac_parser_free(parser);
return 1;
}
/* Store audio mimetypes */
char** audio_mimetypes = guac_copy_mimetypes(parser->argv, parser->argc);
user->info.audio_mimetypes = (const char**) audio_mimetypes;
/* Get supported video formats */
if (guac_parser_expect(parser, socket, usec_timeout, "video")) {
/* Log error */
guac_user_log_handshake_failure(user);
guac_user_log_guac_error(user, GUAC_LOG_DEBUG,
"Error reading \"video\"");
guac_parser_free(parser);
return 1;
}
/* Store video mimetypes */
char** video_mimetypes = guac_copy_mimetypes(parser->argv, parser->argc);
user->info.video_mimetypes = (const char**) video_mimetypes;
/* Get supported image formats */
if (guac_parser_expect(parser, socket, usec_timeout, "image")) {
/* Log error */
guac_user_log_handshake_failure(user);
guac_user_log_guac_error(user, GUAC_LOG_DEBUG,
"Error reading \"image\"");
guac_parser_free(parser);
return 1;
}
/* Store image mimetypes */
char** image_mimetypes = guac_copy_mimetypes(parser->argv, parser->argc);
user->info.image_mimetypes = (const char**) image_mimetypes;
/* Get args from connect instruction */
if (guac_parser_expect(parser, socket, usec_timeout, "connect")) {
/* Log error */
guac_user_log_handshake_failure(user);
guac_user_log_guac_error(user, GUAC_LOG_DEBUG,
"Error reading \"connect\"");
/* Perform the handshake with the client. */
if (__guac_user_handshake(user, parser, usec_timeout)) {
guac_parser_free(parser);
return 1;
}
@ -400,9 +325,16 @@ int guac_user_handle_connection(guac_user* user, int usec_timeout) {
/* Acknowledge connection availability */
guac_protocol_send_ready(socket, client->connection_id);
guac_socket_flush(socket);
/* Attempt join */
if (guac_client_add_user(client, user, parser->argc, parser->argv))
/* Verify argument count. */
if (parser->argc != (num_args + 1)) {
guac_client_log(client, GUAC_LOG_ERROR, "Client did not return the "
"expected number of arguments.");
return 1;
}
/* Attempt to join user to connection. */
if (guac_client_add_user(client, user, (parser->argc - 1), parser->argv + 1))
guac_client_log(client, GUAC_LOG_ERROR, "User \"%s\" could NOT "
"join connection \"%s\"", user->user_id, client->connection_id);
@ -412,6 +344,12 @@ int guac_user_handle_connection(guac_user* user, int usec_timeout) {
guac_client_log(client, GUAC_LOG_INFO, "User \"%s\" joined connection "
"\"%s\" (%i users now present)", user->user_id,
client->connection_id, client->connected_users);
if (strcmp(parser->argv[0],"") != 0)
guac_client_log(client, GUAC_LOG_DEBUG, "Client is using protocol "
"version \"%s\"", parser->argv[0]);
else
guac_client_log(client, GUAC_LOG_DEBUG, "Client has not defined "
"its protocol version.");
/* Handle user I/O, wait for connection to terminate */
guac_user_start(parser, user, usec_timeout);
@ -422,12 +360,15 @@ int guac_user_handle_connection(guac_user* user, int usec_timeout) {
"users remain)", user->user_id, client->connected_users);
}
/* Free mimetype lists */
guac_free_mimetypes(audio_mimetypes);
guac_free_mimetypes(video_mimetypes);
guac_free_mimetypes(image_mimetypes);
/* Free mimetype character arrays. */
guac_free_mimetypes((char **) user->info.audio_mimetypes);
guac_free_mimetypes((char **) user->info.image_mimetypes);
guac_free_mimetypes((char **) user->info.video_mimetypes);
/* Free timezone info. */
free((char *) user->info.timezone);
guac_parser_free(parser);
/* Successful disconnect */

View File

@ -19,18 +19,18 @@
#include "config.h"
#include "client.h"
#include "encode-jpeg.h"
#include "encode-png.h"
#include "encode-webp.h"
#include "guacamole/client.h"
#include "guacamole/object.h"
#include "guacamole/pool.h"
#include "guacamole/protocol.h"
#include "guacamole/socket.h"
#include "guacamole/stream.h"
#include "guacamole/timestamp.h"
#include "guacamole/user.h"
#include "id.h"
#include "object.h"
#include "pool.h"
#include "protocol.h"
#include "socket.h"
#include "stream.h"
#include "timestamp.h"
#include "user.h"
#include "user-handlers.h"
#include <errno.h>
@ -169,19 +169,8 @@ void guac_user_free_object(guac_user* user, guac_object* object) {
int guac_user_handle_instruction(guac_user* user, const char* opcode, int argc, char** argv) {
/* For each defined instruction */
__guac_instruction_handler_mapping* current = __guac_instruction_handler_map;
while (current->opcode != NULL) {
/* If recognized, call handler */
if (strcmp(opcode, current->opcode) == 0)
return current->handler(user, argc, argv);
current++;
}
/* If unrecognized, ignore */
return 0;
return __guac_user_call_opcode_handler(__guac_instruction_handler_map,
user, opcode, argc, argv);
}
@ -240,6 +229,26 @@ void guac_user_log(guac_user* user, guac_client_log_level level,
}
void guac_user_stream_argv(guac_user* user, guac_socket* socket,
const char* mimetype, const char* name, const char* value) {
/* Allocate new stream for argument value */
guac_stream* stream = guac_user_alloc_stream(user);
/* Declare stream as containing connection parameter data */
guac_protocol_send_argv(socket, stream, mimetype, name);
/* Write parameter data */
guac_protocol_send_blobs(socket, stream, value, strlen(value));
/* Terminate stream */
guac_protocol_send_end(socket, stream);
/* Free allocated stream */
guac_user_free_stream(user, stream);
}
void guac_user_stream_png(guac_user* user, guac_socket* socket,
guac_composite_mode mode, const guac_layer* layer, int x, int y,
cairo_surface_t* surface) {

View File

@ -0,0 +1,72 @@
#
# 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.
#
# NOTE: Parts of this file (Makefile.am) are automatically transcluded verbatim
# into Makefile.in. Though the build system (GNU Autotools) automatically adds
# its own license boilerplate to the generated Makefile.in, that boilerplate
# does not apply to the transcluded portions of Makefile.am which are licensed
# to you by the ASF under the Apache License, Version 2.0, as described above.
#
AUTOMAKE_OPTIONS = foreign
ACLOCAL_AMFLAGS = -I m4
lib_LTLIBRARIES = libguac-client-kubernetes.la
libguac_client_kubernetes_la_SOURCES = \
argv.c \
client.c \
clipboard.c \
input.c \
io.c \
pipe.c \
settings.c \
ssl.c \
kubernetes.c \
url.c \
user.c
noinst_HEADERS = \
argv.h \
client.h \
clipboard.h \
input.h \
io.h \
pipe.h \
settings.h \
ssl.h \
kubernetes.h \
url.h \
user.h
libguac_client_kubernetes_la_CFLAGS = \
-Werror -Wall -Iinclude \
@LIBGUAC_INCLUDE@ \
@TERMINAL_INCLUDE@
libguac_client_kubernetes_la_LIBADD = \
@COMMON_LTLIB@ \
@LIBGUAC_LTLIB@ \
@TERMINAL_LTLIB@
libguac_client_kubernetes_la_LDFLAGS = \
-version-info 0:0:0 \
@PTHREAD_LIBS@ \
@SSL_LIBS@ \
@WEBSOCKETS_LIBS@

View File

@ -0,0 +1,224 @@
/*
* 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 "config.h"
#include "argv.h"
#include "kubernetes.h"
#include "terminal/terminal.h"
#include <guacamole/protocol.h>
#include <guacamole/socket.h>
#include <guacamole/user.h>
#include <stdlib.h>
#include <string.h>
/**
* All Kubernetes connection settings which may be updated by unprivileged
* users through "argv" streams.
*/
typedef enum guac_kubernetes_argv_setting {
/**
* The color scheme of the terminal.
*/
GUAC_KUBERNETES_ARGV_SETTING_COLOR_SCHEME,
/**
* The name of the font family used by the terminal.
*/
GUAC_KUBERNETES_ARGV_SETTING_FONT_NAME,
/**
* The size of the font used by the terminal, in points.
*/
GUAC_KUBERNETES_ARGV_SETTING_FONT_SIZE
} guac_kubernetes_argv_setting;
/**
* The value or current status of a connection parameter received over an
* "argv" stream.
*/
typedef struct guac_kubernetes_argv {
/**
* The specific setting being updated.
*/
guac_kubernetes_argv_setting setting;
/**
* Buffer space for containing the received argument value.
*/
char buffer[GUAC_KUBERNETES_ARGV_MAX_LENGTH];
/**
* The number of bytes received so far.
*/
int length;
} guac_kubernetes_argv;
/**
* Handler for "blob" instructions which appends the data from received blobs
* to the end of the in-progress argument value buffer.
*
* @see guac_user_blob_handler
*/
static int guac_kubernetes_argv_blob_handler(guac_user* user,
guac_stream* stream, void* data, int length) {
guac_kubernetes_argv* argv = (guac_kubernetes_argv*) stream->data;
/* Calculate buffer size remaining, including space for null terminator,
* adjusting received length accordingly */
int remaining = sizeof(argv->buffer) - argv->length - 1;
if (length > remaining)
length = remaining;
/* Append received data to end of buffer */
memcpy(argv->buffer + argv->length, data, length);
argv->length += length;
return 0;
}
/**
* Handler for "end" instructions which applies the changes specified by the
* argument value buffer associated with the stream.
*
* @see guac_user_end_handler
*/
static int guac_kubernetes_argv_end_handler(guac_user* user,
guac_stream* stream) {
int size;
guac_client* client = user->client;
guac_kubernetes_client* kubernetes_client = (guac_kubernetes_client*) client->data;
guac_terminal* terminal = kubernetes_client->term;
/* Append null terminator to value */
guac_kubernetes_argv* argv = (guac_kubernetes_argv*) stream->data;
argv->buffer[argv->length] = '\0';
/* Apply changes to chosen setting */
switch (argv->setting) {
/* Update color scheme */
case GUAC_KUBERNETES_ARGV_SETTING_COLOR_SCHEME:
guac_terminal_apply_color_scheme(terminal, argv->buffer);
guac_client_stream_argv(client, client->socket, "text/plain",
"color-scheme", argv->buffer);
break;
/* Update font name */
case GUAC_KUBERNETES_ARGV_SETTING_FONT_NAME:
guac_terminal_apply_font(terminal, argv->buffer, -1, 0);
guac_client_stream_argv(client, client->socket, "text/plain",
"font-name", argv->buffer);
break;
/* Update font size */
case GUAC_KUBERNETES_ARGV_SETTING_FONT_SIZE:
/* Update only if font size is sane */
size = atoi(argv->buffer);
if (size > 0) {
guac_terminal_apply_font(terminal, NULL, size,
kubernetes_client->settings->resolution);
guac_client_stream_argv(client, client->socket, "text/plain",
"font-size", argv->buffer);
}
break;
}
/* Update Kubernetes terminal size */
guac_kubernetes_resize(client, terminal->term_height,
terminal->term_width);
free(argv);
return 0;
}
int guac_kubernetes_argv_handler(guac_user* user, guac_stream* stream,
char* mimetype, char* name) {
guac_kubernetes_argv_setting setting;
/* Allow users to update the color scheme and font details */
if (strcmp(name, "color-scheme") == 0)
setting = GUAC_KUBERNETES_ARGV_SETTING_COLOR_SCHEME;
else if (strcmp(name, "font-name") == 0)
setting = GUAC_KUBERNETES_ARGV_SETTING_FONT_NAME;
else if (strcmp(name, "font-size") == 0)
setting = GUAC_KUBERNETES_ARGV_SETTING_FONT_SIZE;
/* No other connection parameters may be updated */
else {
guac_protocol_send_ack(user->socket, stream, "Not allowed.",
GUAC_PROTOCOL_STATUS_CLIENT_FORBIDDEN);
guac_socket_flush(user->socket);
return 0;
}
guac_kubernetes_argv* argv = malloc(sizeof(guac_kubernetes_argv));
argv->setting = setting;
argv->length = 0;
/* Prepare stream to receive argument value */
stream->blob_handler = guac_kubernetes_argv_blob_handler;
stream->end_handler = guac_kubernetes_argv_end_handler;
stream->data = argv;
/* Signal stream is ready */
guac_protocol_send_ack(user->socket, stream, "Ready for updated "
"parameter.", GUAC_PROTOCOL_STATUS_SUCCESS);
guac_socket_flush(user->socket);
return 0;
}
void* guac_kubernetes_send_current_argv(guac_user* user, void* data) {
guac_kubernetes_client* kubernetes_client = (guac_kubernetes_client*) data;
guac_terminal* terminal = kubernetes_client->term;
/* Send current color scheme */
guac_user_stream_argv(user, user->socket, "text/plain", "color-scheme",
terminal->color_scheme);
/* Send current font name */
guac_user_stream_argv(user, user->socket, "text/plain", "font-name",
terminal->font_name);
/* Send current font size */
char font_size[64];
sprintf(font_size, "%i", terminal->font_size);
guac_user_stream_argv(user, user->socket, "text/plain", "font-size",
font_size);
return NULL;
}

View File

@ -0,0 +1,61 @@
/*
* 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.
*/
#ifndef GUAC_KUBERNETES_ARGV_H
#define GUAC_KUBERNETES_ARGV_H
#include "config.h"
#include <guacamole/user.h>
/**
* The maximum number of bytes to allow for any argument value received via an
* argv stream, including null terminator.
*/
#define GUAC_KUBERNETES_ARGV_MAX_LENGTH 16384
/**
* Handles an incoming stream from a Guacamole "argv" instruction, updating the
* given connection parameter if that parameter is allowed to be updated.
*/
guac_user_argv_handler guac_kubernetes_argv_handler;
/**
* Sends the current values of all non-sensitive parameters which may be set
* while the connection is running to the given user. Note that the user
* receiving these values will not necessarily be able to set new values
* themselves if their connection is read-only. This function can be used as
* the callback for guac_client_foreach_user() and guac_client_for_owner()
*
* @param user
* The user that should receive the values of all non-sensitive parameters
* which may be set while the connection is running.
*
* @param data
* The guac_kubernetes_client instance associated with the current
* connection.
*
* @return
* Always NULL.
*/
void* guac_kubernetes_send_current_argv(guac_user* user, void* data);
#endif

View File

@ -0,0 +1,133 @@
/*
* 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 "client.h"
#include "common/clipboard.h"
#include "kubernetes.h"
#include "settings.h"
#include "user.h"
#include <guacamole/client.h>
#include <libwebsockets.h>
#include <langinfo.h>
#include <locale.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
guac_client* guac_kubernetes_lws_current_client = NULL;
/**
* Logging callback invoked by libwebsockets to log a single line of logging
* output. As libwebsockets messages are all generally low-level, the log
* level provided by libwebsockets is ignored here, with all messages logged
* instead at guacd's debug level.
*
* @param level
* The libwebsockets log level associated with the log message. This value
* is ignored by this implementation of the logging callback.
*
* @param line
* The line of logging output to log.
*/
static void guac_kubernetes_log(int level, const char* line) {
char buffer[1024];
/* Drop log message if there's nowhere to log yet */
if (guac_kubernetes_lws_current_client == NULL)
return;
/* Trim length of line to fit buffer (plus null terminator) */
int length = strlen(line);
if (length > sizeof(buffer) - 1)
length = sizeof(buffer) - 1;
/* Copy as much of the received line as will fit in the buffer */
memcpy(buffer, line, length);
/* If the line ends with a newline character, trim the character */
if (length > 0 && buffer[length - 1] == '\n')
length--;
/* Null-terminate the trimmed string */
buffer[length] = '\0';
/* Log using guacd's own log facilities */
guac_client_log(guac_kubernetes_lws_current_client, GUAC_LOG_DEBUG,
"libwebsockets: %s", buffer);
}
int guac_client_init(guac_client* client) {
/* Ensure reference to main guac_client remains available in all
* libwebsockets contexts */
guac_kubernetes_lws_current_client = client;
/* Redirect libwebsockets logging */
lws_set_log_level(LLL_ERR | LLL_WARN | LLL_NOTICE | LLL_INFO,
guac_kubernetes_log);
/* Set client args */
client->args = GUAC_KUBERNETES_CLIENT_ARGS;
/* Allocate client instance data */
guac_kubernetes_client* kubernetes_client = calloc(1, sizeof(guac_kubernetes_client));
client->data = kubernetes_client;
/* Init clipboard */
kubernetes_client->clipboard = guac_common_clipboard_alloc(GUAC_KUBERNETES_CLIPBOARD_MAX_LENGTH);
/* Set handlers */
client->join_handler = guac_kubernetes_user_join_handler;
client->free_handler = guac_kubernetes_client_free_handler;
/* Set locale and warn if not UTF-8 */
setlocale(LC_CTYPE, "");
if (strcmp(nl_langinfo(CODESET), "UTF-8") != 0) {
guac_client_log(client, GUAC_LOG_INFO,
"Current locale does not use UTF-8. Some characters may "
"not render correctly.");
}
/* Success */
return 0;
}
int guac_kubernetes_client_free_handler(guac_client* client) {
guac_kubernetes_client* kubernetes_client =
(guac_kubernetes_client*) client->data;
/* Wait client thread to terminate */
pthread_join(kubernetes_client->client_thread, NULL);
/* Free settings */
if (kubernetes_client->settings != NULL)
guac_kubernetes_settings_free(kubernetes_client->settings);
guac_common_clipboard_free(kubernetes_client->clipboard);
free(kubernetes_client);
return 0;
}

View File

@ -17,40 +17,28 @@
* under the License.
*/
#include "config.h"
#ifndef GUAC_KUBERNETES_CLIENT_H
#define GUAC_KUBERNETES_CLIENT_H
#include "client_suite.h"
#include <guacamole/client.h>
#include <CUnit/Basic.h>
/**
* The maximum number of bytes to allow within the clipboard.
*/
#define GUAC_KUBERNETES_CLIPBOARD_MAX_LENGTH 262144
int client_suite_init() {
return 0;
}
/**
* Static reference to the guac_client associated with the active Kubernetes
* connection. While libwebsockets provides some means of storing and
* retrieving custom data in some structures, this is not always available.
*/
extern guac_client* guac_kubernetes_lws_current_client;
int client_suite_cleanup() {
return 0;
}
/**
* Free handler. Required by libguac and called when the guac_client is
* disconnected and must be cleaned up.
*/
guac_client_free_handler guac_kubernetes_client_free_handler;
int register_client_suite() {
/* Add client test suite */
CU_pSuite suite = CU_add_suite("client",
client_suite_init, client_suite_cleanup);
if (suite == NULL) {
CU_cleanup_registry();
return CU_get_error();
}
/* Add tests */
if (
CU_add_test(suite, "layer-pool", test_layer_pool) == NULL
|| CU_add_test(suite, "buffer-pool", test_buffer_pool) == NULL
) {
CU_cleanup_registry();
return CU_get_error();
}
return 0;
}
#endif

View File

@ -0,0 +1,65 @@
/*
* 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 "clipboard.h"
#include "common/clipboard.h"
#include "kubernetes.h"
#include <guacamole/client.h>
#include <guacamole/stream.h>
#include <guacamole/user.h>
int guac_kubernetes_clipboard_handler(guac_user* user, guac_stream* stream,
char* mimetype) {
guac_client* client = user->client;
guac_kubernetes_client* kubernetes_client =
(guac_kubernetes_client*) client->data;
/* Clear clipboard and prepare for new data */
guac_common_clipboard_reset(kubernetes_client->clipboard, mimetype);
/* Set handlers for clipboard stream */
stream->blob_handler = guac_kubernetes_clipboard_blob_handler;
stream->end_handler = guac_kubernetes_clipboard_end_handler;
return 0;
}
int guac_kubernetes_clipboard_blob_handler(guac_user* user,
guac_stream* stream, void* data, int length) {
guac_client* client = user->client;
guac_kubernetes_client* kubernetes_client =
(guac_kubernetes_client*) client->data;
/* Append new data */
guac_common_clipboard_append(kubernetes_client->clipboard, data, length);
return 0;
}
int guac_kubernetes_clipboard_end_handler(guac_user* user,
guac_stream* stream) {
/* Nothing to do - clipboard is implemented within client */
return 0;
}

View File

@ -17,39 +17,25 @@
* under the License.
*/
#ifndef GUAC_KUBERNETES_CLIPBOARD_H
#define GUAC_KUBERNETES_CLIPBOARD_H
#ifndef _GUAC_TEST_COMMON_SUITE_H
#define _GUAC_TEST_COMMON_SUITE_H
#include <guacamole/user.h>
/**
* Test suite containing unit tests for the "common" utility library included
* for the sake of simplifying guacamole-server development, but not included
* as part of libguac.
*
* @file common_suite.h
* Handler for inbound clipboard streams.
*/
#include "config.h"
guac_user_clipboard_handler guac_kubernetes_clipboard_handler;
/**
* Registers the common test suite with CUnit.
* Handler for data received along clipboard streams.
*/
int register_common_suite();
guac_user_blob_handler guac_kubernetes_clipboard_blob_handler;
/**
* Unit test for string utility functions.
* Handler for end-of-stream related to clipboard.
*/
void test_guac_string();
/**
* Unit test for character conversion functions.
*/
void test_guac_iconv();
/**
* Unit test for rectangle calculation functions.
*/
void test_guac_rect();
guac_user_end_handler guac_kubernetes_clipboard_end_handler;
#endif

View File

@ -0,0 +1,94 @@
/*
* 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/recording.h"
#include "input.h"
#include "kubernetes.h"
#include "terminal/terminal.h"
#include <guacamole/client.h>
#include <guacamole/user.h>
#include <stdlib.h>
int guac_kubernetes_user_mouse_handler(guac_user* user,
int x, int y, int mask) {
guac_client* client = user->client;
guac_kubernetes_client* kubernetes_client =
(guac_kubernetes_client*) client->data;
/* Skip if terminal not yet ready */
guac_terminal* term = kubernetes_client->term;
if (term == NULL)
return 0;
/* Report mouse position within recording */
if (kubernetes_client->recording != NULL)
guac_common_recording_report_mouse(kubernetes_client->recording, x, y,
mask);
guac_terminal_send_mouse(term, user, x, y, mask);
return 0;
}
int guac_kubernetes_user_key_handler(guac_user* user, int keysym, int pressed) {
guac_client* client = user->client;
guac_kubernetes_client* kubernetes_client =
(guac_kubernetes_client*) client->data;
/* Report key state within recording */
if (kubernetes_client->recording != NULL)
guac_common_recording_report_key(kubernetes_client->recording,
keysym, pressed);
/* Skip if terminal not yet ready */
guac_terminal* term = kubernetes_client->term;
if (term == NULL)
return 0;
guac_terminal_send_key(term, keysym, pressed);
return 0;
}
int guac_kubernetes_user_size_handler(guac_user* user, int width, int height) {
/* Get terminal */
guac_client* client = user->client;
guac_kubernetes_client* kubernetes_client =
(guac_kubernetes_client*) client->data;
/* Skip if terminal not yet ready */
guac_terminal* terminal = kubernetes_client->term;
if (terminal == NULL)
return 0;
/* Resize terminal */
guac_terminal_resize(terminal, width, height);
/* Update Kubernetes terminal window size if connected */
guac_kubernetes_resize(client, terminal->term_height,
terminal->term_width);
return 0;
}

View File

@ -17,27 +17,28 @@
* under the License.
*/
#ifndef GUAC_KUBERNETES_INPUT_H
#define GUAC_KUBERNETES_INPUT_H
#ifndef _GUAC_TEST_PROTOCOL_SUITE_H
#define _GUAC_TEST_PROTOCOL_SUITE_H
#include <guacamole/user.h>
#include "config.h"
/**
* Handler for key events. Required by libguac and called whenever key events
* are received.
*/
guac_user_key_handler guac_kubernetes_user_key_handler;
/* Unicode (UTF-8) strings */
/**
* Handler for mouse events. Required by libguac and called whenever mouse
* events are received.
*/
guac_user_mouse_handler guac_kubernetes_user_mouse_handler;
#define UTF8_1 "\xe7\x8a\xac" /* One character */
#define UTF8_2 UTF8_1 "\xf0\x90\xac\x80" /* Two characters */
#define UTF8_3 UTF8_2 "z" /* Three characters */
#define UTF8_4 UTF8_3 "\xc3\xa1" /* Four characters */
#define UTF8_8 UTF8_4 UTF8_4 /* Eight characters */
int register_protocol_suite();
void test_base64_decode();
void test_instruction_parse();
void test_instruction_read();
void test_instruction_write();
void test_nest_write();
/**
* Handler for size events. Required by libguac and called whenever the remote
* display (window) is resized.
*/
guac_user_size_handler guac_kubernetes_user_size_handler;
#endif

View File

@ -0,0 +1,143 @@
/*
* 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 "kubernetes.h"
#include "terminal/terminal.h"
#include <guacamole/client.h>
#include <libwebsockets.h>
#include <pthread.h>
#include <stdbool.h>
#include <string.h>
void guac_kubernetes_receive_data(guac_client* client,
const char* buffer, size_t length) {
guac_kubernetes_client* kubernetes_client =
(guac_kubernetes_client*) client->data;
/* Strip channel index from beginning of buffer */
int channel = *(buffer++);
length--;
switch (channel) {
/* Write STDOUT / STDERR directly to terminal as output */
case GUAC_KUBERNETES_CHANNEL_STDOUT:
case GUAC_KUBERNETES_CHANNEL_STDERR:
guac_terminal_write(kubernetes_client->term, buffer, length);
break;
/* Ignore data on other channels */
default:
guac_client_log(client, GUAC_LOG_DEBUG, "Received %i bytes along "
"channel %i.", length, channel);
}
}
void guac_kubernetes_send_message(guac_client* client,
int channel, const char* data, int length) {
guac_kubernetes_client* kubernetes_client =
(guac_kubernetes_client*) client->data;
pthread_mutex_lock(&(kubernetes_client->outbound_message_lock));
/* Add message to buffer if space is available */
if (kubernetes_client->outbound_messages_waiting
< GUAC_KUBERNETES_MAX_OUTBOUND_MESSAGES) {
/* Calculate storage position of next message */
int index = (kubernetes_client->outbound_messages_top
+ kubernetes_client->outbound_messages_waiting)
% GUAC_KUBERNETES_MAX_OUTBOUND_MESSAGES;
/* Obtain pointer to message slot at calculated position */
guac_kubernetes_message* message =
&(kubernetes_client->outbound_messages[index]);
/* Copy details of message into buffer */
message->channel = channel;
memcpy(message->data, data, length);
message->length = length;
/* One more message is now waiting */
kubernetes_client->outbound_messages_waiting++;
/* Notify libwebsockets that we need a callback to send pending
* messages */
lws_callback_on_writable(kubernetes_client->wsi);
lws_cancel_service(kubernetes_client->context);
}
/* Warn if data has to be dropped */
else
guac_client_log(client, GUAC_LOG_WARNING, "Send buffer could not be "
"flushed in time to handle additional data. Outbound "
"message dropped.");
pthread_mutex_unlock(&(kubernetes_client->outbound_message_lock));
}
bool guac_kubernetes_write_pending_message(guac_client* client) {
bool messages_remain;
guac_kubernetes_client* kubernetes_client =
(guac_kubernetes_client*) client->data;
pthread_mutex_lock(&(kubernetes_client->outbound_message_lock));
/* Send one message from top of buffer */
if (kubernetes_client->outbound_messages_waiting > 0) {
/* Obtain pointer to message at top */
int top = kubernetes_client->outbound_messages_top;
guac_kubernetes_message* message =
&(kubernetes_client->outbound_messages[top]);
/* Write message including channel index */
lws_write(kubernetes_client->wsi,
((unsigned char*) message) + LWS_PRE,
message->length + 1, LWS_WRITE_BINARY);
/* Advance top to next message */
kubernetes_client->outbound_messages_top++;
kubernetes_client->outbound_messages_top %=
GUAC_KUBERNETES_MAX_OUTBOUND_MESSAGES;
/* One less message is waiting */
kubernetes_client->outbound_messages_waiting--;
}
/* Record whether messages remained at time of completion */
messages_remain = (kubernetes_client->outbound_messages_waiting > 0);
pthread_mutex_unlock(&(kubernetes_client->outbound_message_lock));
return messages_remain;
}

View File

@ -0,0 +1,144 @@
/*
* 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.
*/
#ifndef GUAC_KUBERNETES_IO_H
#define GUAC_KUBERNETES_IO_H
#include <guacamole/client.h>
#include <libwebsockets.h>
#include <stdbool.h>
#include <stdint.h>
/**
* The maximum amount of data to include in any particular WebSocket message
* to Kubernetes. This excludes the storage space required for the channel
* index.
*/
#define GUAC_KUBERNETES_MAX_MESSAGE_SIZE 1024
/**
* The index of the Kubernetes channel used for STDIN.
*/
#define GUAC_KUBERNETES_CHANNEL_STDIN 0
/**
* The index of the Kubernetes channel used for STDOUT.
*/
#define GUAC_KUBERNETES_CHANNEL_STDOUT 1
/**
* The index of the Kubernetes channel used for STDERR.
*/
#define GUAC_KUBERNETES_CHANNEL_STDERR 2
/**
* The index of the Kubernetes channel used for terminal resize messages.
*/
#define GUAC_KUBERNETES_CHANNEL_RESIZE 4
/**
* An outbound message to be received by Kubernetes over WebSocket.
*/
typedef struct guac_kubernetes_message {
/**
* lws_write() requires leading padding of LWS_PRE bytes to provide
* scratch space for WebSocket framing.
*/
uint8_t _padding[LWS_PRE];
/**
* The index of the channel receiving the data, such as
* GUAC_KUBERNETES_CHANNEL_STDIN.
*/
uint8_t channel;
/**
* The data that should be sent to Kubernetes (along with the channel
* index).
*/
char data[GUAC_KUBERNETES_MAX_MESSAGE_SIZE];
/**
* The length of the data to be sent, excluding the channel index.
*/
int length;
} guac_kubernetes_message;
/**
* Handles data received from Kubernetes over WebSocket, decoding the channel
* index of the received data and forwarding that data accordingly.
*
* @param client
* The guac_client associated with the connection to Kubernetes.
*
* @param buffer
* The data received from Kubernetes.
*
* @param length
* The size of the data received from Kubernetes, in bytes.
*/
void guac_kubernetes_receive_data(guac_client* client,
const char* buffer, size_t length);
/**
* Requests that the given data be sent along the given channel to the
* Kubernetes server when the WebSocket connection is next available for
* writing. If the WebSocket connection has not been available for writing for
* long enough that the outbound message buffer is full, the request to send
* this particular message will be dropped.
*
* @param client
* The guac_client associated with the Kubernetes connection.
*
* @param channel
* The Kubernetes channel on which to send the message,
* such as GUAC_KUBERNETES_CHANNEL_STDIN.
*
* @param data
* A buffer containing the data to send.
*
* @param length
* The number of bytes to send.
*/
void guac_kubernetes_send_message(guac_client* client,
int channel, const char* data, int length);
/**
* Writes the oldest pending message within the outbound message queue,
* as scheduled with guac_kubernetes_send_message(), removing that message
* from the queue. This function MAY NOT be invoked outside the libwebsockets
* event callback and MUST only be invoked in the context of a
* LWS_CALLBACK_CLIENT_WRITEABLE event. If no messages are pending, this
* function has no effect.
*
* @param client
* The guac_client associated with the Kubernetes connection.
*
* @return
* true if messages still remain to be written within the outbound message
* queue, false otherwise.
*/
bool guac_kubernetes_write_pending_message(guac_client* client);
#endif

View File

@ -0,0 +1,413 @@
/*
* 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 "config.h"
#include "argv.h"
#include "client.h"
#include "common/recording.h"
#include "io.h"
#include "kubernetes.h"
#include "ssl.h"
#include "terminal/terminal.h"
#include "url.h"
#include <guacamole/client.h>
#include <guacamole/protocol.h>
#include <libwebsockets.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
/**
* Callback invoked by libwebsockets for events related to a WebSocket being
* used for communicating with an attached Kubernetes pod.
*
* @param wsi
* The libwebsockets handle for the WebSocket connection.
*
* @param reason
* The reason (event) that this callback was invoked.
*
* @param user
* Arbitrary data assocated with the WebSocket session. In some cases,
* this is actually event-specific data (such as the
* LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERT event).
*
* @param in
* A pointer to arbitrary, reason-specific data.
*
* @param length
* An arbitrary, reason-specific length value.
*
* @return
* An undocumented integer value related the success of handling the
* event, or -1 if the WebSocket connection should be closed.
*/
static int guac_kubernetes_lws_callback(struct lws* wsi,
enum lws_callback_reasons reason, void* user,
void* in, size_t length) {
guac_client* client = guac_kubernetes_lws_current_client;
guac_kubernetes_client* kubernetes_client =
(guac_kubernetes_client*) client->data;
/* Do not handle any further events if connection is closing */
if (client->state != GUAC_CLIENT_RUNNING) {
#ifdef HAVE_LWS_CALLBACK_HTTP_DUMMY
return lws_callback_http_dummy(wsi, reason, user, in, length);
#else
return 0;
#endif
}
switch (reason) {
/* Complete initialization of SSL */
case LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERTS:
guac_kubernetes_init_ssl(client, (SSL_CTX*) user);
break;
/* Failed to connect */
case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_NOT_FOUND,
"Error connecting to Kubernetes server: %s",
in != NULL ? (char*) in : "(no error description "
"available)");
break;
/* Connected / logged in */
case LWS_CALLBACK_CLIENT_ESTABLISHED:
guac_client_log(client, GUAC_LOG_INFO,
"Kubernetes connection successful.");
/* Allow terminal to render */
guac_terminal_start(kubernetes_client->term);
/* Schedule check for pending messages in case messages were added
* to the outbound message buffer prior to the connection being
* fully established */
lws_callback_on_writable(wsi);
break;
/* Data received via WebSocket */
case LWS_CALLBACK_CLIENT_RECEIVE:
guac_kubernetes_receive_data(client, (const char*) in, length);
break;
/* WebSocket is ready for writing */
case LWS_CALLBACK_CLIENT_WRITEABLE:
/* Send any pending messages, requesting another callback if
* yet more messages remain */
if (guac_kubernetes_write_pending_message(client))
lws_callback_on_writable(wsi);
break;
#ifdef HAVE_LWS_CALLBACK_CLIENT_CLOSED
/* Connection closed (client-specific) */
case LWS_CALLBACK_CLIENT_CLOSED:
#endif
/* Connection closed */
case LWS_CALLBACK_WSI_DESTROY:
case LWS_CALLBACK_CLOSED:
guac_client_stop(client);
guac_client_log(client, GUAC_LOG_DEBUG, "WebSocket connection to "
"Kubernetes server closed.");
break;
/* No other event types are applicable */
default:
break;
}
#ifdef HAVE_LWS_CALLBACK_HTTP_DUMMY
return lws_callback_http_dummy(wsi, reason, user, in, length);
#else
return 0;
#endif
}
/**
* List of all WebSocket protocols which should be declared as supported by
* libwebsockets during the initial WebSocket handshake, along with
* corresponding event-handling callbacks.
*/
struct lws_protocols guac_kubernetes_lws_protocols[] = {
{
.name = GUAC_KUBERNETES_LWS_PROTOCOL,
.callback = guac_kubernetes_lws_callback
},
{ 0 }
};
/**
* Input thread, started by the main Kubernetes client thread. This thread
* continuously reads from the terminal's STDIN and transfers all read
* data to the Kubernetes connection.
*
* @param data
* The current guac_client instance.
*
* @return
* Always NULL.
*/
static void* guac_kubernetes_input_thread(void* data) {
guac_client* client = (guac_client*) data;
guac_kubernetes_client* kubernetes_client =
(guac_kubernetes_client*) client->data;
char buffer[GUAC_KUBERNETES_MAX_MESSAGE_SIZE];
int bytes_read;
/* Write all data read */
while ((bytes_read = guac_terminal_read_stdin(kubernetes_client->term, buffer, sizeof(buffer))) > 0) {
/* Send received data to Kubernetes along STDIN channel */
guac_kubernetes_send_message(client, GUAC_KUBERNETES_CHANNEL_STDIN,
buffer, bytes_read);
}
return NULL;
}
void* guac_kubernetes_client_thread(void* data) {
guac_client* client = (guac_client*) data;
guac_kubernetes_client* kubernetes_client =
(guac_kubernetes_client*) client->data;
guac_kubernetes_settings* settings = kubernetes_client->settings;
pthread_t input_thread;
char endpoint_path[GUAC_KUBERNETES_MAX_ENDPOINT_LENGTH];
/* Verify that the pod name was specified (it's always required) */
if (settings->kubernetes_pod == NULL) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
"The name of the Kubernetes pod is a required parameter.");
goto fail;
}
/* Generate endpoint for attachment URL */
if (guac_kubernetes_endpoint_attach(endpoint_path, sizeof(endpoint_path),
settings->kubernetes_namespace,
settings->kubernetes_pod,
settings->kubernetes_container)) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
"Unable to generate path for Kubernetes API endpoint: "
"Resulting path too long");
goto fail;
}
guac_client_log(client, GUAC_LOG_DEBUG, "The endpoint for attaching to "
"the requested Kubernetes pod is \"%s\".", endpoint_path);
/* Set up screen recording, if requested */
if (settings->recording_path != NULL) {
kubernetes_client->recording = guac_common_recording_create(client,
settings->recording_path,
settings->recording_name,
settings->create_recording_path,
!settings->recording_exclude_output,
!settings->recording_exclude_mouse,
settings->recording_include_keys);
}
/* Create terminal */
kubernetes_client->term = guac_terminal_create(client,
kubernetes_client->clipboard, settings->disable_copy,
settings->max_scrollback, settings->font_name, settings->font_size,
settings->resolution, settings->width, settings->height,
settings->color_scheme, settings->backspace);
/* Fail if terminal init failed */
if (kubernetes_client->term == NULL) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
"Terminal initialization failed");
goto fail;
}
/* Send current values of exposed arguments to owner only */
guac_client_for_owner(client, guac_kubernetes_send_current_argv,
kubernetes_client);
/* Set up typescript, if requested */
if (settings->typescript_path != NULL) {
guac_terminal_create_typescript(kubernetes_client->term,
settings->typescript_path,
settings->typescript_name,
settings->create_typescript_path);
}
/* Init libwebsockets context creation parameters */
struct lws_context_creation_info context_info = {
.port = CONTEXT_PORT_NO_LISTEN, /* We are not a WebSocket server */
.uid = -1,
.gid = -1,
.protocols = guac_kubernetes_lws_protocols,
.user = client
};
/* Init WebSocket connection parameters which do not vary by Guacmaole
* connection parameters or creation of future libwebsockets objects */
struct lws_client_connect_info connection_info = {
.host = settings->hostname,
.address = settings->hostname,
.origin = settings->hostname,
.port = settings->port,
.protocol = GUAC_KUBERNETES_LWS_PROTOCOL,
.userdata = client
};
/* If requested, use an SSL/TLS connection for communication with
* Kubernetes. Note that we disable hostname checks here because we
* do our own validation - libwebsockets does not validate properly if
* IP addresses are used. */
if (settings->use_ssl) {
#ifdef HAVE_LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT
context_info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
#endif
#ifdef HAVE_LCCSCF_USE_SSL
connection_info.ssl_connection = LCCSCF_USE_SSL
| LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK;
#else
connection_info.ssl_connection = 2; /* SSL + no hostname check */
#endif
}
/* Create libwebsockets context */
kubernetes_client->context = lws_create_context(&context_info);
if (!kubernetes_client->context) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
"Initialization of libwebsockets failed");
goto fail;
}
/* Generate path dynamically */
connection_info.context = kubernetes_client->context;
connection_info.path = endpoint_path;
/* Open WebSocket connection to Kubernetes */
kubernetes_client->wsi = lws_client_connect_via_info(&connection_info);
if (kubernetes_client->wsi == NULL) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
"Connection via libwebsockets failed");
goto fail;
}
/* Init outbound message buffer */
pthread_mutex_init(&(kubernetes_client->outbound_message_lock), NULL);
/* Start input thread */
if (pthread_create(&(input_thread), NULL, guac_kubernetes_input_thread, (void*) client)) {
guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Unable to start input thread");
goto fail;
}
/* Force a redraw of the attached display (there will be no content
* otherwise, given the stream nature of attaching to a running
* container) */
guac_kubernetes_force_redraw(client);
/* As long as client is connected, continue polling libwebsockets */
while (client->state == GUAC_CLIENT_RUNNING) {
/* Cease polling libwebsockets if an error condition is signalled */
if (lws_service(kubernetes_client->context,
GUAC_KUBERNETES_SERVICE_INTERVAL) < 0)
break;
}
/* Kill client and Wait for input thread to die */
guac_terminal_stop(kubernetes_client->term);
guac_client_stop(client);
pthread_join(input_thread, NULL);
fail:
/* Kill and free terminal, if allocated */
if (kubernetes_client->term != NULL)
guac_terminal_free(kubernetes_client->term);
/* Clean up recording, if in progress */
if (kubernetes_client->recording != NULL)
guac_common_recording_free(kubernetes_client->recording);
/* Free WebSocket context if successfully allocated */
if (kubernetes_client->context != NULL)
lws_context_destroy(kubernetes_client->context);
guac_client_log(client, GUAC_LOG_INFO, "Kubernetes connection ended.");
return NULL;
}
void guac_kubernetes_resize(guac_client* client, int rows, int columns) {
char buffer[64];
guac_kubernetes_client* kubernetes_client =
(guac_kubernetes_client*) client->data;
/* Send request only if different from last request */
if (kubernetes_client->rows != rows ||
kubernetes_client->columns != columns) {
kubernetes_client->rows = rows;
kubernetes_client->columns = columns;
/* Construct terminal resize message for Kubernetes */
int length = snprintf(buffer, sizeof(buffer),
"{\"Width\":%i,\"Height\":%i}", columns, rows);
/* Schedule message for sending */
guac_kubernetes_send_message(client, GUAC_KUBERNETES_CHANNEL_RESIZE,
buffer, length);
}
}
void guac_kubernetes_force_redraw(guac_client* client) {
guac_kubernetes_client* kubernetes_client =
(guac_kubernetes_client*) client->data;
/* Get current terminal dimensions */
guac_terminal* term = kubernetes_client->term;
int rows = term->term_height;
int columns = term->term_width;
/* Force a redraw by increasing the terminal size by one character in
* each dimension and then resizing it back to normal (the same technique
* used by kubectl */
guac_kubernetes_resize(client, rows + 1, columns + 1);
guac_kubernetes_resize(client, rows, columns);
}

Some files were not shown because too many files have changed in this diff Show More