/* * Asterisk -- An open source telephony toolkit. * * Copyright (C) 1999 - 2008, Digium, Inc. * * Mark Spencer * * See http://www.asterisk.org for more information about * the Asterisk project. Please do not directly contact * any of the maintainers of this project for assistance; * the project provides a web site, mailing lists and IRC * channels for your use. * * This program is free software, distributed under the terms of * the GNU General Public License Version 2. See the LICENSE file * at the top of the source tree. */ /*! \file * * \brief Core PBX routines. * * \author Mark Spencer */ #include "asterisk.h" ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/_private.h" #include "asterisk/paths.h" /* use ast_config_AST_SYSTEM_NAME */ #include #include #include #if defined(HAVE_SYSINFO) #include #endif #if defined(SOLARIS) #include #endif #include "asterisk/lock.h" #include "asterisk/cli.h" #include "asterisk/pbx.h" #include "asterisk/channel.h" #include "asterisk/file.h" #include "asterisk/callerid.h" #include "asterisk/cdr.h" #include "asterisk/config.h" #include "asterisk/term.h" #include "asterisk/time.h" #include "asterisk/manager.h" #include "asterisk/ast_expr.h" #include "asterisk/linkedlists.h" #define SAY_STUBS /* generate declarations and stubs for say methods */ #include "asterisk/say.h" #include "asterisk/utils.h" #include "asterisk/causes.h" #include "asterisk/musiconhold.h" #include "asterisk/app.h" #include "asterisk/devicestate.h" #include "asterisk/event.h" #include "asterisk/hashtab.h" #include "asterisk/module.h" #include "asterisk/indications.h" #include "asterisk/taskprocessor.h" #include "asterisk/xml.h" /*! * \note I M P O R T A N T : * * The speed of extension handling will likely be among the most important * aspects of this PBX. The switching scheme as it exists right now isn't * terribly bad (it's O(N+M), where N is the # of extensions and M is the avg # * of priorities, but a constant search time here would be great ;-) * * A new algorithm to do searching based on a 'compiled' pattern tree is introduced * here, and shows a fairly flat (constant) search time, even for over * 10000 patterns. * * Also, using a hash table for context/priority name lookup can help prevent * the find_extension routines from absorbing exponential cpu cycles as the number * of contexts/priorities grow. I've previously tested find_extension with red-black trees, * which have O(log2(n)) speed. Right now, I'm using hash tables, which do * searches (ideally) in O(1) time. While these techniques do not yield much * speed in small dialplans, they are worth the trouble in large dialplans. * */ /*** DOCUMENTATION Answer a channel if ringing. Asterisk will wait this number of milliseconds before returning to the dialplan after answering the call. If the call has not been answered, this application will answer it. Otherwise, it has no effect on the call. Hangup Play an audio file while waiting for digits of an extension to go to. Explicitly specifies which language to attempt to use for the requested sound files. This is the dialplan context that this application will use when exiting to a dialed extension. This application will play the given list of files (do not put extension) while waiting for an extension to be dialed by the calling channel. To continue waiting for digits after this application has finished playing files, the WaitExten application should be used. If one of the requested sound files does not exist, call processing will be terminated. This application sets the following channel variable upon completion: The status of the background attempt as a text string. ControlPlayback WaitExten BackgroundDetect TIMEOUT Indicate the Busy condition. If specified, the calling channel will be hung up after the specified number of seconds. Otherwise, this application will wait until the calling channel hangs up. This application will indicate the busy condition to the calling channel. Congestion Progess Playtones Hangup Indicate the Congestion condition. If specified, the calling channel will be hung up after the specified number of seconds. Otherwise, this application will wait until the calling channel hangs up. This application will indicate the congestion condition to the calling channel. Busy Progess Playtones Hangup Conditional application execution based on the current time. This application will execute the specified dialplan application, with optional arguments, if the current time matches the given time specification. Exec TryExec Jump to a particular priority, extension, or context. This application will set the current context, extension, and priority in the channel structure. After it completes, the pbx engine will continue dialplan execution at the specified location. If no specific extension, or extension and context, are specified, then this application will just set the specified priority of the current extension. At least a priority is required as an argument, or the goto will return a -1, and the channel and call will be terminated. If the location that is put into the channel information is bogus, and asterisk cannot find that location in the dialplan, then the execution engine will try to find and execute the code in the i (invalid) extension in the current context. If that does not exist, it will try to execute the h extension. If either or neither the h or i extensions have been defined, the channel is hung up, and the execution of instructions on the channel is terminated. What this means is that, for example, you specify a context that does not exist, then it will not be possible to find the h or i extensions, and the call will terminate! GotoIf GotoIfTime Gosub Macro Conditional goto. Continue at labeliftrue if the condition is true. Continue at labeliffalse if the condition is false. This application will set the current context, extension, and priority in the channel structure based on the evaluation of the given condition. After this application completes, the pbx engine will continue dialplan execution at the specified location in the dialplan. The labels are specified with the same syntax as used within the Goto application. If the label chosen by the condition is omitted, no jump is performed, and the execution passes to the next instruction. If the target location is bogus, and does not exist, the execution engine will try to find and execute the code in the i (invalid) extension in the current context. If that does not exist, it will try to execute the h extension. If either or neither the h or i extensions have been defined, the channel is hung up, and the execution of instructions on the channel is terminated. Remember that this command can set the current context, and if the context specified does not exist, then it will not be able to find any 'h' or 'i' extensions there, and the channel and call will both be terminated!. Goto GotoIfTime GosubIf MacroIf Conditional Goto based on the current time. This application will set the context, extension, and priority in the channel structure based on the evaluation of the given time specification. After this application completes, the pbx engine will continue dialplan execution at the specified location in the dialplan. If the current time is within the given time specification, the channel will continue at labeliftrue. Otherwise the channel will continue at labeliffalse. If the label chosen by the condition is omitted, no jump is performed, and execution passes to the next instruction. If the target jump location is bogus, the same actions would be taken as for Goto. Further information on the time specification can be found in examples illustrating how to do time-based context includes in the dialplan. GotoIf IFTIME Import a variable from a channel into a new variable. This application imports a variable from the specified channel (as opposed to the current one) and stores it as a variable (newvar) in the current channel (the channel that is calling this application). Variables created by this application have the same inheritance properties as those created with the Set application. Set Hang up the calling channel. If a causecode is given the channel's hangup cause will be set to the given value. This application will hang up the calling channel. Answer Busy Congestion Returns AST_PBX_INCOMPLETE value. If specified, then Incomplete will not attempt to answer the channel first. Most channel types need to be in Answer state in order to receive DTMF. Signals the PBX routines that the previous matched extension is incomplete and that further input should be allowed before matching can be considered to be complete. Can be used within a pattern match when certain criteria warrants a longer match. Returns AST_PBX_KEEPALIVE value. This application is chiefly meant for internal use with Gosubs. Please do not run it alone from the dialplan! Do Nothing (No Operation). Any text provided can be viewed at the Asterisk CLI. This application does nothing. However, it is useful for debugging purposes. This method can be used to see the evaluations of variables or functions without having any effect. Verbose Log Indicate proceeding. This application will request that a proceeding message be provided to the calling channel. Indicate progress. This application will request that in-band progress information be provided to the calling channel. Busy Congestion Ringing Playtones Handle an exceptional condition. This application will jump to the e extension in the current context, setting the dialplan function EXCEPTION(). If the e extension does not exist, the call will hangup. Exception Resets the Call Data Record. This application causes the Call Data Record to be reset. ForkCDR NoCDR Indicate ringing tone. This application will request that the channel indicate a ringing tone to the user. Busy Congestion Progress Playtones Say Alpha. This application will play the sounds that correspond to the letters of the given string. SayDigits SayNumber SayPhonetic CHANNEL Say Digits. This application will play the sounds that correspond to the digits of the given number. This will use the language that is currently set for the channel. SayAlpha SayNumber SayPhonetic CHANNEL Say Number. This application will play the sounds that correspond to the given digits. Optionally, a gender may be specified. This will use the language that is currently set for the channel. See the LANGUAGE() function for more information on setting the language for the channel. SayAlpha SayDigits SayPhonetic CHANNEL Say Phonetic. This application will play the sounds from the phonetic alphabet that correspond to the letters in the given string. SayAlpha SayDigits SayNumber Set channel variable or function value. This function can be used to set the value of channel variables or dialplan functions. When setting variables, if the variable name is prefixed with _, the variable will be inherited into channels created from the current channel. If the variable name is prefixed with __, the variable will be inherited into channels created from the current channel and all children channels. If (and only if), in /etc/asterisk/asterisk.conf, you have a [compat] category, and you have app_set = 1.6 under that,then the behavior of this app changes, and does not strip surrounding quotes from the right hand side as it did previously in 1.4. The app_set = 1.6 is only inserted if make samples is executed, or if users insert this by hand into the asterisk.conf file. The advantages of not stripping out quoting, and not caring about the separator characters (comma and vertical bar) were sufficient to make these changes in 1.6. Confusion about how many backslashes would be needed to properly protect separators and quotes in various database access strings has been greatly reduced by these changes. MSet GLOBAL SET ENV Set channel variable(s) or function value(s). This function can be used to set the value of channel variables or dialplan functions. When setting variables, if the variable name is prefixed with _, the variable will be inherited into channels created from the current channel If the variable name is prefixed with __, the variable will be inherited into channels created from the current channel and all children channels. MSet behaves in a similar fashion to the way Set worked in 1.2/1.4 and is thus prone to doing things that you may not expect. For example, it strips surrounding double-quotes from the right-hand side (value). If you need to put a separator character (comma or vert-bar), you will need to escape them by inserting a backslash before them. Avoid its use if possible. Set Set the AMA Flags. This application will set the channel's AMA Flags for billing purposes. CDR Waits for some time. Can be passed with fractions of a second. For example, 1.5 will ask the application to wait for 1.5 seconds. This application waits for a specified number of seconds. Waits for an extension to be entered. Can be passed with fractions of a second. For example, 1.5 will ask the application to wait for 1.5 seconds. This application waits for the user to enter a new extension for a specified number of seconds. Background TIMEOUT Retrieve the details of the current dialplan exception. The following fields are available for retrieval: INVALID, ERROR, RESPONSETIMEOUT, ABSOLUTETIMEOUT, or custom value set by the RaiseException() application The context executing when the exception occurred. The extension executing when the exception occurred. The numeric priority executing when the exception occurred. Retrieve the details (specified field) of the current dialplan exception. RaiseException ***/ #ifdef LOW_MEMORY #define EXT_DATA_SIZE 256 #else #define EXT_DATA_SIZE 8192 #endif #define SWITCH_DATA_LENGTH 256 #define VAR_BUF_SIZE 4096 #define VAR_NORMAL 1 #define VAR_SOFTTRAN 2 #define VAR_HARDTRAN 3 #define BACKGROUND_SKIP (1 << 0) #define BACKGROUND_NOANSWER (1 << 1) #define BACKGROUND_MATCHEXTEN (1 << 2) #define BACKGROUND_PLAYBACK (1 << 3) AST_APP_OPTIONS(background_opts, { AST_APP_OPTION('s', BACKGROUND_SKIP), AST_APP_OPTION('n', BACKGROUND_NOANSWER), AST_APP_OPTION('m', BACKGROUND_MATCHEXTEN), AST_APP_OPTION('p', BACKGROUND_PLAYBACK), }); #define WAITEXTEN_MOH (1 << 0) #define WAITEXTEN_DIALTONE (1 << 1) AST_APP_OPTIONS(waitexten_opts, { AST_APP_OPTION_ARG('m', WAITEXTEN_MOH, 0), AST_APP_OPTION_ARG('d', WAITEXTEN_DIALTONE, 0), }); struct ast_context; struct ast_app; static struct ast_taskprocessor *device_state_tps; AST_THREADSTORAGE(switch_data); /*! \brief ast_exten: An extension The dialplan is saved as a linked list with each context having it's own linked list of extensions - one item per priority. */ struct ast_exten { char *exten; /*!< Extension name */ int matchcid; /*!< Match caller id ? */ const char *cidmatch; /*!< Caller id to match for this extension */ int priority; /*!< Priority */ const char *label; /*!< Label */ struct ast_context *parent; /*!< The context this extension belongs to */ const char *app; /*!< Application to execute */ struct ast_app *cached_app; /*!< Cached location of application */ void *data; /*!< Data to use (arguments) */ void (*datad)(void *); /*!< Data destructor */ struct ast_exten *peer; /*!< Next higher priority with our extension */ struct ast_hashtab *peer_table; /*!< Priorities list in hashtab form -- only on the head of the peer list */ struct ast_hashtab *peer_label_table; /*!< labeled priorities in the peers -- only on the head of the peer list */ const char *registrar; /*!< Registrar */ struct ast_exten *next; /*!< Extension with a greater ID */ char stuff[0]; }; /*! \brief ast_include: include= support in extensions.conf */ struct ast_include { const char *name; const char *rname; /*!< Context to include */ const char *registrar; /*!< Registrar */ int hastime; /*!< If time construct exists */ struct ast_timing timing; /*!< time construct */ struct ast_include *next; /*!< Link them together */ char stuff[0]; }; /*! \brief ast_sw: Switch statement in extensions.conf */ struct ast_sw { char *name; const char *registrar; /*!< Registrar */ char *data; /*!< Data load */ int eval; AST_LIST_ENTRY(ast_sw) list; char stuff[0]; }; /*! \brief ast_ignorepat: Ignore patterns in dial plan */ struct ast_ignorepat { const char *registrar; struct ast_ignorepat *next; const char pattern[0]; }; /*! \brief match_char: forms a syntax tree for quick matching of extension patterns */ struct match_char { int is_pattern; /* the pattern started with '_' */ int deleted; /* if this is set, then... don't return it */ char *x; /* the pattern itself-- matches a single char */ int specificity; /* simply the strlen of x, or 10 for X, 9 for Z, and 8 for N; and '.' and '!' will add 11 ? */ struct match_char *alt_char; struct match_char *next_char; struct ast_exten *exten; /* attached to last char of a pattern for exten */ }; struct scoreboard /* make sure all fields are 0 before calling new_find_extension */ { int total_specificity; int total_length; char last_char; /* set to ! or . if they are the end of the pattern */ int canmatch; /* if the string to match was just too short */ struct match_char *node; struct ast_exten *canmatch_exten; struct ast_exten *exten; }; /*! \brief ast_context: An extension context */ struct ast_context { ast_rwlock_t lock; /*!< A lock to prevent multiple threads from clobbering the context */ struct ast_exten *root; /*!< The root of the list of extensions */ struct ast_hashtab *root_table; /*!< For exact matches on the extensions in the pattern tree, and for traversals of the pattern_tree */ struct match_char *pattern_tree; /*!< A tree to speed up extension pattern matching */ struct ast_context *next; /*!< Link them together */ struct ast_include *includes; /*!< Include other contexts */ struct ast_ignorepat *ignorepats; /*!< Patterns for which to continue playing dialtone */ char *registrar; /*!< Registrar -- make sure you malloc this, as the registrar may have to survive module unloads */ int refcount; /*!< each module that would have created this context should inc/dec this as appropriate */ AST_LIST_HEAD_NOLOCK(, ast_sw) alts; /*!< Alternative switches */ ast_mutex_t macrolock; /*!< A lock to implement "exclusive" macros - held whilst a call is executing in the macro */ char name[0]; /*!< Name of the context */ }; /*! \brief ast_app: A registered application */ struct ast_app { int (*execute)(struct ast_channel *chan, void *data); AST_DECLARE_STRING_FIELDS( AST_STRING_FIELD(synopsis); /*!< Synopsis text for 'show applications' */ AST_STRING_FIELD(description); /*!< Description (help text) for 'show application <name>' */ AST_STRING_FIELD(syntax); /*!< Syntax text for 'core show applications' */ AST_STRING_FIELD(arguments); /*!< Arguments description */ AST_STRING_FIELD(seealso); /*!< See also */ ); enum ast_doc_src docsrc;/*!< Where the documentation come from. */ AST_RWLIST_ENTRY(ast_app) list; /*!< Next app in list */ struct ast_module *module; /*!< Module this app belongs to */ char name[0]; /*!< Name of the application */ }; /*! \brief ast_state_cb: An extension state notify register item */ struct ast_state_cb { int id; void *data; ast_state_cb_type callback; AST_LIST_ENTRY(ast_state_cb) entry; }; /*! \brief Structure for dial plan hints \note Hints are pointers from an extension in the dialplan to one or more devices (tech/name) - See \ref AstExtState */ struct ast_hint { struct ast_exten *exten; /*!< Extension */ int laststate; /*!< Last known state */ AST_LIST_HEAD_NOLOCK(, ast_state_cb) callbacks; /*!< Callback list for this extension */ AST_RWLIST_ENTRY(ast_hint) list;/*!< Pointer to next hint in list */ }; static const struct cfextension_states { int extension_state; const char * const text; } extension_states[] = { { AST_EXTENSION_NOT_INUSE, "Idle" }, { AST_EXTENSION_INUSE, "InUse" }, { AST_EXTENSION_BUSY, "Busy" }, { AST_EXTENSION_UNAVAILABLE, "Unavailable" }, { AST_EXTENSION_RINGING, "Ringing" }, { AST_EXTENSION_INUSE | AST_EXTENSION_RINGING, "InUse&Ringing" }, { AST_EXTENSION_ONHOLD, "Hold" }, { AST_EXTENSION_INUSE | AST_EXTENSION_ONHOLD, "InUse&Hold" } }; struct statechange { AST_LIST_ENTRY(statechange) entry; char dev[0]; }; struct pbx_exception { AST_DECLARE_STRING_FIELDS( AST_STRING_FIELD(context); /*!< Context associated with this exception */ AST_STRING_FIELD(exten); /*!< Exten associated with this exception */ AST_STRING_FIELD(reason); /*!< The exception reason */ ); int priority; /*!< Priority associated with this exception */ }; #ifdef AST_XML_DOCS /*! \brief Default documentation language. */ static const char default_documentation_language[] = "en_US"; /*! \brief Number of columns to print when showing the XML documentation with a * 'core show application/function *' CLI command. Used in text wrapping.*/ static const int xmldoc_text_columns = 74; /*! \brief This is a value that we will use to let the wrapping mechanism move the cursor * backward and forward xmldoc_max_diff positions before cutting the middle of a * word, trying to find a space or a \n. */ static const int xmldoc_max_diff = 5; /*! \brief XML documentation language. */ static char documentation_language[6]; /*! \brief XML documentation tree */ struct documentation_tree { char *filename; /*!< XML document filename. */ struct ast_xml_doc *doc; /*!< Open document pointer. */ AST_RWLIST_ENTRY(documentation_tree) entry; }; /*! * \brief Container of documentation trees * * \note A RWLIST is a sufficient container type to use here for now. * However, some changes will need to be made to implement ref counting * if reload support is added in the future. */ static AST_RWLIST_HEAD_STATIC(xmldoc_tree, documentation_tree); #endif /*! \brief Maximum number of characters needed for a color escape sequence, plus a null char */ #define MAX_ESCAPE_CHARS 23 static int pbx_builtin_answer(struct ast_channel *, void *); static int pbx_builtin_goto(struct ast_channel *, void *); static int pbx_builtin_hangup(struct ast_channel *, void *); static int pbx_builtin_background(struct ast_channel *, void *); static int pbx_builtin_wait(struct ast_channel *, void *); static int pbx_builtin_waitexten(struct ast_channel *, void *); static int pbx_builtin_incomplete(struct ast_channel *, void *); static int pbx_builtin_keepalive(struct ast_channel *, void *); static int pbx_builtin_resetcdr(struct ast_channel *, void *); static int pbx_builtin_setamaflags(struct ast_channel *, void *); static int pbx_builtin_ringing(struct ast_channel *, void *); static int pbx_builtin_proceeding(struct ast_channel *, void *); static int pbx_builtin_progress(struct ast_channel *, void *); static int pbx_builtin_congestion(struct ast_channel *, void *); static int pbx_builtin_busy(struct ast_channel *, void *); static int pbx_builtin_noop(struct ast_channel *, void *); static int pbx_builtin_gotoif(struct ast_channel *, void *); static int pbx_builtin_gotoiftime(struct ast_channel *, void *); static int pbx_builtin_execiftime(struct ast_channel *, void *); static int pbx_builtin_saynumber(struct ast_channel *, void *); static int pbx_builtin_saydigits(struct ast_channel *, void *); static int pbx_builtin_saycharacters(struct ast_channel *, void *); static int pbx_builtin_sayphonetic(struct ast_channel *, void *); static int matchcid(const char *cidpattern, const char *callerid); int pbx_builtin_setvar(struct ast_channel *, void *); void log_match_char_tree(struct match_char *node, char *prefix); /* for use anywhere */ int pbx_builtin_setvar_multiple(struct ast_channel *, void *); static int pbx_builtin_importvar(struct ast_channel *, void *); static void set_ext_pri(struct ast_channel *c, const char *exten, int pri); static void new_find_extension(const char *str, struct scoreboard *score, struct match_char *tree, int length, int spec, const char *callerid, const char *label, enum ext_match_t action); static struct match_char *already_in_tree(struct match_char *current, char *pat); static struct match_char *add_exten_to_pattern_tree(struct ast_context *con, struct ast_exten *e1, int findonly); static struct match_char *add_pattern_node(struct ast_context *con, struct match_char *current, char *pattern, int is_pattern, int already, int specificity, struct match_char **parent); static void create_match_char_tree(struct ast_context *con); static struct ast_exten *get_canmatch_exten(struct match_char *node); static void destroy_pattern_tree(struct match_char *pattern_tree); int ast_hashtab_compare_contexts(const void *ah_a, const void *ah_b); static int hashtab_compare_extens(const void *ha_a, const void *ah_b); static int hashtab_compare_exten_numbers(const void *ah_a, const void *ah_b); static int hashtab_compare_exten_labels(const void *ah_a, const void *ah_b); unsigned int ast_hashtab_hash_contexts(const void *obj); static unsigned int hashtab_hash_extens(const void *obj); static unsigned int hashtab_hash_priority(const void *obj); static unsigned int hashtab_hash_labels(const void *obj); static void __ast_internal_context_destroy( struct ast_context *con); #ifdef AST_XML_DOCS static char *xmldoc_colorization(const char *bwinput); #endif /* a func for qsort to use to sort a char array */ static int compare_char(const void *a, const void *b) { const char *ac = a; const char *bc = b; if ((*ac) < (*bc)) return -1; else if ((*ac) == (*bc)) return 0; else return 1; } /* labels, contexts are case sensitive priority numbers are ints */ int ast_hashtab_compare_contexts(const void *ah_a, const void *ah_b) { const struct ast_context *ac = ah_a; const struct ast_context *bc = ah_b; if (!ac || !bc) /* safety valve, but it might prevent a crash you'd rather have happen */ return 1; /* assume context names are registered in a string table! */ return strcmp(ac->name, bc->name); } static int hashtab_compare_extens(const void *ah_a, const void *ah_b) { const struct ast_exten *ac = ah_a; const struct ast_exten *bc = ah_b; int x = strcmp(ac->exten, bc->exten); if (x) { /* if exten names are diff, then return */ return x; } /* but if they are the same, do the cidmatch values match? */ if (ac->matchcid && bc->matchcid) { return strcmp(ac->cidmatch,bc->cidmatch); } else if (!ac->matchcid && !bc->matchcid) { return 0; /* if there's no matchcid on either side, then this is a match */ } else { return 1; /* if there's matchcid on one but not the other, they are different */ } } static int hashtab_compare_exten_numbers(const void *ah_a, const void *ah_b) { const struct ast_exten *ac = ah_a; const struct ast_exten *bc = ah_b; return ac->priority != bc->priority; } static int hashtab_compare_exten_labels(const void *ah_a, const void *ah_b) { const struct ast_exten *ac = ah_a; const struct ast_exten *bc = ah_b; return strcmp(ac->label, bc->label); } unsigned int ast_hashtab_hash_contexts(const void *obj) { const struct ast_context *ac = obj; return ast_hashtab_hash_string(ac->name); } static unsigned int hashtab_hash_extens(const void *obj) { const struct ast_exten *ac = obj; unsigned int x = ast_hashtab_hash_string(ac->exten); unsigned int y = 0; if (ac->matchcid) y = ast_hashtab_hash_string(ac->cidmatch); return x+y; } static unsigned int hashtab_hash_priority(const void *obj) { const struct ast_exten *ac = obj; return ast_hashtab_hash_int(ac->priority); } static unsigned int hashtab_hash_labels(const void *obj) { const struct ast_exten *ac = obj; return ast_hashtab_hash_string(ac->label); } AST_RWLOCK_DEFINE_STATIC(globalslock); static struct varshead globals = AST_LIST_HEAD_NOLOCK_INIT_VALUE; static int autofallthrough = 1; static int extenpatternmatchnew = 0; static char *overrideswitch = NULL; /*! \brief Subscription for device state change events */ static struct ast_event_sub *device_state_sub; AST_MUTEX_DEFINE_STATIC(maxcalllock); static int countcalls; static int totalcalls; static AST_RWLIST_HEAD_STATIC(acf_root, ast_custom_function); /*! \brief Declaration of builtin applications */ static struct pbx_builtin { char name[AST_MAX_APP]; int (*execute)(struct ast_channel *chan, void *data); } builtins[] = { /* These applications are built into the PBX core and do not need separate modules */ { "Answer", pbx_builtin_answer }, { "BackGround", pbx_builtin_background }, { "Busy", pbx_builtin_busy }, { "Congestion", pbx_builtin_congestion }, { "ExecIfTime", pbx_builtin_execiftime }, { "Goto", pbx_builtin_goto }, { "GotoIf", pbx_builtin_gotoif }, { "GotoIfTime", pbx_builtin_gotoiftime }, { "ImportVar", pbx_builtin_importvar }, { "Hangup", pbx_builtin_hangup }, { "Incomplete", pbx_builtin_incomplete }, { "KeepAlive", pbx_builtin_keepalive }, { "NoOp", pbx_builtin_noop }, { "Proceeding", pbx_builtin_proceeding }, { "Progress", pbx_builtin_progress }, { "RaiseException", pbx_builtin_raise_exception }, { "ResetCDR", pbx_builtin_resetcdr }, { "Ringing", pbx_builtin_ringing }, { "SayAlpha", pbx_builtin_saycharacters }, { "SayDigits", pbx_builtin_saydigits }, { "SayNumber", pbx_builtin_saynumber }, { "SayPhonetic", pbx_builtin_sayphonetic }, { "Set", pbx_builtin_setvar }, { "MSet", pbx_builtin_setvar_multiple }, { "SetAMAFlags", pbx_builtin_setamaflags }, { "Wait", pbx_builtin_wait }, { "WaitExten", pbx_builtin_waitexten } }; static struct ast_context *contexts; static struct ast_hashtab *contexts_table = NULL; AST_RWLOCK_DEFINE_STATIC(conlock); /*!< Lock for the ast_context list */ static AST_RWLIST_HEAD_STATIC(apps, ast_app); static AST_RWLIST_HEAD_STATIC(switches, ast_switch); static int stateid = 1; /* WARNING: When holding this list's lock, do _not_ do anything that will cause conlock to be taken, unless you _already_ hold it. The ast_merge_contexts_and_delete function will take the locks in conlock/hints order, so any other paths that require both locks must also take them in that order. */ static AST_RWLIST_HEAD_STATIC(hints, ast_hint); static AST_LIST_HEAD_NOLOCK_STATIC(statecbs, ast_state_cb); #ifdef CONTEXT_DEBUG /* these routines are provided for doing run-time checks on the extension structures, in case you are having problems, this routine might help you localize where the problem is occurring. It's kinda like a debug memory allocator's arena checker... It'll eat up your cpu cycles! but you'll see, if you call it in the right places, right where your problems began... */ /* you can break on the check_contexts_trouble() routine in your debugger to stop at the moment there's a problem */ void check_contexts_trouble(void); void check_contexts_trouble(void) { int x = 1; x = 2; } static struct ast_context *find_context_locked(const char *context); int check_contexts(char *, int); int check_contexts(char *file, int line ) { struct ast_hashtab_iter *t1; struct ast_context *c1, *c2; int found = 0; struct ast_exten *e1, *e2, *e3; struct ast_exten ex; /* try to find inconsistencies */ /* is every context in the context table in the context list and vice-versa ? */ if (!contexts_table) { ast_log(LOG_NOTICE,"Called from: %s:%d: No contexts_table!\n", file, line); usleep(500000); } t1 = ast_hashtab_start_traversal(contexts_table); while( (c1 = ast_hashtab_next(t1))) { for(c2=contexts;c2;c2=c2->next) { if (!strcmp(c1->name, c2->name)) { found = 1; break; } } if (!found) { ast_log(LOG_NOTICE,"Called from: %s:%d: Could not find the %s context in the linked list\n", file, line, c1->name); check_contexts_trouble(); } } ast_hashtab_end_traversal(t1); for(c2=contexts;c2;c2=c2->next) { c1 = find_context_locked(c2->name); if (!c1) { ast_log(LOG_NOTICE,"Called from: %s:%d: Could not find the %s context in the hashtab\n", file, line, c2->name); check_contexts_trouble(); } else ast_unlock_contexts(); } /* loop thru all contexts, and verify the exten structure compares to the hashtab structure */ for(c2=contexts;c2;c2=c2->next) { c1 = find_context_locked(c2->name); if (c1) { ast_unlock_contexts(); /* is every entry in the root list also in the root_table? */ for(e1 = c1->root; e1; e1=e1->next) { char dummy_name[1024]; ex.exten = dummy_name; ex.matchcid = e1->matchcid; ex.cidmatch = e1->cidmatch; ast_copy_string(dummy_name, e1->exten, sizeof(dummy_name)); e2 = ast_hashtab_lookup(c1->root_table, &ex); if (!e2) { if (e1->matchcid) { ast_log(LOG_NOTICE,"Called from: %s:%d: The %s context records the exten %s (CID match: %s) but it is not in its root_table\n", file, line, c2->name, dummy_name, e1->cidmatch ); } else { ast_log(LOG_NOTICE,"Called from: %s:%d: The %s context records the exten %s but it is not in its root_table\n", file, line, c2->name, dummy_name ); } check_contexts_trouble(); } } /* is every entry in the root_table also in the root list? */ if (!c2->root_table) { if (c2->root) { ast_log(LOG_NOTICE,"Called from: %s:%d: No c2->root_table for context %s!\n", file, line, c2->name); usleep(500000); } } else { t1 = ast_hashtab_start_traversal(c2->root_table); while( (e2 = ast_hashtab_next(t1)) ) { for(e1=c2->root;e1;e1=e1->next) { if (!strcmp(e1->exten, e2->exten)) { found = 1; break; } } if (!found) { ast_log(LOG_NOTICE,"Called from: %s:%d: The %s context records the exten %s but it is not in its root_table\n", file, line, c2->name, e2->exten); check_contexts_trouble(); } } ast_hashtab_end_traversal(t1); } } /* is every priority reflected in the peer_table at the head of the list? */ /* is every entry in the root list also in the root_table? */ /* are the per-extension peer_tables in the right place? */ for(e1 = c2->root; e1; e1 = e1->next) { for(e2=e1;e2;e2=e2->peer) { ex.priority = e2->priority; if (e2 != e1 && e2->peer_table) { ast_log(LOG_NOTICE,"Called from: %s:%d: The %s context, %s exten, %d priority has a peer_table entry, and shouldn't!\n", file, line, c2->name, e1->exten, e2->priority ); check_contexts_trouble(); } if (e2 != e1 && e2->peer_label_table) { ast_log(LOG_NOTICE,"Called from: %s:%d: The %s context, %s exten, %d priority has a peer_label_table entry, and shouldn't!\n", file, line, c2->name, e1->exten, e2->priority ); check_contexts_trouble(); } if (e2 == e1 && !e2->peer_table){ ast_log(LOG_NOTICE,"Called from: %s:%d: The %s context, %s exten, %d priority doesn't have a peer_table!\n", file, line, c2->name, e1->exten, e2->priority ); check_contexts_trouble(); } if (e2 == e1 && !e2->peer_label_table) { ast_log(LOG_NOTICE,"Called from: %s:%d: The %s context, %s exten, %d priority doesn't have a peer_label_table!\n", file, line, c2->name, e1->exten, e2->priority ); check_contexts_trouble(); } e3 = ast_hashtab_lookup(e1->peer_table, &ex); if (!e3) { ast_log(LOG_NOTICE,"Called from: %s:%d: The %s context, %s exten, %d priority is not reflected in the peer_table\n", file, line, c2->name, e1->exten, e2->priority ); check_contexts_trouble(); } } if (!e1->peer_table){ ast_log(LOG_NOTICE,"Called from: %s:%d: No e1->peer_table!\n", file, line); usleep(500000); } /* is every entry in the peer_table also in the peer list? */ t1 = ast_hashtab_start_traversal(e1->peer_table); while( (e2 = ast_hashtab_next(t1)) ) { for(e3=e1;e3;e3=e3->peer) { if (e3->priority == e2->priority) { found = 1; break; } } if (!found) { ast_log(LOG_NOTICE,"Called from: %s:%d: The %s context, %s exten, %d priority is not reflected in the peer list\n", file, line, c2->name, e1->exten, e2->priority ); check_contexts_trouble(); } } ast_hashtab_end_traversal(t1); } } return 0; } #endif /* \note This function is special. It saves the stack so that no matter how many times it is called, it returns to the same place */ int pbx_exec(struct ast_channel *c, /*!< Channel */ struct ast_app *app, /*!< Application */ void *data) /*!< Data for execution */ { int res; struct ast_module_user *u = NULL; const char *saved_c_appl; const char *saved_c_data; if (c->cdr && !ast_check_hangup(c)) ast_cdr_setapp(c->cdr, app->name, data); /* save channel values */ saved_c_appl= c->appl; saved_c_data= c->data; c->appl = app->name; c->data = data; if (app->module) u = __ast_module_user_add(app->module, c); res = app->execute(c, S_OR(data, "")); if (app->module && u) __ast_module_user_remove(app->module, u); /* restore channel values */ c->appl = saved_c_appl; c->data = saved_c_data; return res; } /*! Go no deeper than this through includes (not counting loops) */ #define AST_PBX_MAX_STACK 128 /*! \brief Find application handle in linked list */ struct ast_app *pbx_findapp(const char *app) { struct ast_app *tmp; AST_RWLIST_RDLOCK(&apps); AST_RWLIST_TRAVERSE(&apps, tmp, list) { if (!strcasecmp(tmp->name, app)) break; } AST_RWLIST_UNLOCK(&apps); return tmp; } static struct ast_switch *pbx_findswitch(const char *sw) { struct ast_switch *asw; AST_RWLIST_RDLOCK(&switches); AST_RWLIST_TRAVERSE(&switches, asw, list) { if (!strcasecmp(asw->name, sw)) break; } AST_RWLIST_UNLOCK(&switches); return asw; } static inline int include_valid(struct ast_include *i) { if (!i->hastime) return 1; return ast_check_timing(&(i->timing)); } static void pbx_destroy(struct ast_pbx *p) { ast_free(p); } /* form a tree that fully describes all the patterns in a context's extensions * in this tree, a "node" represents an individual character or character set * meant to match the corresponding character in a dial string. The tree * consists of a series of match_char structs linked in a chain * via the alt_char pointers. More than one pattern can share the same parts of the * tree as other extensions with the same pattern to that point. * My first attempt to duplicate the finding of the 'best' pattern was flawed in that * I misunderstood the general algorithm. I thought that the 'best' pattern * was the one with lowest total score. This was not true. Thus, if you have * patterns "1XXXXX" and "X11111", you would be tempted to say that "X11111" is * the "best" match because it has fewer X's, and is therefore more specific, * but this is not how the old algorithm works. It sorts matching patterns * in a similar collating sequence as sorting alphabetic strings, from left to * right. Thus, "1XXXXX" comes before "X11111", and would be the "better" match, * because "1" is more specific than "X". * So, to accomodate this philosophy, I sort the tree branches along the alt_char * line so they are lowest to highest in specificity numbers. This way, as soon * as we encounter our first complete match, we automatically have the "best" * match and can stop the traversal immediately. Same for CANMATCH/MATCHMORE. * If anyone would like to resurrect the "wrong" pattern trie searching algorithm, * they are welcome to revert pbx to before 1 Apr 2008. * As an example, consider these 4 extensions: * (a) NXXNXXXXXX * (b) 307754XXXX * (c) fax * (d) NXXXXXXXXX * * In the above, between (a) and (d), (a) is a more specific pattern than (d), and would win over * most numbers. For all numbers beginning with 307754, (b) should always win. * * These pattern should form a (sorted) tree that looks like this: * { "3" } --next--> { "0" } --next--> { "7" } --next--> { "7" } --next--> { "5" } ... blah ... --> { "X" exten_match: (b) } * | * |alt * | * { "f" } --next--> { "a" } --next--> { "x" exten_match: (c) } * { "N" } --next--> { "X" } --next--> { "X" } --next--> { "N" } --next--> { "X" } ... blah ... --> { "X" exten_match: (a) } * | | * | |alt * |alt | * | { "X" } --next--> { "X" } ... blah ... --> { "X" exten_match: (d) } * | * NULL * * In the above, I could easily turn "N" into "23456789", but I think that a quick "if( *z >= '2' && *z <= '9' )" might take * fewer CPU cycles than a call to strchr("23456789",*z), where *z is the char to match... * * traversal is pretty simple: one routine merely traverses the alt list, and for each matching char in the pattern, it calls itself * on the corresponding next pointer, incrementing also the pointer of the string to be matched, and passing the total specificity and length. * We pass a pointer to a scoreboard down through, also. * The scoreboard isn't as necessary to the revised algorithm, but I kept it as a handy way to return the matched extension. * The first complete match ends the traversal, which should make this version of the pattern matcher faster * the previous. The same goes for "CANMATCH" or "MATCHMORE"; the first such match ends the traversal. In both * these cases, the reason we can stop immediately, is because the first pattern match found will be the "best" * according to the sort criteria. * Hope the limit on stack depth won't be a problem... this routine should * be pretty lean as far a stack usage goes. Any non-match terminates the recursion down a branch. * * In the above example, with the number "3077549999" as the pattern, the traversor could match extensions a, b and d. All are * of length 10; they have total specificities of 24580, 10246, and 25090, respectively, not that this matters * at all. (b) wins purely because the first character "3" is much more specific (lower specificity) than "N". I have * left the specificity totals in the code as an artifact; at some point, I will strip it out. * * Just how much time this algorithm might save over a plain linear traversal over all possible patterns is unknown, * because it's a function of how many extensions are stored in a context. With thousands of extensions, the speedup * can be very noticeable. The new matching algorithm can run several hundreds of times faster, if not a thousand or * more times faster in extreme cases. * * MatchCID patterns are also supported, and stored in the tree just as the extension pattern is. Thus, you * can have patterns in your CID field as well. * * */ static void update_scoreboard(struct scoreboard *board, int length, int spec, struct ast_exten *exten, char last, const char *callerid, int deleted, struct match_char *node) { /* if this extension is marked as deleted, then skip this -- if it never shows on the scoreboard, it will never be found, nor will halt the traversal. */ if (deleted) return; board->total_specificity = spec; board->total_length = length; board->exten = exten; board->last_char = last; board->node = node; #ifdef NEED_DEBUG_HERE ast_log(LOG_NOTICE,"Scoreboarding (LONGER) %s, len=%d, score=%d\n", exten->exten, length, spec); #endif } void log_match_char_tree(struct match_char *node, char *prefix) { char extenstr[40]; struct ast_str *my_prefix = ast_str_alloca(1024); extenstr[0] = '\0'; if (node && node->exten && node->exten) snprintf(extenstr, sizeof(extenstr), "(%p)", node->exten); if (strlen(node->x) > 1) { ast_debug(1, "%s[%s]:%c:%c:%d:%s%s%s\n", prefix, node->x, node->is_pattern ? 'Y':'N', node->deleted? 'D':'-', node->specificity, node->exten? "EXTEN:":"", node->exten ? node->exten->exten : "", extenstr); } else { ast_debug(1, "%s%s:%c:%c:%d:%s%s%s\n", prefix, node->x, node->is_pattern ? 'Y':'N', node->deleted? 'D':'-', node->specificity, node->exten? "EXTEN:":"", node->exten ? node->exten->exten : "", extenstr); } ast_str_set(&my_prefix, 0, "%s+ ", prefix); if (node->next_char) log_match_char_tree(node->next_char, my_prefix->str); if (node->alt_char) log_match_char_tree(node->alt_char, prefix); } static void cli_match_char_tree(struct match_char *node, char *prefix, int fd) { char extenstr[40]; struct ast_str *my_prefix = ast_str_alloca(1024); extenstr[0] = '\0'; if (node && node->exten && node->exten) snprintf(extenstr, sizeof(extenstr), "(%p)", node->exten); if (strlen(node->x) > 1) { ast_cli(fd, "%s[%s]:%c:%c:%d:%s%s%s\n", prefix, node->x, node->is_pattern ? 'Y' : 'N', node->deleted ? 'D' : '-', node->specificity, node->exten? "EXTEN:" : "", node->exten ? node->exten->exten : "", extenstr); } else { ast_cli(fd, "%s%s:%c:%c:%d:%s%s%s\n", prefix, node->x, node->is_pattern ? 'Y' : 'N', node->deleted ? 'D' : '-', node->specificity, node->exten? "EXTEN:" : "", node->exten ? node->exten->exten : "", extenstr); } ast_str_set(&my_prefix, 0, "%s+ ", prefix); if (node->next_char) cli_match_char_tree(node->next_char, my_prefix->str, fd); if (node->alt_char) cli_match_char_tree(node->alt_char, prefix, fd); } static struct ast_exten *get_canmatch_exten(struct match_char *node) { /* find the exten at the end of the rope */ struct match_char *node2 = node; for (node2 = node; node2; node2 = node2->next_char) { if (node2->exten) { #ifdef NEED_DEBUG_HERE ast_log(LOG_NOTICE,"CanMatch_exten returns exten %s(%p)\n", node2->exten->exten, node2->exten); #endif return node2->exten; } } #ifdef NEED_DEBUG_HERE ast_log(LOG_NOTICE,"CanMatch_exten returns NULL, match_char=%s\n", node->x); #endif return 0; } static struct ast_exten *trie_find_next_match(struct match_char *node) { struct match_char *m3; struct match_char *m4; struct ast_exten *e3; if (node && node->x[0] == '.' && !node->x[1]) /* dot and ! will ALWAYS be next match in a matchmore */ return node->exten; if (node && node->x[0] == '!' && !node->x[1]) return node->exten; if (!node || !node->next_char) return NULL; m3 = node->next_char; if (m3->exten) return m3->exten; for(m4=m3->alt_char; m4; m4 = m4->alt_char) { if (m4->exten) return m4->exten; } for(m4=m3; m4; m4 = m4->alt_char) { e3 = trie_find_next_match(m3); if (e3) return e3; } return NULL; } #ifdef DEBUG_THIS static char *action2str(enum ext_match_t action) { switch(action) { case E_MATCH: return "MATCH"; case E_CANMATCH: return "CANMATCH"; case E_MATCHMORE: return "MATCHMORE"; case E_FINDLABEL: return "FINDLABEL"; case E_SPAWN: return "SPAWN"; default: return "?ACTION?"; } } #endif static void new_find_extension(const char *str, struct scoreboard *score, struct match_char *tree, int length, int spec, const char *label, const char *callerid, enum ext_match_t action) { struct match_char *p; /* note minimal stack storage requirements */ struct ast_exten pattern = { .label = label }; #ifdef DEBUG_THIS if (tree) ast_log(LOG_NOTICE,"new_find_extension called with %s on (sub)tree %s action=%s\n", str, tree->x, action2str(action)); else ast_log(LOG_NOTICE,"new_find_extension called with %s on (sub)tree NULL action=%s\n", str, action2str(action)); #endif for (p=tree; p; p=p->alt_char) { if (p->x[0] == 'N') { if (p->x[1] == 0 && *str >= '2' && *str <= '9' ) { #define NEW_MATCHER_CHK_MATCH \ if (p->exten && !(*(str+1))) { /* if a shorter pattern matches along the way, might as well report it */ \ if (action == E_MATCH || action == E_SPAWN || action == E_FINDLABEL) { /* if in CANMATCH/MATCHMORE, don't let matches get in the way */ \ update_scoreboard(score, length+1, spec+p->specificity, p->exten,0,callerid, p->deleted, p); \ if (!p->deleted) { \ if (action == E_FINDLABEL) { \ if (ast_hashtab_lookup(score->exten->peer_label_table, &pattern)) { \ ast_debug(4, "Found label in preferred extension\n"); \ return; \ } \ } else { \ ast_debug(4,"returning an exact match-- first found-- %s\n", p->exten->exten); \ return; /* the first match, by definition, will be the best, because of the sorted tree */ \ } \ } \ } \ } #define NEW_MATCHER_RECURSE \ if (p->next_char && ( *(str+1) || (p->next_char->x[0] == '/' && p->next_char->x[1] == 0) \ || p->next_char->x[0] == '!')) { \ if (*(str+1) || p->next_char->x[0] == '!') { \ new_find_extension(str+1, score, p->next_char, length+1, spec+p->specificity, callerid, label, action); \ if (score->exten) { \ ast_debug(4,"returning an exact match-- %s\n", score->exten->exten); \ return; /* the first match is all we need */ \ } \ } else { \ new_find_extension("/", score, p->next_char, length+1, spec+p->specificity, callerid, label, action); \ if (score->exten || ((action == E_CANMATCH || action == E_MATCHMORE) && score->canmatch)) { \ ast_debug(4,"returning a (can/more) match--- %s\n", score->exten ? score->exten->exten : \ "NULL"); \ return; /* the first match is all we need */ \ } \ } \ } else if (p->next_char && !*(str+1)) { \ score->canmatch = 1; \ score->canmatch_exten = get_canmatch_exten(p); \ if (action == E_CANMATCH || action == E_MATCHMORE) { \ ast_debug(4,"returning a canmatch/matchmore--- str=%s\n", str); \ return; \ } \ } NEW_MATCHER_CHK_MATCH; NEW_MATCHER_RECURSE; } } else if (p->x[0] == 'Z') { if (p->x[1] == 0 && *str >= '1' && *str <= '9' ) { NEW_MATCHER_CHK_MATCH; NEW_MATCHER_RECURSE; } } else if (p->x[0] == 'X') { if (p->x[1] == 0 && *str >= '0' && *str <= '9' ) { NEW_MATCHER_CHK_MATCH; NEW_MATCHER_RECURSE; } } else if (p->x[0] == '.' && p->x[1] == 0) { /* how many chars will the . match against? */ int i = 0; const char *str2 = str; while (*str2 && *str2 != '/') { str2++; i++; } if (p->exten && *str2 != '/') { update_scoreboard(score, length+i, spec+(i*p->specificity), p->exten, '.', callerid, p->deleted, p); if (score->exten) { ast_debug(4,"return because scoreboard has a match with '/'--- %s\n", score->exten->exten); return; /* the first match is all we need */ } } if (p->next_char && p->next_char->x[0] == '/' && p->next_char->x[1] == 0) { new_find_extension("/", score, p->next_char, length+i, spec+(p->specificity*i), callerid, label, action); if (score->exten || ((action == E_CANMATCH || action == E_MATCHMORE) && score->canmatch)) { ast_debug(4,"return because scoreboard has exact match OR CANMATCH/MATCHMORE & canmatch set--- %s\n", score->exten ? score->exten->exten : "NULL"); return; /* the first match is all we need */ } } } else if (p->x[0] == '!' && p->x[1] == 0) { /* how many chars will the . match against? */ int i = 1; const char *str2 = str; while (*str2 && *str2 != '/') { str2++; i++; } if (p->exten && *str2 != '/') { update_scoreboard(score, length+1, spec+(p->specificity*i), p->exten, '!', callerid, p->deleted, p); if (score->exten) { ast_debug(4,"return because scoreboard has a '!' match--- %s\n", score->exten->exten); return; /* the first match is all we need */ } } if (p->next_char && p->next_char->x[0] == '/' && p->next_char->x[1] == 0) { new_find_extension("/", score, p->next_char, length+i, spec+(p->specificity*i), callerid, label, action); if (score->exten || ((action == E_CANMATCH || action == E_MATCHMORE) && score->canmatch)) { ast_debug(4,"return because scoreboard has exact match OR CANMATCH/MATCHMORE & canmatch set with '/' and '!'--- %s\n", score->exten ? score->exten->exten : "NULL"); return; /* the first match is all we need */ } } } else if (p->x[0] == '/' && p->x[1] == 0) { /* the pattern in the tree includes the cid match! */ if (p->next_char && callerid && *callerid) { new_find_extension(callerid, score, p->next_char, length+1, spec, callerid, label, action); if (score->exten || ((action == E_CANMATCH || action == E_MATCHMORE) && score->canmatch)) { ast_debug(4,"return because scoreboard has exact match OR CANMATCH/MATCHMORE & canmatch set with '/'--- %s\n", score->exten ? score->exten->exten : "NULL"); return; /* the first match is all we need */ } } } else if (strchr(p->x, *str)) { ast_debug(4, "Nothing strange about this match\n"); NEW_MATCHER_CHK_MATCH; NEW_MATCHER_RECURSE; } } ast_debug(4,"return at end of func\n"); } /* the algorithm for forming the extension pattern tree is also a bit simple; you * traverse all the extensions in a context, and for each char of the extension, * you see if it exists in the tree; if it doesn't, you add it at the appropriate * spot. What more can I say? At the end of each exten, you cap it off by adding the * address of the extension involved. Duplicate patterns will be complained about. * * Ideally, this would be done for each context after it is created and fully * filled. It could be done as a finishing step after extensions.conf or .ael is * loaded, or it could be done when the first search is encountered. It should only * have to be done once, until the next unload or reload. * * I guess forming this pattern tree would be analogous to compiling a regex. Except * that a regex only handles 1 pattern, really. This trie holds any number * of patterns. Well, really, it **could** be considered a single pattern, * where the "|" (or) operator is allowed, I guess, in a way, sort of... */ static struct match_char *already_in_tree(struct match_char *current, char *pat) { struct match_char *t; if (!current) return 0; for (t = current; t; t = t->alt_char) { if (!strcmp(pat, t->x)) /* uh, we may want to sort exploded [] contents to make matching easy */ return t; } return 0; } /* The first arg is the location of the tree ptr, or the address of the next_char ptr in the node, so we can mess with it, if we need to insert at the beginning of the list */ static void insert_in_next_chars_alt_char_list(struct match_char **parent_ptr, struct match_char *node) { struct match_char *curr, *lcurr; /* insert node into the tree at "current", so the alt_char list from current is sorted in increasing value as you go to the leaves */ if (!(*parent_ptr)) { *parent_ptr = node; } else { if ((*parent_ptr)->specificity > node->specificity){ /* insert at head */ node->alt_char = (*parent_ptr); *parent_ptr = node; } else { lcurr = *parent_ptr; for (curr=(*parent_ptr)->alt_char; curr; curr = curr->alt_char) { if (curr->specificity > node->specificity) { node->alt_char = curr; lcurr->alt_char = node; break; } lcurr = curr; } if (!curr) { lcurr->alt_char = node; } } } } static struct match_char *add_pattern_node(struct ast_context *con, struct match_char *current, char *pattern, int is_pattern, int already, int specificity, struct match_char **nextcharptr) { struct match_char *m; if (!(m = ast_calloc(1, sizeof(*m)))) return NULL; if (!(m->x = ast_strdup(pattern))) { ast_free(m); return NULL; } /* the specificity scores are the same as used in the old pattern matcher. */ m->is_pattern = is_pattern; if (specificity == 1 && is_pattern && pattern[0] == 'N') m->specificity = 0x0802; else if (specificity == 1 && is_pattern && pattern[0] == 'Z') m->specificity = 0x0901; else if (specificity == 1 && is_pattern && pattern[0] == 'X') m->specificity = 0x0a00; else if (specificity == 1 && is_pattern && pattern[0] == '.') m->specificity = 0x10000; else if (specificity == 1 && is_pattern && pattern[0] == '!') m->specificity = 0x20000; else m->specificity = specificity; if (!con->pattern_tree) { insert_in_next_chars_alt_char_list(&con->pattern_tree, m); } else { if (already) { /* switch to the new regime (traversing vs appending)*/ insert_in_next_chars_alt_char_list(nextcharptr, m); } else { insert_in_next_chars_alt_char_list(¤t->next_char, m); } } return m; } static struct match_char *add_exten_to_pattern_tree(struct ast_context *con, struct ast_exten *e1, int findonly) { struct match_char *m1 = NULL, *m2 = NULL, **m0; int specif; int already; int pattern = 0; char buf[256]; char extenbuf[512]; char *s1 = extenbuf; int l1 = strlen(e1->exten) + strlen(e1->cidmatch) + 2; strncpy(extenbuf,e1->exten,sizeof(extenbuf)); if (e1->matchcid && l1 <= sizeof(extenbuf)) { strcat(extenbuf,"/"); strcat(extenbuf,e1->cidmatch); } else if (l1 > sizeof(extenbuf)) { ast_log(LOG_ERROR,"The pattern %s/%s is too big to deal with: it will be ignored! Disaster!\n", e1->exten, e1->cidmatch); return 0; } #ifdef NEED_DEBUG ast_log(LOG_DEBUG,"Adding exten %s%c%s to tree\n", s1, e1->matchcid? '/':' ', e1->matchcid? e1->cidmatch : ""); #endif m1 = con->pattern_tree; /* each pattern starts over at the root of the pattern tree */ m0 = &con->pattern_tree; already = 1; if ( *s1 == '_') { pattern = 1; s1++; } while( *s1 ) { if (pattern && *s1 == '[' && *(s1-1) != '\\') { char *s2 = buf; buf[0] = 0; s1++; /* get past the '[' */ while (*s1 != ']' && *(s1-1) != '\\' ) { if (*s1 == '\\') { if (*(s1+1) == ']') { *s2++ = ']'; s1++;s1++; } else if (*(s1+1) == '\\') { *s2++ = '\\'; s1++;s1++; } else if (*(s1+1) == '-') { *s2++ = '-'; s1++; s1++; } else if (*(s1+1) == '[') { *s2++ = '['; s1++; s1++; } } else if (*s1 == '-') { /* remember to add some error checking to all this! */ char s3 = *(s1-1); char s4 = *(s1+1); for (s3++; s3 <= s4; s3++) { *s2++ = s3; } s1++; s1++; } else { *s2++ = *s1++; } } *s2 = 0; /* null terminate the exploded range */ /* sort the characters */ specif = strlen(buf); qsort(buf, specif, 1, compare_char); specif <<= 8; specif += buf[0]; } else { if (*s1 == '\\') { s1++; buf[0] = *s1; } else { if (pattern) { if (*s1 == 'n') /* make sure n,x,z patterns are canonicalized to N,X,Z */ *s1 = 'N'; else if (*s1 == 'x') *s1 = 'X'; else if (*s1 == 'z') *s1 = 'Z'; } buf[0] = *s1; } buf[1] = 0; specif = 1; } m2 = 0; if (already && (m2=already_in_tree(m1,buf)) && m2->next_char) { if (!(*(s1+1))) { /* if this is the end of the pattern, but not the end of the tree, then mark this node with the exten... a shorter pattern might win if the longer one doesn't match */ m2->exten = e1; m2->deleted = 0; } m1 = m2->next_char; /* m1 points to the node to compare against */ m0 = &m2->next_char; /* m0 points to the ptr that points to m1 */ } else { /* not already OR not m2 OR nor m2->next_char */ if (m2) { if (findonly) return m2; m1 = m2; /* while m0 stays the same */ } else { if (findonly) return m1; m1 = add_pattern_node(con, m1, buf, pattern, already,specif, m0); /* m1 is the node just added */ m0 = &m1->next_char; } if (!(*(s1+1))) { m1->deleted = 0; m1->exten = e1; } already = 0; } s1++; /* advance to next char */ } return m1; } static void create_match_char_tree(struct ast_context *con) { struct ast_hashtab_iter *t1; struct ast_exten *e1; #ifdef NEED_DEBUG int biggest_bucket, resizes, numobjs, numbucks; ast_log(LOG_DEBUG,"Creating Extension Trie for context %s\n", con->name); ast_hashtab_get_stats(con->root_table, &biggest_bucket, &resizes, &numobjs, &numbucks); ast_log(LOG_DEBUG,"This tree has %d objects in %d bucket lists, longest list=%d objects, and has resized %d times\n", numobjs, numbucks, biggest_bucket, resizes); #endif t1 = ast_hashtab_start_traversal(con->root_table); while( (e1 = ast_hashtab_next(t1)) ) { if (e1->exten) add_exten_to_pattern_tree(con, e1, 0); else ast_log(LOG_ERROR,"Attempt to create extension with no extension name.\n"); } ast_hashtab_end_traversal(t1); } static void destroy_pattern_tree(struct match_char *pattern_tree) /* pattern tree is a simple binary tree, sort of, so the proper way to destroy it is... recursively! */ { /* destroy all the alternates */ if (pattern_tree->alt_char) { destroy_pattern_tree(pattern_tree->alt_char); pattern_tree->alt_char = 0; } /* destroy all the nexts */ if (pattern_tree->next_char) { destroy_pattern_tree(pattern_tree->next_char); pattern_tree->next_char = 0; } pattern_tree->exten = 0; /* never hurts to make sure there's no pointers laying around */ if (pattern_tree->x) free(pattern_tree->x); free(pattern_tree); } /* * Special characters used in patterns: * '_' underscore is the leading character of a pattern. * In other position it is treated as a regular char. * ' ' '-' space and '-' are separator and ignored. * . one or more of any character. Only allowed at the end of * a pattern. * ! zero or more of anything. Also impacts the result of CANMATCH * and MATCHMORE. Only allowed at the end of a pattern. * In the core routine, ! causes a match with a return code of 2. * In turn, depending on the search mode: (XXX check if it is implemented) * - E_MATCH retuns 1 (does match) * - E_MATCHMORE returns 0 (no match) * - E_CANMATCH returns 1 (does match) * * / should not appear as it is considered the separator of the CID info. * XXX at the moment we may stop on this char. * * X Z N match ranges 0-9, 1-9, 2-9 respectively. * [ denotes the start of a set of character. Everything inside * is considered literally. We can have ranges a-d and individual * characters. A '[' and '-' can be considered literally if they * are just before ']'. * XXX currently there is no way to specify ']' in a range, nor \ is * considered specially. * * When we compare a pattern with a specific extension, all characters in the extension * itself are considered literally with the only exception of '-' which is considered * as a separator and thus ignored. * XXX do we want to consider space as a separator as well ? * XXX do we want to consider the separators in non-patterns as well ? */ /*! * \brief helper functions to sort extensions and patterns in the desired way, * so that more specific patterns appear first. * * ext_cmp1 compares individual characters (or sets of), returning * an int where bits 0-7 are the ASCII code of the first char in the set, * while bit 8-15 are the cardinality of the set minus 1. * This way more specific patterns (smaller cardinality) appear first. * Wildcards have a special value, so that we can directly compare them to * sets by subtracting the two values. In particular: * 0x000xx one character, xx * 0x0yyxx yy character set starting with xx * 0x10000 '.' (one or more of anything) * 0x20000 '!' (zero or more of anything) * 0x30000 NUL (end of string) * 0x40000 error in set. * The pointer to the string is advanced according to needs. * NOTES: * 1. the empty set is equivalent to NUL. * 2. given that a full set has always 0 as the first element, * we could encode the special cases as 0xffXX where XX * is 1, 2, 3, 4 as used above. */ static int ext_cmp1(const char **p) { uint32_t chars[8]; int c, cmin = 0xff, count = 0; const char *end; /* load, sign extend and advance pointer until we find * a valid character. */ while ( (c = *(*p)++) && (c == ' ' || c == '-') ) ; /* ignore some characters */ /* always return unless we have a set of chars */ switch (toupper(c)) { default: /* ordinary character */ return 0x0000 | (c & 0xff); case 'N': /* 2..9 */ return 0x0700 | '2' ; case 'X': /* 0..9 */ return 0x0900 | '0'; case 'Z': /* 1..9 */ return 0x0800 | '1'; case '.': /* wildcard */ return 0x10000; case '!': /* earlymatch */ return 0x20000; /* less specific than NULL */ case '\0': /* empty string */ *p = NULL; return 0x30000; case '[': /* pattern */ break; } /* locate end of set */ end = strchr(*p, ']'); if (end == NULL) { ast_log(LOG_WARNING, "Wrong usage of [] in the extension\n"); return 0x40000; /* XXX make this entry go last... */ } memset(chars, '\0', sizeof(chars)); /* clear all chars in the set */ for (; *p < end ; (*p)++) { unsigned char c1, c2; /* first-last char in range */ c1 = (unsigned char)((*p)[0]); if (*p + 2 < end && (*p)[1] == '-') { /* this is a range */ c2 = (unsigned char)((*p)[2]); *p += 2; /* skip a total of 3 chars */ } else /* individual character */ c2 = c1; if (c1 < cmin) cmin = c1; for (; c1 <= c2; c1++) { uint32_t mask = 1 << (c1 % 32); if ( (chars[ c1 / 32 ] & mask) == 0) count += 0x100; chars[ c1 / 32 ] |= mask; } } (*p)++; return count == 0 ? 0x30000 : (count | cmin); } /*! * \brief the full routine to compare extensions in rules. */ static int ext_cmp(const char *a, const char *b) { /* make sure non-patterns come first. * If a is not a pattern, it either comes first or * we use strcmp to compare the strings. */ int ret = 0; if (a[0] != '_') return (b[0] == '_') ? -1 : strcmp(a, b); /* Now we know a is a pattern; if b is not, a comes first */ if (b[0] != '_') return 1; #if 0 /* old mode for ext matching */ return strcmp(a, b); #endif /* ok we need full pattern sorting routine */ while (!ret && a && b) ret = ext_cmp1(&a) - ext_cmp1(&b); if (ret == 0) return 0; else return (ret > 0) ? 1 : -1; } int ast_extension_cmp(const char *a, const char *b) { return ext_cmp(a, b); } /*! * \internal * \brief used ast_extension_{match|close} * mode is as follows: * E_MATCH success only on exact match * E_MATCHMORE success only on partial match (i.e. leftover digits in pattern) * E_CANMATCH either of the above. * \retval 0 on no-match * \retval 1 on match * \retval 2 on early match. */ static int _extension_match_core(const char *pattern, const char *data, enum ext_match_t mode) { mode &= E_MATCH_MASK; /* only consider the relevant bits */ #ifdef NEED_DEBUG_HERE ast_log(LOG_NOTICE,"match core: pat: '%s', dat: '%s', mode=%d\n", pattern, data, (int)mode); #endif if ( (mode == E_MATCH) && (pattern[0] == '_') && (!strcasecmp(pattern,data)) ) { /* note: if this test is left out, then _x. will not match _x. !!! */ #ifdef NEED_DEBUG_HERE ast_log(LOG_NOTICE,"return (1) - pattern matches pattern\n"); #endif return 1; } if (pattern[0] != '_') { /* not a pattern, try exact or partial match */ int ld = strlen(data), lp = strlen(pattern); if (lp < ld) { /* pattern too short, cannot match */ #ifdef NEED_DEBUG_HERE ast_log(LOG_NOTICE,"return (0) - pattern too short, cannot match\n"); #endif return 0; } /* depending on the mode, accept full or partial match or both */ if (mode == E_MATCH) { #ifdef NEED_DEBUG_HERE ast_log(LOG_NOTICE,"return (!strcmp(%s,%s) when mode== E_MATCH)\n", pattern, data); #endif return !strcmp(pattern, data); /* 1 on match, 0 on fail */ } if (ld == 0 || !strncasecmp(pattern, data, ld)) { /* partial or full match */ #ifdef NEED_DEBUG_HERE ast_log(LOG_NOTICE,"return (mode(%d) == E_MATCHMORE ? lp(%d) > ld(%d) : 1)\n", mode, lp, ld); #endif return (mode == E_MATCHMORE) ? lp > ld : 1; /* XXX should consider '!' and '/' ? */ } else { #ifdef NEED_DEBUG_HERE ast_log(LOG_NOTICE,"return (0) when ld(%d) > 0 && pattern(%s) != data(%s)\n", ld, pattern, data); #endif return 0; } } pattern++; /* skip leading _ */ /* * XXX below we stop at '/' which is a separator for the CID info. However we should * not store '/' in the pattern at all. When we insure it, we can remove the checks. */ while (*data && *pattern && *pattern != '/') { const char *end; if (*data == '-') { /* skip '-' in data (just a separator) */ data++; continue; } switch (toupper(*pattern)) { case '[': /* a range */ end = strchr(pattern+1, ']'); /* XXX should deal with escapes ? */ if (end == NULL) { ast_log(LOG_WARNING, "Wrong usage of [] in the extension\n"); return 0; /* unconditional failure */ } for (pattern++; pattern != end; pattern++) { if (pattern+2 < end && pattern[1] == '-') { /* this is a range */ if (*data >= pattern[0] && *data <= pattern[2]) break; /* match found */ else { pattern += 2; /* skip a total of 3 chars */ continue; } } else if (*data == pattern[0]) break; /* match found */ } if (pattern == end) { #ifdef NEED_DEBUG_HERE ast_log(LOG_NOTICE,"return (0) when pattern==end\n"); #endif return 0; } pattern = end; /* skip and continue */ break; case 'N': if (*data < '2' || *data > '9') { #ifdef NEED_DEBUG_HERE ast_log(LOG_NOTICE,"return (0) N is matched\n"); #endif return 0; } break; case 'X': if (*data < '0' || *data > '9') { #ifdef NEED_DEBUG_HERE ast_log(LOG_NOTICE,"return (0) X is matched\n"); #endif return 0; } break; case 'Z': if (*data < '1' || *data > '9') { #ifdef NEED_DEBUG_HERE ast_log(LOG_NOTICE,"return (0) Z is matched\n"); #endif return 0; } break; case '.': /* Must match, even with more digits */ #ifdef NEED_DEBUG_HERE ast_log(LOG_NOTICE,"return (1) when '.' is matched\n"); #endif return 1; case '!': /* Early match */ #ifdef NEED_DEBUG_HERE ast_log(LOG_NOTICE,"return (2) when '!' is matched\n"); #endif return 2; case ' ': case '-': /* Ignore these in patterns */ data--; /* compensate the final data++ */ break; default: if (*data != *pattern) { #ifdef NEED_DEBUG_HERE ast_log(LOG_NOTICE,"return (0) when *data(%c) != *pattern(%c)\n", *data, *pattern); #endif return 0; } } data++; pattern++; } if (*data) /* data longer than pattern, no match */ { #ifdef NEED_DEBUG_HERE ast_log(LOG_NOTICE,"return (0) when data longer than pattern\n"); #endif return 0; } /* * match so far, but ran off the end of the data. * Depending on what is next, determine match or not. */ if (*pattern == '\0' || *pattern == '/') { /* exact match */ #ifdef NEED_DEBUG_HERE ast_log(LOG_NOTICE,"at end, return (%d) in 'exact match'\n", (mode==E_MATCHMORE) ? 0 : 1); #endif return (mode == E_MATCHMORE) ? 0 : 1; /* this is a failure for E_MATCHMORE */ } else if (*pattern == '!') { /* early match */ #ifdef NEED_DEBUG_HERE ast_log(LOG_NOTICE,"at end, return (2) when '!' is matched\n"); #endif return 2; } else { /* partial match */ #ifdef NEED_DEBUG_HERE ast_log(LOG_NOTICE,"at end, return (%d) which deps on E_MATCH\n", (mode == E_MATCH) ? 0 : 1); #endif return (mode == E_MATCH) ? 0 : 1; /* this is a failure for E_MATCH */ } } /* * Wrapper around _extension_match_core() to do performance measurement * using the profiling code. */ static int extension_match_core(const char *pattern, const char *data, enum ext_match_t mode) { int i; static int prof_id = -2; /* marker for 'unallocated' id */ if (prof_id == -2) prof_id = ast_add_profile("ext_match", 0); ast_mark(prof_id, 1); i = _extension_match_core(pattern, data, mode); ast_mark(prof_id, 0); return i; } int ast_extension_match(const char *pattern, const char *data) { return extension_match_core(pattern, data, E_MATCH); } int ast_extension_close(const char *pattern, const char *data, int needmore) { if (needmore != E_MATCHMORE && needmore != E_CANMATCH) ast_log(LOG_WARNING, "invalid argument %d\n", needmore); return extension_match_core(pattern, data, needmore); } struct fake_context /* this struct is purely for matching in the hashtab */ { ast_rwlock_t lock; struct ast_exten *root; struct ast_hashtab *root_table; struct match_char *pattern_tree; struct ast_context *next; struct ast_include *includes; struct ast_ignorepat *ignorepats; const char *registrar; int refcount; AST_LIST_HEAD_NOLOCK(, ast_sw) alts; ast_mutex_t macrolock; char name[256]; }; struct ast_context *ast_context_find(const char *name) { struct ast_context *tmp = NULL; struct fake_context item; strncpy(item.name,name,256); ast_rdlock_contexts(); if( contexts_table ) { tmp = ast_hashtab_lookup(contexts_table,&item); } else { while ( (tmp = ast_walk_contexts(tmp)) ) { if (!name || !strcasecmp(name, tmp->name)) break; } } ast_unlock_contexts(); return tmp; } #define STATUS_NO_CONTEXT 1 #define STATUS_NO_EXTENSION 2 #define STATUS_NO_PRIORITY 3 #define STATUS_NO_LABEL 4 #define STATUS_SUCCESS 5 static int matchcid(const char *cidpattern, const char *callerid) { /* If the Caller*ID pattern is empty, then we're matching NO Caller*ID, so failing to get a number should count as a match, otherwise not */ if (ast_strlen_zero(callerid)) return ast_strlen_zero(cidpattern) ? 1 : 0; return ast_extension_match(cidpattern, callerid); } struct ast_exten *pbx_find_extension(struct ast_channel *chan, struct ast_context *bypass, struct pbx_find_info *q, const char *context, const char *exten, int priority, const char *label, const char *callerid, enum ext_match_t action) { int x, res; struct ast_context *tmp = NULL; struct ast_exten *e = NULL, *eroot = NULL; struct ast_include *i = NULL; struct ast_sw *sw = NULL; struct ast_exten pattern = {NULL, }; struct scoreboard score = {0, }; struct ast_str *tmpdata = NULL; pattern.label = label; pattern.priority = priority; #ifdef NEED_DEBUG_HERE ast_log(LOG_NOTICE,"Looking for cont/ext/prio/label/action = %s/%s/%d/%s/%d\n", context, exten, priority, label, (int)action); #endif if (ast_strlen_zero(exten)) return NULL; /* Initialize status if appropriate */ if (q->stacklen == 0) { q->status = STATUS_NO_CONTEXT; q->swo = NULL; q->data = NULL; q->foundcontext = NULL; } else if (q->stacklen >= AST_PBX_MAX_STACK) { ast_log(LOG_WARNING, "Maximum PBX stack exceeded\n"); return NULL; } /* Check first to see if we've already been checked */ for (x = 0; x < q->stacklen; x++) { if (!strcasecmp(q->incstack[x], context)) return NULL; } if (bypass) /* bypass means we only look there */ tmp = bypass; else { /* look in contexts */ struct fake_context item; strncpy(item.name,context,256); tmp = ast_hashtab_lookup(contexts_table,&item); #ifdef NOTNOW tmp = NULL; while ((tmp = ast_walk_contexts(tmp)) ) { if (!strcmp(tmp->name, context)) break; } #endif if (!tmp) return NULL; } if (q->status < STATUS_NO_EXTENSION) q->status = STATUS_NO_EXTENSION; /* Do a search for matching extension */ eroot = NULL; score.total_specificity = 0; score.exten = 0; score.total_length = 0; if (!tmp->pattern_tree && tmp->root_table) { create_match_char_tree(tmp); #ifdef NEED_DEBUG ast_log(LOG_DEBUG,"Tree Created in context %s:\n", context); log_match_char_tree(tmp->pattern_tree," "); #endif } #ifdef NEED_DEBUG ast_log(LOG_NOTICE,"The Trie we are searching in:\n"); log_match_char_tree(tmp->pattern_tree, ":: "); #endif do { if (!ast_strlen_zero(overrideswitch)) { char *osw = ast_strdupa(overrideswitch), *name; struct ast_switch *asw; ast_switch_f *aswf = NULL; char *datap; int eval = 0; name = strsep(&osw, "/"); asw = pbx_findswitch(name); if (!asw) { ast_log(LOG_WARNING, "No such switch '%s'\n", name); break; } if (osw && strchr(osw, '$')) { eval = 1; } if (eval && !(tmpdata = ast_str_thread_get(&switch_data, 512))) { ast_log(LOG_WARNING, "Can't evaluate overrideswitch?!"); break; } else if (eval) { /* Substitute variables now */ pbx_substitute_variables_helper(chan, osw, tmpdata->str, tmpdata->len); datap = tmpdata->str; } else { datap = osw; } /* equivalent of extension_match_core() at the switch level */ if (action == E_CANMATCH) aswf = asw->canmatch; else if (action == E_MATCHMORE) aswf = asw->matchmore; else /* action == E_MATCH */ aswf = asw->exists; if (!aswf) { res = 0; } else { if (chan) { ast_autoservice_start(chan); } res = aswf(chan, context, exten, priority, callerid, datap); if (chan) { ast_autoservice_stop(chan); } } if (res) { /* Got a match */ q->swo = asw; q->data = datap; q->foundcontext = context; /* XXX keep status = STATUS_NO_CONTEXT ? */ return NULL; } } } while (0); if (extenpatternmatchnew) { new_find_extension(exten, &score, tmp->pattern_tree, 0, 0, callerid, label, action); eroot = score.exten; if (score.last_char == '!' && action == E_MATCHMORE) { /* We match an extension ending in '!'. * The decision in this case is final and is NULL (no match). */ #ifdef NEED_DEBUG_HERE ast_log(LOG_NOTICE,"Returning MATCHMORE NULL with exclamation point.\n"); #endif return NULL; } if (!eroot && (action == E_CANMATCH || action == E_MATCHMORE) && score.canmatch_exten) { q->status = STATUS_SUCCESS; #ifdef NEED_DEBUG_HERE ast_log(LOG_NOTICE,"Returning CANMATCH exten %s\n", score.canmatch_exten->exten); #endif return score.canmatch_exten; } if ((action == E_MATCHMORE || action == E_CANMATCH) && eroot) { if (score.node) { struct ast_exten *z = trie_find_next_match(score.node); if (z) { #ifdef NEED_DEBUG_HERE ast_log(LOG_NOTICE,"Returning CANMATCH/MATCHMORE next_match exten %s\n", z->exten); #endif } else { if (score.canmatch_exten) { #ifdef NEED_DEBUG_HERE ast_log(LOG_NOTICE,"Returning CANMATCH/MATCHMORE canmatchmatch exten %s(%p)\n", score.canmatch_exten->exten, score.canmatch_exten); #endif return score.canmatch_exten; } else { #ifdef NEED_DEBUG_HERE ast_log(LOG_NOTICE,"Returning CANMATCH/MATCHMORE next_match exten NULL\n"); #endif } } return z; } #ifdef NEED_DEBUG_HERE ast_log(LOG_NOTICE,"Returning CANMATCH/MATCHMORE NULL (no next_match)\n"); #endif return NULL; /* according to the code, complete matches are null matches in MATCHMORE mode */ } if (eroot) { /* found entry, now look for the right priority */ if (q->status < STATUS_NO_PRIORITY) q->status = STATUS_NO_PRIORITY; e = NULL; if (action == E_FINDLABEL && label ) { if (q->status < STATUS_NO_LABEL) q->status = STATUS_NO_LABEL; e = ast_hashtab_lookup(eroot->peer_label_table, &pattern); } else { e = ast_hashtab_lookup(eroot->peer_table, &pattern); } if (e) { /* found a valid match */ q->status = STATUS_SUCCESS; q->foundcontext = context; #ifdef NEED_DEBUG_HERE ast_log(LOG_NOTICE,"Returning complete match of exten %s\n", e->exten); #endif return e; } } } else { /* the old/current default exten pattern match algorithm */ /* scan the list trying to match extension and CID */ eroot = NULL; while ( (eroot = ast_walk_context_extensions(tmp, eroot)) ) { int match = extension_match_core(eroot->exten, exten, action); /* 0 on fail, 1 on match, 2 on earlymatch */ if (!match || (eroot->matchcid && !matchcid(eroot->cidmatch, callerid))) continue; /* keep trying */ if (match == 2 && action == E_MATCHMORE) { /* We match an extension ending in '!'. * The decision in this case is final and is NULL (no match). */ return NULL; } /* found entry, now look for the right priority */ if (q->status < STATUS_NO_PRIORITY) q->status = STATUS_NO_PRIORITY; e = NULL; if (action == E_FINDLABEL && label ) { if (q->status < STATUS_NO_LABEL) q->status = STATUS_NO_LABEL; e = ast_hashtab_lookup(eroot->peer_label_table, &pattern); } else { e = ast_hashtab_lookup(eroot->peer_table, &pattern); } #ifdef NOTNOW while ( (e = ast_walk_extension_priorities(eroot, e)) ) { /* Match label or priority */ if (action == E_FINDLABEL) { if (q->status < STATUS_NO_LABEL) q->status = STATUS_NO_LABEL; if (label && e->label && !strcmp(label, e->label)) break; /* found it */ } else if (e->priority == priority) { break; /* found it */ } /* else keep searching */ } #endif if (e) { /* found a valid match */ q->status = STATUS_SUCCESS; q->foundcontext = context; return e; } } } /* Check alternative switches */ AST_LIST_TRAVERSE(&tmp->alts, sw, list) { struct ast_switch *asw = pbx_findswitch(sw->name); ast_switch_f *aswf = NULL; char *datap; if (!asw) { ast_log(LOG_WARNING, "No such switch '%s'\n", sw->name); continue; } /* Substitute variables now */ if (sw->eval) { if (!(tmpdata = ast_str_thread_get(&switch_data, 512))) { ast_log(LOG_WARNING, "Can't evaluate switch?!"); continue; } pbx_substitute_variables_helper(chan, sw->data, tmpdata->str, tmpdata->len); } /* equivalent of extension_match_core() at the switch level */ if (action == E_CANMATCH) aswf = asw->canmatch; else if (action == E_MATCHMORE) aswf = asw->matchmore; else /* action == E_MATCH */ aswf = asw->exists; datap = sw->eval ? tmpdata->str : sw->data; if (!aswf) res = 0; else { if (chan) ast_autoservice_start(chan); res = aswf(chan, context, exten, priority, callerid, datap); if (chan) ast_autoservice_stop(chan); } if (res) { /* Got a match */ q->swo = asw; q->data = datap; q->foundcontext = context; /* XXX keep status = STATUS_NO_CONTEXT ? */ return NULL; } } q->incstack[q->stacklen++] = tmp->name; /* Setup the stack */ /* Now try any includes we have in this context */ for (i = tmp->includes; i; i = i->next) { if (include_valid(i)) { if ((e = pbx_find_extension(chan, bypass, q, i->rname, exten, priority, label, callerid, action))) { #ifdef NEED_DEBUG_HERE ast_log(LOG_NOTICE,"Returning recursive match of %s\n", e->exten); #endif return e; } if (q->swo) return NULL; } } return NULL; } /*! * \brief extract offset:length from variable name. * \return 1 if there is a offset:length part, which is * trimmed off (values go into variables) */ static int parse_variable_name(char *var, int *offset, int *length, int *isfunc) { int parens = 0; *offset = 0; *length = INT_MAX; *isfunc = 0; for (; *var; var++) { if (*var == '(') { (*isfunc)++; parens++; } else if (*var == ')') { parens--; } else if (*var == ':' && parens == 0) { *var++ = '\0'; sscanf(var, "%d:%d", offset, length); return 1; /* offset:length valid */ } } return 0; } /*! *\brief takes a substring. It is ok to call with value == workspace. * \param value * \param offset < 0 means start from the end of the string and set the beginning * to be that many characters back. * \param length is the length of the substring, a value less than 0 means to leave * that many off the end. * \param workspace * \param workspace_len * Always return a copy in workspace. */ static char *substring(const char *value, int offset, int length, char *workspace, size_t workspace_len) { char *ret = workspace; int lr; /* length of the input string after the copy */ ast_copy_string(workspace, value, workspace_len); /* always make a copy */ lr = strlen(ret); /* compute length after copy, so we never go out of the workspace */ /* Quick check if no need to do anything */ if (offset == 0 && length >= lr) /* take the whole string */ return ret; if (offset < 0) { /* translate negative offset into positive ones */ offset = lr + offset; if (offset < 0) /* If the negative offset was greater than the length of the string, just start at the beginning */ offset = 0; } /* too large offset result in empty string so we know what to return */ if (offset >= lr) return ret + lr; /* the final '\0' */ ret += offset; /* move to the start position */ if (length >= 0 && length < lr - offset) /* truncate if necessary */ ret[length] = '\0'; else if (length < 0) { if (lr > offset - length) /* After we remove from the front and from the rear, is there anything left? */ ret[lr + length - offset] = '\0'; else ret[0] = '\0'; } return ret; } /*! \brief Support for Asterisk built-in variables in the dialplan \note See also - \ref AstVar Channel variables - \ref AstCauses The HANGUPCAUSE variable */ void pbx_retrieve_variable(struct ast_channel *c, const char *var, char **ret, char *workspace, int workspacelen, struct varshead *headp) { const char not_found = '\0'; char *tmpvar; const char *s; /* the result */ int offset, length; int i, need_substring; struct varshead *places[2] = { headp, &globals }; /* list of places where we may look */ if (c) { ast_channel_lock(c); places[0] = &c->varshead; } /* * Make a copy of var because parse_variable_name() modifies the string. * Then if called directly, we might need to run substring() on the result; * remember this for later in 'need_substring', 'offset' and 'length' */ tmpvar = ast_strdupa(var); /* parse_variable_name modifies the string */ need_substring = parse_variable_name(tmpvar, &offset, &length, &i /* ignored */); /* * Look first into predefined variables, then into variable lists. * Variable 's' points to the result, according to the following rules: * s == ¬_found (set at the beginning) means that we did not find a * matching variable and need to look into more places. * If s != ¬_found, s is a valid result string as follows: * s = NULL if the variable does not have a value; * you typically do this when looking for an unset predefined variable. * s = workspace if the result has been assembled there; * typically done when the result is built e.g. with an snprintf(), * so we don't need to do an additional copy. * s != workspace in case we have a string, that needs to be copied * (the ast_copy_string is done once for all at the end). * Typically done when the result is already available in some string. */ s = ¬_found; /* default value */ if (c) { /* This group requires a valid channel */ /* Names with common parts are looked up a piece at a time using strncmp. */ if (!strncmp(var, "CALL", 4)) { if (!strncmp(var + 4, "ING", 3)) { if (!strcmp(var + 7, "PRES")) { /* CALLINGPRES */ snprintf(workspace, workspacelen, "%d", c->cid.cid_pres); s = workspace; } else if (!strcmp(var + 7, "ANI2")) { /* CALLINGANI2 */ snprintf(workspace, workspacelen, "%d", c->cid.cid_ani2); s = workspace; } else if (!strcmp(var + 7, "TON")) { /* CALLINGTON */ snprintf(workspace, workspacelen, "%d", c->cid.cid_ton); s = workspace; } else if (!strcmp(var + 7, "TNS")) { /* CALLINGTNS */ snprintf(workspace, workspacelen, "%d", c->cid.cid_tns); s = workspace; } } } else if (!strcmp(var, "HINT")) { s = ast_get_hint(workspace, workspacelen, NULL, 0, c, c->context, c->exten) ? workspace : NULL; } else if (!strcmp(var, "HINTNAME")) { s = ast_get_hint(NULL, 0, workspace, workspacelen, c, c->context, c->exten) ? workspace : NULL; } else if (!strcmp(var, "EXTEN")) { s = c->exten; } else if (!strcmp(var, "CONTEXT")) { s = c->context; } else if (!strcmp(var, "PRIORITY")) { snprintf(workspace, workspacelen, "%d", c->priority); s = workspace; } else if (!strcmp(var, "CHANNEL")) { s = c->name; } else if (!strcmp(var, "UNIQUEID")) { s = c->uniqueid; } else if (!strcmp(var, "HANGUPCAUSE")) { snprintf(workspace, workspacelen, "%d", c->hangupcause); s = workspace; } } if (s == ¬_found) { /* look for more */ if (!strcmp(var, "EPOCH")) { snprintf(workspace, workspacelen, "%u",(int)time(NULL)); s = workspace; } else if (!strcmp(var, "SYSTEMNAME")) { s = ast_config_AST_SYSTEM_NAME; } else if (!strcmp(var, "ENTITYID")) { ast_eid_to_str(workspace, workspacelen, &g_eid); s = workspace; } } /* if not found, look into chanvars or global vars */ for (i = 0; s == ¬_found && i < ARRAY_LEN(places); i++) { struct ast_var_t *variables; if (!places[i]) continue; if (places[i] == &globals) ast_rwlock_rdlock(&globalslock); AST_LIST_TRAVERSE(places[i], variables, entries) { if (!strcasecmp(ast_var_name(variables), var)) { s = ast_var_value(variables); break; } } if (places[i] == &globals) ast_rwlock_unlock(&globalslock); } if (s == ¬_found || s == NULL) *ret = NULL; else { if (s != workspace) ast_copy_string(workspace, s, workspacelen); *ret = workspace; if (need_substring) *ret = substring(*ret, offset, length, workspace, workspacelen); } if (c) ast_channel_unlock(c); } static void exception_store_free(void *data) { struct pbx_exception *exception = data; ast_string_field_free_memory(exception); ast_free(exception); } static struct ast_datastore_info exception_store_info = { .type = "EXCEPTION", .destroy = exception_store_free, }; int pbx_builtin_raise_exception(struct ast_channel *chan, void *vreason) { const char *reason = vreason; struct ast_datastore *ds = ast_channel_datastore_find(chan, &exception_store_info, NULL); struct pbx_exception *exception = NULL; if (!ds) { ds = ast_datastore_alloc(&exception_store_info, NULL); if (!ds) return -1; exception = ast_calloc(1, sizeof(struct pbx_exception)); if (!exception) { ast_datastore_free(ds); return -1; } if (ast_string_field_init(exception, 128)) { ast_free(exception); ast_datastore_free(ds); return -1; } ds->data = exception; ast_channel_datastore_add(chan, ds); } else exception = ds->data; ast_string_field_set(exception, reason, reason); ast_string_field_set(exception, context, chan->context); ast_string_field_set(exception, exten, chan->exten); exception->priority = chan->priority; set_ext_pri(chan, "e", 0); return 0; } static int acf_exception_read(struct ast_channel *chan, const char *name, char *data, char *buf, size_t buflen) { struct ast_datastore *ds = ast_channel_datastore_find(chan, &exception_store_info, NULL); struct pbx_exception *exception = NULL; if (!ds || !ds->data) return -1; exception = ds->data; if (!strcasecmp(data, "REASON")) ast_copy_string(buf, exception->reason, buflen); else if (!strcasecmp(data, "CONTEXT")) ast_copy_string(buf, exception->context, buflen); else if (!strncasecmp(data, "EXTEN", 5)) ast_copy_string(buf, exception->exten, buflen); else if (!strcasecmp(data, "PRIORITY")) snprintf(buf, buflen, "%d", exception->priority); else return -1; return 0; } static struct ast_custom_function exception_function = { .name = "EXCEPTION", .read = acf_exception_read, }; static char *handle_show_functions(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct ast_custom_function *acf; int count_acf = 0; int like = 0; switch (cmd) { case CLI_INIT: e->command = "core show functions [like]"; e->usage = "Usage: core show functions [like ]\n" " List builtin functions, optionally only those matching a given string\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc == 5 && (!strcmp(a->argv[3], "like")) ) { like = 1; } else if (a->argc != 3) { return CLI_SHOWUSAGE; } ast_cli(a->fd, "%s Custom Functions:\n--------------------------------------------------------------------------------\n", like ? "Matching" : "Installed"); AST_RWLIST_RDLOCK(&acf_root); AST_RWLIST_TRAVERSE(&acf_root, acf, acflist) { if (!like || strstr(acf->name, a->argv[4])) { count_acf++; ast_cli(a->fd, "%-20.20s %-35.35s %s\n", acf->name, acf->syntax, acf->synopsis); } } AST_RWLIST_UNLOCK(&acf_root); ast_cli(a->fd, "%d %scustom functions installed.\n", count_acf, like ? "matching " : ""); return CLI_SUCCESS; } static char *handle_show_function(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct ast_custom_function *acf; /* Maximum number of characters added by terminal coloring is 22 */ char infotitle[64 + AST_MAX_APP + 22], syntitle[40], destitle[40], argtitle[40], seealsotitle[40]; char info[64 + AST_MAX_APP], *synopsis = NULL, *description = NULL, *seealso = NULL; char stxtitle[40], *syntax = NULL, *arguments = NULL; int syntax_size, description_size, synopsis_size, arguments_size, seealso_size; char *ret = NULL; int which = 0; int wordlen; switch (cmd) { case CLI_INIT: e->command = "core show function"; e->usage = "Usage: core show function \n" " Describe a particular dialplan function.\n"; return NULL; case CLI_GENERATE: wordlen = strlen(a->word); /* case-insensitive for convenience in this 'complete' function */ AST_RWLIST_RDLOCK(&acf_root); AST_RWLIST_TRAVERSE(&acf_root, acf, acflist) { if (!strncasecmp(a->word, acf->name, wordlen) && ++which > a->n) { ret = ast_strdup(acf->name); break; } } AST_RWLIST_UNLOCK(&acf_root); return ret; } if (a->argc < 4) { return CLI_SHOWUSAGE; } if (!(acf = ast_custom_function_find(a->argv[3]))) { ast_cli(a->fd, "No function by that name registered.\n"); return CLI_FAILURE; } syntax_size = strlen(S_OR(acf->syntax, "Not Available")) + MAX_ESCAPE_CHARS; if (!(syntax = ast_malloc(syntax_size))) { ast_cli(a->fd, "Memory allocation failure!\n"); return CLI_FAILURE; } snprintf(info, sizeof(info), "\n -= Info about function '%s' =- \n\n", acf->name); term_color(infotitle, info, COLOR_MAGENTA, 0, sizeof(infotitle)); term_color(syntitle, "[Synopsis]\n", COLOR_MAGENTA, 0, 40); term_color(destitle, "[Description]\n", COLOR_MAGENTA, 0, 40); term_color(stxtitle, "[Syntax]\n", COLOR_MAGENTA, 0, 40); term_color(argtitle, "[Arguments]\n", COLOR_MAGENTA, 0, 40); term_color(seealsotitle, "[See Also]\n", COLOR_MAGENTA, 0, 40); term_color(syntax, S_OR(acf->syntax, "Not available"), COLOR_CYAN, 0, syntax_size); #ifdef AST_XML_DOCS if (acf->docsrc == AST_XML_DOC) { arguments = xmldoc_colorization(S_OR(acf->arguments, "Not available")); synopsis = xmldoc_colorization(S_OR(acf->synopsis, "Not available")); description = xmldoc_colorization(S_OR(acf->desc, "Not available")); seealso = xmldoc_colorization(S_OR(acf->seealso, "Not available")); } else #endif { synopsis_size = strlen(S_OR(acf->synopsis, "Not Available")) + MAX_ESCAPE_CHARS; synopsis = ast_malloc(synopsis_size); description_size = strlen(S_OR(acf->desc, "Not Available")) + MAX_ESCAPE_CHARS; description = ast_malloc(description_size); arguments_size = strlen(S_OR(acf->arguments, "Not Available")) + MAX_ESCAPE_CHARS; arguments = ast_malloc(arguments_size); seealso_size = strlen(S_OR(acf->seealso, "Not Available")) + MAX_ESCAPE_CHARS; seealso = ast_malloc(seealso_size); /* check allocated memory. */ if (!synopsis || !description || !arguments || !seealso) { ast_free(synopsis); ast_free(description); ast_free(arguments); ast_free(seealso); ast_free(syntax); return CLI_FAILURE; } term_color(arguments, S_OR(acf->arguments, "Not available"), COLOR_CYAN, 0, arguments_size); term_color(synopsis, S_OR(acf->synopsis, "Not available"), COLOR_CYAN, 0, synopsis_size); term_color(description, S_OR(acf->desc, "Not available"), COLOR_CYAN, 0, description_size); term_color(seealso, S_OR(acf->seealso, "Not available"), COLOR_CYAN, 0, seealso_size); } ast_cli(a->fd,"%s%s%s\n\n%s%s\n\n%s%s\n\n%s%s\n\n%s%s\n", infotitle, syntitle, synopsis, destitle, description, stxtitle, syntax, argtitle, arguments, seealsotitle, seealso); ast_free(arguments); ast_free(synopsis); ast_free(description); ast_free(seealso); ast_free(syntax); return CLI_SUCCESS; } struct ast_custom_function *ast_custom_function_find(const char *name) { struct ast_custom_function *acf = NULL; AST_RWLIST_RDLOCK(&acf_root); AST_RWLIST_TRAVERSE(&acf_root, acf, acflist) { if (!strcmp(name, acf->name)) break; } AST_RWLIST_UNLOCK(&acf_root); return acf; } int ast_custom_function_unregister(struct ast_custom_function *acf) { struct ast_custom_function *cur; if (!acf) return -1; AST_RWLIST_WRLOCK(&acf_root); if ((cur = AST_RWLIST_REMOVE(&acf_root, acf, acflist))) { if (cur->docsrc == AST_XML_DOC) { ast_string_field_free_memory(acf); } ast_verb(2, "Unregistered custom function %s\n", cur->name); } AST_RWLIST_UNLOCK(&acf_root); return cur ? 0 : -1; } #ifdef AST_XML_DOCS static const struct strcolorized_tags { const char *init; /*!< Replace initial tag with this string. */ const char *end; /*!< Replace end tag with this string. */ const int colorfg; /*!< Foreground color. */ const char *inittag; /*!< Initial tag description. */ const char *endtag; /*!< Ending tag description. */ } colorized_tags[] = { { "<", ">", COLOR_GREEN, "", "" }, { "\'", "\'", COLOR_BLUE, "", "" }, { "*", "*", COLOR_RED, "", "" }, { "\"", "\"", COLOR_YELLOW, "", "" }, { "\"", "\"", COLOR_CYAN, "", "" }, { "${", "}", COLOR_GREEN, "", "" }, { "", "", COLOR_BLUE, "", "" }, { "", "", COLOR_BLUE, "", "" }, { "\'", "\'", COLOR_GRAY, "", "" }, /* Special tags */ { "", "", COLOR_YELLOW, "", "" }, { "", "", COLOR_RED, "", "" } }; static const struct strspecial_tags { const char *tagname; /*!< Special tag name. */ const char *init; /*!< Print this at the beginning. */ const char *end; /*!< Print this at the end. */ } special_tags[] = { { "note", "NOTE: ", "" }, { "warning", "WARNING!!!: ", "" } }; /*! \internal * \brief Calculate the space in bytes used by a format string * that will be passed to a sprintf function. * \param postbr The format string to use to calculate the length. * \retval The postbr length. */ static int xmldoc_postbrlen(const char *postbr) { int postbrreallen = 0, i; size_t postbrlen; if (!postbr) { return 0; } postbrlen = strlen(postbr); for (i = 0; i < postbrlen; i++) { if (postbr[i] == '\t') { postbrreallen += 8 - (postbrreallen % 8); } else { postbrreallen++; } } return postbrreallen; } /*! \internal * \brief Setup postbr to be used while wrapping the text. * Add to postbr array all the spaces and tabs at the beginning of text. * \param postbr output array. * \param len text array length. * \param text Text with format string before the actual string. */ static void xmldoc_setpostbr(char *postbr, size_t len, const char *text) { int c, postbrlen = 0; if (!text) { return; } for (c = 0; c < len; c++) { if (text[c] == '\t' || text[c] == ' ') { postbr[postbrlen++] = text[c]; } else { break; } } postbr[postbrlen] = '\0'; } /*! \internal * \brief Try to find a space or a break in text starting at currentpost * and moving at most maxdiff positions. * Helper for xmldoc_string_wrap(). * \param text Input string where it will search. * \param currentpos Current position within text. * \param maxdiff Not move more than maxdiff inside text. * \retval 1 if a space or break is found inside text while moving. * \retval 0 if no space or break is found. */ static int xmldoc_wait_nextspace(const char *text, int currentpos, int maxdiff) { int i, textlen; if (!text) { return 0; } textlen = strlen(text); for (i = currentpos; i < textlen; i++) { if (text[i] == ESC) { /* Move to the end of the escape sequence */ while (i < textlen && text[i] != 'm') { i++; } } else if (text[i] == ' ' || text[i] == '\n') { /* Found the next space or linefeed */ return 1; } else if (i - currentpos > maxdiff) { /* We have looked the max distance and didn't find it */ return 0; } } /* Reached the end and did not find it */ return 0; } /*! \internal * \brief Helper function for xmldoc_string_wrap(). * Try to found a space or a break inside text moving backward * not more than maxdiff positions. * \param text The input string where to search for a space. * \param currentpos The current cursor position. * \param maxdiff The max number of positions to move within text. * \retval 0 If no space is found (Notice that text[currentpos] is not a space or a break) * \retval > 0 If a space or a break is found, and the result is the position relative to * currentpos. */ static int xmldoc_foundspace_backward(const char *text, int currentpos, int maxdiff) { int i; for (i = currentpos; i > 0; i--) { if (text[i] == ' ' || text[i] == '\n') { return (currentpos - i); } else if (text[i] == 'm' && (text[i - 1] >= '0' || text[i - 1] <= '9')) { /* give up, we found the end of a possible ESC sequence. */ return 0; } else if (currentpos - i > maxdiff) { /* give up, we can't move anymore. */ return 0; } } /* we found the beginning of the text */ return 0; } /*! \internal * \brief Justify a text to a number of columns. * \param text Input text to be justified. * \param columns Number of columns to preserve in the text. * \param maxdiff Try to not cut a word when goinf down. * \retval NULL on error. * \retval The wrapped text. */ static char *xmldoc_string_wrap(const char *text, int columns, int maxdiff) { struct ast_str *tmp; char *ret, postbr[160]; int count = 1, i, backspace, needtobreak = 0, colmax, textlen; /* sanity check */ if (!text || columns <= 0 || maxdiff < 0) { ast_log(LOG_WARNING, "Passing wrong arguments while trying to wrap the text\n"); return NULL; } tmp = ast_str_create(strlen(text) * 3); if (!tmp) { return NULL; } /* Check for blanks and tabs and put them in postbr. */ xmldoc_setpostbr(postbr, sizeof(postbr), text); colmax = columns - xmldoc_postbrlen(postbr); textlen = strlen(text); for (i = 0; i < textlen; i++) { if (needtobreak || !(count % colmax)) { if (text[i] == ' ') { ast_str_append(&tmp, 0, "\n%s", postbr); needtobreak = 0; count = 1; } else if (text[i] != '\n') { needtobreak = 1; if (xmldoc_wait_nextspace(text, i, maxdiff)) { /* wait for the next space */ ast_str_append(&tmp, 0, "%c", text[i]); continue; } /* Try to look backwards */ backspace = xmldoc_foundspace_backward(text, i, maxdiff); if (backspace) { needtobreak = 1; tmp->used -= backspace; tmp->str[tmp->used] = '\0'; i -= backspace + 1; continue; } ast_str_append(&tmp, 0, "\n%s", postbr); needtobreak = 0; count = 1; } /* skip blanks after a \n */ while (text[i] == ' ') { i++; } } if (text[i] == '\n') { xmldoc_setpostbr(postbr, sizeof(postbr), &text[i] + 1); colmax = columns - xmldoc_postbrlen(postbr); needtobreak = 0; count = 1; } if (text[i] == ESC) { /* Ignore Escape sequences. */ do { ast_str_append(&tmp, 0, "%c", text[i]); i++; } while (i < textlen && text[i] != 'm'); } else { count++; } ast_str_append(&tmp, 0, "%c", text[i]); } ret = ast_strdup(tmp->str); ast_free(tmp); return ret; } /*! \internal * \brief Colorize the xmldoc output. * \param bwinput Not colorized input. * \retval NULL on error. * \retval New malloced buffer colorized. */ static char *xmldoc_colorization(const char *bwinput) { struct ast_str *colorized; char *wrapped = NULL; int i, c, len, colorsection; char *tmp; size_t bwinputlen; static const int base_fg = COLOR_CYAN; if (!bwinput) { return NULL; } bwinputlen = strlen(bwinput); if (!(colorized = ast_str_create(256))) { return NULL; } ast_term_color_code(&colorized, base_fg, 0); if (!colorized) { return NULL; } for (i = 0; i < bwinputlen; i++) { colorsection = 0; /* Check if we are at the beginning of a tag to be colorized. */ for (c = 0; c < ARRAY_LEN(colorized_tags); c++) { if (strncasecmp(bwinput + i, colorized_tags[c].inittag, strlen(colorized_tags[c].inittag))) { continue; } if (!(tmp = strcasestr(bwinput + i + strlen(colorized_tags[c].inittag), colorized_tags[c].endtag))) { continue; } len = tmp - (bwinput + i + strlen(colorized_tags[c].inittag)); /* Setup color */ ast_term_color_code(&colorized, colorized_tags[c].colorfg, 0); if (!colorized) { return NULL; } /* copy initial string replace */ ast_str_append(&colorized, 0, "%s", colorized_tags[c].init); if (!colorized) { return NULL; } { char buf[len + 1]; ast_copy_string(buf, bwinput + i + strlen(colorized_tags[c].inittag), sizeof(buf)); ast_str_append(&colorized, 0, "%s", buf); } if (!colorized) { return NULL; } /* copy the ending string replace */ ast_str_append(&colorized, 0, "%s", colorized_tags[c].end); if (!colorized) { return NULL; } /* Continue with the last color. */ ast_term_color_code(&colorized, base_fg, 0); if (!colorized) { return NULL; } i += len + strlen(colorized_tags[c].endtag) + strlen(colorized_tags[c].inittag) - 1; colorsection = 1; break; } if (!colorsection) { ast_str_append(&colorized, 0, "%c", bwinput[i]); if (!colorized) { return NULL; } } } ast_term_color_code(&colorized, COLOR_BRWHITE, 0); if (!colorized) { return NULL; } /* Wrap the text, notice that string wrap will avoid cutting an ESC sequence. */ wrapped = xmldoc_string_wrap(colorized->str, xmldoc_text_columns, xmldoc_max_diff); ast_free(colorized); return wrapped; } /*! \internal * \brief Cleanup spaces and tabs after a \n * \param text String to be cleaned up. * \param output buffer (not already allocated). * \param lastspaces Remove last spaces in the string. */ static void xmldoc_string_cleanup(const char *text, struct ast_str **output, int lastspaces) { int i; size_t textlen; if (!text) { *output = NULL; return; } textlen = strlen(text); *output = ast_str_create(textlen); if (!(*output)) { ast_log(LOG_ERROR, "Problem allocating output buffer\n"); return; } for (i = 0; i < textlen; i++) { if (text[i] == '\n' || text[i] == '\r') { /* remove spaces/tabs/\n after a \n. */ while (text[i + 1] == '\t' || text[i + 1] == '\r' || text[i + 1] == '\n') { i++; } ast_str_append(output, 0, " "); continue; } else { ast_str_append(output, 0, "%c", text[i]); } } /* remove last spaces (we dont want always to remove the trailing spaces). */ if (lastspaces) { ast_str_trim_blanks(*output); } } /*! \internal * \brief Get the application/function node for 'name' application/function with language 'language' * if we don't find any, get the first application with 'name' no matter which language with. * \param type 'application', 'function', ... * \param name Application or Function name. * \param language Try to get this language (if not found try with en_US) * \retval NULL on error. * \retval A node of type ast_xml_node. */ static struct ast_xml_node *xmldoc_get_node(const char *type, const char *name, const char *language) { struct ast_xml_node *node = NULL; struct documentation_tree *doctree; const char *lang; AST_RWLIST_RDLOCK(&xmldoc_tree); AST_LIST_TRAVERSE(&xmldoc_tree, doctree, entry) { /* the core xml documents have priority over thirdparty document. */ node = ast_xml_get_root(doctree->doc); while ((node = ast_xml_find_element(node, type, "name", name))) { /* Check language */ lang = ast_xml_get_attribute(node, "language"); if (lang && !strcmp(lang, language)) { ast_xml_free_attr(lang); break; } else if (lang) { ast_xml_free_attr(lang); } } if (node && ast_xml_node_get_children(node)) { break; } /* We didn't find the application documentation for the specified language, so, try to load documentation for any language */ node = ast_xml_get_root(doctree->doc); if (ast_xml_node_get_children(node)) { if ((node = ast_xml_find_element(ast_xml_node_get_children(node), type, "name", name))) { break; } } } AST_RWLIST_UNLOCK(&xmldoc_tree); return node; } /*! \internal * \brief Helper function used to build the syntax, it allocates the needed buffer (or reallocates it), * and based on the reverse value it makes use of fmt to print the parameter list inside the * realloced buffer (syntax). * \param reverse We are going backwards while generating the syntax? * \param len Current length of 'syntax' buffer. * \param syntax Output buffer for the concatenated values. * \param fmt A format string that will be used in a sprintf call. */ static __attribute__((format(printf,4,5))) void xmldoc_reverse_helper(int reverse, int *len, char **syntax, const char *fmt, ...) { int totlen, tmpfmtlen; char *tmpfmt, tmp; va_list ap; va_start(ap, fmt); if (ast_vasprintf(&tmpfmt, fmt, ap) < 0) { va_end(ap); return; } va_end(ap); tmpfmtlen = strlen(tmpfmt); totlen = *len + tmpfmtlen + 1; *syntax = ast_realloc(*syntax, totlen); if (!*syntax) { ast_free(tmpfmt); return; } if (reverse) { memmove(*syntax + tmpfmtlen, *syntax, *len); /* Save this char, it will be overwritten by the \0 of strcpy. */ tmp = (*syntax)[0]; strcpy(*syntax, tmpfmt); /* Restore the already saved char. */ (*syntax)[tmpfmtlen] = tmp; (*syntax)[totlen - 1] = '\0'; } else { strcpy(*syntax + *len, tmpfmt); } *len = totlen - 1; ast_free(tmpfmt); } /*! \internal * \brief Check if the passed node has tags inside it. * \param node Root node to search argument elements. * \retval 1 If a element is found inside 'node'. * \retval 0 If no is found inside 'node'. */ static int xmldoc_has_arguments(struct ast_xml_node *fixnode) { struct ast_xml_node *node = fixnode; for (node = ast_xml_node_get_children(fixnode); node; node = ast_xml_node_get_next(node)) { if (!strcasecmp(ast_xml_node_get_name(node), "argument")) { return 1; } } return 0; } /*! \internal * \brief Build the syntax for a specified starting node. * \param rootnode A pointer to the ast_xml root node. * \param rootname Name of the application, function, option, etc. to build the syntax. * \param childname The name of each parameter node. * \param printparenthesis Boolean if we must print parenthesis if not parameters are found in the rootnode. * \param printrootname Boolean if we must print the rootname before the syntax and parenthesis at the begining/end. * \retval NULL on error. * \retval An ast_malloc'ed string with the syntax generated. */ static char *xmldoc_get_syntax(struct ast_xml_node *rootnode, const char *rootname, const char *childname, int printparenthesis, int printrootname) { #define GOTONEXT(__rev, __a) (__rev ? ast_xml_node_get_prev(__a) : ast_xml_node_get_next(__a)) #define ISLAST(__rev, __a) (__rev == 1 ? (ast_xml_node_get_prev(__a) ? 0 : 1) : (ast_xml_node_get_next(__a) ? 0 : 1)) #define MP(__a) ((multiple ? __a : "")) struct ast_xml_node *node = NULL, *firstparam = NULL, *lastparam = NULL; const char *paramtype, *multipletype, *paramname, *attrargsep, *parenthesis, *argname; int reverse, required, paramcount = 0, openbrackets = 0, len = 0, hasparams=0; int reqfinode = 0, reqlanode = 0, optmidnode = 0, prnparenthesis; char *syntax = NULL, *argsep; int paramnamemalloc, multiple; if (ast_strlen_zero(rootname) || ast_strlen_zero(childname)) { ast_log(LOG_WARNING, "Tried to look in XML tree with faulty rootname or childname while creating a syntax.\n"); return NULL; } if (!rootnode || !ast_xml_node_get_children(rootnode)) { /* If the rootnode field is not found, at least print name. */ ast_asprintf(&syntax, "%s%s", (printrootname ? rootname : ""), (printparenthesis ? "()" : "")); return syntax; } /* Get the argument separator from the root node attribute name 'argsep', if not found defaults to ','. */ attrargsep = ast_xml_get_attribute(rootnode, "argsep"); if (attrargsep) { argsep = ast_strdupa(attrargsep); ast_xml_free_attr(attrargsep); } else { argsep = ast_strdupa(","); } /* Get order of evaluation. */ for (node = ast_xml_node_get_children(rootnode); node; node = ast_xml_node_get_next(node)) { if (strcasecmp(ast_xml_node_get_name(node), childname)) { continue; } required = 0; hasparams = 1; if ((paramtype = ast_xml_get_attribute(node, "required"))) { if (ast_true(paramtype)) { required = 1; } ast_xml_free_attr(paramtype); } lastparam = node; reqlanode = required; if (!firstparam) { /* first parameter node */ firstparam = node; reqfinode = required; } } if (!hasparams) { /* This application, function, option, etc, doesn't have any params. */ ast_asprintf(&syntax, "%s%s", (printrootname ? rootname : ""), (printparenthesis ? "()" : "")); return syntax; } if (reqfinode && reqlanode) { /* check midnode */ for (node = ast_xml_node_get_children(rootnode); node; node = ast_xml_node_get_next(node)) { if (strcasecmp(ast_xml_node_get_name(node), childname)) { continue; } if (node != firstparam && node != lastparam) { if ((paramtype = ast_xml_get_attribute(node, "required"))) { if (!ast_true(paramtype)) { optmidnode = 1; break; } ast_xml_free_attr(paramtype); } } } } if ((!reqfinode && reqlanode) || (reqfinode && reqlanode && optmidnode)) { reverse = 1; node = lastparam; } else { reverse = 0; node = firstparam; } /* init syntax string. */ if (reverse) { xmldoc_reverse_helper(reverse, &len, &syntax, (printrootname ? (printrootname == 2 ? ")]" : ")"): "")); } else { xmldoc_reverse_helper(reverse, &len, &syntax, "%s%s", (printrootname ? rootname : ""), (printrootname ? (printrootname == 2 ? "[(" : "(") : "")); } for (; node; node = GOTONEXT(reverse, node)) { if (strcasecmp(ast_xml_node_get_name(node), childname)) { continue; } /* Get the argument name, if it is not the leaf, go inside that parameter. */ if (xmldoc_has_arguments(node)) { parenthesis = ast_xml_get_attribute(node, "hasparams"); prnparenthesis = 0; if (parenthesis) { prnparenthesis = ast_true(parenthesis); if (!strcasecmp(parenthesis, "optional")) { prnparenthesis = 2; } ast_xml_free_attr(parenthesis); } argname = ast_xml_get_attribute(node, "name"); if (argname) { paramname = xmldoc_get_syntax(node, argname, "argument", prnparenthesis, prnparenthesis); ast_xml_free_attr(argname); paramnamemalloc = 1; } else { /* Malformed XML, print **UNKOWN** */ paramname = ast_strdup("**unknown**"); } paramnamemalloc = 1; } else { paramnamemalloc = 0; paramname = ast_xml_get_attribute(node, "name"); if (!paramname) { ast_log(LOG_WARNING, "Malformed XML %s: no %s name\n", rootname, childname); if (syntax) { /* Free already allocated syntax */ ast_free(syntax); } /* to give up is ok? */ ast_asprintf(&syntax, "%s%s", (printrootname ? rootname : ""), (printparenthesis ? "()" : "")); return syntax; } } /* Defaults to 'false'. */ multiple = 0; if ((multipletype = ast_xml_get_attribute(node, "multiple"))) { if (ast_true(multipletype)) { multiple = 1; } ast_xml_free_attr(multipletype); } required = 0; /* Defaults to 'false'. */ if ((paramtype = ast_xml_get_attribute(node, "required"))) { if (ast_true(paramtype)) { required = 1; } ast_xml_free_attr(paramtype); } /* build syntax core. */ if (required) { /* First parameter */ if (!paramcount) { xmldoc_reverse_helper(reverse, &len, &syntax, "%s%s%s%s", paramname, MP("["), MP(argsep), MP("...]")); } else { /* Time to close open brackets. */ while (openbrackets > 0) { xmldoc_reverse_helper(reverse, &len, &syntax, (reverse ? "[" : "]")); openbrackets--; } if (reverse) { xmldoc_reverse_helper(reverse, &len, &syntax, "%s%s", paramname, argsep); } else { xmldoc_reverse_helper(reverse, &len, &syntax, "%s%s", argsep, paramname); } xmldoc_reverse_helper(reverse, &len, &syntax, "%s%s%s", MP("["), MP(argsep), MP("...]")); } } else { /* First parameter */ if (!paramcount) { xmldoc_reverse_helper(reverse, &len, &syntax, "[%s%s%s%s]", paramname, MP("["), MP(argsep), MP("...]")); } else { if (ISLAST(reverse, node)) { /* This is the last parameter. */ if (reverse) { xmldoc_reverse_helper(reverse, &len, &syntax, "[%s%s%s%s]%s", paramname, MP("["), MP(argsep), MP("...]"), argsep); } else { xmldoc_reverse_helper(reverse, &len, &syntax, "%s[%s%s%s%s]", argsep, paramname, MP("["), MP(argsep), MP("...]")); } } else { if (reverse) { xmldoc_reverse_helper(reverse, &len, &syntax, "%s%s%s%s%s]", paramname, argsep, MP("["), MP(argsep), MP("...]")); } else { xmldoc_reverse_helper(reverse, &len, &syntax, "[%s%s%s%s%s", argsep, paramname, MP("["), MP(argsep), MP("...]")); } openbrackets++; } } } if (paramnamemalloc) { ast_free((char *) paramname); } else { ast_xml_free_attr(paramname); } paramcount++; } /* Time to close open brackets. */ while (openbrackets > 0) { xmldoc_reverse_helper(reverse, &len, &syntax, (reverse ? "[" : "]")); openbrackets--; } /* close syntax string. */ if (reverse) { xmldoc_reverse_helper(reverse, &len, &syntax, "%s%s", (printrootname ? rootname : ""), (printrootname ? (printrootname == 2 ? "[(" : "(") : "")); } else { xmldoc_reverse_helper(reverse, &len, &syntax, (printrootname ? (printrootname == 2 ? ")]" : ")") : "")); } return syntax; #undef ISLAST #undef GOTONEXT #undef MP } /*! \internal * \brief Get the syntax for a specified application or function. * \param type Application or Function ? * \param name Name of the application or function. * \retval NULL on error. * \retval The generated syntax in a ast_malloc'ed string. */ static char *xmldoc_build_syntax(const char *type, const char *name) { struct ast_xml_node *node; char *syntax = NULL; node = xmldoc_get_node(type, name, documentation_language); if (!node) { return NULL; } for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) { if (!strcasecmp(ast_xml_node_get_name(node), "syntax")) { break; } } if (node) { syntax = xmldoc_get_syntax(node, name, "parameter", 1, 1); } return syntax; } /*! \internal * \brief Parse a element. * \param node The element pointer. * \param tabs Added this string before the content of the element. * \param posttabs Added this string after the content of the element. * \param buffer This must be an already allocated ast_str. It will be used * to store the result (if already has something it will be appended to the current * string). * \retval 1 If 'node' is a named 'para'. * \retval 2 If data is appended in buffer. * \retval 0 on error. */ static int xmldoc_parse_para(struct ast_xml_node *node, const char *tabs, const char *posttabs, struct ast_str **buffer) { const char *tmptext; struct ast_xml_node *tmp; int ret = 0; struct ast_str *tmpstr; if (!node || !ast_xml_node_get_children(node)) { return ret; } if (strcasecmp(ast_xml_node_get_name(node), "para")) { return ret; } ast_str_append(buffer, 0, "%s", tabs); ret = 1; for (tmp = ast_xml_node_get_children(node); tmp; tmp = ast_xml_node_get_next(tmp)) { /* Get the text inside the element and append it to buffer. */ tmptext = ast_xml_get_text(tmp); if (tmptext) { /* Strip \n etc. */ xmldoc_string_cleanup(tmptext, &tmpstr, 0); ast_xml_free_text(tmptext); if (tmpstr) { if (strcasecmp(ast_xml_node_get_name(tmp), "text")) { ast_str_append(buffer, 0, "<%s>%s", ast_xml_node_get_name(tmp), tmpstr->str, ast_xml_node_get_name(tmp)); } else { ast_str_append(buffer, 0, "%s", tmpstr->str); } ast_free(tmpstr); ret = 2; } } } ast_str_append(buffer, 0, "%s", posttabs); return ret; } /*! \internal * \brief Parse special elements defined in 'struct special_tags' special elements must have a element inside them. * \param fixnode special tag node pointer. * \param tabs put tabs before printing the node content. * \param posttabs put posttabs after printing node content. * \param buffer Output buffer, the special tags will be appended here. * \retval 0 if no special element is parsed. * \retval 1 if a special element is parsed (data is appended to buffer). * \retval 2 if a special element is parsed and also a element is parsed inside the specialtag. */ static int xmldoc_parse_specialtags(struct ast_xml_node *fixnode, const char *tabs, const char *posttabs, struct ast_str **buffer) { struct ast_xml_node *node = fixnode; int ret = 0, i, count = 0; if (!node || !ast_xml_node_get_children(node)) { return ret; } for (i = 0; i < ARRAY_LEN(special_tags); i++) { if (strcasecmp(ast_xml_node_get_name(node), special_tags[i].tagname)) { continue; } ret = 1; /* This is a special tag. */ /* concat data */ if (!ast_strlen_zero(special_tags[i].init)) { ast_str_append(buffer, 0, "%s%s", tabs, special_tags[i].init); } /* parse elements inside special tags. */ for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) { /* first just print it without tabs at the begining. */ if (xmldoc_parse_para(node, (!count ? "" : tabs), posttabs, buffer) == 2) { ret = 2; } } if (!ast_strlen_zero(special_tags[i].end)) { ast_str_append(buffer, 0, "%s%s", special_tags[i].end, posttabs); } break; } return ret; } /*! \internal * \brief Parse an element from the xml documentation. * \param fixnode Pointer to the 'argument' xml node. * \param insideparameter If we are parsing an inside a . * \param paramtabs pre tabs if we are inside a parameter element. * \param tabs What to be printed before the argument name. * \param buffer Output buffer to put values found inside the element. * \retval 1 If there is content inside the argument. * \retval 0 If the argument element is not parsed, or there is no content inside it. */ static int xmldoc_parse_argument(struct ast_xml_node *fixnode, int insideparameter, const char *paramtabs, const char *tabs, struct ast_str **buffer) { struct ast_xml_node *node = fixnode; const char *argname; int count = 0, ret = 0; if (!node || !ast_xml_node_get_children(node)) { return ret; } /* Print the argument names */ argname = ast_xml_get_attribute(node, "name"); if (!argname) { return 0; } ast_str_append(buffer, 0, "%s%s%s", tabs, argname, (insideparameter ? "\n" : "")); ast_xml_free_attr(argname); for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) { if (xmldoc_parse_para(node, (insideparameter ? paramtabs : (!count ? " - " : tabs)), "\n", buffer) == 2) { count++; ret = 1; } else if (xmldoc_parse_specialtags(node, (insideparameter ? paramtabs : (!count ? " - " : tabs)), "\n", buffer) == 2) { count++; ret = 1; } } return ret; } /*! \internal * \brief Parse a node inside a node. * \param node The variable node to parse. * \param tabs A string to be appended at the begining of the output that will be stored * in buffer. * \param buffer This must be an already created ast_str. It will be used * to store the result (if already has something it will be appended to the current * string). * \retval 0 if no data is appended. * \retval 1 if data is appended. */ static int xmldoc_parse_variable(struct ast_xml_node *node, const char *tabs, struct ast_str **buffer) { struct ast_xml_node *tmp; const char *valname; const char *tmptext; struct ast_str *cleanstr; int ret = 0, printedpara=0; for (tmp = ast_xml_node_get_children(node); tmp; tmp = ast_xml_node_get_next(tmp)) { if (xmldoc_parse_para(tmp, (ret ? tabs : ""), "\n", buffer)) { printedpara = 1; continue; } else if (xmldoc_parse_specialtags(tmp, (ret ? tabs : ""), "\n", buffer)) { printedpara = 1; continue; } if (strcasecmp(ast_xml_node_get_name(tmp), "value")) { continue; } /* Parse a tag only. */ if (!printedpara) { ast_str_append(buffer, 0, "\n"); printedpara = 1; } /* Parse each desciption */ valname = ast_xml_get_attribute(tmp, "name"); if (valname) { ret = 1; ast_str_append(buffer, 0, "%s%s", tabs, valname); ast_xml_free_attr(valname); } tmptext = ast_xml_get_text(tmp); /* Check inside this node for any explanation about its meaning. */ if (tmptext) { /* Cleanup text. */ xmldoc_string_cleanup(tmptext, &cleanstr, 1); ast_xml_free_text(tmptext); if (cleanstr && cleanstr->used > 0) { ast_str_append(buffer, 0, ":%s", cleanstr->str); } ast_free(cleanstr); } ast_str_append(buffer, 0, "\n"); } return ret; } /*! \internal * \brief Parse a node and put all the output inside 'buffer'. * \param node The variablelist node pointer. * \param tabs A string to be appended at the begining of the output that will be stored * in buffer. * \param buffer This must be an already created ast_str. It will be used * to store the result (if already has something it will be appended to the current * string). * \retval 1 If a element is parsed. * \retval 0 On error. */ static int xmldoc_parse_variablelist(struct ast_xml_node *node, const char *tabs, struct ast_str **buffer) { struct ast_xml_node *tmp; const char *varname; char *vartabs; int ret = 0; if (!node || !ast_xml_node_get_children(node)) { return ret; } if (strcasecmp(ast_xml_node_get_name(node), "variablelist")) { return ret; } /* use this spacing (add 4 spaces) inside a variablelist node. */ ast_asprintf(&vartabs, "%s ", tabs); if (!vartabs) { return ret; } for (tmp = ast_xml_node_get_children(node); tmp; tmp = ast_xml_node_get_next(tmp)) { /* We can have a element inside the variable list */ if ((xmldoc_parse_para(tmp, (ret ? tabs : ""), "\n", buffer))) { ret = 1; continue; } else if ((xmldoc_parse_specialtags(tmp, (ret ? tabs : ""), "\n", buffer))) { ret = 1; continue; } if (!strcasecmp(ast_xml_node_get_name(tmp), "variable")) { /* Store the variable name in buffer. */ varname = ast_xml_get_attribute(tmp, "name"); if (varname) { ast_str_append(buffer, 0, "%s%s: ", tabs, varname); ast_xml_free_attr(varname); /* Parse the possible values. */ xmldoc_parse_variable(tmp, vartabs, buffer); ret = 1; } } } ast_free(vartabs); return ret; } /*! \internal * \brief Parse the node content. * \param type 'application' or 'function'. * \param name Application or functions name. * \retval NULL on error. * \retval Content of the see-also node. */ static char *xmldoc_build_seealso(const char *type, const char *name) { struct ast_str *outputstr; char *output; struct ast_xml_node *node; const char *typename; const char *content; int first = 1; if (ast_strlen_zero(type) || ast_strlen_zero(name)) { return NULL; } /* get the application/function root node. */ node = xmldoc_get_node(type, name, documentation_language); if (!node || !ast_xml_node_get_children(node)) { return NULL; } /* Find the node. */ for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) { if (!strcasecmp(ast_xml_node_get_name(node), "see-also")) { break; } } if (!node || !ast_xml_node_get_children(node)) { /* we couldnt find a node. */ return NULL; } /* prepare the output string. */ outputstr = ast_str_create(128); if (!outputstr) { return NULL; } /* get into the node. */ for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) { if (strcasecmp(ast_xml_node_get_name(node), "ref")) { continue; } /* parse the node. 'type' attribute is required. */ typename = ast_xml_get_attribute(node, "type"); if (!typename) { continue; } content = ast_xml_get_text(node); if (!content) { ast_xml_free_attr(typename); continue; } if (!strcasecmp(typename, "application")) { ast_str_append(&outputstr, 0, "%s%s()", (first ? "" : ", "), content); } else if (!strcasecmp(typename, "function")) { ast_str_append(&outputstr, 0, "%s%s", (first ? "" : ", "), content); } else if (!strcasecmp(typename, "astcli")) { ast_str_append(&outputstr, 0, "%s%s", (first ? "" : ", "), content); } else { ast_str_append(&outputstr, 0, "%s%s", (first ? "" : ", "), content); } first = 0; ast_xml_free_text(content); } output = ast_strdup(outputstr->str); ast_free(outputstr); return output; } /*! \internal * \brief Parse a node. * \brief fixnode An ast_xml_node pointer to the node. * \bried buffer The output buffer. * \retval 0 if content is not found inside the enum element (data is not appended to buffer). * \retval 1 if content is found and data is appended to buffer. */ static int xmldoc_parse_enum(struct ast_xml_node *fixnode, const char *tabs, struct ast_str **buffer) { struct ast_xml_node *node = fixnode; int ret = 0; for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) { if ((xmldoc_parse_para(node, (ret ? tabs : " - "), "\n", buffer))) { ret = 1; } else if ((xmldoc_parse_specialtags(node, (ret ? tabs : " - "), "\n", buffer))) { ret = 1; } } return ret; } /*! \internal * \brief Parse a node. * \param fixnode As ast_xml pointer to the node. * \param buffer The ast_str output buffer. * \retval 0 if no node was parsed. * \retval 1 if a node was parsed. */ static int xmldoc_parse_enumlist(struct ast_xml_node *fixnode, const char *tabs, struct ast_str **buffer) { struct ast_xml_node *node = fixnode; const char *enumname; int ret = 0; for (node = ast_xml_node_get_children(node); node; node = ast_xml_node_get_next(node)) { if (strcasecmp(ast_xml_node_get_name(node), "enum")) { continue; } enumname = ast_xml_get_attribute(node, "name"); if (enumname) { ast_str_append(buffer, 0, "%s%s", tabs, enumname); ast_xml_free_attr(enumname); /* parse only enum elements inside a enumlist node. */ if ((xmldoc_parse_enum(node, tabs, buffer))) { ret = 1; } else { ast_str_append(buffer, 0, "\n"); } } } return ret; } /*! \internal * \brief Parse an