/* * 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 WCHAR *dup_formstr( FORMAT *format, FORMSTR *str, int *ret_len ) { WCHAR *val; if (!str->len) return NULL; if ((val = msi_alloc( (str->len + 1) * sizeof(WCHAR) ))) { memcpy( val, get_formstr_data(format, str), str->len * sizeof(WCHAR) ); val[str->len] = 0; *ret_len = str->len; } return val; } static WCHAR *deformat_index( FORMAT *format, FORMSTR *str, int *ret_len ) { WCHAR *val, *ret; DWORD len; int field; if (!(val = msi_alloc( (str->len + 1) * sizeof(WCHAR) ))) return NULL; lstrcpynW(val, get_formstr_data(format, str), str->len + 1); field = atoiW( val ); msi_free( val ); if (MSI_RecordIsNull( format->record, field ) || MSI_RecordGetStringW( format->record, field, NULL, &len )) return NULL; len++; if (!(ret = msi_alloc( len * sizeof(WCHAR) ))) return NULL; ret[0] = 0; if (MSI_RecordGetStringW( format->record, field, ret, &len )) { msi_free( ret ); return NULL; } *ret_len = len; return ret; } static WCHAR *deformat_property( FORMAT *format, FORMSTR *str, int *ret_len ) { WCHAR *prop, *ret; DWORD len = 0; UINT r; if (!(prop = msi_alloc( (str->len + 1) * sizeof(WCHAR) ))) return NULL; lstrcpynW( prop, get_formstr_data(format, str), str->len + 1 ); r = msi_get_property( format->package->db, prop, NULL, &len ); if (r != ERROR_SUCCESS && r != ERROR_MORE_DATA) { msi_free( prop ); return NULL; } len++; if ((ret = msi_alloc( len * sizeof(WCHAR) ))) msi_get_property( format->package->db, prop, ret, &len ); msi_free( prop ); *ret_len = len; return ret; } static WCHAR *deformat_component( FORMAT *format, FORMSTR *str, int *ret_len ) { WCHAR *key, *ret; MSICOMPONENT *comp; if (!(key = msi_alloc( (str->len + 1) * sizeof(WCHAR) ))) return NULL; lstrcpynW(key, get_formstr_data(format, str), str->len + 1); if (!(comp = msi_get_loaded_component( format->package, key ))) { msi_free( key ); return NULL; } 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 ) ); if (ret) *ret_len = strlenW( ret ); else *ret_len = 0; msi_free( key ); return ret; } static WCHAR *deformat_file( FORMAT *format, FORMSTR *str, BOOL shortname, int *ret_len ) { WCHAR *key, *ret = NULL; const MSIFILE *file; DWORD len = 0; if (!(key = msi_alloc( (str->len + 1) * sizeof(WCHAR) ))) return NULL; lstrcpynW(key, get_formstr_data(format, str), str->len + 1); if (!(file = msi_get_loaded_file( format->package, key ))) goto done; if (!shortname) { if ((ret = strdupW( file->TargetPath ))) len = strlenW( ret ); goto done; } if ((len = GetShortPathNameW(file->TargetPath, NULL, 0)) <= 0) { if ((ret = strdupW( file->TargetPath ))) len = strlenW( ret ); goto done; } len++; if ((ret = msi_alloc( len * sizeof(WCHAR) ))) len = GetShortPathNameW( file->TargetPath, ret, len ); done: msi_free( key ); *ret_len = len; return ret; } static WCHAR *deformat_environment( FORMAT *format, FORMSTR *str, int *ret_len ) { WCHAR *key, *ret = NULL; DWORD len; if (!(key = msi_alloc((str->len + 1) * sizeof(WCHAR)))) return NULL; lstrcpynW(key, get_formstr_data(format, str), str->len + 1); if ((len = GetEnvironmentVariableW( key, NULL, 0 ))) { len++; if ((ret = msi_alloc( len * sizeof(WCHAR) ))) *ret_len = GetEnvironmentVariableW( key, ret, len ); } msi_free( key ); return ret; } static WCHAR *deformat_literal( FORMAT *format, FORMSTR *str, BOOL *propfound, int *type, int *len ) { LPCWSTR data = get_formstr_data(format, str); WCHAR *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, len ); } } else if (ch == '~') { if (str->len != 1) replaced = NULL; else if ((replaced = msi_alloc( sizeof(WCHAR) ))) { *replaced = 0; *len = 0; } } else if (ch == '%' || ch == '#' || ch == '!' || ch == '$') { str->n++; str->len--; switch (ch) { case '%': replaced = deformat_environment( format, str, len ); break; case '#': replaced = deformat_file( format, str, FALSE, len ); break; case '!': replaced = deformat_file( format, str, TRUE, len ); break; case '$': replaced = deformat_component( format, str, len ); break; } *type = FORMAT_LITERAL; } else { replaced = deformat_property( format, str, len ); *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; WCHAR *rc, buf[26]; static const WCHAR fmt[] = {'%','i',':',' ','[','%','i',']',' ',0}; DWORD size; count = MSI_RecordGetFieldCount(record); rc = msi_alloc(1); rc[0] = 0; size = 1; for (i = 1; i <= count; i++) { sprintfW(buf, fmt, i, i); size += lstrlenW(buf); rc = msi_realloc(rc, size * sizeof(WCHAR)); lstrcatW(rc, 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, WCHAR *replace, int len ) { FORMSTR *ret; LPWSTR str, ptr; DWORD size = 0; int n; if (replace) { if (!len) size = 1; else size = len; } 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 (!len) str[n++] = 0; else { memcpy( str + n, replace, len * sizeof(WCHAR) ); n += len; str[n] = 0; } } 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 && !len) format->n++; if (!replace) return NULL; ret = msi_alloc_zero(sizeof(FORMSTR)); if (!ret) return NULL; ret->len = len; ret->type = type; ret->n = format->n; ret->propfound = propfound; ret->nonprop = nonprop; return ret; } static WCHAR *replace_stack_group( FORMAT *format, STACK *values, BOOL *propfound, BOOL *nonprop, int *oldsize, int *type, int *len ) { WCHAR *replaced; FORMSTR *content, *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, len ); *type = content->type; msi_free(content); if (format->groups == 0) format->propfailed = FALSE; return replaced; } static WCHAR *replace_stack_prop( FORMAT *format, STACK *values, BOOL *propfound, BOOL *nonprop, int *oldsize, int *type, int *len ) { WCHAR *replaced; FORMSTR *content, *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 && format->record) { replaced = deformat_index( format, content, len ); 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, type, len ); } else { *nonprop = TRUE; content->n--; content->len += 2; replaced = dup_formstr( format, content, len ); } msi_free(content); return replaced; } static UINT replace_stack(FORMAT *format, STACK *stack, STACK *values) { WCHAR *replaced = NULL; FORMSTR *beg, *top, *node; BOOL propfound = FALSE, nonprop = FALSE, group = FALSE; int type, n, len = 0, oldsize = 0; node = stack_peek(values); type = node->type; n = node->n; if (type == FORMAT_LBRACK) replaced = replace_stack_prop( format, values, &propfound, &nonprop, &oldsize, &type, &len ); else if (type == FORMAT_LBRACE) { replaced = replace_stack_group( format, values, &propfound, &nonprop, &oldsize, &type, &len ); group = TRUE; } format->n = n; beg = format_replace( format, propfound, nonprop, oldsize, type, replaced, len ); msi_free(replaced); if (!beg) return ERROR_SUCCESS; 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) { 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 ) { WCHAR *format, *deformated = NULL; UINT rc = ERROR_INVALID_PARAMETER; DWORD len; MSIRECORD *record_deformated; int field_count, i; TRACE("%p %p %p %p\n", package, record, buffer, size); dump_record(record); if (!(format = msi_dup_record_field( record, 0 ))) format = build_default_format( record ); field_count = MSI_RecordGetFieldCount(record); record_deformated = MSI_CloneRecord(record); if (!record_deformated) { rc = ERROR_OUTOFMEMORY; goto end; } MSI_RecordSetStringW(record_deformated, 0, format); for (i = 1; i <= field_count; i++) { if (MSI_RecordGetString(record, i)) { deformat_string_internal(package, MSI_RecordGetString(record, i), &deformated, &len, NULL); MSI_RecordSetStringW(record_deformated, i, deformated); msi_free(deformated); } } deformat_string_internal(package, format, &deformated, &len, record_deformated); 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; msiobj_release(&record_deformated->hdr); end: msi_free( format ); 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, SysStringLen(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 *fmt, WCHAR **data ) { DWORD len; MSIRECORD *rec; *data = NULL; if (!fmt) return 0; if (!(rec = MSI_CreateRecord( 1 ))) return 0; MSI_RecordSetStringW( rec, 0, fmt ); MSI_FormatRecordW( package, rec, NULL, &len ); if (!(*data = msi_alloc( ++len * sizeof(WCHAR) ))) { msiobj_release( &rec->hdr ); return 0; } MSI_FormatRecordW( package, rec, *data, &len ); msiobj_release( &rec->hdr ); return len; }