✨ Add patch manifest verifier
This commit is contained in:
parent
f01a880ae6
commit
77e039901a
2 changed files with 163 additions and 1 deletions
|
@ -2,6 +2,7 @@ import { TextDecoder } from 'util';
|
||||||
import * as xmlJs from 'xml-js';
|
import * as xmlJs from 'xml-js';
|
||||||
import getUrlContents from '../cdn/getUrlContents';
|
import getUrlContents from '../cdn/getUrlContents';
|
||||||
import { Product } from '../interfaces/ISettings';
|
import { Product } from '../interfaces/ISettings';
|
||||||
|
import verifyPatchmanifest from '../ssn/verifyPatchmanifest';
|
||||||
import verifyProductName from '../ssn/verifyProductName';
|
import verifyProductName from '../ssn/verifyProductName';
|
||||||
import extractFile from './extractFile';
|
import extractFile from './extractFile';
|
||||||
import readSsnFile from './readSsnFile';
|
import readSsnFile from './readSsnFile';
|
||||||
|
@ -33,7 +34,8 @@ export default async function getPatchmanifest(product: Product): Promise<any> {
|
||||||
const patchmanifestFile = await extractFile(firstFile, [new DataView(ssnFile)]);
|
const patchmanifestFile = await extractFile(firstFile, [new DataView(ssnFile)]);
|
||||||
const patchmanifestXml = Decoder.decode(patchmanifestFile);
|
const patchmanifestXml = Decoder.decode(patchmanifestFile);
|
||||||
|
|
||||||
const patchManifestJson = xmlJs.xml2js(patchmanifestXml);
|
const patchManifestJson = xmlJs.xml2js(patchmanifestXml) as xmlJs.Element;
|
||||||
|
verifyPatchmanifest(patchManifestJson, product);
|
||||||
|
|
||||||
return patchManifestJson;
|
return patchManifestJson;
|
||||||
}
|
}
|
||||||
|
|
160
src/ssn/verifyPatchmanifest.ts
Normal file
160
src/ssn/verifyPatchmanifest.ts
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
import * as xmlJs from 'xml-js';
|
||||||
|
import { Product } from '../interfaces/ISettings';
|
||||||
|
|
||||||
|
/** Receives a JSON-converted version of the manifest.xml file */
|
||||||
|
export default function verifyPatchmanifest(obj: xmlJs.Element, product: Product): any {
|
||||||
|
//<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
if (obj.declaration === undefined || obj.declaration.attributes === undefined || Object.keys(obj.declaration.attributes).length !== 2 || obj.declaration.attributes.version !== '1.0' || obj.declaration.attributes.encoding !== 'utf-8') {
|
||||||
|
throw new Error('Expected declration with version 1.0 and utf-8 encoding.');
|
||||||
|
}
|
||||||
|
|
||||||
|
//<PatchManifest xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
||||||
|
if (obj.elements === undefined || obj.elements.length !== 1) {
|
||||||
|
throw new Error('Expected one root element.');
|
||||||
|
}
|
||||||
|
const root = obj.elements[0];
|
||||||
|
if (root.type !== 'element' || root.name !== 'PatchManifest') {
|
||||||
|
throw new Error(`Expected root element to be called PatchManifest but it was "${root.name}".`);
|
||||||
|
}
|
||||||
|
if (root.attributes === undefined || Object.keys(root.attributes).length !== 2 || root.attributes['xmlns:xsi'] !== 'http://www.w3.org/2001/XMLSchema-instance' || root.attributes['xmlns:xsd'] !== 'http://www.w3.org/2001/XMLSchema') {
|
||||||
|
throw new Error(`Expected root element to have attributes xmlns:xsi and xmlns:xsd.`);
|
||||||
|
}
|
||||||
|
if (root.elements === undefined) {
|
||||||
|
throw new Error(`Expected child elements under PatchManifest but there were none.`);
|
||||||
|
}
|
||||||
|
if (root.elements.length !== 9) {
|
||||||
|
throw new Error(`Expected 9 child elements under PatchManifest but there were ${root.elements.length}.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
//<Dependencies />
|
||||||
|
const Dependencies = root.elements[0];
|
||||||
|
if (Dependencies.type !== 'element' || Dependencies.name !== 'Dependencies' || Dependencies.attributes !== undefined || Dependencies.elements !== undefined) {
|
||||||
|
throw new Error('Expected Dependencies element with no child elements and no attributes.');
|
||||||
|
}
|
||||||
|
|
||||||
|
//<Name>assets_swtor_de_de</Name>
|
||||||
|
const Name = root.elements[1];
|
||||||
|
if (Name.type !== 'element' || Name.name !== 'Name' || Name.attributes !== undefined || Name.elements === undefined || Name.elements.length !== 1 || Name.elements[0].type !== 'text' || Name.elements[0].text !== product) {
|
||||||
|
throw new Error('Expected Name element with one child element equal to product and no attributes.');
|
||||||
|
}
|
||||||
|
|
||||||
|
//<RequiredRelease>289</RequiredRelease>
|
||||||
|
const RequiredRelease = root.elements[2];
|
||||||
|
if (RequiredRelease.type !== 'element' || RequiredRelease.name !== 'RequiredRelease' || RequiredRelease.attributes !== undefined || RequiredRelease.elements === undefined) {
|
||||||
|
throw new Error('Expected RequiredRelease element.');
|
||||||
|
}
|
||||||
|
if (RequiredRelease.elements.length !== 1 || RequiredRelease.elements[0].type !== 'text' || typeof RequiredRelease.elements[0].text !== 'string' || String(RequiredRelease.elements[0].text).match(/^(0|[1-9][0-9]*)$/) === null) {
|
||||||
|
throw new Error('Expected integer in RequiredRelease element.');
|
||||||
|
}
|
||||||
|
|
||||||
|
//<UpcomingRelease>-1</UpcomingRelease>
|
||||||
|
const UpcomingRelease = root.elements[3];
|
||||||
|
if (UpcomingRelease.type !== 'element' || UpcomingRelease.name !== 'UpcomingRelease' || UpcomingRelease.attributes !== undefined || UpcomingRelease.elements === undefined) {
|
||||||
|
throw new Error('Expected UpcomingRelease element.');
|
||||||
|
}
|
||||||
|
if (UpcomingRelease.elements.length !== 1 || UpcomingRelease.elements[0].type !== 'text' || UpcomingRelease.elements[0].text !== '-1') {
|
||||||
|
throw new Error('Expected -1 in UpcomingRelease element.');
|
||||||
|
}
|
||||||
|
|
||||||
|
//<TargetDirectory>{ModulePath}</TargetDirectory>
|
||||||
|
const TargetDirectory = root.elements[4];
|
||||||
|
if (TargetDirectory.type !== 'element' || TargetDirectory.name !== 'TargetDirectory' || TargetDirectory.attributes !== undefined || TargetDirectory.elements === undefined) {
|
||||||
|
throw new Error('Expected TargetDirectory element.');
|
||||||
|
}
|
||||||
|
if (TargetDirectory.elements.length !== 1 || TargetDirectory.elements[0].type !== 'text' || TargetDirectory.elements[0].text !== '{ModulePath}') {
|
||||||
|
throw new Error('Expected {ModulePath} in TargetDirectory element.');
|
||||||
|
}
|
||||||
|
|
||||||
|
//<RequiresElevation>false</RequiresElevation>
|
||||||
|
const RequiresElevation = root.elements[5];
|
||||||
|
if (RequiresElevation.type !== 'element' || RequiresElevation.name !== 'RequiresElevation' || RequiresElevation.attributes !== undefined || RequiresElevation.elements === undefined) {
|
||||||
|
throw new Error('Expected RequiresElevation element.');
|
||||||
|
}
|
||||||
|
if (RequiresElevation.elements.length !== 1 || RequiresElevation.elements[0].type !== 'text' || RequiresElevation.elements[0].text !== 'false') {
|
||||||
|
throw new Error('Expected false in RequiresElevation element.');
|
||||||
|
}
|
||||||
|
|
||||||
|
//<Maintenance>false</Maintenance>
|
||||||
|
const Maintenance = root.elements[6];
|
||||||
|
if (Maintenance.type !== 'element' || Maintenance.name !== 'Maintenance' || Maintenance.attributes !== undefined || Maintenance.elements === undefined) {
|
||||||
|
throw new Error('Expected Maintenance element.');
|
||||||
|
}
|
||||||
|
if (Maintenance.elements.length !== 1 || Maintenance.elements[0].type !== 'text' || Maintenance.elements[0].text !== 'false') {
|
||||||
|
throw new Error('Expected false in Maintenance element.');
|
||||||
|
}
|
||||||
|
|
||||||
|
//<Releases>
|
||||||
|
const Releases = root.elements[7];
|
||||||
|
if (Releases.type !== 'element' || Releases.name !== 'Releases' || Releases.attributes !== undefined || Releases.elements === undefined) {
|
||||||
|
throw new Error('Expected Releases element.');
|
||||||
|
}
|
||||||
|
for (let i = 0, il = Releases.elements.length; i < il; i += 1) {
|
||||||
|
const Release = Releases.elements[i];
|
||||||
|
//<Release>
|
||||||
|
if (Release.type !== 'element' || Release.name !== 'Release' || Release.attributes !== undefined || Release.elements === undefined || Release.elements.length !== 3) {
|
||||||
|
throw new Error('Expected Release element.');
|
||||||
|
}
|
||||||
|
//<Id>0</Id>
|
||||||
|
const Id = Release.elements[0];
|
||||||
|
if (Id.type !== 'element' || Id.name !== 'Id' || Id.attributes !== undefined || Id.elements === undefined || Id.elements.length !== 1 || Id.elements[0].type !== 'text' || typeof Id.elements[0].text !== 'string' || String(Id.elements[0].text).match(/^(0|[1-9][0-9]*)$/) === null) {
|
||||||
|
throw new Error('Expected Id element.');
|
||||||
|
}
|
||||||
|
//<ReleaseName>53678f8057e52896a8145dca5c188ab3f24fa55f</SHA1>
|
||||||
|
const Sha1 = Release.elements[1];
|
||||||
|
if (Sha1.type !== 'element' || Sha1.name !== 'SHA1' || Sha1.attributes !== undefined || Sha1.elements === undefined || Sha1.elements.length !== 1 || Sha1.elements[0].type !== 'text' || typeof Sha1.elements[0].text !== 'string' || String(Sha1.elements[0].text).match(/^[0-9a-z]{40}$/) === null) {
|
||||||
|
throw new Error('Expected SHA1 element.');
|
||||||
|
}
|
||||||
|
//<Name>assets_swtor_de_de_0</Name>
|
||||||
|
const ReleaseName = Release.elements[2];
|
||||||
|
if (ReleaseName.type !== 'element' || ReleaseName.name !== 'Name' || ReleaseName.attributes !== undefined || ReleaseName.elements === undefined || ReleaseName.elements.length !== 1 || ReleaseName.elements[0].type !== 'text' || ReleaseName.elements[0].text !== `${product}_${Id.elements[0].text}`) {
|
||||||
|
throw new Error('Expected Release->Name element.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//<ReleaseUpdatePaths>
|
||||||
|
const ReleaseUpdatePaths = root.elements[8];
|
||||||
|
if (ReleaseUpdatePaths.type !== 'element' || ReleaseUpdatePaths.name !== 'ReleaseUpdatePaths' || ReleaseUpdatePaths.attributes !== undefined || ReleaseUpdatePaths.elements === undefined) {
|
||||||
|
throw new Error('Expected ReleaseUpdatePaths element.');
|
||||||
|
}
|
||||||
|
for (let i = 0, il = ReleaseUpdatePaths.elements.length; i < il; i += 1) {
|
||||||
|
const ReleaseUpdatePath = ReleaseUpdatePaths.elements[i];
|
||||||
|
//<ReleaseUpdatePath>
|
||||||
|
if (ReleaseUpdatePath.type !== 'element' || ReleaseUpdatePath.name !== 'ReleaseUpdatePath' || ReleaseUpdatePath.attributes !== undefined || ReleaseUpdatePath.elements === undefined || ReleaseUpdatePath.elements.length !== 3) {
|
||||||
|
throw new Error('Expected ReleaseUpdatePath element.');
|
||||||
|
}
|
||||||
|
//<From>289</From>
|
||||||
|
const From = ReleaseUpdatePath.elements[0];
|
||||||
|
if (From.type !== 'element' || From.name !== 'From' || From.attributes !== undefined || From.elements === undefined || From.elements.length !== 1 || From.elements[0].type !== 'text' || typeof From.elements[0].text !== 'string' || String(From.elements[0].text).match(/^(-1|0|[1-9][0-9]*)$/) === null) {
|
||||||
|
throw new Error('Expected From element.');
|
||||||
|
}
|
||||||
|
//<To>285</To>
|
||||||
|
const To = ReleaseUpdatePath.elements[1];
|
||||||
|
if (To.type !== 'element' || To.name !== 'To' || To.attributes !== undefined || To.elements === undefined || To.elements.length !== 1 || To.elements[0].type !== 'text' || typeof To.elements[0].text !== 'string' || String(To.elements[0].text).match(/^(0|[1-9][0-9]*)$/) === null) {
|
||||||
|
throw new Error('Expected To element.');
|
||||||
|
}
|
||||||
|
//TODO: check if From and To are valid relations
|
||||||
|
//<ExtraDataItem>
|
||||||
|
const ExtraData = ReleaseUpdatePath.elements[2];
|
||||||
|
if (ExtraData.type !== 'element' || ExtraData.name !== 'ExtraData' || ExtraData.attributes !== undefined || ExtraData.elements === undefined) {
|
||||||
|
throw new Error('Expected ExtraData element.');
|
||||||
|
}
|
||||||
|
for (let j = 0, jl = ExtraData.elements.length; j < jl; j += 1) {
|
||||||
|
//<ExtraDataItem>
|
||||||
|
const ExtraDataItem = ExtraData.elements[j];
|
||||||
|
if (ExtraDataItem.type !== 'element' || ExtraDataItem.name !== 'ExtraDataItem' || ExtraDataItem.attributes !== undefined || ExtraDataItem.elements === undefined || ExtraDataItem.elements.length !== 2) {
|
||||||
|
throw new Error('Expected ExtraDataItem element.');
|
||||||
|
}
|
||||||
|
//<Key>MetafileUrl</Key>
|
||||||
|
const Key = ExtraDataItem.elements[0];
|
||||||
|
if (Key.type !== 'element' || Key.name !== 'Key' || Key.attributes !== undefined || Key.elements === undefined || Key.elements.length !== 1 || Key.elements[0].type !== 'text') {
|
||||||
|
throw new Error('Expected Key element.');
|
||||||
|
}
|
||||||
|
//<Value>http://cdn-patch.swtor.com/patch/assets_swtor_de_de/assets_swtor_de_de_289to285.solidpkg</Value>
|
||||||
|
const Value = ExtraDataItem.elements[1];
|
||||||
|
if (Value.type !== 'element' || Value.name !== 'Value' || Value.attributes !== undefined || Value.elements === undefined || Value.elements.length !== 1 || Value.elements[0].type !== 'text') {
|
||||||
|
throw new Error('Expected Value element.');
|
||||||
|
}
|
||||||
|
//TODO: parse Key and Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue