2007-02-02 17:28:04 +00:00
|
|
|
/******************************************************************************
|
|
|
|
* Copyright (c) 2002 - 2006 IBM Corporation.
|
|
|
|
* All rights reserved. This program and the accompanying materials
|
|
|
|
* are made available under the terms of the Eclipse Public License v1.0
|
|
|
|
* which accompanies this distribution, and is available at
|
|
|
|
* http://www.eclipse.org/legal/epl-v10.html
|
|
|
|
*
|
|
|
|
* Contributors:
|
|
|
|
* IBM Corporation - initial API and implementation
|
|
|
|
*****************************************************************************/
|
|
|
|
package com.ibm.wala.cast.js.test;
|
|
|
|
|
2007-02-08 19:08:41 +00:00
|
|
|
import java.io.File;
|
2013-11-30 18:02:32 +00:00
|
|
|
import java.io.FileNotFoundException;
|
2007-02-08 19:08:41 +00:00
|
|
|
import java.io.IOException;
|
|
|
|
import java.net.JarURLConnection;
|
|
|
|
import java.net.URL;
|
2011-04-04 21:20:31 +00:00
|
|
|
import java.util.Set;
|
2007-02-02 17:28:04 +00:00
|
|
|
|
2013-11-30 18:02:32 +00:00
|
|
|
import org.junit.Assert;
|
2007-02-02 17:28:04 +00:00
|
|
|
|
2012-01-27 20:15:33 +00:00
|
|
|
import com.ibm.wala.cast.ir.ssa.AstIRFactory;
|
2013-04-09 22:47:22 +00:00
|
|
|
import com.ibm.wala.cast.ir.translator.TranslatorToCAst.Error;
|
2015-12-01 02:28:40 +00:00
|
|
|
import com.ibm.wala.cast.js.html.DefaultSourceExtractor;
|
|
|
|
import com.ibm.wala.cast.js.html.JSSourceExtractor;
|
2011-05-23 16:27:07 +00:00
|
|
|
import com.ibm.wala.cast.js.html.WebPageLoaderFactory;
|
2011-04-04 21:20:31 +00:00
|
|
|
import com.ibm.wala.cast.js.html.WebUtil;
|
2012-01-06 21:48:01 +00:00
|
|
|
import com.ibm.wala.cast.js.ipa.callgraph.JSAnalysisOptions;
|
2009-04-30 13:16:52 +00:00
|
|
|
import com.ibm.wala.cast.js.ipa.callgraph.JSCFABuilder;
|
2013-04-30 19:10:06 +00:00
|
|
|
import com.ibm.wala.cast.js.ipa.callgraph.JSCallGraphUtil;
|
2009-04-30 13:16:52 +00:00
|
|
|
import com.ibm.wala.cast.js.ipa.callgraph.JSZeroOrOneXCFABuilder;
|
2013-06-06 06:30:52 +00:00
|
|
|
import com.ibm.wala.cast.js.ipa.callgraph.PropertyNameContextSelector;
|
|
|
|
import com.ibm.wala.cast.js.ipa.callgraph.correlations.extraction.CorrelatedPairExtractorFactory;
|
2007-12-06 14:58:36 +00:00
|
|
|
import com.ibm.wala.cast.js.loader.JavaScriptLoader;
|
2007-02-08 19:08:41 +00:00
|
|
|
import com.ibm.wala.cast.js.loader.JavaScriptLoaderFactory;
|
2013-04-09 22:47:22 +00:00
|
|
|
import com.ibm.wala.cast.loader.CAstAbstractLoader;
|
2013-06-06 06:30:52 +00:00
|
|
|
import com.ibm.wala.cast.tree.rewrite.CAstRewriterFactory;
|
2013-04-30 19:10:06 +00:00
|
|
|
import com.ibm.wala.classLoader.IMethod;
|
2017-02-03 01:33:27 +00:00
|
|
|
import com.ibm.wala.classLoader.Module;
|
2011-04-04 21:20:31 +00:00
|
|
|
import com.ibm.wala.classLoader.SourceModule;
|
2013-04-09 22:47:22 +00:00
|
|
|
import com.ibm.wala.classLoader.SourceURLModule;
|
2007-07-20 15:20:13 +00:00
|
|
|
import com.ibm.wala.ipa.callgraph.AnalysisCache;
|
2007-02-08 19:08:41 +00:00
|
|
|
import com.ibm.wala.ipa.callgraph.AnalysisScope;
|
|
|
|
import com.ibm.wala.ipa.callgraph.CallGraph;
|
2007-05-15 17:45:03 +00:00
|
|
|
import com.ibm.wala.ipa.callgraph.Entrypoint;
|
2007-02-08 19:08:41 +00:00
|
|
|
import com.ibm.wala.ipa.callgraph.propagation.PropagationCallGraphBuilder;
|
|
|
|
import com.ibm.wala.ipa.callgraph.propagation.cfa.ZeroXInstanceKeys;
|
2007-07-06 22:08:41 +00:00
|
|
|
import com.ibm.wala.ipa.cha.ClassHierarchyException;
|
|
|
|
import com.ibm.wala.ipa.cha.IClassHierarchy;
|
2012-01-27 20:15:33 +00:00
|
|
|
import com.ibm.wala.ssa.IRFactory;
|
2009-06-22 15:06:12 +00:00
|
|
|
import com.ibm.wala.util.CancelException;
|
2013-04-09 22:47:22 +00:00
|
|
|
import com.ibm.wala.util.WalaException;
|
2013-08-07 19:16:13 +00:00
|
|
|
import com.ibm.wala.util.collections.HashSetFactory;
|
2015-12-01 02:28:40 +00:00
|
|
|
import com.ibm.wala.util.functions.Function;
|
2013-11-30 18:02:32 +00:00
|
|
|
import com.ibm.wala.util.io.FileProvider;
|
2007-02-02 17:28:04 +00:00
|
|
|
|
2012-01-06 21:48:53 +00:00
|
|
|
/**
|
2012-02-17 20:22:55 +00:00
|
|
|
* TODO this class is a mess. rewrite.
|
2012-01-06 21:48:53 +00:00
|
|
|
*/
|
2012-02-17 20:21:59 +00:00
|
|
|
public class JSCallGraphBuilderUtil extends com.ibm.wala.cast.js.ipa.callgraph.JSCallGraphUtil {
|
2007-02-02 17:28:04 +00:00
|
|
|
|
2012-02-17 20:22:14 +00:00
|
|
|
public static enum CGBuilderType {
|
2013-06-07 16:48:00 +00:00
|
|
|
ZERO_ONE_CFA(false, true, true),
|
|
|
|
ZERO_ONE_CFA_NO_CALL_APPLY(false, false, true),
|
|
|
|
ONE_CFA(true, true, true);
|
|
|
|
|
2012-02-17 20:22:14 +00:00
|
|
|
private final boolean useOneCFA;
|
2012-02-17 20:22:55 +00:00
|
|
|
|
2012-02-17 20:22:14 +00:00
|
|
|
private final boolean handleCallApply;
|
2013-06-06 06:30:52 +00:00
|
|
|
|
|
|
|
private final boolean extractCorrelatedPairs;
|
2012-02-17 20:22:14 +00:00
|
|
|
|
2013-06-07 16:48:00 +00:00
|
|
|
private CGBuilderType(boolean useOneCFA, boolean handleCallApply, boolean extractCorrelatedPairs) {
|
2012-02-17 20:22:14 +00:00
|
|
|
this.useOneCFA = useOneCFA;
|
|
|
|
this.handleCallApply = handleCallApply;
|
2013-06-06 06:30:52 +00:00
|
|
|
this.extractCorrelatedPairs = extractCorrelatedPairs;
|
2012-02-17 20:22:14 +00:00
|
|
|
}
|
|
|
|
|
2012-02-17 20:22:55 +00:00
|
|
|
public boolean useOneCFA() {
|
2012-02-17 20:22:14 +00:00
|
|
|
return useOneCFA;
|
|
|
|
}
|
|
|
|
|
2012-02-17 20:22:55 +00:00
|
|
|
public boolean handleCallApply() {
|
2012-02-17 20:22:14 +00:00
|
|
|
return handleCallApply;
|
|
|
|
}
|
2012-02-17 20:22:55 +00:00
|
|
|
|
2013-06-06 06:30:52 +00:00
|
|
|
public boolean extractCorrelatedPairs() {
|
|
|
|
return extractCorrelatedPairs;
|
|
|
|
}
|
2012-02-17 20:22:14 +00:00
|
|
|
}
|
2012-02-17 20:22:55 +00:00
|
|
|
|
2013-06-25 15:57:37 +00:00
|
|
|
/**
|
|
|
|
* create a CG builder for script. Note that the script at dir/name is loaded via the classloader, not from the filesystem.
|
|
|
|
*/
|
2015-02-26 14:34:03 +00:00
|
|
|
public static JSCFABuilder makeScriptCGBuilder(String dir, String name, CGBuilderType builderType, ClassLoader loader) throws IOException, WalaException {
|
|
|
|
URL script = getURLforFile(dir, name, loader);
|
2013-06-06 09:05:39 +00:00
|
|
|
CAstRewriterFactory preprocessor = builderType.extractCorrelatedPairs ? new CorrelatedPairExtractorFactory(translatorFactory, script) : null;
|
2013-06-06 06:30:52 +00:00
|
|
|
JavaScriptLoaderFactory loaders = JSCallGraphUtil.makeLoaders(preprocessor);
|
2007-02-02 17:28:04 +00:00
|
|
|
|
2015-02-26 14:34:03 +00:00
|
|
|
AnalysisScope scope = makeScriptScope(dir, name, loaders, loader);
|
2012-01-27 20:15:33 +00:00
|
|
|
|
2012-02-17 20:22:14 +00:00
|
|
|
return makeCG(loaders, scope, builderType, AstIRFactory.makeDefaultFactory());
|
2012-01-27 20:15:33 +00:00
|
|
|
}
|
|
|
|
|
2015-02-26 14:34:03 +00:00
|
|
|
public static URL getURLforFile(String dir, String name, ClassLoader loader) throws IOException {
|
2013-11-30 18:02:32 +00:00
|
|
|
File f = null;
|
|
|
|
FileProvider provider = new FileProvider();
|
|
|
|
try {
|
2015-02-26 14:34:03 +00:00
|
|
|
f = provider.getFile(dir + File.separator + name, loader);
|
2013-11-30 18:02:32 +00:00
|
|
|
} catch (FileNotFoundException e) {
|
|
|
|
// I guess we need to do this on Windows sometimes? --MS
|
|
|
|
// if this fails, we won't catch the exception
|
|
|
|
f = provider.getFile(dir + "/" + name, JSCallGraphBuilderUtil.class.getClassLoader());
|
2007-02-02 17:28:04 +00:00
|
|
|
}
|
2013-11-30 18:02:32 +00:00
|
|
|
return f.toURI().toURL();
|
2013-06-06 06:30:52 +00:00
|
|
|
}
|
|
|
|
|
2015-02-26 14:34:03 +00:00
|
|
|
public static AnalysisScope makeScriptScope(String dir, String name, JavaScriptLoaderFactory loaders, ClassLoader loader) throws IOException {
|
|
|
|
return makeScope(makeSourceModules(dir, name, loader), loaders, JavaScriptLoader.JS);
|
|
|
|
}
|
|
|
|
|
2014-10-28 20:09:32 +00:00
|
|
|
public static AnalysisScope makeScriptScope(String dir, String name, JavaScriptLoaderFactory loaders) throws IOException {
|
2015-02-26 14:34:03 +00:00
|
|
|
return makeScope(makeSourceModules(dir, name, JSCallGraphBuilderUtil.class.getClassLoader()), loaders, JavaScriptLoader.JS);
|
2014-10-28 20:09:32 +00:00
|
|
|
}
|
|
|
|
|
2017-02-03 01:33:27 +00:00
|
|
|
public static Module[] makeSourceModules(String dir, String name) throws IOException {
|
2015-02-26 14:34:03 +00:00
|
|
|
return makeSourceModules(dir, name, JSCallGraphBuilderUtil.class.getClassLoader());
|
|
|
|
}
|
|
|
|
|
2017-02-03 01:33:27 +00:00
|
|
|
public static Module[] makeSourceModules(String dir, String name, ClassLoader loader) throws IOException {
|
2015-02-26 14:34:03 +00:00
|
|
|
URL script = getURLforFile(dir, name, loader);
|
2017-02-03 01:33:27 +00:00
|
|
|
Module[] modules = new Module[] {
|
2014-10-28 20:09:32 +00:00
|
|
|
(script.openConnection() instanceof JarURLConnection)? new SourceURLModule(script): makeSourceModule(script, dir, name),
|
|
|
|
getPrologueFile("prologue.js")
|
|
|
|
};
|
|
|
|
return modules;
|
2007-11-06 04:14:53 +00:00
|
|
|
}
|
|
|
|
|
2015-02-26 14:34:03 +00:00
|
|
|
public static JSCFABuilder makeScriptCGBuilder(String dir, String name, ClassLoader loader) throws IOException, WalaException {
|
|
|
|
return makeScriptCGBuilder(dir, name, CGBuilderType.ZERO_ONE_CFA, loader);
|
|
|
|
}
|
|
|
|
|
2013-04-09 22:47:22 +00:00
|
|
|
public static JSCFABuilder makeScriptCGBuilder(String dir, String name) throws IOException, WalaException {
|
2015-02-26 14:34:03 +00:00
|
|
|
return makeScriptCGBuilder(dir, name, CGBuilderType.ZERO_ONE_CFA, JSCallGraphBuilderUtil.class.getClassLoader());
|
2007-02-02 17:28:04 +00:00
|
|
|
}
|
|
|
|
|
2013-04-09 22:47:22 +00:00
|
|
|
public static CallGraph makeScriptCG(String dir, String name) throws IOException, IllegalArgumentException, CancelException, WalaException {
|
2015-02-26 14:34:03 +00:00
|
|
|
return makeScriptCG(dir, name, CGBuilderType.ZERO_ONE_CFA, JSCallGraphBuilderUtil.class.getClassLoader());
|
|
|
|
}
|
|
|
|
|
|
|
|
public static CallGraph makeScriptCG(String dir, String name, ClassLoader loader) throws IOException, IllegalArgumentException, CancelException, WalaException {
|
|
|
|
return makeScriptCG(dir, name, CGBuilderType.ZERO_ONE_CFA, loader);
|
2012-01-27 20:15:33 +00:00
|
|
|
}
|
|
|
|
|
2015-02-26 14:34:03 +00:00
|
|
|
public static CallGraph makeScriptCG(String dir, String name, CGBuilderType builderType, ClassLoader loader) throws IOException,
|
2013-04-09 22:47:22 +00:00
|
|
|
IllegalArgumentException, CancelException, WalaException {
|
2015-02-26 14:34:03 +00:00
|
|
|
PropagationCallGraphBuilder b = makeScriptCGBuilder(dir, name, builderType, loader);
|
2007-05-15 17:45:03 +00:00
|
|
|
CallGraph CG = b.makeCallGraph(b.getOptions());
|
2012-03-01 02:45:51 +00:00
|
|
|
// dumpCG(b.getPointerAnalysis(), CG);
|
2007-02-02 17:28:04 +00:00
|
|
|
return CG;
|
|
|
|
}
|
|
|
|
|
2013-04-30 19:10:06 +00:00
|
|
|
public static CallGraph makeScriptCG(SourceModule[] scripts, CGBuilderType builderType, IRFactory<IMethod> irFactory) throws IOException, IllegalArgumentException,
|
2013-04-09 22:47:22 +00:00
|
|
|
CancelException, WalaException {
|
2013-06-06 09:05:39 +00:00
|
|
|
CAstRewriterFactory preprocessor = builderType.extractCorrelatedPairs ? new CorrelatedPairExtractorFactory(translatorFactory, scripts) : null;
|
2013-06-06 06:30:52 +00:00
|
|
|
PropagationCallGraphBuilder b = makeCGBuilder(makeLoaders(preprocessor), scripts, builderType, irFactory);
|
2007-05-15 17:45:03 +00:00
|
|
|
CallGraph CG = b.makeCallGraph(b.getOptions());
|
2012-03-01 02:45:51 +00:00
|
|
|
// dumpCG(b.getPointerAnalysis(), CG);
|
2007-02-02 17:28:04 +00:00
|
|
|
return CG;
|
|
|
|
}
|
|
|
|
|
2015-12-01 02:28:40 +00:00
|
|
|
public static JSCFABuilder makeHTMLCGBuilder(URL url, Function<Void, JSSourceExtractor> fExtractor) throws IOException, WalaException {
|
|
|
|
return makeHTMLCGBuilder(url, CGBuilderType.ZERO_ONE_CFA, fExtractor);
|
2012-01-06 21:48:53 +00:00
|
|
|
}
|
2012-02-17 20:22:55 +00:00
|
|
|
|
2015-12-01 02:28:40 +00:00
|
|
|
public static JSCFABuilder makeHTMLCGBuilder(URL url, CGBuilderType builderType, Function<Void, JSSourceExtractor> fExtractor) throws IOException, WalaException {
|
2013-04-30 19:10:06 +00:00
|
|
|
IRFactory<IMethod> irFactory = AstIRFactory.makeDefaultFactory();
|
2013-06-06 09:05:39 +00:00
|
|
|
CAstRewriterFactory preprocessor = builderType.extractCorrelatedPairs ? new CorrelatedPairExtractorFactory(translatorFactory, url) : null;
|
2013-04-09 22:47:22 +00:00
|
|
|
JavaScriptLoaderFactory loaders = new WebPageLoaderFactory(translatorFactory, preprocessor);
|
2015-12-01 02:28:40 +00:00
|
|
|
SourceModule[] scriptsArray = makeHtmlScope(url, loaders, fExtractor);
|
2013-08-07 19:16:13 +00:00
|
|
|
|
|
|
|
JSCFABuilder builder = makeCGBuilder(loaders, scriptsArray, builderType, irFactory);
|
|
|
|
if(builderType.extractCorrelatedPairs)
|
|
|
|
builder.setContextSelector(new PropertyNameContextSelector(builder.getAnalysisCache(), 2, builder.getContextSelector()));
|
|
|
|
builder.setBaseURL(url);
|
|
|
|
return builder;
|
|
|
|
}
|
|
|
|
|
2015-12-01 02:28:40 +00:00
|
|
|
public static SourceModule[] makeHtmlScope(URL url, JavaScriptLoaderFactory loaders, Function<Void,JSSourceExtractor> fExtractor) {
|
2017-02-03 01:33:27 +00:00
|
|
|
Set<Module> scripts = HashSetFactory.make();
|
2013-08-07 19:16:13 +00:00
|
|
|
|
|
|
|
JavaScriptLoader.addBootstrapFile(WebUtil.preamble);
|
2013-08-07 20:44:21 +00:00
|
|
|
scripts.add(getPrologueFile("prologue.js"));
|
|
|
|
scripts.add(getPrologueFile("preamble.js"));
|
2013-08-07 19:16:13 +00:00
|
|
|
|
2013-04-09 22:47:22 +00:00
|
|
|
try {
|
2015-12-01 02:28:40 +00:00
|
|
|
scripts.addAll(WebUtil.extractScriptFromHTML(url, fExtractor).fst);
|
2013-04-09 22:47:22 +00:00
|
|
|
} catch (Error e) {
|
|
|
|
SourceModule dummy = new SourceURLModule(url);
|
2013-08-07 19:16:13 +00:00
|
|
|
scripts.add(dummy);
|
2013-04-09 22:47:22 +00:00
|
|
|
((CAstAbstractLoader)loaders.getTheLoader()).addMessage(dummy, e.warning);
|
|
|
|
}
|
2013-08-07 19:16:13 +00:00
|
|
|
|
|
|
|
SourceModule[] scriptsArray = scripts.toArray(new SourceModule[ scripts.size() ]);
|
|
|
|
return scriptsArray;
|
2007-02-02 17:28:04 +00:00
|
|
|
}
|
|
|
|
|
2015-12-01 02:28:40 +00:00
|
|
|
public static CallGraph makeHTMLCG(URL url, Function<Void, JSSourceExtractor> fExtractor) throws IOException, IllegalArgumentException, CancelException, WalaException {
|
|
|
|
PropagationCallGraphBuilder b = makeHTMLCGBuilder(url, fExtractor);
|
2007-05-15 17:45:03 +00:00
|
|
|
CallGraph CG = b.makeCallGraph(b.getOptions());
|
2012-01-27 20:15:33 +00:00
|
|
|
dumpCG(b.getPointerAnalysis(), CG);
|
2011-05-06 15:14:32 +00:00
|
|
|
return CG;
|
|
|
|
}
|
|
|
|
|
2015-12-01 02:28:40 +00:00
|
|
|
public static CallGraph makeHTMLCG(URL url, CGBuilderType builderType, Function<Void, JSSourceExtractor> fExtractor) throws IOException, IllegalArgumentException,
|
2013-04-09 22:47:22 +00:00
|
|
|
CancelException, WalaException {
|
2015-12-01 02:28:40 +00:00
|
|
|
PropagationCallGraphBuilder b = makeHTMLCGBuilder(url, builderType, fExtractor);
|
2012-01-06 21:48:53 +00:00
|
|
|
CallGraph CG = b.makeCallGraph(b.getOptions());
|
2007-02-02 17:28:04 +00:00
|
|
|
return CG;
|
|
|
|
}
|
|
|
|
|
2013-04-30 19:10:06 +00:00
|
|
|
public static JSCFABuilder makeCGBuilder(JavaScriptLoaderFactory loaders, SourceModule[] scripts, CGBuilderType builderType, IRFactory<IMethod> irFactory) throws IOException, WalaException {
|
2007-12-10 04:33:40 +00:00
|
|
|
AnalysisScope scope = makeScope(scripts, loaders, JavaScriptLoader.JS);
|
2012-02-17 20:22:14 +00:00
|
|
|
return makeCG(loaders, scope, builderType, irFactory);
|
2007-02-02 17:28:04 +00:00
|
|
|
}
|
|
|
|
|
2013-04-30 19:10:06 +00:00
|
|
|
protected static JSCFABuilder makeCG(JavaScriptLoaderFactory loaders, AnalysisScope scope, CGBuilderType builderType, IRFactory<IMethod> irFactory) throws IOException, WalaException {
|
2007-02-02 17:28:04 +00:00
|
|
|
try {
|
2007-07-06 22:08:41 +00:00
|
|
|
IClassHierarchy cha = makeHierarchy(scope, loaders);
|
2012-01-06 21:52:26 +00:00
|
|
|
com.ibm.wala.cast.js.util.Util.checkForFrontEndErrors(cha);
|
2007-05-15 17:45:03 +00:00
|
|
|
Iterable<Entrypoint> roots = makeScriptRoots(cha);
|
2012-01-06 21:48:01 +00:00
|
|
|
JSAnalysisOptions options = makeOptions(scope, cha, roots);
|
2012-02-17 20:22:55 +00:00
|
|
|
options.setHandleCallApply(builderType.handleCallApply());
|
2012-01-27 20:15:33 +00:00
|
|
|
AnalysisCache cache = makeCache(irFactory);
|
2012-02-17 20:22:55 +00:00
|
|
|
JSCFABuilder builder = new JSZeroOrOneXCFABuilder(cha, options, cache, null, null, ZeroXInstanceKeys.ALLOCATIONS,
|
|
|
|
builderType.useOneCFA());
|
2013-06-06 06:30:52 +00:00
|
|
|
if(builderType.extractCorrelatedPairs())
|
|
|
|
builder.setContextSelector(new PropertyNameContextSelector(builder.getAnalysisCache(), 2, builder.getContextSelector()));
|
2007-02-02 17:28:04 +00:00
|
|
|
|
|
|
|
return builder;
|
|
|
|
} catch (ClassHierarchyException e) {
|
2007-05-15 17:45:03 +00:00
|
|
|
Assert.assertTrue("internal error building class hierarchy", false);
|
2007-02-02 17:28:04 +00:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
2015-12-01 02:28:40 +00:00
|
|
|
|
|
|
|
public static CallGraph makeHTMLCG(URL url, CGBuilderType zeroOneCfaNoCallApply) throws IllegalArgumentException, IOException, CancelException, WalaException {
|
|
|
|
return makeHTMLCG(url, zeroOneCfaNoCallApply, DefaultSourceExtractor.factory);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static CallGraph makeHTMLCG(URL url) throws IllegalArgumentException, IOException, CancelException, WalaException {
|
|
|
|
return makeHTMLCG(url, DefaultSourceExtractor.factory);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static JSCFABuilder makeHTMLCGBuilder(URL url) throws IOException, WalaException {
|
|
|
|
return makeHTMLCGBuilder(url, DefaultSourceExtractor.factory);
|
|
|
|
}
|
2007-02-02 17:28:04 +00:00
|
|
|
}
|