| /* | |
| * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
| */ | |
| /* | |
| http://msdn.microsoft.com/library/default.asp?url=/library/en-us/msi/setup/msiformatrecord.asp | |
| */ | |
| #include <stdarg.h> | |
| #include <stdio.h> | |
| #define COBJMACROS | |
| #include "windef.h" | |
| #include "winbase.h" | |
| #include "winerror.h" | |
| #include "winreg.h" | |
| #include "wine/debug.h" | |
| #include "fdi.h" | |
| #include "msi.h" | |
| #include "msiquery.h" | |
| #include "msvcrt/fcntl.h" | |
| #include "objbase.h" | |
| #include "objidl.h" | |
| #include "msipriv.h" | |
| #include "winnls.h" | |
| #include "winuser.h" | |
| #include "shlobj.h" | |
| #include "wine/unicode.h" | |
| #include "ver.h" | |
| #include "action.h" | |
| WINE_DEFAULT_DEBUG_CHANNEL(msi); | |
| LPWSTR build_default_format(MSIRECORD* record) | |
| { | |
| int i; | |
| int count; | |
| LPWSTR rc; | |
| static const WCHAR fmt[] = {'%','i',':',' ','[','%','i',']',' ',0}; | |
| WCHAR buf[11]; | |
| count = MSI_RecordGetFieldCount(record); | |
| rc = HeapAlloc(GetProcessHeap(),0,(11*count)*sizeof(WCHAR)); | |
| rc[0] = 0; | |
| for (i = 1; i <= count; i++) | |
| { | |
| sprintfW(buf,fmt,i,i); | |
| strcatW(rc,buf); | |
| } | |
| return rc; | |
| } | |
| static const WCHAR* scanW(LPCWSTR buf, WCHAR token, DWORD len) | |
| { | |
| DWORD i; | |
| for (i = 0; i < len; i++) | |
| if (buf[i] == token) | |
| return &buf[i]; | |
| return NULL; | |
| } | |
| /* break out helper functions for deformating */ | |
| static LPWSTR deformat_component(MSIPACKAGE* package, LPCWSTR key, DWORD* sz) | |
| { | |
| LPWSTR value = NULL; | |
| INT index; | |
| *sz = 0; | |
| if (!package) | |
| return NULL; | |
| ERR("POORLY HANDLED DEFORMAT.. [$componentkey] \n"); | |
| index = get_loaded_component(package,key); | |
| if (index >= 0) | |
| { | |
| value = resolve_folder(package, package->components[index].Directory, | |
| FALSE, FALSE, NULL); | |
| *sz = (strlenW(value)) * sizeof(WCHAR); | |
| } | |
| return value; | |
| } | |
| static LPWSTR deformat_file(MSIPACKAGE* package, LPCWSTR key, DWORD* sz) | |
| { | |
| LPWSTR value = NULL; | |
| INT index; | |
| *sz = 0; | |
| if (!package) | |
| return NULL; | |
| index = get_loaded_file(package,key); | |
| if (index >=0) | |
| { | |
| value = strdupW(package->files[index].TargetPath); | |
| *sz = (strlenW(value)) * sizeof(WCHAR); | |
| } | |
| return value; | |
| } | |
| static LPWSTR deformat_environment(MSIPACKAGE* package, LPCWSTR key, | |
| DWORD* chunk) | |
| { | |
| LPWSTR value = NULL; | |
| DWORD sz; | |
| sz = GetEnvironmentVariableW(key,NULL,0); | |
| if (sz > 0) | |
| { | |
| sz++; | |
| value = HeapAlloc(GetProcessHeap(),0,sz * sizeof(WCHAR)); | |
| GetEnvironmentVariableW(&key[1],value,sz); | |
| *chunk = (strlenW(value)) * sizeof(WCHAR); | |
| } | |
| else | |
| { | |
| ERR("Unknown environment variable\n"); | |
| *chunk = 0; | |
| value = NULL; | |
| } | |
| return value; | |
| } | |
| static LPWSTR deformat_NULL(DWORD* chunk) | |
| { | |
| LPWSTR value; | |
| value = HeapAlloc(GetProcessHeap(),0,sizeof(WCHAR)*2); | |
| value[0] = 0; | |
| *chunk = sizeof(WCHAR); | |
| return value; | |
| } | |
| static LPWSTR deformat_escape(LPCWSTR key, DWORD* chunk) | |
| { | |
| LPWSTR value; | |
| value = HeapAlloc(GetProcessHeap(),0,sizeof(WCHAR)*2); | |
| value[0] = key[0]; | |
| *chunk = sizeof(WCHAR); | |
| return value; | |
| } | |
| static BOOL is_key_number(LPCWSTR key) | |
| { | |
| INT index = 0; | |
| if (key[0] == 0) | |
| return FALSE; | |
| while (isdigitW(key[index])) index++; | |
| if (key[index] == 0) | |
| return TRUE; | |
| else | |
| return FALSE; | |
| } | |
| static LPWSTR deformat_index(MSIRECORD* record, LPCWSTR key, DWORD* chunk ) | |
| { | |
| INT index; | |
| LPWSTR value; | |
| index = atoiW(key); | |
| TRACE("record index %i\n",index); | |
| value = load_dynamic_stringW(record,index); | |
| if (value) | |
| *chunk = strlenW(value) * sizeof(WCHAR); | |
| else | |
| { | |
| value = NULL; | |
| *chunk = 0; | |
| } | |
| return value; | |
| } | |
| static LPWSTR deformat_property(MSIPACKAGE* package, LPCWSTR key, DWORD* chunk) | |
| { | |
| UINT rc; | |
| LPWSTR value; | |
| if (!package) | |
| return NULL; | |
| value = load_dynamic_property(package,key, &rc); | |
| if (rc == ERROR_SUCCESS) | |
| *chunk = (strlenW(value)) * sizeof(WCHAR); | |
| return value; | |
| } | |
| static BOOL find_next_outermost_key(LPCWSTR source, DWORD len_remaining, | |
| LPWSTR *key, LPCWSTR *mark, LPCWSTR* mark2, | |
| BOOL *nested) | |
| { | |
| INT count = 0; | |
| INT total_count = 0; | |
| int i; | |
| *mark = scanW(source,'[',len_remaining); | |
| if (!*mark) | |
| return FALSE; | |
| count = 1; | |
| total_count = 1; | |
| *nested = FALSE; | |
| for (i = 1; (*mark - source) + i < len_remaining && count > 0; i++) | |
| { | |
| if ((*mark)[i] == '[' && (*mark)[i-1] != '\\') | |
| { | |
| count ++; | |
| total_count ++; | |
| *nested = TRUE; | |
| } | |
| else if ((*mark)[i] == ']' && (*mark)[i-1] != '\\') | |
| { | |
| count --; | |
| } | |
| } | |
| if (count > 0) | |
| return FALSE; | |
| *mark2 = &(*mark)[i-1]; | |
| i = *mark2 - *mark; | |
| *key = HeapAlloc(GetProcessHeap(),0,i*sizeof(WCHAR)); | |
| /* do not have the [] in the key */ | |
| i -= 1; | |
| strncpyW(*key,&(*mark)[1],i); | |
| (*key)[i] = 0; | |
| TRACE("Found Key %s\n",debugstr_w(*key)); | |
| return TRUE; | |
| } | |
| /* | |
| * len is in WCHARs | |
| * return is also in WCHARs | |
| */ | |
| static DWORD deformat_string_internal(MSIPACKAGE *package, LPCWSTR ptr, | |
| WCHAR** data, DWORD len, MSIRECORD* record) | |
| { | |
| LPCWSTR mark = NULL; | |
| LPCWSTR mark2 = NULL; | |
| DWORD size=0; | |
| DWORD chunk=0; | |
| LPWSTR key; | |
| LPWSTR value = NULL; | |
| DWORD sz; | |
| LPBYTE newdata = NULL; | |
| const WCHAR* progress = NULL; | |
| BOOL nested; | |
| if (ptr==NULL) | |
| { | |
| TRACE("Deformatting NULL string\n"); | |
| *data = NULL; | |
| return 0; | |
| } | |
| TRACE("Starting with %s\n",debugstr_w(ptr)); | |
| /* scan for special characters... fast exit */ | |
| if (!scanW(ptr,'[',len) || (scanW(ptr,'[',len) && !scanW(ptr,']',len))) | |
| { | |
| /* not formatted */ | |
| *data = HeapAlloc(GetProcessHeap(),0,(len*sizeof(WCHAR))); | |
| memcpy(*data,ptr,len*sizeof(WCHAR)); | |
| TRACE("Returning %s\n",debugstr_w(*data)); | |
| return len; | |
| } | |
| progress = ptr; | |
| while (progress - ptr < len) | |
| { | |
| /* formatted string located */ | |
| if (!find_next_outermost_key(progress, len - (progress - ptr), &key, | |
| &mark, &mark2, &nested)) | |
| { | |
| LPBYTE nd2; | |
| TRACE("after value %s .. %s\n",debugstr_w((LPWSTR)newdata), | |
| debugstr_w(mark)); | |
| chunk = (len - (progress - ptr)) * sizeof(WCHAR); | |
| TRACE("after chunk is %li + %li\n",size,chunk); | |
| if (size) | |
| nd2 = HeapReAlloc(GetProcessHeap(),0,newdata,(size+chunk)); | |
| else | |
| nd2 = HeapAlloc(GetProcessHeap(),0,chunk); | |
| newdata = nd2; | |
| memcpy(&newdata[size],progress,chunk); | |
| size+=chunk; | |
| break; | |
| } | |
| if (mark != progress) | |
| { | |
| LPBYTE tgt; | |
| DWORD old_size = size; | |
| INT cnt = (mark - progress); | |
| TRACE("%i (%i) characters before marker\n",cnt,(mark-progress)); | |
| size += cnt * sizeof(WCHAR); | |
| if (!old_size) | |
| tgt = HeapAlloc(GetProcessHeap(),0,size); | |
| else | |
| tgt = HeapReAlloc(GetProcessHeap(),0,newdata,size); | |
| newdata = tgt; | |
| memcpy(&newdata[old_size],progress,(cnt * sizeof(WCHAR))); | |
| } | |
| progress = mark; | |
| if (nested) | |
| { | |
| TRACE("Nested key... %s\n",debugstr_w(key)); | |
| deformat_string_internal(package, key, &value, strlenW(key)+1, | |
| record); | |
| HeapFree(GetProcessHeap(),0,key); | |
| key = value; | |
| } | |
| TRACE("Current %s .. %s\n",debugstr_w((LPWSTR)newdata),debugstr_w(key)); | |
| if (!package) | |
| { | |
| /* only deformat number indexs */ | |
| if (is_key_number(key)) | |
| value = deformat_index(record,key,&chunk); | |
| else | |
| { | |
| chunk = (strlenW(key) + 2)*sizeof(WCHAR); | |
| value = HeapAlloc(GetProcessHeap(),0,chunk); | |
| value[0] = '['; | |
| memcpy(&value[1],key,strlenW(key)*sizeof(WCHAR)); | |
| value[strlenW(key)+1] = ']'; | |
| } | |
| } | |
| else | |
| { | |
| sz = 0; | |
| switch (key[0]) | |
| { | |
| case '~': | |
| value = deformat_NULL(&chunk); | |
| break; | |
| case '$': | |
| value = deformat_component(package,&key[1],&chunk); | |
| break; | |
| case '#': | |
| case '!': /* should be short path */ | |
| value = deformat_file(package,&key[1], &chunk); | |
| break; | |
| case '\\': | |
| value = deformat_escape(&key[1],&chunk); | |
| break; | |
| case '%': | |
| value = deformat_environment(package,&key[1],&chunk); | |
| break; | |
| default: | |
| if (is_key_number(key)) | |
| value = deformat_index(record,key,&chunk); | |
| else | |
| value = deformat_property(package,key,&chunk); | |
| break; | |
| } | |
| } | |
| HeapFree(GetProcessHeap(),0,key); | |
| if (value!=NULL) | |
| { | |
| LPBYTE nd2; | |
| TRACE("value %s, chunk %li size %li\n",debugstr_w((LPWSTR)value), | |
| chunk, size); | |
| if (size) | |
| nd2= HeapReAlloc(GetProcessHeap(),0,newdata,(size + chunk)); | |
| else | |
| nd2= HeapAlloc(GetProcessHeap(),0,chunk); | |
| newdata = nd2; | |
| memcpy(&newdata[size],value,chunk); | |
| size+=chunk; | |
| HeapFree(GetProcessHeap(),0,value); | |
| } | |
| progress = mark2+1; | |
| } | |
| TRACE("after everything %s\n",debugstr_w((LPWSTR)newdata)); | |
| *data = (LPWSTR)newdata; | |
| return size / sizeof(WCHAR); | |
| } | |
| UINT MSI_FormatRecordW( MSIPACKAGE* package, MSIRECORD* record, LPWSTR buffer, | |
| DWORD *size ) | |
| { | |
| LPWSTR deformated; | |
| LPWSTR rec; | |
| DWORD len; | |
| UINT rc = ERROR_INVALID_PARAMETER; | |
| TRACE("%p %p %p %li\n",package, record ,buffer, *size); | |
| rec = load_dynamic_stringW(record,0); | |
| if (!rec) | |
| rec = build_default_format(record); | |
| TRACE("(%s)\n",debugstr_w(rec)); | |
| len = deformat_string_internal(package,rec,&deformated,strlenW(rec), | |
| record); | |
| 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; | |
| HeapFree(GetProcessHeap(),0,rec); | |
| HeapFree(GetProcessHeap(),0,deformated); | |
| return rc; | |
| } | |
| UINT MSI_FormatRecordA( MSIPACKAGE* package, MSIRECORD* record, LPSTR buffer, | |
| DWORD *size ) | |
| { | |
| LPWSTR deformated; | |
| LPWSTR rec; | |
| DWORD len,lenA; | |
| UINT rc = ERROR_INVALID_PARAMETER; | |
| TRACE("%p %p %p %li\n",package, record ,buffer, *size); | |
| rec = load_dynamic_stringW(record,0); | |
| if (!rec) | |
| rec = build_default_format(record); | |
| TRACE("(%s)\n",debugstr_w(rec)); | |
| len = deformat_string_internal(package,rec,&deformated,strlenW(rec), | |
| record); | |
| lenA = WideCharToMultiByte(CP_ACP,0,deformated,len,NULL,0,NULL,NULL); | |
| if (buffer) | |
| { | |
| WideCharToMultiByte(CP_ACP,0,deformated,len,buffer,*size,NULL, NULL); | |
| if (*size>lenA) | |
| { | |
| rc = ERROR_SUCCESS; | |
| buffer[lenA] = 0; | |
| } | |
| else | |
| { | |
| rc = ERROR_MORE_DATA; | |
| buffer[(*size)-1] = 0; | |
| } | |
| } | |
| else | |
| rc = ERROR_SUCCESS; | |
| *size = lenA; | |
| HeapFree(GetProcessHeap(),0,rec); | |
| HeapFree(GetProcessHeap(),0,deformated); | |
| return rc; | |
| } | |
| UINT WINAPI MsiFormatRecordW( MSIHANDLE hInstall, MSIHANDLE hRecord, | |
| LPWSTR szResult, DWORD *sz ) | |
| { | |
| UINT r = ERROR_INVALID_HANDLE; | |
| MSIPACKAGE *package; | |
| MSIRECORD *record; | |
| TRACE("%ld %ld %p %p\n", hInstall, hRecord, szResult, sz); | |
| 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; | |
| } | |
| package = msihandle2msiinfo( hInstall, MSIHANDLETYPE_PACKAGE ); | |
| 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, DWORD *sz ) | |
| { | |
| UINT r = ERROR_INVALID_HANDLE; | |
| MSIPACKAGE *package = NULL; | |
| MSIRECORD *record = NULL; | |
| TRACE("%ld %ld %p %p\n", hInstall, hRecord, szResult, sz); | |
| 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; | |
| } | |
| package = msihandle2msiinfo( hInstall, MSIHANDLETYPE_PACKAGE ); | |
| r = MSI_FormatRecordA( package, record, szResult, sz ); | |
| msiobj_release( &record->hdr ); | |
| if (package) | |
| msiobj_release( &package->hdr ); | |
| return r; | |
| } |