Simple and Scoped Attribute decoders.
authorcantor <cantor@cb58f699-b61c-0410-a6fe-9272a202ed29>
Sun, 18 Feb 2007 06:07:01 +0000 (06:07 +0000)
committercantor <cantor@cb58f699-b61c-0410-a6fe-9272a202ed29>
Sun, 18 Feb 2007 06:07:01 +0000 (06:07 +0000)
git-svn-id: https://svn.middleware.georgetown.edu/cpp-sp/trunk@2173 cb58f699-b61c-0410-a6fe-9272a202ed29

shibsp/Makefile.am
shibsp/SPConfig.cpp
shibsp/SPConfig.h
shibsp/attribute/Attribute.cpp
shibsp/attribute/AttributeDecoder.h [new file with mode: 0644]
shibsp/attribute/ScopedAttributeDecoder.cpp [new file with mode: 0644]
shibsp/attribute/SimpleAttributeDecoder.cpp [new file with mode: 0644]
shibsp/impl/XMLServiceProvider.cpp
shibsp/shibsp.vcproj

index 819e749..8ca36c5 100644 (file)
@@ -38,6 +38,7 @@ libshibspinclude_HEADERS = \
 
 attrinclude_HEADERS = \
        attribute/Attribute.h \
+       attribute/AttributeDecoder.h \
        attribute/NameIDAttribute.h \
        attribute/ScopedAttribute.h \
        attribute/SimpleAttribute.h
@@ -73,6 +74,8 @@ libshibsp_la_SOURCES = \
        SessionCache.cpp \
        SPConfig.cpp \
        attribute/Attribute.cpp \
+       attribute/SimpleAttributeDecoder.cpp \
+       attribute/ScopedAttributeDecoder.cpp \
        binding/impl/SOAPClient.cpp \
        impl/RemotedSessionCache.cpp \
        impl/StorageServiceSessionCache.cpp \
index 81a33f0..4bcd466 100644 (file)
 #include "ServiceProvider.h"
 #include "SessionCache.h"
 #include "SPConfig.h"
+#include "attribute/AttributeDecoder.h"
 #include "metadata/MetadataExt.h"
 #include "remoting/ListenerService.h"
 #include "security/PKIXTrustEngine.h"
 
-#include "attribute/SimpleAttribute.h"
-#include "attribute/ScopedAttribute.h"
-#include "attribute/NameIDAttribute.h"
-
 #include <log4cpp/Category.hh>
 #include <saml/SAMLConfig.h>
 #include <xmltooling/util/NDC.h>
@@ -108,6 +105,7 @@ bool SPInternalConfig::init(const char* catalog_path)
     registerSessionCaches();
     registerServiceProviders();
     registerAttributeFactories();
+    registerAttributeDecoders();
     
     log.info("library initialization complete");
     return true;
@@ -124,8 +122,6 @@ void SPInternalConfig::term()
     delete m_serviceProvider;
     m_serviceProvider = NULL;
 
-    Attribute::deregisterFactories();
-    
     SingleLogoutServiceManager.deregisterFactories();
     SessionInitiatorManager.deregisterFactories();
     SessionCacheManager.deregisterFactories();
@@ -134,6 +130,8 @@ void SPInternalConfig::term()
     ManageNameIDServiceManager.deregisterFactories();
     ListenerServiceManager.deregisterFactories();
     HandlerManager.deregisterFactories();
+    Attribute::deregisterFactories();
+    AttributeDecoderManager.deregisterFactories();
     AssertionConsumerServiceManager.deregisterFactories();
     AccessControlManager.deregisterFactories();
 
index 70b46c2..78a4ff8 100644 (file)
@@ -34,6 +34,7 @@
 namespace shibsp {
 
     class SHIBSP_API AccessControl;
+    class SHIBSP_API AttributeDecoder;
     class SHIBSP_API Handler;
     class SHIBSP_API ListenerService;
     class SHIBSP_API RequestMapper;
@@ -142,6 +143,11 @@ namespace shibsp {
         xmltooling::PluginManager<AccessControl,const xercesc::DOMElement*> AccessControlManager;
 
         /**
+         * Manages factories for AttributeDecoder plugins.
+         */
+        xmltooling::PluginManager<AttributeDecoder,const xercesc::DOMElement*> AttributeDecoderManager;
+
+        /**
          * Manages factories for Handler plugins that implement AssertionConsumerService functionality.
          */
         xmltooling::PluginManager<Handler,const xercesc::DOMElement*> AssertionConsumerServiceManager;
index 1cb990b..d6df740 100644 (file)
  * A resolved attribute.
  */
 
-
 #include "internal.h"
+#include "attribute/AttributeDecoder.h"
 #include "attribute/SimpleAttribute.h"
 #include "attribute/ScopedAttribute.h"
 #include "attribute/NameIDAttribute.h"
 
+#include <shibsp/SPConfig.h>
+
 using namespace shibsp;
+using namespace xmltooling;
 using namespace std;
 
 namespace shibsp {
 
-    Attribute* SimpleAttributeFactory(DDF& in) {
+    SHIBSP_DLLLOCAL Attribute* SimpleAttributeFactory(DDF& in) {
         return new SimpleAttribute(in);
     }
     
-    Attribute* ScopedAttributeFactory(DDF& in) {
+    SHIBSP_DLLLOCAL Attribute* ScopedAttributeFactory(DDF& in) {
         return new ScopedAttribute(in);
     }
     
-    Attribute* NameIDAttributeFactory(DDF& in) {
+    SHIBSP_DLLLOCAL Attribute* NameIDAttributeFactory(DDF& in) {
         return new NameIDAttribute(in);
     }
     
+    SHIBSP_DLLLOCAL PluginManager<AttributeDecoder,const DOMElement*>::Factory SimpleAttributeDecoderFactory;
+    SHIBSP_DLLLOCAL PluginManager<AttributeDecoder,const DOMElement*>::Factory ScopedAttributeDecoderFactory;
+    //SHIBSP_DLLLOCAL PluginManager<AttributeDecoder,const DOMElement*>::Factory NameIDAttributeDecoderFactory;
 };
 
+void shibsp::registerAttributeDecoders()
+{
+    SPConfig& conf = SPConfig::getConfig();
+    conf.AttributeDecoderManager.registerFactory(SIMPLE_ATTRIBUTE_DECODER, SimpleAttributeDecoderFactory);
+    conf.AttributeDecoderManager.registerFactory(SCOPED_ATTRIBUTE_DECODER, ScopedAttributeDecoderFactory);
+    //conf.AttributeDecoderManager.registerFactory(NAMEID_ATTRIBUTE_DECODER, NameIDAttributeDecoderFactory);
+}
+
 void shibsp::registerAttributeFactories()
 {
     Attribute::registerFactory("", SimpleAttributeFactory);
diff --git a/shibsp/attribute/AttributeDecoder.h b/shibsp/attribute/AttributeDecoder.h
new file mode 100644 (file)
index 0000000..944a59c
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ *  Copyright 2001-2007 Internet2
+ * 
+ * Licensed 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.
+ */
+
+/**
+ * @file shibsp/attribute/AttributeDecoder.h
+ * 
+ * Decodes SAML NameID/Attribute objects into resolved Attributes.
+ */
+
+#ifndef __shibsp_attrdecoder_h__
+#define __shibsp_attrdecoder_h__
+
+#include <shibsp/attribute/Attribute.h>
+#include <xmltooling/XMLObject.h>
+
+namespace shibsp {
+
+    /**
+     * Decodes XML objects into resolved Attributes.
+     */
+    class SHIBSP_API AttributeDecoder
+    {
+        MAKE_NONCOPYABLE(AttributeDecoder);
+    protected:
+        AttributeDecoder() {}
+    public:
+        virtual ~AttributeDecoder() {}
+        
+        /**
+         * Decodes an XMLObject into a resolved Attribute.
+         * 
+         * @param id        ID of resolved attribute
+         * @param xmlObject XMLObject to decode
+         * @return a resolved Attribute
+         */
+        virtual Attribute* decode(const char* id, const xmltooling::XMLObject* xmlObject) const=0;
+    };
+
+    /** Decodes SimpleAttributes */
+    #define SIMPLE_ATTRIBUTE_DECODER "Simple"
+    
+    /** Decodes ScopedAttributes */
+    #define SCOPED_ATTRIBUTE_DECODER "Scoped"
+
+    /** Decodes NameIDAttributes */
+    #define NAMEID_ATTRIBUTE_DECODER "NameID"
+
+    /** Registers built-in AttributeDecoders into the runtime. */
+    void registerAttributeDecoders();
+};
+
+#endif /* __shibsp_attrdecoder_h__ */
diff --git a/shibsp/attribute/ScopedAttributeDecoder.cpp b/shibsp/attribute/ScopedAttributeDecoder.cpp
new file mode 100644 (file)
index 0000000..8bab5ec
--- /dev/null
@@ -0,0 +1,175 @@
+/*\r
+ *  Copyright 2001-2007 Internet2\r
+ * \r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *     http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+/**\r
+ * ScopedAttributeDecoder.cpp\r
+ * \r
+ * Decodes SAML into ScopedAttributes\r
+ */\r
+\r
+#include "internal.h"\r
+#include "attribute/AttributeDecoder.h"\r
+#include "attribute/ScopedAttribute.h"\r
+\r
+#include <log4cpp/Category.hh>\r
+#include <saml/saml1/core/Assertions.h>\r
+#include <saml/saml2/core/Assertions.h>\r
+\r
+using namespace shibsp;\r
+using namespace opensaml::saml1;\r
+using namespace opensaml::saml2;\r
+using namespace xmltooling;\r
+using namespace log4cpp;\r
+using namespace std;\r
+\r
+namespace shibsp {\r
+    class ScopedAttributeDecoder : virtual public AttributeDecoder\r
+    {\r
+    public:\r
+        ScopedAttributeDecoder(const DOMElement* e) {}\r
+        ~ScopedAttributeDecoder() {}\r
+\r
+        shibsp::Attribute* decode(const char* id, const XMLObject* xmlObject) const;\r
+    };\r
+\r
+    AttributeDecoder* SHIBSP_DLLLOCAL ScopedAttributeDecoderFactory(const DOMElement* const & e)\r
+    {\r
+        return new ScopedAttributeDecoder(e);\r
+    }\r
+\r
+    static const XMLCh Scope[] = UNICODE_LITERAL_5(S,c,o,p,e);\r
+};\r
+\r
+shibsp::Attribute* ScopedAttributeDecoder::decode(const char* id, const XMLObject* xmlObject) const\r
+{\r
+    char* val;\r
+    char* scope;\r
+    const XMLCh* xmlscope;\r
+    QName scopeqname(NULL,Scope);\r
+    auto_ptr<ScopedAttribute> scoped(new ScopedAttribute(id));\r
+    vector< pair<string,string> >& dest = scoped->getValues();\r
+    vector<XMLObject*>::const_iterator v,stop;\r
+\r
+    Category& log = Category::getInstance(SHIBSP_LOGCAT".AttributeDecoder");\r
+    \r
+    if (xmlObject && XMLString::equals(opensaml::saml1::Attribute::LOCAL_NAME,xmlObject->getElementQName().getLocalPart())) {\r
+        const opensaml::saml2::Attribute* saml2attr = dynamic_cast<const opensaml::saml2::Attribute*>(xmlObject);\r
+        if (saml2attr) {\r
+            const vector<XMLObject*>& values = saml2attr->getAttributeValues();\r
+            v = values.begin();\r
+            stop = values.end();\r
+            if (log.isDebugEnabled()) {\r
+                auto_ptr_char n(saml2attr->getName());\r
+                log.debug("decoding ScopedAttribute (%s) from SAML 2 Attribute (%s) with %lu value(s)", id, n.get() ? n.get() : "unnamed", values.size());\r
+            }\r
+        }\r
+        else {\r
+            const opensaml::saml1::Attribute* saml1attr = dynamic_cast<const opensaml::saml1::Attribute*>(xmlObject);\r
+            if (saml1attr) {\r
+                const vector<XMLObject*>& values = saml2attr->getAttributeValues();\r
+                v = values.begin();\r
+                stop = values.end();\r
+                if (log.isDebugEnabled()) {\r
+                    auto_ptr_char n(saml1attr->getAttributeName());\r
+                    log.debug("decoding ScopedAttribute (%s) from SAML 1 Attribute (%s) with %lu value(s)", id, n.get() ? n.get() : "unnamed", values.size());\r
+                }\r
+            }\r
+            else {\r
+                log.warn("XMLObject type not recognized by ScopedAttributeDecoder, no values returned");\r
+                return NULL;\r
+            }\r
+        }\r
+\r
+        for (; v!=stop; ++v) {\r
+            if (!(*v)->hasChildren()) {\r
+                val = toUTF8((*v)->getTextContent());\r
+                if (val && *val) {\r
+                    const AttributeExtensibleXMLObject* aexo=dynamic_cast<const AttributeExtensibleXMLObject*>(*v);\r
+                    xmlscope = aexo->getAttribute(scopeqname);\r
+                    if (xmlscope && *xmlscope) {\r
+                        scope = toUTF8(xmlscope);\r
+                        dest.push_back(make_pair(val,scope));\r
+                        delete[] scope;\r
+                    }\r
+                    else {\r
+                        scope = strchr(val, '@');\r
+                        if (scope) {\r
+                            *scope++ = 0;\r
+                            if (*scope)\r
+                                dest.push_back(make_pair(val,scope));\r
+                            else\r
+                                log.warn("ignoring unscoped AttributeValue");\r
+                        }\r
+                        else {\r
+                            log.warn("ignoring unscoped AttributeValue");\r
+                        }\r
+                    }\r
+                }\r
+                else {\r
+                    log.warn("skipping empty AttributeValue");\r
+                }\r
+                delete[] val;\r
+            }\r
+            else {\r
+                log.warn("skipping complex AttributeValue");\r
+            }\r
+        }\r
+\r
+        return dest.empty() ? NULL : scoped.release();\r
+    }\r
+\r
+    const NameID* saml2name = dynamic_cast<const NameID*>(xmlObject);\r
+    if (saml2name) {\r
+        if (log.isDebugEnabled()) {\r
+            auto_ptr_char f(saml2name->getFormat());\r
+            log.debug("decoding ScopedAttribute (%s) from SAML 2 NameID with Format (%s)", id, f.get() ? f.get() : "unspecified");\r
+        }\r
+        val = toUTF8(saml2name->getName());\r
+    }\r
+    else {\r
+        const NameIdentifier* saml1name = dynamic_cast<const NameIdentifier*>(xmlObject);\r
+        if (saml1name) {\r
+            if (log.isDebugEnabled()) {\r
+                auto_ptr_char f(saml1name->getFormat());\r
+                log.debug("decoding ScopedAttribute (%s) from SAML 1 NameIdentifier with Format (%s)", id, f.get() ? f.get() : "unspecified");\r
+            }\r
+            val = toUTF8(saml1name->getName());\r
+        }\r
+        else {\r
+            log.warn("XMLObject type not recognized by ScopedAttributeDecoder, no values returned");\r
+            return NULL;\r
+        }\r
+    }\r
+\r
+    if (val && *val && *val!='@') {\r
+        scope = strchr(val, '@');\r
+        if (scope) {\r
+            *scope++ = 0;\r
+            if (*scope)\r
+                dest.push_back(make_pair(val,scope));\r
+            else\r
+                log.warn("ignoring NameID with no scope");\r
+        }\r
+        else {\r
+            log.warn("ignoring NameID with no scope delimiter (@)");\r
+        }\r
+    }\r
+    else {\r
+        log.warn("ignoring empty NameID");\r
+    }\r
+    delete[] val;\r
+    return dest.empty() ? NULL : scoped.release();\r
+}\r
diff --git a/shibsp/attribute/SimpleAttributeDecoder.cpp b/shibsp/attribute/SimpleAttributeDecoder.cpp
new file mode 100644 (file)
index 0000000..f199b1f
--- /dev/null
@@ -0,0 +1,137 @@
+/*\r
+ *  Copyright 2001-2007 Internet2\r
+ * \r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *     http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+/**\r
+ * SimpleAttributeDecoder.cpp\r
+ * \r
+ * Decodes SAML into SimpleAttributes\r
+ */\r
+\r
+#include "internal.h"\r
+#include "attribute/AttributeDecoder.h"\r
+#include "attribute/SimpleAttribute.h"\r
+\r
+#include <log4cpp/Category.hh>\r
+#include <saml/saml1/core/Assertions.h>\r
+#include <saml/saml2/core/Assertions.h>\r
+\r
+using namespace shibsp;\r
+using namespace opensaml::saml1;\r
+using namespace opensaml::saml2;\r
+using namespace xmltooling;\r
+using namespace log4cpp;\r
+using namespace std;\r
+\r
+namespace shibsp {\r
+    class SimpleAttributeDecoder : virtual public AttributeDecoder\r
+    {\r
+    public:\r
+        SimpleAttributeDecoder(const DOMElement* e) {}\r
+        ~SimpleAttributeDecoder() {}\r
+\r
+        shibsp::Attribute* decode(const char* id, const XMLObject* xmlObject) const;\r
+    };\r
+\r
+    AttributeDecoder* SHIBSP_DLLLOCAL SimpleAttributeDecoderFactory(const DOMElement* const & e)\r
+    {\r
+        return new SimpleAttributeDecoder(e);\r
+    }\r
+};\r
+\r
+shibsp::Attribute* SimpleAttributeDecoder::decode(const char* id, const XMLObject* xmlObject) const\r
+{\r
+    char* val;\r
+    auto_ptr<SimpleAttribute> simple(new SimpleAttribute(id));\r
+    vector<string>& dest = simple->getValues();\r
+    vector<XMLObject*>::const_iterator v,stop;\r
+\r
+    Category& log = Category::getInstance(SHIBSP_LOGCAT".AttributeDecoder");\r
+\r
+    if (xmlObject && XMLString::equals(opensaml::saml1::Attribute::LOCAL_NAME,xmlObject->getElementQName().getLocalPart())) {\r
+        const opensaml::saml2::Attribute* saml2attr = dynamic_cast<const opensaml::saml2::Attribute*>(xmlObject);\r
+        if (saml2attr) {\r
+            const vector<XMLObject*>& values = saml2attr->getAttributeValues();\r
+            v = values.begin();\r
+            stop = values.end();\r
+            if (log.isDebugEnabled()) {\r
+                auto_ptr_char n(saml2attr->getName());\r
+                log.debug("decoding SimpleAttribute (%s) from SAML 2 Attribute (%s) with %lu value(s)", id, n.get() ? n.get() : "unnamed", values.size());\r
+            }\r
+        }\r
+        else {\r
+            const opensaml::saml1::Attribute* saml1attr = dynamic_cast<const opensaml::saml1::Attribute*>(xmlObject);\r
+            if (saml1attr) {\r
+                const vector<XMLObject*>& values = saml2attr->getAttributeValues();\r
+                v = values.begin();\r
+                stop = values.end();\r
+                if (log.isDebugEnabled()) {\r
+                    auto_ptr_char n(saml1attr->getAttributeName());\r
+                    log.debug("decoding SimpleAttribute (%s) from SAML 1 Attribute (%s) with %lu value(s)", id, n.get() ? n.get() : "unnamed", values.size());\r
+                }\r
+            }\r
+            else {\r
+                log.warn("XMLObject type not recognized by SimpleAttributeDecoder, no values returned");\r
+                return NULL;\r
+            }\r
+        }\r
+\r
+        for (; v!=stop; ++v) {\r
+            if (!(*v)->hasChildren()) {\r
+                val = toUTF8((*v)->getTextContent());\r
+                if (val && *val)\r
+                    dest.push_back(val);\r
+                else\r
+                    log.warn("skipping empty AttributeValue");\r
+                delete[] val;\r
+            }\r
+            else {\r
+                log.warn("skipping complex AttributeValue");\r
+            }\r
+        }\r
+\r
+        return dest.empty() ? NULL : simple.release();\r
+    }\r
+\r
+    const NameID* saml2name = dynamic_cast<const NameID*>(xmlObject);\r
+    if (saml2name) {\r
+        if (log.isDebugEnabled()) {\r
+            auto_ptr_char f(saml2name->getFormat());\r
+            log.debug("decoding SimpleAttribute (%s) from SAML 2 NameID with Format (%s)", id, f.get() ? f.get() : "unspecified");\r
+        }\r
+        val = toUTF8(saml2name->getName());\r
+    }\r
+    else {\r
+        const NameIdentifier* saml1name = dynamic_cast<const NameIdentifier*>(xmlObject);\r
+        if (saml1name) {\r
+            if (log.isDebugEnabled()) {\r
+                auto_ptr_char f(saml1name->getFormat());\r
+                log.debug("decoding SimpleAttribute (%s) from SAML 1 NameIdentifier with Format (%s)", id, f.get() ? f.get() : "unspecified");\r
+            }\r
+            val = toUTF8(saml1name->getName());\r
+        }\r
+        else {\r
+            log.warn("XMLObject type not recognized by SimpleAttributeDecoder, no values returned");\r
+            return NULL;\r
+        }\r
+    }\r
+\r
+    if (val && *val)\r
+        dest.push_back(val);\r
+    else\r
+        log.warn("ignoring empty NameID");\r
+    delete[] val;\r
+    return dest.empty() ? NULL : simple.release();\r
+}\r
index 426462b..54dbde6 100644 (file)
@@ -473,14 +473,14 @@ XMLApplication::XMLApplication(
             child = XMLHelper::getFirstChildElement(e,_MetadataProvider);\r
             if (child) {\r
                 auto_ptr_char type(child->getAttributeNS(NULL,_type));\r
-                log.info("building metadata provider of type %s...",type.get());\r
+                log.info("building MetadataProvider of type %s...",type.get());\r
                 try {\r
                     auto_ptr<MetadataProvider> mp(samlConf.MetadataProviderManager.newPlugin(type.get(),child));\r
                     mp->init();\r
                     m_metadata = mp.release();\r
                 }\r
                 catch (exception& ex) {\r
-                    log.crit("error building/initializing metadata provider: %s", ex.what());\r
+                    log.crit("error building/initializing MetadataProvider: %s", ex.what());\r
                 }\r
             }\r
         }\r
@@ -489,12 +489,12 @@ XMLApplication::XMLApplication(
             child = XMLHelper::getFirstChildElement(e,_TrustEngine);\r
             if (child) {\r
                 auto_ptr_char type(child->getAttributeNS(NULL,_type));\r
-                log.info("building trust engine of type %s...",type.get());\r
+                log.info("building TrustEngine of type %s...",type.get());\r
                 try {\r
                     m_trust = xmlConf.TrustEngineManager.newPlugin(type.get(),child);\r
                 }\r
                 catch (exception& ex) {\r
-                    log.crit("error building trust engine: %s",ex.what());\r
+                    log.crit("error building TrustEngine: %s",ex.what());\r
                 }\r
             }\r
         }\r
index d640797..72cf763 100644 (file)
                                        RelativePath=".\attribute\Attribute.cpp"\r
                                        >\r
                                </File>\r
+                               <File\r
+                                       RelativePath=".\attribute\ScopedAttributeDecoder.cpp"\r
+                                       >\r
+                               </File>\r
+                               <File\r
+                                       RelativePath=".\attribute\SimpleAttributeDecoder.cpp"\r
+                                       >\r
+                               </File>\r
                        </Filter>\r
                        <Filter\r
                                Name="binding"\r