freemarker-notifications mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ddek...@apache.org
Subject [2/2] incubator-freemarker git commit: Fixed FREEMARKER-18: JSP EL function and custom tag with the same name overwrite each other
Date Sun, 27 Mar 2016 09:58:07 GMT
Fixed FREEMARKER-18: JSP EL function and custom tag with the same name overwrite each other


Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/c1d26de3
Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/c1d26de3
Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/c1d26de3

Branch: refs/heads/2.3-gae
Commit: c1d26de343756ca618b1fc4bc9e44d4611e141f6
Parents: 25465f0
Author: ddekany <ddekany@apache.org>
Authored: Sun Mar 27 11:53:14 2016 +0200
Committer: ddekany <ddekany@apache.org>
Committed: Sun Mar 27 11:55:46 2016 +0200

----------------------------------------------------------------------
 .../ext/jsp/CustomTagAndELFunctionCombiner.java | 210 +++++++++++++++++++
 .../java/freemarker/ext/jsp/TaglibFactory.java  |  37 +++-
 .../ext/jsp/RealServletContainertTest.java      |  10 +-
 .../ext/jsp/webapps/basic/CONTENTS.txt          |   3 +
 .../WEB-INF/el-function-tag-name-clash.tld      |  50 +++++
 .../webapps/basic/elFunctionsTagNameClash.ftl   |  25 +++
 .../webapps/basic/elFunctionsTagNameClash.jsp   |  26 +++
 7 files changed, 350 insertions(+), 11 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/c1d26de3/src/main/java/freemarker/ext/jsp/CustomTagAndELFunctionCombiner.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/ext/jsp/CustomTagAndELFunctionCombiner.java b/src/main/java/freemarker/ext/jsp/CustomTagAndELFunctionCombiner.java
new file mode 100644
index 0000000..ba771e3
--- /dev/null
+++ b/src/main/java/freemarker/ext/jsp/CustomTagAndELFunctionCombiner.java
@@ -0,0 +1,210 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package freemarker.ext.jsp;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.List;
+import java.util.Map;
+
+import freemarker.core.BugException;
+import freemarker.core.Environment;
+import freemarker.core._UnexpectedTypeErrorExplainerTemplateModel;
+import freemarker.ext.beans.SimpleMethodModel;
+import freemarker.template.TemplateDirectiveBody;
+import freemarker.template.TemplateDirectiveModel;
+import freemarker.template.TemplateException;
+import freemarker.template.TemplateMethodModelEx;
+import freemarker.template.TemplateModel;
+import freemarker.template.TemplateModelException;
+import freemarker.template.TemplateSequenceModel;
+import freemarker.template.TemplateTransformModel;
+import freemarker.template.utility.ClassUtil;
+
+/**
+ * Used when a custom JSP tag and an EL function uses the same name in a tag library, to
create a single FTL value from
+ * the two. As FTL as no separate namespace for "tags" and functions, both aspect has to
be implemented by the same
+ * value.
+ * 
+ * @sine 2.3.25
+ */
+@SuppressWarnings("rawtypes")
+class CustomTagAndELFunctionCombiner {
+
+    /**
+     * @param customTag
+     *            Either a {@link TemplateDirectiveModel} or a {@link TemplateTransformModel}.
+     */
+    static TemplateModel combine(TemplateModel customTag, TemplateMethodModelEx elFunction)
{
+        if (customTag instanceof TemplateDirectiveModel) {
+            return elFunction instanceof SimpleMethodModel //
+                    ? new TemplateDirectiveModelAndSimpleMethodModel( //
+                            (TemplateDirectiveModel) customTag, (SimpleMethodModel) elFunction)
//
+                    : new TemplateDirectiveModelAndTemplateMethodModelEx( //
+                            (TemplateDirectiveModel) customTag, elFunction);
+        } else if (customTag instanceof TemplateTransformModel) {
+            return (elFunction instanceof SimpleMethodModel)
+                    ? new TemplateTransformModelAndSimpleMethodModel( //
+                            (TemplateTransformModel) customTag, (SimpleMethodModel) elFunction)
//
+                    : new TemplateTransformModelAndTemplateMethodModelEx( //
+                            (TemplateTransformModel) customTag, elFunction);
+        } else {
+            throw new BugException(
+                    "Unexpected custom JSP tag class: " + ClassUtil.getShortClassNameOfObject(customTag));
+        }
+    }
+
+    /**
+     * Tells if the value can be used as the "custom tag" parameter to
+     * {@link #combine(TemplateModel, TemplateMethodModelEx)}.
+     */
+    static boolean canBeCombinedAsCustomTag(TemplateModel tm) {
+        return (tm instanceof TemplateDirectiveModel || tm instanceof TemplateTransformModel)
+                && !(tm instanceof CombinedTemplateModel);
+    }
+
+    /**
+     * Tells if the value can be used as the "EL function" parameter to
+     * {@link #combine(TemplateModel, TemplateMethodModelEx)}.
+     */
+    static boolean canBeCombinedAsELFunction(TemplateModel tm) {
+        return tm instanceof TemplateMethodModelEx && !(tm instanceof CombinedTemplateModel);
+    }
+
+    private static class CombinedTemplateModel {
+        // Marker only
+    };
+
+    private static class TemplateDirectiveModelAndSimpleMethodModel extends CombinedTemplateModel
+            implements TemplateDirectiveModel, TemplateMethodModelEx, TemplateSequenceModel,
+            _UnexpectedTypeErrorExplainerTemplateModel {
+
+        private final TemplateDirectiveModel templateDirectiveModel;
+        private final SimpleMethodModel simpleMethodModel;
+
+        public TemplateDirectiveModelAndSimpleMethodModel( //
+                TemplateDirectiveModel templateDirectiveModel, SimpleMethodModel simpleMethodModel)
{
+            this.templateDirectiveModel = templateDirectiveModel;
+            this.simpleMethodModel = simpleMethodModel;
+        }
+
+        public Object exec(List arguments) throws TemplateModelException {
+            return simpleMethodModel.exec(arguments);
+        }
+
+        public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody
body)
+                throws TemplateException, IOException {
+            templateDirectiveModel.execute(env, params, loopVars, body);
+        }
+
+        public Object[] explainTypeError(Class[] expectedClasses) {
+            return simpleMethodModel.explainTypeError(expectedClasses);
+        }
+
+        public TemplateModel get(int index) throws TemplateModelException {
+            return simpleMethodModel.get(index);
+        }
+
+        public int size() throws TemplateModelException {
+            return simpleMethodModel.size();
+        }
+
+    }
+
+    private static class TemplateDirectiveModelAndTemplateMethodModelEx extends CombinedTemplateModel
+            implements TemplateDirectiveModel, TemplateMethodModelEx {
+
+        private final TemplateDirectiveModel templateDirectiveModel;
+        private final TemplateMethodModelEx templateMethodModelEx;
+
+        public TemplateDirectiveModelAndTemplateMethodModelEx( //
+                TemplateDirectiveModel templateDirectiveModel, TemplateMethodModelEx templateMethodModelEx)
{
+            this.templateDirectiveModel = templateDirectiveModel;
+            this.templateMethodModelEx = templateMethodModelEx;
+        }
+
+        public Object exec(List arguments) throws TemplateModelException {
+            return templateMethodModelEx.exec(arguments);
+        }
+
+        public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody
body)
+                throws TemplateException, IOException {
+            templateDirectiveModel.execute(env, params, loopVars, body);
+        }
+
+    }
+
+    private static class TemplateTransformModelAndTemplateMethodModelEx extends CombinedTemplateModel
+            implements TemplateTransformModel, TemplateMethodModelEx {
+
+        private final TemplateTransformModel templateTransformModel;
+        private final TemplateMethodModelEx templateMethodModelEx;
+
+        public TemplateTransformModelAndTemplateMethodModelEx( //
+                TemplateTransformModel templateTransformModel, TemplateMethodModelEx templateMethodModelEx)
{
+            this.templateTransformModel = templateTransformModel;
+            this.templateMethodModelEx = templateMethodModelEx;
+        }
+
+        public Object exec(List arguments) throws TemplateModelException {
+            return templateMethodModelEx.exec(arguments);
+        }
+
+        public Writer getWriter(Writer out, Map args) throws TemplateModelException, IOException
{
+            return templateTransformModel.getWriter(out, args);
+        }
+
+    }
+
+    private static class TemplateTransformModelAndSimpleMethodModel extends CombinedTemplateModel
+            implements TemplateTransformModel, TemplateMethodModelEx, TemplateSequenceModel,
+            _UnexpectedTypeErrorExplainerTemplateModel {
+
+        private final TemplateTransformModel templateTransformModel;
+        private final SimpleMethodModel simpleMethodModel;
+
+        public TemplateTransformModelAndSimpleMethodModel( //
+                TemplateTransformModel templateTransformModel, SimpleMethodModel simpleMethodModel)
{
+            this.templateTransformModel = templateTransformModel;
+            this.simpleMethodModel = simpleMethodModel;
+        }
+
+        public Object exec(List arguments) throws TemplateModelException {
+            return simpleMethodModel.exec(arguments);
+        }
+
+        public Object[] explainTypeError(Class[] expectedClasses) {
+            return simpleMethodModel.explainTypeError(expectedClasses);
+        }
+
+        public TemplateModel get(int index) throws TemplateModelException {
+            return simpleMethodModel.get(index);
+        }
+
+        public int size() throws TemplateModelException {
+            return simpleMethodModel.size();
+        }
+
+        public Writer getWriter(Writer out, Map args) throws TemplateModelException, IOException
{
+            return templateTransformModel.getWriter(out, args);
+        }
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/c1d26de3/src/main/java/freemarker/ext/jsp/TaglibFactory.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/ext/jsp/TaglibFactory.java b/src/main/java/freemarker/ext/jsp/TaglibFactory.java
index 0be4d9d..cab6222 100644
--- a/src/main/java/freemarker/ext/jsp/TaglibFactory.java
+++ b/src/main/java/freemarker/ext/jsp/TaglibFactory.java
@@ -37,6 +37,7 @@ import java.net.URL;
 import java.net.URLConnection;
 import java.net.URLDecoder;
 import java.net.URLEncoder;
+import java.security.acl.Owner;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Enumeration;
@@ -1618,7 +1619,7 @@ public class TaglibFactory implements TemplateHashModel {
 
         private final BeansWrapper beansWrapper;
 
-        private final Map tagsAndFunctions = new HashMap();
+        private final Map<String, TemplateModel> tagsAndFunctions = new HashMap<String,
TemplateModel>();
         private final List listeners = new ArrayList();
 
         private Locator locator;
@@ -1649,7 +1650,7 @@ public class TaglibFactory implements TemplateHashModel {
             }
         }
 
-        Map getTagsAndFunctions() {
+        Map<String, TemplateModel> getTagsAndFunctions() {
             return tagsAndFunctions;
         }
 
@@ -1710,12 +1711,12 @@ public class TaglibFactory implements TemplateHashModel {
 
                     final Class tagClass = resoveClassFromTLD(tagClassCData, "custom tag",
tagNameCData);
 
-                    final TemplateModel impl;
+                    final TemplateModel customTagModel;
                     try {
                         if (Tag.class.isAssignableFrom(tagClass)) {
-                            impl = new TagTransformModel(tagNameCData, tagClass);
+                            customTagModel = new TagTransformModel(tagNameCData, tagClass);
                         } else {
-                            impl = new SimpleTagDirectiveModel(tagNameCData, tagClass);
+                            customTagModel = new SimpleTagDirectiveModel(tagNameCData, tagClass);
                         }
                     } catch (IntrospectionException e) {
                         throw new TldParsingSAXException(
@@ -1724,7 +1725,16 @@ public class TaglibFactory implements TemplateHashModel {
                                 e);
                     }
 
-                    tagsAndFunctions.put(tagNameCData, impl);
+                    TemplateModel replacedTagOrFunction = tagsAndFunctions.put(tagNameCData,
customTagModel);
+                    if (replacedTagOrFunction != null) {
+                        if (CustomTagAndELFunctionCombiner.canBeCombinedAsELFunction(replacedTagOrFunction))
{
+                            tagsAndFunctions.put(tagNameCData, CustomTagAndELFunctionCombiner.combine(
+                                    customTagModel, (TemplateMethodModelEx) replacedTagOrFunction));
+                        } else {
+                            LOG.warn("TLD contains multiple tags with name " + StringUtil.jQuote(tagNameCData)
+                                    + "; keeping only the last one.");
+                        }
+                    }
 
                     tagNameCData = null;
                     tagClassCData = null;
@@ -1756,16 +1766,25 @@ public class TaglibFactory implements TemplateHashModel {
                                 locator);
                     }
 
-                    final TemplateMethodModelEx methodModel;
+                    final TemplateMethodModelEx elFunctionModel;
                     try {
-                        methodModel = beansWrapper.wrap(null, functionMethod);
+                        elFunctionModel = beansWrapper.wrap(null, functionMethod);
                     } catch (Exception e) {
                         throw new TldParsingSAXException(
                                 "FreeMarker object wrapping failed on method : " + functionMethod,
                                 locator);
                     }
 
-                    tagsAndFunctions.put(functionNameCData, methodModel);
+                    TemplateModel replacedTagOrFunction = tagsAndFunctions.put(functionNameCData,
elFunctionModel);
+                    if (replacedTagOrFunction != null) {
+                        if (CustomTagAndELFunctionCombiner.canBeCombinedAsCustomTag(replacedTagOrFunction))
{
+                            tagsAndFunctions.put(functionNameCData, CustomTagAndELFunctionCombiner.combine(
+                                    replacedTagOrFunction, elFunctionModel));
+                        } else {
+                            LOG.warn("TLD contains multiple functions with name " + StringUtil.jQuote(functionNameCData)
+                                    + "; keeping only the last one.");
+                        }
+                    }
 
                     functionNameCData = null;
                     functionClassCData = null;

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/c1d26de3/src/test/java/freemarker/ext/jsp/RealServletContainertTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/ext/jsp/RealServletContainertTest.java b/src/test/java/freemarker/ext/jsp/RealServletContainertTest.java
index 51d0231..1038c2e 100644
--- a/src/test/java/freemarker/ext/jsp/RealServletContainertTest.java
+++ b/src/test/java/freemarker/ext/jsp/RealServletContainertTest.java
@@ -111,10 +111,16 @@ public class RealServletContainertTest extends WebAppTestCase {
 
     @Test
     public void basicELFunctions() throws Exception {
-        //System.out.println(getResponseContent(WEBAPP_EL_FUNCTIONS, "tester?view=1.jsp"));
-        //System.out.println(getResponseContent(WEBAPP_EL_FUNCTIONS, "tester?view=1.ftl"));
         assertJSPAndFTLOutputEquals(WEBAPP_BASIC, "tester?view=customELFunctions1");
     }
+
+    // https://issues.apache.org/jira/browse/FREEMARKER-18
+    @Test
+    public void basicELFunctionsTagNameClash() throws Exception {
+        // System.out.println(getResponseContent(WEBAPP_BASIC, "tester?view=elFunctionsTagNameClash.jsp"));
+        // System.out.println(getResponseContent(WEBAPP_BASIC, "tester?view=elFunctionsTagNameClash.ftl"));
+        assertJSPAndFTLOutputEquals(WEBAPP_BASIC, "tester?view=elFunctionsTagNameClash");
+    }
     
     @Test
     public void tldDiscoveryBasic() throws Exception {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/c1d26de3/src/test/resources/freemarker/ext/jsp/webapps/basic/CONTENTS.txt
----------------------------------------------------------------------
diff --git a/src/test/resources/freemarker/ext/jsp/webapps/basic/CONTENTS.txt b/src/test/resources/freemarker/ext/jsp/webapps/basic/CONTENTS.txt
index c3f6857..19127ec 100644
--- a/src/test/resources/freemarker/ext/jsp/webapps/basic/CONTENTS.txt
+++ b/src/test/resources/freemarker/ext/jsp/webapps/basic/CONTENTS.txt
@@ -23,10 +23,13 @@ attributes.ftl
 customELFunctions1.ftl
 customELFunctions1.jsp
 customTags1.ftl
+elFunctionsTagNameClash.ftl
+elFunctionsTagNameClash.jsp
 trivial-jstl-@Ignore.ftl
 trivial.ftl
 trivial.jsp
 
+WEB-INF/el-function-tag-name-clash.tld
 WEB-INF/el-functions.tld
 WEB-INF/expected
 WEB-INF/test.tld

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/c1d26de3/src/test/resources/freemarker/ext/jsp/webapps/basic/WEB-INF/el-function-tag-name-clash.tld
----------------------------------------------------------------------
diff --git a/src/test/resources/freemarker/ext/jsp/webapps/basic/WEB-INF/el-function-tag-name-clash.tld
b/src/test/resources/freemarker/ext/jsp/webapps/basic/WEB-INF/el-function-tag-name-clash.tld
new file mode 100644
index 0000000..c0eb104
--- /dev/null
+++ b/src/test/resources/freemarker/ext/jsp/webapps/basic/WEB-INF/el-function-tag-name-clash.tld
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+  
+    http://www.apache.org/licenses/LICENSE-2.0
+  
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+-->
+<taglib xmlns="http://java.sun.com/xml/ns/javaee" version="2.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd">
+  <tlib-version>3.0</tlib-version>
+  <short-name>FreeMarker-test-taglib</short-name>
+  <uri>http://freemarker.org/test/taglibs/el-functions-tag-name-clash</uri>
+
+  <tag>
+    <name>foo</name>
+    <tag-class>freemarker.ext.jsp.taglibmembers.TestSimpleTag2</tag-class>
+    <body-content>empty</body-content>
+  </tag>
+
+  <tag>
+    <name>bar</name>
+    <tag-class>freemarker.ext.jsp.taglibmembers.TestTag</tag-class>
+    <body-content>JSP</body-content>
+  </tag>
+
+  <function>
+    <name>foo</name>
+    <function-class>freemarker.ext.jsp.taglibmembers.TestFunctions</function-class>
+    <function-signature>java.lang.String reverse(java.lang.String)</function-signature>
+  </function>
+
+  <function>
+    <name>bar</name>
+    <function-class>freemarker.ext.jsp.taglibmembers.TestFunctions</function-class>
+    <function-signature>java.lang.String reverse(java.lang.String)</function-signature>
+  </function>
+
+</taglib>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/c1d26de3/src/test/resources/freemarker/ext/jsp/webapps/basic/elFunctionsTagNameClash.ftl
----------------------------------------------------------------------
diff --git a/src/test/resources/freemarker/ext/jsp/webapps/basic/elFunctionsTagNameClash.ftl
b/src/test/resources/freemarker/ext/jsp/webapps/basic/elFunctionsTagNameClash.ftl
new file mode 100644
index 0000000..dbac123
--- /dev/null
+++ b/src/test/resources/freemarker/ext/jsp/webapps/basic/elFunctionsTagNameClash.ftl
@@ -0,0 +1,25 @@
+<#--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+  
+    http://www.apache.org/licenses/LICENSE-2.0
+  
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+-->
+<#assign t = JspTaglibs["http://freemarker.org/test/taglibs/el-functions-tag-name-clash"]>
+
+${t.foo("abc")}
+<@t.foo />
+
+${t.bar("abc")}
+<@t.bar>abc</@t.bar>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/c1d26de3/src/test/resources/freemarker/ext/jsp/webapps/basic/elFunctionsTagNameClash.jsp
----------------------------------------------------------------------
diff --git a/src/test/resources/freemarker/ext/jsp/webapps/basic/elFunctionsTagNameClash.jsp
b/src/test/resources/freemarker/ext/jsp/webapps/basic/elFunctionsTagNameClash.jsp
new file mode 100644
index 0000000..d6abe6f
--- /dev/null
+++ b/src/test/resources/freemarker/ext/jsp/webapps/basic/elFunctionsTagNameClash.jsp
@@ -0,0 +1,26 @@
+<%--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+  
+    http://www.apache.org/licenses/LICENSE-2.0
+  
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+--%>
+<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
+<%@ taglib prefix="t" uri="http://freemarker.org/test/taglibs/el-functions-tag-name-clash"
%>
+
+${t:foo("abc")}
+<t:foo />
+
+${t:bar("abc")}
+<t:bar>abc</t:bar>
\ No newline at end of file


Mime
View raw message