@montevideo-tech/cmcd-validator
Version:
This is a library created to validate CMCD
2 lines (1 loc) • 17 kB
JavaScript
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).CMCDValidator={})}(this,(function(e){"use strict";let t;const n=new Uint8Array(16);function r(){if(!t&&(t="undefined"!=typeof crypto&&crypto.getRandomValues&&crypto.getRandomValues.bind(crypto),!t))throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");return t(n)}const o=[];for(let e=0;e<256;++e)o.push((e+256).toString(16).slice(1));var i={randomUUID:"undefined"!=typeof crypto&&crypto.randomUUID&&crypto.randomUUID.bind(crypto)};function s(e,t,n){if(i.randomUUID&&!t&&!e)return i.randomUUID();const s=(e=e||{}).random||(e.rng||r)();if(s[6]=15&s[6]|64,s[8]=63&s[8]|128,t){n=n||0;for(let e=0;e<16;++e)t[n+e]=s[e];return t}return function(e,t=0){return(o[e[t+0]]+o[e[t+1]]+o[e[t+2]]+o[e[t+3]]+"-"+o[e[t+4]]+o[e[t+5]]+"-"+o[e[t+6]]+o[e[t+7]]+"-"+o[e[t+8]]+o[e[t+9]]+"-"+o[e[t+10]]+o[e[t+11]]+o[e[t+12]]+o[e[t+13]]+o[e[t+14]]+o[e[t+15]]).toLowerCase()}(s)}var a="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{};var u={exports:{}};!function(e){!function(t){var n,r={};r.VERSION="1.6.1";var o={},i=function(e,t){return function(){return t.apply(e,arguments)}},s=function(){var e,t,n=arguments,r=n[0];for(t=1;t<n.length;t++)for(e in n[t])!(e in r)&&n[t].hasOwnProperty(e)&&(r[e]=n[t][e]);return r},a=function(e,t){return{value:e,name:t}};r.TRACE=a(1,"TRACE"),r.DEBUG=a(2,"DEBUG"),r.INFO=a(3,"INFO"),r.TIME=a(4,"TIME"),r.WARN=a(5,"WARN"),r.ERROR=a(8,"ERROR"),r.OFF=a(99,"OFF");var u=function(e){this.context=e,this.setLevel(e.filterLevel),this.log=this.info};u.prototype={setLevel:function(e){e&&"value"in e&&(this.context.filterLevel=e)},getLevel:function(){return this.context.filterLevel},enabledFor:function(e){var t=this.context.filterLevel;return e.value>=t.value},trace:function(){this.invoke(r.TRACE,arguments)},debug:function(){this.invoke(r.DEBUG,arguments)},info:function(){this.invoke(r.INFO,arguments)},warn:function(){this.invoke(r.WARN,arguments)},error:function(){this.invoke(r.ERROR,arguments)},time:function(e){"string"==typeof e&&e.length>0&&this.invoke(r.TIME,[e,"start"])},timeEnd:function(e){"string"==typeof e&&e.length>0&&this.invoke(r.TIME,[e,"end"])},invoke:function(e,t){n&&this.enabledFor(e)&&n(t,s({level:e},this.context))}};var l,c=new u({filterLevel:r.OFF});(l=r).enabledFor=i(c,c.enabledFor),l.trace=i(c,c.trace),l.debug=i(c,c.debug),l.time=i(c,c.time),l.timeEnd=i(c,c.timeEnd),l.info=i(c,c.info),l.warn=i(c,c.warn),l.error=i(c,c.error),l.log=l.info,r.setHandler=function(e){n=e},r.setLevel=function(e){for(var t in c.setLevel(e),o)o.hasOwnProperty(t)&&o[t].setLevel(e)},r.getLevel=function(){return c.getLevel()},r.get=function(e){return o[e]||(o[e]=new u(s({name:e},c.context)))},r.createDefaultHandler=function(e){(e=e||{}).formatter=e.formatter||function(e,t){t.name&&e.unshift("["+t.name+"]")};var t={},n=function(e,t){Function.prototype.apply.call(e,console,t)};return"undefined"==typeof console?function(){}:function(o,i){o=Array.prototype.slice.call(o);var s,a=console.log;i.level===r.TIME?(s=(i.name?"["+i.name+"] ":"")+o[0],"start"===o[1]?console.time?console.time(s):t[s]=(new Date).getTime():console.timeEnd?console.timeEnd(s):n(a,[s+": "+((new Date).getTime()-t[s])+"ms"])):(i.level===r.WARN&&console.warn?a=console.warn:i.level===r.ERROR&&console.error?a=console.error:i.level===r.INFO&&console.info?a=console.info:i.level===r.DEBUG&&console.debug?a=console.debug:i.level===r.TRACE&&console.trace&&(a=console.trace),e.formatter(o,i),n(a,o))}},r.useDefaults=function(e){r.setLevel(e&&e.defaultLevel||r.DEBUG),r.setHandler(r.createDefaultHandler(e))},r.setDefaults=r.useDefaults,e.exports?e.exports=r:(r._prevLogger=t.Logger,r.noConflict=function(){return t.Logger=r._prevLogger,r},t.Logger=r)}(a)}(u);var l,c=(l=u.exports)&&l.__esModule&&Object.prototype.hasOwnProperty.call(l,"default")?l.default:l;const d={number:"number",boolean:"boolean",token:"token",string:"string"},f={br:d.number,bl:d.number,bs:d.boolean,cid:d.string,d:d.number,dl:d.number,mtp:d.number,nor:d.string,nrr:d.string,ot:d.token,pr:d.number,rtp:d.number,sf:d.token,sid:d.string,st:d.token,su:d.boolean,tb:d.number,v:d.number},p={noCMCDRequest:"no-CMCD-request",unknownKey:"unknown-key",invalidValue:"invalid-value",wrongTypeValue:"wrong-type-value",incorrectFormat:"incorrect-format",parameterEncoding:"parameter-encoding",missingKey:"missing-key",invalidHeader:"invalid-header",invalidJson:"invalid-json",duplicateKey:"duplicate-key",duplicatedHeader:"duplicated-header",noHeader:"no-header",emptyHeader:"empty-header",unnecessaryKey:"unnecessary-key",wrongCustomType:"wrong-custom-type",invalidCustomKey:"invalid-custom-key",unknownSpecificKey:"unknown-specific-key",wrongCustomHeader:"wrong-custom-header",queryMalformed:"query-malformed",noAmpersandBetweenRequests:"no-ampersand-between-requests:"},y={noCMCDRequest:"No CMCD request found",unknownKey:"Key is not part of reserved keys",invalidValue:"Value does not meet requirements",wrongTypeValue:"Value type is incorrect",incorrectFormat:"Format is incorrect",parameterEncoding:"Parameter is not encoded",missingKey:"Key is missing",invalidHeader:"Header is not valid",invalidJson:"Json format is not valid",duplicateKey:"Key/Keys are not unique",duplicatedHeader:"Header is duplicated",noHeader:"There is no CMCD headers.",emptyHeader:"There is a header with no content",unnecessaryKey:"This key must not be sent with the current value",wrongCustomType:"The type does not mach with CMCD types",invalidCustomKey:"Custom key names MUST carry a hyphenated prefix",unknownSpecificKey:"The key does not mach with CMCD keys",wrongCustomHeader:"The header does not mach with CMCD headers",queryMalformed:"The query is malformed",noAmpersandBetweenRequests:"Ampersand required between two or more requests"},h={"CMCD-Object":["br","d","ot","tb"],"CMCD-Request":["bl","dl","mtp","nor","nrr","su"],"CMCD-Session":["cid","pr","sf","sid","st","v"],"CMCD-Status":["bs","rtp"]},m={noAlphabeticalOrder:"no-alphabetical-order",valuePr:"pr-value",valueV:"v-value",noSidReceived:"no-sid-received",blWithWrongOtValue:"bl-with-wrong-ot-value",noReverseDnsCustomKey:"customkey-without-reversedns",specificKeysNotSent:"specific-keys-not-sent"},v={noAlphabeticalOrder:"Keys are not arranged alphabetically",valuePr:"Should only be sent if not equal to 1.00",valueV:"Client should omit this field if the version is 1",noSidReceived:"No sid received from CMCD message",blWithWrongOtValue:"bl key should only be sent with an object type of a, v or av",noReverseDnsCustomKey:"Customkey without Reverse DNS, could be: com.example-customkeyname",specificKeysNotSent:"A specific/custom key was set but not sent"},b=(e,t)=>Object.keys(e).find((n=>e[n]===t)),g=(e,t,n,r,o)=>{if(c.useDefaults({defaultLevel:c.TRACE}),!Object.values(p).includes(e))return-1;const i=b(p,e);return void 0===o?(c.info(`${t}: Error in '${n}': ${y[i]}`),{type:e,key:n,value:r,description:y[i]}):(c.info(`${t}: Error in '${n}': ${o}`),{type:e,key:n,value:r,description:o})},k=(e,t,n,r,o)=>{let i=!0;const s=e.split(","),a=[];s.forEach((e=>{const o=e.split(":")[0].replace(/{|"/g,"");if(!r?.specificKey||r.specificKey?.includes(o)){if(a.includes(o)){const e=`The key ${o} is repeated.`;t.push(g(p.duplicateKey,n,o,void 0,e)),i=!1}a.push(o)}}));try{const s=JSON.parse(e);Object.keys(s).forEach((e=>{r?.specificKey&&!r.specificKey?.includes(e)||((e,t,n,r,o)=>{if(r[e]===d.boolean&&typeof t!==d.boolean){const r=`The value for the key ${e} must be a boolean.`;return n.push(g(p.wrongTypeValue,o,e,t,r)),!1}return!0})(e,s[e],t,o,n)&&((e,t,n,r,o)=>{if(r[e]===d.number&&typeof t!==d.number){const r=`The value for the key ${e} must be a number`;return n.push(g(p.wrongTypeValue,o,e,t,r)),!1}return!0})(e,s[e],t,o,n)&&((e,t,n,r,o)=>{if(!(r[e]!==d.string&&r[e]!==d.token||typeof t!==d.boolean&&typeof t!==d.number)){const i=`The value for the key ${e} must be a ${r[e]}.`;return n.push(g(p.wrongTypeValue,o,e,t,i)),!1}return!0})(e,s[e],t,o,n)||(i=!1)}))}catch(e){t.push(g(p.invalidJson,n)),i=!1}return i},C=e=>0===e.indexOf('"')&&e.lastIndexOf('"')===e.length-1,w=(e,t,n,r,o)=>{if(r[e]===d.string&&!C(t)||r[e]===d.token&&C(t)){const r=`Incorrect format for key: ${e}`;return n.push(g(p.incorrectFormat,o,e,t,r)),!1}return!0},$=(e,t,n,r,o)=>{if(r[e]===d.boolean&&"true"===t){const r="If the value is TRUE, the = and the value must be omitted";return n.push(g(p.incorrectFormat,o,e,t,r)),!1}if((typeof t===d.number||typeof t===d.string&&"false"!==t)&&r[e]===d.boolean){const r=`The value for the key ${e} must be a boolean.`;return n.push(g(p.wrongTypeValue,o,e,t,r)),!1}return!0},T=(e,t,n,r,o)=>{if(r[e]===d.number&&Number.isNaN(parseInt(t,10))){const r=`The value for the key ${e} must be a number`;return n.push(g(p.incorrectFormat,o,e,t,r)),!1}return!0},D=(e,t,n,r)=>{if(e.split("=").length>2||1===e.split("=").length&&n[e]!==d.boolean){const n="key-value pair not separated by =.";return t.push(g(p.incorrectFormat,r,e,void 0,n)),!1}return!0},R=(e,t,n,r)=>{if(t.includes(e)){const t=`The key ${e} is repeated.`;return n.push(g(p.duplicateKey,r,e,void 0,t)),!0}return!1},E=(e,t,n,r,o,i)=>{if(!((e,t,n)=>!!e.includes("CMCD=")||(t.push(g(p.noCMCDRequest,n)),!1))(e,t,n)||((e,t,n)=>{let r=!1;try{decodeURI(e)===e&&(t.push(g(p.parameterEncoding,n)),r=!0)}catch(e){t.push(g(p.queryMalformed,n)),r=!0}return r})(e,t,n))return!1;const s=e.split("?").pop(),a=decodeURIComponent(s).split("CMCD=");if(!((e,t,n)=>!(e[0].length>0&&"&"!==e[0][e[0].length-1]&&(t.push(g(p.noAmpersandBetweenRequests,n)),1)))(a,t,n))return!1;if(a.shift(),((e,t,n)=>e.length>1&&(t.push(g(p.incorrectFormat,n)),!0))(a,t,n))return!1;const u=decodeURIComponent(s).split("CMCD=")[1].split("&")[0].split(","),l=[];let c=!0;return u.forEach((e=>{if(D(e,t,i,n)){const[r,s]=e.split("=");if(o?.specificKey&&!o.specificKey?.includes(r))return;R(r,l,t,n)&&(c=!1),w(r,s,t,i,n)&&$(r,s,t,i,n)&&T(r,s,t,i,n)||(c=!1),l.push(r)}else c=!1})),c},K=(e,t,n,r)=>{if(c.useDefaults({defaultLevel:c.TRACE}),!Object.values(m).includes(e))return-1;const o=b(m,e);return c.info(`${t}: Warning in '${n}': ${v[o]}`),{type:e,key:n,value:r,description:v[o]}},O=(e,t,n)=>{JSON.stringify(e)!==JSON.stringify(e.sort())&&t.push(K(m.noAlphabeticalOrder,n))},M=(e,t,n,r,o,i,s,a=!0)=>{const u=e.split("\n"),l=[],c=[];let d=[],f=!0;return u.forEach((e=>{const[u,y]=e.split(": ");return u in i&&(((e,t,n,r)=>{if(t.includes(e)){const t=`This header ${e} is repeated.`;return n.push(g(p.duplicatedHeader,r,e,t)),!0}return!1})(u,l,t,n)||((e,t,n,r)=>{if(""===e){const e=`Empty header detected! Header: ${t}`;return n.push(g(p.emptyHeader,r,t,e)),!0}return!1})(y,u,t,n)?(f=!1,!1):(y.split(",").forEach((e=>{if(D(e,t,s,n)){const[r,a]=e.split("=");if(o?.specificKey&&!o.specificKey?.includes(r))return;!R(r,c,t,n)&&((e,t,n,r,o)=>{if(!r[e].includes(t)){const r=`The key ${t} does not go under the header ${e}`;return n.push(g(p.incorrectFormat,o,t,r)),!1}return!0})(u,r,t,i,n)&&w(r,a,t,s,n)&&$(r,a,t,s,n)&&T(r,a,t,s,n)||(f=!1),c.push(r),d.push(r)}else f=!1})),!0===a&&(O(d,r,n),d=[]),void l.push(u)))})),!!((e,t,n)=>{if(0===e.length){const e="No headers detected!";return t.push(g(p.noCMCDRequest,n,e)),!1}return!0})(l,t,n)&&f},V=(e,t,n,r)=>{if(n.length>64){const o=`Invalid value for key ${t}. Maximum length is 64 characters.`;e.push(g(p.invalidValue,r,t,n,o))}},N=(e,t,n,r,o)=>{if(!r.includes(n)){const i=`${t} value does not meet the necessary requirements. Must be one of the following values: ${r}.`;e.push(g(p.invalidValue,o,t,n,i))}},I=(e,t,n,r,o,i)=>{if(n%r!=0){const s=`${t} value is not rounded to the nearest ${r}${o}.`;e.push(g(p.invalidValue,i,t,n,s))}},A=(e,t,n,r,o)=>{if(n===r){const i=`The ${t} key should not be sent if the value is ${r}`;e.push(g(p.unnecessaryKey,o,t,n,i))}},L=(e,t,n,r,o,i,s)=>{!0===s&&((e,t,n)=>{"sid"in e||t.push(K(m.noSidReceived,n))})(e,r,n);const a=Object.keys(e);o?.specificKey&&s&&o.specificKey.forEach((e=>{a.includes(e)||r.push(K(m.specificKeysNotSent,n,e))})),o?.customKey&&s&&o.customKey.forEach((e=>{a.includes(e.key)||r.push(K(m.specificKeysNotSent,n,e.key))})),Object.keys(e).forEach((a=>{if(o?.specificKey&&!o.specificKey?.includes(a))return;const u=e[a];switch(((e,t,n,r,o)=>{if(!(n in o)){const o=`The key ${n} is not reserved.`;e.push(g(p.unknownKey,t,n,r,o))}})(t,n,a,u,i),((e,t,n,r)=>{if(typeof n!==f[t]&&f[t]===d.token&&typeof n!==d.string){const o=`${t} type is incorrect.`;e.push(g(p.wrongTypeValue,r,t,n,o))}})(t,a,u,n),((e,t,n,r)=>{if(f[t]===d.number&&n<0){const o=`The ${t} value must be greater than 0.`;e.push(g(p.invalidValue,r,t,n,o))}})(t,a,u,n),a){case"bl":I(t,a,u,100,"ms",n),!0===s&&((e,t,n,r,o)=>{"ot"in e&&["a","v","av"].includes(e.ot)||t.push(K(m.blWithWrongOtValue,o,n,r))})(e,r,a,u,n);break;case"cid":case"sid":V(t,a,u,n);break;case"dl":I(t,a,u,100,"ms",n);break;case"mtp":A(t,a,u,0,n),I(t,a,u,100,"kbps",n);break;case"nor":((e,t,n,r)=>{if(decodeURIComponent(n)===n&&encodeURIComponent(n)!==n){const o=`The key: ${t} must be URLencoded.`;return e.push(g(p.parameterEncoding,r,t,n,o)),!1}return!0})(t,a,u,n)&&((e,t,n,r)=>{if(/^(http:\/\/|https:\/\/)/.test(decodeURIComponent(n))){const o=`The key: ${t} must be a relative path.`;return e.push(g(p.incorrectFormat,r,t,n,o)),!1}})(t,a,u,n);break;case"nrr":((e,t,n,r)=>{if(!/^(\d*-\d*)$/.test(n)&&!/^\d*-$/.test(n)&&!/^-\d*$/.test(n)){const o="Invalid Nrr format";e.push(g(p.incorrectFormat,r,t,n,o))}})(t,a,u,n);break;case"ot":((e,t,n,r)=>{N(e,t,n,["m","a","v","av","i","c","tt","k","o"],r)})(t,a,u,n);break;case"rtp":I(t,a,u,100,"kbps",n);break;case"sf":((e,t,n,r)=>{N(e,t,n,["d","h","s","o"],r)})(t,a,u,n);break;case"st":((e,t,n,r)=>{N(e,t,n,["v","l"],r)})(t,a,u,n);break;case"su":case"bs":A(t,a,u,!1,n);break;case"pr":s&&((e,t,n,r,o)=>{"pr"===n&&1===r&&t.push(K(m.valuePr,o,n,r))})(0,r,a,u,n);break;case"v":s&&((e,t,n,r,o)=>{"v"===n&&1===e.v&&t.push(K(m.valueV,o,n,r))})(e,r,a,u,n)}}))},F=(e,t,n,r,o=!0)=>{if(!e)return[!0,f,h];const{customKey:i,specificKey:s}=e;let a=!1;const u={...f},l={...h};if(i){const e=Object.values(d);i.forEach((i=>{/^[a-zA-Z0-9.]+-[a-zA-Z0-9]+$/.test(i.key)||(t.push(g(p.invalidCustomKey,n,i.key)),a=!0),e.includes(i.type)||(t.push(g(p.wrongCustomType,n,i.key,i.type)),a=!0),/^([a-zA-Z0-9]+\.[a-zA-Z0-9]+)+$/.test(i.key.split("-")[0])||!0!==o||r.push(K(m.noReverseDnsCustomKey,n)),i.headerType&&!(i.headerType in h)&&(t.push(g(p.invalidHeader,n,i.key,i.type)),a=!0),a||(u[i.key]=i.type,i.headerType&&l[i.headerType].push(i.key))}))}if(s){const e=Object.keys(u);s.forEach((r=>{e.includes(r)||t.push(g(p.unknownSpecificKey,n,r))}))}return 0===t.length?[!0,u,l]:[!1,u,l]},U=(e,t,n,r)=>({valid:0===e.length,errors:e,warnings:t,parsedData:r,rawData:n});e.CMCDHeaderValidator=(e,t,n=!0)=>{const r=[],o=e,i=[],a=s();c.useDefaults({defaultLevel:c.TRACE}),c.info(`${a}: Started CMCD Header Validation.`);const[u,l,d]=F(t,r,a,i,n);if(!u)return U(r,i,o);c.info(`${a}: Validating header format.`);if(!M(e,r,a,i,t,d,l,n))return c.info(`${a}: Header not valid.`),U(r,i,o);c.info(`${a}: Header is valid.`),c.info(`${a}: Parsing header.`);const f=((e,t,n)=>{const r=e.split("\n"),o={};return r.forEach((e=>{let[r,i]=e.split(":");t[r]&&(i=i.replace(/ /g,""),i.split(",").forEach((e=>{if(e.includes("=")){let[t,r]=e.split("=");r=Number.isNaN(Number(r))?r.replace(/"/g,""):Number(r),"boolean"===n[t]&&"false"===r&&(r=!1),o[t]=r}else o[e]=!0})))})),o})(e,d,l);return L(f,r,a,i,t,l,n),U(r,i,o,f)},e.CMCDJsonValidator=(e,t,n=!0)=>{const r=[],o=e,i=[],a=s();c.useDefaults({defaultLevel:c.TRACE}),c.info(`${a}: Started CMCD Json Validation.`);const[u,l]=F(t,r,a,i,n);if(!u)return c.info(`${a}: Configuration is not valid.`),U(r,i,o);c.info(`${a}: Configuration es valid.`),c.info(`${a}: Validating Json format.`);if(!k(e,r,a,t,l))return c.info(`${a}: Json not valid.`),U(r,i,o);c.info(`${a}: Json is valid.`);const d=JSON.parse(e);return!0===n&&O(Object.keys(d),i,a),c.info(`${a}: Validating Json keys.`),L(d,r,a,i,t,l,n),U(r,i,o,d)},e.CMCDQueryValidator=(e,t,n=!0)=>{const r=[],o=e,i=[],a=s();c.useDefaults({defaultLevel:c.TRACE}),c.info(`${a}: Started CMCD Query Validation.`);const[u,l]=F(t,r,a,i,n);if(c.info(`${a}: Check Configuration.`),!u)return c.info(`${a}: Configuration not valid.`),U(r,i,o);c.info(`${a}: Validating query format.`);if(!E(e,r,a,0,t,l))return c.info(`${a}: Query not valid.`),U(r,i,o);c.info(`${a}: Query is valid.`),c.info(`${a}: Parsing query.`);const d=((e,t)=>{const n=decodeURIComponent(e).split("CMCD=")[1].split("&")[0].split(","),r={};return n.forEach((e=>{if(e.includes("=")){let[n,o]=e.split("=");o=Number.isNaN(Number(o))?o.replace(/"/g,""):Number(o),"boolean"===t[n]&&"false"===o&&(o=!1),r[n]=o}else r[e]=!0})),r})(e,l);return!0===n&&O(Object.keys(d),i),c.info(`${a}: Validating query keys.`),L(d,r,a,i,t,l,n),U(r,i,o,d)},Object.defineProperty(e,"__esModule",{value:!0})}));