Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
dcd85b6640
19
.gitignore
vendored
19
.gitignore
vendored
@ -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
|
||||
|
||||
@ -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 \
|
||||
|
||||
60
Makefile.am
60
Makefile.am
@ -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
2
NOTICE
@ -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
89
README-unit-testing.md
Normal 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.
|
||||
|
||||
@ -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.
|
||||
|
||||
172
configure.ac
172
configure.ac
@ -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
5
src/common-ssh/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
|
||||
# Auto-generated test runner and binary
|
||||
_generated_runner.c
|
||||
test_common_ssh
|
||||
|
||||
@ -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 \
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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)) {
|
||||
|
||||
67
src/common-ssh/tests/Makefile.am
Normal file
67
src/common-ssh/tests/Makefile.am
Normal 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
|
||||
|
||||
263
src/common-ssh/tests/sftp/normalize_path.c
Normal file
263
src/common-ssh/tests/sftp/normalize_path.c
Normal 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
5
src/common/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
|
||||
# Auto-generated test runner and binary
|
||||
_generated_runner.c
|
||||
test_common
|
||||
|
||||
@ -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 \
|
||||
|
||||
@ -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));
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
72
src/common/tests/Makefile.am
Normal file
72
src/common/tests/Makefile.am
Normal 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
|
||||
|
||||
@ -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));
|
||||
|
||||
156
src/common/tests/rect/clip_and_split.c
Normal file
156
src/common/tests/rect/clip_and_split.c
Normal 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);
|
||||
|
||||
}
|
||||
|
||||
43
src/common/tests/rect/constrain.c
Normal file
43
src/common/tests/rect/constrain.c
Normal 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);
|
||||
|
||||
}
|
||||
|
||||
71
src/common/tests/rect/expand_to_grid.c
Normal file
71
src/common/tests/rect/expand_to_grid.c
Normal 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);
|
||||
|
||||
}
|
||||
|
||||
42
src/common/tests/rect/extend.c
Normal file
42
src/common/tests/rect/extend.c
Normal 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);
|
||||
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
}
|
||||
|
||||
91
src/common/tests/rect/intersects.c
Normal file
91
src/common/tests/rect/intersects.c
Normal 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);
|
||||
|
||||
}
|
||||
|
||||
33
src/common/tests/string/count_occurrences.c
Normal file
33
src/common/tests/string/count_occurrences.c
Normal 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'));
|
||||
}
|
||||
|
||||
@ -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
35
src/guacd/.gitignore
vendored
@ -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
|
||||
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
42
src/libguac/.gitignore
vendored
42
src/libguac/.gitignore
vendored
@ -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
|
||||
|
||||
|
||||
@ -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@ \
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "error.h"
|
||||
#include "guacamole/error.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
@ -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
|
||||
|
||||
53
src/libguac/guacamole/protocol-constants.h
Normal file
53
src/libguac/guacamole/protocol-constants.h
Normal 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
|
||||
|
||||
@ -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.
|
||||
*
|
||||
|
||||
@ -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.
|
||||
|
||||
161
src/libguac/guacamole/string.h
Normal file
161
src/libguac/guacamole/string.h
Normal 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
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "pool.h"
|
||||
#include "guacamole/pool.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
|
||||
@ -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) {
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -23,7 +23,7 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "audio.h"
|
||||
#include "guacamole/audio.h"
|
||||
|
||||
/**
|
||||
* The number of bytes to send in each audio blob.
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "socket.h"
|
||||
#include "guacamole/socket.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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
107
src/libguac/string.c
Normal 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;
|
||||
|
||||
}
|
||||
|
||||
79
src/libguac/tests/Makefile.am
Normal file
79
src/libguac/tests/Makefile.am
Normal 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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
145
src/libguac/tests/parser/read.c
Normal file
145
src/libguac/tests/parser/read.c
Normal 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);
|
||||
|
||||
}
|
||||
|
||||
@ -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]++;
|
||||
|
||||
}
|
||||
|
||||
@ -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=";
|
||||
136
src/libguac/tests/socket/fd_send_instruction.c
Normal file
136
src/libguac/tests/socket/fd_send_instruction.c
Normal 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);
|
||||
|
||||
}
|
||||
|
||||
149
src/libguac/tests/socket/nested_send_instruction.c
Normal file
149
src/libguac/tests/socket/nested_send_instruction.c
Normal 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);
|
||||
|
||||
}
|
||||
|
||||
153
src/libguac/tests/string/strlcat.c
Normal file
153
src/libguac/tests/string/strlcat.c
Normal 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));
|
||||
|
||||
}
|
||||
|
||||
102
src/libguac/tests/string/strlcpy.c
Normal file
102
src/libguac/tests/string/strlcpy.c
Normal 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');
|
||||
|
||||
}
|
||||
|
||||
148
src/libguac/tests/string/strljoin.c
Normal file
148
src/libguac/tests/string/strljoin.c
Normal 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');
|
||||
|
||||
}
|
||||
|
||||
33
src/libguac/tests/unicode/charsize.c
Normal file
33
src/libguac/tests/unicode/charsize.c
Normal 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'));
|
||||
}
|
||||
|
||||
52
src/libguac/tests/unicode/read.c
Normal file
52
src/libguac/tests/unicode/read.c
Normal 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);
|
||||
|
||||
}
|
||||
|
||||
@ -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"));
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
}
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "timestamp.h"
|
||||
#include "guacamole/timestamp.h"
|
||||
|
||||
#include <sys/time.h>
|
||||
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "unicode.h"
|
||||
#include "guacamole/unicode.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 */
|
||||
|
||||
@ -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) {
|
||||
|
||||
72
src/protocols/kubernetes/Makefile.am
Normal file
72
src/protocols/kubernetes/Makefile.am
Normal 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@
|
||||
|
||||
224
src/protocols/kubernetes/argv.c
Normal file
224
src/protocols/kubernetes/argv.c
Normal 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;
|
||||
|
||||
}
|
||||
|
||||
61
src/protocols/kubernetes/argv.h
Normal file
61
src/protocols/kubernetes/argv.h
Normal 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
|
||||
|
||||
133
src/protocols/kubernetes/client.c
Normal file
133
src/protocols/kubernetes/client.c
Normal 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;
|
||||
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
65
src/protocols/kubernetes/clipboard.c
Normal file
65
src/protocols/kubernetes/clipboard.c
Normal 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;
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
94
src/protocols/kubernetes/input.c
Normal file
94
src/protocols/kubernetes/input.c
Normal 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;
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
143
src/protocols/kubernetes/io.c
Normal file
143
src/protocols/kubernetes/io.c
Normal 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;
|
||||
|
||||
}
|
||||
|
||||
|
||||
144
src/protocols/kubernetes/io.h
Normal file
144
src/protocols/kubernetes/io.h
Normal 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
|
||||
|
||||
413
src/protocols/kubernetes/kubernetes.c
Normal file
413
src/protocols/kubernetes/kubernetes.c
Normal 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
Loading…
Reference in New Issue
Block a user