Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement warning messages for invalid characters in names #1009

Merged
merged 6 commits into from
Jun 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
223 changes: 223 additions & 0 deletions src/lib/Accessory.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,229 @@ describe("Accessory", () => {
});
});

describe("Accessory and Service naming checks", () => {
let consoleLogSpy: jest.SpyInstance;

beforeEach(() => {
consoleLogSpy = jest.spyOn(console, "warn");
});

afterEach(() => {
consoleLogSpy.mockRestore();
});

test("Accessory Name ending with !", async () => {
const accessoryBadName = new Accessory("Bad Name!",uuid.generate("Bad Name"));

const publishInfo: PublishInfo = {
username: serverUsername,
pincode: "000-00-000",
category: Categories.SWITCH,
advertiser: undefined,
};

await accessoryBadName.publish(publishInfo);
// eslint-disable-next-line max-len
expect(consoleLogSpy).toHaveBeenCalledWith("HAP-NodeJS WARNING: The accessory 'Bad Name! 7430' is getting published with the characteristic 'Name' not following HomeKit naming rules ('Bad Name! 7430'). Use only alphanumeric, space, and apostrophe characters, start and end with an alphabetic or numeric character, and don't include emojis. This might prevent the accessory from being added to the Home App or leading to the accessory being unresponsive!");
NorthernMan54 marked this conversation as resolved.
Show resolved Hide resolved

await awaitEventOnce(accessoryBadName, AccessoryEventTypes.ADVERTISED);
await accessoryBadName?.unpublish();
await accessoryBadName?.destroy();
});

test("Accessory Name containing !", async () => {
const accessoryBadName = new Accessory("Bad ! Name",uuid.generate("Bad Name"));

const publishInfo: PublishInfo = {
username: serverUsername,
pincode: "000-00-000",
category: Categories.SWITCH,
advertiser: undefined,
};

await accessoryBadName.publish(publishInfo);
// eslint-disable-next-line max-len
expect(consoleLogSpy).toHaveBeenCalledWith("HAP-NodeJS WARNING: The accessory 'Bad ! Name 7430' is getting published with the characteristic 'Name' not following HomeKit naming rules ('Bad ! Name 7430'). Use only alphanumeric, space, and apostrophe characters, start and end with an alphabetic or numeric character, and don't include emojis. This might prevent the accessory from being added to the Home App or leading to the accessory being unresponsive!");

await awaitEventOnce(accessoryBadName, AccessoryEventTypes.ADVERTISED);
await accessoryBadName?.unpublish();
await accessoryBadName?.destroy();
});

test("Accessory Name containing '", async () => {
const accessoryBadName = new Accessory("Bad ' Name",uuid.generate("Bad ' Name"));

const publishInfo: PublishInfo = {
username: serverUsername,
pincode: "000-00-000",
category: Categories.SWITCH,
advertiser: undefined,
};

await accessoryBadName.publish(publishInfo);
expect(consoleLogSpy).toBeCalledTimes(0);

await awaitEventOnce(accessoryBadName, AccessoryEventTypes.ADVERTISED);
await accessoryBadName?.unpublish();
await accessoryBadName?.destroy();
});

test("Accessory Name starting with '", async () => {
const accessoryBadName = new Accessory("'Bad Name",uuid.generate("Bad Name'"));

const publishInfo: PublishInfo = {
username: serverUsername,
pincode: "000-00-000",
category: Categories.SWITCH,
advertiser: undefined,
};

await accessoryBadName.publish(publishInfo);
expect(accessoryBadName.displayName.startsWith(TEST_DISPLAY_NAME));
expect(consoleLogSpy).toBeCalledTimes(2);
// eslint-disable-next-line max-len
expect(consoleLogSpy).toHaveBeenCalledWith("HAP-NodeJS WARNING: The accessory ''Bad Name 7430' is getting published with the characteristic 'Name' not following HomeKit naming rules (''Bad Name 7430'). Use only alphanumeric, space, and apostrophe characters, start and end with an alphabetic or numeric character, and don't include emojis. This might prevent the accessory from being added to the Home App or leading to the accessory being unresponsive!");

await awaitEventOnce(accessoryBadName, AccessoryEventTypes.ADVERTISED);
await accessoryBadName?.unpublish();
await accessoryBadName?.destroy();
});

test("Service Name containing !", async () => {
const switchService = new Service.Switch("My Bad ! Switch");
const accessoryBadName = new Accessory("Bad Name",uuid.generate("Bad Name"));
accessoryBadName.addService(switchService);

const publishInfo: PublishInfo = {
username: serverUsername,
pincode: "000-00-000",
category: Categories.SWITCH,
advertiser: undefined,
};

await accessoryBadName.publish(publishInfo);
// eslint-disable-next-line max-len
expect(consoleLogSpy).toHaveBeenCalledWith("HAP-NodeJS WARNING: The accessory 'My Bad ! Switch' is getting published with the characteristic 'Name' not following HomeKit naming rules ('My Bad ! Switch'). Use only alphanumeric, space, and apostrophe characters, start and end with an alphabetic or numeric character, and don't include emojis. This might prevent the accessory from being added to the Home App or leading to the accessory being unresponsive!");

await awaitEventOnce(accessoryBadName, AccessoryEventTypes.ADVERTISED);
await accessoryBadName?.unpublish();
await accessoryBadName?.destroy();
});

test("Service Name ending with !", async () => {
const switchService = new Service.Switch("My Bad Switch!");
const accessoryBadName = new Accessory("Bad Name",uuid.generate("Bad Name"));
accessoryBadName.addService(switchService);

const publishInfo: PublishInfo = {
username: serverUsername,
pincode: "000-00-000",
category: Categories.SWITCH,
advertiser: undefined,
};

await accessoryBadName.publish(publishInfo);
expect(consoleLogSpy).toBeCalledTimes(1);
// eslint-disable-next-line max-len
expect(consoleLogSpy).toHaveBeenCalledWith("HAP-NodeJS WARNING: The accessory 'My Bad Switch!' is getting published with the characteristic 'Name' not following HomeKit naming rules ('My Bad Switch!'). Use only alphanumeric, space, and apostrophe characters, start and end with an alphabetic or numeric character, and don't include emojis. This might prevent the accessory from being added to the Home App or leading to the accessory being unresponsive!");

await awaitEventOnce(accessoryBadName, AccessoryEventTypes.ADVERTISED);
await accessoryBadName?.unpublish();
await accessoryBadName?.destroy();
});

test("Service Name containing '", async () => {
const switchService = new Service.Switch("My Bad ' Switch");
const accessoryBadName = new Accessory("Bad Name",uuid.generate("Bad Name"));
accessoryBadName.addService(switchService);

const publishInfo: PublishInfo = {
username: serverUsername,
pincode: "000-00-000",
category: Categories.SWITCH,
advertiser: undefined,
};

await accessoryBadName.publish(publishInfo);
expect(consoleLogSpy).toBeCalledTimes(0);

await awaitEventOnce(accessoryBadName, AccessoryEventTypes.ADVERTISED);
await accessoryBadName?.unpublish();
await accessoryBadName?.destroy();
});

test("Service Name ending with '", async () => {
const switchService = new Service.Switch("My Bad Switch'");
const accessoryBadName = new Accessory("Bad Name",uuid.generate("Bad Name"));
accessoryBadName.addService(switchService);

const publishInfo: PublishInfo = {
username: serverUsername,
pincode: "000-00-000",
category: Categories.SWITCH,
advertiser: undefined,
};

await accessoryBadName.publish(publishInfo);
expect(consoleLogSpy).toBeCalledTimes(1);
// eslint-disable-next-line max-len
expect(consoleLogSpy).toHaveBeenCalledWith("HAP-NodeJS WARNING: The accessory 'My Bad Switch'' is getting published with the characteristic 'Name' not following HomeKit naming rules ('My Bad Switch''). Use only alphanumeric, space, and apostrophe characters, start and end with an alphabetic or numeric character, and don't include emojis. This might prevent the accessory from being added to the Home App or leading to the accessory being unresponsive!");

await awaitEventOnce(accessoryBadName, AccessoryEventTypes.ADVERTISED);
await accessoryBadName?.unpublish();
await accessoryBadName?.destroy();
});

test("Service Name beginning with '", async () => {
const switchService = new Service.Switch("'My Bad Switch");
const accessoryBadName = new Accessory("Bad Name",uuid.generate("Bad Name"));
accessoryBadName.addService(switchService);

const publishInfo: PublishInfo = {
username: serverUsername,
pincode: "000-00-000",
category: Categories.SWITCH,
advertiser: undefined,
};

await accessoryBadName.publish(publishInfo);
expect(consoleLogSpy).toBeCalledTimes(1);
// eslint-disable-next-line max-len
expect(consoleLogSpy).toHaveBeenCalledWith("HAP-NodeJS WARNING: The accessory ''My Bad Switch' is getting published with the characteristic 'Name' not following HomeKit naming rules (''My Bad Switch'). Use only alphanumeric, space, and apostrophe characters, start and end with an alphabetic or numeric character, and don't include emojis. This might prevent the accessory from being added to the Home App or leading to the accessory being unresponsive!");

await awaitEventOnce(accessoryBadName, AccessoryEventTypes.ADVERTISED);
await accessoryBadName?.unpublish();
await accessoryBadName?.destroy();
});

test("Service ConfiguredName beginning with '", async () => {
const switchService = new Service.Switch("My Bad Switch");
const accessoryBadName = new Accessory("Bad Name",uuid.generate("Bad Name"));
switchService.addCharacteristic(Characteristic.ConfiguredName);
accessoryBadName.addService(switchService);

const publishInfo: PublishInfo = {
username: serverUsername,
pincode: "000-00-000",
category: Categories.SWITCH,
advertiser: undefined,
};

await accessoryBadName.publish(publishInfo);

switchService.getCharacteristic(Characteristic.ConfiguredName).updateValue("'Bad Name");

expect(consoleLogSpy).toBeCalledTimes(1);
// eslint-disable-next-line max-len
expect(consoleLogSpy).toHaveBeenCalledWith("HAP-NodeJS WARNING: The accessory 'unknown' is getting published with the characteristic 'Configured Name' not following HomeKit naming rules (''Bad Name'). Use only alphanumeric, space, and apostrophe characters, start and end with an alphabetic or numeric character, and don't include emojis. This might prevent the accessory from being added to the Home App or leading to the accessory being unresponsive!");

await awaitEventOnce(accessoryBadName, AccessoryEventTypes.ADVERTISED);
await accessoryBadName?.unpublish();
await accessoryBadName?.destroy();
});

});

NorthernMan54 marked this conversation as resolved.
Show resolved Hide resolved
describe("pairing", () => {
let defaultPairingInfo: PairingInformation;

Expand Down
5 changes: 5 additions & 0 deletions src/lib/Accessory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ import { EventName, HAPConnection, HAPUsername } from "./util/eventedhttp";
import { formatOutgoingCharacteristicValue } from "./util/request-util";
import * as uuid from "./util/uuid";
import { toShortForm } from "./util/uuid";
import { checkName } from "./util/checkName";

const debug = createDebug("HAP-NodeJS:Accessory");
const MAX_ACCESSORIES = 149; // Maximum number of bridged accessories per bridge.
Expand Down Expand Up @@ -498,6 +499,7 @@ export class Accessory extends EventEmitter {
"valid UUID from any arbitrary string, like a serial number.");

// create our initial "Accessory Information" Service that all Accessories are expected to have
checkName(this.displayName, "name", displayName);
this.addService(Service.AccessoryInformation)
.setCharacteristic(Characteristic.Name, displayName);

Expand Down Expand Up @@ -1053,11 +1055,14 @@ export class Accessory extends EventEmitter {
const serialNumber = service.getCharacteristic(Characteristic.SerialNumber).value;
const firmwareRevision = service.getCharacteristic(Characteristic.FirmwareRevision).value;
const name = service.getCharacteristic(Characteristic.Name).value;
const manufacturer = service.getCharacteristic(Characteristic.Manufacturer).value;

checkValue("Model", model);
checkValue("SerialNumber", serialNumber);
checkValue("FirmwareRevision", firmwareRevision);
checkValue("Name", name);
checkName(this.displayName, "Name", name);
checkName(this.displayName, "Manufacturer", manufacturer);
}

if (mainAccessory) {
Expand Down
5 changes: 5 additions & 0 deletions src/lib/Characteristic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ import {
numericUpperBound,
} from "./util/request-util";
import { BASE_UUID, toShortForm } from "./util/uuid";
import { checkName } from "./util/checkName";

const debug = createDebug("HAP-NodeJS:Characteristic");

Expand Down Expand Up @@ -2976,6 +2977,10 @@ export class Characteristic extends EventEmitter {
value = value.substring(0, maxLength);
}

if (this.UUID === "000000E3-0000-1000-8000-0026BB765291") {
checkName("unknown", this.displayName, value);
}

return value;
}
case Formats.DATA:
Expand Down
2 changes: 2 additions & 0 deletions src/lib/Service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ import { IdentifierCache } from "./model/IdentifierCache";
import { HAPConnection } from "./util/eventedhttp";
import { HapStatusError } from "./util/hapStatusError";
import { toShortForm } from "./util/uuid";
import { checkName } from "./util/checkName";

const debug = createDebug("HAP-NodeJS:Service");

Expand Down Expand Up @@ -553,6 +554,7 @@ export class Service extends EventEmitter {
// if you don't provide a display name, some HomeKit apps may choose to hide the device.
if (displayName) {
// create the characteristic if necessary
checkName(this.displayName, "Name", displayName);
const nameCharacteristic =
this.getCharacteristic(Characteristic.Name) ||
this.addCharacteristic(Characteristic.Name);
Expand Down
54 changes: 54 additions & 0 deletions src/lib/util/checkName.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { checkName } from "./checkName";

describe("#checkName()", () => {
let consoleLogSpy: jest.SpyInstance;

beforeEach(() => {
consoleLogSpy = jest.spyOn(console, "warn");
});

afterEach(() => {
consoleLogSpy.mockRestore();
});

test("Accessory Name ending with !", async () => {
checkName("displayName", "name", "bad name!");

expect(consoleLogSpy).toBeCalledTimes(1);
// eslint-disable-next-line max-len
expect(consoleLogSpy).toHaveBeenCalledWith("HAP-NodeJS WARNING: The accessory 'displayName' is getting published with the characteristic 'name' not following HomeKit naming rules ('bad name!'). Use only alphanumeric, space, and apostrophe characters, start and end with an alphabetic or numeric character, and don't include emojis. This might prevent the accessory from being added to the Home App or leading to the accessory being unresponsive!");
});

test("Accessory Name begining with !", async () => {
checkName("displayName", "name", "!bad name");

expect(consoleLogSpy).toBeCalledTimes(1);
// eslint-disable-next-line max-len
expect(consoleLogSpy).toHaveBeenCalledWith("HAP-NodeJS WARNING: The accessory 'displayName' is getting published with the characteristic 'name' not following HomeKit naming rules ('!bad name'). Use only alphanumeric, space, and apostrophe characters, start and end with an alphabetic or numeric character, and don't include emojis. This might prevent the accessory from being added to the Home App or leading to the accessory being unresponsive!");
});

test("Accessory Name containing !", async () => {
checkName("displayName", "name", "bad ! name");

expect(consoleLogSpy).toBeCalledTimes(1);
// eslint-disable-next-line max-len
expect(consoleLogSpy).toHaveBeenCalledWith("HAP-NodeJS WARNING: The accessory 'displayName' is getting published with the characteristic 'name' not following HomeKit naming rules ('bad ! name'). Use only alphanumeric, space, and apostrophe characters, start and end with an alphabetic or numeric character, and don't include emojis. This might prevent the accessory from being added to the Home App or leading to the accessory being unresponsive!");
});

test("Accessory Name begining with '", async () => {
checkName("displayName", "name", "'bad name");

expect(consoleLogSpy).toBeCalledTimes(1);
// eslint-disable-next-line max-len
expect(consoleLogSpy).toHaveBeenCalledWith("HAP-NodeJS WARNING: The accessory 'displayName' is getting published with the characteristic 'name' not following HomeKit naming rules (''bad name'). Use only alphanumeric, space, and apostrophe characters, start and end with an alphabetic or numeric character, and don't include emojis. This might prevent the accessory from being added to the Home App or leading to the accessory being unresponsive!");
});

test("Accessory Name containing '", async () => {
checkName("displayName", "name", "bad ' name");

expect(consoleLogSpy).toBeCalledTimes(0);
// eslint-disable-next-line max-len
// expect(consoleLogSpy).toHaveBeenCalledWith("HAP-NodeJS WARNING: The accessory 'displayName' is getting published with the characteristic 'name' not following HomeKit naming rules ('bad name!'). Use only alphanumeric, space, and apostrophe characters, start and end with an alphabetic or numeric character, and don't include emojis. This might prevent the accessory from being added to the Home App or leading to the accessory being unresponsive!");
});

});
19 changes: 19 additions & 0 deletions src/lib/util/checkName.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* Checks that supplied field meets Apple HomeKit naming rules
* https://developer.apple.com/design/human-interface-guidelines/homekit#Help-people-choose-useful-names
* @private Private API
*/

// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
export function checkName(displayName: string, name: string, value: any): void {
const validHK = /^[a-zA-Z0-9\s'-.]+$/; // Ensure only letter, numbers, apostrophe, or dash
const startWith = /^[a-zA-Z0-9]/; // Ensure only letters or numbers are at the beginning of string
const endWith = /[a-zA-Z0-9]$/; // Ensure only letters or numbers are at the end of string

if (!validHK.test(value) || !startWith.test(value) || !endWith.test(value)) {
console.warn("HAP-NodeJS WARNING: The accessory '" + displayName + "' is getting published with the characteristic '" +
name + "'" + " not following HomeKit naming rules ('" + value + "'). " +
"Use only alphanumeric, space, and apostrophe characters, start and end with an alphabetic or numeric character, and don't include emojis. " +
"This might prevent the accessory from being added to the Home App or leading to the accessory being unresponsive!");
}
}
Loading