Skip to content

Commit c76dd3d

Browse files
committed
fix(ldap): preserve binary attrs when ensureSchema inherits caller opts
ensureSchema() was forwarding the user's full options (including outputType: TREE and onObject) to its internal attributeSchema query, which returned a tree instead of a flat list. The for-loop then iterated OU nodes with no attributeSyntax field, leaving _schemaCache empty. interpretValue() fell through to UTF-8 decoding for every binary attr (jpegPhoto, objectSid, objectGUID, NT security descriptors). Force FLAT + no onObject on the schema query. Align demo_ldap TREE count to leaf count. Refresh README examples (rename demo_comtypes → demo_comtypes_file, add demo_comtypes_ads for Active Directory via IDirectorySearch + ADsOpenObject).
1 parent 89128a8 commit c76dd3d

9 files changed

Lines changed: 791 additions & 8 deletions

File tree

README.md

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,26 @@ h.magic = 0x12345678; // stored as 12 34 56 78 on the wire
380380
- No automatic memory management for returned pointers
381381
- Both `class extends Structure` (Python-like) and `struct({...})` (functional) syntaxes available
382382
- Only type classes (`c_int32`) are accepted, not string literals (`"int32"`) — same as Python
383+
- `c_char_p` / `c_wchar_p` argtypes also accept a raw `BigInt` / `number` pointer
384+
address. Python ctypes is stricter (raises `TypeError` on `int`); for portable
385+
code use `c_void_p` argtype when passing a raw address — both libraries
386+
accept it there. See below.
387+
388+
### Passing raw pointer addresses
389+
390+
```javascript
391+
// You receive a LPWSTR address from another API (e.g. IDirectorySearch::
392+
// GetNextColumnName writes an address into a Buffer):
393+
const addr = pszColumn.readBigUInt64LE(0);
394+
395+
// Node-ctypes (convenience — accepted on string argtypes):
396+
lib.wcslen.argtypes = [c_wchar_p];
397+
lib.wcslen(addr); // works
398+
399+
// Python-idiomatic (works in both libraries — use for portable code):
400+
lib.wcslen.argtypes = [c_void_p];
401+
lib.wcslen(addr); // works
402+
```
383403

384404
### Debugging callback leaks
385405

@@ -434,11 +454,12 @@ const p = new Point({ x: 3, y: 4 }); // p.x, p.y typed as number
434454
## Examples
435455

436456
- [Windows Controls Demo](examples/windows/demo_controls.js) — Win32 common controls showcase
437-
- [Windows Registry Demo](examples/windows/demo_registry.js) — setValue, getValue, openKey, deleteValue, deleteKey
438-
- [Windows Tray Demo](examples/windows/demo_tray.js) — System tray menu
439-
- [Windows COM Automation Demo](examples/windows/demo_comtypes.js) — IShellLinkW / IPersistFile COM interfaces
440-
- [Windows LDAP Demo](examples/windows/demo_ldap.js) — LDAP directory queries
441-
- [Smart Card (PC/SC) Demo](examples/demo_scard.js) — WinSCard on Windows, pcsclite on macOS/Linux
457+
- [Windows Registry Demo](examples/windows/demo_registry.js)`setValue` / `getValue` / `openKey` / `deleteValue` / `deleteKey`
458+
- [Windows Tray Demo](examples/windows/demo_tray.js) — system tray icon and popup menu
459+
- [Windows COM — Shortcut](examples/windows/demo_comtypes_file.js) — create a `.lnk` via `IShellLinkW` + `IPersistFile`
460+
- [Windows COM — Active Directory](examples/windows/demo_comtypes_ads.js)`IDirectorySearch` / `ADsOpenObject` for AD queries (`--tree` / `--flat`)
461+
- [Windows LDAP Demo](examples/windows/demo_ldap.js) — LDAP v3 paged search (`wldap32`), tree/flat output, auth with `SEC_WINNT_AUTH_IDENTITY`
462+
- [Smart Card (PC/SC) Demo](examples/demo_scard.js)`WinSCard` on Windows, `pcsclite` on macOS/Linux
442463

443464
## Tests
444465

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// IDirectorySearch interface definitions for comtypes
2+
//
3+
// References:
4+
// https://learn.microsoft.com/en-us/windows/win32/api/iads/nn-iads-idirectorysearch
5+
6+
import { DWORD, LPCWSTR, LPVOID, HANDLE } from "../../wintypes.js";
7+
import { COMInterface, STDMETHOD, HRESULT, IUnknown, GUID } from "../comtypes.js";
8+
9+
export const IID_IDirectorySearch = new GUID("{109BA8EC-92F0-11D0-A790-00C04FD8D5A8}");
10+
11+
// ═══════════════════════════════════════════════════════════════════════
12+
// IDirectorySearch — Active Directory search interface
13+
// ═══════════════════════════════════════════════════════════════════════
14+
// vtable: IUnknown (3) + 10 own methods = 13 slots
15+
//
16+
// ADS_SEARCH_HANDLE is `HANDLE` (void* pointer-sized). For `ExecuteSearch`,
17+
// pass (DWORD)-1 (= 0xFFFFFFFF) as dwNumberAttributes to request all attributes.
18+
19+
export const IDirectorySearch = COMInterface({
20+
name: "IDirectorySearch",
21+
iid: "{109BA8EC-92F0-11D0-A790-00C04FD8D5A8}",
22+
extends: IUnknown,
23+
methods: [
24+
// slot 3: SetSearchPreference(PADS_SEARCHPREF_INFO, DWORD)
25+
STDMETHOD(HRESULT, "SetSearchPreference", [LPVOID, DWORD]),
26+
// slot 4: ExecuteSearch(LPWSTR, LPWSTR*, DWORD, PADS_SEARCH_HANDLE)
27+
STDMETHOD(HRESULT, "ExecuteSearch", [LPCWSTR, LPVOID, DWORD, LPVOID]),
28+
// slot 5: AbandonSearch(ADS_SEARCH_HANDLE)
29+
STDMETHOD(HRESULT, "AbandonSearch", [HANDLE]),
30+
// slot 6: GetFirstRow(ADS_SEARCH_HANDLE)
31+
STDMETHOD(HRESULT, "GetFirstRow", [HANDLE]),
32+
// slot 7: GetNextRow(ADS_SEARCH_HANDLE)
33+
STDMETHOD(HRESULT, "GetNextRow", [HANDLE]),
34+
// slot 8: GetPreviousRow(ADS_SEARCH_HANDLE)
35+
STDMETHOD(HRESULT, "GetPreviousRow", [HANDLE]),
36+
// slot 9: GetNextColumnName(ADS_SEARCH_HANDLE, LPWSTR*)
37+
STDMETHOD(HRESULT, "GetNextColumnName", [HANDLE, LPVOID]),
38+
// slot 10: GetColumn(ADS_SEARCH_HANDLE, LPWSTR, PADS_SEARCH_COLUMN)
39+
STDMETHOD(HRESULT, "GetColumn", [HANDLE, LPCWSTR, LPVOID]),
40+
// slot 11: FreeColumn(PADS_SEARCH_COLUMN)
41+
STDMETHOD(HRESULT, "FreeColumn", [LPVOID]),
42+
// slot 12: CloseSearchHandle(ADS_SEARCH_HANDLE)
43+
STDMETHOD(HRESULT, "CloseSearchHandle", [HANDLE]),
44+
],
45+
});
46+
47+
// ═══════════════════════════════════════════════════════════════════════
48+
// ADS_SEARCHPREF_ENUM — search preference identifiers
49+
// ═══════════════════════════════════════════════════════════════════════
50+
51+
export const ADS_SEARCHPREF_ASYNCHRONOUS = 0;
52+
export const ADS_SEARCHPREF_DEREF_ALIASES = 1;
53+
export const ADS_SEARCHPREF_SIZE_LIMIT = 2;
54+
export const ADS_SEARCHPREF_TIME_LIMIT = 3;
55+
export const ADS_SEARCHPREF_ATTRIBTYPES_ONLY = 4;
56+
export const ADS_SEARCHPREF_SEARCH_SCOPE = 5;
57+
export const ADS_SEARCHPREF_TIMEOUT = 6;
58+
export const ADS_SEARCHPREF_PAGESIZE = 7;
59+
export const ADS_SEARCHPREF_PAGED_TIME_LIMIT = 8;
60+
export const ADS_SEARCHPREF_CHASE_REFERRALS = 9;
61+
export const ADS_SEARCHPREF_SORT_ON = 10;
62+
export const ADS_SEARCHPREF_CACHE_RESULTS = 11;
63+
export const ADS_SEARCHPREF_DIRSYNC = 12;
64+
export const ADS_SEARCHPREF_TOMBSTONE = 13;
65+
66+
// ═══════════════════════════════════════════════════════════════════════
67+
// ADS_SCOPEENUM — search scope values
68+
// ═══════════════════════════════════════════════════════════════════════
69+
70+
export const ADS_SCOPE_BASE = 0;
71+
export const ADS_SCOPE_ONELEVEL = 1;
72+
export const ADS_SCOPE_SUBTREE = 2;
73+
74+
// ═══════════════════════════════════════════════════════════════════════
75+
// ADS_CHASE_REFERRALS_ENUM
76+
// ═══════════════════════════════════════════════════════════════════════
77+
78+
export const ADS_CHASE_REFERRALS_NEVER = 0;
79+
export const ADS_CHASE_REFERRALS_SUBORDINATE = 0x20;
80+
export const ADS_CHASE_REFERRALS_EXTERNAL = 0x40;
81+
export const ADS_CHASE_REFERRALS_ALWAYS = ADS_CHASE_REFERRALS_SUBORDINATE | ADS_CHASE_REFERRALS_EXTERNAL;
82+
83+
// ═══════════════════════════════════════════════════════════════════════
84+
// ADSTYPEENUM — column value types (for ADS_SEARCH_COLUMN.dwADsType)
85+
// ═══════════════════════════════════════════════════════════════════════
86+
87+
export const ADSTYPE_INVALID = 0;
88+
export const ADSTYPE_DN_STRING = 1;
89+
export const ADSTYPE_CASE_EXACT_STRING = 2;
90+
export const ADSTYPE_CASE_IGNORE_STRING = 3;
91+
export const ADSTYPE_PRINTABLE_STRING = 4;
92+
export const ADSTYPE_NUMERIC_STRING = 5;
93+
export const ADSTYPE_BOOLEAN = 6;
94+
export const ADSTYPE_INTEGER = 7;
95+
export const ADSTYPE_OCTET_STRING = 8;
96+
export const ADSTYPE_UTC_TIME = 9;
97+
export const ADSTYPE_LARGE_INTEGER = 10;
98+
export const ADSTYPE_TYPEDNAME = 11;
99+
export const ADSTYPE_FAXNUMBER = 12;
100+
export const ADSTYPE_PATH = 13;
101+
export const ADSTYPE_OBJECT_CLASS = 14;
102+
export const ADSTYPE_NT_SECURITY_DESCRIPTOR = 25;
103+
104+
// S_ADS_NOMORE_ROWS — returned by GetNextRow/GetFirstRow when no more rows
105+
export const S_ADS_NOMORE_ROWS = 0x00005012;
106+
// S_ADS_NOMORE_COLUMNS — returned by GetNextColumnName when no more columns
107+
export const S_ADS_NOMORE_COLUMNS = 0x00005013;

0 commit comments

Comments
 (0)