outlook_mapi_stub/
lib.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT license.
3
4use proc_macro::TokenStream;
5use quote::{format_ident, quote};
6use syn::{
7    Abi, Expr, ExprLit, FnArg, ForeignItemFn, Ident, Lit, LitStr, Meta, MetaNameValue, Pat,
8    PatType, Result, ReturnType, braced,
9    parse::{Parse, ParseStream},
10    parse_macro_input,
11    punctuated::{Pair, Punctuated},
12    token::Comma,
13};
14
15struct DelayLoadAttr {
16    pub name: LitStr,
17}
18
19impl Parse for DelayLoadAttr {
20    fn parse(input: ParseStream) -> Result<Self> {
21        let meta: Meta = input.parse()?;
22        match meta {
23            Meta::NameValue(MetaNameValue {
24                path,
25                value:
26                    Expr::Lit(ExprLit {
27                        lit: Lit::Str(name),
28                        ..
29                    }),
30                ..
31            }) if path.get_ident().map(Ident::to_string).as_deref() == Some("name") => {
32                Ok(DelayLoadAttr { name: name.clone() })
33            }
34            _ => Err(input.error(r#"expected #[delay_load(name = "...")]"#)),
35        }
36    }
37}
38
39struct ExternDecl {
40    pub abi: LitStr,
41    pub ident: Ident,
42    pub inputs: Punctuated<FnArg, Comma>,
43    pub output: ReturnType,
44}
45
46impl Parse for ExternDecl {
47    fn parse(input: ParseStream) -> Result<Self> {
48        let abi: Abi = input.parse()?;
49        let abi = abi
50            .name
51            .ok_or_else(|| input.error(r#"expected "system" or "cdecl""#))?;
52
53        let content;
54        braced!(content in input);
55        let foreign_item: ForeignItemFn = content.parse()?;
56
57        Ok(ExternDecl {
58            abi,
59            ident: foreign_item.sig.ident,
60            inputs: foreign_item.sig.inputs,
61            output: foreign_item.sig.output,
62        })
63    }
64}
65
66/// Implement a delay load helper for the foreign function declaration in an extern block.
67#[proc_macro_attribute]
68pub fn delay_load(attr: TokenStream, input: TokenStream) -> TokenStream {
69    let attr = parse_macro_input!(attr as DelayLoadAttr);
70    let ast = parse_macro_input!(input as ExternDecl);
71    impl_delay_load(&attr, &ast)
72}
73
74fn no_arg_size(undecorated: &str) -> bool {
75    use std::{collections::BTreeSet, sync::OnceLock};
76
77    static NO_ARG_SIZE_MAPI: OnceLock<BTreeSet<&'static str>> = OnceLock::new();
78    let no_arg_size_mapi = NO_ARG_SIZE_MAPI.get_or_init(|| {
79        BTreeSet::from([
80            // "BMAPIAddress",
81            // "BMAPIDetails",
82            // "BMAPIFindNext",
83            // "BMAPIGetAddress",
84            // "BMAPIGetReadMail",
85            // "BMAPIReadMail",
86            // "BMAPIResolveName",
87            // "BMAPISaveMail",
88            // "BMAPISendMail",
89            // "FGetComponentPath",
90            "FixMAPI",
91            "GetOutlookVersion",
92            // "GetTnefStreamCodepage",
93            "HrGetOmiProvidersFlags",
94            "HrSetOmiProvidersFlagsInvalid",
95            // "LAUNCHWIZARD",
96            // "MAPIAddress",
97            // "MAPIAdminProfiles",
98            // "MAPIAllocateBuffer",
99            // "MAPIAllocateMore",
100            // "MAPIDeleteMail",
101            // "MAPIDetails",
102            // "MAPIFindNext",
103            // "MAPIFreeBuffer",
104            // "MAPIInitialize",
105            // "MAPILogoff",
106            // "MAPILogon",
107            // "MAPILogonEx",
108            // "MAPIOpenFormMgr",
109            // "MAPIOpenLocalFormContainer",
110            // "MAPIReadMail",
111            // "MAPIResolveName",
112            // "MAPISaveMail",
113            // "MAPISendDocuments",
114            // "MAPISendMail",
115            // "MAPISendMailW",
116            // "MAPIUninitialize",
117            // "OpenStreamOnFile",
118            // "OpenTnefStream",
119            // "OpenTnefStreamEx",
120            // "PRProviderInit",
121            // "RTFSync",
122            // "ScMAPIXFromCMC",
123            // "ScMAPIXFromSMAPI",
124            // "WrapCompressedRTFStream",
125        ])
126    });
127
128    static NO_ARG_SIZE_OLMAPI: OnceLock<BTreeSet<&'static str>> = OnceLock::new();
129    let no_arg_size_olmapi = NO_ARG_SIZE_OLMAPI.get_or_init(|| {
130        BTreeSet::from([
131            "BMAPIAddress",
132            "BMAPIDetails",
133            "BMAPIFindNext",
134            "BMAPIGetAddress",
135            "BMAPIGetReadMail",
136            "BMAPIReadMail",
137            "BMAPIResolveName",
138            "BMAPISaveMail",
139            "BMAPISendMail",
140            "ClosePerformanceData",
141            "CollectPerformanceData",
142            "CreateMapiInitializationMonitor",
143            "CreateObject",
144            "DoDeliveryReport",
145            "EndBoot",
146            "EtwTraceMessage",
147            "FGetComponentPath",
148            "GetTnefStreamCodepage",
149            "HrEnsureProviderResourceDLL",
150            "HrGetDefaultStoragePathA",
151            "HrGetDefaultStoragePathW",
152            "HrGetEDPIdentifierFromStoreEIDOnMapi",
153            "HrGetOpenTnefStream",
154            "HrGetProviderResourceDLL",
155            "HrNotify",
156            "LAUNCHWIZARD",
157            "MAPIAddress",
158            "MAPIAdminProfiles",
159            "MAPIAllocateBuffer",
160            "MAPIAllocateBufferProv",
161            "MAPIAllocateMore",
162            "MAPIAllocateMoreProv",
163            "MAPICrashRecovery",
164            "MAPIDeleteMail",
165            "MAPIDetails",
166            "MAPIFindNext",
167            "MAPIFreeBuffer",
168            "MAPIInitialize",
169            "MAPILogoff",
170            "MAPILogon",
171            "MAPILogonEx",
172            "MAPIOpenFormMgr",
173            "MAPIOpenLocalFormContainer",
174            "MAPIReadMail",
175            "MAPIResolveName",
176            "MAPISaveMail",
177            "MAPISendDocuments",
178            "MAPISendMail",
179            "MAPISendMailW",
180            "MAPIUninitialize",
181            "MAPIValidateAllocatedBuffer",
182            "MSProviderInit",
183            "OpenPerformanceData",
184            "OpenStreamOnFile",
185            "OpenStreamOnFileW",
186            "OpenTnefStream",
187            "OpenTnefStreamEx",
188            "OverrideMAPIResourcePath",
189            "PRProviderInit",
190            "RPCTRACE",
191            "RTFSync",
192            "RTFSyncCpid",
193            "RopString",
194            "RpcTraceReadRegSettings",
195            "ScMAPIXFromCMC",
196            "ScMAPIXFromSMAPI",
197            "Unload",
198            "WrapCompressedRTFStream",
199            "WrapCompressedRTFStreamEx",
200            "fnevString",
201            "g_dwRpcThreshold",
202        ])
203    });
204
205    no_arg_size_mapi.contains(undecorated) || no_arg_size_olmapi.contains(undecorated)
206}
207
208fn impl_delay_load(attr: &DelayLoadAttr, ast: &ExternDecl) -> TokenStream {
209    let dll = &attr.name.value();
210    let abi = &ast.abi;
211    let name = &ast.ident;
212    let inputs = &ast.inputs;
213    let output = &ast.output;
214
215    let mut args_size = quote! { 0 };
216    let mut forward_args: Punctuated<Box<Pat>, Comma> = Punctuated::new();
217    for pair in inputs.pairs() {
218        match pair {
219            Pair::Punctuated(FnArg::Typed(PatType { pat, ty, .. }), comma) => {
220                forward_args.push_value(pat.clone());
221                forward_args.push_punct(*comma);
222                args_size = quote! { #args_size + mem::size_of::<#ty>() };
223            }
224            Pair::End(FnArg::Typed(PatType { pat, ty, .. })) => {
225                forward_args.push_value(pat.clone());
226                args_size = quote! { #args_size + mem::size_of::<#ty>() };
227            }
228            _ => panic!("should not have a receiver/self argument"),
229        }
230    }
231
232    let func_type = format_ident!("PFN{}", name);
233    let proc_name = LitStr::new(&format!("{name}"), name.span());
234
235    let undecorated = format!("{name}");
236    let build_proc_name = if no_arg_size(undecorated.as_str()) {
237        quote! {
238            let proc_name = s!(#proc_name);
239        }
240    } else {
241        quote! {
242            let mut proc_name: Vec<_> = #proc_name.bytes().collect();
243            #[cfg(target_pointer_width = "32")]
244            {
245                const ARG_SIZE: usize = #args_size;
246                proc_name.extend(format!("@{ARG_SIZE}").bytes());
247            }
248            proc_name.push(0);
249            let proc_name = PCSTR::from_raw(proc_name.as_ptr());
250        }
251    };
252
253    let call_export = if dll.as_str() == "olmapi32" {
254        quote! {
255            static EXPORT: OnceLock<Option<#func_type>> = OnceLock::new();
256
257            use ::windows::Win32::{Foundation::E_FAIL, System::LibraryLoader::*};
258
259            match (EXPORT.get_or_init(|| {
260                unsafe {
261                    let module = crate::get_mapi_module();
262                    GetProcAddress(module, proc_name).map(|export| unsafe { mem::transmute(export) })
263                }
264            })) {
265                Some(export) => {
266                    unsafe {
267                        export(#forward_args)
268                    }
269                },
270                None => E_FAIL
271            }
272        }
273    } else {
274        let missing_export =
275            LitStr::new(&format!("{name} is not exported from {dll}"), name.span());
276
277        quote! {
278            static EXPORT: OnceLock<#func_type> = OnceLock::new();
279
280            (EXPORT.get_or_init(|| {
281                use ::windows::Win32::System::LibraryLoader::*;
282
283                unsafe {
284                    let module = crate::get_mapi_module();
285                    mem::transmute(GetProcAddress(module, proc_name).expect(#missing_export))
286                }
287            }))(#forward_args)
288        }
289    };
290
291    let output = quote! {
292        unsafe fn #name(#inputs) #output {
293            use std::{mem, sync::OnceLock};
294            use ::windows_core::*;
295
296            #build_proc_name
297
298            type #func_type = unsafe extern #abi fn(#inputs) #output;
299
300            #call_export
301        }
302    };
303
304    output.into()
305}