GUACAMOLE-200: Refactor RDPDR print job object to top-level.

Project: http://git-wip-us.apache.org/repos/asf/incubator-guacamole-server/repo
Commit: 
http://git-wip-us.apache.org/repos/asf/incubator-guacamole-server/commit/1537e475
Tree: 
http://git-wip-us.apache.org/repos/asf/incubator-guacamole-server/tree/1537e475
Diff: 
http://git-wip-us.apache.org/repos/asf/incubator-guacamole-server/diff/1537e475

Branch: refs/heads/master
Commit: 1537e475af475ff4e5388c573f9f83b4ef0a13ba
Parents: 17093a8
Author: Michael Jumper <mjum...@apache.org>
Authored: Mon Feb 13 23:51:33 2017 -0800
Committer: Michael Jumper <mjum...@apache.org>
Committed: Mon Feb 13 23:51:33 2017 -0800

----------------------------------------------------------------------
 src/protocols/rdp/Makefile.am                  |   5 +-
 src/protocols/rdp/guac_rdpdr/rdpdr_print_job.c | 651 --------------------
 src/protocols/rdp/guac_rdpdr/rdpdr_print_job.h | 232 -------
 src/protocols/rdp/guac_rdpdr/rdpdr_printer.c   |  18 +-
 src/protocols/rdp/rdp_print_job.c              | 651 ++++++++++++++++++++
 src/protocols/rdp/rdp_print_job.h              | 232 +++++++
 6 files changed, 895 insertions(+), 894 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-guacamole-server/blob/1537e475/src/protocols/rdp/Makefile.am
----------------------------------------------------------------------
diff --git a/src/protocols/rdp/Makefile.am b/src/protocols/rdp/Makefile.am
index 7daf0e6..c49afea 100644
--- a/src/protocols/rdp/Makefile.am
+++ b/src/protocols/rdp/Makefile.am
@@ -41,6 +41,7 @@ libguac_client_rdp_la_SOURCES = \
     rdp_gdi.c                   \
     rdp_glyph.c                 \
     rdp_keymap.c                \
+    rdp_print_job.c             \
     rdp_pointer.c               \
     rdp_rail.c                  \
     rdp_settings.c              \
@@ -72,9 +73,9 @@ guacdr_sources =                             \
     guac_rdpdr/rdpdr_fs_service.c            \
     guac_rdpdr/rdpdr_messages.c              \
     guac_rdpdr/rdpdr_printer.c               \
-    guac_rdpdr/rdpdr_print_job.c             \
     guac_rdpdr/rdpdr_service.c               \
     rdp_fs.c                                 \
+    rdp_print_job.c                          \
     rdp_stream.c                             \
     unicode.c
 
@@ -90,7 +91,6 @@ noinst_HEADERS =                             \
     guac_rdpdr/rdpdr_fs_service.h            \
     guac_rdpdr/rdpdr_messages.h              \
     guac_rdpdr/rdpdr_printer.h               \
-    guac_rdpdr/rdpdr_print_job.h             \
     guac_rdpdr/rdpdr_service.h               \
     guac_rdpsnd/rdpsnd_messages.h            \
     guac_rdpsnd/rdpsnd_service.h             \
@@ -111,6 +111,7 @@ noinst_HEADERS =                             \
     rdp_glyph.h                              \
     rdp_keymap.h                             \
     rdp_pointer.h                            \
+    rdp_print_job.h                          \
     rdp_rail.h                               \
     rdp_settings.h                           \
     rdp_status.h                             \

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-server/blob/1537e475/src/protocols/rdp/guac_rdpdr/rdpdr_print_job.c
----------------------------------------------------------------------
diff --git a/src/protocols/rdp/guac_rdpdr/rdpdr_print_job.c 
b/src/protocols/rdp/guac_rdpdr/rdpdr_print_job.c
deleted file mode 100644
index c3cb03c..0000000
--- a/src/protocols/rdp/guac_rdpdr/rdpdr_print_job.c
+++ /dev/null
@@ -1,651 +0,0 @@
-/*
- * 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 "rdpdr_print_job.h"
-
-#include <guacamole/client.h>
-#include <guacamole/protocol.h>
-#include <guacamole/socket.h>
-#include <guacamole/stream.h>
-#include <guacamole/user.h>
-
-#include <errno.h>
-#include <pthread.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-
-/**
- * The command to run when filtering postscript to produce PDF. This must be
- * a NULL-terminated array of arguments, where the first argument is the name
- * of the file to run.
- */
-char* const guac_rdpdr_pdf_filter_command[] = {
-    "gs",
-    "-q",
-    "-dNOPAUSE",
-    "-dBATCH",
-    "-dSAFER",
-    "-dPARANOIDSAFER",
-    "-sDEVICE=pdfwrite",
-    "-sOutputFile=-",
-    "-c",
-    ".setpdfwrite",
-    "-sstdout=/dev/null",
-    "-f",
-    "-",
-    NULL
-};
-
-/**
- * Updates the state of the given print job. Any threads currently blocked by a
- * call to guac_rdpdr_print_job_wait_for_ack() will be unblocked.
- *
- * @param job
- *     The print job whose state should be updated.
- *
- * @param state
- *     The new state to assign to the given print job.
- */
-static void guac_rdpdr_print_job_set_state(guac_rdpdr_print_job* job,
-        guac_rdpdr_print_job_state state) {
-
-    pthread_mutex_lock(&(job->state_lock));
-
-    /* Update stream state, signalling modification */
-    job->state = state;
-    pthread_cond_signal(&(job->state_modified));
-
-    pthread_mutex_unlock(&(job->state_lock));
-
-}
-
-/**
- * Suspends execution of the current thread until the state of the given print
- * job is not GUAC_RDPDR_PRINT_JOB_WAITING_FOR_ACK. If the state of the print
- * job is GUAC_RDPDR_PRINT_JOB_ACK_RECEIVED, the state is automatically reset
- * back to GUAC_RDPDR_PRINT_JOB_WAITING_FOR_ACK prior to returning.
- *
- * @param job
- *     The print job to wait for.
- *
- * @return
- *     Zero if the state of the print job is GUAC_RDPDR_PRINT_JOB_CLOSED,
- *     non-zero if the state was GUAC_RDPDR_PRINT_JOB_ACK_RECEIVED and has been
- *     automatically reset to GUAC_RDPDR_PRINT_JOB_WAITING_FOR_ACK.
- */
-static int guac_rdpdr_print_job_wait_for_ack(guac_rdpdr_print_job* job) {
-
-    /* Wait for ack if stream open and not yet received */
-    pthread_mutex_lock(&(job->state_lock));
-    if (job->state == GUAC_RDPDR_PRINT_JOB_WAITING_FOR_ACK)
-        pthread_cond_wait(&job->state_modified, &job->state_lock);
-
-    /* Reset state if ack received */
-    int got_ack = (job->state == GUAC_RDPDR_PRINT_JOB_ACK_RECEIVED);
-    if (got_ack)
-        job->state = GUAC_RDPDR_PRINT_JOB_WAITING_FOR_ACK;
-
-    /* Return whether ack was successfully received */
-    pthread_mutex_unlock(&(job->state_lock));
-    return got_ack;
-
-}
-
-/**
- * Sends a "file" instruction to the given user describing the PDF file that
- * will be sent using the output of the given print job. If the given user no
- * longer exists, the print stream will be automatically terminated.
- *
- * @param user
- *     The user receiving the "file" instruction.
- *
- * @param data
- *     A pointer to the guac_rdpdr_print_job representing the print job being
- *     streamed.
- *
- * @return
- *     Always NULL.
- */
-static void* guac_rdpdr_print_job_begin_stream(guac_user* user, void* data) {
-
-    guac_rdpdr_print_job* job = (guac_rdpdr_print_job*) data;
-    guac_client_log(job->client, GUAC_LOG_DEBUG, "Beginning print stream: %s",
-            job->filename);
-
-    /* Kill job and do nothing if user no longer exists */
-    if (user == NULL) {
-        guac_rdpdr_print_job_kill(job);
-        return NULL;
-    }
-
-    /* Send document as a PDF file stream */
-    guac_protocol_send_file(user->socket, job->stream,
-            "application/pdf", job->filename);
-
-    guac_socket_flush(user->socket);
-    return NULL;
-
-}
-
-/**
- * Sends a "blob" instruction to the given user containing the provided data
- * along the stream associated with the provided print job. If the given user
- * no longer exists, the print stream will be automatically terminated.
- *
- * @param user
- *     The user receiving the "blob" instruction.
- *
- * @param data
- *     A pointer to an guac_rdpdr_print_blob structure containing the data to
- *     be written, the number of bytes being written, and the print job being
- *     streamed.
- *
- * @return
- *     Always NULL.
- */
-static void* guac_rdpdr_print_job_send_blob(guac_user* user, void* data) {
-
-    guac_rdpdr_print_blob* blob = (guac_rdpdr_print_blob*) data;
-    guac_rdpdr_print_job* job = blob->job;
-
-    guac_client_log(job->client, GUAC_LOG_DEBUG, "Sending %i byte(s) "
-            "of filtered output.", blob->length);
-
-    /* Kill job and do nothing if user no longer exists */
-    if (user == NULL) {
-        guac_rdpdr_print_job_kill(job);
-        return NULL;
-    }
-
-    /* Send single blob of print data */
-    guac_protocol_send_blob(user->socket, job->stream,
-            blob->buffer, blob->length);
-
-    guac_socket_flush(user->socket);
-    return NULL;
-
-}
-
-/**
- * Sends an "end" instruction to the given user, closing the stream associated
- * with the given print job. If the given user no longer exists, the print
- * stream will be automatically terminated.
- *
- * @param user
- *     The user receiving the "end" instruction.
- *
- * @param data
- *     A pointer to the guac_rdpdr_print_job representing the print job being
- *     streamed.
- *
- * @return
- *     Always NULL.
- */
-static void* guac_rdpdr_print_job_end_stream(guac_user* user, void* data) {
-
-    guac_rdpdr_print_job* job = (guac_rdpdr_print_job*) data;
-    guac_client_log(job->client, GUAC_LOG_DEBUG, "End of print stream.");
-
-    /* Kill job and do nothing if user no longer exists */
-    if (user == NULL) {
-        guac_rdpdr_print_job_kill(job);
-        return NULL;
-    }
-
-    /* Explicitly close down stream */
-    guac_protocol_send_end(user->socket, job->stream);
-    guac_socket_flush(user->socket);
-
-    /* Clean up our end of the stream */
-    guac_user_free_stream(job->user, job->stream);
-
-    return NULL;
-
-}
-
-/**
- * Handler for "ack" messages received in response to printed data. Additional
- * data will be sent as a result or, if no data remains, the stream will be
- * terminated. It is required that the data pointer of the provided stream be
- * set to the file descriptor from which the printed data should be read.
- *
- * @param user
- *     The user to whom the printed data is being sent.
- *
- * @param stream
- *     The stream along which the printed data is to be sent. The data pointer
- *     of this stream MUST be set to the file descriptor from which the data
- *     being sent is to be read.
- *
- * @param message
- *     An arbitrary, human-readable message describing the success/failure of
- *     the operation being acknowledged (either stream creation or receipt of
- *     a blob).
- *
- * @param status
- *     The status code describing the success/failure of the operation being
- *     acknowledged (either stream creation or receipt of a blob).
- *
- * @return
- *     Always zero.
- */
-static int guac_rdpdr_print_filter_ack_handler(guac_user* user,
-        guac_stream* stream, char* message, guac_protocol_status status) {
-
-    guac_rdpdr_print_job* job = (guac_rdpdr_print_job*) stream->data;
-
-    /* Update state for successful acks */
-    if (status == GUAC_PROTOCOL_STATUS_SUCCESS)
-        guac_rdpdr_print_job_set_state(job, GUAC_RDPDR_PRINT_JOB_ACK_RECEIVED);
-
-    /* Terminate stream if ack signals an error */
-    else {
-
-        /* Note that the stream was aborted by the user */
-        guac_client_log(job->client, GUAC_LOG_INFO, "User explicitly aborted "
-                "print stream.");
-
-        /* Kill job (the results will no longer be received) */
-        guac_rdpdr_print_job_kill(job);
-
-    }
-
-    return 0;
-
-}
-
-/**
- * Forks a new print filtering process which accepts PostScript input and
- * produces PDF output. File descriptors for writing input and reading output
- * will automatically be allocated and must be manually closed when processing
- * is complete.
- *
- * @param client
- *     The guac_client associated with the print job for which this filter
- *     process is being created.
- *
- * @param input_fd
- *     A pointer to an int which should receive the input file descriptor of
- *     the filter process. PostScript input for the filter process should be
- *     written to this file descriptor.
- *
- * @param output_fd
- *     A pointer to an int which should receive the output file descriptor of
- *     the filter process. PDF output from the filter process must be
- *     continuously read from this file descriptor or the pipeline may block.
- *
- * @return
- *     The PID of the filter process, or -1 if the filter process could not be
- *     created. If the filter process could not be created, the values assigned
- *     through input_fd and output_fd are undefined.
- */
-static pid_t guac_rdpdr_create_filter_process(guac_client* client,
-        int* input_fd, int* output_fd) {
-
-    int child_pid;
-    int stdin_pipe[2];
-    int stdout_pipe[2];
-
-    /* Create STDIN pipe */
-    if (pipe(stdin_pipe)) {
-        guac_client_log(client, GUAC_LOG_ERROR, "Unable to create STDIN "
-                "pipe for PDF filter process: %s", strerror(errno));
-        return -1;
-    }
-
-    /* Create STDOUT pipe */
-    if (pipe(stdout_pipe)) {
-        guac_client_log(client, GUAC_LOG_ERROR, "Unable to create STDOUT "
-                "pipe for PDF filter process: %s", strerror(errno));
-        close(stdin_pipe[0]);
-        close(stdin_pipe[1]);
-        return -1;
-    }
-
-    /* Store parent side of stdin/stdout */
-    *input_fd = stdin_pipe[1];
-    *output_fd = stdout_pipe[0];
-
-    /* Fork child process */
-    child_pid = fork();
-
-    /* Log fork errors */
-    if (child_pid == -1) {
-        guac_client_log(client, GUAC_LOG_ERROR, "Unable to fork PDF filter "
-                "process: %s", strerror(errno));
-        close(stdin_pipe[0]);
-        close(stdin_pipe[1]);
-        close(stdout_pipe[0]);
-        close(stdout_pipe[1]);
-        return -1;
-    }
-
-    /* Child process */
-    if (child_pid == 0) {
-
-        /* Close unneeded ends of pipe */
-        close(stdin_pipe[1]);
-        close(stdout_pipe[0]);
-
-        /* Reassign file descriptors as STDIN/STDOUT */
-        dup2(stdin_pipe[0], STDIN_FILENO);
-        dup2(stdout_pipe[1], STDOUT_FILENO);
-
-        /* Run PDF filter */
-        guac_client_log(client, GUAC_LOG_INFO, "Running %s",
-                guac_rdpdr_pdf_filter_command[0]);
-        if (execvp(guac_rdpdr_pdf_filter_command[0],
-                    guac_rdpdr_pdf_filter_command) < 0)
-            guac_client_log(client, GUAC_LOG_ERROR, "Unable to execute PDF "
-                    "filter command: %s", strerror(errno));
-        else
-            guac_client_log(client, GUAC_LOG_ERROR, "Unable to execute PDF "
-                    "filter command, but no error given");
-
-        /* Terminate child process */
-        exit(1);
-
-    }
-
-    /* Log fork success */
-    guac_client_log(client, GUAC_LOG_INFO, "Created PDF filter process "
-            "PID=%i", child_pid);
-
-    /* Close unneeded ends of pipe */
-    close(stdin_pipe[0]);
-    close(stdout_pipe[1]);
-    return child_pid;
-
-}
-
-/**
- * Thread which continuously reads from the output file descriptor associated
- * with the given print job, writing filtered PDF output to the associated
- * Guacamole stream, and terminating only after the print job has completed
- * processing or the associated Guacamole stream has closed.
- *
- * @param data
- *     A pointer to the guac_rdpdr_print_job representing the print job that
- *     should be read.
- *
- * @return
- *     Always NULL.
- */
-static void* guac_rdpdr_print_job_output_thread(void* data) {
-
-    int length;
-    char buffer[6048];
-
-    guac_rdpdr_print_job* job = (guac_rdpdr_print_job*) data;
-    guac_client_log(job->client, GUAC_LOG_DEBUG, "Reading output from filter "
-            "process...");
-
-    /* Read continuously while data remains */
-    while ((length = read(job->output_fd, buffer, sizeof(buffer))) > 0) {
-
-        /* Wait for client to be ready for blob */
-        if (guac_rdpdr_print_job_wait_for_ack(job)) {
-
-            guac_rdpdr_print_blob blob = {
-                .job    = job,
-                .buffer = buffer,
-                .length = length
-            };
-
-            /* Write a single blob of output */
-            guac_client_for_user(job->client, job->user,
-                    guac_rdpdr_print_job_send_blob, &blob);
-
-        }
-
-        /* Abort if stream is closed */
-        else {
-            guac_client_log(job->client, GUAC_LOG_DEBUG, "Print stream "
-                    "explicitly aborted.");
-            break;
-        }
-
-    }
-
-    /* Warn of read errors */
-    if (length < 0)
-        guac_client_log(job->client, GUAC_LOG_ERROR,
-                "Error reading from filter: %s", strerror(errno));
-
-    /* Terminate stream */
-    guac_client_for_user(job->client, job->user,
-            guac_rdpdr_print_job_end_stream, job);
-
-    /* Ensure all associated file descriptors are closed */
-    close(job->input_fd);
-    close(job->output_fd);
-
-    guac_client_log(job->client, GUAC_LOG_DEBUG, "Print job completed.");
-    return NULL;
-
-}
-
-void* guac_rdpdr_print_job_alloc(guac_user* user, void* data) {
-
-    /* Allocate nothing if user does not exist */
-    if (user == NULL)
-        return NULL;
-
-    /* Allocate stream for print job output */
-    guac_stream* stream = guac_user_alloc_stream(user);
-    if (stream == NULL)
-        return NULL;
-
-    /* Bail early if allocation fails */
-    guac_rdpdr_print_job* job = malloc(sizeof(guac_rdpdr_print_job));
-    if (job == NULL)
-        return NULL;
-
-    /* Associate job with stream and dependent data */
-    job->client = user->client;
-    job->user = user;
-    job->stream = stream;
-    job->bytes_received = 0;
-
-    /* Set default filename for job */
-    strcpy(job->filename, GUAC_RDPDR_PRINT_JOB_DEFAULT_FILENAME);
-
-    /* Prepare stream for receipt of acks */
-    stream->ack_handler = guac_rdpdr_print_filter_ack_handler;
-    stream->data = job;
-
-    /* Create print filter process */
-    job->filter_pid = guac_rdpdr_create_filter_process(job->client,
-            &job->input_fd, &job->output_fd);
-
-    /* Abort if print filter process cannot be created */
-    if (job->filter_pid == -1) {
-        guac_user_free_stream(user, stream);
-        free(job);
-        return NULL;
-    }
-
-    /* Init stream state signal and lock */
-    job->state = GUAC_RDPDR_PRINT_JOB_WAITING_FOR_ACK;
-    pthread_cond_init(&job->state_modified, NULL);
-    pthread_mutex_init(&job->state_lock, NULL);
-
-    /* Start output thread */
-    pthread_create(&job->output_thread, NULL,
-            guac_rdpdr_print_job_output_thread, job);
-
-    /* Print job allocated successfully */
-    return job;
-
-}
-
-/**
- * Attempts to parse the given PostScript "%%Title:" header, storing the
- * contents within the filename of the given print job. If the given buffer
- * does not immediately begin with the "%%Title:" header, this function has no
- * effect.
- *
- * @param job
- *     The job whose filename should be set if the "%%Title:" header is
- *     successfully parsed.
- *
- * @param buffer
- *     The buffer to parse as the "%%Title:" header.
- *
- * @param length
- *     The number of bytes within the buffer.
- *
- * @return
- *     Non-zero if the given buffer began with the "%%Title:" header and this
- *     header was successfully parsed, zero otherwise.
- */
-static int guac_rdpdr_print_job_parse_title_header(guac_rdpdr_print_job* job,
-        void* buffer, int length) {
-
-    int i;
-    char* current = buffer;
-    char* filename = job->filename;
-
-    /* Verify that the buffer begins with "%%Title: " */
-    if (strncmp(current, "%%Title: ", 9) != 0)
-        return 0;
-
-    /* Skip past "%%Title: " */
-    current += 9;
-    length -= 9;
-
-    /* Calculate space remaining in filename */
-    int remaining = sizeof(job->filename) - 5 /* ".pdf\0" */;
-
-    /* Do not exceed bounds of provided buffer */
-    if (length < remaining)
-        remaining = length;
-
-    /* Copy as much of title as reasonable */
-    for (i = 0; i < remaining; i++) {
-
-        /* Get character, stop at EOL */
-        char c = *(current++);
-        if (c == '\r' || c == '\n')
-            break;
-
-        /* Copy to filename */
-        *(filename++) = c;
-
-    }
-
-    /* Append extension to filename */
-    strcpy(filename, ".pdf");
-
-    /* Title successfully parsed */
-    return 1;
-
-}
-
-/**
- * Searches through the given buffer for PostScript headers denoting the title
- * of the document, assigning the filename of the given print job using the
- * discovered title. If no title can be found within
- * GUAC_RDPDR_PRINT_JOB_TITLE_SEARCH_LENGTH bytes, this function has no effect.
- *
- * @param job
- *     The job whose filename should be set if the document title can be found
- *     within the given buffer.
- *
- * @param buffer
- *     The buffer to search for the document title.
- *
- * @param length
- *     The number of bytes within the buffer.
- */
-static void guac_rdpdr_print_job_read_filename(guac_rdpdr_print_job* job,
-        void* buffer, int length) {
-
-    char* current = buffer;
-    int i;
-
-    /* Restrict search area */
-    if (length > GUAC_RDPDR_PRINT_JOB_TITLE_SEARCH_LENGTH)
-        length = GUAC_RDPDR_PRINT_JOB_TITLE_SEARCH_LENGTH;
-
-    /* Search for document title within buffer */
-    for (i = 0; i < length; i++) {
-
-        /* If document title has been found, we're done */
-        if (guac_rdpdr_print_job_parse_title_header(job, current, length))
-            break;
-
-        /* Advance to next character */
-        length--;
-        current++;
-
-    }
-
-}
-
-int guac_rdpdr_print_job_write(guac_rdpdr_print_job* job,
-        void* buffer, int length) {
-
-    /* Create print job, if not yet created */
-    if (job->bytes_received == 0) {
-
-        /* Attempt to read document title from first buffer of data */
-        guac_rdpdr_print_job_read_filename(job, buffer, length);
-
-        /* Begin print stream */
-        guac_client_for_user(job->client, job->user,
-                guac_rdpdr_print_job_begin_stream, job);
-
-    }
-
-    /* Update counter of bytes received */
-    job->bytes_received += length;
-
-    /* Write data to filter process */
-    return write(job->input_fd, buffer, length);
-
-}
-
-void guac_rdpdr_print_job_free(guac_rdpdr_print_job* job) {
-
-    /* No more input will be provided */
-    close(job->input_fd);
-
-    /* Wait for job to terminate */
-    pthread_join(job->output_thread, NULL);
-
-    /* Free base structure */
-    free(job);
-
-}
-
-void guac_rdpdr_print_job_kill(guac_rdpdr_print_job* job) {
-
-    /* Stop all handling of I/O */
-    close(job->input_fd);
-    close(job->output_fd);
-
-    /* Mark stream as closed */
-    guac_rdpdr_print_job_set_state(job, GUAC_RDPDR_PRINT_JOB_CLOSED);
-
-}
-

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-server/blob/1537e475/src/protocols/rdp/guac_rdpdr/rdpdr_print_job.h
----------------------------------------------------------------------
diff --git a/src/protocols/rdp/guac_rdpdr/rdpdr_print_job.h 
b/src/protocols/rdp/guac_rdpdr/rdpdr_print_job.h
deleted file mode 100644
index 97e3d62..0000000
--- a/src/protocols/rdp/guac_rdpdr/rdpdr_print_job.h
+++ /dev/null
@@ -1,232 +0,0 @@
-/*
- * 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_RDPDR_PRINT_JOB_H
-#define GUAC_RDPDR_PRINT_JOB_H
-
-#include "config.h"
-
-#include <guacamole/client.h>
-#include <guacamole/stream.h>
-#include <guacamole/user.h>
-
-#include <pthread.h>
-#include <unistd.h>
-
-/**
- * The maximum number of bytes in the filename of an RDPDR print job sent as a
- * file over the Guacamole protocol, including NULL terminator.
- */
-#define GUAC_RDPDR_PRINT_JOB_FILENAME_MAX_LENGTH 1024
-
-/**
- * The default filename to use for the PDF output of an RDPDR print job if no
- * document title can be found within the printed data.
- */
-#define GUAC_RDPDR_PRINT_JOB_DEFAULT_FILENAME "guacamole-print.pdf"
-
-/**
- * The maximum number of bytes to search through at the beginning of a
- * PostScript document when locating the document title.
- */
-#define GUAC_RDPDR_PRINT_JOB_TITLE_SEARCH_LENGTH 2048
-
-/**
- * The current state of an RDPDR print job.
- */
-typedef enum guac_rdpdr_print_job_state {
-
-    /**
-     * The print stream has been opened with the Guacamole client, but the
-     * client has not yet confirmed that it is ready to receive data.
-     */
-    GUAC_RDPDR_PRINT_JOB_WAITING_FOR_ACK,
-
-    /**
-     * The print stream has been opened with the Guacamole client, and the
-     * client has responded with an "ack", confirming that it is ready to
-     * receive data (or that data has been received and it is ready to receive
-     * more).
-     */
-    GUAC_RDPDR_PRINT_JOB_ACK_RECEIVED,
-
-    /**
-     * The print stream has been closed or the printer is terminating, and no
-     * further data should be sent to the client.
-     */
-    GUAC_RDPDR_PRINT_JOB_CLOSED
-
-} guac_rdpdr_print_job_state;
-
-/**
- * Data specific to an instance of the printer device.
- */
-typedef struct guac_rdpdr_print_job {
-
-    guac_client* client;
-
-    /**
-     * The user receiving the output from the print job.
-     */
-    guac_user* user;
-
-    /**
-     * The stream along which the print job output should be sent.
-     */
-    guac_stream* stream;
-
-    /**
-     * The PID of the print filter process converting PostScript data into PDF.
-     */
-    pid_t filter_pid;
-
-    /**
-     * The filename that should be used when the converted PDF output is
-     * streamed to the Guacamole user. This value will be automatically
-     * determined based on the contents of the printed document.
-     */
-    char filename[GUAC_RDPDR_PRINT_JOB_FILENAME_MAX_LENGTH];
-
-    /**
-     * File descriptor that should be written to when sending documents to the
-     * printer.
-     */
-    int input_fd;
-
-    /**
-     * File descriptor that should be read from when receiving output from the
-     * printer.
-     */
-    int output_fd;
-
-    /**
-     * The current state of the print stream, dependent on whether the client
-     * has acknowledged creation of the stream, whether the client has
-     * acknowledged receipt of data along the steam, and whether the print
-     * stream itself has closed.
-     */
-    guac_rdpdr_print_job_state state;
-
-    /**
-     * Lock which is acquired prior to modifying the state property or waiting
-     * on the state_modified conditional.
-     */
-    pthread_mutex_t state_lock;
-
-    /**
-     * Conditional which signals modification to the state property of this
-     * structure.
-     */
-    pthread_cond_t state_modified;
-
-    /**
-     * Thread which transfers data from the printer to the Guacamole client.
-     */
-    pthread_t output_thread;
-
-    /**
-     * The number of bytes received in the current print job.
-     */
-    int bytes_received;
-
-} guac_rdpdr_print_job;
-
-/**
- * A blob of print data being sent to the Guacamole user.
- */
-typedef struct guac_rdpdr_print_blob {
-
-    /**
-     * The print job which generated the data being sent.
-     */
-    guac_rdpdr_print_job* job;
-
-    /**
-     * The data being sent.
-     */
-    void* buffer;
-
-    /**
-     * The number of bytes of data being sent.
-     */
-    int length;
-
-} guac_rdpdr_print_blob;
-
-/**
- * Allocates a new print job for the given user. It is expected that this
- * function will be invoked via a call to guac_client_for_user() or
- * guac_client_for_owner().
- *
- * @param user
- *     The user that should receive the output from the print job.
- *
- * @param data
- *     An arbitrary data parameter required by guac_client_for_user() and
- *     guac_client_for_owner() but ignored by this function. This should
- *     always be NULL.
- *
- * @return
- *     A pointer to a newly-allocated guac_rdpdr_print_job, or NULL if the
- *     print job could not be created.
- */
-void* guac_rdpdr_print_job_alloc(guac_user* user, void* data);
-
-/**
- * Writes PostScript print data to the given active print job. The print job
- * will automatically convert this data to PDF, streaming the result to the
- * Guacamole user associated with the print job. This function may block if
- * the print job is not yet ready for more data.
- *
- * @param buffer
- *     The PostScript print data to write to the given print job.
- *
- * @param length
- *     The number of bytes of PostScript print data to write.
- *
- * @return
- *     The number of bytes written, or -1 if an error occurs which prevents
- *     further writes.
- */
-int guac_rdpdr_print_job_write(guac_rdpdr_print_job* job,
-        void* buffer, int length);
-
-/**
- * Frees the memory associated with the given print job, closing all underlying
- * file descriptors, and ending the file transfer to the associated Guacamole
- * user. This function may block if the print filter process has not yet
- * finished processing the received data.
- *
- * @param job
- *     The print job to free.
- */
-void guac_rdpdr_print_job_free(guac_rdpdr_print_job* job);
-
-/**
- * Forcibly kills the given print job, stopping all associated processing and
- * streaming. The memory associated with the print job will still need to be
- * reclaimed via guac_rdpdr_print_job_free().
- *
- * @param job
- *     The print job to kill.
- */
-void guac_rdpdr_print_job_kill(guac_rdpdr_print_job* job);
-
-#endif
-

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-server/blob/1537e475/src/protocols/rdp/guac_rdpdr/rdpdr_printer.c
----------------------------------------------------------------------
diff --git a/src/protocols/rdp/guac_rdpdr/rdpdr_printer.c 
b/src/protocols/rdp/guac_rdpdr/rdpdr_printer.c
index 0545f89..812eaf0 100644
--- a/src/protocols/rdp/guac_rdpdr/rdpdr_printer.c
+++ b/src/protocols/rdp/guac_rdpdr/rdpdr_printer.c
@@ -21,8 +21,8 @@
 
 #include "rdpdr_messages.h"
 #include "rdpdr_printer.h"
-#include "rdpdr_print_job.h"
 #include "rdpdr_service.h"
+#include "rdp_print_job.h"
 #include "rdp_status.h"
 
 #include <freerdp/utils/svc_plugin.h>
@@ -53,7 +53,7 @@ void guac_rdpdr_process_print_job_create(guac_rdpdr_device* 
device,
 
     /* Create print job */
     device->data = guac_client_for_owner(device->rdpdr->client,
-            guac_rdpdr_print_job_alloc, NULL);
+            guac_rdp_print_job_alloc, NULL);
 
     /* Respond with success */
     wStream* output_stream = guac_rdpdr_new_io_completion(device,
@@ -67,7 +67,7 @@ void guac_rdpdr_process_print_job_create(guac_rdpdr_device* 
device,
 void guac_rdpdr_process_print_job_write(guac_rdpdr_device* device,
         wStream* input_stream, int completion_id) {
 
-    guac_rdpdr_print_job* job = (guac_rdpdr_print_job*) device->data;
+    guac_rdp_print_job* job = (guac_rdp_print_job*) device->data;
 
     unsigned char* buffer;
     int length;
@@ -80,7 +80,7 @@ void guac_rdpdr_process_print_job_write(guac_rdpdr_device* 
device,
     buffer = Stream_Pointer(input_stream);
 
     /* Write data only if job exists, translating status for RDP */
-    if (job != NULL && (length = guac_rdpdr_print_job_write(job,
+    if (job != NULL && (length = guac_rdp_print_job_write(job,
                     buffer, length)) >= 0) {
         status = STATUS_SUCCESS;
     }
@@ -105,9 +105,9 @@ void guac_rdpdr_process_print_job_close(guac_rdpdr_device* 
device,
         wStream* input_stream, int completion_id) {
 
     /* End print job */
-    guac_rdpdr_print_job* job = (guac_rdpdr_print_job*) device->data;
+    guac_rdp_print_job* job = (guac_rdp_print_job*) device->data;
     if (job != NULL) {
-        guac_rdpdr_print_job_free(job);
+        guac_rdp_print_job_free(job);
         device->data = NULL;
     }
 
@@ -180,10 +180,10 @@ static void 
guac_rdpdr_device_printer_iorequest_handler(guac_rdpdr_device* devic
 static void guac_rdpdr_device_printer_free_handler(guac_rdpdr_device* device) {
 
     /* Terminate and free print job if open */
-    guac_rdpdr_print_job* job = (guac_rdpdr_print_job*) device->data;
+    guac_rdp_print_job* job = (guac_rdp_print_job*) device->data;
     if (job != NULL) {
-        guac_rdpdr_print_job_kill(job);
-        guac_rdpdr_print_job_free(job);
+        guac_rdp_print_job_kill(job);
+        guac_rdp_print_job_free(job);
     }
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-server/blob/1537e475/src/protocols/rdp/rdp_print_job.c
----------------------------------------------------------------------
diff --git a/src/protocols/rdp/rdp_print_job.c 
b/src/protocols/rdp/rdp_print_job.c
new file mode 100644
index 0000000..a3b51bc
--- /dev/null
+++ b/src/protocols/rdp/rdp_print_job.c
@@ -0,0 +1,651 @@
+/*
+ * 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 "rdp_print_job.h"
+
+#include <guacamole/client.h>
+#include <guacamole/protocol.h>
+#include <guacamole/socket.h>
+#include <guacamole/stream.h>
+#include <guacamole/user.h>
+
+#include <errno.h>
+#include <pthread.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/**
+ * The command to run when filtering postscript to produce PDF. This must be
+ * a NULL-terminated array of arguments, where the first argument is the name
+ * of the file to run.
+ */
+char* const guac_rdp_pdf_filter_command[] = {
+    "gs",
+    "-q",
+    "-dNOPAUSE",
+    "-dBATCH",
+    "-dSAFER",
+    "-dPARANOIDSAFER",
+    "-sDEVICE=pdfwrite",
+    "-sOutputFile=-",
+    "-c",
+    ".setpdfwrite",
+    "-sstdout=/dev/null",
+    "-f",
+    "-",
+    NULL
+};
+
+/**
+ * Updates the state of the given print job. Any threads currently blocked by a
+ * call to guac_rdp_print_job_wait_for_ack() will be unblocked.
+ *
+ * @param job
+ *     The print job whose state should be updated.
+ *
+ * @param state
+ *     The new state to assign to the given print job.
+ */
+static void guac_rdp_print_job_set_state(guac_rdp_print_job* job,
+        guac_rdp_print_job_state state) {
+
+    pthread_mutex_lock(&(job->state_lock));
+
+    /* Update stream state, signalling modification */
+    job->state = state;
+    pthread_cond_signal(&(job->state_modified));
+
+    pthread_mutex_unlock(&(job->state_lock));
+
+}
+
+/**
+ * Suspends execution of the current thread until the state of the given print
+ * job is not GUAC_RDP_PRINT_JOB_WAITING_FOR_ACK. If the state of the print
+ * job is GUAC_RDP_PRINT_JOB_ACK_RECEIVED, the state is automatically reset
+ * back to GUAC_RDP_PRINT_JOB_WAITING_FOR_ACK prior to returning.
+ *
+ * @param job
+ *     The print job to wait for.
+ *
+ * @return
+ *     Zero if the state of the print job is GUAC_RDP_PRINT_JOB_CLOSED,
+ *     non-zero if the state was GUAC_RDP_PRINT_JOB_ACK_RECEIVED and has been
+ *     automatically reset to GUAC_RDP_PRINT_JOB_WAITING_FOR_ACK.
+ */
+static int guac_rdp_print_job_wait_for_ack(guac_rdp_print_job* job) {
+
+    /* Wait for ack if stream open and not yet received */
+    pthread_mutex_lock(&(job->state_lock));
+    if (job->state == GUAC_RDP_PRINT_JOB_WAITING_FOR_ACK)
+        pthread_cond_wait(&job->state_modified, &job->state_lock);
+
+    /* Reset state if ack received */
+    int got_ack = (job->state == GUAC_RDP_PRINT_JOB_ACK_RECEIVED);
+    if (got_ack)
+        job->state = GUAC_RDP_PRINT_JOB_WAITING_FOR_ACK;
+
+    /* Return whether ack was successfully received */
+    pthread_mutex_unlock(&(job->state_lock));
+    return got_ack;
+
+}
+
+/**
+ * Sends a "file" instruction to the given user describing the PDF file that
+ * will be sent using the output of the given print job. If the given user no
+ * longer exists, the print stream will be automatically terminated.
+ *
+ * @param user
+ *     The user receiving the "file" instruction.
+ *
+ * @param data
+ *     A pointer to the guac_rdp_print_job representing the print job being
+ *     streamed.
+ *
+ * @return
+ *     Always NULL.
+ */
+static void* guac_rdp_print_job_begin_stream(guac_user* user, void* data) {
+
+    guac_rdp_print_job* job = (guac_rdp_print_job*) data;
+    guac_client_log(job->client, GUAC_LOG_DEBUG, "Beginning print stream: %s",
+            job->filename);
+
+    /* Kill job and do nothing if user no longer exists */
+    if (user == NULL) {
+        guac_rdp_print_job_kill(job);
+        return NULL;
+    }
+
+    /* Send document as a PDF file stream */
+    guac_protocol_send_file(user->socket, job->stream,
+            "application/pdf", job->filename);
+
+    guac_socket_flush(user->socket);
+    return NULL;
+
+}
+
+/**
+ * Sends a "blob" instruction to the given user containing the provided data
+ * along the stream associated with the provided print job. If the given user
+ * no longer exists, the print stream will be automatically terminated.
+ *
+ * @param user
+ *     The user receiving the "blob" instruction.
+ *
+ * @param data
+ *     A pointer to an guac_rdp_print_blob structure containing the data to
+ *     be written, the number of bytes being written, and the print job being
+ *     streamed.
+ *
+ * @return
+ *     Always NULL.
+ */
+static void* guac_rdp_print_job_send_blob(guac_user* user, void* data) {
+
+    guac_rdp_print_blob* blob = (guac_rdp_print_blob*) data;
+    guac_rdp_print_job* job = blob->job;
+
+    guac_client_log(job->client, GUAC_LOG_DEBUG, "Sending %i byte(s) "
+            "of filtered output.", blob->length);
+
+    /* Kill job and do nothing if user no longer exists */
+    if (user == NULL) {
+        guac_rdp_print_job_kill(job);
+        return NULL;
+    }
+
+    /* Send single blob of print data */
+    guac_protocol_send_blob(user->socket, job->stream,
+            blob->buffer, blob->length);
+
+    guac_socket_flush(user->socket);
+    return NULL;
+
+}
+
+/**
+ * Sends an "end" instruction to the given user, closing the stream associated
+ * with the given print job. If the given user no longer exists, the print
+ * stream will be automatically terminated.
+ *
+ * @param user
+ *     The user receiving the "end" instruction.
+ *
+ * @param data
+ *     A pointer to the guac_rdp_print_job representing the print job being
+ *     streamed.
+ *
+ * @return
+ *     Always NULL.
+ */
+static void* guac_rdp_print_job_end_stream(guac_user* user, void* data) {
+
+    guac_rdp_print_job* job = (guac_rdp_print_job*) data;
+    guac_client_log(job->client, GUAC_LOG_DEBUG, "End of print stream.");
+
+    /* Kill job and do nothing if user no longer exists */
+    if (user == NULL) {
+        guac_rdp_print_job_kill(job);
+        return NULL;
+    }
+
+    /* Explicitly close down stream */
+    guac_protocol_send_end(user->socket, job->stream);
+    guac_socket_flush(user->socket);
+
+    /* Clean up our end of the stream */
+    guac_user_free_stream(job->user, job->stream);
+
+    return NULL;
+
+}
+
+/**
+ * Handler for "ack" messages received in response to printed data. Additional
+ * data will be sent as a result or, if no data remains, the stream will be
+ * terminated. It is required that the data pointer of the provided stream be
+ * set to the file descriptor from which the printed data should be read.
+ *
+ * @param user
+ *     The user to whom the printed data is being sent.
+ *
+ * @param stream
+ *     The stream along which the printed data is to be sent. The data pointer
+ *     of this stream MUST be set to the file descriptor from which the data
+ *     being sent is to be read.
+ *
+ * @param message
+ *     An arbitrary, human-readable message describing the success/failure of
+ *     the operation being acknowledged (either stream creation or receipt of
+ *     a blob).
+ *
+ * @param status
+ *     The status code describing the success/failure of the operation being
+ *     acknowledged (either stream creation or receipt of a blob).
+ *
+ * @return
+ *     Always zero.
+ */
+static int guac_rdp_print_filter_ack_handler(guac_user* user,
+        guac_stream* stream, char* message, guac_protocol_status status) {
+
+    guac_rdp_print_job* job = (guac_rdp_print_job*) stream->data;
+
+    /* Update state for successful acks */
+    if (status == GUAC_PROTOCOL_STATUS_SUCCESS)
+        guac_rdp_print_job_set_state(job, GUAC_RDP_PRINT_JOB_ACK_RECEIVED);
+
+    /* Terminate stream if ack signals an error */
+    else {
+
+        /* Note that the stream was aborted by the user */
+        guac_client_log(job->client, GUAC_LOG_INFO, "User explicitly aborted "
+                "print stream.");
+
+        /* Kill job (the results will no longer be received) */
+        guac_rdp_print_job_kill(job);
+
+    }
+
+    return 0;
+
+}
+
+/**
+ * Forks a new print filtering process which accepts PostScript input and
+ * produces PDF output. File descriptors for writing input and reading output
+ * will automatically be allocated and must be manually closed when processing
+ * is complete.
+ *
+ * @param client
+ *     The guac_client associated with the print job for which this filter
+ *     process is being created.
+ *
+ * @param input_fd
+ *     A pointer to an int which should receive the input file descriptor of
+ *     the filter process. PostScript input for the filter process should be
+ *     written to this file descriptor.
+ *
+ * @param output_fd
+ *     A pointer to an int which should receive the output file descriptor of
+ *     the filter process. PDF output from the filter process must be
+ *     continuously read from this file descriptor or the pipeline may block.
+ *
+ * @return
+ *     The PID of the filter process, or -1 if the filter process could not be
+ *     created. If the filter process could not be created, the values assigned
+ *     through input_fd and output_fd are undefined.
+ */
+static pid_t guac_rdp_create_filter_process(guac_client* client,
+        int* input_fd, int* output_fd) {
+
+    int child_pid;
+    int stdin_pipe[2];
+    int stdout_pipe[2];
+
+    /* Create STDIN pipe */
+    if (pipe(stdin_pipe)) {
+        guac_client_log(client, GUAC_LOG_ERROR, "Unable to create STDIN "
+                "pipe for PDF filter process: %s", strerror(errno));
+        return -1;
+    }
+
+    /* Create STDOUT pipe */
+    if (pipe(stdout_pipe)) {
+        guac_client_log(client, GUAC_LOG_ERROR, "Unable to create STDOUT "
+                "pipe for PDF filter process: %s", strerror(errno));
+        close(stdin_pipe[0]);
+        close(stdin_pipe[1]);
+        return -1;
+    }
+
+    /* Store parent side of stdin/stdout */
+    *input_fd = stdin_pipe[1];
+    *output_fd = stdout_pipe[0];
+
+    /* Fork child process */
+    child_pid = fork();
+
+    /* Log fork errors */
+    if (child_pid == -1) {
+        guac_client_log(client, GUAC_LOG_ERROR, "Unable to fork PDF filter "
+                "process: %s", strerror(errno));
+        close(stdin_pipe[0]);
+        close(stdin_pipe[1]);
+        close(stdout_pipe[0]);
+        close(stdout_pipe[1]);
+        return -1;
+    }
+
+    /* Child process */
+    if (child_pid == 0) {
+
+        /* Close unneeded ends of pipe */
+        close(stdin_pipe[1]);
+        close(stdout_pipe[0]);
+
+        /* Reassign file descriptors as STDIN/STDOUT */
+        dup2(stdin_pipe[0], STDIN_FILENO);
+        dup2(stdout_pipe[1], STDOUT_FILENO);
+
+        /* Run PDF filter */
+        guac_client_log(client, GUAC_LOG_INFO, "Running %s",
+                guac_rdp_pdf_filter_command[0]);
+        if (execvp(guac_rdp_pdf_filter_command[0],
+                    guac_rdp_pdf_filter_command) < 0)
+            guac_client_log(client, GUAC_LOG_ERROR, "Unable to execute PDF "
+                    "filter command: %s", strerror(errno));
+        else
+            guac_client_log(client, GUAC_LOG_ERROR, "Unable to execute PDF "
+                    "filter command, but no error given");
+
+        /* Terminate child process */
+        exit(1);
+
+    }
+
+    /* Log fork success */
+    guac_client_log(client, GUAC_LOG_INFO, "Created PDF filter process "
+            "PID=%i", child_pid);
+
+    /* Close unneeded ends of pipe */
+    close(stdin_pipe[0]);
+    close(stdout_pipe[1]);
+    return child_pid;
+
+}
+
+/**
+ * Thread which continuously reads from the output file descriptor associated
+ * with the given print job, writing filtered PDF output to the associated
+ * Guacamole stream, and terminating only after the print job has completed
+ * processing or the associated Guacamole stream has closed.
+ *
+ * @param data
+ *     A pointer to the guac_rdp_print_job representing the print job that
+ *     should be read.
+ *
+ * @return
+ *     Always NULL.
+ */
+static void* guac_rdp_print_job_output_thread(void* data) {
+
+    int length;
+    char buffer[6048];
+
+    guac_rdp_print_job* job = (guac_rdp_print_job*) data;
+    guac_client_log(job->client, GUAC_LOG_DEBUG, "Reading output from filter "
+            "process...");
+
+    /* Read continuously while data remains */
+    while ((length = read(job->output_fd, buffer, sizeof(buffer))) > 0) {
+
+        /* Wait for client to be ready for blob */
+        if (guac_rdp_print_job_wait_for_ack(job)) {
+
+            guac_rdp_print_blob blob = {
+                .job    = job,
+                .buffer = buffer,
+                .length = length
+            };
+
+            /* Write a single blob of output */
+            guac_client_for_user(job->client, job->user,
+                    guac_rdp_print_job_send_blob, &blob);
+
+        }
+
+        /* Abort if stream is closed */
+        else {
+            guac_client_log(job->client, GUAC_LOG_DEBUG, "Print stream "
+                    "explicitly aborted.");
+            break;
+        }
+
+    }
+
+    /* Warn of read errors */
+    if (length < 0)
+        guac_client_log(job->client, GUAC_LOG_ERROR,
+                "Error reading from filter: %s", strerror(errno));
+
+    /* Terminate stream */
+    guac_client_for_user(job->client, job->user,
+            guac_rdp_print_job_end_stream, job);
+
+    /* Ensure all associated file descriptors are closed */
+    close(job->input_fd);
+    close(job->output_fd);
+
+    guac_client_log(job->client, GUAC_LOG_DEBUG, "Print job completed.");
+    return NULL;
+
+}
+
+void* guac_rdp_print_job_alloc(guac_user* user, void* data) {
+
+    /* Allocate nothing if user does not exist */
+    if (user == NULL)
+        return NULL;
+
+    /* Allocate stream for print job output */
+    guac_stream* stream = guac_user_alloc_stream(user);
+    if (stream == NULL)
+        return NULL;
+
+    /* Bail early if allocation fails */
+    guac_rdp_print_job* job = malloc(sizeof(guac_rdp_print_job));
+    if (job == NULL)
+        return NULL;
+
+    /* Associate job with stream and dependent data */
+    job->client = user->client;
+    job->user = user;
+    job->stream = stream;
+    job->bytes_received = 0;
+
+    /* Set default filename for job */
+    strcpy(job->filename, GUAC_RDP_PRINT_JOB_DEFAULT_FILENAME);
+
+    /* Prepare stream for receipt of acks */
+    stream->ack_handler = guac_rdp_print_filter_ack_handler;
+    stream->data = job;
+
+    /* Create print filter process */
+    job->filter_pid = guac_rdp_create_filter_process(job->client,
+            &job->input_fd, &job->output_fd);
+
+    /* Abort if print filter process cannot be created */
+    if (job->filter_pid == -1) {
+        guac_user_free_stream(user, stream);
+        free(job);
+        return NULL;
+    }
+
+    /* Init stream state signal and lock */
+    job->state = GUAC_RDP_PRINT_JOB_WAITING_FOR_ACK;
+    pthread_cond_init(&job->state_modified, NULL);
+    pthread_mutex_init(&job->state_lock, NULL);
+
+    /* Start output thread */
+    pthread_create(&job->output_thread, NULL,
+            guac_rdp_print_job_output_thread, job);
+
+    /* Print job allocated successfully */
+    return job;
+
+}
+
+/**
+ * Attempts to parse the given PostScript "%%Title:" header, storing the
+ * contents within the filename of the given print job. If the given buffer
+ * does not immediately begin with the "%%Title:" header, this function has no
+ * effect.
+ *
+ * @param job
+ *     The job whose filename should be set if the "%%Title:" header is
+ *     successfully parsed.
+ *
+ * @param buffer
+ *     The buffer to parse as the "%%Title:" header.
+ *
+ * @param length
+ *     The number of bytes within the buffer.
+ *
+ * @return
+ *     Non-zero if the given buffer began with the "%%Title:" header and this
+ *     header was successfully parsed, zero otherwise.
+ */
+static int guac_rdp_print_job_parse_title_header(guac_rdp_print_job* job,
+        void* buffer, int length) {
+
+    int i;
+    char* current = buffer;
+    char* filename = job->filename;
+
+    /* Verify that the buffer begins with "%%Title: " */
+    if (strncmp(current, "%%Title: ", 9) != 0)
+        return 0;
+
+    /* Skip past "%%Title: " */
+    current += 9;
+    length -= 9;
+
+    /* Calculate space remaining in filename */
+    int remaining = sizeof(job->filename) - 5 /* ".pdf\0" */;
+
+    /* Do not exceed bounds of provided buffer */
+    if (length < remaining)
+        remaining = length;
+
+    /* Copy as much of title as reasonable */
+    for (i = 0; i < remaining; i++) {
+
+        /* Get character, stop at EOL */
+        char c = *(current++);
+        if (c == '\r' || c == '\n')
+            break;
+
+        /* Copy to filename */
+        *(filename++) = c;
+
+    }
+
+    /* Append extension to filename */
+    strcpy(filename, ".pdf");
+
+    /* Title successfully parsed */
+    return 1;
+
+}
+
+/**
+ * Searches through the given buffer for PostScript headers denoting the title
+ * of the document, assigning the filename of the given print job using the
+ * discovered title. If no title can be found within
+ * GUAC_RDP_PRINT_JOB_TITLE_SEARCH_LENGTH bytes, this function has no effect.
+ *
+ * @param job
+ *     The job whose filename should be set if the document title can be found
+ *     within the given buffer.
+ *
+ * @param buffer
+ *     The buffer to search for the document title.
+ *
+ * @param length
+ *     The number of bytes within the buffer.
+ */
+static void guac_rdp_print_job_read_filename(guac_rdp_print_job* job,
+        void* buffer, int length) {
+
+    char* current = buffer;
+    int i;
+
+    /* Restrict search area */
+    if (length > GUAC_RDP_PRINT_JOB_TITLE_SEARCH_LENGTH)
+        length = GUAC_RDP_PRINT_JOB_TITLE_SEARCH_LENGTH;
+
+    /* Search for document title within buffer */
+    for (i = 0; i < length; i++) {
+
+        /* If document title has been found, we're done */
+        if (guac_rdp_print_job_parse_title_header(job, current, length))
+            break;
+
+        /* Advance to next character */
+        length--;
+        current++;
+
+    }
+
+}
+
+int guac_rdp_print_job_write(guac_rdp_print_job* job,
+        void* buffer, int length) {
+
+    /* Create print job, if not yet created */
+    if (job->bytes_received == 0) {
+
+        /* Attempt to read document title from first buffer of data */
+        guac_rdp_print_job_read_filename(job, buffer, length);
+
+        /* Begin print stream */
+        guac_client_for_user(job->client, job->user,
+                guac_rdp_print_job_begin_stream, job);
+
+    }
+
+    /* Update counter of bytes received */
+    job->bytes_received += length;
+
+    /* Write data to filter process */
+    return write(job->input_fd, buffer, length);
+
+}
+
+void guac_rdp_print_job_free(guac_rdp_print_job* job) {
+
+    /* No more input will be provided */
+    close(job->input_fd);
+
+    /* Wait for job to terminate */
+    pthread_join(job->output_thread, NULL);
+
+    /* Free base structure */
+    free(job);
+
+}
+
+void guac_rdp_print_job_kill(guac_rdp_print_job* job) {
+
+    /* Stop all handling of I/O */
+    close(job->input_fd);
+    close(job->output_fd);
+
+    /* Mark stream as closed */
+    guac_rdp_print_job_set_state(job, GUAC_RDP_PRINT_JOB_CLOSED);
+
+}
+

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-server/blob/1537e475/src/protocols/rdp/rdp_print_job.h
----------------------------------------------------------------------
diff --git a/src/protocols/rdp/rdp_print_job.h 
b/src/protocols/rdp/rdp_print_job.h
new file mode 100644
index 0000000..aa879d9
--- /dev/null
+++ b/src/protocols/rdp/rdp_print_job.h
@@ -0,0 +1,232 @@
+/*
+ * 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_RDP_PRINT_JOB_H
+#define GUAC_RDP_PRINT_JOB_H
+
+#include "config.h"
+
+#include <guacamole/client.h>
+#include <guacamole/stream.h>
+#include <guacamole/user.h>
+
+#include <pthread.h>
+#include <unistd.h>
+
+/**
+ * The maximum number of bytes in the filename of an RDP print job sent as a
+ * file over the Guacamole protocol, including NULL terminator.
+ */
+#define GUAC_RDP_PRINT_JOB_FILENAME_MAX_LENGTH 1024
+
+/**
+ * The default filename to use for the PDF output of an RDP print job if no
+ * document title can be found within the printed data.
+ */
+#define GUAC_RDP_PRINT_JOB_DEFAULT_FILENAME "guacamole-print.pdf"
+
+/**
+ * The maximum number of bytes to search through at the beginning of a
+ * PostScript document when locating the document title.
+ */
+#define GUAC_RDP_PRINT_JOB_TITLE_SEARCH_LENGTH 2048
+
+/**
+ * The current state of an RDP print job.
+ */
+typedef enum guac_rdp_print_job_state {
+
+    /**
+     * The print stream has been opened with the Guacamole client, but the
+     * client has not yet confirmed that it is ready to receive data.
+     */
+    GUAC_RDP_PRINT_JOB_WAITING_FOR_ACK,
+
+    /**
+     * The print stream has been opened with the Guacamole client, and the
+     * client has responded with an "ack", confirming that it is ready to
+     * receive data (or that data has been received and it is ready to receive
+     * more).
+     */
+    GUAC_RDP_PRINT_JOB_ACK_RECEIVED,
+
+    /**
+     * The print stream has been closed or the printer is terminating, and no
+     * further data should be sent to the client.
+     */
+    GUAC_RDP_PRINT_JOB_CLOSED
+
+} guac_rdp_print_job_state;
+
+/**
+ * Data specific to an instance of the printer device.
+ */
+typedef struct guac_rdp_print_job {
+
+    guac_client* client;
+
+    /**
+     * The user receiving the output from the print job.
+     */
+    guac_user* user;
+
+    /**
+     * The stream along which the print job output should be sent.
+     */
+    guac_stream* stream;
+
+    /**
+     * The PID of the print filter process converting PostScript data into PDF.
+     */
+    pid_t filter_pid;
+
+    /**
+     * The filename that should be used when the converted PDF output is
+     * streamed to the Guacamole user. This value will be automatically
+     * determined based on the contents of the printed document.
+     */
+    char filename[GUAC_RDP_PRINT_JOB_FILENAME_MAX_LENGTH];
+
+    /**
+     * File descriptor that should be written to when sending documents to the
+     * printer.
+     */
+    int input_fd;
+
+    /**
+     * File descriptor that should be read from when receiving output from the
+     * printer.
+     */
+    int output_fd;
+
+    /**
+     * The current state of the print stream, dependent on whether the client
+     * has acknowledged creation of the stream, whether the client has
+     * acknowledged receipt of data along the steam, and whether the print
+     * stream itself has closed.
+     */
+    guac_rdp_print_job_state state;
+
+    /**
+     * Lock which is acquired prior to modifying the state property or waiting
+     * on the state_modified conditional.
+     */
+    pthread_mutex_t state_lock;
+
+    /**
+     * Conditional which signals modification to the state property of this
+     * structure.
+     */
+    pthread_cond_t state_modified;
+
+    /**
+     * Thread which transfers data from the printer to the Guacamole client.
+     */
+    pthread_t output_thread;
+
+    /**
+     * The number of bytes received in the current print job.
+     */
+    int bytes_received;
+
+} guac_rdp_print_job;
+
+/**
+ * A blob of print data being sent to the Guacamole user.
+ */
+typedef struct guac_rdp_print_blob {
+
+    /**
+     * The print job which generated the data being sent.
+     */
+    guac_rdp_print_job* job;
+
+    /**
+     * The data being sent.
+     */
+    void* buffer;
+
+    /**
+     * The number of bytes of data being sent.
+     */
+    int length;
+
+} guac_rdp_print_blob;
+
+/**
+ * Allocates a new print job for the given user. It is expected that this
+ * function will be invoked via a call to guac_client_for_user() or
+ * guac_client_for_owner().
+ *
+ * @param user
+ *     The user that should receive the output from the print job.
+ *
+ * @param data
+ *     An arbitrary data parameter required by guac_client_for_user() and
+ *     guac_client_for_owner() but ignored by this function. This should
+ *     always be NULL.
+ *
+ * @return
+ *     A pointer to a newly-allocated guac_rdp_print_job, or NULL if the
+ *     print job could not be created.
+ */
+void* guac_rdp_print_job_alloc(guac_user* user, void* data);
+
+/**
+ * Writes PostScript print data to the given active print job. The print job
+ * will automatically convert this data to PDF, streaming the result to the
+ * Guacamole user associated with the print job. This function may block if
+ * the print job is not yet ready for more data.
+ *
+ * @param buffer
+ *     The PostScript print data to write to the given print job.
+ *
+ * @param length
+ *     The number of bytes of PostScript print data to write.
+ *
+ * @return
+ *     The number of bytes written, or -1 if an error occurs which prevents
+ *     further writes.
+ */
+int guac_rdp_print_job_write(guac_rdp_print_job* job,
+        void* buffer, int length);
+
+/**
+ * Frees the memory associated with the given print job, closing all underlying
+ * file descriptors, and ending the file transfer to the associated Guacamole
+ * user. This function may block if the print filter process has not yet
+ * finished processing the received data.
+ *
+ * @param job
+ *     The print job to free.
+ */
+void guac_rdp_print_job_free(guac_rdp_print_job* job);
+
+/**
+ * Forcibly kills the given print job, stopping all associated processing and
+ * streaming. The memory associated with the print job will still need to be
+ * reclaimed via guac_rdp_print_job_free().
+ *
+ * @param job
+ *     The print job to kill.
+ */
+void guac_rdp_print_job_kill(guac_rdp_print_job* job);
+
+#endif
+

Reply via email to