text: /*
   * Implementation of the Microsoft Installer (msi.dll)
   *
   * Copyright 2007 James Hawkins
   * Copyright 2015 Hans Leidekker 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>
  
  #define COBJMACROS
  
  #include "windef.h"
  #include "winbase.h"
  #include "winerror.h"
  #include "msi.h"
  #include "msiquery.h"
  #include "objbase.h"
  #include "msipriv.h"
  #include "query.h"
  
  #include "wine/debug.h"
  #include "wine/unicode.h"
  
  WINE_DEFAULT_DEBUG_CHANNEL(msidb);
  
  #define NUM_STREAMS_COLS    2
  
  typedef struct tagMSISTREAMSVIEW
  {
      MSIVIEW view;
      MSIDATABASE *db;
      UINT num_cols;
  } MSISTREAMSVIEW;
  
  static BOOL streams_resize_table( MSIDATABASE *db, UINT size )
  {
      if (!db->num_streams_allocated)
      {
          if (!(db->streams = msi_alloc_zero( size * sizeof(MSISTREAM) ))) return FALSE;
          db->num_streams_allocated = size;
          return TRUE;
      }
      while (size >= db->num_streams_allocated)
      {
          MSISTREAM *tmp;
          UINT new_size = db->num_streams_allocated * 2;
          if (!(tmp = msi_realloc_zero( db->streams, new_size * sizeof(MSISTREAM) ))) return FALSE;
          db->streams = tmp;
          db->num_streams_allocated = new_size;
      }
      return TRUE;
  }
  
  static UINT STREAMS_fetch_int(struct tagMSIVIEW *view, UINT row, UINT col, UINT *val)
  {
      MSISTREAMSVIEW *sv = (MSISTREAMSVIEW *)view;
  
      TRACE("(%p, %d, %d, %p)\n", view, row, col, val);
  
      if (col != 1)
          return ERROR_INVALID_PARAMETER;
  
      if (row >= sv->db->num_streams)
          return ERROR_NO_MORE_ITEMS;
  
      *val = sv->db->streams[row].str_index;
  
      return ERROR_SUCCESS;
  }
  
  static UINT STREAMS_fetch_stream(struct tagMSIVIEW *view, UINT row, UINT col, IStream **stm)
  {
      MSISTREAMSVIEW *sv = (MSISTREAMSVIEW *)view;
      LARGE_INTEGER pos;
      HRESULT hr;
  
      TRACE("(%p, %d, %d, %p)\n", view, row, col, stm);
  
      if (row >= sv->db->num_streams)
          return ERROR_FUNCTION_FAILED;
  
      pos.QuadPart = 0;
      hr = IStream_Seek( sv->db->streams[row].stream, pos, STREAM_SEEK_SET, NULL );
      if (FAILED( hr ))
          return ERROR_FUNCTION_FAILED;
  
      *stm = sv->db->streams[row].stream;
      IStream_AddRef( *stm );
  
      return ERROR_SUCCESS;
  }
  
  static UINT STREAMS_get_row( struct tagMSIVIEW *view, UINT row, MSIRECORD **rec )
  {
      MSISTREAMSVIEW *sv = (MSISTREAMSVIEW *)view;
  
      TRACE("%p %d %p\n", sv, row, rec);
  
      return msi_view_get_row( sv->db, view, row, rec );
  }
  
  static UINT STREAMS_set_row(struct tagMSIVIEW *view, UINT row, MSIRECORD *rec, UINT mask)
  {
      MSISTREAMSVIEW *sv = (MSISTREAMSVIEW *)view;
  
      TRACE("(%p, %d, %p, %08x)\n", view, row, rec, mask);
  
      if (row > sv->db->num_streams || mask >= (1 << sv->num_cols))
          return ERROR_INVALID_PARAMETER;
  
      if (mask & 1)
      {
          const WCHAR *name = MSI_RecordGetString( rec, 1 );
  
          if (!name) return ERROR_INVALID_PARAMETER;
          sv->db->streams[row].str_index = msi_add_string( sv->db->strings, name, -1, StringNonPersistent );
      }
      if (mask & 2)
      {
          IStream *old, *new;
          HRESULT hr;
          UINT r;
  
          r = MSI_RecordGetIStream( rec, 2, &new );
          if (r != ERROR_SUCCESS)
              return r;
  
          old = sv->db->streams[row].stream;
          hr = IStream_QueryInterface( new, &IID_IStream, (void **)&sv->db->streams[row].stream );
          if (FAILED( hr ))
          {
              IStream_Release( new );
              return ERROR_FUNCTION_FAILED;
          }
          if (old) IStream_Release( old );
      }
  
      return ERROR_SUCCESS;
  }
  
  static UINT streams_find_row( MSISTREAMSVIEW *sv, MSIRECORD *rec, UINT *row )
  {
      const WCHAR *str;
      UINT r, i, id, val;
  
      str = MSI_RecordGetString( rec, 1 );
      r = msi_string2id( sv->db->strings, str, -1, &id );
      if (r != ERROR_SUCCESS)
          return r;
  
      for (i = 0; i < sv->db->num_streams; i++)
      {
          STREAMS_fetch_int( &sv->view, i, 1, &val );
  
          if (val == id)
          {
              if (row) *row = i;
              return ERROR_SUCCESS;
          }
      }
  
      return ERROR_FUNCTION_FAILED;
  }
  
  static UINT STREAMS_insert_row(struct tagMSIVIEW *view, MSIRECORD *rec, UINT row, BOOL temporary)
  {
      MSISTREAMSVIEW *sv = (MSISTREAMSVIEW *)view;
      UINT i, r, num_rows = sv->db->num_streams + 1;
  
      TRACE("(%p, %p, %d, %d)\n", view, rec, row, temporary);
  
      r = streams_find_row( sv, rec, NULL );
      if (r == ERROR_SUCCESS)
          return ERROR_FUNCTION_FAILED;
  
      if (!streams_resize_table( sv->db, num_rows ))
          return ERROR_FUNCTION_FAILED;
  
      if (row == -1)
          row = num_rows - 1;
  
      /* shift the rows to make room for the new row */
      for (i = num_rows - 1; i > row; i--)
      {
          sv->db->streams[i] = sv->db->streams[i - 1];
      }
  
      r = STREAMS_set_row( view, row, rec, (1 << sv->num_cols) - 1 );
      if (r == ERROR_SUCCESS)
          sv->db->num_streams = num_rows;
  
      return r;
  }
  
  static UINT STREAMS_delete_row(struct tagMSIVIEW *view, UINT row)
  {
      FIXME("(%p %d): stub!\n", view, row);
      return ERROR_SUCCESS;
  }
  
  static UINT STREAMS_execute(struct tagMSIVIEW *view, MSIRECORD *record)
  {
      TRACE("(%p, %p)\n", view, record);
      return ERROR_SUCCESS;
  }
  
  static UINT STREAMS_close(struct tagMSIVIEW *view)
  {
      TRACE("(%p)\n", view);
      return ERROR_SUCCESS;
  }
  
  static UINT STREAMS_get_dimensions(struct tagMSIVIEW *view, UINT *rows, UINT *cols)
  {
      MSISTREAMSVIEW *sv = (MSISTREAMSVIEW *)view;
  
      TRACE("(%p, %p, %p)\n", view, rows, cols);
  
      if (cols) *cols = sv->num_cols;
      if (rows) *rows = sv->db->num_streams;
  
      return ERROR_SUCCESS;
  }
  
  static UINT STREAMS_get_column_info( struct tagMSIVIEW *view, UINT n, LPCWSTR *name,
                                       UINT *type, BOOL *temporary, LPCWSTR *table_name )
  {
      MSISTREAMSVIEW *sv = (MSISTREAMSVIEW *)view;
  
      TRACE("(%p, %d, %p, %p, %p, %p)\n", view, n, name, type, temporary, table_name);
  
      if (!n || n > sv->num_cols)
          return ERROR_INVALID_PARAMETER;
  
      switch (n)
      {
      case 1:
          if (name) *name = szName;
          if (type) *type = MSITYPE_STRING | MSITYPE_VALID | MAX_STREAM_NAME_LEN;
          break;
  
      case 2:
          if (name) *name = szData;
          if (type) *type = MSITYPE_STRING | MSITYPE_VALID | MSITYPE_NULLABLE;
          break;
      }
      if (table_name) *table_name = szStreams;
      if (temporary) *temporary = FALSE;
      return ERROR_SUCCESS;
  }
  
  static UINT streams_modify_update(struct tagMSIVIEW *view, MSIRECORD *rec)
  {
      MSISTREAMSVIEW *sv = (MSISTREAMSVIEW *)view;
      UINT r, row;
  
      r = streams_find_row(sv, rec, &row);
      if (r != ERROR_SUCCESS)
          return ERROR_FUNCTION_FAILED;
  
      return STREAMS_set_row( view, row, rec, (1 << sv->num_cols) - 1 );
  }
  
  static UINT streams_modify_assign(struct tagMSIVIEW *view, MSIRECORD *rec)
  {
      MSISTREAMSVIEW *sv = (MSISTREAMSVIEW *)view;
      UINT r;
  
      r = streams_find_row( sv, rec, NULL );
      if (r == ERROR_SUCCESS)
          return streams_modify_update(view, rec);
  
      return STREAMS_insert_row(view, rec, -1, FALSE);
  }
  
  static UINT STREAMS_modify(struct tagMSIVIEW *view, MSIMODIFY eModifyMode, MSIRECORD *rec, UINT row)
  {
      UINT r;
  
      TRACE("%p %d %p\n", view, eModifyMode, rec);
  
      switch (eModifyMode)
      {
      case MSIMODIFY_ASSIGN:
          r = streams_modify_assign(view, rec);
          break;
  
      case MSIMODIFY_INSERT:
          r = STREAMS_insert_row(view, rec, -1, FALSE);
          break;
  
      case MSIMODIFY_UPDATE:
          r = streams_modify_update(view, rec);
          break;
  
      case MSIMODIFY_VALIDATE_NEW:
      case MSIMODIFY_INSERT_TEMPORARY:
      case MSIMODIFY_REFRESH:
      case MSIMODIFY_REPLACE:
      case MSIMODIFY_MERGE:
      case MSIMODIFY_DELETE:
      case MSIMODIFY_VALIDATE:
      case MSIMODIFY_VALIDATE_FIELD:
      case MSIMODIFY_VALIDATE_DELETE:
          FIXME("%p %d %p - mode not implemented\n", view, eModifyMode, rec );
          r = ERROR_CALL_NOT_IMPLEMENTED;
          break;
  
      default:
          r = ERROR_INVALID_DATA;
      }
  
      return r;
  }
  
  static UINT STREAMS_delete(struct tagMSIVIEW *view)
  {
      MSISTREAMSVIEW *sv = (MSISTREAMSVIEW *)view;
  
      TRACE("(%p)\n", view);
  
      msi_free(sv);
      return ERROR_SUCCESS;
  }
  
  static UINT STREAMS_find_matching_rows(struct tagMSIVIEW *view, UINT col,
                                         UINT val, UINT *row, MSIITERHANDLE *handle)
  {
      MSISTREAMSVIEW *sv = (MSISTREAMSVIEW *)view;
      UINT index = PtrToUlong(*handle);
  
      TRACE("(%p, %d, %d, %p, %p)\n", view, col, val, row, handle);
  
      if (!col || col > sv->num_cols)
          return ERROR_INVALID_PARAMETER;
  
      while (index < sv->db->num_streams)
      {
          if (sv->db->streams[index].str_index == val)
          {
              *row = index;
              break;
          }
          index++;
      }
  
      *handle = UlongToPtr(++index);
  
      if (index > sv->db->num_streams)
          return ERROR_NO_MORE_ITEMS;
  
      return ERROR_SUCCESS;
  }
  
  static const MSIVIEWOPS streams_ops =
  {
      STREAMS_fetch_int,
      STREAMS_fetch_stream,
      STREAMS_get_row,
      STREAMS_set_row,
      STREAMS_insert_row,
      STREAMS_delete_row,
      STREAMS_execute,
      STREAMS_close,
      STREAMS_get_dimensions,
      STREAMS_get_column_info,
      STREAMS_modify,
      STREAMS_delete,
      STREAMS_find_matching_rows,
      NULL,
      NULL,
      NULL,
      NULL,
      NULL,
      NULL,
  };
  
  static HRESULT open_stream( MSIDATABASE *db, const WCHAR *name, IStream **stream )
  {
      HRESULT hr;
  
      hr = IStorage_OpenStream( db->storage, name, NULL, STGM_READ|STGM_SHARE_EXCLUSIVE, 0, stream );
      if (FAILED( hr ))
      {
          MSITRANSFORM *transform;
  
          LIST_FOR_EACH_ENTRY( transform, &db->transforms, MSITRANSFORM, entry )
          {
              hr = IStorage_OpenStream( transform->stg, name, NULL, STGM_READ|STGM_SHARE_EXCLUSIVE, 0, stream );
              if (SUCCEEDED( hr ))
                  break;
          }
      }
      return hr;
  }
  
  static MSISTREAM *find_stream( MSIDATABASE *db, const WCHAR *name )
  {
      UINT r, id, i;
  
      r = msi_string2id( db->strings, name, -1, &id );
      if (r != ERROR_SUCCESS)
          return NULL;
  
      for (i = 0; i < db->num_streams; i++)
      {
          if (db->streams[i].str_index == id) return &db->streams[i];
      }
      return NULL;
  }
  
  static UINT append_stream( MSIDATABASE *db, const WCHAR *name, IStream *stream )
  {
      WCHAR decoded[MAX_STREAM_NAME_LEN + 1];
      UINT i = db->num_streams;
  
      if (!streams_resize_table( db, db->num_streams + 1 ))
          return ERROR_OUTOFMEMORY;
  
      decode_streamname( name, decoded );
      db->streams[i].str_index = msi_add_string( db->strings, decoded, -1, StringNonPersistent );
      db->streams[i].stream = stream;
      db->num_streams++;
  
      TRACE("added %s\n", debugstr_w( decoded ));
      return ERROR_SUCCESS;
  }
  
  static UINT load_streams( MSIDATABASE *db )
  {
      IEnumSTATSTG *stgenum;
      STATSTG stat;
      HRESULT hr;
      UINT count, r = ERROR_SUCCESS;
      IStream *stream;
  
      hr = IStorage_EnumElements( db->storage, 0, NULL, 0, &stgenum );
      if (FAILED( hr ))
          return ERROR_FUNCTION_FAILED;
  
      for (;;)
      {
          count = 0;
          hr = IEnumSTATSTG_Next( stgenum, 1, &stat, &count );
          if (FAILED( hr ) || !count)
              break;
  
          /* table streams are not in the _Streams table */
          if (stat.type != STGTY_STREAM || *stat.pwcsName == 0x4840 ||
              find_stream( db, stat.pwcsName ))
          {
              CoTaskMemFree( stat.pwcsName );
              continue;
          }
          TRACE("found new stream %s\n", debugstr_w( stat.pwcsName ));
  
          hr = open_stream( db, stat.pwcsName, &stream );
          if (FAILED( hr ))
          {
              ERR("unable to open stream %08x\n", hr);
              CoTaskMemFree( stat.pwcsName );
              r = ERROR_FUNCTION_FAILED;
              break;
          }
  
          r = append_stream( db, stat.pwcsName, stream );
          CoTaskMemFree( stat.pwcsName );
          if (r != ERROR_SUCCESS)
              break;
      }
  
      TRACE("loaded %u streams\n", db->num_streams);
      IEnumSTATSTG_Release( stgenum );
      return r;
  }
  
  UINT msi_get_stream( MSIDATABASE *db, const WCHAR *name, IStream **ret )
  {
      MSISTREAM *stream;
      WCHAR *encname;
      HRESULT hr;
      UINT r;
  
      if ((stream = find_stream( db, name )))
      {
          LARGE_INTEGER pos;
  
          pos.QuadPart = 0;
          hr = IStream_Seek( stream->stream, pos, STREAM_SEEK_SET, NULL );
          if (FAILED( hr ))
              return ERROR_FUNCTION_FAILED;
  
          *ret = stream->stream;
          IStream_AddRef( *ret );
          return ERROR_SUCCESS;
      }
  
      if (!(encname = encode_streamname( FALSE, name )))
          return ERROR_OUTOFMEMORY;
  
      hr = open_stream( db, encname, ret );
      msi_free( encname );
      if (FAILED( hr ))
          return ERROR_FUNCTION_FAILED;
  
      r = append_stream( db, name, *ret );
      if (r != ERROR_SUCCESS)
      {
          IStream_Release( *ret );
          return r;
      }
  
      IStream_AddRef( *ret );
      return ERROR_SUCCESS;
  }
  
  UINT STREAMS_CreateView(MSIDATABASE *db, MSIVIEW **view)
  {
      MSISTREAMSVIEW *sv;
      UINT r;
  
      TRACE("(%p, %p)\n", db, view);
  
      r = load_streams( db );
      if (r != ERROR_SUCCESS)
          return r;
  
      if (!(sv = msi_alloc_zero( sizeof(MSISTREAMSVIEW) )))
          return ERROR_OUTOFMEMORY;
  
      sv->view.ops = &streams_ops;
      sv->num_cols = NUM_STREAMS_COLS;
      sv->db = db;
  
      *view = (MSIVIEW *)sv;
  
      return ERROR_SUCCESS;
  }
  
  static HRESULT write_stream( IStream *dst, IStream *src )
  {
      HRESULT hr;
      char buf[4096];
      STATSTG stat;
      LARGE_INTEGER pos;
      UINT count, size;
  
      hr = IStream_Stat( src, &stat, STATFLAG_NONAME );
      if (FAILED( hr )) return hr;
  
      hr = IStream_SetSize( dst, stat.cbSize );
      if (FAILED( hr )) return hr;
  
      pos.QuadPart = 0;
      hr = IStream_Seek( dst, pos, STREAM_SEEK_SET, NULL );
      if (FAILED( hr )) return hr;
  
      for (;;)
      {
          size = min( sizeof(buf), stat.cbSize.QuadPart );
          hr = IStream_Read( src, buf, size, &count );
          if (FAILED( hr ) || count != size)
          {
              WARN("failed to read stream: %08x\n", hr);
              return E_INVALIDARG;
          }
          stat.cbSize.QuadPart -= count;
          if (count)
          {
              size = count;
              hr = IStream_Write( dst, buf, size, &count );
              if (FAILED( hr ) || count != size)
              {
                  WARN("failed to write stream: %08x\n", hr);
                  return E_INVALIDARG;
              }
          }
          if (!stat.cbSize.QuadPart) break;
      }
  
      return S_OK;
  }
  
  UINT msi_commit_streams( MSIDATABASE *db )
  {
      UINT i;
      const WCHAR *name;
      WCHAR *encname;
      IStream *stream;
      HRESULT hr;
  
      TRACE("got %u streams\n", db->num_streams);
  
      for (i = 0; i < db->num_streams; i++)
      {
          name = msi_string_lookup( db->strings, db->streams[i].str_index, NULL );
          if (!(encname = encode_streamname( FALSE, name ))) return ERROR_OUTOFMEMORY;
  
          hr = open_stream( db, encname, &stream );
          if (FAILED( hr )) /* new stream */
          {
              hr = IStorage_CreateStream( db->storage, encname, STGM_WRITE|STGM_SHARE_EXCLUSIVE, 0, 0, &stream );
              if (FAILED( hr ))
              {
                  ERR("failed to create stream %s (hr = %08x)\n", debugstr_w(encname), hr);
                  msi_free( encname );
                  return ERROR_FUNCTION_FAILED;
              }
              hr = write_stream( stream, db->streams[i].stream );
              if (FAILED( hr ))
              {
                  ERR("failed to write stream %s (hr = %08x)\n", debugstr_w(encname), hr);
                  msi_free( encname );
                  IStream_Release( stream );
                  return ERROR_FUNCTION_FAILED;
              }
          }
          hr = IStream_Commit( stream, 0 );
          IStream_Release( stream );
          if (FAILED( hr ))
          {
              WARN("failed to commit stream %s (hr = %08x)\n", debugstr_w(encname), hr);
              msi_free( encname );
              return ERROR_FUNCTION_FAILED;
          }
          msi_free( encname );
      }
  
      return ERROR_SUCCESS;
  }
  