From: Paul L. <pa...@sq...> - 2009-04-05 08:20:29
|
Jon, Sorry for the late reply on this, but: 1) Was any of the filters/spam changes applicable to DEVEL too? 2) Are you sure you want to change the known functionality of sqimap_msgs_list_copy()? Yes, it's HIGHLY annoying that the function is mis-named, but this being stable code, I wouldn't think we'd want to change out the functionality of the core. It may be better to leave the name of the *move* function "sqimap_msgs_list_copy" and create a new *copy* function named something such as "sqimap_msgs_list_copy_for_real". The downside to that last idea is that it really diverges from 1.5 compatibility even more than it already does. I know that at least the Spam Buttons plugin depends on the sqimap_msgs_list_copy() function as it was (with *move* functionality). Fortunately, I have it inside a wrapper that is specifically intended to fix the naming problem, and it is a very fast change to alter the version number check that it does therein..... but I don't know if there are some other plugins out there that will break because of this change. Thoughts? On Sat, Jan 3, 2009 at 4:48 PM, <jan...@us...> wrote: > Revision: 13388 > http://squirrelmail.svn.sourceforge.net/squirrelmail/?rev=13388&view=rev > Author: jangliss > Date: 2009-01-03 23:48:07 +0000 (Sat, 03 Jan 2009) > > Log Message: > ----------- > - Fix for (spam) filters plugin scanning only the first message returned > - Backported fetch handling code from dev for upcoming code optimizations > - Removed some uses of code marked as "obsolete" from core code, and core plugins > - Altered sqimap_msgs_lists_copy to actually COPY, and not MOVE > - Created _move function. > > Modified Paths: > -------------- > branches/SM-1_4-STABLE/squirrelmail/ChangeLog > branches/SM-1_4-STABLE/squirrelmail/functions/imap_messages.php > branches/SM-1_4-STABLE/squirrelmail/functions/tree.php > branches/SM-1_4-STABLE/squirrelmail/plugins/delete_move_next/setup.php > branches/SM-1_4-STABLE/squirrelmail/plugins/filters/filters.php > branches/SM-1_4-STABLE/squirrelmail/plugins/spamcop/setup.php > branches/SM-1_4-STABLE/squirrelmail/src/delete_message.php > branches/SM-1_4-STABLE/squirrelmail/src/move_messages.php > > Modified: branches/SM-1_4-STABLE/squirrelmail/ChangeLog > =================================================================== > --- branches/SM-1_4-STABLE/squirrelmail/ChangeLog 2009-01-03 00:14:42 UTC (rev 13387) > +++ branches/SM-1_4-STABLE/squirrelmail/ChangeLog 2009-01-03 23:48:07 UTC (rev 13388) > @@ -8,6 +8,19 @@ > (get_location()) (#2388423) > - Add informational type option widget > - Add password type option widget > + - Fix filters plugin to allow spam filters to scan multiple > + messages, rather than the first message returned (#1634735). > + - Removed code from spam filters plugin to stop if falling back > + to searching all messages when there was no new messages. > + - Altered filters plugin to issue single move/delete statement > + for multiple messages. > + - Updated some core code, and several plugins, to not use code > + marked as obsolete. > + - Corrected sqimap_msgs_list_copy to actually copy messages, > + rather than move. > + - Created new sqimap_msgs_list_copy to move messages. > + - Migrated some fetch handling code from dev branch in plans to > + update some core functionality to allow reusability of code. > > Version 1.4.17 - 03 December 2008 > --------------------------------- > > Modified: branches/SM-1_4-STABLE/squirrelmail/functions/imap_messages.php > =================================================================== > --- branches/SM-1_4-STABLE/squirrelmail/functions/imap_messages.php 2009-01-03 00:14:42 UTC (rev 13387) > +++ branches/SM-1_4-STABLE/squirrelmail/functions/imap_messages.php 2009-01-03 23:48:07 UTC (rev 13388) > @@ -15,21 +15,49 @@ > > > /** > - * Moves a set of messages ($id) to another mailbox ($mailbox) > - * WARNING: function name does not match performed operation. > - * Function performs message copy and flags existing messages > - * as deleted > + * Copies a set of messages ($id) to another mailbox ($mailbox) > + * > + * @param int $imap_stream The resource ID for the IMAP socket > + * @param mixed $id A string or array of messages to copy > + * @param string $mailbox The mailbox to copy messages to > + * @return bool Returns true on successful copy, false on failure > */ > function sqimap_msgs_list_copy($imap_stream, $id, $mailbox) { > global $uid_support; > $msgs_id = sqimap_message_list_squisher($id); > $read = sqimap_run_command ($imap_stream, "COPY $msgs_id \"$mailbox\"", true, $response, $message, $uid_support); > - $read = sqimap_run_command ($imap_stream, "STORE $msgs_id +FLAGS (\\Deleted)", true, $response, $message, $uid_support); > + > + if ($response == 'OK') { > + return true; > + } else { > + return false; > + } > } > > > /** > + * Moves a set of messages ($id) to another mailbox ($mailbox) > + * > + * @param int $imap_stream The resource ID for the IMAP socket > + * @param mixed $id A string or array of messages to copy > + * @param string $mailbox The destination mailbox > + * @param bool $handle_errors Show error messages in case of a NO, BAD, or BYE response > + * @return bool If move completed without error. > + */ > +function sqimap_msgs_list_move($imap_stream, $id, $mailbox, $handle_errors = true) { > + if (sqimap_msgs_list_copy ($imap_stream, $id, $mailbox, $handle_errors)) { > + return sqimap_toggle_flag($imap_stream, $id, '\\Deleted', true, true); > + } else { > + return false; > + } > +} > + > +/** > * Deletes a message and move it to trash or expunge the mailbox > + * > + * @param int $imap_stream The resource ID for the IMAP socket > + * @param string $mailbox The mailbox to delete messages from > + * @param mixed $id A string or array of messages to delete > */ > function sqimap_msgs_list_delete($imap_stream, $mailbox, $id) { > global $move_to_trash, $trash_folder, $uid_support; > @@ -479,7 +507,174 @@ > } > } > > + > /** > + * Parses a fetch response > + * > + * @param array $aResponse IMAP Response > + * @param array $aMessageList Placeholder array for results. The keys of the > + * placeholder array should be the UID so we can reconstruct the order. > + * @return array $aMessageList Associative array with messages. > + */ > +function parseFetch(&$aResponse, $aMessageList = array()) { > + for($j=0, $iCnt = count($aResponse);$j<$iCnt; ++$j) { > + $aMsg = array(); > + > + $read = implode('', $aResponse[$j]); > + // Clear up some memory > + unset($aResponse[$j]); > + > + /* > + *<space>#id<space>FETCH<space>(.... > + */ > + > + $i_space = strpos($read,' ', 2); > + $id = substr($read, 2, $i_space - 2); > + $aMsg['ID'] = $id; > + $fetch = substr($read, $i_space+1,5); > + > + if (!is_numeric($id) && $fetch !== 'FETCH') { > + $aMsg['ERROR'] = $read; > + break; > + } > + > + $i = strpos($read, '(', $i_space+5); > + $read = substr($read, $i+1); > + $i_len = strlen($read); > + $i = 0; > + > + while($i < $i_len && $i !== false) { > + $read = trim(substr($read, $i)); > + $i_len = strlen($read); > + $i = strpos($read, ' '); > + $arg = substr($read,0,$i); > + ++$i; > + > + switch($arg) { > + case 'UID': > + $i_pos = strpos($read,' ',$i); > + if (!$i_pos) { > + $i_pos = strpos($read, ')', $i); > + } > + if ($i_pos) { > + $unique_id = substr($read, $i, $i_pos-$i); > + $i = $i_pos + 1; > + } else { > + break 3; > + } > + break; > + case 'FLAGS': > + $flags = parseArray($read, $i); > + if (!$flags) break 3; > + $aFlags = array(); > + foreach($flags as $flag) { > + $flag = strtolower($flag); > + $aFlags[$flag] = true; > + } > + $aMsg['FLAGS'] = $aFlags; > + break; > + case 'RFC822.SIZE': > + $i_pos = strpos($read, ' ', $i); > + if (!$i_pos) { > + $i_pos = strpos($read, ')', $i); > + } > + if ($i_pos) { > + $aMsg['SIZE'] = substr($read,$i,$i_pos-$i); > + $i = $i_pos + 1; > + } else { > + break 3; > + } > + break; > + case 'ENVELOPE': > + // sqimap_parse_address($read,$i,$aMsg); > + break; // to be implemented, moving imap code out of the Message class > + case 'BODYSTRUCTURE': > + break; // to be implemented, moving imap code out of the Message class > + case 'INTERNALDATE': > + $aMsg['INTERNALDATE'] = trim(str_replace(' ', ' ',parseString($read,$i))); > + break; > + case 'BODY.PEEK[HEADER.FIELDS': > + case 'BODY[HEADER.FIELDS': > + $i = strpos($read,'{',$i); // header is always returned as literal because it contain \n characters > + $header = parseString($read,$i); > + if ($header === false) break 2; > + /* First we replace all \r\n by \n, and unfold the header */ > + $hdr = trim(str_replace(array("\r\n", "\n\t", "\n "),array("\n", ' ', ' '), $header)); > + /* Now we can make a new header array with > + each element representing a headerline */ > + $aHdr = explode("\n" , $hdr); > + $aReceived = array(); > + foreach ($aHdr as $line) { > + $pos = strpos($line, ':'); > + if ($pos > 0) { > + $field = strtolower(substr($line, 0, $pos)); > + if (!strstr($field,' ')) { /* valid field */ > + $value = trim(substr($line, $pos+1)); > + switch($field) { > + case 'date': > + $aMsg['date'] = trim(str_replace(' ', ' ', $value)); > + break; > + case 'x-priority': $aMsg['x-priority'] = ($value) ? (int) $value{0} : 3; break; > + case 'priority': > + case 'importance': > + // duplicate code with Rfc822Header.cls:parsePriority() > + if (!isset($aMsg['x-priority'])) { > + $aPrio = preg_split('/\s/',trim($value)); > + $sPrio = strtolower(array_shift($aPrio)); > + if (is_numeric($sPrio)) { > + $iPrio = (int) $sPrio; > + } elseif ( $sPrio == 'non-urgent' || $sPrio == 'low' ) { > + $iPrio = 5; > + } elseif ( $sPrio == 'urgent' || $sPrio == 'high' ) { > + $iPrio = 1; > + } else { > + // default is normal priority > + $iPrio = 3; > + } > + $aMsg['x-priority'] = $iPrio; > + } > + break; > + case 'content-type': > + $type = $value; > + if ($pos = strpos($type, ";")) { > + $type = substr($type, 0, $pos); > + } > + $type = explode("/", $type); > + if(!is_array($type) || count($type) < 2) { > + $aMsg['content-type'] = array('text','plain'); > + } else { > + $aMsg['content-type'] = array(strtolower($type[0]),strtolower($type[1])); > + } > + break; > + case 'received': > + $aMsg['received'][] = $value; > + break; > + default: > + $aMsg[$field] = $value; > + break; > + } > + } > + } > + } > + break; > + default: > + ++$i; > + break; > + } > + } > + if (!empty($unique_id)) { > + $msgi = "$unique_id"; > + $aMsg['UID'] = $unique_id; > + } else { > + $msgi = ''; > + } > + $aMessageList[$msgi] = $aMsg; > + $aResponse[$j] = NULL; > + } > + return $aMessageList; > +} > + > +/** > * Normalise the different Priority headers into a uniform value, > * namely that of the X-Priority header (1, 3, 5). Supports: > * Prioirty, X-Priority, Importance. > > Modified: branches/SM-1_4-STABLE/squirrelmail/functions/tree.php > =================================================================== > --- branches/SM-1_4-STABLE/squirrelmail/functions/tree.php 2009-01-03 00:14:42 UTC (rev 13387) > +++ branches/SM-1_4-STABLE/squirrelmail/functions/tree.php 2009-01-03 23:48:07 UTC (rev 13388) > @@ -145,7 +145,7 @@ > $mbx_response = sqimap_mailbox_select($imap_stream, $tree[$index]['value']); > $messageCount = $mbx_response['EXISTS']; > if ($messageCount > 0) { > - sqimap_messages_copy($imap_stream, 1, '*', $trash_folder . $delimiter . $subFolderName); > + sqimap_msgs_list_copy($imap_stream, '1:*', $trash_folder . $delimiter . $subFolderName); > } > // after copy close the mailbox to get in unselected state > sqimap_run_command($imap_stream,'CLOSE',false,$response,$message); > @@ -156,7 +156,7 @@ > $mbx_response = sqimap_mailbox_select($imap_stream, $tree[$index]['value']); > $messageCount = $mbx_response['EXISTS']; > if ($messageCount > 0) { > - sqimap_messages_copy($imap_stream, 1, '*', $trash_folder . $delimiter . $subFolderName); > + sqimap_msgs_list_copy($imap_stream, '1:*', $trash_folder . $delimiter . $subFolderName); > } > // after copy close the mailbox to get in unselected state > sqimap_run_command($imap_stream,'CLOSE',false,$response,$message); > > Modified: branches/SM-1_4-STABLE/squirrelmail/plugins/delete_move_next/setup.php > =================================================================== > --- branches/SM-1_4-STABLE/squirrelmail/plugins/delete_move_next/setup.php 2009-01-03 00:14:42 UTC (rev 13387) > +++ branches/SM-1_4-STABLE/squirrelmail/plugins/delete_move_next/setup.php 2009-01-03 23:48:07 UTC (rev 13388) > @@ -278,7 +278,7 @@ > sqgetGlobalVar('delete_id', $delete_id, SQ_GET); > sqgetGlobalVar('mailbox', $mailbox, SQ_GET); > > - sqimap_messages_delete($imapConnection, $delete_id, $delete_id, $mailbox); > + sqimap_msgs_list_delete($imapConnection, $mailbox, $delete_id); > if ($auto_expunge) { > delete_move_expunge_from_all($delete_id); > // sqimap_mailbox_expunge($imapConnection, $mailbox, true); > @@ -293,8 +293,7 @@ > sqgetGlobalVar('targetMailbox', $targetMailbox, SQ_POST); > > // Move message > - sqimap_messages_copy($imapConnection, $move_id, $move_id, $targetMailbox); > - sqimap_messages_flag($imapConnection, $move_id, $move_id, 'Deleted', true); > + sqimap_msgs_list_move($imapConnection, $move_id, $targetMailbox); > if ($auto_expunge) { > delete_move_expunge_from_all($move_id); > // sqimap_mailbox_expunge($imapConnection, $mailbox, true); > > Modified: branches/SM-1_4-STABLE/squirrelmail/plugins/filters/filters.php > =================================================================== > --- branches/SM-1_4-STABLE/squirrelmail/plugins/filters/filters.php 2009-01-03 00:14:42 UTC (rev 13387) > +++ branches/SM-1_4-STABLE/squirrelmail/plugins/filters/filters.php 2009-01-03 23:48:07 UTC (rev 13388) > @@ -340,9 +340,8 @@ > */ > for ($j=2, $num = count($ids); $j < $num; $j++) { > $id = trim($ids[$j]); > - if (sqimap_messages_copy ($imap, $id, $id, $where_to, false)) { > + if (sqimap_msgs_list_move($imap, $id, $where_to)) { > $del_id[] = $id; > - sqimap_messages_flag ($imap, $id, $id, 'Deleted', false); > } > } > } > @@ -369,119 +368,102 @@ > filters_LoadCache(); > } > > - $run = 0; > + $run = false; > > foreach ($filters as $Key=> $Value) { > if ($Value['enabled']) { > - $run ++; > + $run = true; > + break; > } > } > > // short-circuit > - if ($run == 0) { > + if (!$run) { > return; > } > > sqimap_mailbox_select($imap_stream, 'INBOX'); > > - // Ask for a big list of all "Received" headers in the inbox with > - // flags for each message. Kinda big. > - if ($filters_spam_scan != 'new') { > - $read = sqimap_run_command($imap_stream, 'FETCH 1:* (FLAGS BODY.PEEK[HEADER.FIELDS ' . > - '(RECEIVED)])', true, $response, $message, $uid_support); > - } else { > + $search_array = array(); > + if ($filters_spam_scan == 'new') { > $read = sqimap_run_command($imap_stream, 'SEARCH UNSEEN', true, $response, $message, $uid_support); > - if ($response != 'OK' || trim($read[0]) == '* SEARCH') { > - $read = sqimap_run_command($imap_stream, 'FETCH 1:* (FLAGS BODY.PEEK[HEADER.FIELDS ' . > - '(RECEIVED)])', true, $response, $message, $uid_support); > - } else { > - if (isset($read[0])) { > - if (preg_match("/^\* SEARCH (.+)$/", $read[0], $regs)) { > - $search_array = preg_split("/ /", trim($regs[1])); > + if (isset($read[0])) { > + for ($i = 0, $iCnt = count($read); $i < $iCnt; ++$i) { > + if (preg_match("/^\* SEARCH (.+)$/", $read[$i], $regs)) { > + $search_array = explode(' ', trim($regs[1])); > + break; > } > } > - $msgs_str = sqimap_message_list_squisher($search_array); > - $imap_query = 'FETCH '.$msgs_str; > - $imap_query .= ' (FLAGS BODY.PEEK[HEADER.FIELDS '; > - $imap_query .= '(RECEIVED)])'; > - $read = sqimap_run_command($imap_stream,$imap_query, true, $response, $message, $uid_support); > } > } > > + if ($filters_spam_scan == 'new' && count($search_array)) { > + $msg_str = sqimap_message_list_squisher($search_array); > + $imap_query = 'FETCH ' . $msg_str . ' (FLAGS BODY.PEEK[HEADER.FIELDS (RECEIVED)])'; > + } else if ($filters_spam_scan != 'new') { > + $imap_query = 'FETCH 1:* (FLAGS BODY.PEEK[HEADER.FIELDS (RECEIVED)])'; > + } else { > + return; > + } > + > + $read = sqimap_run_command_list($imap_stream, $imap_query, true, $response, $message, $uid_support); > + > if (isset($response) && $response != 'OK') { > return; > } > + > + $messages = parseFetch($read, $search_array); > + > + $bulkquery = (strlen($SpamFilters_BulkQuery) > 0 ? true : false); > > - if (strlen($SpamFilters_BulkQuery) > 0) { > - filters_bulkquery($filters_spam_scan, $filters, $read); > - } > - > - $i = 0; > - while ($i < count($read)) { > - // EIMS will give funky results > - $Chunks = explode(' ', $read[$i]); > - if ($Chunks[0] != '*') { > - $i ++; > - continue; > + foreach($messages as $id=>$message) { > + if (isset($message['UID'])) { > + $MsgNum = $message['UID']; > + } else { > + $MsgNum = $id; > } > - $MsgNum = $Chunks[1]; > - > - $IPs = array(); > - $i ++; > - $IsSpam = 0; > - > - // Look through all of the Received headers for IP addresses > - // Stop when I get ")" on a line > - // Stop if I get "*" on a line (don't advance) > - // and above all, stop if $i is bigger than the total # of lines > - while (($i < count($read)) && > - ($read[$i][0] != ')' && $read[$i][0] != '*' && > - $read[$i][0] != "\n") && (! $IsSpam)) { > - // Check to see if this line is the right "Received from" line > - // to check > - if (is_int(strpos($read[$i], $SpamFilters_YourHop))) { > - > - // short-circuit and skip work if we don't scan this one > - $read[$i] = ereg_replace('[^0-9\.]', ' ', $read[$i]); > - $elements = explode(' ', $read[$i]); > - foreach ($elements as $value) { > - if ($value != '' && > - ereg('[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}', > - $value, $regs)) { > - $Chunks = explode('.', $value); > - if (filters_spam_check_site($Chunks[0], > - $Chunks[1], $Chunks[2], $Chunks[3], > - $filters)) { > - $IsSpam ++; > - break; // no sense in checking more IPs > + > + if (isset($message['received'])) { > + foreach($message['received'] as $received) { > + if (is_int(strpos($received, $SpamFilters_YourHop))) { > + if (preg_match('/([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})/', $received, $matches)) { > + $IsSpam = false; > + if (filters_spam_check_site($matches[1], $matches[2], $matches[3], $matches[4], $filters)) { > + $aSpamIds[] = $MsgNum; > + $IsSpam = true; > } > - // If we've checked one IP and YourHop is > - // just a space > - if ($SpamFilters_YourHop == ' ') { > - break; // don't check any more > + > + if ($bulkquery) { > + array_shift($matches); > + $IP = explode('.', $matches); > + foreach($filters as $key=>$value) { > + if ($filters[$key]['enabled'] && $filters[$key]['dns']) { > + if (strlen($SpamFilters_DNScache[$IP . '.' . $filters[$key]['dns']]) == 0) { > + $IPs[$IP] = true; > + break; > + } > + } > + } > } > + > + if ($SpamFilters_YourHop == ' ' || $IsSpam) { > + break; > + } > } > } > } > - $i ++; > } > - > - // Lookie! It's spam! Yum! > - if ($IsSpam) { > - if (sqimap_mailbox_exists($imap_stream, $filters_spam_folder)) { > - // check if message copy was successful > - if (sqimap_messages_copy ($imap_stream, $MsgNum, $MsgNum, > - $filters_spam_folder, false)) { > - sqimap_messages_flag ($imap_stream, $MsgNum, $MsgNum, > - 'Deleted', false); > - } > - } > - } else { > - } > } > + > + if (count($aSpamIds) && sqimap_mailbox_exists($imap_stream, $filters_spam_folder)) { > + sqimap_msgs_list_move($imap_stream, $aSpamIds, $filters_spam_folder); > + sqimap_mailbox_expunge($imap_stream, 'INBOX', true, $aSpamIds); > + } > + > + if ($bulkquery && count($IPs)) { > + filters_bulkquery($filters, $IPs); > + } > > - sqimap_mailbox_expunge($imap_stream, 'INBOX'); > - > if ($SpamFilters_SharedCache) { > filters_SaveCache(); > } else { > > Modified: branches/SM-1_4-STABLE/squirrelmail/plugins/spamcop/setup.php > =================================================================== > --- branches/SM-1_4-STABLE/squirrelmail/plugins/spamcop/setup.php 2009-01-03 00:14:42 UTC (rev 13387) > +++ branches/SM-1_4-STABLE/squirrelmail/plugins/spamcop/setup.php 2009-01-03 23:48:07 UTC (rev 13388) > @@ -131,8 +131,7 @@ > if ($spamcop_delete) { > $imapConnection = sqimap_login($username, $key, $imapServerAddress, $imapPort, 0); > sqimap_mailbox_select($imapConnection, $mailbox); > - sqimap_messages_delete($imapConnection, $spamcop_is_composing, > - $spamcop_is_composing, $mailbox); > + sqimap_msgs_list_delete($imapConnection, $mailbox, $spamcop_is_composing); > if ($auto_expunge) > sqimap_mailbox_expunge($imapConnection, $mailbox, true); > } > > Modified: branches/SM-1_4-STABLE/squirrelmail/src/delete_message.php > =================================================================== > --- branches/SM-1_4-STABLE/squirrelmail/src/delete_message.php 2009-01-03 00:14:42 UTC (rev 13387) > +++ branches/SM-1_4-STABLE/squirrelmail/src/delete_message.php 2009-01-03 23:48:07 UTC (rev 13388) > @@ -54,7 +54,7 @@ > > sqimap_mailbox_select($imapConnection, $mailbox); > > -sqimap_messages_delete($imapConnection, $message, $message, $mailbox); > +sqimap_msgs_list_delete($imapConnection, $mailbox, $message); > if ($auto_expunge) { > sqimap_mailbox_expunge($imapConnection, $mailbox, true); > } > > Modified: branches/SM-1_4-STABLE/squirrelmail/src/move_messages.php > =================================================================== > --- branches/SM-1_4-STABLE/squirrelmail/src/move_messages.php 2009-01-03 00:14:42 UTC (rev 13387) > +++ branches/SM-1_4-STABLE/squirrelmail/src/move_messages.php 2009-01-03 23:48:07 UTC (rev 13388) > @@ -231,7 +231,7 @@ > if (count($id)) { > // move messages only when target mailbox is not the same as source mailbox > if ($mailbox!=$targetMailbox) { > - sqimap_msgs_list_copy($imapConnection,$id,$targetMailbox); > + sqimap_msgs_list_move($imapConnection,$id,$targetMailbox); > if ($auto_expunge) { > $cnt = sqimap_mailbox_expunge($imapConnection, $mailbox, true); > } else { > > > This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. > > ------------------------------------------------------------------------------ > ----- > squirrelmail-cvs mailing list > List address: squ...@li... > List info (subscribe/unsubscribe/change options): https://lists.sourceforge.net/lists/listinfo/squirrelmail-cvs > Repository: http://squirrelmail.org/svn > |