<% /*
    $Revision$
    $Id$

    File:           api2_rest.inc
    Description:    Functies voor de http-REST interface op de modellen
    Notes:          Hier wordt van alles met de 'buitenwereld' gecommuniceerd.
                    Het manipuleren met behulp van modellen gebeurt in api2.inc
    Status:

*/
%>
<!-- #include file="api2.inc" -->
<!-- #include file="../scf/scaffolding_common.inc" -->
<%
var DEZE = this;
api2_rest = {
    authenticate: function _authenticate()
    {
        var APIKEY;
        if (S("fac_api_key_in_url"))
            APIKEY = getQParam("APIKEY", "");
        if (!APIKEY && Request.ServerVariables("HTTP_X_FACILITOR_API_KEY").Count)
            APIKEY = String(Request.ServerVariables("HTTP_X_FACILITOR_API_KEY")); // Meegegeven als X-FACILITOR-API-Key

        if (!APIKEY && Session("user_key") > 0)
        {
            user_key = Session("user_key"); // Hierdoor is de API intern te gebruiken zonder authenticatie
        }
        else
        {
            if (Session("user_key") > 0)
                {} // Tijdens ontwikkeling heb je soms in tweede tab de GUI open. Laat dat ongemoeid.
            else
                Session.Abandon(); // Altijd, voor de zekerheid

            var sql = "SELECT prs_perslid_key, prs_perslid_naam"
                    + "  FROM prs_perslid"
                    + " WHERE prs_perslid_verwijder IS NULL"
                    + "   AND prs_perslid_apikey = " + safe.quoted_sql(APIKEY);
            var oRs = Oracle.Execute(sql);
            if (oRs.Eof || !APIKEY)
            {
                Response.Status = "401 Unauthorized";
                // Sommige applicaties kunnen in reactie hierop een b64 encoded username:password sturen
                // Die onderscheppen wij in LoginTry.asp uiteindelijk
                if (S("basic_auth_realm"))
                    Response.AddHeader("WWW-Authenticate", "Basic realm=\"" + S("basic_auth_realm") + "\"");
                Response.End;
            };
            __Log("API2 User is: " + oRs("prs_perslid_naam").Value);
            /* global */ user_key = oRs("prs_perslid_key").Value;

            if (typeof NO_ADDHEADER == "undefined" && Request.Servervariables("HTTP_FCLT_VERSION").Count > 0)
            {   // wordt opgepikt door FCLTAPI.DLL voor in de logging en daarna gestript. Niet in Fiddler dus
                Response.AddHeader ("FCLT_USERID", customerId + "\\" + String(user_key));
            }

            oRs.Close();
        }

        // APP? Die kan meta-rechten hebben (bijvoorbeeld auth-token opvragen van gebruiker)
        var APPKEY;
        if (S("fac_api_key_in_url"))
            APPKEY = getQParam("HTTP_X_FACILITOR_APP_KEY", "");
        if (!APPKEY && Request.ServerVariables("HTTP_X_FACILITOR_APP_KEY").Count)
            APPKEY = String(Request.ServerVariables("HTTP_X_FACILITOR_APP_KEY")); // Meegegeven als X-FACILITOR-APP-Key
        if (APPKEY)
        {
           var sql = "SELECT prs_perslid_key, prs_perslid_naam"
                   + "  FROM prs_perslid"
                   + " WHERE prs_perslid_verwijder IS NULL"
                    + "   AND prs_perslid_apikey = " + safe.quoted_sql(APPKEY);
            var oRs = Oracle.Execute(sql);
            if (oRs.Eof)
            {
                __DoLog("Unauthorized app");
                Response.Status = "401 Unauthorized";
                Response.End;
            };
            __Log("APP User is: " + oRs("prs_perslid_naam").Value);
            /* global */ app_user_key = oRs("prs_perslid_key").Value;
            /* global */ app_user = new Perslid(app_user_key);
            oRs.Close()
        }

        /* global */ user = new Perslid(user_key); // wordt mogelijk nog overruled door imporsonate
        CheckForLogging(Request.QueryString("logging")); // Nu pas kan autorisatie via user gecontroleerd worden
    },

    impersonate: function _impersonate(model)
    {
        // Impersonate? (anno jan-2016 in de praktijk nergens gebruikt, kan mogelijk vervallen)
        if (!S("fac_api_allow_impersonate") || !model.impersonate_auth)
            return;

        var IMPERS;
        if (S("fac_api_key_in_url"))
            IMPERS = getQParam("SWITCHUSER", "");
        if (!IMPERS && Request.ServerVariables("HTTP_X_FACILITOR_SWITCH_USER").Count)
            IMPERS = String(Request.ServerVariables("HTTP_X_FACILITOR_SWITCH_USER")); // Meegegeven als X-FACILITOR-SWITCH-USER
        if (!IMPERS)
            return;

        var sql = "SELECT prs_perslid_key, prs_perslid_naam"
               + "  FROM prs_perslid"
               + " WHERE prs_perslid_verwijder IS NULL"
                + "   AND prs_perslid_oslogin = " + safe.quoted_sql_upper(IMPERS);
        var oRs = Oracle.Execute(sql);
        if (oRs.Eof)
        {
            Response.Status = "412 Invalid X-Facilitor-Switch-User header";
            Response.End;
        };
        __Log("IMPERS User is: " + oRs("prs_perslid_naam").Value);
        var other_user_key = oRs("prs_perslid_key").Value;
        oRs.Close();

        var xfunc = user.func_enabled2(model.module, { prs_key: other_user_key, isOptional: true });
        var can = (xfunc && xfunc.canRead(model.impersonate_auth));
        if (can)
        {
            /* global */ user_key = other_user_key;
            /* global */ user = new Perslid(user_key);
        }
        else
        {
            Response.Status = "412 Unauthorized X-Facilitor-Switch-User header";
            Response.End;
        }
    },
    process: function _process(model)
    {
        var wasCodePage = Session.Codepage;
        Session.Codepage = 65001; // We doen *uitsluitend* utf-8
        Response.Charset = 'utf-8';
        var format = getQParamSafe("format", "invalid").toLowerCase();
        if (format == "json")
            /* global */ JSON_Result = true; // Zelf doen we er niets mee maar
                                             // shared.simple_page kijkt er naar

        api2_rest.authenticate();
        // Kip-ei: de omzetting naar new model() mag pas als je geauthenticeerd bent
        // Hierboven willen we het echter al wel meegeven
        if (typeof model == "function") // Nieuwe stijl is het een function. Even compatible.
            model = new model();

        api2_rest.impersonate(model);

        var method = String(Request.ServerVariables("REQUEST_METHOD"));

        if (!/GET|PUT|POST|DELETE/.test(method)) // Overigens houdt IIS deze al eerder tegen
        {
            Response.Status = "405 Method not allowed";
            Response.End;
        }
        if (!("REST_" + method in model))
        {
            Response.Status = "501 Not Implemented";
            // TODO The response MUST include an Allow header containing a list of valid methods for the requested resource.
            Response.End;
        }
        var jsondata = {};
        var filter = shared.qs2json(model);
        filter = api2_rest.plugin.transform_filter(filter);
        var requestparams = { filter: filter, include: getQParamArray("include", []) };

        if (/PUT|POST/.test(method)) // Dan is er in de body data meegestuurd
        {
            switch (format)
            {
                case "json":
                {
                    var parsed = RequestJSON();
                    if (parsed.error)
                        api2.error(500, "Error parsing input JSON: " + parsed.error);
                    jsondata = api2_rest.plugin.transform_incoming(requestparams, parsed.json);
                    if (!jsondata)
                        api2.error(500, "Error parsing input JSON: Empty");
                    break;
                }
                case "xml":
                {
                    var parsed = RequestXML();
                    if (parsed.error)
                        api2.error(500, "Error parsing input XML: " + parsed.error);
                    jsondata = api2_rest.xml2json(parsed.xml);
                    if (!jsondata)
                        api2.error(500, "Error parsing input XML: Empty");
                    break;
                }
                default:
                    UNKNOWN_FORMAT_TYPE;
            }
            if (!jsondata || !(model.record_name in jsondata || (model.multi_update && model.records_name in jsondata)))
            {
                api2.error(500,  "No '{0}' found in input".format(model.record_name));
            }
        }

        var key = getQParamInt("id", -1); // Voor POST/PUT/DELETE
        var isSingle = /PUT|POST|DELETE/.test(method) || (key > 0);  // PUT, POST en DELETE altijd single

        if (format == "doc")
        {
            // Dan hoeven we verder niets te doen
        }
        else if (format == "api")
        {
            // TODO: Onderstaande in een of ander standaardformaat opleveren?
            var result = { id: model.records_name,
                           methods: [],
                           includes: [],
                           fields: []
                         };
            for (var i in model.includes)
                result.includes.push(i);
            if ("REST_GET" in model)
                result.methods.push("GET");
            if ("REST_PUT" in model)
                result.methods.push("PUT");
            if ("REST_POST" in model)
                result.methods.push("POST");
            if ("REST_DELETE" in model)
                result.methods.push("DELETE");

            for (var fld in model.fields)
            {                           // TODO: We missen hard-coded filters als reservableequipment/allowedinroom nu nog
                if (!model.fields.hidden)
                    result.fields.push({ id: fld,
                                          filter: model.fields[fld].filter,
                                          type: model.fields[fld].typ,
                                          label: model.fields[fld].label
                                        });
            }
        }
        else
        {
            if (method == "DELETE")
            {
                var result = model["REST_" + method]( requestparams, key );
            }
            else if (method == "GET" && filter.mode == "attachment" && "custom_fields" in model.includes)
            {
                if (wasCodePage != 65001)
                {
                    // Door de IIS rewriter is de filenaam in de url utf-8 encoded
                    // Zet dat hier terug om naar Windows-1252
                    var fileStream = new ActiveXObject("ADODB.Stream");
                    fileStream.Open();
                    fileStream.Type = 2; // adTypeText
                    fileStream.Charset = 'Windows-1252';
                    fileStream.WriteText(filter.filename);
                    fileStream.Position = 0;
                    fileStream.Charset = 'utf-8';
                    filter.filename = fileStream.ReadText();
                    fileStream.Close();
                }

                requestparams.filter.id = key; // Die kan er maar beter wel zijn!
                requestparams.include = ["custom_fields"];
                var data = model.REST_GET(requestparams);
                if (!data.length) // mogelijk not authorized op hele record
                    Response.Status = "404 Not Found";
                else
                    model.includes["custom_fields"].model.streamattachment(filter, data[0].custom_fields);
                Response.End;
            }
            else if (method == "GET")
            {
                var result = model["REST_" + method]( requestparams, null, key );
            }
            else if (model.record_name in jsondata) // een enkel record
            {
                if (jsondata[model.record_name] instanceof Array)
                    abort_with_warning("API2 error: {0} should be single record only.".format(method));
                var result = model["REST_" + method]( requestparams, jsondata[model.record_name], key );
            }
            else
            {   // Loop door de multiple records en geef de REST_ functie altijd één record
                for (var record in jsondata[model.records_name])
                {
                    var thisdata = jsondata[model.records_name][record];
                    var result = model["REST_" + method]( requestparams, thisdata, thisdata.id );
                }
            }
        }

        switch (method)
        {
            case "DELETE":
            {
                Response.Status = "204 No Content";
                Response.End;
                break;
            }
            case "GET":
            {
                data = result;
                break;
            }
            case "PUT":
            case "POST":
            {
                var key = result.key;
                if (key > 0)
                {
                    var params = { filter: shared.qs2json(model), include: getQParamArray("include", []) }, jsondata, key
                    // requestparams.include is mogelijk uitgebreid met wat er in de body stond
                    data = model.REST_GET({ filter: { id: key }, include: requestparams.include }); // resulterende data weer terug
                    __Log(data);
                }
                else
                {
                    data = [];
                    isSingle = false;
                }
            }
        }

        api2_rest.deliver(data, model, format, isSingle);

    },
    // Data is een array met 'records'
    deliver: function _deliver(data, model, format, single )
    {
        if (single && !data.length)
        {
            Response.Status = "404 Not Found";
            Response.End;
        }

        if (format == "html" || format == "json")
        {
            var result = { };
            if (single)
                result[model.record_name] = data[0];
            else
            {
                result.total_count = model.total_count;
                result.limit = model.limit;
                result.offset = 0;
                result[model.records_name] = data;
            }

            var resultdata = api2_rest.plugin.transform_outgoing({}, result);
        }
        switch (format)
        {
            case "api":
            {
                //var xml_antwoord = api2_rest.json2xml(data, model, single);
                var str_antwoord = JSON.stringify(data, null, getQParam("pretty","0")=="1"?2:0);
                Response.ContentType = "application/json";
                break;
            }
            case "doc":
            {
                if (model.fields) // Ga de hints er bij zoeken
                {
                    var safefieldnames = [];
                    var lcl2fld = {};
                    for (var fld in model.fields)
                    {
                        var lclname = "{0}.{1}.hint".format(model.records_name, fld);
                        lcl2fld[lclname] = fld;
                        safefieldnames.push(safe.quoted_sql(lclname));
                    }
                    //model.fields[fld].hint = "Hallo";
                    var sql = "SELECT fac_locale_xsl_label, "
                            + "       COALESCE(fac_locale_xsl_cust, fac_locale_xsl_tekst) fac_locale_xsl_tekst"
                            + "  FROM fac_locale_xsl xsl"
                            + " WHERE fac_locale_xsl_lang = " + safe.quoted_sql(user_lang)
                            + "   AND fac_locale_xsl_module = 'ASP'"
                            + "   AND fac_locale_xsl_label IN (" + safefieldnames.join(", ") + ")";
                    var oRs = Oracle.Execute(sql);
                    while (!oRs.Eof)
                    {
                        model.fields(lcl2fld[oRs("fac_locale_xsl_label").Value]) = oRs("fac_locale_xsl_tekst").value;
                        oRs.MoveNext();
                    }
                }
                Response.ContentType = "text/html";
                var str_antwoord = simple_json2xml(model, "api");
                var xml_antwoord = new ActiveXObject("MSXML2.DOMDocument.6.0");
                xml_antwoord.loadXML(str_antwoord);
                if (xml_antwoord.parseError.errorCode)
                {
                  abort_with_warning("XSL error: " + xml_antwoord.parseError.reason + " @ " + xml_antwoord.parseError.line + "." + xml_antwoord.parseError.linepos  + "\n"+  xml_antwoord.parseError.srcText);
                }

                var style = new ActiveXObject("MSXML2.DOMDocument.6.0");
                style.async = false;
                style.resolveExternals = false;
                if (Request.QueryString("debug").Count == 0)
                {
                    var xslname = model.xslname || "reference.xsl";
                    style.load(Server.MapPath(rooturl + "/appl/api2/" + xslname)); // De stylesheet laden. API's redeneren vanuit de root
                    var str_antwoord = xml_antwoord.transformNode(style); // terugstoppen in antwoord
                }
                else
                {
                    if (Application("otap_environment") != "O")
                        ONLY_ON_OTAP_O;

                    style.load(Server.MapPath(rooturl + "/appl/shared/indent.xsl")); // De stylesheet laden. API's redeneren vanuit de root
                    var str_antwoord = "<pre>" + Server.HTMLEncode(xml_antwoord.transformNode(style)) + "</pre>";
                }
                if (style.parseError.errorCode)
                {
                  abort_with_warning("XSL error: " + style.parseError.reason + " @ " + style.parseError.line + "." + style.parseError.linepos );
                }
                break;
            }
            case "json":
                var str_antwoord = JSON.stringify(resultdata, null, getQParam("pretty","0")=="1"?2:0);
                var jsonp = getQParam("jsonp", getQParam("callback",""));
                if (jsonp)
                {
                    str_antwoord = jsonp + "(" + str_antwoord + ")";
                    Response.ContentType = "application/javascript";
                }
                else
                    Response.ContentType = "application/json";
                break;
            case "html":
                Response.ContentType = "text/html";
                var antwoord = JSON.stringify(resultdata, null, 2);
                var str_antwoord = "<!DOCTYPE html><html><head></head><body><pre>"
                                 +      Server.HTMLEncode(antwoord)
                                 + "</pre></body></html>";
                break;
            case "xml":
                Response.ContentType = "text/xml";
                var xml_antwoord = api2_rest.json2xml(data, model, single);
                // TODO: Output XSL transform ondersteunen?
                var xsl = getQParamSafe("xsl", "");
                var xslfile;
                if (xsl)
                {
                    var fso = new ActiveXObject("Scripting.FileSystemObject");
                    xslfile = Server.MapPath(custpath + "/xsl/" + xsl + ".xsl");
                    if (!fso.FileExists(xslfile))
                      abort_with_warning("Stylesheet '{0}' not found".format(xsl));
                }
                else if (getQParam("pretty","0")=="1")
                    xslfile = Server.MapPath(rooturl + "/appl/shared/indent.xsl");
                if (xslfile)
                {
                    var style = new ActiveXObject("MSXML2.DOMDocument.6.0");
                    style.async = false;
                    style.resolveExternals = false;
                    style.load(xslfile);
                    if (style.parseError.errorCode)
                    {
                      abort_with_warning("XSL error: " + style.parseError.reason + " @ " + style.parseError.line + "." + style.parseError.linepos );
                    }
                    var str_antwoord = xml_antwoord.transformNode(style); // terugstoppen in antwoord
                }
                else
                    var str_antwoord = xml_antwoord.xml;
                //Response.ContentType = "application/json";
                //var str_antwoord = JSON.stringify(api2_rest.xml2json(xml_antwoord), null, 2);;
                break;
            default:
                WRONG_FORMAT;
        }

        // str_antwoord heeft nu het te versturen antwoord
        // Bepaal eTag
        var oCrypto = new ActiveXObject("SLNKDWF.Crypto");
        var eTag = '"' + oCrypto.hex_sha1(String(S("cache_changecounter")) + "_" + str_antwoord).toLowerCase() + '"';
        Response.AddHeader("ETag", eTag);
        if (Request.ServerVariables("HTTP_IF_NONE_MATCH") == eTag)
        {   // We hebben een match! Effectief besparen wel alleen op dataverkeer, de queries zijn al geweest
            Response.Clear();
            Response.Status = "304 Not modified";
            Response.End;
        }

        Response.write(str_antwoord);
    },

    // TODO: Wanneer attributes gebruiken en wanneer (sub)-elements?
    // Streven: data == xml2json(json2xml(data))
    json2xml: function _json2xml(data, model, single)
    {
        var rootname = model.records_name;
        var record_name = model.record_name;
        var xmlDoc = new ActiveXObject("MSXML2.DOMDocument.6.0");
        xmlDoc.appendChild(xmlDoc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"utf-8\""));

        var record2json = function(record, record_name)
        {
            var elementRecord = xmlDoc.createElement(record_name);
            for (var fld in record)
            {
                var elementField = xmlDoc.createElement(fld);
                if (record[fld] instanceof Date)
                {
                    var elementFieldText = xmlDoc.createTextNode(String(record[fld].toJSON()));
                    elementField.appendChild(elementFieldText);
                }
                else if (record[fld] instanceof Array)
                {
                    var inc_record_name;
                    for (var ii in model.includes)
                    {
//                        if (model.includes[ii].model.records_name == fld)
//                            inc_record_name = model.includes[ii].model.record_name;
                        if (ii == fld)
                            inc_record_name = model.includes[ii].model.record_name;
                    }
                    // Fallback voor attachments array --> "attachment"
                    if (!inc_record_name)
                        inc_record_name = fld.substr(0, fld.length-1);

                    for (var i = 0; i < record[fld].length; i++)
                        elementField.appendChild(record2json(record[fld][i], inc_record_name));
                }
                else if (record[fld] && typeof record[fld] == "object") // Veronderstelt dat dit foreign met name/key is
                {
                    if ("name" in record[fld] && "id" in record[fld])
                    {
                        elementField.setAttribute("name", record[fld].name===null?"":record[fld].name);
                        elementField.setAttribute("id", record[fld].id);
                    }
                    else
                        elementField = record2json(record[fld], fld);
                }
                else
                {
                    var elementFieldText = xmlDoc.createTextNode(String(record[fld]||""));
                    elementField.appendChild(elementFieldText);
                }
                elementRecord.appendChild(elementField);
            }
            return elementRecord;
        };

        if (single)
        {
            xmlDoc.appendChild(record2json(data[0], record_name));
        }
        else
        {
            var arrayElement = xmlDoc.createElement(rootname);
            for (var i = 0; i < data.length; i++)
                arrayElement.appendChild(record2json(data[i], record_name));
            xmlDoc.appendChild(arrayElement);
        }
        return xmlDoc;
    },
    // Streven: data == json2xml(xml2json(xml))
    // http://davidwalsh.name/convert-xml-json maar @attributes er uit gehaald
    xml2json: function _xml2json(xml)
    {
        // Create the return object
        var obj = {};

        if (xml.nodeType == 1)
        { // element
          // do attributes
          if (xml.attributes.length > 0)
          {
            // JGL removed: obj["@attributes"] = {};
            for (var j = 0; j < xml.attributes.length; j++)
            {
              var attribute = xml.attributes.item(j);
              obj[attribute.nodeName] = attribute.nodeValue;
            }
          }
        }
        else if (xml.nodeType == 3)
        { // text
          obj = xml.nodeValue;
        }

        // do children
        if (xml.hasChildNodes())
        {
          for(var i = 0; i < xml.childNodes.length; i++)
          {
            var item = xml.childNodes.item(i);
            var nodeName = item.nodeName;
            if (typeof(obj[nodeName]) == "undefined")
            {
              // JGL Added: Only one Textnode is simplified. Autodetect data
              if (item.nodeType == 3 && xml.childNodes.length == 1)
              {
                  var dt = myJSON.internal_parsedate(null, item.nodeValue);
                  if (dt && dt instanceof Date)
                      return dt;
                  return item.nodeValue;
              }

              obj[nodeName] = api2_rest.xml2json(item);
            }
            else
            {
              if (typeof(obj[nodeName].push) == "undefined")
              {
                var old = obj[nodeName];
                obj[nodeName] = [];
                obj[nodeName].push(old);
              }
              obj[nodeName].push(api2_rest.xml2json(item));
            }
          }
        }
        return obj;
    },
    find_plugin: function()
    {
        var plugin_name = getQParamSafe("plugin", "").toLowerCase();
        if (!plugin_name)
            return {};
        var fso = new ActiveXObject("Scripting.FileSystemObject");
        var paths = ["/cust/" + customerId, "/cust", "/appl/api2"]; // Hieronder zoeken naar '/plugins' folder
        for (var p in paths)
        {
            var ppath = Server.MapPath(rooturl + paths[p] + "/plugins/" + plugin_name + ".wsc")
            //__Log(ppath);
            if (fso.FileExists(ppath))
            {
                try
                {
                    var hook = GetObject("script:" + ppath);
                }
                catch(e)
                {
                    api2.error(500, "Loading {0} failed: {1}".format(ppath, e.description));
                }
				// Via DEZE kan de aanroeper eigenlijk alle globale functies benaderen
                // zoals __Log
                hook.initialize({ S: S, Oracle: Oracle, customerId: customerId, safe: safe, DEZE: DEZE });
                return hook;
            }
        }
        api2.error(500, "Undefined plugin {0}".format(plugin_name));
    },
    plugin: {
        transform_filter: function(filter)
        {
            var outdata = filter;
            var hook = api2_rest.find_plugin();
            if ("transform_filter" in hook)
            {
                outdata = hook.transform_filter(filter);
            }
            hook = null;
            return outdata;
        },
        transform_incoming: function(params, data)
        {
            var outdata = data;
            var hook = api2_rest.find_plugin();
            if ("transform_incoming" in hook)
            {
                outdata = hook.transform_incoming(params, data);
            }
            hook = null;
            return outdata;
        },
        transform_outgoing: function(params, data)
        {
            var outdata = data;
            var hook = api2_rest.find_plugin();
            if ("transform_outgoing" in hook)
                outdata = hook.transform_outgoing(params, data);
            hook = null;
            return outdata;
        }
    }
}

// LET OP: Verwacht wordt dat de JSON-code in de body utf-8 encoded is, niet windows-1252!
// (in de praktijk moet je *moeite* doen om windows-1252 te krijgen dus dit is handiger)
function RequestJSON()
{
    var jvraag;
    if(Request.TotalBytes > 0)
    {
        var lngBytesCount = Request.TotalBytes;
        jvraag = BytesToStr(Request.BinaryRead(lngBytesCount));
    }
    __Log("Vraag: " + jvraag);
    try
    {
        var vraag = myJSON.parse(jvraag);
    }
    catch (e)
    {
        __DoLog("JSON eval faalt met: {0}<br>{1}".format(e.description, jvraag), "ffff00");
        return { error: e.description };
    }
    return { json: vraag };
}

function RequestXML()
{
    try
    {
        var inputXML = Server.CreateObject("MSXML2.DOMDocument.6.0");
        inputXML.load(Request);
    }
    catch (e)
    {
        return { error: e.description };
    }
    if (inputXML.parseError.errorCode)
    {
        return { error: inputXML.parseError.reason + " @ " + inputXML.parseError.line + "." + inputXML.parseError.linepos };
    }
    return { xml: inputXML };
}

function BytesToStr(bytes)
{
    var stream = Server.CreateObject("ADODB.Stream");
    stream.type = 1;
    stream.open;
    stream.write(bytes);
    stream.position = 0;
    stream.type = 2; // Text
    stream.charset = "utf-8";

    var sOut = stream.readtext();
    stream.close;
    return sOut;
}

function getQParamISODate(pName, defVal)
{
    return _get_ParamISODate(Request.Querystring, pName, defVal)
}
function getFParamISODate(pName, defVal)
{
    return _get_ParamISODate(Request.Form, pName, defVal)
}
function _get_ParamISODate(pColl, pName, defVal)
{
    var strval = _get_Param(pColl, pName, defVal, true); // force: een lege waarde wordt als afwezig beschouwd
    if (strval)
    {
        var dt = myJSON.internal_parsedate(null, strval);
        if (dt && dt instanceof Date)
            return dt;
    }

    if (defVal instanceof Date)
    {
        return defVal;
    }
    if (defVal === null) // bewust triple===
    {
        return null;
    }
    // Error message will get to client and/or IIS logfiles
    eval("INTERNAL_ERROR_PARAMETER_" + pName + "_IS_NOT_ISODATE");
}

var simple_json2xml = (function (undefined) {
    "use strict";
    var tag = function (name, closing) {
        return (closing ? "</" : "\n<") + name + ">";
    };
    return function (obj, rootname) {
        var xml = "";
        for (var i in obj) {
            if (obj.hasOwnProperty(i)) {
                var value = obj[i],
                    type = typeof value;
                if (value instanceof Array && type == 'object') {
                    for (var sub in value) {
                        xml += simple_json2xml(value[sub]);
                    }
                } else if (value instanceof Object && type == 'object') {
                    xml += tag(i) + simple_json2xml(value) + tag(i, 1);
                } else {
                    xml += tag(i) + (value===null?"":Server.HTMLEncode(value)) + tag(i, 1);
                }
            }
        }

        return rootname ? tag(rootname) + xml + tag(rootname, 1) : xml;
    };
})(simple_json2xml || {});
%>