/* * Implementation of the Microsoft Installer (msi.dll) * * Copyright 2005 Mike McCormack for CodeWeavers * Copyright 2005 Aric Stewart for CodeWeavers * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ #include #include #define COBJMACROS #include "windef.h" #include "winbase.h" #include "winerror.h" #include "wine/debug.h" #include "msi.h" #include "winnls.h" #include "objbase.h" #include "oleauto.h" #include "msipriv.h" #include "msiserver.h" #include "wine/unicode.h" WINE_DEFAULT_DEBUG_CHANNEL(msi); /* types arranged by precedence */ #define FORMAT_NULL 0x0001 #define FORMAT_LITERAL 0x0002 #define FORMAT_NUMBER 0x0004 #define FORMAT_LBRACK 0x0010 #define FORMAT_LBRACE 0x0020 #define FORMAT_RBRACK 0x0011 #define FORMAT_RBRACE 0x0021 #define FORMAT_ESCAPE 0x0040 #define FORMAT_PROPNULL 0x0080 #define FORMAT_ERROR 0x1000 #define FORMAT_FAIL 0x2000 #define left_type(x) (x & 0xF0) typedef struct _tagFORMAT { MSIPACKAGE *package; MSIRECORD *record; LPWSTR deformatted; int len; int n; BOOL propfailed; BOOL groupfailed; int groups; } FORMAT; typedef struct _tagFORMSTR { struct list entry; int n; int len; int type; BOOL propfound; BOOL nonprop; } FORMSTR; typedef struct _tagSTACK { struct list items; } STACK; static STACK *create_stack(void) { STACK *stack = msi_alloc(sizeof(STACK)); list_init(&stack->items); return stack; } static void free_stack(STACK *stack) { while (!list_empty(&stack->items)) { FORMSTR *str = LIST_ENTRY(list_head(&stack->items), FORMSTR, entry); list_remove(&str->entry); msi_free(str); } msi_free(stack); } static void stack_push(STACK *stack, FORMSTR *str) { list_add_head(&stack->items, &str->entry); } static FORMSTR *stack_pop(STACK *stack) { FORMSTR *ret; if (list_empty(&stack->items)) return NULL; ret = LIST_ENTRY(list_head(&stack->items), FORMSTR, entry); list_remove(&ret->entry); return ret; } static FORMSTR *stack_find(STACK *stack, int type) { FORMSTR *str; LIST_FOR_EACH_ENTRY(str, &stack->items, FORMSTR, entry) { if (str->type == type) return str; } return NULL; } static FORMSTR *stack_peek(STACK *stack) { return LIST_ENTRY(list_head(&stack->items), FORMSTR, entry); } static LPCWSTR get_formstr_data(FORMAT *format, FORMSTR *str) { return &format->deformatted[str->n]; } static LPWSTR dup_formstr(FORMAT *format, FORMSTR *str) { LPWSTR val; LPCWSTR data; if (str->len == 0) return NULL; val = msi_alloc((str->len + 1) * sizeof(WCHAR)); data = get_formstr_data(format, str); lstrcpynW(val, data, str->len + 1); return val; } static LPWSTR deformat_index(FORMAT *format, FORMSTR *str) { LPWSTR val, ret; val = msi_alloc((str->len + 1) * sizeof(WCHAR)); lstrcpynW(val, get_formstr_data(format, str), str->len + 1); ret = msi_dup_record_field(format->record, atoiW(val)); msi_free(val); return ret; } static LPWSTR deformat_property(FORMAT *format, FORMSTR *str) { LPWSTR val, ret; val = msi_alloc((str->len + 1) * sizeof(WCHAR)); lstrcpynW(val, get_formstr_data(format, str), str->len + 1); ret = msi_dup_property(format->package->db, val); msi_free(val); return ret; } static LPWSTR deformat_component(FORMAT *format, FORMSTR *str) { LPWSTR key, ret = NULL; MSICOMPONENT *comp; key = msi_alloc((str->len + 1) * sizeof(WCHAR)); lstrcpynW(key, get_formstr_data(format, str), str->len + 1); comp = msi_get_loaded_component(format->package, key); if (!comp) goto done; if (comp->Action == INSTALLSTATE_SOURCE) ret = msi_resolve_source_folder( format->package, comp->Directory, NULL ); else ret = strdupW( msi_get_target_folder( format->package, comp->Directory ) ); done: msi_free(key); return ret; } static LPWSTR deformat_file(FORMAT *format, FORMSTR *str, BOOL shortname) { LPWSTR key, ret = NULL; MSIFILE *file; DWORD size; key = msi_alloc((str->len + 1) * sizeof(WCHAR)); lstrcpynW(key, get_formstr_data(format, str), str->len + 1); file = msi_get_loaded_file(format->package, key); if (!file) goto done; if (!shortname) { ret = strdupW(file->TargetPath); goto done; } size = GetShortPathNameW(file->TargetPath, NULL, 0); if (size <= 0) { ret = strdupW(file->TargetPath); goto done; } size++; ret = msi_alloc(size * sizeof(WCHAR)); GetShortPathNameW(file->TargetPath, ret, size); done: msi_free(key); return ret; } static LPWSTR deformat_environment(FORMAT *format, FORMSTR *str) { LPWSTR key, ret = NULL; DWORD sz; key = msi_alloc((str->len + 1) * sizeof(WCHAR)); lstrcpynW(key, get_formstr_data(format, str), str->len + 1); sz = GetEnvironmentVariableW(key, NULL ,0); if (sz <= 0) goto done; sz++; ret = msi_alloc(sz * sizeof(WCHAR)); GetEnvironmentVariableW(key, ret, sz); done: msi_free(key); return ret; } static LPWSTR deformat_literal(FORMAT *format, FORMSTR *str, BOOL *propfound, BOOL *nonprop, int *type) { LPCWSTR data = get_formstr_data(format, str); LPWSTR replaced = NULL; char ch = data[0]; if (ch == '\\') { str->n++; if (str->len == 1) { str->len = 0; replaced = NULL; } else { str->len = 1; replaced = dup_formstr(format, str); } } else if (ch == '~') { if (str->len != 1) replaced = NULL; else { replaced = msi_alloc(sizeof(WCHAR)); *replaced = '\0'; } } else if (ch == '%' || ch == '#' || ch == '!' || ch == '$') { str->n++; str->len--; switch (ch) { case '%': replaced = deformat_environment(format, str); break; case '#': replaced = deformat_file(format, str, FALSE); break; case '!': replaced = deformat_file(format, str, TRUE); break; case '$': replaced = deformat_component(format, str); break; } *type = FORMAT_LITERAL; } else { replaced = deformat_property(format, str); *type = FORMAT_LITERAL; if (replaced) *propfound = TRUE; else format->propfailed = TRUE; } return replaced; } static LPWSTR build_default_format(const MSIRECORD* record) { int i; int count; LPWSTR rc, buf; static const WCHAR fmt[] = {'%','i',':',' ','%','s',' ',0}; static const WCHAR fmt_null[] = {'%','i',':',' ',' ',0}; static const WCHAR fmt_index[] = {'%','i',0}; LPCWSTR str; WCHAR index[10]; DWORD size, max_len, len; count = MSI_RecordGetFieldCount(record); max_len = MAX_PATH; buf = msi_alloc((max_len + 1) * sizeof(WCHAR)); rc = NULL; size = 1; for (i = 1; i <= count; i++) { sprintfW(index, fmt_index, i); str = MSI_RecordGetString(record, i); len = (str) ? lstrlenW(str) : 0; len += (sizeof(fmt_null)/sizeof(fmt_null[0]) - 3) + lstrlenW(index); size += len; if (len > max_len) { max_len = len; buf = msi_realloc(buf, (max_len + 1) * sizeof(WCHAR)); if (!buf) return NULL; } if (str) sprintfW(buf, fmt, i, str); else sprintfW(buf, fmt_null, i); if (!rc) { rc = msi_alloc(size * sizeof(WCHAR)); lstrcpyW(rc, buf); } else { rc = msi_realloc(rc, size * sizeof(WCHAR)); lstrcatW(rc, buf); } } msi_free(buf); return rc; } static BOOL format_is_number(WCHAR x) { return ((x >= '0') && (x <= '9')); } static BOOL format_str_is_number(LPWSTR str) { LPWSTR ptr; for (ptr = str; *ptr; ptr++) if (!format_is_number(*ptr)) return FALSE; return TRUE; } static BOOL format_is_alpha(WCHAR x) { return (!format_is_number(x) && x != '\0' && x != '[' && x != ']' && x != '{' && x != '}'); } static BOOL format_is_literal(WCHAR x) { return (format_is_alpha(x) || format_is_number(x)); } static int format_lex(FORMAT *format, FORMSTR **out) { int type, len = 1; FORMSTR *str; LPCWSTR data; WCHAR ch; *out = NULL; if (!format->deformatted) return FORMAT_NULL; *out = msi_alloc_zero(sizeof(FORMSTR)); if (!*out) return FORMAT_FAIL; str = *out; str->n = format->n; str->len = 1; data = get_formstr_data(format, str); ch = data[0]; switch (ch) { case '{': type = FORMAT_LBRACE; break; case '}': type = FORMAT_RBRACE; break; case '[': type = FORMAT_LBRACK; break; case ']': type = FORMAT_RBRACK; break; case '~': type = FORMAT_PROPNULL; break; case '\0': type = FORMAT_NULL; break; default: type = 0; } if (type) { str->type = type; format->n++; return type; } if (ch == '\\') { while (data[len] && data[len] != ']') len++; type = FORMAT_ESCAPE; } else if (format_is_alpha(ch)) { while (format_is_literal(data[len])) len++; type = FORMAT_LITERAL; } else if (format_is_number(ch)) { while (format_is_number(data[len])) len++; type = FORMAT_NUMBER; if (data[len] != ']') { while (format_is_literal(data[len])) len++; type = FORMAT_LITERAL; } } else { ERR("Got unknown character %c(%x)\n", ch, ch); return FORMAT_ERROR; } format->n += len; str->len = len; str->type = type; return type; } static FORMSTR *format_replace(FORMAT *format, BOOL propfound, BOOL nonprop, int oldsize, int type, LPWSTR replace) { FORMSTR *ret; LPWSTR str, ptr; DWORD size = 0; int n; if (replace) { if (!*replace) size = 1; else size = lstrlenW(replace); } size -= oldsize; size = format->len + size + 1; if (size <= 1) { msi_free(format->deformatted); format->deformatted = NULL; format->len = 0; return NULL; } str = msi_alloc(size * sizeof(WCHAR)); if (!str) return NULL; str[0] = '\0'; memcpy(str, format->deformatted, format->n * sizeof(WCHAR)); n = format->n; if (replace) { if (!*replace) { str[n] = '\0'; n++; } else { lstrcpyW(&str[n], replace); n += lstrlenW(replace); } } ptr = &format->deformatted[format->n + oldsize]; memcpy(&str[n], ptr, (lstrlenW(ptr) + 1) * sizeof(WCHAR)); msi_free(format->deformatted); format->deformatted = str; format->len = size - 1; /* don't reformat the NULL */ if (replace && !*replace) format->n++; if (!replace) return NULL; ret = msi_alloc_zero(sizeof(FORMSTR)); if (!ret) return NULL; ret->len = lstrlenW(replace); ret->type = type; ret->n = format->n; ret->propfound = propfound; ret->nonprop = nonprop; return ret; } static LPWSTR replace_stack_group(FORMAT *format, STACK *values, BOOL *propfound, BOOL *nonprop, int *oldsize, int *type) { LPWSTR replaced = NULL; FORMSTR *content; FORMSTR *node; int n; *nonprop = FALSE; *propfound = FALSE; node = stack_pop(values); n = node->n; *oldsize = node->len; msi_free(node); while ((node = stack_pop(values))) { *oldsize += node->len; if (node->nonprop) *nonprop = TRUE; if (node->propfound) *propfound = TRUE; msi_free(node); } content = msi_alloc_zero(sizeof(FORMSTR)); content->n = n; content->len = *oldsize; content->type = FORMAT_LITERAL; if (!format->groupfailed && (*oldsize == 2 || (format->propfailed && !*nonprop))) { msi_free(content); return NULL; } else if (format->deformatted[content->n + 1] == '{' && format->deformatted[content->n + content->len - 2] == '}') { format->groupfailed = FALSE; content->len = 0; } else if (*propfound && !*nonprop && !format->groupfailed && format->groups == 0) { content->n++; content->len -= 2; } else { if (format->groups != 0) format->groupfailed = TRUE; *nonprop = TRUE; } replaced = dup_formstr(format, content); *type = content->type; msi_free(content); if (format->groups == 0) format->propfailed = FALSE; return replaced; } static LPWSTR replace_stack_prop(FORMAT *format, STACK *values, BOOL *propfound, BOOL *nonprop, int *oldsize, int *type) { LPWSTR replaced = NULL; FORMSTR *content; FORMSTR *node; int n; *propfound = FALSE; *nonprop = FALSE; node = stack_pop(values); n = node->n; *oldsize = node->len; *type = stack_peek(values)->type; msi_free(node); while ((node = stack_pop(values))) { *oldsize += node->len; if (*type != FORMAT_ESCAPE && stack_peek(values) && node->type != *type) *type = FORMAT_LITERAL; msi_free(node); } content = msi_alloc_zero(sizeof(FORMSTR)); content->n = n + 1; content->len = *oldsize - 2; content->type = *type; if (*type == FORMAT_NUMBER) { replaced = deformat_index(format, content); if (replaced) *propfound = TRUE; else format->propfailed = TRUE; if (replaced) *type = format_str_is_number(replaced) ? FORMAT_NUMBER : FORMAT_LITERAL; } else if (format->package) { replaced = deformat_literal(format, content, propfound, nonprop, type); } else { *nonprop = TRUE; content->n--; content->len += 2; replaced = dup_formstr(format, content); } msi_free(content); return replaced; } static UINT replace_stack(FORMAT *format, STACK *stack, STACK *values) { LPWSTR replaced = NULL; FORMSTR *beg; FORMSTR *top; FORMSTR *node; BOOL propfound = FALSE; BOOL nonprop = FALSE; BOOL group = FALSE; int oldsize = 0; int type, n; node = stack_peek(values); type = node->type; n = node->n; if (type == FORMAT_LBRACK) replaced = replace_stack_prop(format, values, &propfound, &nonprop, &oldsize, &type); else if (type == FORMAT_LBRACE) { replaced = replace_stack_group(format, values, &propfound, &nonprop, &oldsize, &type); group = TRUE; } format->n = n; beg = format_replace(format, propfound, nonprop, oldsize, type, replaced); if (!beg) return ERROR_SUCCESS; msi_free(replaced); format->n = beg->n + beg->len; top = stack_peek(stack); if (top) { type = top->type; if ((type == FORMAT_LITERAL || type == FORMAT_NUMBER) && type == beg->type) { top->len += beg->len; if (group) top->nonprop = FALSE; if (type == FORMAT_LITERAL) top->nonprop = beg->nonprop; if (beg->propfound) top->propfound = TRUE; msi_free(beg); return ERROR_SUCCESS; } } stack_push(stack, beg); return ERROR_SUCCESS; } static BOOL verify_format(LPWSTR data) { int count = 0; while (*data) { if (*data == '[' && *(data - 1) != '\\') count++; else if (*data == ']') count--; data++; } if (count > 0) return FALSE; return TRUE; } static DWORD deformat_string_internal(MSIPACKAGE *package, LPCWSTR ptr, WCHAR** data, DWORD *len, MSIRECORD* record, INT* failcount) { FORMAT format; FORMSTR *str = NULL; STACK *stack, *temp; FORMSTR *node; int type; if (!ptr) { *data = NULL; *len = 0; return ERROR_SUCCESS; } *data = strdupW(ptr); *len = lstrlenW(ptr); ZeroMemory(&format, sizeof(FORMAT)); format.package = package; format.record = record; format.deformatted = *data; format.len = *len; if (!verify_format(*data)) return ERROR_SUCCESS; stack = create_stack(); temp = create_stack(); while ((type = format_lex(&format, &str)) != FORMAT_NULL) { if (type == FORMAT_LBRACK || type == FORMAT_LBRACE || type == FORMAT_LITERAL || type == FORMAT_NUMBER || type == FORMAT_ESCAPE || type == FORMAT_PROPNULL) { if (type == FORMAT_LBRACE) { format.propfailed = FALSE; format.groups++; } else if (type == FORMAT_ESCAPE && !stack_find(stack, FORMAT_LBRACK)) { format.n -= str->len - 1; str->len = 1; } stack_push(stack, str); } else if (type == FORMAT_RBRACK || type == FORMAT_RBRACE) { if (type == FORMAT_RBRACE) format.groups--; stack_push(stack, str); if (stack_find(stack, left_type(type))) { do { node = stack_pop(stack); stack_push(temp, node); } while (node->type != left_type(type)); replace_stack(&format, stack, temp); } } } *data = format.deformatted; *len = format.len; msi_free(str); free_stack(stack); free_stack(temp); return ERROR_SUCCESS; } UINT MSI_FormatRecordW( MSIPACKAGE* package, MSIRECORD* record, LPWSTR buffer, LPDWORD size ) { LPWSTR deformated; LPWSTR rec; DWORD len; UINT rc = ERROR_INVALID_PARAMETER; TRACE("%p %p %p %p\n", package, record, buffer, size); rec = msi_dup_record_field(record,0); if (!rec) rec = build_default_format(record); TRACE("(%s)\n",debugstr_w(rec)); deformat_string_internal(package, rec, &deformated, &len, record, NULL); if (buffer) { if (*size>len) { memcpy(buffer,deformated,len*sizeof(WCHAR)); rc = ERROR_SUCCESS; buffer[len] = 0; } else { if (*size > 0) { memcpy(buffer,deformated,(*size)*sizeof(WCHAR)); buffer[(*size)-1] = 0; } rc = ERROR_MORE_DATA; } } else rc = ERROR_SUCCESS; *size = len; msi_free(rec); msi_free(deformated); return rc; } UINT WINAPI MsiFormatRecordW( MSIHANDLE hInstall, MSIHANDLE hRecord, LPWSTR szResult, LPDWORD sz ) { UINT r = ERROR_INVALID_HANDLE; MSIPACKAGE *package; MSIRECORD *record; TRACE("%d %d %p %p\n", hInstall, hRecord, szResult, sz); package = msihandle2msiinfo( hInstall, MSIHANDLETYPE_PACKAGE ); if (!package) { HRESULT hr; IWineMsiRemotePackage *remote_package; BSTR value = NULL; awstring wstr; remote_package = (IWineMsiRemotePackage *)msi_get_remote( hInstall ); if (remote_package) { hr = IWineMsiRemotePackage_FormatRecord( remote_package, hRecord, &value ); if (FAILED(hr)) goto done; wstr.unicode = TRUE; wstr.str.w = szResult; r = msi_strcpy_to_awstring( value, &wstr, sz ); done: IWineMsiRemotePackage_Release( remote_package ); SysFreeString( value ); if (FAILED(hr)) { if (HRESULT_FACILITY(hr) == FACILITY_WIN32) return HRESULT_CODE(hr); return ERROR_FUNCTION_FAILED; } return r; } } record = msihandle2msiinfo( hRecord, MSIHANDLETYPE_RECORD ); if (!record) return ERROR_INVALID_HANDLE; if (!sz) { msiobj_release( &record->hdr ); if (szResult) return ERROR_INVALID_PARAMETER; else return ERROR_SUCCESS; } r = MSI_FormatRecordW( package, record, szResult, sz ); msiobj_release( &record->hdr ); if (package) msiobj_release( &package->hdr ); return r; } UINT WINAPI MsiFormatRecordA( MSIHANDLE hInstall, MSIHANDLE hRecord, LPSTR szResult, LPDWORD sz ) { UINT r; DWORD len, save; LPWSTR value; TRACE("%d %d %p %p\n", hInstall, hRecord, szResult, sz); if (!hRecord) return ERROR_INVALID_HANDLE; if (!sz) { if (szResult) return ERROR_INVALID_PARAMETER; else return ERROR_SUCCESS; } r = MsiFormatRecordW( hInstall, hRecord, NULL, &len ); if (r != ERROR_SUCCESS) return r; value = msi_alloc(++len * sizeof(WCHAR)); if (!value) return ERROR_OUTOFMEMORY; r = MsiFormatRecordW( hInstall, hRecord, value, &len ); if (r != ERROR_SUCCESS) goto done; save = len + 1; len = WideCharToMultiByte(CP_ACP, 0, value, len + 1, NULL, 0, NULL, NULL); WideCharToMultiByte(CP_ACP, 0, value, len, szResult, *sz, NULL, NULL); if (szResult && len > *sz) { if (*sz) szResult[*sz - 1] = '\0'; r = ERROR_MORE_DATA; } *sz = save - 1; done: msi_free(value); return r; } /* wrapper to resist a need for a full rewrite right now */ DWORD deformat_string( MSIPACKAGE *package, const WCHAR *ptr, WCHAR **data ) { if (ptr) { DWORD size = 0; MSIRECORD *rec = MSI_CreateRecord( 1 ); MSI_RecordSetStringW( rec, 0, ptr ); MSI_FormatRecordW( package, rec, NULL, &size ); size++; *data = msi_alloc( size * sizeof(WCHAR) ); if (size > 1) MSI_FormatRecordW( package, rec, *data, &size ); else *data[0] = 0; msiobj_release( &rec->hdr ); return size * sizeof(WCHAR); } *data = NULL; return 0; }