예제 #1
0
파일: Recipe.php 프로젝트: DSNS-LAB/Dmail
 /**
  * Constructs a new procmail recipe.
  *
  * @param array $params        Array of parameters.
  *                               REQUIRED FIELDS:
  *                                'action'
  *                               OPTIONAL FIELDS:
  *                                'action-value' (only used if the
  *                                'action' requires it)
  * @param array $scriptparams  Array of parameters passed to
  *                             Ingo_Script_Procmail::.
  */
 public function __construct($params = array(), $scriptparams = array())
 {
     $this->_disable = !empty($params['disable']);
     $this->_params = array_merge($this->_params, $scriptparams);
     switch ($params['action']) {
         case Ingo_Storage::ACTION_KEEP:
             // Note: you may have to set the DEFAULT variable in your
             // backend configuration.
             if (isset($this->_params['delivery_agent']) && isset($this->_params['delivery_mailbox_prefix'])) {
                 $this->_action[] = '| ' . $this->_params['delivery_agent'] . ' ' . $this->_params['delivery_mailbox_prefix'] . '$DEFAULT';
             } elseif (isset($this->_params['delivery_agent'])) {
                 $this->_action[] = '| ' . $this->_params['delivery_agent'] . ' $DEFAULT';
             } else {
                 $this->_action[] = '$DEFAULT';
             }
             break;
         case Ingo_Storage::ACTION_MOVE:
             if (isset($this->_params['delivery_agent']) && isset($this->_params['delivery_mailbox_prefix'])) {
                 $this->_action[] = '| ' . $this->_params['delivery_agent'] . ' ' . $this->_params['delivery_mailbox_prefix'] . $this->procmailPath($params['action-value']);
             } elseif (isset($this->_params['delivery_agent'])) {
                 $this->_action[] = '| ' . $this->_params['delivery_agent'] . ' ' . $this->procmailPath($params['action-value']);
             } else {
                 $this->_action[] = $this->procmailPath($params['action-value']);
             }
             break;
         case Ingo_Storage::ACTION_DISCARD:
             $this->_action[] = '/dev/null';
             break;
         case Ingo_Storage::ACTION_REDIRECT:
             $this->_action[] = '! ' . $params['action-value'];
             break;
         case Ingo_Storage::ACTION_REDIRECTKEEP:
             $this->_action[] = '{';
             $this->_action[] = '  :0 c';
             $this->_action[] = '  ! ' . $params['action-value'];
             $this->_action[] = '';
             $this->_action[] = '  :0' . (isset($this->_params['delivery_agent']) ? ' w' : '');
             if (isset($this->_params['delivery_agent']) && isset($this->_params['delivery_mailbox_prefix'])) {
                 $this->_action[] = '  | ' . $this->_params['delivery_agent'] . ' ' . $this->_params['delivery_mailbox_prefix'] . '$DEFAULT';
             } elseif (isset($this->_params['delivery_agent'])) {
                 $this->_action[] = '  | ' . $this->_params['delivery_agent'] . ' $DEFAULT';
             } else {
                 $this->_action[] = '  $DEFAULT';
             }
             $this->_action[] = '}';
             break;
         case Ingo_Storage::ACTION_REJECT:
             $this->_action[] = '{';
             $this->_action[] = '  :0 h';
             $this->_action[] = '  SUBJECT=| formail -xSubject:';
             $this->_action[] = '';
             $this->_action[] = '  :0 h';
             $this->_action[] = '  SENDER=| formail -zxFrom:';
             $this->_action[] = '';
             $this->_action[] = '  :0 Wh';
             $this->_action[] = '  * !^FROM_DAEMON';
             $this->_action[] = '  * !^X-Loop: $SENDER';
             $this->_action[] = '  | (formail -rA"X-Loop: $SENDER" \\';
             $reason = $params['action-value'];
             if (Horde_Mime::is8bit($reason)) {
                 $this->_action[] = '    -i"Subject: Re: $SUBJECT" \\';
                 $this->_action[] = '    -i"Content-Transfer-Encoding: quoted-printable" \\';
                 $this->_action[] = '    -i"Content-Type: text/plain; charset=UTF-8" ; \\';
                 $reason = Horde_Mime::quotedPrintableEncode($reason, "\n");
             } else {
                 $this->_action[] = '    -i"Subject: Re: $SUBJECT" ; \\';
             }
             $reason = addcslashes($reason, "\\\n\r\t\"`");
             $this->_action[] = '    ' . $this->_params['echo'] . ' -e "' . $reason . '" \\';
             $this->_action[] = '  ) | $SENDMAIL -oi -t';
             $this->_action[] = '}';
             break;
         case Ingo_Storage::ACTION_VACATION:
             $days = $params['action-value']['days'];
             $timed = !empty($params['action-value']['start']) && !empty($params['action-value']['end']);
             $this->_action[] = '{';
             foreach ($params['action-value']['addresses'] as $address) {
                 if (!empty($address)) {
                     $this->_action[] = '  :0';
                     $this->_action[] = '  * ^TO_' . $address;
                     $this->_action[] = '  {';
                     $this->_action[] = '    FILEDATE=`test -f ${VACATION_DIR:-.}/\'.vacation.' . $address . '\' && ' . $this->_params['ls'] . ' -lcn --time-style=+%s ${VACATION_DIR:-.}/\'.vacation.' . $address . '\' | ' . 'awk \'{ print $6 + (' . $days * 86400 . ') }\'`';
                     $this->_action[] = '    DATE=`' . $this->_params['date'] . ' +%s`';
                     $this->_action[] = '    DUMMY=`test -f ${VACATION_DIR:-.}/\'.vacation.' . $address . '\' && ' . 'test $FILEDATE -le $DATE && ' . 'rm ${VACATION_DIR:-.}/\'.vacation.' . $address . '\'`';
                     if ($timed) {
                         $this->_action[] = '    START=' . $params['action-value']['start'];
                         $this->_action[] = '    END=' . $params['action-value']['end'];
                     }
                     $this->_action[] = '';
                     $this->_action[] = '    :0 h';
                     $this->_action[] = '    SUBJECT=| formail -xSubject:';
                     $this->_action[] = '';
                     $this->_action[] = '    :0 Whc: ${VACATION_DIR:-.}/vacation.lock';
                     if ($timed) {
                         $this->_action[] = '    * ? test $DATE -gt $START && test $END -gt $DATE';
                     }
                     $this->_action[] = '    {';
                     $this->_action[] = '      :0 Wh';
                     $this->_action[] = '      * ^TO_' . $address;
                     $this->_action[] = '      * !^X-Loop: ' . $address;
                     $this->_action[] = '      * !^X-Spam-Flag: YES';
                     if (count($params['action-value']['excludes']) > 0) {
                         foreach ($params['action-value']['excludes'] as $exclude) {
                             if (!empty($exclude)) {
                                 $this->_action[] = '      * !^From.*' . $exclude;
                             }
                         }
                     }
                     if ($params['action-value']['ignorelist']) {
                         $this->_action[] = '      * !^FROM_DAEMON';
                     }
                     $this->_action[] = '      | formail -rD 8192 ${VACATION_DIR:-.}/.vacation.' . $address;
                     $this->_action[] = '      :0 eh';
                     $this->_action[] = '      | (formail -rI"Precedence: junk" \\';
                     $this->_action[] = '       -a"From: <' . $address . '>" \\';
                     $this->_action[] = '       -A"X-Loop: ' . $address . '" \\';
                     $reason = Ingo_Script_Util::vacationReason($params['action-value']['reason'], $params['action-value']['start'], $params['action-value']['end']);
                     if (Horde_Mime::is8bit($reason)) {
                         $this->_action[] = '       -i"Subject: ' . Horde_Mime::encode($params['action-value']['subject'] . ' (Re: $SUBJECT)') . '" \\';
                         $this->_action[] = '       -i"Content-Transfer-Encoding: quoted-printable" \\';
                         $this->_action[] = '       -i"Content-Type: text/plain; charset=UTF-8" ; \\';
                         $reason = Horde_Mime::quotedPrintableEncode($reason, "\n");
                     } else {
                         $this->_action[] = '       -i"Subject: ' . Horde_Mime::encode($params['action-value']['subject'] . ' (Re: $SUBJECT)') . '" ; \\';
                     }
                     $reason = addcslashes($reason, "\\\n\r\t\"`");
                     $this->_action[] = '       ' . $this->_params['echo'] . ' -e "' . $reason . '" \\';
                     $this->_action[] = '      ) | $SENDMAIL -f' . $address . ' -oi -t';
                     $this->_action[] = '    }';
                     $this->_action[] = '  }';
                 }
             }
             $this->_action[] = '}';
             break;
         case Ingo_Storage::ACTION_FORWARD:
             /* Make sure that we prevent mail loops using 3 methods.
              *
              * First, we call sendmail -f to set the envelope sender to be the
              * same as the original sender, so bounces will go to the original
              * sender rather than to us.  This unfortunately triggers lots of
              * Authentication-Warning: messages in sendmail's logs.
              *
              * Second, add an X-Loop header, to handle the case where the
              * address we forward to forwards back to us.
              *
              * Third, don't forward mailer daemon messages (i.e., bounces).
              * Method 1 above should make this redundant, unless we're sending
              * mail from this account and have a bad forward-to account.
              *
              * Get the from address, saving a call to formail if possible.
              * The procmail code for doing this is borrowed from the
              * Procmail Library Project, http://pm-lib.sourceforge.net/.
              * The Ingo project has the permission to use Procmail Library code
              * under Apache licence v 1.x or any later version.
              * Permission obtained 2006-04-04 from Author Jari Aalto. */
             $this->_action[] = '{';
             $this->_action[] = '  :0 ';
             $this->_action[] = '  *$ ! ^From *\\/[^  ]+';
             $this->_action[] = '  *$ ! ^Sender: *\\/[^   ]+';
             $this->_action[] = '  *$ ! ^From: *\\/[^     ]+';
             $this->_action[] = '  *$ ! ^Reply-to: *\\/[^     ]+';
             $this->_action[] = '  {';
             $this->_action[] = '    OUTPUT = `formail -zxFrom:`';
             $this->_action[] = '  }';
             $this->_action[] = '  :0 E';
             $this->_action[] = '  {';
             $this->_action[] = '    OUTPUT = $MATCH';
             $this->_action[] = '  }';
             $this->_action[] = '';
             /* Forward to each address on our list. */
             foreach ($params['action-value'] as $address) {
                 if (!empty($address)) {
                     $this->_action[] = '  :0 c';
                     $this->_action[] = '  * !^FROM_MAILER';
                     $this->_action[] = '  * !^X-Loop: to-' . $address;
                     $this->_action[] = '  | formail -A"X-Loop: to-' . $address . '" | $SENDMAIL -oi -f $OUTPUT ' . $address;
                 }
             }
             /* In case of mail loop or bounce, store a copy locally.  Note
              * that if we forward to more than one address, only a mail loop
              * on the last address will cause a local copy to be saved.  TODO:
              * The next two lines are redundant (and create an extra copy of
              * the message) if "Keep a copy of messages in this account" is
              * checked. */
             $this->_action[] = '  :0 E' . (isset($this->_params['delivery_agent']) ? 'w' : '');
             if (isset($this->_params['delivery_agent'])) {
                 $this->_action[] = isset($this->_params['delivery_mailbox_prefix']) ? ' | ' . $this->_params['delivery_agent'] . ' ' . $this->_params['delivery_mailbox_prefix'] . '$DEFAULT' : ' | ' . $this->_params['delivery_agent'] . ' $DEFAULT';
             } else {
                 $this->_action[] = '  $DEFAULT';
             }
             $this->_action[] = '  :0 ';
             $this->_action[] = '  /dev/null';
             $this->_action[] = '}';
             break;
         default:
             $this->_valid = false;
             break;
     }
 }
예제 #2
0
 /**
  * Serialize data.
  *
  * @param mixed $data    The data to be serialized.
  * @param mixed $mode    The mode of serialization. Can be
  *                       either a single mode or array of modes.
  *                       If array, will be serialized in the
  *                       order provided.
  * @param mixed $params  Any additional parameters the serialization method
  *                       requires.
  *
  * @return string  A serialized string.
  * @throws Horde_Serialize_Exception
  */
 protected static function _serialize($data, $mode, $params = null)
 {
     switch ($mode) {
         case self::NONE:
             break;
             // $params['level'] = Level of compression (default: 3)
             // $params['workfactor'] = How does compression phase behave when given
             //                         worst case, highly repetitive, input data
             //                         (default: 30)
         // $params['level'] = Level of compression (default: 3)
         // $params['workfactor'] = How does compression phase behave when given
         //                         worst case, highly repetitive, input data
         //                         (default: 30)
         case self::BZIP:
             $data = bzcompress($data, isset($params['level']) ? $params['level'] : 3, isset($params['workfactor']) ? $params['workfactor'] : 30);
             if (is_integer($data)) {
                 $data = false;
             }
             break;
         case self::WDDX:
             $data = wddx_serialize_value($data);
             break;
         case self::IMAP8:
             $data = Horde_Mime::quotedPrintableEncode($data);
             break;
         case self::IMAPUTF7:
             $data = Horde_Imap_Client_Utf7imap::Utf8ToUtf7Imap(Horde_String::convertCharset($data, 'ISO-8859-1', 'UTF-8'));
             break;
         case self::IMAPUTF8:
             $data = Horde_Mime::decode($data);
             break;
             // $params['level'] = Level of compression (default: 3)
         // $params['level'] = Level of compression (default: 3)
         case self::GZ_DEFLATE:
             $data = gzdeflate($data, isset($params['level']) ? $params['level'] : 3);
             break;
         case self::BASIC:
             $data = serialize($data);
             break;
             // $params['level'] = Level of compression (default: 3)
         // $params['level'] = Level of compression (default: 3)
         case self::GZ_COMPRESS:
             $data = gzcompress($data, isset($params['level']) ? $params['level'] : 3);
             break;
         case self::BASE64:
             $data = base64_encode($data);
             break;
             // $params['level'] = Level of compression (default: 3)
         // $params['level'] = Level of compression (default: 3)
         case self::GZ_ENCODE:
             $data = gzencode($data, isset($params['level']) ? $params['level'] : 3);
             break;
         case self::RAW:
             $data = rawurlencode($data);
             break;
         case self::URL:
             $data = urlencode($data);
             break;
             // $params = Source character set
         // $params = Source character set
         case self::UTF7:
             $data = Horde_String::convertCharset($data, $params, 'UTF-7');
             break;
             // $params = Source character set
         // $params = Source character set
         case self::UTF7_BASIC:
             $data = self::serialize($data, array(self::UTF7, self::BASIC), $params);
             break;
         case self::JSON:
             $tmp = json_encode($data);
             /* Basic error handling attempts.
              * TODO: JSON_ERROR_UTF8 = 5; available as of PHP 5.3.3 */
             if (json_last_error() === 5) {
                 $data = json_encode(Horde_String::convertCharset($data, $params, 'UTF-8', true));
             } else {
                 $data = $tmp;
             }
             break;
         case self::LZF:
             $data = lzf_compress($data);
             break;
     }
     if ($data === false) {
         throw new Horde_Serialize_Exception('Serialization failed.');
     }
     return $data;
 }
예제 #3
0
 /**
  * Export this component in vCal format.
  *
  * @param string $base  The type of the base object.
  *
  * @return string  vCal format data.
  */
 protected function _exportvData($base = 'VCALENDAR')
 {
     $result = 'BEGIN:' . Horde_String::upper($base) . $this->_newline;
     // VERSION is not allowed for entries enclosed in VCALENDAR/ICALENDAR,
     // as it is part of the enclosing VCALENDAR/ICALENDAR. See rfc2445
     if ($base !== 'VEVENT' && $base !== 'VTODO' && $base !== 'VALARM' && $base !== 'VJOURNAL' && $base !== 'VFREEBUSY' && $base != 'VTIMEZONE' && $base != 'STANDARD' && $base != 'DAYLIGHT') {
         // Ensure that version is the first attribute.
         $result .= 'VERSION:' . $this->_version . $this->_newline;
     }
     foreach ($this->_attributes as $attribute) {
         $name = $attribute['name'];
         if ($name == 'VERSION') {
             // Already done.
             continue;
         }
         $params_str = '';
         $params = $attribute['params'];
         if ($params) {
             foreach ($params as $param_name => $param_value) {
                 /* Skip CHARSET for iCalendar 2.0 data, not allowed. */
                 if ($param_name == 'CHARSET' && !$this->_oldFormat) {
                     continue;
                 }
                 /* Skip VALUE=DATE for vCalendar 1.0 data, not allowed. */
                 if ($this->_oldFormat && $param_name == 'VALUE' && $param_value == 'DATE') {
                     continue;
                 }
                 if ($param_value === null) {
                     $params_str .= ";{$param_name}";
                 } else {
                     if (!is_array($param_value)) {
                         $param_value = array($param_value);
                     }
                     foreach ($param_value as &$one_param_value) {
                         $len = strlen($one_param_value);
                         $safe_value = '';
                         $quote = false;
                         for ($i = 0; $i < $len; ++$i) {
                             $ord = ord($one_param_value[$i]);
                             // Accept only valid characters.
                             if ($ord == 9 || $ord == 32 || $ord == 33 || $ord >= 35 && $ord <= 126 || $ord >= 128) {
                                 $safe_value .= $one_param_value[$i];
                                 // Characters above 128 do not need to be
                                 // quoted as per RFC2445 but Outlook requires
                                 // this.
                                 if ($ord == 44 || $ord == 58 || $ord == 59 || $ord >= 128) {
                                     $quote = true;
                                 }
                             }
                         }
                         if ($quote) {
                             $safe_value = '"' . $safe_value . '"';
                         }
                         $one_param_value = $safe_value;
                     }
                     $params_str .= ";{$param_name}=" . implode(',', $param_value);
                 }
             }
         }
         $value = $attribute['value'];
         switch ($name) {
             // Date fields.
             case 'COMPLETED':
             case 'CREATED':
             case 'DCREATED':
             case 'LAST-MODIFIED':
             case 'X-MOZ-LASTACK':
             case 'X-MOZ-SNOOZE-TIME':
                 $value = $this->_exportDateTime($value);
                 break;
             case 'DTEND':
             case 'DTSTART':
             case 'DTSTAMP':
             case 'DUE':
             case 'AALARM':
             case 'RECURRENCE-ID':
                 $floating = $base == 'STANDARD' || $base == 'DAYLIGHT' || isset($params['TZID']);
                 if (isset($params['VALUE'])) {
                     if ($params['VALUE'] == 'DATE') {
                         // VCALENDAR 1.0 uses T000000 - T235959 for all day events:
                         if ($this->_oldFormat && $name == 'DTEND') {
                             $d = new Horde_Date($value);
                             $value = new Horde_Date(array('year' => $d->year, 'month' => $d->month, 'mday' => $d->mday - 1));
                             $value = $this->_exportDate($value, '235959');
                         } else {
                             $value = $this->_exportDate($value, '000000');
                         }
                     } else {
                         $value = $this->_exportDateTime($value, $floating);
                     }
                 } else {
                     $value = $this->_exportDateTime($value, $floating);
                 }
                 break;
                 // Comma seperated dates.
             // Comma seperated dates.
             case 'EXDATE':
             case 'RDATE':
                 $floating = $base == 'STANDARD' || $base == 'DAYLIGHT';
                 $dates = array();
                 foreach ($value as $date) {
                     if (isset($params['VALUE'])) {
                         if ($params['VALUE'] == 'DATE') {
                             $dates[] = $this->_exportDate($date, '000000');
                         } elseif ($params['VALUE'] == 'PERIOD') {
                             $dates[] = $this->_exportPeriod($date);
                         } else {
                             $dates[] = $this->_exportDateTime($date, $floating);
                         }
                     } else {
                         $dates[] = $this->_exportDateTime($date, $floating);
                     }
                 }
                 $value = implode($this->_oldFormat ? ';' : ',', $dates);
                 break;
             case 'TRIGGER':
                 if (isset($params['VALUE'])) {
                     if ($params['VALUE'] == 'DATE-TIME') {
                         $value = $this->_exportDateTime($value);
                     } elseif ($params['VALUE'] == 'DURATION') {
                         $value = $this->_exportDuration($value);
                     }
                 } else {
                     $value = $this->_exportDuration($value);
                 }
                 break;
                 // Duration fields.
             // Duration fields.
             case 'DURATION':
                 $value = $this->_exportDuration($value);
                 break;
                 // Period of time fields.
             // Period of time fields.
             case 'FREEBUSY':
                 $value_str = '';
                 foreach ($value as $period) {
                     $value_str .= empty($value_str) ? '' : ',';
                     $value_str .= $this->_exportPeriod($period);
                 }
                 $value = $value_str;
                 break;
                 // UTC offset fields.
             // UTC offset fields.
             case 'TZOFFSETFROM':
             case 'TZOFFSETTO':
                 $value = $this->_exportUtcOffset($value);
                 break;
                 // Integer fields.
             // Integer fields.
             case 'PERCENT-COMPLETE':
             case 'PRIORITY':
             case 'REPEAT':
             case 'SEQUENCE':
                 $value = "{$value}";
                 break;
                 // Geo fields.
             // Geo fields.
             case 'GEO':
                 if ($this->_oldFormat) {
                     $value = $value['longitude'] . ',' . $value['latitude'];
                 } else {
                     $value = $value['latitude'] . ';' . $value['longitude'];
                 }
                 break;
                 // Recurrence fields.
             // Recurrence fields.
             case 'EXRULE':
             case 'RRULE':
                 break;
             default:
                 if ($this->_oldFormat) {
                     if (is_array($attribute['values']) && count($attribute['values']) > 1) {
                         $values = $attribute['values'];
                         if ($name == 'N' || $name == 'ADR' || $name == 'ORG') {
                             $glue = ';';
                         } else {
                             $glue = ',';
                         }
                         $values = str_replace(';', '\\;', $values);
                         $value = implode($glue, $values);
                     } else {
                         /* vcard 2.1 and vcalendar 1.0 escape only
                          * semicolons */
                         $value = str_replace(';', '\\;', $value);
                     }
                     // Text containing newlines or ASCII >= 127 must be BASE64
                     // or QUOTED-PRINTABLE encoded. Currently we use
                     // QUOTED-PRINTABLE as default.
                     if (preg_match("/[^ -]/", $value) && empty($params['ENCODING'])) {
                         $params['ENCODING'] = 'QUOTED-PRINTABLE';
                         $params_str .= ';ENCODING=QUOTED-PRINTABLE';
                         // Add CHARSET as well. At least the synthesis client
                         // gets confused otherwise
                         if (empty($params['CHARSET'])) {
                             $params['CHARSET'] = 'UTF-8';
                             $params_str .= ';CHARSET=' . $params['CHARSET'];
                         }
                     }
                 } else {
                     if (is_array($attribute['values']) && count($attribute['values'])) {
                         $values = $attribute['values'];
                         if ($name == 'N' || $name == 'ADR' || $name == 'ORG') {
                             $glue = ';';
                         } else {
                             $glue = ',';
                         }
                         // As of rfc 2426 2.5 semicolon and comma must be
                         // escaped.
                         $values = str_replace(array('\\', ';', ','), array('\\\\', '\\;', '\\,'), $values);
                         $value = implode($glue, $values);
                     } else {
                         // As of rfc 2426 2.5 semicolon and comma must be
                         // escaped.
                         $value = str_replace(array('\\', ';', ','), array('\\\\', '\\;', '\\,'), $value);
                     }
                     $value = preg_replace('/\\r?\\n/', '\\n', $value);
                 }
                 break;
         }
         $value = str_replace("\r", '', $value);
         if (!empty($params['ENCODING']) && $params['ENCODING'] == 'QUOTED-PRINTABLE' && strlen(trim($value))) {
             $result .= $name . $params_str . ':' . preg_replace(array('/(?<!\\r)\\n/', '/(?<!=)\\r\\n/'), array("\r\n", "=0D=0A=\r\n "), Horde_Mime::quotedPrintableEncode($value)) . $this->_newline;
         } else {
             $attr_string = $name . $params_str . ':' . $value;
             if (!$this->_oldFormat) {
                 if (isset($params['ENCODING']) && $params['ENCODING'] == 'b') {
                     $attr_string = trim(chunk_split($attr_string, 75, $this->_newline . ' '));
                 } else {
                     $attr_string = Horde_String::wordwrap($attr_string, 75, $this->_newline . ' ', true, true);
                 }
             }
             $result .= $attr_string . $this->_newline;
         }
     }
     $tzs = array();
     foreach ($this->_components as $component) {
         if (!$component instanceof Horde_Icalendar_Vtimezone || !isset($tzs[$component->getAttribute('TZID')])) {
             $result .= $component->exportvCalendar();
             if ($component instanceof Horde_Icalendar_Vtimezone) {
                 $tzs[$component->getAttribute('TZID')] = true;
             }
         }
     }
     return $result . 'END:' . $base . $this->_newline;
 }