| /* |
| * parse.c - Parses commands Speech Dispatcher got from client |
| * |
| * Copyright (C) 2001, 2002, 2003, 2006 Brailcom, o.p.s. |
| * |
| * This is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2, or (at your option) |
| * any later version. |
| * |
| * This software is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program. If not, see <https://www.gnu.org/licenses/>. |
| * |
| * $Id: parse.c,v 1.73 2008-07-01 08:52:31 hanke Exp $ |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include <ctype.h> |
| |
| #include "speechd.h" |
| |
| #include "set.h" |
| #include "history.h" |
| #include "msg.h" |
| #include "server.h" |
| #include "sem_functions.h" |
| #include "output.h" |
| #include "fdsetconv.h" |
| |
| /* |
| Parse() receives input data and parses them. It can |
| be either command or data to speak. If it's command, it |
| is immadetaily executed (eg. parameters are set). If it's |
| data to speak, they are queued in corresponding queues |
| with corresponding parameters for synthesis. |
| */ |
| /* SSIP command allowed inside block? */ |
| #define BLOCK_NO 0 |
| #define BLOCK_OK 1 |
| |
| #define CHECK_SSIP_COMMAND(cmd_name, parse_function, allowed_in_block)\ |
| if(!strcmp(command, cmd_name)){ \ |
| g_free(command); \ |
| if ((allowed_in_block == BLOCK_NO) && speechd_socket->inside_block) \ |
| return g_strdup(ERR_NOT_ALLOWED_INSIDE_BLOCK); \ |
| return (char*) (parse_function) (buf, bytes, fd, speechd_socket); \ |
| } |
| |
| #define NOT_ALLOWED_INSIDE_BLOCK() \ |
| if(speechd_socket->inside_block > 0) \ |
| return g_strdup(ERR_NOT_ALLOWED_INSIDE_BLOCK); |
| |
| #define ALLOWED_INSIDE_BLOCK() ; |
| |
| char *parse(const char *buf, const int bytes, const int fd) |
| { |
| TSpeechDMessage *new; |
| char *command; |
| int end_data; |
| char *pos; |
| int reparted; |
| int msg_uid; |
| GString *ok_queued_reply; |
| TSpeechDSock *speechd_socket = speechd_socket_get_by_fd(fd); |
| assert(speechd_socket); |
| |
| end_data = 0; |
| if ((buf == NULL) || (bytes == 0)) { |
| if (SPEECHD_DEBUG) |
| FATAL("invalid buffer for parse()\n"); |
| return g_strdup(ERR_INTERNAL); |
| } |
| |
| /* First the condition that we are not in data mode and we |
| * are awaiting commands */ |
| if (speechd_socket->awaiting_data == 0) { |
| /* Read the command */ |
| command = get_param(buf, 0, bytes, 1); |
| |
| MSG(5, "Command caught: \"%s\"", command); |
| |
| /* Here we will check which command we got and process |
| * it with its parameters. */ |
| |
| if (command == NULL) { |
| if (SPEECHD_DEBUG) |
| FATAL("Invalid buffer for parse()\n"); |
| return g_strdup(ERR_INTERNAL); |
| } |
| |
| CHECK_SSIP_COMMAND("set", parse_set, BLOCK_OK); |
| CHECK_SSIP_COMMAND("history", parse_history, BLOCK_NO); |
| CHECK_SSIP_COMMAND("stop", parse_stop, BLOCK_NO); |
| CHECK_SSIP_COMMAND("cancel", parse_cancel, BLOCK_NO); |
| CHECK_SSIP_COMMAND("pause", parse_pause, BLOCK_NO); |
| CHECK_SSIP_COMMAND("resume", parse_resume, BLOCK_NO); |
| CHECK_SSIP_COMMAND("sound_icon", parse_snd_icon, BLOCK_OK); |
| CHECK_SSIP_COMMAND("char", parse_char, BLOCK_OK); |
| CHECK_SSIP_COMMAND("key", parse_key, BLOCK_OK) |
| CHECK_SSIP_COMMAND("list", parse_list, BLOCK_NO); |
| CHECK_SSIP_COMMAND("get", parse_get, BLOCK_NO); |
| CHECK_SSIP_COMMAND("help", parse_help, BLOCK_NO); |
| CHECK_SSIP_COMMAND("block", parse_block, BLOCK_OK); |
| |
| if (!strcmp(command, "bye") || !strcmp(command, "quit")) { |
| MSG(4, "Bye received."); |
| /* Send a reply to the socket */ |
| if (write(fd, OK_BYE, strlen(OK_BYE))) { |
| MSG(2, |
| "ERROR: Can't write OK_BYE message to client socket: %s", |
| strerror(errno)); |
| } |
| |
| speechd_connection_destroy(fd); |
| /* This is internal Speech Dispatcher message, see serve() */ |
| g_free(command); |
| return g_strdup("999 CLIENT GONE"); /* This is an internal message, not part of SSIP */ |
| } |
| |
| if (!strcmp(command, "speak")) { |
| g_free(command); |
| /* Ckeck if we have enough space in awaiting_data table for |
| * this client, that can have higher file descriptor that |
| * everything we got before */ |
| server_data_on(fd); |
| return g_strdup(OK_RECEIVE_DATA); |
| } |
| g_free(command); |
| return g_strdup(ERR_INVALID_COMMAND); |
| |
| /* The other case is that we are in awaiting_data mode and |
| * we are waiting for text that is coming through the chanel */ |
| } else { |
| enddata: |
| /* In the end of the data flow we got a "\r\n." NEWLINE command. */ |
| MSG(5, "Buffer: |%s| %d bytes:", buf, bytes); |
| |
| if (((bytes >= 5) && ((!strncmp(buf, "\r\n." NEWLINE, bytes)))) |
| || (end_data == 1) |
| || ((bytes == 3) && (!strncmp(buf, "." NEWLINE, bytes)))) { |
| |
| MSG(5, "Finishing data"); |
| end_data = 0; |
| |
| /* Set the flag to command mode */ |
| MSG(5, "Switching back to command mode..."); |
| speechd_socket->awaiting_data = 0; |
| |
| /* Prepare element (text+settings commands) to be queued. */ |
| |
| /* TODO: Remove? */ |
| if ((bytes == 3) && (speechd_socket->o_bytes > 2)) |
| speechd_socket->o_bytes -= 2; |
| |
| /* Check if message contains any data */ |
| if (speechd_socket->o_bytes == 0) |
| return g_strdup(OK_MSG_CANCELED); |
| |
| /* Check buffer for proper UTF-8 encoding */ |
| if (!g_utf8_validate |
| (speechd_socket->o_buf->str, |
| speechd_socket->o_bytes, NULL)) { |
| MSG(4, |
| "ERROR: Invalid character encoding on input (failed UTF-8 validation)"); |
| MSG(4, "Rejecting this message."); |
| return g_strdup(ERR_INVALID_ENCODING); |
| } |
| |
| new = |
| (TSpeechDMessage *) |
| g_malloc(sizeof(TSpeechDMessage)); |
| new->bytes = speechd_socket->o_bytes; |
| assert(speechd_socket->o_buf != NULL); |
| new->buf = |
| deescape_dot(speechd_socket->o_buf->str, |
| new->bytes); |
| reparted = speechd_socket->inside_block; |
| MSG(5, "New buf is now: |%s|", new->buf); |
| if ((msg_uid = |
| queue_message(new, fd, 1, SPD_MSGTYPE_TEXT, |
| reparted)) == 0) { |
| if (SPEECHD_DEBUG) |
| FATAL("Can't queue message\n"); |
| g_free(new->buf); |
| g_free(new); |
| return g_strdup(ERR_INTERNAL); |
| } |
| |
| /* Clear the counter of bytes in the output buffer. */ |
| server_data_off(fd); |
| ok_queued_reply = g_string_new(""); |
| g_string_printf(ok_queued_reply, |
| C_OK_MESSAGE_QUEUED "-%d" NEWLINE |
| OK_MESSAGE_QUEUED, msg_uid); |
| return g_string_free(ok_queued_reply, 0); |
| } |
| |
| { |
| int real_bytes; |
| if (bytes >= 5) { |
| if ((pos = strstr(buf, "\r\n." NEWLINE))) { |
| real_bytes = pos - buf; |
| end_data = 1; |
| MSG(5, "Command in data caught"); |
| } else { |
| real_bytes = bytes; |
| } |
| } else { |
| real_bytes = bytes; |
| } |
| /* Get the number of bytes read before, sum it with the number of bytes read |
| * now and store again in the counter */ |
| speechd_socket->o_bytes += real_bytes; |
| |
| g_string_insert_len(speechd_socket->o_buf, -1, buf, |
| real_bytes); |
| } |
| } |
| |
| if (end_data == 1) |
| goto enddata; |
| |
| /* Don't reply on data */ |
| return g_strdup("999 DATA"); |
| |
| } |
| |
| #undef CHECK_SSIP_COMMAND |
| |
| #define CHECK_PARAM(param) \ |
| if (param == NULL){ \ |
| MSG(4, "Missing parameter from client"); \ |
| return g_strdup(ERR_MISSING_PARAMETER); \ |
| } |
| |
| #define GET_PARAM_INT(name, pos) \ |
| { \ |
| char *helper; \ |
| helper = get_param(buf, pos, bytes, 0); \ |
| CHECK_PARAM(helper); \ |
| if (!isanum(helper)){ \ |
| g_free(helper); \ |
| return g_strdup(ERR_NOT_A_NUMBER); \ |
| } \ |
| name = atoi(helper); \ |
| g_free(helper); \ |
| } |
| |
| #define CONV_DOWN 1 |
| #define NO_CONV 0 |
| |
| #define GET_PARAM_STR(name, pos, up_lo_case) \ |
| name = get_param(buf, pos, bytes, up_lo_case); \ |
| CHECK_PARAM(name); |
| |
| /* Tests if cmd is the same as str AND deallocates cmd if |
| the test is successful */ |
| #define TEST_CMD(cmd, str) \ |
| (!strcmp(cmd, str) ? g_free(cmd), 1 : 0 ) |
| |
| /* Parses @history commands and calls the appropriate history_ functions. */ |
| char *parse_history(const char *buf, const int bytes, const int fd, |
| const TSpeechDSock * speechd_socket) |
| { |
| char *cmd_main; |
| GET_PARAM_STR(cmd_main, 1, CONV_DOWN); |
| |
| if (TEST_CMD(cmd_main, "get")) { |
| char *hist_get_sub; |
| GET_PARAM_STR(hist_get_sub, 2, CONV_DOWN); |
| |
| if (TEST_CMD(hist_get_sub, "client_list")) { |
| /* No longer able to get client list */ |
| return g_strdup(ERR_NOT_IMPLEMENTED); |
| } else if (TEST_CMD(hist_get_sub, "client_id")) { |
| /* Can still get your client id */ |
| return (char *)history_get_client_id(fd); |
| } else if (TEST_CMD(hist_get_sub, "client_messages")) { |
| int start, num; |
| char *who; |
| int who_id; |
| int client_id = get_client_uid_by_fd(fd); |
| |
| /* TODO: This needs to be (sim || am)-plified */ |
| who = get_param(buf, 3, bytes, 1); |
| CHECK_PARAM(who); |
| if (!strcmp(who, "self")) |
| /* TODO: Get all our messages, that should be allowed but how many to get... */ |
| return g_strdup(ERR_NOT_IMPLEMENTED); |
| if (!strcmp(who, "all")) |
| /* No longer allowed, security hole here */ |
| return g_strdup(ERR_NOT_IMPLEMENTED); |
| if (!isanum(who)) |
| return g_strdup(ERR_NOT_A_NUMBER); |
| who_id = atoi(who); |
| |
| /* Check if the id is the client */ |
| if (who_id != client_id) |
| return g_strdup(ERR_NOT_IMPLEMENTED); |
| |
| g_free(who); |
| GET_PARAM_INT(start, 4); |
| GET_PARAM_INT(num, 5); |
| return (char *)history_get_message_list(who_id, start, |
| num); |
| } else if (TEST_CMD(hist_get_sub, "last")) { |
| return (char *)history_get_last(fd); |
| } else if (TEST_CMD(hist_get_sub, "message")) { |
| int msg_id; |
| GET_PARAM_INT(msg_id, 3); |
| return (char *)history_get_message(msg_id); |
| } else { |
| return g_strdup(ERR_MISSING_PARAMETER); |
| } |
| } else if (TEST_CMD(cmd_main, "cursor")) { |
| char *hist_cur_sub; |
| GET_PARAM_STR(hist_cur_sub, 2, CONV_DOWN); |
| |
| if (TEST_CMD(hist_cur_sub, "set")) { |
| int who; |
| char *location; |
| |
| GET_PARAM_INT(who, 3); |
| GET_PARAM_STR(location, 4, CONV_DOWN); |
| |
| if (TEST_CMD(location, "last")) { |
| return (char *)history_cursor_set_last(fd, who); |
| } else if (TEST_CMD(location, "first")) { |
| return (char *)history_cursor_set_first(fd, |
| who); |
| } else if (TEST_CMD(location, "pos")) { |
| int pos; |
| GET_PARAM_INT(pos, 5); |
| return (char *)history_cursor_set_pos(fd, who, |
| pos); |
| } else { |
| g_free(location); |
| return g_strdup(ERR_MISSING_PARAMETER); |
| } |
| } else if (TEST_CMD(hist_cur_sub, "forward")) { |
| return (char *)history_cursor_forward(fd); |
| } else if (TEST_CMD(hist_cur_sub, "backward")) { |
| return (char *)history_cursor_backward(fd); |
| } else if (TEST_CMD(hist_cur_sub, "get")) { |
| return (char *)history_cursor_get(fd); |
| } else { |
| g_free(hist_cur_sub); |
| return g_strdup(ERR_MISSING_PARAMETER); |
| } |
| |
| } else if (TEST_CMD(cmd_main, "say")) { |
| int msg_id; |
| GET_PARAM_INT(msg_id, 2); |
| return (char *)history_say_id(fd, msg_id); |
| } else if (TEST_CMD(cmd_main, "sort")) { |
| // TODO: everything :) |
| return g_strdup(ERR_NOT_IMPLEMENTED); |
| } |
| |
| g_free(cmd_main); |
| return g_strdup(ERR_INVALID_COMMAND); |
| } |
| |
| #define SSIP_SET_COMMAND(param) \ |
| if (who == 0) ret = set_ ## param ## _self(fd, param); \ |
| else if (who == 1) ret = set_ ## param ## _uid(uid, param); \ |
| else if (who == 2) ret = set_ ## param ## _all(param); \ |
| |
| #define SSIP_ON_OFF_PARAM(param, ok_message, err_message, inside_block) \ |
| if (!strcmp(set_sub, #param)){ \ |
| char *helper_s; \ |
| int param; \ |
| \ |
| inside_block \ |
| \ |
| GET_PARAM_STR(helper_s, 3, CONV_DOWN); \ |
| \ |
| if(TEST_CMD(helper_s, "on")) param = 1; \ |
| else if(TEST_CMD(helper_s, "off")) param = 0; \ |
| else{ \ |
| g_free(helper_s); \ |
| return g_strdup(ERR_PARAMETER_NOT_ON_OFF); \ |
| } \ |
| SSIP_SET_COMMAND(param); \ |
| if (ret) return g_strdup(err_message); \ |
| return g_strdup(ok_message); \ |
| } |
| |
| char *parse_set(const char *buf, const int bytes, const int fd, |
| const TSpeechDSock * speechd_socket) |
| { |
| int who; /* 0 - self, 1 - uid specified, 2 - all */ |
| int uid = -1; /* uid of the client (only if who == 1) */ |
| /* uid = -1 avoids gcc warning */ |
| int ret = -1; // =-1 has no effect but avoids gcc warning |
| char *set_sub; |
| char *who_s; |
| |
| GET_PARAM_STR(who_s, 1, CONV_DOWN); |
| |
| if (TEST_CMD(who_s, "self")) |
| who = 0; |
| else if (TEST_CMD(who_s, "all")) |
| who = 2; |
| else if (isanum(who_s)) { |
| who = 1; |
| uid = atoi(who_s); |
| g_free(who_s); |
| } else { |
| g_free(who_s); |
| return g_strdup(ERR_PARAMETER_INVALID); |
| } |
| |
| GET_PARAM_STR(set_sub, 2, CONV_DOWN); |
| |
| if (TEST_CMD(set_sub, "priority")) { |
| char *priority_s; |
| SPDPriority priority; |
| NOT_ALLOWED_INSIDE_BLOCK(); |
| |
| /* Setting priority only allowed for "self" */ |
| if (who != 0) |
| return g_strdup(ERR_COULDNT_SET_PRIORITY); |
| GET_PARAM_STR(priority_s, 3, CONV_DOWN); |
| |
| if (TEST_CMD(priority_s, "important")) |
| priority = SPD_IMPORTANT; |
| else if (TEST_CMD(priority_s, "message")) |
| priority = SPD_MESSAGE; |
| else if (TEST_CMD(priority_s, "text")) |
| priority = SPD_TEXT; |
| else if (TEST_CMD(priority_s, "notification")) |
| priority = SPD_NOTIFICATION; |
| else if (TEST_CMD(priority_s, "progress")) |
| priority = SPD_PROGRESS; |
| else { |
| g_free(priority_s); |
| return g_strdup(ERR_UNKNOWN_PRIORITY); |
| } |
| |
| ret = set_priority_self(fd, priority); |
| if (ret) |
| return g_strdup(ERR_COULDNT_SET_PRIORITY); |
| return g_strdup(OK_PRIORITY_SET); |
| } else if (TEST_CMD(set_sub, "language")) { |
| char *language; |
| |
| GET_PARAM_STR(language, 3, CONV_DOWN); |
| |
| SSIP_SET_COMMAND(language); |
| g_free(language); |
| |
| if (ret) |
| return g_strdup(ERR_COULDNT_SET_LANGUAGE); |
| return g_strdup(OK_LANGUAGE_SET); |
| } else if (TEST_CMD(set_sub, "synthesis_voice")) { |
| char *synthesis_voice = NULL; |
| char *tmp = NULL; |
| gchar **split_command; |
| int i; |
| |
| split_command = g_strsplit(buf, " ", 0); |
| for (i = 0; split_command[i] != NULL; i++) { |
| tmp = NULL; |
| |
| if (i == 3) { |
| if (g_str_has_suffix(split_command[i], "\n")) { |
| synthesis_voice = g_strndup(split_command[i], strlen(split_command[i]) - 2); |
| } else { |
| synthesis_voice = g_strdup(split_command[i]); |
| } |
| } else if (i > 3) { |
| if (g_str_has_suffix(split_command[i], "\n")) { |
| tmp = g_strndup(split_command[i], strlen(split_command[i]) - 2); |
| g_free(split_command[i]); |
| split_command[i] = tmp; |
| tmp = NULL; |
| } |
| |
| tmp = g_strjoin(" ", synthesis_voice, split_command[i], NULL); |
| g_free(synthesis_voice); |
| synthesis_voice = tmp; |
| } |
| } |
| |
| g_strfreev(split_command); |
| |
| if (i < 3) |
| synthesis_voice = NULL; |
| |
| SSIP_SET_COMMAND(synthesis_voice); |
| g_free(synthesis_voice); |
| |
| if (ret) |
| return g_strdup(ERR_COULDNT_SET_VOICE); |
| return g_strdup(OK_VOICE_SET); |
| } else if (TEST_CMD(set_sub, "client_name")) { |
| char *client_name; |
| NOT_ALLOWED_INSIDE_BLOCK(); |
| |
| /* Setting client name only allowed for "self" */ |
| if (who != 0) |
| return g_strdup(ERR_PARAMETER_INVALID); |
| |
| GET_PARAM_STR(client_name, 3, CONV_DOWN); |
| |
| ret = set_client_name_self(fd, client_name); |
| g_free(client_name); |
| |
| if (ret) |
| return g_strdup(ERR_COULDNT_SET_CLIENT_NAME); |
| return g_strdup(OK_CLIENT_NAME_SET); |
| } else if (TEST_CMD(set_sub, "rate")) { |
| signed int rate; |
| GET_PARAM_INT(rate, 3); |
| |
| if (rate < -100) |
| return g_strdup(ERR_RATE_TOO_LOW); |
| if (rate > +100) |
| return g_strdup(ERR_RATE_TOO_HIGH); |
| |
| SSIP_SET_COMMAND(rate); |
| if (ret) |
| return g_strdup(ERR_COULDNT_SET_RATE); |
| return g_strdup(OK_RATE_SET); |
| } else if (TEST_CMD(set_sub, "pitch")) { |
| signed int pitch; |
| GET_PARAM_INT(pitch, 3); |
| |
| if (pitch < -100) |
| return g_strdup(ERR_PITCH_TOO_LOW); |
| if (pitch > +100) |
| return g_strdup(ERR_PITCH_TOO_HIGH); |
| |
| SSIP_SET_COMMAND(pitch); |
| if (ret) |
| return g_strdup(ERR_COULDNT_SET_PITCH); |
| return g_strdup(OK_PITCH_SET); |
| } else if (TEST_CMD(set_sub, "pitch_range")) { |
| signed int pitch_range; |
| GET_PARAM_INT(pitch_range, 3); |
| |
| if (pitch_range < -100) |
| return g_strdup(ERR_PITCH_RANGE_TOO_LOW); |
| if (pitch_range > +100) |
| return g_strdup(ERR_PITCH_RANGE_TOO_HIGH); |
| |
| SSIP_SET_COMMAND(pitch_range); |
| if (ret) |
| return g_strdup(ERR_COULDNT_SET_PITCH_RANGE); |
| return g_strdup(OK_PITCH_RANGE_SET); |
| } else if (TEST_CMD(set_sub, "volume")) { |
| signed int volume; |
| GET_PARAM_INT(volume, 3); |
| |
| if (volume < -100) |
| return g_strdup(ERR_VOLUME_TOO_LOW); |
| if (volume > +100) |
| return g_strdup(ERR_VOLUME_TOO_HIGH); |
| |
| SSIP_SET_COMMAND(volume); |
| if (ret) |
| return g_strdup(ERR_COULDNT_SET_VOLUME); |
| return g_strdup(OK_VOLUME_SET); |
| } else if (TEST_CMD(set_sub, "voice_type")) { |
| char *voice; |
| GET_PARAM_STR(voice, 3, CONV_DOWN); |
| |
| SSIP_SET_COMMAND(voice); |
| g_free(voice); |
| |
| if (ret) |
| return g_strdup(ERR_COULDNT_SET_VOICE); |
| return g_strdup(OK_VOICE_SET); |
| } else if (TEST_CMD(set_sub, "punctuation")) { |
| char *punct_s; |
| SPDPunctuation punctuation_mode; |
| |
| GET_PARAM_STR(punct_s, 3, CONV_DOWN); |
| |
| if (TEST_CMD(punct_s, "all")) |
| punctuation_mode = SPD_PUNCT_ALL; |
| else if (TEST_CMD(punct_s, "most")) |
| punctuation_mode = SPD_PUNCT_MOST; |
| else if (TEST_CMD(punct_s, "some")) |
| punctuation_mode = SPD_PUNCT_SOME; |
| else if (TEST_CMD(punct_s, "none")) |
| punctuation_mode = SPD_PUNCT_NONE; |
| else { |
| g_free(punct_s); |
| return g_strdup(ERR_PARAMETER_INVALID); |
| } |
| |
| SSIP_SET_COMMAND(punctuation_mode); |
| |
| if (ret) |
| return g_strdup(ERR_COULDNT_SET_PUNCT_MODE); |
| return g_strdup(OK_PUNCT_MODE_SET); |
| } else if (TEST_CMD(set_sub, "output_module")) { |
| char *output_module; |
| NOT_ALLOWED_INSIDE_BLOCK(); |
| GET_PARAM_STR(output_module, 3, CONV_DOWN); |
| |
| SSIP_SET_COMMAND(output_module); |
| g_free(output_module); |
| |
| if (ret) |
| return g_strdup(ERR_COULDNT_SET_OUTPUT_MODULE); |
| return g_strdup(OK_OUTPUT_MODULE_SET); |
| } else if (TEST_CMD(set_sub, "cap_let_recogn")) { |
| int capital_letter_recognition; |
| char *recognition; |
| GET_PARAM_STR(recognition, 3, CONV_DOWN); |
| |
| if (TEST_CMD(recognition, "none")) |
| capital_letter_recognition = SPD_CAP_NONE; |
| else if (TEST_CMD(recognition, "spell")) |
| capital_letter_recognition = SPD_CAP_SPELL; |
| else if (TEST_CMD(recognition, "icon")) |
| capital_letter_recognition = SPD_CAP_ICON; |
| else { |
| g_free(recognition); |
| return g_strdup(ERR_PARAMETER_INVALID); |
| } |
| |
| SSIP_SET_COMMAND(capital_letter_recognition); |
| |
| if (ret) |
| return g_strdup(ERR_COULDNT_SET_CAP_LET_RECOG); |
| return g_strdup(OK_CAP_LET_RECOGN_SET); |
| } else if (TEST_CMD(set_sub, "pause_context")) { |
| int pause_context; |
| GET_PARAM_INT(pause_context, 3); |
| |
| SSIP_SET_COMMAND(pause_context); |
| if (ret) |
| return g_strdup(ERR_COULDNT_SET_PAUSE_CONTEXT); |
| return g_strdup(OK_PAUSE_CONTEXT_SET); |
| } else |
| SSIP_ON_OFF_PARAM(spelling, |
| OK_SPELLING_SET, ERR_COULDNT_SET_SPELLING, |
| NOT_ALLOWED_INSIDE_BLOCK()) |
| else |
| SSIP_ON_OFF_PARAM(ssml_mode, |
| OK_SSML_MODE_SET, ERR_COULDNT_SET_SSML_MODE, |
| ALLOWED_INSIDE_BLOCK()) |
| else |
| SSIP_ON_OFF_PARAM(debug, |
| g_strdup_printf("262-%s" NEWLINE OK_DEBUGGING, |
| SpeechdOptions. |
| debug_destination), |
| ERR_COULDNT_SET_DEBUGGING,; |
| ) |
| else if (TEST_CMD(set_sub, "notification")) { |
| char *scope; |
| char *par_s; |
| int par; |
| |
| if (who != 0) |
| return g_strdup(ERR_PARAMETER_INVALID); |
| |
| GET_PARAM_STR(scope, 3, CONV_DOWN); |
| GET_PARAM_STR(par_s, 4, CONV_DOWN); |
| |
| if (TEST_CMD(par_s, "on")) |
| par = 1; |
| else if (TEST_CMD(par_s, "off")) |
| par = 0; |
| else { |
| g_free(par_s); |
| return g_strdup(ERR_PARAMETER_INVALID); |
| } |
| |
| ret = set_notification_self(fd, scope, par); |
| g_free(scope); |
| |
| if (ret) |
| return g_strdup(ERR_COULDNT_SET_NOTIFICATION); |
| return g_strdup(OK_NOTIFICATION_SET); |
| } |
| |
| g_free(set_sub); |
| return g_strdup(ERR_INVALID_COMMAND); |
| } |
| |
| #undef SSIP_SET_COMMAND |
| |
| char *parse_stop(const char *buf, const int bytes, const int fd, |
| const TSpeechDSock * speechd_socket) |
| { |
| int uid = 0; |
| char *who_s; |
| |
| MSG(5, "Stop received from fd %d.", fd); |
| |
| GET_PARAM_STR(who_s, 1, CONV_DOWN); |
| |
| if (TEST_CMD(who_s, "all")) { |
| pthread_mutex_lock(&element_free_mutex); |
| speaking_stop_all(); |
| pthread_mutex_unlock(&element_free_mutex); |
| } else if (TEST_CMD(who_s, "self")) { |
| uid = get_client_uid_by_fd(fd); |
| if (uid == 0) |
| return g_strdup(ERR_INTERNAL); |
| pthread_mutex_lock(&element_free_mutex); |
| speaking_stop(uid); |
| pthread_mutex_unlock(&element_free_mutex); |
| } else if (isanum(who_s)) { |
| uid = atoi(who_s); |
| g_free(who_s); |
| |
| if (uid <= 0) |
| return g_strdup(ERR_ID_NOT_EXIST); |
| pthread_mutex_lock(&element_free_mutex); |
| speaking_stop(uid); |
| pthread_mutex_unlock(&element_free_mutex); |
| } else { |
| g_free(who_s); |
| return g_strdup(ERR_PARAMETER_INVALID); |
| } |
| |
| return g_strdup(OK_STOPPED); |
| } |
| |
| char *parse_cancel(const char *buf, const int bytes, const int fd, |
| const TSpeechDSock * speechd_socket) |
| { |
| int uid = 0; |
| char *who_s; |
| |
| MSG(4, "Cancel received from fd %d.", fd); |
| |
| GET_PARAM_STR(who_s, 1, CONV_DOWN); |
| |
| if (TEST_CMD(who_s, "all")) { |
| speaking_cancel_all(); |
| } else if (TEST_CMD(who_s, "self")) { |
| uid = get_client_uid_by_fd(fd); |
| if (uid == 0) |
| return g_strdup(ERR_INTERNAL); |
| speaking_cancel(uid); |
| } else if (isanum(who_s)) { |
| uid = atoi(who_s); |
| g_free(who_s); |
| |
| if (uid <= 0) |
| return g_strdup(ERR_ID_NOT_EXIST); |
| speaking_cancel(uid); |
| } else { |
| g_free(who_s); |
| return g_strdup(ERR_PARAMETER_INVALID); |
| } |
| |
| return g_strdup(OK_CANCELED); |
| } |
| |
| char *parse_pause(const char *buf, const int bytes, const int fd, |
| const TSpeechDSock * speechd_socket) |
| { |
| int uid = 0; |
| char *who_s; |
| |
| MSG(4, "Pause received from fd %d.", fd); |
| |
| GET_PARAM_STR(who_s, 1, CONV_DOWN); |
| |
| /* Note: In this case, the semaphore has a special meaning |
| to allow the speaking loop detect the request for pause */ |
| |
| if (TEST_CMD(who_s, "all")) { |
| pause_requested = 1; |
| pause_requested_fd = fd; |
| speaking_semaphore_post(); |
| } else if (TEST_CMD(who_s, "self")) { |
| uid = get_client_uid_by_fd(fd); |
| if (uid == 0) |
| return g_strdup(ERR_INTERNAL); |
| pause_requested = 2; |
| pause_requested_fd = fd; |
| pause_requested_uid = uid; |
| speaking_semaphore_post(); |
| } else if (isanum(who_s)) { |
| uid = atoi(who_s); |
| g_free(who_s); |
| if (uid <= 0) |
| return g_strdup(ERR_ID_NOT_EXIST); |
| pause_requested = 2; |
| pause_requested_fd = fd; |
| pause_requested_uid = uid; |
| speaking_semaphore_post(); |
| } else { |
| g_free(who_s); |
| return g_strdup(ERR_PARAMETER_INVALID); |
| } |
| |
| return g_strdup(OK_PAUSED); |
| } |
| |
| char *parse_resume(const char *buf, const int bytes, const int fd, |
| const TSpeechDSock * speechd_socket) |
| { |
| int uid = 0; |
| char *who_s; |
| |
| MSG(4, "Resume received from fd %d.", fd); |
| |
| GET_PARAM_STR(who_s, 1, CONV_DOWN); |
| |
| if (TEST_CMD(who_s, "all")) { |
| speaking_resume_all(); |
| } else if (TEST_CMD(who_s, "self")) { |
| uid = get_client_uid_by_fd(fd); |
| if (uid == 0) |
| return g_strdup(ERR_INTERNAL); |
| speaking_resume(uid); |
| } else if (isanum(who_s)) { |
| uid = atoi(who_s); |
| g_free(who_s); |
| if (uid <= 0) |
| return g_strdup(ERR_ID_NOT_EXIST); |
| speaking_resume(uid); |
| } else { |
| g_free(who_s); |
| return g_strdup(ERR_PARAMETER_INVALID); |
| } |
| |
| return g_strdup(OK_RESUMED); |
| } |
| |
| char *parse_general_event(const char *buf, const int bytes, const int fd, |
| const TSpeechDSock * speechd_socket, |
| SPDMessageType type) |
| { |
| char *param; |
| TSpeechDMessage *msg; |
| int msg_uid; |
| |
| GET_PARAM_STR(param, 1, NO_CONV); |
| |
| if (param[0] == 0) { |
| g_free(param); |
| return g_strdup(ERR_MISSING_PARAMETER); |
| } |
| |
| /* Check for proper UTF-8 */ |
| /* Check buffer for proper UTF-8 encoding */ |
| if (!g_utf8_validate(buf, bytes, NULL)) { |
| MSG(4, |
| "ERROR: Invalid character encoding on event input (failed UTF-8 validation)"); |
| MSG(4, "Rejecting this event (char/key/sound_icon)."); |
| g_free(param); |
| return g_strdup(ERR_INVALID_ENCODING); |
| } |
| |
| if ((type == SPD_MSGTYPE_CHAR || type == SPD_MSGTYPE_KEY) |
| && !strcmp(param, "space")) |
| { |
| g_free(param); |
| param = g_strdup(" "); |
| } |
| |
| msg = (TSpeechDMessage *) g_malloc(sizeof(TSpeechDMessage)); |
| msg->bytes = strlen(param); |
| msg->buf = g_strdup(param); |
| |
| msg_uid = queue_message(msg, fd, 1, type, speechd_socket->inside_block); |
| if (msg_uid == 0) { |
| if (SPEECHD_DEBUG) |
| FATAL("Couldn't queue message\n"); |
| MSG(2, "Error: Couldn't queue message!\n"); |
| } |
| |
| g_free(param); |
| |
| return g_strdup_printf(C_OK_MESSAGE_QUEUED "-%d" NEWLINE |
| OK_MESSAGE_QUEUED, msg_uid); |
| } |
| |
| char *parse_snd_icon(const char *buf, const int bytes, const int fd, |
| const TSpeechDSock * speechd_socket) |
| { |
| return parse_general_event(buf, bytes, fd, speechd_socket, |
| SPD_MSGTYPE_SOUND_ICON); |
| } |
| |
| char *parse_char(const char *buf, const int bytes, const int fd, |
| const TSpeechDSock * speechd_socket) |
| { |
| return parse_general_event(buf, bytes, fd, speechd_socket, |
| SPD_MSGTYPE_CHAR); |
| } |
| |
| char *parse_key(const char *buf, const int bytes, const int fd, |
| const TSpeechDSock * speechd_socket) |
| { |
| return parse_general_event(buf, bytes, fd, speechd_socket, |
| SPD_MSGTYPE_KEY); |
| } |
| |
| char *parse_list(const char *buf, const int bytes, const int fd, |
| const TSpeechDSock * speechd_socket) |
| { |
| char *list_type; |
| char *voice_list; |
| |
| GET_PARAM_STR(list_type, 1, CONV_DOWN); |
| |
| if (TEST_CMD(list_type, "voices")) { |
| voice_list = (char *)g_malloc(1024); |
| sprintf(voice_list, |
| C_OK_VOICES "-MALE1" NEWLINE |
| C_OK_VOICES "-MALE2" NEWLINE |
| C_OK_VOICES "-MALE3" NEWLINE |
| C_OK_VOICES "-FEMALE1" NEWLINE |
| C_OK_VOICES "-FEMALE2" NEWLINE |
| C_OK_VOICES "-FEMALE3" NEWLINE |
| C_OK_VOICES "-CHILD_MALE" NEWLINE |
| C_OK_VOICES "-CHILD_FEMALE" NEWLINE OK_VOICE_LIST_SENT); |
| return voice_list; |
| } else if (TEST_CMD(list_type, "output_modules")) { |
| GString *result = g_string_new(""); |
| OutputModule *mod; |
| int i, len; |
| |
| len = g_list_length(output_modules); |
| for (i = 0; i < len; i++) { |
| mod = g_list_nth_data(output_modules, i); |
| if (strcmp(mod->name, "dummy") && |
| strcmp(mod->name, "espeak-ng-fallback") && |
| strcmp(mod->name, "generic")) |
| g_string_append_printf(result, C_OK_MODULES |
| "-%s" NEWLINE, |
| mod->name); |
| } |
| |
| g_string_append(result, OK_MODULES_LIST_SENT); |
| return g_string_free(result, 0); |
| } else if (TEST_CMD(list_type, "synthesis_voices")) { |
| int uid; |
| TFDSetElement *settings; |
| SPDVoice **voices; |
| GString *result; |
| int i; |
| char *language; |
| char *variant; |
| |
| uid = get_client_uid_by_fd(fd); |
| settings = get_client_settings_by_uid(uid); |
| if (settings == NULL) |
| return g_strdup(ERR_INTERNAL); |
| |
| language = get_param(buf, 2, bytes, NO_CONV); |
| variant = get_param(buf, 3, bytes, NO_CONV); |
| |
| voices = output_list_voices(settings->output_module, language, variant); |
| g_free(language); |
| g_free(variant); |
| if (voices == NULL) |
| return g_strdup(ERR_CANT_REPORT_VOICES); |
| |
| result = g_string_new(""); |
| for (i = 0;; i++) { |
| if (voices[i] == NULL) |
| break; |
| g_string_append_printf(result, |
| C_OK_VOICES "-%s\t%s\t%s" NEWLINE, |
| voices[i]->name, |
| voices[i]->language, |
| voices[i]->variant); |
| g_free(voices[i]->name); |
| g_free(voices[i]->language); |
| g_free(voices[i]->variant); |
| g_free(voices[i]); |
| } |
| g_string_append(result, OK_VOICE_LIST_SENT); |
| g_free(voices); |
| return g_string_free(result, 0); |
| } else { |
| g_free(list_type); |
| return g_strdup(ERR_PARAMETER_INVALID); |
| } |
| } |
| |
| char *parse_get(const char *buf, const int bytes, const int fd, |
| const TSpeechDSock * speechd_socket) |
| { |
| char *get_type; |
| GString *result; |
| |
| TFDSetElement *settings; |
| |
| settings = get_client_settings_by_fd(fd); |
| if (settings == NULL) |
| return g_strdup(ERR_INTERNAL); |
| |
| GET_PARAM_STR(get_type, 1, CONV_DOWN); |
| |
| result = g_string_new(""); |
| if (TEST_CMD(get_type, "voice_type")) { |
| switch (settings->msg_settings.voice_type) { |
| case SPD_MALE1: |
| g_string_append(result, C_OK_GET "-MALE1" NEWLINE OK_GET); |
| break; |
| case SPD_MALE2: |
| g_string_append(result, C_OK_GET "-MALE2" NEWLINE OK_GET); |
| break; |
| case SPD_MALE3: |
| g_string_append(result, C_OK_GET "-MALE3" NEWLINE OK_GET); |
| break; |
| case SPD_FEMALE1: |
| g_string_append(result, C_OK_GET "-FEMALE1" NEWLINE OK_GET); |
| break; |
| case SPD_FEMALE2: |
| g_string_append(result, C_OK_GET "-FEMALE2" NEWLINE OK_GET); |
| break; |
| case SPD_FEMALE3: |
| g_string_append(result, C_OK_GET "-FEMALE3" NEWLINE OK_GET); |
| break; |
| case SPD_CHILD_MALE: |
| g_string_append(result, |
| C_OK_GET "-CHILD_MALE" NEWLINE OK_GET); |
| break; |
| case SPD_CHILD_FEMALE: |
| g_string_append(result, |
| C_OK_GET "-CHILD_FEMALE" NEWLINE OK_GET); |
| break; |
| default: |
| g_string_append(result, |
| C_OK_GET "-NO_VOICE" NEWLINE OK_GET); |
| break; |
| } |
| } else if (TEST_CMD(get_type, "output_module")) { |
| g_string_append_printf(result, C_OK_GET "-%s" NEWLINE OK_GET, |
| settings->output_module); |
| } else if (TEST_CMD(get_type, "language")) { |
| g_string_append_printf(result, C_OK_GET "-%s" NEWLINE OK_GET, |
| settings->msg_settings.voice.language); |
| } else if (TEST_CMD(get_type, "rate")) { |
| g_string_append_printf(result, C_OK_GET "-%d" NEWLINE OK_GET, |
| settings->msg_settings.rate); |
| } else if (TEST_CMD(get_type, "pitch")) { |
| g_string_append_printf(result, C_OK_GET "-%d" NEWLINE OK_GET, |
| settings->msg_settings.pitch); |
| } else if (TEST_CMD(get_type, "volume")) { |
| g_string_append_printf(result, C_OK_GET "-%d" NEWLINE OK_GET, |
| settings->msg_settings.volume); |
| } else if (TEST_CMD(get_type, "punctuation")) { |
| char *punct = EPunctMode2str(settings->msg_settings.punctuation_mode); |
| g_string_append_printf(result, C_OK_GET "-%s" NEWLINE OK_GET, |
| punct); |
| g_free(punct); |
| } else { |
| g_free(get_type); |
| g_string_append(result, ERR_PARAMETER_INVALID); |
| } |
| return g_string_free(result, 0); |
| } |
| |
| char *parse_help(const char *buf, const int bytes, const int fd, |
| const TSpeechDSock * speechd_socket) |
| { |
| char *help; |
| |
| help = (char *)g_malloc(1024 * sizeof(char)); |
| |
| sprintf(help, |
| C_OK_HELP "- SPEAK -- say text " NEWLINE |
| C_OK_HELP "- KEY -- say a combination of keys " NEWLINE |
| C_OK_HELP "- CHAR -- say a character " NEWLINE |
| C_OK_HELP "- SOUND_ICON -- execute a sound icon " NEWLINE |
| C_OK_HELP "- SET -- set a parameter " NEWLINE |
| C_OK_HELP "- GET -- get a current parameter " NEWLINE |
| C_OK_HELP "- LIST -- list available arguments " NEWLINE |
| C_OK_HELP |
| "- HISTORY -- commands related to history " NEWLINE |
| C_OK_HELP "- QUIT -- close the connection " NEWLINE |
| OK_HELP_SENT); |
| |
| return help; |
| } |
| |
| char *parse_block(const char *buf, const int bytes, const int fd, |
| TSpeechDSock * speechd_socket) |
| { |
| char *cmd_main; |
| GET_PARAM_STR(cmd_main, 1, CONV_DOWN); |
| |
| if (TEST_CMD(cmd_main, "begin")) { |
| assert(speechd_socket->inside_block >= 0); |
| if (speechd_socket->inside_block == 0) { |
| speechd_socket->inside_block = ++SpeechdStatus.max_gid; |
| return g_strdup(OK_INSIDE_BLOCK); |
| } else { |
| return g_strdup(ERR_ALREADY_INSIDE_BLOCK); |
| } |
| } else if (TEST_CMD(cmd_main, "end")) { |
| assert(speechd_socket->inside_block >= 0); |
| if (speechd_socket->inside_block > 0) { |
| speechd_socket->inside_block = 0; |
| return g_strdup(OK_OUTSIDE_BLOCK); |
| } else { |
| return g_strdup(ERR_ALREADY_OUTSIDE_BLOCK); |
| } |
| } else { |
| g_free(cmd_main); |
| return g_strdup(ERR_PARAMETER_INVALID); |
| } |
| } |
| |
| /* |
| * deescape_dot: Replace .. with . at the start of lines or at the |
| * start of the string. |
| * @orig_text: text to be unescaped. |
| * @orig_len: length of the text. |
| * Returns: a freshly allocated string, containing the unescaped data. |
| * |
| * In SSIP, the message terminator is \r\n.\r\n, just as it is in SMTP |
| * and similar protocols. Thus, period needs to be escaped when it |
| * is the only character on a line. deescape_dot reverts that |
| * transformation, after the message is received. |
| * This function deserves further examination. |
| */ |
| |
| char *deescape_dot(const char *orig_text, size_t orig_len) |
| { |
| /* Constants. DOTLINE is CRLF followed by a period. |
| * DOTLINELEN is the length of DOTLINE. |
| * ESCAPED_DOTLINELEN is the length of the sequence \r\n.., |
| * which is used in the original (unescaped) text. |
| */ |
| static const char *DOTLINE = "\r\n."; |
| static const size_t DOTLINELEN = 3; |
| static const size_t ESCAPED_DOTLINELEN = 4; /* \r\n.. */ |
| |
| char *out_text = NULL; |
| char *out_ptr; |
| const char *orig_end = orig_text + orig_len; |
| |
| if (orig_text == NULL) |
| return NULL; |
| |
| out_text = g_malloc(orig_len + 1); |
| /* We may have allocated more than we need. In any case, out_text |
| * can be no longer than orig_text. |
| * Note: g_malloc aborts the program on failure to allocate. */ |
| |
| out_ptr = out_text; |
| if (orig_len >= 2) { |
| /* De-escape .. at start of text. */ |
| if ((orig_text[0] == '.') && (orig_text[1] == '.')) { |
| *(out_ptr++) = '.'; |
| orig_text = orig_text + 2; |
| } |
| } |
| |
| while (orig_text < orig_end) { |
| if ((orig_text[0] == '\r') && (orig_text[1] == '\n') |
| && (orig_text[2] == '.') && (orig_text[3] == '.')) { |
| /* We just found \r\n.., the sequence we want to unescape. */ |
| memcpy(out_ptr, DOTLINE, DOTLINELEN); |
| out_ptr += DOTLINELEN; |
| orig_text += ESCAPED_DOTLINELEN; |
| } else { |
| /* Just copy the character from source to destination... */ |
| *(out_ptr++) = *(orig_text++); |
| } |
| } |
| |
| *out_ptr = '\0'; /* NUL-terminate. */ |
| return out_text; |
| } |
| |
| /* isanum() tests if the given string is a number, |
| * returns 1 if yes, 0 otherwise. */ |
| int isanum(const char *str) |
| { |
| int i; |
| if (!isdigit(str[0]) && !((str[0] == '+') || (str[0] == '-'))) |
| return 0; |
| for (i = 1; i <= strlen(str) - 1; i++) { |
| if (!isdigit(str[i])) |
| return 0; |
| } |
| return 1; |
| } |
| |
| /* Gets command parameter _n_ from the text buffer _buf_ |
| * which has _bytes_ bytes. Note that the parameter with |
| * index 0 is the command itself. */ |
| char *get_param(const char *buf, const int n, const int bytes, |
| const int lower_case) |
| { |
| char *param; |
| char *par; |
| int i, y, z = 0; |
| |
| assert(bytes != 0); |
| param = (char *)g_malloc(bytes); |
| assert(param != NULL); |
| |
| strcpy(param, ""); |
| i = 0; |
| |
| /* Read all the parameters one by one, |
| * but stop after the one with index n, |
| * while maintaining it's value in _param_ */ |
| for (y = 0; y <= n; y++) { |
| z = 0; |
| for (; i < bytes; i++) { |
| if (buf[i] == ' ') |
| break; |
| param[z] = buf[i]; |
| z++; |
| } |
| i++; |
| } |
| |
| if (z <= 0) { |
| g_free(param); |
| return NULL; |
| } |
| |
| /* Write the trailing zero */ |
| if (i >= bytes) { |
| param[z > 1 ? z - 2 : 0] = 0; |
| } else { |
| param[z] = 0; |
| } |
| |
| if (lower_case) { |
| par = g_ascii_strdown(param, strlen(param)); |
| g_free(param); |
| } else { |
| par = param; |
| } |
| |
| return par; |
| } |
| |
| /* Read one char (which _pointer_ is pointing to) from an UTF-8 string |
| * and store it into _character_. _character_ must have space for |
| * at least 7 bytes (6 bytes character + 1 byte trailing 0). This |
| * function doesn't validate if the string is valid UTF-8. |
| */ |
| int spd_utf8_read_char(const char *pointer, char *character) |
| { |
| int bytes; |
| gunichar u_char; |
| |
| u_char = g_utf8_get_char(pointer); |
| bytes = g_unichar_to_utf8(u_char, character); |
| character[bytes] = 0; |
| |
| return bytes; |
| } |