wine/dlls/msi/format.c
Rob Shearman 4a75b8bf09 msi: Fix the value parameter of IWineMsiRemotePackage::FormatRecord to have the right level of indirection for an [out] parameter.
Remove the redundant size parameter and simplify the client code such 
that the remote function is only called once, with the value being 
automatically allocated. Add corresponding code on the server side to 
automatically allocate said value.
2008-04-17 11:39:37 +02:00

1022 lines
23 KiB
C

/*
* 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 <stdarg.h>
#include <stdio.h>
#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, val);
msi_free(val);
return ret;
}
static LPWSTR deformat_component(FORMAT *format, FORMSTR *str)
{
LPWSTR key, ret = NULL;
MSICOMPONENT *comp;
BOOL source;
key = msi_alloc((str->len + 1) * sizeof(WCHAR));
lstrcpynW(key, get_formstr_data(format, str), str->len + 1);
comp = get_loaded_component(format->package, key);
if (!comp)
goto done;
source = (comp->Action == INSTALLSTATE_SOURCE) ? TRUE : FALSE;
ret = resolve_folder(format->package, comp->Directory, source, FALSE, TRUE, NULL);
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 = 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;
stack = create_stack();
temp = create_stack();
if (!verify_format(*data))
return ERROR_SUCCESS;
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 %i\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("%ld %ld %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("%ld %ld %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;
}