Patch 9.0.0577
Problem: Buffer underflow with unexpected :finally.
Solution: Check CSF_TRY can be found.
Files: src/ex_eval.c, src/testdir/test_trycatch.vim
*** ../vim-9.0.0576/src/ex_eval.c 2022-09-21 18:59:10.671074961 +0100
--- src/ex_eval.c 2022-09-24 17:21:20.299859969 +0100
***************
*** 1935,2062 ****
if (cmdmod_error(FALSE))
return;
! if (cstack->cs_trylevel <= 0 || cstack->cs_idx < 0)
eap->errmsg = _(e_finally_without_try);
! else
{
! if (!(cstack->cs_flags[cstack->cs_idx] & CSF_TRY))
! {
! eap->errmsg = get_end_emsg(cstack);
! for (idx = cstack->cs_idx - 1; idx > 0; --idx)
! if (cstack->cs_flags[idx] & CSF_TRY)
! break;
! // Make this error pending, so that the commands in the following
! // finally clause can be executed. This overrules also a pending
! // ":continue", ":break", ":return", or ":finish".
! pending = CSTP_ERROR;
! }
! else
! idx = cstack->cs_idx;
! if (cstack->cs_flags[idx] & CSF_FINALLY)
{
! // Give up for a multiple ":finally" and ignore it.
! eap->errmsg = _(e_multiple_finally);
! return;
}
- rewind_conditionals(cstack, idx, CSF_WHILE | CSF_FOR,
- &cstack->cs_looplevel);
/*
! * Don't do something when the corresponding try block never got active
! * (because of an inactive surrounding conditional or after an error or
! * interrupt or throw) or for a ":finally" without ":try" or a multiple
! * ":finally". After every other error (did_emsg or the conditional
! * errors detected above) or after an interrupt (got_int) or an
! * exception (did_throw), the finally clause must be executed.
*/
! skip = !(cstack->cs_flags[cstack->cs_idx] & CSF_TRUE);
! if (!skip)
{
! // When debugging or a breakpoint was encountered, display the
! // debug prompt (if not already done). The user then knows that the
! // finally clause is executed.
! if (dbg_check_skipped(eap))
! {
! // Handle a ">quit" debug command as if an interrupt had
! // occurred before the ":finally". That is, discard the
! // original exception and replace it by an interrupt
! // exception.
! (void)do_intthrow(cstack);
! }
!
! /*
! * If there is a preceding catch clause and it caught the exception,
! * finish the exception now. This happens also after errors except
! * when this is a multiple ":finally" or one not within a ":try".
! * After an error or interrupt, this also discards a pending
! * ":continue", ":break", ":finish", or ":return" from the preceding
! * try block or catch clause.
! */
! cleanup_conditionals(cstack, CSF_TRY, FALSE);
!
! if (cstack->cs_idx >= 0
! && (cstack->cs_flags[cstack->cs_idx] & CSF_TRY))
! {
! // Variables declared in the previous block can no longer be
! // used.
! leave_block(cstack);
! enter_block(cstack);
! }
! /*
! * Make did_emsg, got_int, did_throw pending. If set, they overrule
! * a pending ":continue", ":break", ":return", or ":finish". Then
! * we have particularly to discard a pending return value (as done
! * by the call to cleanup_conditionals() above when did_emsg or
! * got_int is set). The pending values are restored by the
! * ":endtry", except if there is a new error, interrupt, exception,
! * ":continue", ":break", ":return", or ":finish" in the following
! * finally clause. A missing ":endwhile", ":endfor" or ":endif"
! * detected here is treated as if did_emsg and did_throw had
! * already been set, respectively in case that the error is not
! * converted to an exception, did_throw had already been unset.
! * We must not set did_emsg here since that would suppress the
! * error message.
! */
! if (pending == CSTP_ERROR || did_emsg || got_int || did_throw)
{
! if (cstack->cs_pending[cstack->cs_idx] == CSTP_RETURN)
! {
! report_discard_pending(CSTP_RETURN,
! cstack->cs_rettv[cstack->cs_idx]);
! discard_pending_return(cstack->cs_rettv[cstack->cs_idx]);
! }
! if (pending == CSTP_ERROR && !did_emsg)
! pending |= (THROW_ON_ERROR) ? CSTP_THROW : 0;
! else
! pending |= did_throw ? CSTP_THROW : 0;
! pending |= did_emsg ? CSTP_ERROR : 0;
! pending |= got_int ? CSTP_INTERRUPT : 0;
! cstack->cs_pending[cstack->cs_idx] = pending;
!
! // It's mandatory that the current exception is stored in the
! // cstack so that it can be rethrown at the ":endtry" or be
! // discarded if the finally clause is left by a ":continue",
! // ":break", ":return", ":finish", error, interrupt, or another
! // exception. When emsg() is called for a missing ":endif" or
! // a missing ":endwhile"/":endfor" detected here, the
! // exception will be discarded.
! if (did_throw && cstack->cs_exception[cstack->cs_idx]
! != current_exception)
! internal_error("ex_finally()");
}
!
! /*
! * Set CSL_HAD_FINA, so do_cmdline() will reset did_emsg,
! * got_int, and did_throw and make the finally clause active.
! * This will happen after emsg() has been called for a missing
! * ":endif" or a missing ":endwhile"/":endfor" detected here, so
! * that the following finally clause will be executed even then.
! */
! cstack->cs_lflags |= CSL_HAD_FINA;
}
}
}
--- 1935,2061 ----
if (cmdmod_error(FALSE))
return;
! for (idx = cstack->cs_idx; idx >= 0; --idx)
! if (cstack->cs_flags[idx] & CSF_TRY)
! break;
! if (cstack->cs_trylevel <= 0 || idx < 0)
! {
eap->errmsg = _(e_finally_without_try);
! return;
! }
!
! if (!(cstack->cs_flags[cstack->cs_idx] & CSF_TRY))
{
! eap->errmsg = get_end_emsg(cstack);
! // Make this error pending, so that the commands in the following
! // finally clause can be executed. This overrules also a pending
! // ":continue", ":break", ":return", or ":finish".
! pending = CSTP_ERROR;
! }
!
! if (cstack->cs_flags[idx] & CSF_FINALLY)
! {
! // Give up for a multiple ":finally" and ignore it.
! eap->errmsg = _(e_multiple_finally);
! return;
! }
! rewind_conditionals(cstack, idx, CSF_WHILE | CSF_FOR,
! &cstack->cs_looplevel);
!
! /*
! * Don't do something when the corresponding try block never got active
! * (because of an inactive surrounding conditional or after an error or
! * interrupt or throw) or for a ":finally" without ":try" or a multiple
! * ":finally". After every other error (did_emsg or the conditional
! * errors detected above) or after an interrupt (got_int) or an
! * exception (did_throw), the finally clause must be executed.
! */
! skip = !(cstack->cs_flags[cstack->cs_idx] & CSF_TRUE);
! if (!skip)
! {
! // When debugging or a breakpoint was encountered, display the
! // debug prompt (if not already done). The user then knows that the
! // finally clause is executed.
! if (dbg_check_skipped(eap))
{
! // Handle a ">quit" debug command as if an interrupt had
! // occurred before the ":finally". That is, discard the
! // original exception and replace it by an interrupt
! // exception.
! (void)do_intthrow(cstack);
}
/*
! * If there is a preceding catch clause and it caught the exception,
! * finish the exception now. This happens also after errors except
! * when this is a multiple ":finally" or one not within a ":try".
! * After an error or interrupt, this also discards a pending
! * ":continue", ":break", ":finish", or ":return" from the preceding
! * try block or catch clause.
*/
! cleanup_conditionals(cstack, CSF_TRY, FALSE);
! if (cstack->cs_idx >= 0
! && (cstack->cs_flags[cstack->cs_idx] & CSF_TRY))
{
! // Variables declared in the previous block can no longer be
! // used.
! leave_block(cstack);
! enter_block(cstack);
! }
! /*
! * Make did_emsg, got_int, did_throw pending. If set, they overrule
! * a pending ":continue", ":break", ":return", or ":finish". Then
! * we have particularly to discard a pending return value (as done
! * by the call to cleanup_conditionals() above when did_emsg or
! * got_int is set). The pending values are restored by the
! * ":endtry", except if there is a new error, interrupt, exception,
! * ":continue", ":break", ":return", or ":finish" in the following
! * finally clause. A missing ":endwhile", ":endfor" or ":endif"
! * detected here is treated as if did_emsg and did_throw had
! * already been set, respectively in case that the error is not
! * converted to an exception, did_throw had already been unset.
! * We must not set did_emsg here since that would suppress the
! * error message.
! */
! if (pending == CSTP_ERROR || did_emsg || got_int || did_throw)
! {
! if (cstack->cs_pending[cstack->cs_idx] == CSTP_RETURN)
{
! report_discard_pending(CSTP_RETURN,
! cstack->cs_rettv[cstack->cs_idx]);
! discard_pending_return(cstack->cs_rettv[cstack->cs_idx]);
}
! if (pending == CSTP_ERROR && !did_emsg)
! pending |= (THROW_ON_ERROR) ? CSTP_THROW : 0;
! else
! pending |= did_throw ? CSTP_THROW : 0;
! pending |= did_emsg ? CSTP_ERROR : 0;
! pending |= got_int ? CSTP_INTERRUPT : 0;
! cstack->cs_pending[cstack->cs_idx] = pending;
!
! // It's mandatory that the current exception is stored in the
! // cstack so that it can be rethrown at the ":endtry" or be
! // discarded if the finally clause is left by a ":continue",
! // ":break", ":return", ":finish", error, interrupt, or another
! // exception. When emsg() is called for a missing ":endif" or
! // a missing ":endwhile"/":endfor" detected here, the
! // exception will be discarded.
! if (did_throw && cstack->cs_exception[cstack->cs_idx]
! != current_exception)
! internal_error("ex_finally()");
}
+
+ /*
+ * Set CSL_HAD_FINA, so do_cmdline() will reset did_emsg,
+ * got_int, and did_throw and make the finally clause active.
+ * This will happen after emsg() has been called for a missing
+ * ":endif" or a missing ":endwhile"/":endfor" detected here, so
+ * that the following finally clause will be executed even then.
+ */
+ cstack->cs_lflags |= CSL_HAD_FINA;
}
}
***************
*** 2076,2260 ****
if (cmdmod_error(FALSE))
return;
! if (cstack->cs_trylevel <= 0 || cstack->cs_idx < 0)
! eap->errmsg = _(e_endtry_without_try);
! else
{
! /*
! * Don't do something after an error, interrupt or throw in the try
! * block, catch clause, or finally clause preceding this ":endtry" or
! * when an error or interrupt occurred after a ":continue", ":break",
! * ":return", or ":finish" in a try block or catch clause preceding this
! * ":endtry" or when the try block never got active (because of an
! * inactive surrounding conditional or after an error or interrupt or
! * throw) or when there is a surrounding conditional and it has been
! * made inactive by a ":continue", ":break", ":return", or ":finish" in
! * the finally clause. The latter case need not be tested since then
! * anything pending has already been discarded. */
! skip = did_emsg || got_int || did_throw
|| !(cstack->cs_flags[cstack->cs_idx] & CSF_TRUE);
! if (!(cstack->cs_flags[cstack->cs_idx] & CSF_TRY))
! {
! eap->errmsg = get_end_emsg(cstack);
! // Find the matching ":try" and report what's missing.
! idx = cstack->cs_idx;
! do
! --idx;
! while (idx > 0 && !(cstack->cs_flags[idx] & CSF_TRY));
! rewind_conditionals(cstack, idx, CSF_WHILE | CSF_FOR,
! &cstack->cs_looplevel);
! skip = TRUE;
! /*
! * If an exception is being thrown, discard it to prevent it from
! * being rethrown at the end of this function. It would be
! * discarded by the error message, anyway. Resets did_throw.
! * This does not affect the script termination due to the error
! * since "trylevel" is decremented after emsg() has been called.
! */
! if (did_throw)
! discard_current_exception();
! // report eap->errmsg, also when there already was an error
! did_emsg = FALSE;
! }
! else
! {
! idx = cstack->cs_idx;
! // Check the flags only when not in a skipped block.
! if (!skip && in_vim9script()
&& (cstack->cs_flags[idx] & (CSF_CATCH|CSF_FINALLY)) == 0)
- {
- // try/endtry without any catch or finally: give an error and
- // continue.
- eap->errmsg = _(e_missing_catch_or_finally);
- }
-
- /*
- * If we stopped with the exception currently being thrown at this
- * try conditional since we didn't know that it doesn't have
- * a finally clause, we need to rethrow it after closing the try
- * conditional.
- */
- if (did_throw && (cstack->cs_flags[idx] & CSF_TRUE)
- && !(cstack->cs_flags[idx] & CSF_FINALLY))
- rethrow = TRUE;
- }
-
- // If there was no finally clause, show the user when debugging or
- // a breakpoint was encountered that the end of the try conditional has
- // been reached: display the debug prompt (if not already done). Do
- // this on normal control flow or when an exception was thrown, but not
- // on an interrupt or error not converted to an exception or when
- // a ":break", ":continue", ":return", or ":finish" is pending. These
- // actions are carried out immediately.
- if ((rethrow || (!skip
- && !(cstack->cs_flags[idx] & CSF_FINALLY)
- && !cstack->cs_pending[idx]))
- && dbg_check_skipped(eap))
{
! // Handle a ">quit" debug command as if an interrupt had occurred
! // before the ":endtry". That is, throw an interrupt exception and
! // set "skip" and "rethrow".
! if (got_int)
! {
! skip = TRUE;
! (void)do_intthrow(cstack);
! // The do_intthrow() call may have reset did_throw or
! // cstack->cs_pending[idx].
! rethrow = FALSE;
! if (did_throw && !(cstack->cs_flags[idx] & CSF_FINALLY))
! rethrow = TRUE;
! }
}
/*
! * If a ":return" is pending, we need to resume it after closing the
! * try conditional; remember the return value. If there was a finally
! * clause making an exception pending, we need to rethrow it. Make it
! * the exception currently being thrown.
*/
! if (!skip)
{
! pending = cstack->cs_pending[idx];
! cstack->cs_pending[idx] = CSTP_NONE;
! if (pending == CSTP_RETURN)
! rettv = cstack->cs_rettv[idx];
! else if (pending & CSTP_THROW)
! current_exception = cstack->cs_exception[idx];
}
! /*
! * Discard anything pending on an error, interrupt, or throw in the
! * finally clause. If there was no ":finally", discard a pending
! * ":continue", ":break", ":return", or ":finish" if an error or
! * interrupt occurred afterwards, but before the ":endtry" was reached.
! * If an exception was caught by the last of the catch clauses and there
! * was no finally clause, finish the exception now. This happens also
! * after errors except when this ":endtry" is not within a ":try".
! * Restore "emsg_silent" if it has been reset by this try conditional.
! */
! (void)cleanup_conditionals(cstack, CSF_TRY | CSF_SILENT, TRUE);
! if (cstack->cs_idx >= 0
! && (cstack->cs_flags[cstack->cs_idx] & CSF_TRY))
! leave_block(cstack);
! --cstack->cs_trylevel;
! if (!skip)
! {
! report_resume_pending(pending,
(pending == CSTP_RETURN) ? rettv :
(pending & CSTP_THROW) ? (void *)current_exception : NULL);
! switch (pending)
! {
! case CSTP_NONE:
! break;
! // Reactivate a pending ":continue", ":break", ":return",
! // ":finish" from the try block or a catch clause of this try
! // conditional. This is skipped, if there was an error in an
! // (unskipped) conditional command or an interrupt afterwards
! // or if the finally clause is present and executed a new error,
! // interrupt, throw, ":continue", ":break", ":return", or
! // ":finish".
! case CSTP_CONTINUE:
! ex_continue(eap);
! break;
! case CSTP_BREAK:
! ex_break(eap);
! break;
! case CSTP_RETURN:
! do_return(eap, FALSE, FALSE, rettv);
! break;
! case CSTP_FINISH:
! do_finish(eap, FALSE);
! break;
!
! // When the finally clause was entered due to an error,
! // interrupt or throw (as opposed to a ":continue", ":break",
! // ":return", or ":finish"), restore the pending values of
! // did_emsg, got_int, and did_throw. This is skipped, if there
! // was a new error, interrupt, throw, ":continue", ":break",
! // ":return", or ":finish". in the finally clause.
! default:
! if (pending & CSTP_ERROR)
! did_emsg = TRUE;
! if (pending & CSTP_INTERRUPT)
! got_int = TRUE;
! if (pending & CSTP_THROW)
! rethrow = TRUE;
! break;
! }
}
-
- if (rethrow)
- // Rethrow the current exception (within this cstack).
- do_throw(cstack);
}
}
/*
--- 2075,2257 ----
if (cmdmod_error(FALSE))
return;
! for (idx = cstack->cs_idx; idx >= 0; --idx)
! if (cstack->cs_flags[idx] & CSF_TRY)
! break;
! if (cstack->cs_trylevel <= 0 || idx < 0)
{
! eap->errmsg = _(e_endtry_without_try);
! return;
! }
!
! /*
! * Don't do something after an error, interrupt or throw in the try
! * block, catch clause, or finally clause preceding this ":endtry" or
! * when an error or interrupt occurred after a ":continue", ":break",
! * ":return", or ":finish" in a try block or catch clause preceding this
! * ":endtry" or when the try block never got active (because of an
! * inactive surrounding conditional or after an error or interrupt or
! * throw) or when there is a surrounding conditional and it has been
! * made inactive by a ":continue", ":break", ":return", or ":finish" in
! * the finally clause. The latter case need not be tested since then
! * anything pending has already been discarded. */
! skip = did_emsg || got_int || did_throw
|| !(cstack->cs_flags[cstack->cs_idx] & CSF_TRUE);
! if (!(cstack->cs_flags[cstack->cs_idx] & CSF_TRY))
! {
! eap->errmsg = get_end_emsg(cstack);
! // Find the matching ":try" and report what's missing.
! rewind_conditionals(cstack, idx, CSF_WHILE | CSF_FOR,
! &cstack->cs_looplevel);
! skip = TRUE;
! /*
! * If an exception is being thrown, discard it to prevent it from
! * being rethrown at the end of this function. It would be
! * discarded by the error message, anyway. Resets did_throw.
! * This does not affect the script termination due to the error
! * since "trylevel" is decremented after emsg() has been called.
! */
! if (did_throw)
! discard_current_exception();
! // report eap->errmsg, also when there already was an error
! did_emsg = FALSE;
! }
! else
! {
! idx = cstack->cs_idx;
! // Check the flags only when not in a skipped block.
! if (!skip && in_vim9script()
&& (cstack->cs_flags[idx] & (CSF_CATCH|CSF_FINALLY)) == 0)
{
! // try/endtry without any catch or finally: give an error and
! // continue.
! eap->errmsg = _(e_missing_catch_or_finally);
}
/*
! * If we stopped with the exception currently being thrown at this
! * try conditional since we didn't know that it doesn't have
! * a finally clause, we need to rethrow it after closing the try
! * conditional.
*/
! if (did_throw && (cstack->cs_flags[idx] & CSF_TRUE)
! && !(cstack->cs_flags[idx] & CSF_FINALLY))
! rethrow = TRUE;
! }
!
! // If there was no finally clause, show the user when debugging or
! // a breakpoint was encountered that the end of the try conditional has
! // been reached: display the debug prompt (if not already done). Do
! // this on normal control flow or when an exception was thrown, but not
! // on an interrupt or error not converted to an exception or when
! // a ":break", ":continue", ":return", or ":finish" is pending. These
! // actions are carried out immediately.
! if ((rethrow || (!skip && !(cstack->cs_flags[idx] & CSF_FINALLY)
! && !cstack->cs_pending[idx]))
! && dbg_check_skipped(eap))
! {
! // Handle a ">quit" debug command as if an interrupt had occurred
! // before the ":endtry". That is, throw an interrupt exception and
! // set "skip" and "rethrow".
! if (got_int)
{
! skip = TRUE;
! (void)do_intthrow(cstack);
! // The do_intthrow() call may have reset did_throw or
! // cstack->cs_pending[idx].
! rethrow = FALSE;
! if (did_throw && !(cstack->cs_flags[idx] & CSF_FINALLY))
! rethrow = TRUE;
}
+ }
! /*
! * If a ":return" is pending, we need to resume it after closing the
! * try conditional; remember the return value. If there was a finally
! * clause making an exception pending, we need to rethrow it. Make it
! * the exception currently being thrown.
! */
! if (!skip)
! {
! pending = cstack->cs_pending[idx];
! cstack->cs_pending[idx] = CSTP_NONE;
! if (pending == CSTP_RETURN)
! rettv = cstack->cs_rettv[idx];
! else if (pending & CSTP_THROW)
! current_exception = cstack->cs_exception[idx];
! }
! /*
! * Discard anything pending on an error, interrupt, or throw in the
! * finally clause. If there was no ":finally", discard a pending
! * ":continue", ":break", ":return", or ":finish" if an error or
! * interrupt occurred afterwards, but before the ":endtry" was reached.
! * If an exception was caught by the last of the catch clauses and there
! * was no finally clause, finish the exception now. This happens also
! * after errors except when this ":endtry" is not within a ":try".
! * Restore "emsg_silent" if it has been reset by this try conditional.
! */
! (void)cleanup_conditionals(cstack, CSF_TRY | CSF_SILENT, TRUE);
!
! if (cstack->cs_idx >= 0 && (cstack->cs_flags[cstack->cs_idx] & CSF_TRY))
! leave_block(cstack);
! --cstack->cs_trylevel;
! if (!skip)
! {
! report_resume_pending(pending,
(pending == CSTP_RETURN) ? rettv :
(pending & CSTP_THROW) ? (void *)current_exception : NULL);
! switch (pending)
! {
! case CSTP_NONE:
! break;
! // Reactivate a pending ":continue", ":break", ":return",
! // ":finish" from the try block or a catch clause of this try
! // conditional. This is skipped, if there was an error in an
! // (unskipped) conditional command or an interrupt afterwards
! // or if the finally clause is present and executed a new error,
! // interrupt, throw, ":continue", ":break", ":return", or
! // ":finish".
! case CSTP_CONTINUE:
! ex_continue(eap);
! break;
! case CSTP_BREAK:
! ex_break(eap);
! break;
! case CSTP_RETURN:
! do_return(eap, FALSE, FALSE, rettv);
! break;
! case CSTP_FINISH:
! do_finish(eap, FALSE);
! break;
!
! // When the finally clause was entered due to an error,
! // interrupt or throw (as opposed to a ":continue", ":break",
! // ":return", or ":finish"), restore the pending values of
! // did_emsg, got_int, and did_throw. This is skipped, if there
! // was a new error, interrupt, throw, ":continue", ":break",
! // ":return", or ":finish". in the finally clause.
! default:
! if (pending & CSTP_ERROR)
! did_emsg = TRUE;
! if (pending & CSTP_INTERRUPT)
! got_int = TRUE;
! if (pending & CSTP_THROW)
! rethrow = TRUE;
! break;
}
}
+
+ if (rethrow)
+ // Rethrow the current exception (within this cstack).
+ do_throw(cstack);
}
/*
*** ../vim-9.0.0576/src/testdir/test_trycatch.vim 2022-05-27 13:47:02.000000000 +0100
--- src/testdir/test_trycatch.vim 2022-09-24 17:22:06.711776571 +0100
***************
*** 3,8 ****
--- 3,9 ----
source check.vim
source shared.vim
+ import './vim9.vim' as v9
"-------------------------------------------------------------------------------
" Test environment {{{1
***************
*** 2008,2013 ****
--- 2009,2035 ----
call assert_fails('try | for i in range(5) | endif | endtry', 'E580:')
call assert_fails('try | while v:true | endtry', 'E170:')
call assert_fails('try | if v:true | endtry', 'E171:')
+
+ " this was using a negative index in cstack[]
+ let lines =<< trim END
+ try
+ for
+ if
+ endwhile
+ if
+ finally
+ END
+ call v9.CheckScriptFailure(lines, 'E690:')
+
+ let lines =<< trim END
+ try
+ for
+ if
+ endwhile
+ if
+ endtry
+ END
+ call v9.CheckScriptFailure(lines, 'E690:')
endfunc
" Test for verbose messages with :try :catch, and :finally {{{1
*** ../vim-9.0.0576/src/version.c 2022-09-24 15:55:22.385626232 +0100
--- src/version.c 2022-09-24 17:03:39.749548167 +0100
***************
*** 701,702 ****
--- 701,704 ----
{ /* Add new patch number below this line */
+ /**/
+ 577,
/**/
--
Microsoft says that MS-Windows is much better for you than Linux.
That's like the Pope saying that catholicism is much better for
you than protestantism.
/// Bram Moolenaar -- Br...@Moolenaar.net --
http://www.Moolenaar.net \\\
/// \\\
\\\ sponsor Vim, vote for features --
http://www.Vim.org/sponsor/ ///
\\\ help me help AIDS victims --
http://ICCF-Holland.org ///