whopper 0.5.9 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/browser/index.d.ts.map +1 -1
  2. package/dist/browser/index.js +56 -3
  3. package/dist/browser/index.js.map +1 -1
  4. package/dist/browser/index.test.js +133 -0
  5. package/dist/browser/index.test.js.map +1 -1
  6. package/dist/browser/types.d.ts +1 -0
  7. package/dist/browser/types.d.ts.map +1 -1
  8. package/dist/commands/detect.d.ts.map +1 -1
  9. package/dist/commands/detect.js +2 -0
  10. package/dist/commands/detect.js.map +1 -1
  11. package/dist/commands/detect.test.js +5 -0
  12. package/dist/commands/detect.test.js.map +1 -1
  13. package/dist/commands/detect_utils.d.ts.map +1 -1
  14. package/dist/commands/detect_utils.js +23 -5
  15. package/dist/commands/detect_utils.js.map +1 -1
  16. package/dist/commands/detect_utils.test.js +86 -9
  17. package/dist/commands/detect_utils.test.js.map +1 -1
  18. package/dist/signatures/_types.d.ts +1 -0
  19. package/dist/signatures/_types.d.ts.map +1 -1
  20. package/dist/signatures/index.d.ts.map +1 -1
  21. package/dist/signatures/index.js +2 -0
  22. package/dist/signatures/index.js.map +1 -1
  23. package/dist/signatures/signatures.test.js +16 -0
  24. package/dist/signatures/signatures.test.js.map +1 -1
  25. package/dist/signatures/technologies/drupal.d.ts.map +1 -1
  26. package/dist/signatures/technologies/drupal.js +17 -0
  27. package/dist/signatures/technologies/drupal.js.map +1 -1
  28. package/dist/signatures/technologies/drupal.test.d.ts +2 -0
  29. package/dist/signatures/technologies/drupal.test.d.ts.map +1 -0
  30. package/dist/signatures/technologies/drupal.test.js +76 -0
  31. package/dist/signatures/technologies/drupal.test.js.map +1 -0
  32. package/dist/signatures/technologies/power_cms.d.ts +3 -0
  33. package/dist/signatures/technologies/power_cms.d.ts.map +1 -0
  34. package/dist/signatures/technologies/power_cms.js +51 -0
  35. package/dist/signatures/technologies/power_cms.js.map +1 -0
  36. package/dist/signatures/technologies/power_cms.test.d.ts +2 -0
  37. package/dist/signatures/technologies/power_cms.test.d.ts.map +1 -0
  38. package/dist/signatures/technologies/power_cms.test.js +83 -0
  39. package/dist/signatures/technologies/power_cms.test.js.map +1 -0
  40. package/package.json +1 -1
@@ -0,0 +1,51 @@
1
+ import { movableTypeSignature } from "./movable_type.js";
2
+ import { perlSignature } from "./perl.js";
3
+ // PowerCMS is an Alfasado CMS built on top of Movable Type, so it shares the
4
+ // mt.cgi admin script and mt-static asset path with MT. Detection keys off the
5
+ // PowerCMS-specific plugin static path to tell it apart from plain MT, and the
6
+ // active mt.cgi admin probe captures the version from the admin footer when
7
+ // available (best-effort; admin pages are not always reachable).
8
+ const adminPageRegexes = [
9
+ // Admin footer version text, e.g. "PowerCMS version 5.43" / "PowerCMS 4.46".
10
+ // Version-bearing patterns come first so the version is captured when present.
11
+ // PowerCMS version numbers have irregular precision (2.0493 / 4.46 / 5.43),
12
+ // so accept an arbitrary number of dot-separated groups.
13
+ "PowerCMS version ([0-9]+(?:\\.[0-9]+)*)", // v5 footer
14
+ "PowerCMS ([0-9]+(?:\\.[0-9]+)*)", // v4 footer
15
+ // v6/v7 dropped the product-name prefix and show just "version 6.83". This
16
+ // bare fallback is intentionally last so v4/v5 still match the prefixed forms
17
+ // above (first-hit-wins), limiting its blast radius to v6/v7 pages. Requiring
18
+ // at least one dot avoids picking up single-token "version 3" style noise, and
19
+ // the leading \b prevents matching words that merely end in "version"
20
+ // (e.g. "Subversion 1.14.2").
21
+ "\\bversion ([0-9]+\\.[0-9]+(?:\\.[0-9]+)*)", // v6/v7 footer
22
+ "/mt-static/plugins/PowerCMS/", // PowerCMS-specific marker, confirms even without a version
23
+ ];
24
+ const activeRules = [
25
+ "/mt.cgi?__mode=logout", // root install (common)
26
+ "/mt/mt.cgi?__mode=logout",
27
+ "/cgi-bin/mt/mt.cgi?__mode=logout",
28
+ "/blog/mt.cgi?__mode=logout",
29
+ ].map((path) => ({ path, bodyRegexes: adminPageRegexes }));
30
+ // Detects PowerCMS from the PowerCMS-specific plugin static path on public
31
+ // pages, then confirms/extracts the version via the mt.cgi admin probe. When
32
+ // PowerCMS is detected, Movable Type is excluded from the output to avoid the
33
+ // underlying MT being reported alongside the actual product.
34
+ export const powerCmsSignature = {
35
+ name: "PowerCMS",
36
+ description: "PowerCMS is a Movable Type-based content management system by Alfasado Inc.",
37
+ cpe: "cpe:/a:alfasado:powercms", // CPE 2.2
38
+ rule: {
39
+ confidence: "high",
40
+ urls: [
41
+ "/mt-static/plugins/PowerCMS/", // distinguishes PowerCMS from plain Movable Type
42
+ ],
43
+ bodies: [
44
+ "/mt-static/plugins/PowerCMS/", // same path referenced inline in public HTML
45
+ ],
46
+ },
47
+ activeRules,
48
+ excludes: [movableTypeSignature.name], // PowerCMS supersedes the underlying Movable Type
49
+ impliedSoftwares: [perlSignature.name], // PowerCMS is a Perl application (like MT)
50
+ };
51
+ //# sourceMappingURL=power_cms.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"power_cms.js","sourceRoot":"","sources":["../../../src/signatures/technologies/power_cms.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE1C,6EAA6E;AAC7E,+EAA+E;AAC/E,+EAA+E;AAC/E,4EAA4E;AAC5E,iEAAiE;AACjE,MAAM,gBAAgB,GAAG;IACvB,6EAA6E;IAC7E,+EAA+E;IAC/E,4EAA4E;IAC5E,yDAAyD;IACzD,yCAAyC,EAAE,YAAY;IACvD,iCAAiC,EAAE,YAAY;IAC/C,2EAA2E;IAC3E,8EAA8E;IAC9E,8EAA8E;IAC9E,+EAA+E;IAC/E,sEAAsE;IACtE,8BAA8B;IAC9B,4CAA4C,EAAE,eAAe;IAC7D,8BAA8B,EAAE,4DAA4D;CAC7F,CAAC;AAEF,MAAM,WAAW,GAAiB;IAChC,uBAAuB,EAAE,wBAAwB;IACjD,0BAA0B;IAC1B,kCAAkC;IAClC,4BAA4B;CAC7B,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,gBAAgB,EAAE,CAAC,CAAC,CAAC;AAE3D,2EAA2E;AAC3E,6EAA6E;AAC7E,8EAA8E;AAC9E,6DAA6D;AAC7D,MAAM,CAAC,MAAM,iBAAiB,GAAc;IAC1C,IAAI,EAAE,UAAU;IAChB,WAAW,EACT,6EAA6E;IAC/E,GAAG,EAAE,0BAA0B,EAAE,UAAU;IAC3C,IAAI,EAAE;QACJ,UAAU,EAAE,MAAM;QAClB,IAAI,EAAE;YACJ,8BAA8B,EAAE,iDAAiD;SAClF;QACD,MAAM,EAAE;YACN,8BAA8B,EAAE,6CAA6C;SAC9E;KACF;IACD,WAAW;IACX,QAAQ,EAAE,CAAC,oBAAoB,CAAC,IAAI,CAAC,EAAE,kDAAkD;IACzF,gBAAgB,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,2CAA2C;CACpF,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=power_cms.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"power_cms.test.d.ts","sourceRoot":"","sources":["../../../src/signatures/technologies/power_cms.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,83 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { matchString } from "../../analyzer/match.js";
3
+ import { powerCmsSignature } from "./power_cms.js";
4
+ import { movableTypeSignature } from "./movable_type.js";
5
+ describe("powerCmsSignature", () => {
6
+ describe("rule (passive, public-facing marker)", () => {
7
+ const urls = powerCmsSignature.rule?.urls ?? [];
8
+ const bodies = powerCmsSignature.rule?.bodies ?? [];
9
+ it("matches the PowerCMS plugin static path in URLs", () => {
10
+ const url = "https://example.com/mt-static/plugins/PowerCMS/css/style.css";
11
+ expect(urls.some((r) => matchString(url, r).hit)).toBe(true);
12
+ });
13
+ it("matches the PowerCMS plugin static path inline in HTML", () => {
14
+ const html = '<link rel="stylesheet" href="/mt-static/plugins/PowerCMS/x.css">';
15
+ expect(bodies.some((r) => matchString(html, r).hit)).toBe(true);
16
+ });
17
+ it("does not match a plain Movable Type asset path", () => {
18
+ const url = "https://example.com/mt-static/themes-base/blog.css";
19
+ expect(urls.some((r) => matchString(url, r).hit)).toBe(false);
20
+ });
21
+ });
22
+ describe("activeRules (mt.cgi admin probe)", () => {
23
+ const rules = powerCmsSignature.activeRules ?? [];
24
+ it("probes the root and common install dirs with logout mode", () => {
25
+ expect(rules.map((r) => r.path)).toEqual([
26
+ "/mt.cgi?__mode=logout",
27
+ "/mt/mt.cgi?__mode=logout",
28
+ "/cgi-bin/mt/mt.cgi?__mode=logout",
29
+ "/blog/mt.cgi?__mode=logout",
30
+ ]);
31
+ });
32
+ it("extracts the version from the 'PowerCMS version X.Y' footer", () => {
33
+ const regex = rules[0].bodyRegexes.find((r) => r.includes("PowerCMS version"));
34
+ const result = matchString("<p>PowerCMS version 5.43</p>", regex);
35
+ expect(result.hit).toBe(true);
36
+ expect(result.version).toBe("5.43");
37
+ });
38
+ it("extracts the version from the 'PowerCMS X.Y' footer", () => {
39
+ const result = rules[0].bodyRegexes
40
+ .map((r) => matchString("Powered by PowerCMS 4.46", r))
41
+ .find((r) => r.hit);
42
+ expect(result.hit).toBe(true);
43
+ expect(result.version).toBe("4.46");
44
+ });
45
+ it("extracts the version from the v6/v7 'version X.Y' footer (no product prefix)", () => {
46
+ const result = rules[0].bodyRegexes
47
+ .map((r) => matchString("<p>version 6.83</p>", r))
48
+ .find((r) => r.hit);
49
+ expect(result.hit).toBe(true);
50
+ expect(result.version).toBe("6.83");
51
+ });
52
+ it("does not pick up a single-token 'version 3' as a version", () => {
53
+ const bare = rules[0].bodyRegexes.find((r) => r === "\\bversion ([0-9]+\\.[0-9]+(?:\\.[0-9]+)*)");
54
+ expect(matchString("requires version 3", bare).hit).toBe(false);
55
+ });
56
+ it("does not match a word that merely ends in 'version' (e.g. Subversion)", () => {
57
+ const bare = rules[0].bodyRegexes.find((r) => r === "\\bversion ([0-9]+\\.[0-9]+(?:\\.[0-9]+)*)");
58
+ expect(matchString("Subversion 1.14.2", bare).hit).toBe(false);
59
+ });
60
+ it("extracts irregular-precision versions (e.g. 2.0493)", () => {
61
+ const result = rules[0].bodyRegexes
62
+ .map((r) => matchString("PowerCMS 2.0493", r))
63
+ .find((r) => r.hit);
64
+ expect(result.version).toBe("2.0493");
65
+ });
66
+ it("confirms PowerCMS via the plugin path even without a version", () => {
67
+ const regex = rules[0].bodyRegexes.find((r) => r.includes("/mt-static/plugins/PowerCMS/"));
68
+ const result = matchString('<img src="/mt-static/plugins/PowerCMS/logo.png">', regex);
69
+ expect(result.hit).toBe(true);
70
+ expect(result.version).toBeUndefined();
71
+ });
72
+ });
73
+ it("uses the Alfasado PowerCMS CPE 2.2 identifier", () => {
74
+ expect(powerCmsSignature.cpe).toBe("cpe:/a:alfasado:powercms");
75
+ });
76
+ it("excludes Movable Type so only PowerCMS is reported", () => {
77
+ expect(powerCmsSignature.excludes).toContain(movableTypeSignature.name);
78
+ });
79
+ it("implies Perl as the underlying runtime", () => {
80
+ expect(powerCmsSignature.impliedSoftwares).toContain("Perl");
81
+ });
82
+ });
83
+ //# sourceMappingURL=power_cms.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"power_cms.test.js","sourceRoot":"","sources":["../../../src/signatures/technologies/power_cms.test.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "whopper",
3
- "version": "0.5.9",
3
+ "version": "0.6.0",
4
4
  "description": "Whopper is a CLI tool that discovers and detects web technologies used on websites.",
5
5
  "scripts": {
6
6
  "build": "tsc -p tsconfig.json",