const vector<string>& ExtensibleAttribute::getSerializedValues() const
{
    if (m_serialized.empty()) {
        const char* formatter = m_obj["_formatter"].string();
        if (formatter) {
            string msg = formatter;
            DDF val = m_obj.first().first();
            while (!val.isnull()) {

                static const char* legal="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890_.[]";

                m_serialized.push_back(string());
                string& processed = m_serialized.back();

                string::size_type i=0,start=0;
                while (start!=string::npos && start<msg.length() && (i=msg.find("$",start))!=string::npos) {
                    if (i>start)
                        processed += msg.substr(start,i-start); // append everything in between
                    start=i+1;                                  // move start to the beginning of the token name
                    i=msg.find_first_not_of(legal,start);       // find token delimiter
                    if (i==start) {                             // append a non legal character
                       processed+=msg[start++];
                       continue;
                    }
                    
                    string tag = msg.substr(start,(i==string::npos) ? i : i-start);
                    if (tag == "_string" && val.string()) {
                        processed += val.string();
                        start=i;
                    }
                    else {
                        DDF child = val.getmember(tag.c_str());
                        if (child.string())
                            processed += child.string();
                        else if (child.isstruct() && child["_string"].string())
                            processed += child["_string"].string();
                        start=i;
                    }
                }
                if (start!=string::npos && start<msg.length())
                    processed += msg.substr(start,i);    // append rest of string

                val = m_obj.first().next();
            }
        }
    }
    return Attribute::getSerializedValues();
}
void AbstractHandler::preservePostData(
    const Application& application, const HTTPRequest& request, HTTPResponse& response, const char* relayState
    ) const
{
#ifdef HAVE_STRCASECMP
    if (strcasecmp(request.getMethod(), "POST")) return;
#else
    if (stricmp(request.getMethod(), "POST")) return;
#endif

    // No specs mean no save.
    const PropertySet* props=application.getPropertySet("Sessions");
    pair<bool,const char*> mech = props->getString("postData");
    if (!mech.first) {
        m_log.info("postData property not supplied, form data will not be preserved across SSO");
        return;
    }

    DDF postData = getPostData(application, request);
    if (postData.isnull())
        return;

    if (strstr(mech.second,"ss:") == mech.second) {
        mech.second+=3;
        if (!*mech.second) {
            postData.destroy();
            throw ConfigurationException("Unsupported postData mechanism ($1).", params(1, mech.second - 3));
        }

        string postkey;
        if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
            DDFJanitor postjan(postData);
#ifndef SHIBSP_LITE
            StorageService* storage = application.getServiceProvider().getStorageService(mech.second);
            if (storage) {
                // Use a random key
                string rsKey;
                SAMLConfig::getConfig().generateRandomBytes(rsKey,20);
                rsKey = SAMLArtifact::toHex(rsKey);
                ostringstream out;
                out << postData;
                if (!storage->createString("PostData", rsKey.c_str(), out.str().c_str(), time(NULL) + 600))
                    throw IOException("Attempted to insert duplicate storage key.");
                postkey = string(mech.second-3) + ':' + rsKey;
            }
            else {
                m_log.error("storage-backed PostData mechanism with invalid StorageService ID (%s)", mech.second);
            }
#endif
        }
        else if (SPConfig::getConfig().isEnabled(SPConfig::InProcess)) {
            DDF out,in = DDF("set::PostData").structure();
            DDFJanitor jin(in),jout(out);
            in.addmember("id").string(mech.second);
            in.add(postData);
            out = application.getServiceProvider().getListenerService()->send(in);
            if (!out.isstring())
                throw IOException("StorageService-backed PostData mechanism did not return a state key.");
            postkey = string(mech.second-3) + ':' + out.string();
        }

        // Set a cookie with key info.
        pair<string,const char*> shib_cookie = getPostCookieNameProps(application, relayState);
        postkey += shib_cookie.second;
        response.setCookie(shib_cookie.first.c_str(), postkey.c_str());
    }
    else {
        postData.destroy();
        throw ConfigurationException("Unsupported postData mechanism ($1).", params(1,mech.second));
    }
}