diff --git a/README.rsp b/README.rsp new file mode 100644 index 0000000..546fdbe --- /dev/null +++ b/README.rsp @@ -0,0 +1,67 @@ +Original Asterisk version: 1.4.24.1 + +asterisk_patches = ["patches/app_queue-state_interface.patch", + "patches/chan_sip-ironxfers.patch", + "patches/misdn-bug13488.patch", + "patches/app_queue-exitwithtimeout-waittime.patch", + "patches/app_queue-linear-strategy.patch", + "patches/app_queue-xfer-origpos.patch", + "patches/asterisk_queue_log_realtime_1.4.19.patch", + "patches/asterisk_realtime_store_destroy_1.4.19.patch", + "patches/res_config_odbc-queue_log.patch", + "patches/rtp_dtmf_0014815.patch", + "patches/voicemail_imap_crash_14508.patch", + "patches/misdn_release_call.patch", + "patches/misdn_showconfig_crash_14976.patch", + "patches/sip_410causecode_14993.patch", + "patches/crash_removeextensiontab_14689.patch", + "patches/app_queue_crash-large-queue-members.patch", + "patches/chan_sip_realtime-rtupdate_14885.patch", + "patches/res_odbc_maxlimits_14888.patch", + "patches/chan_sip_rtp-NAT_14546.patch", + "patches/chan_sip_T38-gateways_12437.patch", + "patches/app_queue_crash-badconfig_14796.patch", + "patches/chan_sip_glarereinvite_12013.patch", + "patches/chan_sip_reinvite-before-ACK_13849.patch", + "patches/app_dial_crash-retrydial_14852.patch", + "patches/chan_sip-thomson.patch", + "patches/chan_dahdi_bris.patch", + "patches/chan_sip_nat-realtime_15194.patch", + "patches/chan_sip_directrtp_14244.patch", + "patches/pbx-multiple_hints_state-15057.patch", + "patches/chan_sip_loop_12215.patch", + "patches/app_meetme_D-option_15050.patch", + "patches/makefile_bash_15209.patch", + "patches/chan_sip_rport_13823.patch", + "patches/res_musiconhold-crash_15109_15123_15195.patch", + "patches/crash_smdi-14561.patch", + "patches/moh_reload-14759.patch", + "patches/stack_size-14932.patch", + "patches/chan_sip-15330.patch", + "patches/chan_sip-add_info_supported_methods.patch", + "patches/return_code_in_ringing-15158.patch", + "patches/voicemail_maxsilence-minmessage_15331.patch", + "patches/multiple_hints-15413.patch", + "patches/AST-2009-008-1.4.patch", + "patches/queue_atxfer_S_OR.patch", + "patches/queue_atxfer_bug_14260.patch", + "patches/AST-2009-009-1.4.patch", + "patches/say.c-issue16105.patch", + "patches/console_colors.patch", + "patches/queue_magic_number.patch", + "patches/queue_wrandom.patch", + "patches/052-debian-runlevel-Makefile.patch", + "patches/053-make-es-sounds.patch", + "patches/055-version-RSP.patch", + "patches/g722-20090218.patch", + "patches/app_queue-sharedlastcall.patch", + "patches/app_queue-R-option.patch", + "patches/tranfer_moh-16513.patch", + #### new files in patch form (diff -uNr) + "patches/live_ast.patch", + "patches/app_pickup2.c.patch", + "patches/app_syslog.c.patch", + "patches/cdr_adaptive_odbc.c.patch", + "patches/func_devstate.c.patch"] + + diff --git a/patches/052-debian-runlevel-Makefile.patch b/patches/052-debian-runlevel-Makefile.patch new file mode 100644 index 0000000..72e4ff1 --- /dev/null +++ b/patches/052-debian-runlevel-Makefile.patch @@ -0,0 +1,11 @@ +--- Makefile (revision 138) ++++ Makefile (working copy) +@@ -681,7 +681,7 @@ + if [ -z "$(DESTDIR)" ]; then /sbin/chkconfig --add asterisk; fi; \ + elif [ -f /etc/debian_version ]; then \ + $(INSTALL) -m 755 contrib/init.d/rc.debian.asterisk $(DESTDIR)/etc/init.d/asterisk; \ +- if [ -z "$(DESTDIR)" ]; then /usr/sbin/update-rc.d asterisk start 50 2 3 4 5 . stop 91 2 3 4 5 .; fi; \ ++ if [ -z "$(DESTDIR)" ]; then /usr/sbin/update-rc.d asterisk start 50 2 3 4 5 . stop 20 0 1 6 .; fi; \ + elif [ -f /etc/gentoo-release ]; then \ + $(INSTALL) -m 755 contrib/init.d/rc.gentoo.asterisk $(DESTDIR)/etc/init.d/asterisk; \ + if [ -z "$(DESTDIR)" ]; then /sbin/rc-update add asterisk default; fi; \ diff --git a/patches/053-make-es-sounds.patch b/patches/053-make-es-sounds.patch new file mode 100644 index 0000000..5d7b338 --- /dev/null +++ b/patches/053-make-es-sounds.patch @@ -0,0 +1,202 @@ +--- Makefile 2009-12-10 21:58:09.000000000 +0100 ++++ Makefile 2010-03-04 14:54:21.000000000 +0100 +@@ -553,13 +544,29 @@ + endif + @echo " + +" + @echo " + **Note** This requires that you have +" + @echo " + doxygen installed on your local system +" +- @echo " +-------------------------------------------+" ++ @echo " + +" ++ @echo " + ------------------ or ----------------- +" ++ @echo " + +" ++ @echo " + +" ++ @echo " + Puedes instalar las voces en castellano +" ++ @echo " + proporcionadas por voipnovatos con: +" ++ @echo " + +" ++ifeq ($(MAKE), gmake) ++ @echo " + $(MAKE) es-sounds +" ++else ++ @echo " + $(MAKE) es-sounds +" ++endif ++ @echo " + +" ++ @echo " +------------------------------------------ +" + @$(MAKE) -s oldmodcheck + + upgrade: bininstall + ++es-sounds: ++ sh contrib/scripts/asterisk-sounds-es.sh ++ + adsi: + mkdir -p $(DESTDIR)$(ASTETCDIR) + for x in configs/*.adsi; do \ + if [ ! -f $(DESTDIR)$(ASTETCDIR)/$$x ]; then \ +--- contrib/scripts/asterisk-sounds-es.sh 2010-03-04 17:59:22.000000000 +0100 ++++ contrib/scripts/asterisk-sounds-es.sh 2010-03-04 14:54:20.000000000 +0100 +@@ -0,0 +1,166 @@ ++#!/usr/bin/env bash ++ ++##################################################################### ++# Script de instalacion y configuracion de las voces de VoIPNovatos # ++# Version 0.3 # ++# Por Elio Rojano (http://www.sinologic.net) # ++# Modificado por Saúl Ibarra (http://www.saghul.net) # ++# Modificado por Jon Bonilla (http://sipdoc.net) # ++# Licencia: GPL # ++##################################################################### ++ ++ ++ESPANOL=`echo $LANG |grep "es"` ++if [ "$ESPANOL" ]; then ++ MSG1="Aceptas la licencia de uso? [S/N]: " ++ MSG2="Escribe tu nombre: " ++ MSG3="Escribe tu email: " ++ MSG4="Escribe la empresa que tendra esta instalación: " ++ MSG5="Registrando usuario ..." ++ MSG6="Descargando el conjunto de sonidos seleccionado..." ++ MSG8="Creando enlaces para compatibilizar Asterisk..." ++ MSG12="Instalando ..." ++ MSG14="¿Quieres los sonidos en GSM? [S/N]: " ++ MSG15="¿Quieres los sonidos en ALAW? [S/N]: " ++ MSG16="¿Quieres los sonidos en ULAW? [S/N]: " ++ MSG17="¿Quieres los sonidos en G729? [S/N]: " ++else ++ MSG1="Are you AGREE with the license? [Y/N]: " ++ MSG2="Type your name: " ++ MSG3="Type your email: " ++ MSG4="Type your company: " ++ MSG5="Registry user ..." ++ MSG6="Downloading sound set CORE ..." ++ MSG7="Downloading sound set EXTRA ..." ++ MSG8="Creating symbolic links to add some more compatibility ..." ++ MSG12="Installing ..." ++ MSG14="Do you wish to install GSM format sounds? [Y/N]: " ++ MSG15="Do you wish to install ALAW format sounds? [Y/N]: " ++ MSG16="Do you wish to install ULAW format sounds? [Y/N]: " ++ MSG17="Do you wish to install G729 format sounds? [Y/N]: " ++fi ++ ++if [ -d /var/lib/asterisk/sounds ]; then ++ pushd /var/lib/asterisk/sounds ++ mkdir -p es ++ wget -qc http://www.voipnovatos.es/voces/licenciadeuso.txt -O- | iconv -f latin1 -t utf8 | more ++ echo "" ++ while [ ! "$ACEPTADA" ]; do ++ echo -n $MSG1 && read ACEPTADA ++ case $ACEPTADA in ++ S|s|Y|y) ACEPTADA="Si" ;; ++ N|n) ACEPTADA="No" ;; ++ *) ACEPTADA="" ;; ++ esac ++ done ++ if [ "$ACEPTADA" == "Si" ]; then ++ while [ ! "$NOMBRE" ]; do echo -n $MSG2; read NOMBRE; done ++ while [ ! "$EMAIL" ]; do echo -n $MSG3; read EMAIL; done ++ while [ ! "$EMPRESA" ]; do echo -n $MSG4; read EMPRESA; done ++ ++ ++ # Qué formatos quieres? ++ echo "" ++ while [ ! "$format_gsm" ]; do ++ echo -n $MSG14 && read format_gsm ++ case $format_gsm in ++ S|s|Y|y) format_gsm="Si" ;; ++ N|n) format_gsm="No" ;; ++ *) format_gsm="" ;; ++ esac ++ done ++ ++ echo "" ++ while [ ! "$format_alaw" ]; do ++ echo -n $MSG15 && read format_alaw ++ case $format_alaw in ++ S|s|Y|y) format_alaw="Si" ;; ++ N|n) format_alaw="No" ;; ++ *) format_alaw="" ;; ++ esac ++ done ++ ++ echo "" ++ while [ ! "$format_ulaw" ]; do ++ echo -n $MSG16 && read format_ulaw ++ case $format_ulaw in ++ S|s|Y|y) format_ulaw="Si" ;; ++ N|n) format_ulaw="No" ;; ++ *) format_ulaw="" ;; ++ esac ++ done ++ ++ echo "" ++ while [ ! "$format_g729" ]; do ++ echo -n $MSG17 && read format_g729 ++ case $format_g729 in ++ S|s|Y|y) format_g729="Si" ;; ++ N|n) format_g729="No" ;; ++ *) format_g729="" ;; ++ esac ++ done ++ ++ ++ # Para hacer uso de las locuciones de cara al publico, deben enviarse estos datos al creador... ++ echo $MSG5 ++ wget -cqF "http://voipnovatos.es/voces.php?name=$NOMBRE&email=$EMAIL&empresa=$EMPRESA&format=$formato" -O /dev/null ++ sleep 1 ++ echo $MSG12 ++ sleep 1 ++ echo $MSG6 ++ if [ "$format_gsm" == "Si" ]; then ++ wget -c http://www.voipnovatos.es/voces/voipnovatos-core-sounds-es-gsm-1.4.tar.gz ++ wget -c http://www.voipnovatos.es/voces/voipnovatos-extra-sounds-es-gsm-1.4.tar.gz ++ fi ++ if [ "$format_alaw" == "Si" ]; then ++ wget -c http://www.voipnovatos.es/voces/voipnovatos-core-sounds-es-alaw-1.4.tar.gz ++ wget -c http://www.voipnovatos.es/voces/voipnovatos-extra-sounds-es-alaw-1.4.tar.gz ++ fi ++ if [ "$format_ulaw" == "Si" ]; then ++ wget -c http://www.voipnovatos.es/voces/voipnovatos-core-sounds-es-ulaw-1.4.tar.gz ++ wget -c http://www.voipnovatos.es/voces/voipnovatos-extra-sounds-es-ulaw-1.4.tar.gz ++ fi ++ if [ "$format_g729" == "Si" ]; then ++ wget -c http://www.voipnovatos.es/voces/voipnovatos-core-sounds-es-g729-1.4.tar.gz ++ wget -c http://www.voipnovatos.es/voces/voipnovatos-extra-sounds-es-g729-1.4.tar.gz ++ fi ++ ++ for f in *.tar.gz; do tar xvzf $f; done ++ rm -f *.tar.gz ++ ++ #Arreglando permisos... ++ chmod 644 /var/lib/asterisk/sounds/dictate/es/* ++ chmod 644 /var/lib/asterisk/sounds/digits/es/* ++ chmod 644 /var/lib/asterisk/sounds/followme/es/* ++ chmod 644 /var/lib/asterisk/sounds/letters/es/* ++ chmod 644 /var/lib/asterisk/sounds/phonetic/es/* ++ chmod 644 /var/lib/asterisk/sounds/silence/es/* ++ chmod 755 /var/lib/asterisk/sounds/dictate ++ chmod 755 /var/lib/asterisk/sounds/digits ++ chmod 755 /var/lib/asterisk/sounds/followme ++ chmod 755 /var/lib/asterisk/sounds/letters ++ chmod 755 /var/lib/asterisk/sounds/phonetic ++ chmod 755 /var/lib/asterisk/sounds/silence ++ chmod 755 /var/lib/asterisk/sounds/dictate/es ++ chmod 755 /var/lib/asterisk/sounds/digits/es ++ chmod 755 /var/lib/asterisk/sounds/followme/es ++ chmod 755 /var/lib/asterisk/sounds/letters/es ++ chmod 755 /var/lib/asterisk/sounds/phonetic/es ++ chmod 755 /var/lib/asterisk/sounds/silence/es ++ chmod 755 /var/lib/asterisk/sounds/es ++ chmod 644 /var/lib/asterisk/sounds/es/* ++ ++ echo $MSG8 ++ ln -s /var/lib/asterisk/sounds/dictate/es /var/lib/asterisk/sounds/es/dictate >/dev/null 2>/dev/null ++ ln -s /var/lib/asterisk/sounds/digits/es /var/lib/asterisk/sounds/es/digits >/dev/null 2>/dev/null ++ ln -s /var/lib/asterisk/sounds/followme/es /var/lib/asterisk/sounds/es/followme >/dev/null 2>/dev/null ++ ln -s /var/lib/asterisk/sounds/letters/es /var/lib/asterisk/sounds/es/letters >/dev/null 2>/dev/null ++ ln -s /var/lib/asterisk/sounds/phonetic/es /var/lib/asterisk/sounds/es/phonetic >/dev/null 2>/dev/null ++ ln -s /var/lib/asterisk/sounds/silence/es /var/lib/asterisk/sounds/es/silence >/dev/null 2>/dev/null ++ ++ fi ++fi ++ ++popd >/dev/null ++exit 0 ++ diff --git a/patches/055-version-RSP.patch b/patches/055-version-RSP.patch new file mode 100644 index 0000000..9f39d2e --- /dev/null +++ b/patches/055-version-RSP.patch @@ -0,0 +1,50 @@ +--- build_tools/make_version_h 2007-02-23 19:59:09.000000000 +0100 ++++ build_tools/make_version_h 2010-05-06 22:06:58.000000000 +0200 +@@ -1,5 +1,5 @@ + #!/bin/sh +-if [ ! -f ../.flavor ]; then ++if [ ! -f .flavor ]; then + cat << END + /* + * version.h +@@ -10,14 +10,14 @@ + + END + else +- aadkver=`cat ../.version` +- aadkflavor=`cat ../.flavor` ++ aadkver=`cat .version` ++ aadkflavor=`cat .flavor` + cat << END + /* + * version.h + * Automatically generated + */ +-#define ASTERISK_VERSION "${ASTERISKVERSION} (${aadkflavor} ${aadkver})" ++#define ASTERISK_VERSION "${ASTERISKVERSION}-${aadkflavor}" + #define ASTERISK_VERSION_NUM ${ASTERISKVERSIONNUM} + + END +--- .flavor 1970-01-01 01:00:00.000000000 +0100 ++++ .flavor 2010-05-06 22:05:31.000000000 +0200 +@@ -0,0 +1 @@ ++RSP (Community supported branch) +--- main/asterisk.c 2009-02-25 13:43:36.000000000 +0100 ++++ main/asterisk.c 2010-05-06 22:05:31.000000000 +0200 +@@ -144,7 +144,15 @@ + ast_verbose("This is free software, with components licensed under the GNU General Public\n"); \ + ast_verbose("License version 2 and other licenses; you are welcome to redistribute it under\n"); \ + ast_verbose("certain conditions. Type 'core show license' for details.\n"); \ +- ast_verbose("=========================================================================\n") ++ ast_verbose("=========================================================================\n"); \ ++ ast_verbose("\n"); \ ++ ast_verbose("Versión RSP de Asterisk " ASTERISK_VERSION ", mantenida por la comunidad\n"); \ ++ ast_verbose("Lista de correo de la versión RSP: http://groups.google.com/group/asterisk-es-rsp \n"); \ ++ ast_verbose("Wiki de la versión RSP: http://www.asterisk-es-rsp.org \n"); \ ++ ast_verbose("Rep. SVN Asterisk-rsp: http://asterisk-es-rsp.irontec.com/svn/asterisk-es-rsp/branches/ \n"); \ ++ ast_verbose("Visor web del repositorio SVN: http://asterisk-es-rsp.irontec.com \n"); \ ++ ast_verbose("\n"); \ ++ ast_verbose("=========================================================================\n") + + /*! \defgroup main_options Main Configuration Options + \brief Main configuration options from \ref Config_ast "asterisk.conf" or diff --git a/patches/AST-2009-003-1.4.diff.txt b/patches/AST-2009-003-1.4.diff.txt new file mode 100644 index 0000000..13a6b3e --- /dev/null +++ b/patches/AST-2009-003-1.4.diff.txt @@ -0,0 +1,178 @@ +--- channels/chan_sip.c (revision 183280) ++++ channels/chan_sip.c (working copy) +@@ -1266,7 +1266,7 @@ + static int transmit_response_with_unsupported(struct sip_pvt *p, const char *msg, const struct sip_request *req, const char *unsupported); + static int transmit_response_with_auth(struct sip_pvt *p, const char *msg, const struct sip_request *req, const char *rand, enum xmittype reliable, const char *header, int stale); + static int transmit_response_with_allow(struct sip_pvt *p, const char *msg, const struct sip_request *req, enum xmittype reliable); +-static void transmit_fake_auth_response(struct sip_pvt *p, struct sip_request *req, enum xmittype reliable); ++static void transmit_fake_auth_response(struct sip_pvt *p, int sipmethod, struct sip_request *req, enum xmittype reliable); + static int transmit_request(struct sip_pvt *p, int sipmethod, int inc, enum xmittype reliable, int newbranch); + static int transmit_request_with_auth(struct sip_pvt *p, int sipmethod, int seqno, enum xmittype reliable, int newbranch); + static int transmit_invite(struct sip_pvt *p, int sipmethod, int sdp, int init); +@@ -8873,10 +8873,96 @@ + /*! \brief Send a fake 401 Unauthorized response when the administrator + wants to hide the names of local users/peers from fishers + */ +-static void transmit_fake_auth_response(struct sip_pvt *p, struct sip_request *req, enum xmittype reliable) ++static void transmit_fake_auth_response(struct sip_pvt *p, int sipmethod, struct sip_request *req, enum xmittype reliable) + { +- ast_string_field_build(p, randdata, "%08lx", ast_random()); /* Create nonce for challenge */ +- transmit_response_with_auth(p, "401 Unauthorized", req, p->randdata, reliable, "WWW-Authenticate", 0); ++ /* We have to emulate EXACTLY what we'd get with a good peer ++ * and a bad password, or else we leak information. */ ++ const char *response = "407 Proxy Authentication Required"; ++ const char *reqheader = "Proxy-Authorization"; ++ const char *respheader = "Proxy-Authenticate"; ++ const char *authtoken; ++ struct ast_dynamic_str *buf; ++ char *c; ++ ++ /* table of recognised keywords, and their value in the digest */ ++ enum keys { K_NONCE, K_LAST }; ++ struct x { ++ const char *key; ++ const char *s; ++ } *i, keys[] = { ++ [K_NONCE] = { "nonce=", "" }, ++ [K_LAST] = { NULL, NULL} ++ }; ++ ++ if (sipmethod == SIP_REGISTER || sipmethod == SIP_SUBSCRIBE) { ++ response = "401 Unauthorized"; ++ reqheader = "Authorization"; ++ respheader = "WWW-Authenticate"; ++ } ++ authtoken = get_header(req, reqheader); ++ if (ast_test_flag(req, SIP_PKT_IGNORE) && !ast_strlen_zero(p->randdata) && ast_strlen_zero(authtoken)) { ++ /* This is a retransmitted invite/register/etc, don't reconstruct authentication ++ * information */ ++ transmit_response_with_auth(p, response, req, p->randdata, 0, respheader, 0); ++ /* Schedule auto destroy in 32 seconds (according to RFC 3261) */ ++ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); ++ return; ++ } else if (ast_strlen_zero(p->randdata) || ast_strlen_zero(authtoken)) { ++ /* We have no auth, so issue challenge and request authentication */ ++ ast_string_field_build(p, randdata, "%08lx", ast_random()); /* Create nonce for challenge */ ++ transmit_response_with_auth(p, response, req, p->randdata, 0, respheader, 0); ++ /* Schedule auto destroy in 32 seconds */ ++ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); ++ return; ++ } ++ ++ if (!(buf = ast_dynamic_str_thread_get(&check_auth_buf, CHECK_AUTH_BUF_INITLEN))) { ++ transmit_response(p, "403 Forbidden (Bad auth)", &p->initreq); ++ return; ++ } ++ ++ /* Make a copy of the response and parse it */ ++ if (ast_dynamic_str_thread_set(&buf, 0, &check_auth_buf, "%s", authtoken) == AST_DYNSTR_BUILD_FAILED) { ++ transmit_response(p, "403 Forbidden (Bad auth)", &p->initreq); ++ return; ++ } ++ ++ c = buf->str; ++ ++ while (c && *(c = ast_skip_blanks(c))) { /* lookup for keys */ ++ for (i = keys; i->key != NULL; i++) { ++ const char *separator = ","; /* default */ ++ ++ if (strncasecmp(c, i->key, strlen(i->key)) != 0) { ++ continue; ++ } ++ /* Found. Skip keyword, take text in quotes or up to the separator. */ ++ c += strlen(i->key); ++ if (*c == '"') { /* in quotes. Skip first and look for last */ ++ c++; ++ separator = "\""; ++ } ++ i->s = c; ++ strsep(&c, separator); ++ break; ++ } ++ if (i->key == NULL) { /* not found, jump after space or comma */ ++ strsep(&c, " ,"); ++ } ++ } ++ ++ /* Verify nonce from request matches our nonce. If not, send 401 with new nonce */ ++ if (strcasecmp(p->randdata, keys[K_NONCE].s)) { ++ if (!ast_test_flag(req, SIP_PKT_IGNORE)) { ++ ast_string_field_build(p, randdata, "%08lx", ast_random()); ++ } ++ transmit_response_with_auth(p, response, req, p->randdata, reliable, respheader, FALSE); ++ ++ /* Schedule auto destroy in 32 seconds */ ++ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); ++ } else { ++ transmit_response(p, "403 Forbidden (Bad auth)", &p->initreq); ++ } + } + + /*! \brief Verify registration of user +@@ -9010,6 +9096,14 @@ + } + } + } ++ if (!peer && global_alwaysauthreject) { ++ /* If we found a peer, we transmit a 100 Trying. Therefore, if we're ++ * trying to avoid leaking information, we MUST also transmit the same ++ * response when we DON'T find a peer. */ ++ transmit_response(p, "100 Trying", req); ++ /* Insert a fake delay between the 100 and the subsequent failure. */ ++ sched_yield(); ++ } + if (!res) { + ast_device_state_changed("SIP/%s", peer->name); + } +@@ -9020,7 +9114,7 @@ + transmit_response(p, "403 Forbidden (Bad auth)", &p->initreq); + break; + case AUTH_USERNAME_MISMATCH: +- /* Username and digest username does not match. ++ /* Username and digest username does not match. + Asterisk uses the From: username for authentication. We need the + users to use the same authentication user name until we support + proper authentication by digest auth name */ +@@ -9030,7 +9124,7 @@ + case AUTH_PEER_NOT_DYNAMIC: + case AUTH_ACL_FAILED: + if (global_alwaysauthreject) { +- transmit_fake_auth_response(p, &p->initreq, XMIT_UNRELIABLE); ++ transmit_fake_auth_response(p, SIP_REGISTER, &p->initreq, XMIT_UNRELIABLE); + } else { + /* URI not found */ + if (res == AUTH_PEER_NOT_DYNAMIC) +@@ -14557,7 +14651,7 @@ + if (res < 0) { /* Something failed in authentication */ + if (res == AUTH_FAKE_AUTH) { + ast_log(LOG_NOTICE, "Sending fake auth rejection for user %s\n", get_header(req, "From")); +- transmit_fake_auth_response(p, req, XMIT_RELIABLE); ++ transmit_fake_auth_response(p, SIP_INVITE, req, XMIT_RELIABLE); + } else { + ast_log(LOG_NOTICE, "Failed to authenticate user %s\n", get_header(req, "From")); + transmit_response_reliable(p, "403 Forbidden", req); +@@ -15594,7 +15688,7 @@ + if (res < 0) { + if (res == AUTH_FAKE_AUTH) { + ast_log(LOG_NOTICE, "Sending fake auth rejection for user %s\n", get_header(req, "From")); +- transmit_fake_auth_response(p, req, XMIT_UNRELIABLE); ++ transmit_fake_auth_response(p, SIP_SUBSCRIBE, req, XMIT_UNRELIABLE); + } else { + ast_log(LOG_NOTICE, "Failed to authenticate user %s for SUBSCRIBE\n", get_header(req, "From")); + transmit_response_reliable(p, "403 Forbidden", req); +--- configs/sip.conf.sample (revision 183280) ++++ configs/sip.conf.sample (working copy) +@@ -141,9 +141,11 @@ + ;callevents=no ; generate manager events when sip ua + ; performs events (e.g. hold) + ;alwaysauthreject = yes ; When an incoming INVITE or REGISTER is to be rejected, +- ; for any reason, always reject with '401 Unauthorized' ++ ; for any reason, always reject with an identical response ++ ; equivalent to valid username and invalid password/hash + ; instead of letting the requester know whether there was +- ; a matching user or peer for their request ++ ; a matching user or peer for their request. This reduces ++ ; the ability of an attacker to scan for valid SIP usernames. + + ;g726nonstandard = yes ; If the peer negotiates G726-32 audio, use AAL2 packing + ; order instead of RFC3551 packing order (this is required diff --git a/patches/AST-2009-008-1.4.patch b/patches/AST-2009-008-1.4.patch new file mode 100644 index 0000000..d3fbbc3 --- /dev/null +++ b/patches/AST-2009-008-1.4.patch @@ -0,0 +1,11 @@ +--- channels/chan_sip.c (revisión: 131) ++++ channels/chan_sip.c (copia de trabajo) +@@ -9097,8 +9097,6 @@ + Asterisk uses the From: username for authentication. We need the + users to use the same authentication user name until we support + proper authentication by digest auth name */ +- transmit_response(p, "403 Authentication user name does not match account name", &p->initreq); +- break; + case AUTH_NOT_FOUND: + case AUTH_PEER_NOT_DYNAMIC: + case AUTH_ACL_FAILED: diff --git a/patches/AST-2009-009-1.4.patch b/patches/AST-2009-009-1.4.patch new file mode 100644 index 0000000..d2f00c9 --- /dev/null +++ b/patches/AST-2009-009-1.4.patch @@ -0,0 +1,3092 @@ +--- static-http/prototype.js (revision 226137) ++++ static-http/prototype.js (working copy) +@@ -1,17 +1,34 @@ +-/* Prototype JavaScript framework, version 1.4.0 +- * (c) 2005 Sam Stephenson ++/* Prototype JavaScript framework, version 1.5.1.2 ++ * (c) 2005-2008 Sam Stephenson + * + * Prototype is freely distributable under the terms of an MIT-style license. +- * For details, see the Prototype web site: http://prototype.conio.net/ ++ * For details, see the Prototype web site: http://www.prototypejs.org/ + * + /*--------------------------------------------------------------------------*/ + + var Prototype = { +- Version: '1.4.0', +- ScriptFragment: '(?:)((\n|\r|.)*?)(?:<\/script>)', ++ Version: '1.5.1.2', + +- emptyFunction: function() {}, +- K: function(x) {return x} ++ Browser: { ++ IE: !!(window.attachEvent && !window.opera), ++ Opera: !!window.opera, ++ WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1, ++ Gecko: navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1 ++ }, ++ ++ BrowserFeatures: { ++ XPath: !!document.evaluate, ++ ElementExtensions: !!window.HTMLElement, ++ SpecificElementExtensions: ++ (document.createElement('div').__proto__ !== ++ document.createElement('form').__proto__) ++ }, ++ ++ ScriptFragment: ']*>([\\S\\s]*?)<\/script>', ++ JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/, ++ ++ emptyFunction: function() { }, ++ K: function(x) { return x } + } + + var Class = { +@@ -25,22 +42,62 @@ + var Abstract = new Object(); + + Object.extend = function(destination, source) { +- for (property in source) { ++ for (var property in source) { + destination[property] = source[property]; + } + return destination; + } + +-Object.inspect = function(object) { +- try { +- if (object == undefined) return 'undefined'; +- if (object == null) return 'null'; +- return object.inspect ? object.inspect() : object.toString(); +- } catch (e) { +- if (e instanceof RangeError) return '...'; +- throw e; ++Object.extend(Object, { ++ inspect: function(object) { ++ try { ++ if (object === undefined) return 'undefined'; ++ if (object === null) return 'null'; ++ return object.inspect ? object.inspect() : object.toString(); ++ } catch (e) { ++ if (e instanceof RangeError) return '...'; ++ throw e; ++ } ++ }, ++ ++ toJSON: function(object) { ++ var type = typeof object; ++ switch(type) { ++ case 'undefined': ++ case 'function': ++ case 'unknown': return; ++ case 'boolean': return object.toString(); ++ } ++ if (object === null) return 'null'; ++ if (object.toJSON) return object.toJSON(); ++ if (object.ownerDocument === document) return; ++ var results = []; ++ for (var property in object) { ++ var value = Object.toJSON(object[property]); ++ if (value !== undefined) ++ results.push(property.toJSON() + ': ' + value); ++ } ++ return '{' + results.join(', ') + '}'; ++ }, ++ ++ keys: function(object) { ++ var keys = []; ++ for (var property in object) ++ keys.push(property); ++ return keys; ++ }, ++ ++ values: function(object) { ++ var values = []; ++ for (var property in object) ++ values.push(object[property]); ++ return values; ++ }, ++ ++ clone: function(object) { ++ return Object.extend({}, object); + } +-} ++}); + + Function.prototype.bind = function() { + var __method = this, args = $A(arguments), object = args.shift(); +@@ -50,17 +107,15 @@ + } + + Function.prototype.bindAsEventListener = function(object) { +- var __method = this; ++ var __method = this, args = $A(arguments), object = args.shift(); + return function(event) { +- return __method.call(object, event || window.event); ++ return __method.apply(object, [event || window.event].concat(args)); + } + } + + Object.extend(Number.prototype, { + toColorPart: function() { +- var digits = this.toString(16); +- if (this < 16) return '0' + digits; +- return digits; ++ return this.toPaddedString(2, 16); + }, + + succ: function() { +@@ -70,14 +125,32 @@ + times: function(iterator) { + $R(0, this, true).each(iterator); + return this; ++ }, ++ ++ toPaddedString: function(length, radix) { ++ var string = this.toString(radix || 10); ++ return '0'.times(length - string.length) + string; ++ }, ++ ++ toJSON: function() { ++ return isFinite(this) ? this.toString() : 'null'; + } + }); + ++Date.prototype.toJSON = function() { ++ return '"' + this.getFullYear() + '-' + ++ (this.getMonth() + 1).toPaddedString(2) + '-' + ++ this.getDate().toPaddedString(2) + 'T' + ++ this.getHours().toPaddedString(2) + ':' + ++ this.getMinutes().toPaddedString(2) + ':' + ++ this.getSeconds().toPaddedString(2) + '"'; ++}; ++ + var Try = { + these: function() { + var returnValue; + +- for (var i = 0; i < arguments.length; i++) { ++ for (var i = 0, length = arguments.length; i < length; i++) { + var lambda = arguments[i]; + try { + returnValue = lambda(); +@@ -102,40 +175,83 @@ + }, + + registerCallback: function() { +- setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); ++ this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + ++ stop: function() { ++ if (!this.timer) return; ++ clearInterval(this.timer); ++ this.timer = null; ++ }, ++ + onTimerEvent: function() { + if (!this.currentlyExecuting) { + try { + this.currentlyExecuting = true; +- this.callback(); ++ this.callback(this); + } finally { + this.currentlyExecuting = false; + } + } + } + } ++Object.extend(String, { ++ interpret: function(value) { ++ return value == null ? '' : String(value); ++ }, ++ specialChar: { ++ '\b': '\\b', ++ '\t': '\\t', ++ '\n': '\\n', ++ '\f': '\\f', ++ '\r': '\\r', ++ '\\': '\\\\' ++ } ++}); + +-/*--------------------------------------------------------------------------*/ ++Object.extend(String.prototype, { ++ gsub: function(pattern, replacement) { ++ var result = '', source = this, match; ++ replacement = arguments.callee.prepareReplacement(replacement); + +-function $() { +- var elements = new Array(); ++ while (source.length > 0) { ++ if (match = source.match(pattern)) { ++ result += source.slice(0, match.index); ++ result += String.interpret(replacement(match)); ++ source = source.slice(match.index + match[0].length); ++ } else { ++ result += source, source = ''; ++ } ++ } ++ return result; ++ }, + +- for (var i = 0; i < arguments.length; i++) { +- var element = arguments[i]; +- if (typeof element == 'string') +- element = document.getElementById(element); ++ sub: function(pattern, replacement, count) { ++ replacement = this.gsub.prepareReplacement(replacement); ++ count = count === undefined ? 1 : count; + +- if (arguments.length == 1) +- return element; ++ return this.gsub(pattern, function(match) { ++ if (--count < 0) return match[0]; ++ return replacement(match); ++ }); ++ }, + +- elements.push(element); +- } ++ scan: function(pattern, iterator) { ++ this.gsub(pattern, iterator); ++ return this; ++ }, + +- return elements; +-} +-Object.extend(String.prototype, { ++ truncate: function(length, truncation) { ++ length = length || 30; ++ truncation = truncation === undefined ? '...' : truncation; ++ return this.length > length ? ++ this.slice(0, length - truncation.length) + truncation : this; ++ }, ++ ++ strip: function() { ++ return this.replace(/^\s+/, '').replace(/\s+$/, ''); ++ }, ++ + stripTags: function() { + return this.replace(/<\/?[^>]+>/gi, ''); + }, +@@ -153,28 +269,40 @@ + }, + + evalScripts: function() { +- return this.extractScripts().map(eval); ++ return this.extractScripts().map(function(script) { return eval(script) }); + }, + + escapeHTML: function() { +- var div = document.createElement('div'); +- var text = document.createTextNode(this); +- div.appendChild(text); +- return div.innerHTML; ++ var self = arguments.callee; ++ self.text.data = this; ++ return self.div.innerHTML; + }, + + unescapeHTML: function() { + var div = document.createElement('div'); + div.innerHTML = this.stripTags(); +- return div.childNodes[0] ? div.childNodes[0].nodeValue : ''; ++ return div.childNodes[0] ? (div.childNodes.length > 1 ? ++ $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) : ++ div.childNodes[0].nodeValue) : ''; + }, + +- toQueryParams: function() { +- var pairs = this.match(/^\??(.*)$/)[1].split('&'); +- return pairs.inject({}, function(params, pairString) { +- var pair = pairString.split('='); +- params[pair[0]] = pair[1]; +- return params; ++ toQueryParams: function(separator) { ++ var match = this.strip().match(/([^?#]*)(#.*)?$/); ++ if (!match) return {}; ++ ++ return match[1].split(separator || '&').inject({}, function(hash, pair) { ++ if ((pair = pair.split('='))[0]) { ++ var key = decodeURIComponent(pair.shift()); ++ var value = pair.length > 1 ? pair.join('=') : pair[0]; ++ if (value != undefined) value = decodeURIComponent(value); ++ ++ if (key in hash) { ++ if (hash[key].constructor != Array) hash[key] = [hash[key]]; ++ hash[key].push(value); ++ } ++ else hash[key] = value; ++ } ++ return hash; + }); + }, + +@@ -182,48 +310,158 @@ + return this.split(''); + }, + ++ succ: function() { ++ return this.slice(0, this.length - 1) + ++ String.fromCharCode(this.charCodeAt(this.length - 1) + 1); ++ }, ++ ++ times: function(count) { ++ var result = ''; ++ for (var i = 0; i < count; i++) result += this; ++ return result; ++ }, ++ + camelize: function() { +- var oStringList = this.split('-'); +- if (oStringList.length == 1) return oStringList[0]; ++ var parts = this.split('-'), len = parts.length; ++ if (len == 1) return parts[0]; + +- var camelizedString = this.indexOf('-') == 0 +- ? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1) +- : oStringList[0]; ++ var camelized = this.charAt(0) == '-' ++ ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1) ++ : parts[0]; + +- for (var i = 1, len = oStringList.length; i < len; i++) { +- var s = oStringList[i]; +- camelizedString += s.charAt(0).toUpperCase() + s.substring(1); +- } ++ for (var i = 1; i < len; i++) ++ camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1); + +- return camelizedString; ++ return camelized; + }, + +- inspect: function() { +- return "'" + this.replace('\\', '\\\\').replace("'", '\\\'') + "'"; ++ capitalize: function() { ++ return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase(); ++ }, ++ ++ underscore: function() { ++ return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase(); ++ }, ++ ++ dasherize: function() { ++ return this.gsub(/_/,'-'); ++ }, ++ ++ inspect: function(useDoubleQuotes) { ++ var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) { ++ var character = String.specialChar[match[0]]; ++ return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16); ++ }); ++ if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"'; ++ return "'" + escapedString.replace(/'/g, '\\\'') + "'"; ++ }, ++ ++ toJSON: function() { ++ return this.inspect(true); ++ }, ++ ++ unfilterJSON: function(filter) { ++ return this.sub(filter || Prototype.JSONFilter, '#{1}'); ++ }, ++ ++ isJSON: function() { ++ var str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''); ++ return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str); ++ }, ++ ++ evalJSON: function(sanitize) { ++ var json = this.unfilterJSON(); ++ try { ++ if (!sanitize || json.isJSON()) return eval('(' + json + ')'); ++ } catch (e) { } ++ throw new SyntaxError('Badly formed JSON string: ' + this.inspect()); ++ }, ++ ++ include: function(pattern) { ++ return this.indexOf(pattern) > -1; ++ }, ++ ++ startsWith: function(pattern) { ++ return this.indexOf(pattern) === 0; ++ }, ++ ++ endsWith: function(pattern) { ++ var d = this.length - pattern.length; ++ return d >= 0 && this.lastIndexOf(pattern) === d; ++ }, ++ ++ empty: function() { ++ return this == ''; ++ }, ++ ++ blank: function() { ++ return /^\s*$/.test(this); + } + }); + ++if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, { ++ escapeHTML: function() { ++ return this.replace(/&/g,'&').replace(//g,'>'); ++ }, ++ unescapeHTML: function() { ++ return this.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); ++ } ++}); ++ ++String.prototype.gsub.prepareReplacement = function(replacement) { ++ if (typeof replacement == 'function') return replacement; ++ var template = new Template(replacement); ++ return function(match) { return template.evaluate(match) }; ++} ++ + String.prototype.parseQuery = String.prototype.toQueryParams; + +-var $break = new Object(); +-var $continue = new Object(); ++Object.extend(String.prototype.escapeHTML, { ++ div: document.createElement('div'), ++ text: document.createTextNode('') ++}); + ++with (String.prototype.escapeHTML) div.appendChild(text); ++ ++var Template = Class.create(); ++Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; ++Template.prototype = { ++ initialize: function(template, pattern) { ++ this.template = template.toString(); ++ this.pattern = pattern || Template.Pattern; ++ }, ++ ++ evaluate: function(object) { ++ return this.template.gsub(this.pattern, function(match) { ++ var before = match[1]; ++ if (before == '\\') return match[2]; ++ return before + String.interpret(object[match[3]]); ++ }); ++ } ++} ++ ++var $break = {}, $continue = new Error('"throw $continue" is deprecated, use "return" instead'); ++ + var Enumerable = { + each: function(iterator) { + var index = 0; + try { + this._each(function(value) { +- try { +- iterator(value, index++); +- } catch (e) { +- if (e != $continue) throw e; +- } ++ iterator(value, index++); + }); + } catch (e) { + if (e != $break) throw e; + } ++ return this; + }, + ++ eachSlice: function(number, iterator) { ++ var index = -number, slices = [], array = this.toArray(); ++ while ((index += number) < array.length) ++ slices.push(array.slice(index, index+number)); ++ return slices.map(iterator); ++ }, ++ + all: function(iterator) { + var result = true; + this.each(function(value, index) { +@@ -234,7 +472,7 @@ + }, + + any: function(iterator) { +- var result = true; ++ var result = false; + this.each(function(value, index) { + if (result = !!(iterator || Prototype.K)(value, index)) + throw $break; +@@ -245,12 +483,12 @@ + collect: function(iterator) { + var results = []; + this.each(function(value, index) { +- results.push(iterator(value, index)); ++ results.push((iterator || Prototype.K)(value, index)); + }); + return results; + }, + +- detect: function (iterator) { ++ detect: function(iterator) { + var result; + this.each(function(value, index) { + if (iterator(value, index)) { +@@ -291,6 +529,14 @@ + return found; + }, + ++ inGroupsOf: function(number, fillWith) { ++ fillWith = fillWith === undefined ? null : fillWith; ++ return this.eachSlice(number, function(slice) { ++ while(slice.length < number) slice.push(fillWith); ++ return slice; ++ }); ++ }, ++ + inject: function(memo, iterator) { + this.each(function(value, index) { + memo = iterator(memo, value, index); +@@ -300,7 +546,7 @@ + + invoke: function(method) { + var args = $A(arguments).slice(1); +- return this.collect(function(value) { ++ return this.map(function(value) { + return value[method].apply(value, args); + }); + }, +@@ -309,7 +555,7 @@ + var result; + this.each(function(value, index) { + value = (iterator || Prototype.K)(value, index); +- if (value >= (result || value)) ++ if (result == undefined || value >= result) + result = value; + }); + return result; +@@ -319,7 +565,7 @@ + var result; + this.each(function(value, index) { + value = (iterator || Prototype.K)(value, index); +- if (value <= (result || value)) ++ if (result == undefined || value < result) + result = value; + }); + return result; +@@ -352,7 +598,7 @@ + }, + + sortBy: function(iterator) { +- return this.collect(function(value, index) { ++ return this.map(function(value, index) { + return {value: value, criteria: iterator(value, index)}; + }).sort(function(left, right) { + var a = left.criteria, b = right.criteria; +@@ -361,7 +607,7 @@ + }, + + toArray: function() { +- return this.collect(Prototype.K); ++ return this.map(); + }, + + zip: function() { +@@ -371,11 +617,14 @@ + + var collections = [this].concat(args).map($A); + return this.map(function(value, index) { +- iterator(value = collections.pluck(index)); +- return value; ++ return iterator(collections.pluck(index)); + }); + }, + ++ size: function() { ++ return this.toArray().length; ++ }, ++ + inspect: function() { + return '#'; + } +@@ -394,19 +643,35 @@ + return iterable.toArray(); + } else { + var results = []; +- for (var i = 0; i < iterable.length; i++) ++ for (var i = 0, length = iterable.length; i < length; i++) + results.push(iterable[i]); + return results; + } + } + ++if (Prototype.Browser.WebKit) { ++ $A = Array.from = function(iterable) { ++ if (!iterable) return []; ++ if (!(typeof iterable == 'function' && iterable == '[object NodeList]') && ++ iterable.toArray) { ++ return iterable.toArray(); ++ } else { ++ var results = []; ++ for (var i = 0, length = iterable.length; i < length; i++) ++ results.push(iterable[i]); ++ return results; ++ } ++ } ++} ++ + Object.extend(Array.prototype, Enumerable); + +-Array.prototype._reverse = Array.prototype.reverse; ++if (!Array.prototype._reverse) ++ Array.prototype._reverse = Array.prototype.reverse; + + Object.extend(Array.prototype, { + _each: function(iterator) { +- for (var i = 0; i < this.length; i++) ++ for (var i = 0, length = this.length; i < length; i++) + iterator(this[i]); + }, + +@@ -425,13 +690,13 @@ + + compact: function() { + return this.select(function(value) { +- return value != undefined || value != null; ++ return value != null; + }); + }, + + flatten: function() { + return this.inject([], function(array, value) { +- return array.concat(value.constructor == Array ? ++ return array.concat(value && value.constructor == Array ? + value.flatten() : [value]); + }); + }, +@@ -444,7 +709,7 @@ + }, + + indexOf: function(object) { +- for (var i = 0; i < this.length; i++) ++ for (var i = 0, length = this.length; i < length; i++) + if (this[i] == object) return i; + return -1; + }, +@@ -453,23 +718,110 @@ + return (inline !== false ? this : this.toArray())._reverse(); + }, + +- shift: function() { +- var result = this[0]; +- for (var i = 0; i < this.length - 1; i++) +- this[i] = this[i + 1]; +- this.length--; +- return result; ++ reduce: function() { ++ return this.length > 1 ? this : this[0]; + }, + ++ uniq: function(sorted) { ++ return this.inject([], function(array, value, index) { ++ if (0 == index || (sorted ? array.last() != value : !array.include(value))) ++ array.push(value); ++ return array; ++ }); ++ }, ++ ++ clone: function() { ++ return [].concat(this); ++ }, ++ ++ size: function() { ++ return this.length; ++ }, ++ + inspect: function() { + return '[' + this.map(Object.inspect).join(', ') + ']'; ++ }, ++ ++ toJSON: function() { ++ var results = []; ++ this.each(function(object) { ++ var value = Object.toJSON(object); ++ if (value !== undefined) results.push(value); ++ }); ++ return '[' + results.join(', ') + ']'; + } + }); +-var Hash = { ++ ++Array.prototype.toArray = Array.prototype.clone; ++ ++function $w(string) { ++ string = string.strip(); ++ return string ? string.split(/\s+/) : []; ++} ++ ++if (Prototype.Browser.Opera){ ++ Array.prototype.concat = function() { ++ var array = []; ++ for (var i = 0, length = this.length; i < length; i++) array.push(this[i]); ++ for (var i = 0, length = arguments.length; i < length; i++) { ++ if (arguments[i].constructor == Array) { ++ for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++) ++ array.push(arguments[i][j]); ++ } else { ++ array.push(arguments[i]); ++ } ++ } ++ return array; ++ } ++} ++var Hash = function(object) { ++ if (object instanceof Hash) this.merge(object); ++ else Object.extend(this, object || {}); ++}; ++ ++Object.extend(Hash, { ++ toQueryString: function(obj) { ++ var parts = []; ++ parts.add = arguments.callee.addPair; ++ ++ this.prototype._each.call(obj, function(pair) { ++ if (!pair.key) return; ++ var value = pair.value; ++ ++ if (value && typeof value == 'object') { ++ if (value.constructor == Array) value.each(function(value) { ++ parts.add(pair.key, value); ++ }); ++ return; ++ } ++ parts.add(pair.key, value); ++ }); ++ ++ return parts.join('&'); ++ }, ++ ++ toJSON: function(object) { ++ var results = []; ++ this.prototype._each.call(object, function(pair) { ++ var value = Object.toJSON(pair.value); ++ if (value !== undefined) results.push(pair.key.toJSON() + ': ' + value); ++ }); ++ return '{' + results.join(', ') + '}'; ++ } ++}); ++ ++Hash.toQueryString.addPair = function(key, value, prefix) { ++ key = encodeURIComponent(key); ++ if (value === undefined) this.push(key); ++ else this.push(key + '=' + (value == null ? '' : encodeURIComponent(value))); ++} ++ ++Object.extend(Hash.prototype, Enumerable); ++Object.extend(Hash.prototype, { + _each: function(iterator) { +- for (key in this) { ++ for (var key in this) { + var value = this[key]; +- if (typeof value == 'function') continue; ++ if (value && value == Hash.prototype[key]) continue; + + var pair = [key, value]; + pair.key = key; +@@ -487,31 +839,66 @@ + }, + + merge: function(hash) { +- return $H(hash).inject($H(this), function(mergedHash, pair) { ++ return $H(hash).inject(this, function(mergedHash, pair) { + mergedHash[pair.key] = pair.value; + return mergedHash; + }); + }, + ++ remove: function() { ++ var result; ++ for(var i = 0, length = arguments.length; i < length; i++) { ++ var value = this[arguments[i]]; ++ if (value !== undefined){ ++ if (result === undefined) result = value; ++ else { ++ if (result.constructor != Array) result = [result]; ++ result.push(value) ++ } ++ } ++ delete this[arguments[i]]; ++ } ++ return result; ++ }, ++ + toQueryString: function() { +- return this.map(function(pair) { +- return pair.map(encodeURIComponent).join('='); +- }).join('&'); ++ return Hash.toQueryString(this); + }, + + inspect: function() { + return '#'; ++ }, ++ ++ toJSON: function() { ++ return Hash.toJSON(this); + } +-} ++}); + + function $H(object) { +- var hash = Object.extend({}, object || {}); +- Object.extend(hash, Enumerable); +- Object.extend(hash, Hash); +- return hash; +-} ++ if (object instanceof Hash) return object; ++ return new Hash(object); ++}; ++ ++// Safari iterates over shadowed properties ++if (function() { ++ var i = 0, Test = function(value) { this.key = value }; ++ Test.prototype.key = 'foo'; ++ for (var property in new Test('bar')) i++; ++ return i > 1; ++}()) Hash.prototype._each = function(iterator) { ++ var cache = []; ++ for (var key in this) { ++ var value = this[key]; ++ if ((value && value == Hash.prototype[key]) || cache.include(key)) continue; ++ cache.push(key); ++ var pair = [key, value]; ++ pair.key = key; ++ pair.value = value; ++ iterator(pair); ++ } ++}; + ObjectRange = Class.create(); + Object.extend(ObjectRange.prototype, Enumerable); + Object.extend(ObjectRange.prototype, { +@@ -523,10 +910,10 @@ + + _each: function(iterator) { + var value = this.start; +- do { ++ while (this.include(value)) { + iterator(value); + value = value.succ(); +- } while (this.include(value)); ++ } + }, + + include: function(value) { +@@ -545,9 +932,9 @@ + var Ajax = { + getTransport: function() { + return Try.these( ++ function() {return new XMLHttpRequest()}, + function() {return new ActiveXObject('Msxml2.XMLHTTP')}, +- function() {return new ActiveXObject('Microsoft.XMLHTTP')}, +- function() {return new XMLHttpRequest()} ++ function() {return new ActiveXObject('Microsoft.XMLHTTP')} + ) || false; + }, + +@@ -561,18 +948,18 @@ + this.responders._each(iterator); + }, + +- register: function(responderToAdd) { +- if (!this.include(responderToAdd)) +- this.responders.push(responderToAdd); ++ register: function(responder) { ++ if (!this.include(responder)) ++ this.responders.push(responder); + }, + +- unregister: function(responderToRemove) { +- this.responders = this.responders.without(responderToRemove); ++ unregister: function(responder) { ++ this.responders = this.responders.without(responder); + }, + + dispatch: function(callback, request, transport, json) { + this.each(function(responder) { +- if (responder[callback] && typeof responder[callback] == 'function') { ++ if (typeof responder[callback] == 'function') { + try { + responder[callback].apply(responder, [request, transport, json]); + } catch (e) {} +@@ -587,7 +974,6 @@ + onCreate: function() { + Ajax.activeRequestCount++; + }, +- + onComplete: function() { + Ajax.activeRequestCount--; + } +@@ -599,19 +985,15 @@ + this.options = { + method: 'post', + asynchronous: true, ++ contentType: 'application/x-www-form-urlencoded', ++ encoding: 'UTF-8', + parameters: '' + } + Object.extend(this.options, options || {}); +- }, + +- responseIsSuccess: function() { +- return this.transport.status == undefined +- || this.transport.status == 0 +- || (this.transport.status >= 200 && this.transport.status < 300); +- }, +- +- responseIsFailure: function() { +- return !this.responseIsSuccess(); ++ this.options.method = this.options.method.toLowerCase(); ++ if (typeof this.options.parameters == 'string') ++ this.options.parameters = this.options.parameters.toQueryParams(); + } + } + +@@ -620,6 +1002,8 @@ + ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; + + Ajax.Request.prototype = Object.extend(new Ajax.Base(), { ++ _complete: false, ++ + initialize: function(url, options) { + this.transport = Ajax.getTransport(); + this.setOptions(options); +@@ -627,113 +1011,161 @@ + }, + + request: function(url) { +- var parameters = this.options.parameters || ''; +- if (parameters.length > 0) parameters += '&_='; ++ this.url = url; ++ this.method = this.options.method; ++ var params = Object.clone(this.options.parameters); + ++ if (!['get', 'post'].include(this.method)) { ++ // simulate other verbs over post ++ params['_method'] = this.method; ++ this.method = 'post'; ++ } ++ ++ this.parameters = params; ++ ++ if (params = Hash.toQueryString(params)) { ++ // when GET, append parameters to URL ++ if (this.method == 'get') ++ this.url += (this.url.include('?') ? '&' : '?') + params; ++ else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ++ params += '&_='; ++ } ++ + try { +- this.url = url; +- if (this.options.method == 'get' && parameters.length > 0) +- this.url += (this.url.match(/\?/) ? '&' : '?') + parameters; +- ++ if (this.options.onCreate) this.options.onCreate(this.transport); + Ajax.Responders.dispatch('onCreate', this, this.transport); + +- this.transport.open(this.options.method, this.url, ++ this.transport.open(this.method.toUpperCase(), this.url, + this.options.asynchronous); + +- if (this.options.asynchronous) { +- this.transport.onreadystatechange = this.onStateChange.bind(this); +- setTimeout((function() {this.respondToReadyState(1)}).bind(this), 10); +- } ++ if (this.options.asynchronous) ++ setTimeout(function() { this.respondToReadyState(1) }.bind(this), 10); + ++ this.transport.onreadystatechange = this.onStateChange.bind(this); + this.setRequestHeaders(); + +- var body = this.options.postBody ? this.options.postBody : parameters; +- this.transport.send(this.options.method == 'post' ? body : null); ++ this.body = this.method == 'post' ? (this.options.postBody || params) : null; ++ this.transport.send(this.body); + +- } catch (e) { ++ /* Force Firefox to handle ready state 4 for synchronous requests */ ++ if (!this.options.asynchronous && this.transport.overrideMimeType) ++ this.onStateChange(); ++ ++ } ++ catch (e) { + this.dispatchException(e); + } + }, + ++ onStateChange: function() { ++ var readyState = this.transport.readyState; ++ if (readyState > 1 && !((readyState == 4) && this._complete)) ++ this.respondToReadyState(this.transport.readyState); ++ }, ++ + setRequestHeaders: function() { +- var requestHeaders = +- ['X-Requested-With', 'XMLHttpRequest', +- 'X-Prototype-Version', Prototype.Version]; ++ var headers = { ++ 'X-Requested-With': 'XMLHttpRequest', ++ 'X-Prototype-Version': Prototype.Version, ++ 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' ++ }; + +- if (this.options.method == 'post') { +- requestHeaders.push('Content-type', +- 'application/x-www-form-urlencoded'); ++ if (this.method == 'post') { ++ headers['Content-type'] = this.options.contentType + ++ (this.options.encoding ? '; charset=' + this.options.encoding : ''); + +- /* Force "Connection: close" for Mozilla browsers to work around +- * a bug where XMLHttpReqeuest sends an incorrect Content-length +- * header. See Mozilla Bugzilla #246651. ++ /* Force "Connection: close" for older Mozilla browsers to work ++ * around a bug where XMLHttpRequest sends an incorrect ++ * Content-length header. See Mozilla Bugzilla #246651. + */ +- if (this.transport.overrideMimeType) +- requestHeaders.push('Connection', 'close'); ++ if (this.transport.overrideMimeType && ++ (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) ++ headers['Connection'] = 'close'; + } + +- if (this.options.requestHeaders) +- requestHeaders.push.apply(requestHeaders, this.options.requestHeaders); ++ // user-defined headers ++ if (typeof this.options.requestHeaders == 'object') { ++ var extras = this.options.requestHeaders; + +- for (var i = 0; i < requestHeaders.length; i += 2) +- this.transport.setRequestHeader(requestHeaders[i], requestHeaders[i+1]); +- }, ++ if (typeof extras.push == 'function') ++ for (var i = 0, length = extras.length; i < length; i += 2) ++ headers[extras[i]] = extras[i+1]; ++ else ++ $H(extras).each(function(pair) { headers[pair.key] = pair.value }); ++ } + +- onStateChange: function() { +- var readyState = this.transport.readyState; +- if (readyState != 1) +- this.respondToReadyState(this.transport.readyState); ++ for (var name in headers) ++ this.transport.setRequestHeader(name, headers[name]); + }, + +- header: function(name) { +- try { +- return this.transport.getResponseHeader(name); +- } catch (e) {} ++ success: function() { ++ return !this.transport.status ++ || (this.transport.status >= 200 && this.transport.status < 300); + }, + +- evalJSON: function() { +- try { +- return eval(this.header('X-JSON')); +- } catch (e) {} +- }, +- +- evalResponse: function() { +- try { +- return eval(this.transport.responseText); +- } catch (e) { +- this.dispatchException(e); +- } +- }, +- + respondToReadyState: function(readyState) { +- var event = Ajax.Request.Events[readyState]; ++ var state = Ajax.Request.Events[readyState]; + var transport = this.transport, json = this.evalJSON(); + +- if (event == 'Complete') { ++ if (state == 'Complete') { + try { ++ this._complete = true; + (this.options['on' + this.transport.status] +- || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')] ++ || this.options['on' + (this.success() ? 'Success' : 'Failure')] + || Prototype.emptyFunction)(transport, json); + } catch (e) { + this.dispatchException(e); + } + +- if ((this.header('Content-type') || '').match(/^text\/javascript/i)) +- this.evalResponse(); ++ var contentType = this.getHeader('Content-type'); ++ if (contentType && this.isSameOrigin() && contentType.strip(). ++ match(/^(text|application)\/(x-)?(java|ecma)script(;.*)?$/i)) ++ this.evalResponse(); + } + + try { +- (this.options['on' + event] || Prototype.emptyFunction)(transport, json); +- Ajax.Responders.dispatch('on' + event, this, transport, json); ++ (this.options['on' + state] || Prototype.emptyFunction)(transport, json); ++ Ajax.Responders.dispatch('on' + state, this, transport, json); + } catch (e) { + this.dispatchException(e); + } + +- /* Avoid memory leak in MSIE: clean up the oncomplete event handler */ +- if (event == 'Complete') ++ if (state == 'Complete') { ++ // avoid memory leak in MSIE: clean up + this.transport.onreadystatechange = Prototype.emptyFunction; ++ } + }, + ++ isSameOrigin: function() { ++ var m = this.url.match(/^\s*https?:\/\/[^\/]*/); ++ return !m || (m[0] == new Template('#{protocol}//#{domain}#{port}').evaluate({ ++ protocol: location.protocol, ++ domain: document.domain, ++ port: location.port ? ':' + location.port : '' ++ })); ++ }, ++ ++ getHeader: function(name) { ++ try { ++ return this.transport.getResponseHeader(name); ++ } catch (e) { return null } ++ }, ++ ++ evalJSON: function() { ++ try { ++ var json = this.getHeader('X-JSON'); ++ return json ? json.evalJSON(!this.isSameOrigin()) : null; ++ } catch (e) { return null } ++ }, ++ ++ evalResponse: function() { ++ try { ++ return eval((this.transport.responseText || '').unfilterJSON()); ++ } catch (e) { ++ this.dispatchException(e); ++ } ++ }, ++ + dispatchException: function(exception) { + (this.options.onException || Prototype.emptyFunction)(this, exception); + Ajax.Responders.dispatch('onException', this, exception); +@@ -744,41 +1176,37 @@ + + Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), { + initialize: function(container, url, options) { +- this.containers = { +- success: container.success ? $(container.success) : $(container), +- failure: container.failure ? $(container.failure) : +- (container.success ? null : $(container)) ++ this.container = { ++ success: (container.success || container), ++ failure: (container.failure || (container.success ? null : container)) + } + + this.transport = Ajax.getTransport(); + this.setOptions(options); + + var onComplete = this.options.onComplete || Prototype.emptyFunction; +- this.options.onComplete = (function(transport, object) { ++ this.options.onComplete = (function(transport, param) { + this.updateContent(); +- onComplete(transport, object); ++ onComplete(transport, param); + }).bind(this); + + this.request(url); + }, + + updateContent: function() { +- var receiver = this.responseIsSuccess() ? +- this.containers.success : this.containers.failure; ++ var receiver = this.container[this.success() ? 'success' : 'failure']; + var response = this.transport.responseText; + +- if (!this.options.evalScripts) +- response = response.stripScripts(); ++ if (!this.options.evalScripts) response = response.stripScripts(); + +- if (receiver) { +- if (this.options.insertion) { ++ if (receiver = $(receiver)) { ++ if (this.options.insertion) + new this.options.insertion(receiver, response); +- } else { +- Element.update(receiver, response); +- } ++ else ++ receiver.update(response); + } + +- if (this.responseIsSuccess()) { ++ if (this.success()) { + if (this.onComplete) + setTimeout(this.onComplete.bind(this), 10); + } +@@ -807,7 +1235,7 @@ + }, + + stop: function() { +- this.updater.onComplete = undefined; ++ this.updater.options.onComplete = undefined; + clearTimeout(this.timer); + (this.onComplete || Prototype.emptyFunction).apply(this, arguments); + }, +@@ -827,129 +1255,370 @@ + this.updater = new Ajax.Updater(this.container, this.url, this.options); + } + }); +-document.getElementsByClassName = function(className, parentElement) { +- var children = ($(parentElement) || document.body).getElementsByTagName('*'); +- return $A(children).inject([], function(elements, child) { +- if (child.className.match(new RegExp("(^|\\s)" + className + "(\\s|$)"))) +- elements.push(child); ++function $(element) { ++ if (arguments.length > 1) { ++ for (var i = 0, elements = [], length = arguments.length; i < length; i++) ++ elements.push($(arguments[i])); + return elements; +- }); ++ } ++ if (typeof element == 'string') ++ element = document.getElementById(element); ++ return Element.extend(element); + } + ++if (Prototype.BrowserFeatures.XPath) { ++ document._getElementsByXPath = function(expression, parentElement) { ++ var results = []; ++ var query = document.evaluate(expression, $(parentElement) || document, ++ null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); ++ for (var i = 0, length = query.snapshotLength; i < length; i++) ++ results.push(query.snapshotItem(i)); ++ return results; ++ }; ++ ++ document.getElementsByClassName = function(className, parentElement) { ++ var q = ".//*[contains(concat(' ', @class, ' '), ' " + className + " ')]"; ++ return document._getElementsByXPath(q, parentElement); ++ } ++ ++} else document.getElementsByClassName = function(className, parentElement) { ++ var children = ($(parentElement) || document.body).getElementsByTagName('*'); ++ var elements = [], child, pattern = new RegExp("(^|\\s)" + className + "(\\s|$)"); ++ for (var i = 0, length = children.length; i < length; i++) { ++ child = children[i]; ++ var elementClassName = child.className; ++ if (elementClassName.length == 0) continue; ++ if (elementClassName == className || elementClassName.match(pattern)) ++ elements.push(Element.extend(child)); ++ } ++ return elements; ++}; ++ + /*--------------------------------------------------------------------------*/ + +-if (!window.Element) { +- var Element = new Object(); +-} ++if (!window.Element) var Element = {}; + +-Object.extend(Element, { ++Element.extend = function(element) { ++ var F = Prototype.BrowserFeatures; ++ if (!element || !element.tagName || element.nodeType == 3 || ++ element._extended || F.SpecificElementExtensions || element == window) ++ return element; ++ ++ var methods = {}, tagName = element.tagName, cache = Element.extend.cache, ++ T = Element.Methods.ByTag; ++ ++ // extend methods for all tags (Safari doesn't need this) ++ if (!F.ElementExtensions) { ++ Object.extend(methods, Element.Methods), ++ Object.extend(methods, Element.Methods.Simulated); ++ } ++ ++ // extend methods for specific tags ++ if (T[tagName]) Object.extend(methods, T[tagName]); ++ ++ for (var property in methods) { ++ var value = methods[property]; ++ if (typeof value == 'function' && !(property in element)) ++ element[property] = cache.findOrStore(value); ++ } ++ ++ element._extended = Prototype.emptyFunction; ++ return element; ++}; ++ ++Element.extend.cache = { ++ findOrStore: function(value) { ++ return this[value] = this[value] || function() { ++ return value.apply(null, [this].concat($A(arguments))); ++ } ++ } ++}; ++ ++Element.Methods = { + visible: function(element) { + return $(element).style.display != 'none'; + }, + +- toggle: function() { +- for (var i = 0; i < arguments.length; i++) { +- var element = $(arguments[i]); +- Element[Element.visible(element) ? 'hide' : 'show'](element); +- } ++ toggle: function(element) { ++ element = $(element); ++ Element[Element.visible(element) ? 'hide' : 'show'](element); ++ return element; + }, + +- hide: function() { +- for (var i = 0; i < arguments.length; i++) { +- var element = $(arguments[i]); +- element.style.display = 'none'; +- } ++ hide: function(element) { ++ $(element).style.display = 'none'; ++ return element; + }, + +- show: function() { +- for (var i = 0; i < arguments.length; i++) { +- var element = $(arguments[i]); +- element.style.display = ''; +- } ++ show: function(element) { ++ $(element).style.display = ''; ++ return element; + }, + + remove: function(element) { + element = $(element); + element.parentNode.removeChild(element); ++ return element; + }, + + update: function(element, html) { ++ html = typeof html == 'undefined' ? '' : html.toString(); + $(element).innerHTML = html.stripScripts(); + setTimeout(function() {html.evalScripts()}, 10); ++ return element; + }, + +- getHeight: function(element) { ++ replace: function(element, html) { + element = $(element); +- return element.offsetHeight; ++ html = typeof html == 'undefined' ? '' : html.toString(); ++ if (element.outerHTML) { ++ element.outerHTML = html.stripScripts(); ++ } else { ++ var range = element.ownerDocument.createRange(); ++ range.selectNodeContents(element); ++ element.parentNode.replaceChild( ++ range.createContextualFragment(html.stripScripts()), element); ++ } ++ setTimeout(function() {html.evalScripts()}, 10); ++ return element; + }, + ++ inspect: function(element) { ++ element = $(element); ++ var result = '<' + element.tagName.toLowerCase(); ++ $H({'id': 'id', 'className': 'class'}).each(function(pair) { ++ var property = pair.first(), attribute = pair.last(); ++ var value = (element[property] || '').toString(); ++ if (value) result += ' ' + attribute + '=' + value.inspect(true); ++ }); ++ return result + '>'; ++ }, ++ ++ recursivelyCollect: function(element, property) { ++ element = $(element); ++ var elements = []; ++ while (element = element[property]) ++ if (element.nodeType == 1) ++ elements.push(Element.extend(element)); ++ return elements; ++ }, ++ ++ ancestors: function(element) { ++ return $(element).recursivelyCollect('parentNode'); ++ }, ++ ++ descendants: function(element) { ++ return $A($(element).getElementsByTagName('*')).each(Element.extend); ++ }, ++ ++ firstDescendant: function(element) { ++ element = $(element).firstChild; ++ while (element && element.nodeType != 1) element = element.nextSibling; ++ return $(element); ++ }, ++ ++ immediateDescendants: function(element) { ++ if (!(element = $(element).firstChild)) return []; ++ while (element && element.nodeType != 1) element = element.nextSibling; ++ if (element) return [element].concat($(element).nextSiblings()); ++ return []; ++ }, ++ ++ previousSiblings: function(element) { ++ return $(element).recursivelyCollect('previousSibling'); ++ }, ++ ++ nextSiblings: function(element) { ++ return $(element).recursivelyCollect('nextSibling'); ++ }, ++ ++ siblings: function(element) { ++ element = $(element); ++ return element.previousSiblings().reverse().concat(element.nextSiblings()); ++ }, ++ ++ match: function(element, selector) { ++ if (typeof selector == 'string') ++ selector = new Selector(selector); ++ return selector.match($(element)); ++ }, ++ ++ up: function(element, expression, index) { ++ element = $(element); ++ if (arguments.length == 1) return $(element.parentNode); ++ var ancestors = element.ancestors(); ++ return expression ? Selector.findElement(ancestors, expression, index) : ++ ancestors[index || 0]; ++ }, ++ ++ down: function(element, expression, index) { ++ element = $(element); ++ if (arguments.length == 1) return element.firstDescendant(); ++ var descendants = element.descendants(); ++ return expression ? Selector.findElement(descendants, expression, index) : ++ descendants[index || 0]; ++ }, ++ ++ previous: function(element, expression, index) { ++ element = $(element); ++ if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element)); ++ var previousSiblings = element.previousSiblings(); ++ return expression ? Selector.findElement(previousSiblings, expression, index) : ++ previousSiblings[index || 0]; ++ }, ++ ++ next: function(element, expression, index) { ++ element = $(element); ++ if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element)); ++ var nextSiblings = element.nextSiblings(); ++ return expression ? Selector.findElement(nextSiblings, expression, index) : ++ nextSiblings[index || 0]; ++ }, ++ ++ getElementsBySelector: function() { ++ var args = $A(arguments), element = $(args.shift()); ++ return Selector.findChildElements(element, args); ++ }, ++ ++ getElementsByClassName: function(element, className) { ++ return document.getElementsByClassName(className, element); ++ }, ++ ++ readAttribute: function(element, name) { ++ element = $(element); ++ if (Prototype.Browser.IE) { ++ if (!element.attributes) return null; ++ var t = Element._attributeTranslations; ++ if (t.values[name]) return t.values[name](element, name); ++ if (t.names[name]) name = t.names[name]; ++ var attribute = element.attributes[name]; ++ return attribute ? attribute.nodeValue : null; ++ } ++ return element.getAttribute(name); ++ }, ++ ++ getHeight: function(element) { ++ return $(element).getDimensions().height; ++ }, ++ ++ getWidth: function(element) { ++ return $(element).getDimensions().width; ++ }, ++ + classNames: function(element) { + return new Element.ClassNames(element); + }, + + hasClassName: function(element, className) { + if (!(element = $(element))) return; +- return Element.classNames(element).include(className); ++ var elementClassName = element.className; ++ if (elementClassName.length == 0) return false; ++ if (elementClassName == className || ++ elementClassName.match(new RegExp("(^|\\s)" + className + "(\\s|$)"))) ++ return true; ++ return false; + }, + + addClassName: function(element, className) { + if (!(element = $(element))) return; +- return Element.classNames(element).add(className); ++ Element.classNames(element).add(className); ++ return element; + }, + + removeClassName: function(element, className) { + if (!(element = $(element))) return; +- return Element.classNames(element).remove(className); ++ Element.classNames(element).remove(className); ++ return element; + }, + ++ toggleClassName: function(element, className) { ++ if (!(element = $(element))) return; ++ Element.classNames(element)[element.hasClassName(className) ? 'remove' : 'add'](className); ++ return element; ++ }, ++ ++ observe: function() { ++ Event.observe.apply(Event, arguments); ++ return $A(arguments).first(); ++ }, ++ ++ stopObserving: function() { ++ Event.stopObserving.apply(Event, arguments); ++ return $A(arguments).first(); ++ }, ++ + // removes whitespace-only text node children + cleanWhitespace: function(element) { + element = $(element); +- for (var i = 0; i < element.childNodes.length; i++) { +- var node = element.childNodes[i]; ++ var node = element.firstChild; ++ while (node) { ++ var nextNode = node.nextSibling; + if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) +- Element.remove(node); ++ element.removeChild(node); ++ node = nextNode; + } ++ return element; + }, + + empty: function(element) { +- return $(element).innerHTML.match(/^\s*$/); ++ return $(element).innerHTML.blank(); + }, + ++ descendantOf: function(element, ancestor) { ++ element = $(element), ancestor = $(ancestor); ++ while (element = element.parentNode) ++ if (element == ancestor) return true; ++ return false; ++ }, ++ + scrollTo: function(element) { + element = $(element); +- var x = element.x ? element.x : element.offsetLeft, +- y = element.y ? element.y : element.offsetTop; +- window.scrollTo(x, y); ++ var pos = Position.cumulativeOffset(element); ++ window.scrollTo(pos[0], pos[1]); ++ return element; + }, + + getStyle: function(element, style) { + element = $(element); +- var value = element.style[style.camelize()]; ++ style = style == 'float' ? 'cssFloat' : style.camelize(); ++ var value = element.style[style]; + if (!value) { +- if (document.defaultView && document.defaultView.getComputedStyle) { +- var css = document.defaultView.getComputedStyle(element, null); +- value = css ? css.getPropertyValue(style) : null; +- } else if (element.currentStyle) { +- value = element.currentStyle[style.camelize()]; +- } ++ var css = document.defaultView.getComputedStyle(element, null); ++ value = css ? css[style] : null; + } ++ if (style == 'opacity') return value ? parseFloat(value) : 1.0; ++ return value == 'auto' ? null : value; ++ }, + +- if (window.opera && ['left', 'top', 'right', 'bottom'].include(style)) +- if (Element.getStyle(element, 'position') == 'static') value = 'auto'; ++ getOpacity: function(element) { ++ return $(element).getStyle('opacity'); ++ }, + +- return value == 'auto' ? null : value; ++ setStyle: function(element, styles, camelized) { ++ element = $(element); ++ var elementStyle = element.style; ++ ++ for (var property in styles) ++ if (property == 'opacity') element.setOpacity(styles[property]) ++ else ++ elementStyle[(property == 'float' || property == 'cssFloat') ? ++ (elementStyle.styleFloat === undefined ? 'cssFloat' : 'styleFloat') : ++ (camelized ? property : property.camelize())] = styles[property]; ++ ++ return element; + }, + +- setStyle: function(element, style) { ++ setOpacity: function(element, value) { + element = $(element); +- for (name in style) +- element.style[name.camelize()] = style[name]; ++ element.style.opacity = (value == 1 || value === '') ? '' : ++ (value < 0.00001) ? 0 : value; ++ return element; + }, + + getDimensions: function(element) { + element = $(element); +- if (Element.getStyle(element, 'display') != 'none') ++ var display = $(element).getStyle('display'); ++ if (display != 'none' && display != null) // Safari bug + return {width: element.offsetWidth, height: element.offsetHeight}; + + // All *Width and *Height properties give 0 on elements with display none, +@@ -957,12 +1626,13 @@ + var els = element.style; + var originalVisibility = els.visibility; + var originalPosition = els.position; ++ var originalDisplay = els.display; + els.visibility = 'hidden'; + els.position = 'absolute'; +- els.display = ''; ++ els.display = 'block'; + var originalWidth = element.clientWidth; + var originalHeight = element.clientHeight; +- els.display = 'none'; ++ els.display = originalDisplay; + els.position = originalPosition; + els.visibility = originalVisibility; + return {width: originalWidth, height: originalHeight}; +@@ -981,6 +1651,7 @@ + element.style.left = 0; + } + } ++ return element; + }, + + undoPositioned: function(element) { +@@ -993,27 +1664,271 @@ + element.style.bottom = + element.style.right = ''; + } ++ return element; + }, + + makeClipping: function(element) { + element = $(element); +- if (element._overflow) return; +- element._overflow = element.style.overflow; ++ if (element._overflow) return element; ++ element._overflow = element.style.overflow || 'auto'; + if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden') + element.style.overflow = 'hidden'; ++ return element; + }, + + undoClipping: function(element) { + element = $(element); +- if (element._overflow) return; +- element.style.overflow = element._overflow; +- element._overflow = undefined; ++ if (!element._overflow) return element; ++ element.style.overflow = element._overflow == 'auto' ? '' : element._overflow; ++ element._overflow = null; ++ return element; + } ++}; ++ ++Object.extend(Element.Methods, { ++ childOf: Element.Methods.descendantOf, ++ childElements: Element.Methods.immediateDescendants + }); + +-var Toggle = new Object(); +-Toggle.display = Element.toggle; ++if (Prototype.Browser.Opera) { ++ Element.Methods._getStyle = Element.Methods.getStyle; ++ Element.Methods.getStyle = function(element, style) { ++ switch(style) { ++ case 'left': ++ case 'top': ++ case 'right': ++ case 'bottom': ++ if (Element._getStyle(element, 'position') == 'static') return null; ++ default: return Element._getStyle(element, style); ++ } ++ }; ++} ++else if (Prototype.Browser.IE) { ++ Element.Methods.getStyle = function(element, style) { ++ element = $(element); ++ style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize(); ++ var value = element.style[style]; ++ if (!value && element.currentStyle) value = element.currentStyle[style]; + ++ if (style == 'opacity') { ++ if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/)) ++ if (value[1]) return parseFloat(value[1]) / 100; ++ return 1.0; ++ } ++ ++ if (value == 'auto') { ++ if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none')) ++ return element['offset'+style.capitalize()] + 'px'; ++ return null; ++ } ++ return value; ++ }; ++ ++ Element.Methods.setOpacity = function(element, value) { ++ element = $(element); ++ var filter = element.getStyle('filter'), style = element.style; ++ if (value == 1 || value === '') { ++ style.filter = filter.replace(/alpha\([^\)]*\)/gi,''); ++ return element; ++ } else if (value < 0.00001) value = 0; ++ style.filter = filter.replace(/alpha\([^\)]*\)/gi, '') + ++ 'alpha(opacity=' + (value * 100) + ')'; ++ return element; ++ }; ++ ++ // IE is missing .innerHTML support for TABLE-related elements ++ Element.Methods.update = function(element, html) { ++ element = $(element); ++ html = typeof html == 'undefined' ? '' : html.toString(); ++ var tagName = element.tagName.toUpperCase(); ++ if (['THEAD','TBODY','TR','TD'].include(tagName)) { ++ var div = document.createElement('div'); ++ switch (tagName) { ++ case 'THEAD': ++ case 'TBODY': ++ div.innerHTML = '' + html.stripScripts() + '
'; ++ depth = 2; ++ break; ++ case 'TR': ++ div.innerHTML = '' + html.stripScripts() + '
'; ++ depth = 3; ++ break; ++ case 'TD': ++ div.innerHTML = '
' + html.stripScripts() + '
'; ++ depth = 4; ++ } ++ $A(element.childNodes).each(function(node) { element.removeChild(node) }); ++ depth.times(function() { div = div.firstChild }); ++ $A(div.childNodes).each(function(node) { element.appendChild(node) }); ++ } else { ++ element.innerHTML = html.stripScripts(); ++ } ++ setTimeout(function() { html.evalScripts() }, 10); ++ return element; ++ } ++} ++else if (Prototype.Browser.Gecko) { ++ Element.Methods.setOpacity = function(element, value) { ++ element = $(element); ++ element.style.opacity = (value == 1) ? 0.999999 : ++ (value === '') ? '' : (value < 0.00001) ? 0 : value; ++ return element; ++ }; ++} ++ ++Element._attributeTranslations = { ++ names: { ++ colspan: "colSpan", ++ rowspan: "rowSpan", ++ valign: "vAlign", ++ datetime: "dateTime", ++ accesskey: "accessKey", ++ tabindex: "tabIndex", ++ enctype: "encType", ++ maxlength: "maxLength", ++ readonly: "readOnly", ++ longdesc: "longDesc" ++ }, ++ values: { ++ _getAttr: function(element, attribute) { ++ return element.getAttribute(attribute, 2); ++ }, ++ _flag: function(element, attribute) { ++ return $(element).hasAttribute(attribute) ? attribute : null; ++ }, ++ style: function(element) { ++ return element.style.cssText.toLowerCase(); ++ }, ++ title: function(element) { ++ var node = element.getAttributeNode('title'); ++ return node.specified ? node.nodeValue : null; ++ } ++ } ++}; ++ ++(function() { ++ Object.extend(this, { ++ href: this._getAttr, ++ src: this._getAttr, ++ type: this._getAttr, ++ disabled: this._flag, ++ checked: this._flag, ++ readonly: this._flag, ++ multiple: this._flag ++ }); ++}).call(Element._attributeTranslations.values); ++ ++Element.Methods.Simulated = { ++ hasAttribute: function(element, attribute) { ++ var t = Element._attributeTranslations, node; ++ attribute = t.names[attribute] || attribute; ++ node = $(element).getAttributeNode(attribute); ++ return node && node.specified; ++ } ++}; ++ ++Element.Methods.ByTag = {}; ++ ++Object.extend(Element, Element.Methods); ++ ++if (!Prototype.BrowserFeatures.ElementExtensions && ++ document.createElement('div').__proto__) { ++ window.HTMLElement = {}; ++ window.HTMLElement.prototype = document.createElement('div').__proto__; ++ Prototype.BrowserFeatures.ElementExtensions = true; ++} ++ ++Element.hasAttribute = function(element, attribute) { ++ if (element.hasAttribute) return element.hasAttribute(attribute); ++ return Element.Methods.Simulated.hasAttribute(element, attribute); ++}; ++ ++Element.addMethods = function(methods) { ++ var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag; ++ ++ if (!methods) { ++ Object.extend(Form, Form.Methods); ++ Object.extend(Form.Element, Form.Element.Methods); ++ Object.extend(Element.Methods.ByTag, { ++ "FORM": Object.clone(Form.Methods), ++ "INPUT": Object.clone(Form.Element.Methods), ++ "SELECT": Object.clone(Form.Element.Methods), ++ "TEXTAREA": Object.clone(Form.Element.Methods) ++ }); ++ } ++ ++ if (arguments.length == 2) { ++ var tagName = methods; ++ methods = arguments[1]; ++ } ++ ++ if (!tagName) Object.extend(Element.Methods, methods || {}); ++ else { ++ if (tagName.constructor == Array) tagName.each(extend); ++ else extend(tagName); ++ } ++ ++ function extend(tagName) { ++ tagName = tagName.toUpperCase(); ++ if (!Element.Methods.ByTag[tagName]) ++ Element.Methods.ByTag[tagName] = {}; ++ Object.extend(Element.Methods.ByTag[tagName], methods); ++ } ++ ++ function copy(methods, destination, onlyIfAbsent) { ++ onlyIfAbsent = onlyIfAbsent || false; ++ var cache = Element.extend.cache; ++ for (var property in methods) { ++ var value = methods[property]; ++ if (!onlyIfAbsent || !(property in destination)) ++ destination[property] = cache.findOrStore(value); ++ } ++ } ++ ++ function findDOMClass(tagName) { ++ var klass; ++ var trans = { ++ "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph", ++ "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList", ++ "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading", ++ "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote", ++ "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION": ++ "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD": ++ "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR": ++ "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET": ++ "FrameSet", "IFRAME": "IFrame" ++ }; ++ if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element'; ++ if (window[klass]) return window[klass]; ++ klass = 'HTML' + tagName + 'Element'; ++ if (window[klass]) return window[klass]; ++ klass = 'HTML' + tagName.capitalize() + 'Element'; ++ if (window[klass]) return window[klass]; ++ ++ window[klass] = {}; ++ window[klass].prototype = document.createElement(tagName).__proto__; ++ return window[klass]; ++ } ++ ++ if (F.ElementExtensions) { ++ copy(Element.Methods, HTMLElement.prototype); ++ copy(Element.Methods.Simulated, HTMLElement.prototype, true); ++ } ++ ++ if (F.SpecificElementExtensions) { ++ for (var tag in Element.Methods.ByTag) { ++ var klass = findDOMClass(tag); ++ if (typeof klass == "undefined") continue; ++ copy(T[tag], klass.prototype); ++ } ++ } ++ ++ Object.extend(Element, Element.Methods); ++ delete Element.ByTag; ++}; ++ ++var Toggle = { display: Element.toggle }; ++ + /*--------------------------------------------------------------------------*/ + + Abstract.Insertion = function(adjacency) { +@@ -1029,7 +1944,8 @@ + try { + this.element.insertAdjacentHTML(this.adjacency, this.content); + } catch (e) { +- if (this.element.tagName.toLowerCase() == 'tbody') { ++ var tagName = this.element.tagName.toUpperCase(); ++ if (['TBODY', 'TR'].include(tagName)) { + this.insertContent(this.contentFromAnonymousTable()); + } else { + throw e; +@@ -1128,220 +2044,814 @@ + + add: function(classNameToAdd) { + if (this.include(classNameToAdd)) return; +- this.set(this.toArray().concat(classNameToAdd).join(' ')); ++ this.set($A(this).concat(classNameToAdd).join(' ')); + }, + + remove: function(classNameToRemove) { + if (!this.include(classNameToRemove)) return; +- this.set(this.select(function(className) { +- return className != classNameToRemove; +- }).join(' ')); ++ this.set($A(this).without(classNameToRemove).join(' ')); + }, + + toString: function() { +- return this.toArray().join(' '); ++ return $A(this).join(' '); + } +-} ++}; + + Object.extend(Element.ClassNames.prototype, Enumerable); +-var Field = { +- clear: function() { +- for (var i = 0; i < arguments.length; i++) +- $(arguments[i]).value = ''; ++/* Portions of the Selector class are derived from Jack Slocum’s DomQuery, ++ * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style ++ * license. Please see http://www.yui-ext.com/ for more information. */ ++ ++var Selector = Class.create(); ++ ++Selector.prototype = { ++ initialize: function(expression) { ++ this.expression = expression.strip(); ++ this.compileMatcher(); + }, + +- focus: function(element) { +- $(element).focus(); ++ compileMatcher: function() { ++ // Selectors with namespaced attributes can't use the XPath version ++ if (Prototype.BrowserFeatures.XPath && !(/\[[\w-]*?:/).test(this.expression)) ++ return this.compileXPathMatcher(); ++ ++ var e = this.expression, ps = Selector.patterns, h = Selector.handlers, ++ c = Selector.criteria, le, p, m; ++ ++ if (Selector._cache[e]) { ++ this.matcher = Selector._cache[e]; return; ++ } ++ this.matcher = ["this.matcher = function(root) {", ++ "var r = root, h = Selector.handlers, c = false, n;"]; ++ ++ while (e && le != e && (/\S/).test(e)) { ++ le = e; ++ for (var i in ps) { ++ p = ps[i]; ++ if (m = e.match(p)) { ++ this.matcher.push(typeof c[i] == 'function' ? c[i](m) : ++ new Template(c[i]).evaluate(m)); ++ e = e.replace(m[0], ''); ++ break; ++ } ++ } ++ } ++ ++ this.matcher.push("return h.unique(n);\n}"); ++ eval(this.matcher.join('\n')); ++ Selector._cache[this.expression] = this.matcher; + }, + +- present: function() { +- for (var i = 0; i < arguments.length; i++) +- if ($(arguments[i]).value == '') return false; +- return true; ++ compileXPathMatcher: function() { ++ var e = this.expression, ps = Selector.patterns, ++ x = Selector.xpath, le, m; ++ ++ if (Selector._cache[e]) { ++ this.xpath = Selector._cache[e]; return; ++ } ++ ++ this.matcher = ['.//*']; ++ while (e && le != e && (/\S/).test(e)) { ++ le = e; ++ for (var i in ps) { ++ if (m = e.match(ps[i])) { ++ this.matcher.push(typeof x[i] == 'function' ? x[i](m) : ++ new Template(x[i]).evaluate(m)); ++ e = e.replace(m[0], ''); ++ break; ++ } ++ } ++ } ++ ++ this.xpath = this.matcher.join(''); ++ Selector._cache[this.expression] = this.xpath; + }, + +- select: function(element) { +- $(element).select(); ++ findElements: function(root) { ++ root = root || document; ++ if (this.xpath) return document._getElementsByXPath(this.xpath, root); ++ return this.matcher(root); + }, + +- activate: function(element) { +- element = $(element); +- element.focus(); +- if (element.select) +- element.select(); ++ match: function(element) { ++ return this.findElements(document).include(element); ++ }, ++ ++ toString: function() { ++ return this.expression; ++ }, ++ ++ inspect: function() { ++ return "#"; + } +-} ++}; + +-/*--------------------------------------------------------------------------*/ ++Object.extend(Selector, { ++ _cache: {}, + +-var Form = { +- serialize: function(form) { +- var elements = Form.getElements($(form)); +- var queryComponents = new Array(); ++ xpath: { ++ descendant: "//*", ++ child: "/*", ++ adjacent: "/following-sibling::*[1]", ++ laterSibling: '/following-sibling::*', ++ tagName: function(m) { ++ if (m[1] == '*') return ''; ++ return "[local-name()='" + m[1].toLowerCase() + ++ "' or local-name()='" + m[1].toUpperCase() + "']"; ++ }, ++ className: "[contains(concat(' ', @class, ' '), ' #{1} ')]", ++ id: "[@id='#{1}']", ++ attrPresence: "[@#{1}]", ++ attr: function(m) { ++ m[3] = m[5] || m[6]; ++ return new Template(Selector.xpath.operators[m[2]]).evaluate(m); ++ }, ++ pseudo: function(m) { ++ var h = Selector.xpath.pseudos[m[1]]; ++ if (!h) return ''; ++ if (typeof h === 'function') return h(m); ++ return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m); ++ }, ++ operators: { ++ '=': "[@#{1}='#{3}']", ++ '!=': "[@#{1}!='#{3}']", ++ '^=': "[starts-with(@#{1}, '#{3}')]", ++ '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']", ++ '*=': "[contains(@#{1}, '#{3}')]", ++ '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]", ++ '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]" ++ }, ++ pseudos: { ++ 'first-child': '[not(preceding-sibling::*)]', ++ 'last-child': '[not(following-sibling::*)]', ++ 'only-child': '[not(preceding-sibling::* or following-sibling::*)]', ++ 'empty': "[count(*) = 0 and (count(text()) = 0 or translate(text(), ' \t\r\n', '') = '')]", ++ 'checked': "[@checked]", ++ 'disabled': "[@disabled]", ++ 'enabled': "[not(@disabled)]", ++ 'not': function(m) { ++ var e = m[6], p = Selector.patterns, ++ x = Selector.xpath, le, m, v; + +- for (var i = 0; i < elements.length; i++) { +- var queryComponent = Form.Element.serialize(elements[i]); +- if (queryComponent) +- queryComponents.push(queryComponent); ++ var exclusion = []; ++ while (e && le != e && (/\S/).test(e)) { ++ le = e; ++ for (var i in p) { ++ if (m = e.match(p[i])) { ++ v = typeof x[i] == 'function' ? x[i](m) : new Template(x[i]).evaluate(m); ++ exclusion.push("(" + v.substring(1, v.length - 1) + ")"); ++ e = e.replace(m[0], ''); ++ break; ++ } ++ } ++ } ++ return "[not(" + exclusion.join(" and ") + ")]"; ++ }, ++ 'nth-child': function(m) { ++ return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m); ++ }, ++ 'nth-last-child': function(m) { ++ return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m); ++ }, ++ 'nth-of-type': function(m) { ++ return Selector.xpath.pseudos.nth("position() ", m); ++ }, ++ 'nth-last-of-type': function(m) { ++ return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m); ++ }, ++ 'first-of-type': function(m) { ++ m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m); ++ }, ++ 'last-of-type': function(m) { ++ m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m); ++ }, ++ 'only-of-type': function(m) { ++ var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m); ++ }, ++ nth: function(fragment, m) { ++ var mm, formula = m[6], predicate; ++ if (formula == 'even') formula = '2n+0'; ++ if (formula == 'odd') formula = '2n+1'; ++ if (mm = formula.match(/^(\d+)$/)) // digit only ++ return '[' + fragment + "= " + mm[1] + ']'; ++ if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b ++ if (mm[1] == "-") mm[1] = -1; ++ var a = mm[1] ? Number(mm[1]) : 1; ++ var b = mm[2] ? Number(mm[2]) : 0; ++ predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " + ++ "((#{fragment} - #{b}) div #{a} >= 0)]"; ++ return new Template(predicate).evaluate({ ++ fragment: fragment, a: a, b: b }); ++ } ++ } + } ++ }, + +- return queryComponents.join('&'); ++ criteria: { ++ tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;', ++ className: 'n = h.className(n, r, "#{1}", c); c = false;', ++ id: 'n = h.id(n, r, "#{1}", c); c = false;', ++ attrPresence: 'n = h.attrPresence(n, r, "#{1}"); c = false;', ++ attr: function(m) { ++ m[3] = (m[5] || m[6]); ++ return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}"); c = false;').evaluate(m); ++ }, ++ pseudo: function(m) { ++ if (m[6]) m[6] = m[6].replace(/"/g, '\\"'); ++ return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m); ++ }, ++ descendant: 'c = "descendant";', ++ child: 'c = "child";', ++ adjacent: 'c = "adjacent";', ++ laterSibling: 'c = "laterSibling";' + }, + +- getElements: function(form) { +- form = $(form); +- var elements = new Array(); ++ patterns: { ++ // combinators must be listed first ++ // (and descendant needs to be last combinator) ++ laterSibling: /^\s*~\s*/, ++ child: /^\s*>\s*/, ++ adjacent: /^\s*\+\s*/, ++ descendant: /^\s/, + +- for (tagName in Form.Element.Serializers) { +- var tagElements = form.getElementsByTagName(tagName); +- for (var j = 0; j < tagElements.length; j++) +- elements.push(tagElements[j]); ++ // selectors follow ++ tagName: /^\s*(\*|[\w\-]+)(\b|$)?/, ++ id: /^#([\w\-\*]+)(\b|$)/, ++ className: /^\.([\w\-\*]+)(\b|$)/, ++ pseudo: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|\s|(?=:))/, ++ attrPresence: /^\[([\w]+)\]/, ++ attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\]]*?)\4|([^'"][^\]]*?)))?\]/ ++ }, ++ ++ handlers: { ++ // UTILITY FUNCTIONS ++ // joins two collections ++ concat: function(a, b) { ++ for (var i = 0, node; node = b[i]; i++) ++ a.push(node); ++ return a; ++ }, ++ ++ // marks an array of nodes for counting ++ mark: function(nodes) { ++ for (var i = 0, node; node = nodes[i]; i++) ++ node._counted = true; ++ return nodes; ++ }, ++ ++ unmark: function(nodes) { ++ for (var i = 0, node; node = nodes[i]; i++) ++ node._counted = undefined; ++ return nodes; ++ }, ++ ++ // mark each child node with its position (for nth calls) ++ // "ofType" flag indicates whether we're indexing for nth-of-type ++ // rather than nth-child ++ index: function(parentNode, reverse, ofType) { ++ parentNode._counted = true; ++ if (reverse) { ++ for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) { ++ node = nodes[i]; ++ if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++; ++ } ++ } else { ++ for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++) ++ if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++; ++ } ++ }, ++ ++ // filters out duplicates and extends all nodes ++ unique: function(nodes) { ++ if (nodes.length == 0) return nodes; ++ var results = [], n; ++ for (var i = 0, l = nodes.length; i < l; i++) ++ if (!(n = nodes[i])._counted) { ++ n._counted = true; ++ results.push(Element.extend(n)); ++ } ++ return Selector.handlers.unmark(results); ++ }, ++ ++ // COMBINATOR FUNCTIONS ++ descendant: function(nodes) { ++ var h = Selector.handlers; ++ for (var i = 0, results = [], node; node = nodes[i]; i++) ++ h.concat(results, node.getElementsByTagName('*')); ++ return results; ++ }, ++ ++ child: function(nodes) { ++ var h = Selector.handlers; ++ for (var i = 0, results = [], node; node = nodes[i]; i++) { ++ for (var j = 0, children = [], child; child = node.childNodes[j]; j++) ++ if (child.nodeType == 1 && child.tagName != '!') results.push(child); ++ } ++ return results; ++ }, ++ ++ adjacent: function(nodes) { ++ for (var i = 0, results = [], node; node = nodes[i]; i++) { ++ var next = this.nextElementSibling(node); ++ if (next) results.push(next); ++ } ++ return results; ++ }, ++ ++ laterSibling: function(nodes) { ++ var h = Selector.handlers; ++ for (var i = 0, results = [], node; node = nodes[i]; i++) ++ h.concat(results, Element.nextSiblings(node)); ++ return results; ++ }, ++ ++ nextElementSibling: function(node) { ++ while (node = node.nextSibling) ++ if (node.nodeType == 1) return node; ++ return null; ++ }, ++ ++ previousElementSibling: function(node) { ++ while (node = node.previousSibling) ++ if (node.nodeType == 1) return node; ++ return null; ++ }, ++ ++ // TOKEN FUNCTIONS ++ tagName: function(nodes, root, tagName, combinator) { ++ tagName = tagName.toUpperCase(); ++ var results = [], h = Selector.handlers; ++ if (nodes) { ++ if (combinator) { ++ // fastlane for ordinary descendant combinators ++ if (combinator == "descendant") { ++ for (var i = 0, node; node = nodes[i]; i++) ++ h.concat(results, node.getElementsByTagName(tagName)); ++ return results; ++ } else nodes = this[combinator](nodes); ++ if (tagName == "*") return nodes; ++ } ++ for (var i = 0, node; node = nodes[i]; i++) ++ if (node.tagName.toUpperCase() == tagName) results.push(node); ++ return results; ++ } else return root.getElementsByTagName(tagName); ++ }, ++ ++ id: function(nodes, root, id, combinator) { ++ var targetNode = $(id), h = Selector.handlers; ++ if (!nodes && root == document) return targetNode ? [targetNode] : []; ++ if (nodes) { ++ if (combinator) { ++ if (combinator == 'child') { ++ for (var i = 0, node; node = nodes[i]; i++) ++ if (targetNode.parentNode == node) return [targetNode]; ++ } else if (combinator == 'descendant') { ++ for (var i = 0, node; node = nodes[i]; i++) ++ if (Element.descendantOf(targetNode, node)) return [targetNode]; ++ } else if (combinator == 'adjacent') { ++ for (var i = 0, node; node = nodes[i]; i++) ++ if (Selector.handlers.previousElementSibling(targetNode) == node) ++ return [targetNode]; ++ } else nodes = h[combinator](nodes); ++ } ++ for (var i = 0, node; node = nodes[i]; i++) ++ if (node == targetNode) return [targetNode]; ++ return []; ++ } ++ return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : []; ++ }, ++ ++ className: function(nodes, root, className, combinator) { ++ if (nodes && combinator) nodes = this[combinator](nodes); ++ return Selector.handlers.byClassName(nodes, root, className); ++ }, ++ ++ byClassName: function(nodes, root, className) { ++ if (!nodes) nodes = Selector.handlers.descendant([root]); ++ var needle = ' ' + className + ' '; ++ for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) { ++ nodeClassName = node.className; ++ if (nodeClassName.length == 0) continue; ++ if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle)) ++ results.push(node); ++ } ++ return results; ++ }, ++ ++ attrPresence: function(nodes, root, attr) { ++ var results = []; ++ for (var i = 0, node; node = nodes[i]; i++) ++ if (Element.hasAttribute(node, attr)) results.push(node); ++ return results; ++ }, ++ ++ attr: function(nodes, root, attr, value, operator) { ++ if (!nodes) nodes = root.getElementsByTagName("*"); ++ var handler = Selector.operators[operator], results = []; ++ for (var i = 0, node; node = nodes[i]; i++) { ++ var nodeValue = Element.readAttribute(node, attr); ++ if (nodeValue === null) continue; ++ if (handler(nodeValue, value)) results.push(node); ++ } ++ return results; ++ }, ++ ++ pseudo: function(nodes, name, value, root, combinator) { ++ if (nodes && combinator) nodes = this[combinator](nodes); ++ if (!nodes) nodes = root.getElementsByTagName("*"); ++ return Selector.pseudos[name](nodes, value, root); + } +- return elements; + }, + ++ pseudos: { ++ 'first-child': function(nodes, value, root) { ++ for (var i = 0, results = [], node; node = nodes[i]; i++) { ++ if (Selector.handlers.previousElementSibling(node)) continue; ++ results.push(node); ++ } ++ return results; ++ }, ++ 'last-child': function(nodes, value, root) { ++ for (var i = 0, results = [], node; node = nodes[i]; i++) { ++ if (Selector.handlers.nextElementSibling(node)) continue; ++ results.push(node); ++ } ++ return results; ++ }, ++ 'only-child': function(nodes, value, root) { ++ var h = Selector.handlers; ++ for (var i = 0, results = [], node; node = nodes[i]; i++) ++ if (!h.previousElementSibling(node) && !h.nextElementSibling(node)) ++ results.push(node); ++ return results; ++ }, ++ 'nth-child': function(nodes, formula, root) { ++ return Selector.pseudos.nth(nodes, formula, root); ++ }, ++ 'nth-last-child': function(nodes, formula, root) { ++ return Selector.pseudos.nth(nodes, formula, root, true); ++ }, ++ 'nth-of-type': function(nodes, formula, root) { ++ return Selector.pseudos.nth(nodes, formula, root, false, true); ++ }, ++ 'nth-last-of-type': function(nodes, formula, root) { ++ return Selector.pseudos.nth(nodes, formula, root, true, true); ++ }, ++ 'first-of-type': function(nodes, formula, root) { ++ return Selector.pseudos.nth(nodes, "1", root, false, true); ++ }, ++ 'last-of-type': function(nodes, formula, root) { ++ return Selector.pseudos.nth(nodes, "1", root, true, true); ++ }, ++ 'only-of-type': function(nodes, formula, root) { ++ var p = Selector.pseudos; ++ return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root); ++ }, ++ ++ // handles the an+b logic ++ getIndices: function(a, b, total) { ++ if (a == 0) return b > 0 ? [b] : []; ++ return $R(1, total).inject([], function(memo, i) { ++ if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i); ++ return memo; ++ }); ++ }, ++ ++ // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type ++ nth: function(nodes, formula, root, reverse, ofType) { ++ if (nodes.length == 0) return []; ++ if (formula == 'even') formula = '2n+0'; ++ if (formula == 'odd') formula = '2n+1'; ++ var h = Selector.handlers, results = [], indexed = [], m; ++ h.mark(nodes); ++ for (var i = 0, node; node = nodes[i]; i++) { ++ if (!node.parentNode._counted) { ++ h.index(node.parentNode, reverse, ofType); ++ indexed.push(node.parentNode); ++ } ++ } ++ if (formula.match(/^\d+$/)) { // just a number ++ formula = Number(formula); ++ for (var i = 0, node; node = nodes[i]; i++) ++ if (node.nodeIndex == formula) results.push(node); ++ } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b ++ if (m[1] == "-") m[1] = -1; ++ var a = m[1] ? Number(m[1]) : 1; ++ var b = m[2] ? Number(m[2]) : 0; ++ var indices = Selector.pseudos.getIndices(a, b, nodes.length); ++ for (var i = 0, node, l = indices.length; node = nodes[i]; i++) { ++ for (var j = 0; j < l; j++) ++ if (node.nodeIndex == indices[j]) results.push(node); ++ } ++ } ++ h.unmark(nodes); ++ h.unmark(indexed); ++ return results; ++ }, ++ ++ 'empty': function(nodes, value, root) { ++ for (var i = 0, results = [], node; node = nodes[i]; i++) { ++ // IE treats comments as element nodes ++ if (node.tagName == '!' || (node.firstChild && !node.innerHTML.match(/^\s*$/))) continue; ++ results.push(node); ++ } ++ return results; ++ }, ++ ++ 'not': function(nodes, selector, root) { ++ var h = Selector.handlers, selectorType, m; ++ var exclusions = new Selector(selector).findElements(root); ++ h.mark(exclusions); ++ for (var i = 0, results = [], node; node = nodes[i]; i++) ++ if (!node._counted) results.push(node); ++ h.unmark(exclusions); ++ return results; ++ }, ++ ++ 'enabled': function(nodes, value, root) { ++ for (var i = 0, results = [], node; node = nodes[i]; i++) ++ if (!node.disabled) results.push(node); ++ return results; ++ }, ++ ++ 'disabled': function(nodes, value, root) { ++ for (var i = 0, results = [], node; node = nodes[i]; i++) ++ if (node.disabled) results.push(node); ++ return results; ++ }, ++ ++ 'checked': function(nodes, value, root) { ++ for (var i = 0, results = [], node; node = nodes[i]; i++) ++ if (node.checked) results.push(node); ++ return results; ++ } ++ }, ++ ++ operators: { ++ '=': function(nv, v) { return nv == v; }, ++ '!=': function(nv, v) { return nv != v; }, ++ '^=': function(nv, v) { return nv.startsWith(v); }, ++ '$=': function(nv, v) { return nv.endsWith(v); }, ++ '*=': function(nv, v) { return nv.include(v); }, ++ '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); }, ++ '|=': function(nv, v) { return ('-' + nv.toUpperCase() + '-').include('-' + v.toUpperCase() + '-'); } ++ }, ++ ++ matchElements: function(elements, expression) { ++ var matches = new Selector(expression).findElements(), h = Selector.handlers; ++ h.mark(matches); ++ for (var i = 0, results = [], element; element = elements[i]; i++) ++ if (element._counted) results.push(element); ++ h.unmark(matches); ++ return results; ++ }, ++ ++ findElement: function(elements, expression, index) { ++ if (typeof expression == 'number') { ++ index = expression; expression = false; ++ } ++ return Selector.matchElements(elements, expression || '*')[index || 0]; ++ }, ++ ++ findChildElements: function(element, expressions) { ++ var exprs = expressions.join(','), expressions = []; ++ exprs.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) { ++ expressions.push(m[1].strip()); ++ }); ++ var results = [], h = Selector.handlers; ++ for (var i = 0, l = expressions.length, selector; i < l; i++) { ++ selector = new Selector(expressions[i].strip()); ++ h.concat(results, selector.findElements(element)); ++ } ++ return (l > 1) ? h.unique(results) : results; ++ } ++}); ++ ++function $$() { ++ return Selector.findChildElements(document, $A(arguments)); ++} ++var Form = { ++ reset: function(form) { ++ $(form).reset(); ++ return form; ++ }, ++ ++ serializeElements: function(elements, getHash) { ++ var data = elements.inject({}, function(result, element) { ++ if (!element.disabled && element.name) { ++ var key = element.name, value = $(element).getValue(); ++ if (value != null) { ++ if (key in result) { ++ if (result[key].constructor != Array) result[key] = [result[key]]; ++ result[key].push(value); ++ } ++ else result[key] = value; ++ } ++ } ++ return result; ++ }); ++ ++ return getHash ? data : Hash.toQueryString(data); ++ } ++}; ++ ++Form.Methods = { ++ serialize: function(form, getHash) { ++ return Form.serializeElements(Form.getElements(form), getHash); ++ }, ++ ++ getElements: function(form) { ++ return $A($(form).getElementsByTagName('*')).inject([], ++ function(elements, child) { ++ if (Form.Element.Serializers[child.tagName.toLowerCase()]) ++ elements.push(Element.extend(child)); ++ return elements; ++ } ++ ); ++ }, ++ + getInputs: function(form, typeName, name) { + form = $(form); + var inputs = form.getElementsByTagName('input'); + +- if (!typeName && !name) +- return inputs; ++ if (!typeName && !name) return $A(inputs).map(Element.extend); + +- var matchingInputs = new Array(); +- for (var i = 0; i < inputs.length; i++) { ++ for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) { + var input = inputs[i]; +- if ((typeName && input.type != typeName) || +- (name && input.name != name)) ++ if ((typeName && input.type != typeName) || (name && input.name != name)) + continue; +- matchingInputs.push(input); ++ matchingInputs.push(Element.extend(input)); + } + + return matchingInputs; + }, + + disable: function(form) { +- var elements = Form.getElements(form); +- for (var i = 0; i < elements.length; i++) { +- var element = elements[i]; +- element.blur(); +- element.disabled = 'true'; +- } ++ form = $(form); ++ Form.getElements(form).invoke('disable'); ++ return form; + }, + + enable: function(form) { +- var elements = Form.getElements(form); +- for (var i = 0; i < elements.length; i++) { +- var element = elements[i]; +- element.disabled = ''; +- } ++ form = $(form); ++ Form.getElements(form).invoke('enable'); ++ return form; + }, + + findFirstElement: function(form) { +- return Form.getElements(form).find(function(element) { ++ return $(form).getElements().find(function(element) { + return element.type != 'hidden' && !element.disabled && + ['input', 'select', 'textarea'].include(element.tagName.toLowerCase()); + }); + }, + + focusFirstElement: function(form) { +- Field.activate(Form.findFirstElement(form)); ++ form = $(form); ++ form.findFirstElement().activate(); ++ return form; + }, + +- reset: function(form) { +- $(form).reset(); ++ request: function(form, options) { ++ form = $(form), options = Object.clone(options || {}); ++ ++ var params = options.parameters; ++ options.parameters = form.serialize(true); ++ ++ if (params) { ++ if (typeof params == 'string') params = params.toQueryParams(); ++ Object.extend(options.parameters, params); ++ } ++ ++ if (form.hasAttribute('method') && !options.method) ++ options.method = form.method; ++ ++ return new Ajax.Request(form.readAttribute('action'), options); + } + } + ++/*--------------------------------------------------------------------------*/ ++ + Form.Element = { ++ focus: function(element) { ++ $(element).focus(); ++ return element; ++ }, ++ ++ select: function(element) { ++ $(element).select(); ++ return element; ++ } ++} ++ ++Form.Element.Methods = { + serialize: function(element) { + element = $(element); ++ if (!element.disabled && element.name) { ++ var value = element.getValue(); ++ if (value != undefined) { ++ var pair = {}; ++ pair[element.name] = value; ++ return Hash.toQueryString(pair); ++ } ++ } ++ return ''; ++ }, ++ ++ getValue: function(element) { ++ element = $(element); + var method = element.tagName.toLowerCase(); +- var parameter = Form.Element.Serializers[method](element); ++ return Form.Element.Serializers[method](element); ++ }, + +- if (parameter) { +- var key = encodeURIComponent(parameter[0]); +- if (key.length == 0) return; ++ clear: function(element) { ++ $(element).value = ''; ++ return element; ++ }, + +- if (parameter[1].constructor != Array) +- parameter[1] = [parameter[1]]; ++ present: function(element) { ++ return $(element).value != ''; ++ }, + +- return parameter[1].map(function(value) { +- return key + '=' + encodeURIComponent(value); +- }).join('&'); +- } ++ activate: function(element) { ++ element = $(element); ++ try { ++ element.focus(); ++ if (element.select && (element.tagName.toLowerCase() != 'input' || ++ !['button', 'reset', 'submit'].include(element.type))) ++ element.select(); ++ } catch (e) {} ++ return element; + }, + +- getValue: function(element) { ++ disable: function(element) { + element = $(element); +- var method = element.tagName.toLowerCase(); +- var parameter = Form.Element.Serializers[method](element); ++ element.blur(); ++ element.disabled = true; ++ return element; ++ }, + +- if (parameter) +- return parameter[1]; ++ enable: function(element) { ++ element = $(element); ++ element.disabled = false; ++ return element; + } + } + ++/*--------------------------------------------------------------------------*/ ++ ++var Field = Form.Element; ++var $F = Form.Element.Methods.getValue; ++ ++/*--------------------------------------------------------------------------*/ ++ + Form.Element.Serializers = { + input: function(element) { + switch (element.type.toLowerCase()) { +- case 'submit': +- case 'hidden': +- case 'password': +- case 'text': +- return Form.Element.Serializers.textarea(element); + case 'checkbox': + case 'radio': + return Form.Element.Serializers.inputSelector(element); ++ default: ++ return Form.Element.Serializers.textarea(element); + } +- return false; + }, + + inputSelector: function(element) { +- if (element.checked) +- return [element.name, element.value]; ++ return element.checked ? element.value : null; + }, + + textarea: function(element) { +- return [element.name, element.value]; ++ return element.value; + }, + + select: function(element) { +- return Form.Element.Serializers[element.type == 'select-one' ? ++ return this[element.type == 'select-one' ? + 'selectOne' : 'selectMany'](element); + }, + + selectOne: function(element) { +- var value = '', opt, index = element.selectedIndex; +- if (index >= 0) { +- opt = element.options[index]; +- value = opt.value; +- if (!value && !('value' in opt)) +- value = opt.text; +- } +- return [element.name, value]; ++ var index = element.selectedIndex; ++ return index >= 0 ? this.optionValue(element.options[index]) : null; + }, + + selectMany: function(element) { +- var value = new Array(); +- for (var i = 0; i < element.length; i++) { ++ var values, length = element.length; ++ if (!length) return null; ++ ++ for (var i = 0, values = []; i < length; i++) { + var opt = element.options[i]; +- if (opt.selected) { +- var optValue = opt.value; +- if (!optValue && !('value' in opt)) +- optValue = opt.text; +- value.push(optValue); +- } ++ if (opt.selected) values.push(this.optionValue(opt)); + } +- return [element.name, value]; ++ return values; ++ }, ++ ++ optionValue: function(opt) { ++ // extend element because hasAttribute may not be native ++ return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text; + } + } + + /*--------------------------------------------------------------------------*/ + +-var $F = Form.Element.getValue; +- +-/*--------------------------------------------------------------------------*/ +- + Abstract.TimedObserver = function() {} + Abstract.TimedObserver.prototype = { + initialize: function(element, frequency, callback) { +@@ -1359,7 +2869,9 @@ + + onTimerEvent: function() { + var value = this.getValue(); +- if (this.lastValue != value) { ++ var changed = ('string' == typeof this.lastValue && 'string' == typeof value ++ ? this.lastValue != value : String(this.lastValue) != String(value)); ++ if (changed) { + this.callback(this.element, value); + this.lastValue = value; + } +@@ -1404,9 +2916,7 @@ + }, + + registerFormCallbacks: function() { +- var elements = Form.getElements(this.element); +- for (var i = 0; i < elements.length; i++) +- this.registerCallback(elements[i]); ++ Form.getElements(this.element).each(this.registerCallback.bind(this)); + }, + + registerCallback: function(element) { +@@ -1416,11 +2926,7 @@ + case 'radio': + Event.observe(element, 'click', this.onElementEvent.bind(this)); + break; +- case 'password': +- case 'text': +- case 'textarea': +- case 'select-one': +- case 'select-multiple': ++ default: + Event.observe(element, 'change', this.onElementEvent.bind(this)); + break; + } +@@ -1455,9 +2961,13 @@ + KEY_RIGHT: 39, + KEY_DOWN: 40, + KEY_DELETE: 46, ++ KEY_HOME: 36, ++ KEY_END: 35, ++ KEY_PAGEUP: 33, ++ KEY_PAGEDOWN: 34, + + element: function(event) { +- return event.target || event.srcElement; ++ return $(event.target || event.srcElement); + }, + + isLeftClick: function(event) { +@@ -1510,7 +3020,7 @@ + + unloadCache: function() { + if (!Event.observers) return; +- for (var i = 0; i < Event.observers.length; i++) { ++ for (var i = 0, length = Event.observers.length; i < length; i++) { + Event.stopObserving.apply(this, Event.observers[i]); + Event.observers[i][0] = null; + } +@@ -1518,36 +3028,37 @@ + }, + + observe: function(element, name, observer, useCapture) { +- var element = $(element); ++ element = $(element); + useCapture = useCapture || false; + + if (name == 'keypress' && +- (navigator.appVersion.match(/Konqueror|Safari|KHTML/) +- || element.attachEvent)) ++ (Prototype.Browser.WebKit || element.attachEvent)) + name = 'keydown'; + +- this._observeAndCache(element, name, observer, useCapture); ++ Event._observeAndCache(element, name, observer, useCapture); + }, + + stopObserving: function(element, name, observer, useCapture) { +- var element = $(element); ++ element = $(element); + useCapture = useCapture || false; + + if (name == 'keypress' && +- (navigator.appVersion.match(/Konqueror|Safari|KHTML/) +- || element.detachEvent)) ++ (Prototype.Browser.WebKit || element.attachEvent)) + name = 'keydown'; + + if (element.removeEventListener) { + element.removeEventListener(name, observer, useCapture); + } else if (element.detachEvent) { +- element.detachEvent('on' + name, observer); ++ try { ++ element.detachEvent('on' + name, observer); ++ } catch (e) {} + } + } + }); + + /* prevent memory leaks in IE */ +-Event.observe(window, 'unload', Event.unloadCache, false); ++if (Prototype.Browser.IE) ++ Event.observe(window, 'unload', Event.unloadCache, false); + var Position = { + // set to true if needed, warning: firefox performance problems + // NOT neeeded for page scrolling, only if draggable contained in +@@ -1594,7 +3105,8 @@ + valueL += element.offsetLeft || 0; + element = element.offsetParent; + if (element) { +- p = Element.getStyle(element, 'position'); ++ if(element.tagName=='BODY') break; ++ var p = Element.getStyle(element, 'position'); + if (p == 'relative' || p == 'absolute') break; + } + } while (element); +@@ -1650,17 +3162,6 @@ + element.offsetWidth; + }, + +- clone: function(source, target) { +- source = $(source); +- target = $(target); +- target.style.position = 'absolute'; +- var offsets = this.cumulativeOffset(source); +- target.style.top = offsets[1] + 'px'; +- target.style.left = offsets[0] + 'px'; +- target.style.width = source.offsetWidth + 'px'; +- target.style.height = source.offsetHeight + 'px'; +- }, +- + page: function(forElement) { + var valueT = 0, valueL = 0; + +@@ -1670,15 +3171,17 @@ + valueL += element.offsetLeft || 0; + + // Safari fix +- if (element.offsetParent==document.body) ++ if (element.offsetParent == document.body) + if (Element.getStyle(element,'position')=='absolute') break; + + } while (element = element.offsetParent); + + element = forElement; + do { +- valueT -= element.scrollTop || 0; +- valueL -= element.scrollLeft || 0; ++ if (!window.opera || element.tagName=='BODY') { ++ valueT -= element.scrollTop || 0; ++ valueL -= element.scrollLeft || 0; ++ } + } while (element = element.parentNode); + + return [valueL, valueT]; +@@ -1739,10 +3242,10 @@ + element._originalHeight = element.style.height; + + element.style.position = 'absolute'; +- element.style.top = top + 'px';; +- element.style.left = left + 'px';; +- element.style.width = width + 'px';; +- element.style.height = height + 'px';; ++ element.style.top = top + 'px'; ++ element.style.left = left + 'px'; ++ element.style.width = width + 'px'; ++ element.style.height = height + 'px'; + }, + + relativize: function(element) { +@@ -1764,7 +3267,7 @@ + // Safari returns margins on body which is incorrect if the child is absolutely + // positioned. For performance reasons, redefine Position.cumulativeOffset for + // KHTML/WebKit only. +-if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) { ++if (Prototype.Browser.WebKit) { + Position.cumulativeOffset = function(element) { + var valueT = 0, valueL = 0; + do { +@@ -1778,4 +3281,6 @@ + + return [valueL, valueT]; + } +-} +\ No newline at end of file ++} ++ ++Element.addMethods(); +\ No newline at end of file diff --git a/patches/README b/patches/README new file mode 100644 index 0000000..d6db1c2 --- /dev/null +++ b/patches/README @@ -0,0 +1,169 @@ +Parches aplicados a Asterisk-es-RSP +================================ + +app_queue-state_interface.patch /* Añade la opcion state_interface a AddQueueMember() de manera que el estado de un Local/ + pueda ser el de un SIP/ */ + +chan_sip-ironxfers.patch /* al recivir un REFER escribe las variables de canal ORIGINAL_CALLID y ORIGINAL_CALLERID + para que se puedan tracear las transferencias */ + +misdn-bug13488.patch /* Soluciona el famoso bug 13488 que provocaba que no se pudieran recibir llamadas por mISDN + al quedarse saturados los canales */ + +AST-2009-003-1.4.diff.txt /* Soluciona el bug de seguridad AST-2009-003 que provocó la salida de + Asterisk 1.4.24.1 */ + +app_queue-exitwithtimeout-waittime.patch /* Guarda el wait time en el queue_log cuando el evento es + EXITWITHTIMEOUT */ + +app_queue-linear-strategy.patch /* Backport de la estratégia 'linear' presente sen Asterisk 1.6 + */ + +app_queue-xfer-origpos.patch /* Guarda en el queue_log la posición original del llamante + en la cola cuando el evento es TRANSFER */ + +asterisk_queue_log_realtime_1.4.19.patch /* Guarda el queue_log en RealTime */ + + +asterisk_realtime_store_destroy_1.4.19.patch /* Funciones necesarias para guardar el queue_log en RealTime */ + + +res_config_odbc-queue_log.patch /* Backport de las funciones store y destroy en ODBC de Asterisk 1.6 */ + + +rtp_dtmf_0014815.patch /* Fix para DTMF duplicados de 1.4.25RC1 */ + + +voicemail_imap_crash_14508.patch /* Fix para crash usando voicemail con imap storage */ + + +misdn_release_call.patch /* Fixea forma de colgar llamada cuando el destino aún no ha contestado */ + + +misdn_showconfig_crash_14976.patch /* Resuelve posible crash al ejecutar "misdn show config" */ + + +sip_410causecode_14993.patch /* Mapea respuesta SIP 410 a ISDN 22 */ + + +crash_removeextensiontab_14689.patch /* Crash cuando se usaba el tabulador para autocompletar "remove extension " */ + + +app_queue_crash-large-queue-members.patch /* Crash cuando hay muchos miembros en una cola */ + + +chan_sip_realtime-rtupdate_14885.patch /* Fix para que no se intente actualizar lastms en realtime cuando rtupdate=no */ + + +res_odbc_maxlimits_14888.patch /* Setea el límite a 1023 aunque se le indiquen límites mayores */ + + +chan_sip_rtp-NAT_14546.patch /* Arregla problema de NAT y RTP en chan_sip */ + + +chan_sip_T38-gateways_12437.patch /* Parche enorme que arregla algunos errores de llamadas cuando Asterisk está conectado a un Gateway PSTN que tiene activado T38 */ + + +app_queue_crash-badconfig_14796.patch /* Crash si se configuraba un "member" vacío en queues.conf */ + + +chan_sip_glarereinvite_12013.patch /* Se corrige el envío de 491 en la recepción de invites */ + + +chan_sip_reinvite-before-ACK_13849.patch /* Acepta Reinvites antes de recibir ACK en vez de mandar un 491 */ + + +app_dial_crash-retrydial_14852.patch /* Crash en la aplicación retrydial */ + + +chan_sip-thomson.patch /* PickUp para terminales Thomson */ + + +chan_dahdi_bris.patch /* Soporte para dispositivos BRI desde DAHDI. */ + + +chan_sip_nat-realtime_15194.patch /* Fix para que los usuarios realtime sip no se gareticen cuando se hace un "sip reload" */ + + +chan_sip_directrtp_14244.patch /* a veces salta direcrtp incluso cuando está desactivado si se ha contestado el canal llamante. Está en el bug 14244 pero no es el que lo resuelve sino que lo han encontrado mirando ese */ + + +pbx-multiple_hints_state-15057.patch /* Resuelve resultados incongruentes en la combinación de múltiples hints */ + + +chan_sip_loop_12215.patch /* Fix para casos en los que se detectaba loop en reinvites muy rápidos */ + + +app_meetme_D-option_15050.patch /* Fix para que la opción D de Meetme pida PIN realmente */ + + +makefile_bash_15209.patch /* Llamar explícitamente a bash en vez de a sh cuando se van a usar funciones de bash al hacer make*/ + + +chan_sip_rport_13823.patch /* Fix para las respuestas a REGISTER cuando se usa rport */ + + +res_musiconhold-crash_15109_15123_15195.patch /* Posible crash a la hora de usar res_musiconhold en algunas circunstancias */ + + +crash_smdi-14561.patch /* Posible crash en MWI SMDI */ + + +moh_reload-14759.patch /* Evitar que moh se detenga tras "moh reload" */ + + +stack_size-14932.patch /* Fix de stack_size para arquitectura de 64 bits */ + + +chan_sip-15330.patch /* Fic de canales zombie*/ + + +chan_sip-add_info_supported_methods.patch /* Añade INFO a la cabecera "allowed"*/ + + +return_code_in_ringing-15158.patch /* Return code en ringing mejorado */ + + +voicemail_maxsilence-minmessage_15331.patch /* Elimina un warning que salía cuando no debía porque la comparación era errónea*/ + + +multiple_hints-15413.patch /* Fix para estados de hints múltiples combinados */ + + +AST-2009-008-1.4.patch /* patch de seguridad para no publicitar usuarios SIP validos */ + + +queue_atxfer_S_OR.patch /* patch para evitar coreumps al realizar una atxfer cuando se han derreferenciado los punteros chan->appl y chan->data */ + + +queue_atxfer_bug_14260.patch /* backport del bug 14260, atxfer desde una cola */ + + +AST-2009-009-1.4.diff /* patch de seguridad para evitar una vulnerabilidad de cross-site scripting AJAX en el manager HTTP. */ + + +say.c-issue16105.patch /* pronunciar correctamente las 13:xx */ + + +console_colors.patch /* Habilita los colores al conectarnos con asterisk -r aunque asterisk no haya sido arrancado con -c. */ + + +queue_magic_number.patch /* adapta call_queue a la estructura astobj2 y elimina el bug que provoca el mensaje "bad magic number" */ + + +live_ast /* Permite ejecutar Asterisk sin realizar la instalacion, sin "ensuciar" el sistema. */ + + +queue_wrandom.patch /* añade la estrategia de cola wrandom, random con penalty */ + + +052-debian-runlevel-Makefile.patch /* Modifica el Makefile para solucionar el problema de los runlevels en debian al hacer make config de asterisk */ + + +app_queue-sharedlastcall.patch /* Añade el parámetro global shared_lastcall que permite que el tiempo WRAPUPTIME sea por miembro de + forma global, evita que si está en varias colas le entren llamadas seguidas sin respetar el tiempo */ + +app_queue-R-option.patch /* Añade la opción 'R' a Queue() para que el usuario escuche MOH mientras está esperando en + en la cola y RINGING cuando la llamada está sonando en el terminal del agente.*/ + +tranfer_moh-16513.patch /* Solventa el issue 16513, con las transferencias atendidas y silencios del MOH */ diff --git a/patches/app_dial_crash-retrydial_14852.patch b/patches/app_dial_crash-retrydial_14852.patch new file mode 100644 index 0000000..d932ed9 --- /dev/null +++ b/patches/app_dial_crash-retrydial_14852.patch @@ -0,0 +1,11 @@ +--- apps/app_dial.c (revisión: 187134) ++++ apps/app_dial.c (revisión: 187135) +@@ -1872,7 +1872,7 @@ + } + } + +- if ((dialdata = strchr(dialdata, '|'))) { ++ if (dialdata && (dialdata = strchr(dialdata, '|'))) { + *dialdata++ = '\0'; + } else { + ast_log(LOG_ERROR, "%s requires more arguments\n",rapp); diff --git a/patches/app_meetme_D-option_15050.patch b/patches/app_meetme_D-option_15050.patch new file mode 100644 index 0000000..3802072 --- /dev/null +++ b/patches/app_meetme_D-option_15050.patch @@ -0,0 +1,11 @@ +--- apps/app_meetme.c (revisión: 195634) ++++ apps/app_meetme.c (revisión: 195635) +@@ -2710,7 +2710,7 @@ + + empty = ast_test_flag(&confflags, CONFFLAG_EMPTY | CONFFLAG_EMPTYNOPIN); + empty_no_pin = ast_test_flag(&confflags, CONFFLAG_EMPTYNOPIN); +- always_prompt = ast_test_flag(&confflags, CONFFLAG_ALWAYSPROMPT); ++ always_prompt = ast_test_flag(&confflags, CONFFLAG_ALWAYSPROMPT | CONFFLAG_DYNAMICPIN); + } + + do { diff --git a/patches/app_pickup2.c.patch b/patches/app_pickup2.c.patch new file mode 100644 index 0000000..bdfbf6a --- /dev/null +++ b/patches/app_pickup2.c.patch @@ -0,0 +1,282 @@ +--- apps/app_pickup2.c 1970-01-01 01:00:00.000000000 +0100 ++++ apps/app_pickup2.c 2010-04-15 22:40:04.000000000 +0200 +@@ -0,0 +1,279 @@ ++/* ++ * Asterisk -- A telephony toolkit for Linux. ++ * ++ * Pickup, channel independent call pickup ++ * ++ * Copyright (C) 2005-2007, Thorsten Knabe ++ * ++ * Copyright (C) 2004, Junghanns.NET GmbH ++ * ++ * Klaus-Peter Junghanns ++ * ++ * Copyright (C) 2004, Florian Overkamp ++ * ++ * This program is free software, distributed under the terms of ++ * the GNU General Public License ++ */ ++ ++/*** MODULEINFO ++ yes ++ ***/ ++ ++#include "asterisk.h" ++ ++ASTERISK_FILE_VERSION(__FILE__, "$Revision: 2 $") ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include "asterisk/lock.h" ++#include "asterisk/file.h" ++#include "asterisk/logger.h" ++#include "asterisk/channel.h" ++#include "asterisk/pbx.h" ++#include "asterisk/module.h" ++#include "asterisk/musiconhold.h" ++#include "asterisk/features.h" ++#include "asterisk/options.h" ++ ++static char *app = "PickUp2"; ++static char *synopsis = "PickUp ringing channel."; ++static char *descrip = ++" PickUp2(Technology/resource[&Technology2/resource2&...][|