|
|
// --------------------------------------------------------------------------
// Module Name: UserPict.cpp
//
// Copyright (c) 2000, Microsoft Corporation
//
// Functions that implement user picture manipulation.
//
// History: 2000-03-24 vtan created
// 2000-05-03 jeffreys reworked using DIB sections
// 2000-10-26 jeffreys switched from %ALLUSERSPROFILE%\Pictures
// to CSIDL_COMMON_APPDATA\User Account Pictures
// --------------------------------------------------------------------------
#include "shellprv.h"
#include <lmcons.h>
#include <shimgdata.h>
#include <aclapi.h> // for SetNamedSecurityInfo
#include <shgina.h> // for ILogonUser
#include <strsafe.h>
#pragma warning(push,4)
// --------------------------------------------------------------------------
// SaveDIBSectionToFile
//
// Arguments: hbm = Source image (DIB section) to save
// hdc = Device Context containing hbm. May be NULL
// if hbm is not selected in any DC or hbm
// is known to have no color table.
// pszFile = Target image file.
//
// Returns: BOOL
//
// Purpose: Write a DIB to disk in the proper format
//
// History: 2000-05-03 jeffreys created
// --------------------------------------------------------------------------
#define DIB_HEADER_MARKER ((WORD) ('M' << 8) | 'B')
// SECURITY: caller is responsible for pszFile being valid
BOOL SaveDIBSectionToFile(HBITMAP hbm, HDC hdc, LPCTSTR pszFile) { BOOL bResult; DIBSECTION ds; HANDLE hFile; BITMAPFILEHEADER bf; DWORD cbWritten;
bResult = FALSE;
// Get the details about the bitmap. This also validates hbm.
if (GetObject(hbm, sizeof(ds), &ds) == 0) return FALSE;
// Fill in a couple of optional fields if necessary
if (ds.dsBmih.biSizeImage == 0) ds.dsBmih.biSizeImage = ds.dsBmih.biHeight * ds.dsBm.bmWidthBytes;
if (ds.dsBmih.biBitCount <= 8 && ds.dsBmih.biClrUsed == 0) ds.dsBmih.biClrUsed = 1 << ds.dsBmih.biBitCount;
// Open the target file. This also validates pszFile.
hFile = CreateFile(pszFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (INVALID_HANDLE_VALUE == hFile) return FALSE;
// Prepare the BITMAPFILEHEADER for writing
bf.bfType = DIB_HEADER_MARKER; bf.bfReserved1 = 0; bf.bfReserved2 = 0;
// The bit offset is the cumulative size of all of the header stuff
bf.bfOffBits = sizeof(bf) + sizeof(ds.dsBmih) + (ds.dsBmih.biClrUsed*sizeof(RGBQUAD)); if (ds.dsBmih.biCompression == BI_BITFIELDS) bf.bfOffBits += sizeof(ds.dsBitfields);
// Round up to the next 16-byte boundary. This isn't strictly necessary,
// but it makes the file layout cleaner. (You can create a file mapping
// and pass it to CreateDIBSection this way.)
bf.bfOffBits = ((bf.bfOffBits + 15) & ~15);
// The file size is the bit offset + the size of the bits
bf.bfSize = bf.bfOffBits + ds.dsBmih.biSizeImage;
// Write the BITMAPFILEHEADER first
bResult = WriteFile(hFile, &bf, sizeof(bf), &cbWritten, NULL);
if (bResult) { // Next is the BITMAPINFOHEADER
bResult = WriteFile(hFile, &ds.dsBmih, sizeof(ds.dsBmih), &cbWritten, NULL); if (bResult) { // Then the 3 bitfields, if necessary
if (ds.dsBmih.biCompression == BI_BITFIELDS) { bResult = WriteFile(hFile, &ds.dsBitfields, sizeof(ds.dsBitfields), &cbWritten, NULL); }
if (bResult) { // Now the color table, if any
if (ds.dsBmih.biClrUsed != 0) { RGBQUAD argb[256]; HDC hdcDelete; HBITMAP hbmOld;
// Assume failure here
bResult = FALSE;
hdcDelete = NULL; if (!hdc) { hdcDelete = CreateCompatibleDC(NULL); if (hdcDelete) { hbmOld = (HBITMAP)SelectObject(hdcDelete, hbm); hdc = hdcDelete; } }
if (hdc && GetDIBColorTable(hdc, 0, ARRAYSIZE(argb), argb) == ds.dsBmih.biClrUsed) { bResult = WriteFile(hFile, argb, ds.dsBmih.biClrUsed*sizeof(RGBQUAD), &cbWritten, NULL); }
if (hdcDelete) { SelectObject(hdcDelete, hbmOld); DeleteDC(hdcDelete); } }
// Finally, write the bits
if (bResult) { SetFilePointer(hFile, bf.bfOffBits, NULL, FILE_BEGIN); bResult = WriteFile(hFile, ds.dsBm.bmBits, ds.dsBmih.biSizeImage, &cbWritten, NULL); SetEndOfFile(hFile); } } } }
CloseHandle(hFile);
if (!bResult) { // Something failed, clean up
DeleteFile(pszFile); }
return bResult; }
// --------------------------------------------------------------------------
// MakeDIBSection
//
// Arguments: pImage = Source image
//
// Returns: HBITMAP
//
// Purpose: Create a DIB section containing the given image
// on a white background
//
// History: 2000-05-03 jeffreys created
// --------------------------------------------------------------------------
HBITMAP MakeDIBSection(IShellImageData *pImage, ULONG cx, ULONG cy) { HBITMAP hbm; HDC hdc; BITMAPINFO dib;
hdc = CreateCompatibleDC(NULL); if (hdc == NULL) return NULL;
dib.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); dib.bmiHeader.biWidth = cx; dib.bmiHeader.biHeight = cy; dib.bmiHeader.biPlanes = 1; dib.bmiHeader.biBitCount = 24; dib.bmiHeader.biCompression = BI_RGB; dib.bmiHeader.biSizeImage = 0; dib.bmiHeader.biXPelsPerMeter = 0; dib.bmiHeader.biYPelsPerMeter = 0; dib.bmiHeader.biClrUsed = 0; dib.bmiHeader.biClrImportant = 0;
hbm = CreateDIBSection(hdc, &dib, DIB_RGB_COLORS, NULL, NULL, 0);
if (hbm) { HBITMAP hbmOld; RECT rc;
hbmOld = (HBITMAP)SelectObject(hdc, hbm);
// Initialize the entire image with white
PatBlt(hdc, 0, 0, cx, cy, WHITENESS);
rc.left = 0; rc.top = 0; rc.right = cx; rc.bottom = cy;
// Draw the source image into the DIB section
HRESULT hr = pImage->Draw(hdc, &rc, NULL);
SelectObject(hdc, hbmOld);
if (FAILED(hr)) { DeleteObject(hbm); hbm = NULL; SetLastError(hr); } }
DeleteDC(hdc);
return hbm; }
// --------------------------------------------------------------------------
// ConvertAndResizeImage
//
// Arguments: pszFileSource = Source image file.
// pszFileTarget = Target image file (resized).
//
// Returns: HRESULT
//
// Purpose: Uses GDI+ via COM interfaces to convert the given image file
// to a bmp sized at 96x96.
//
// History: 2000-03-24 vtan created
// 2000-05-03 jeffreys reworked using DIB sections
// --------------------------------------------------------------------------
HRESULT ConvertAndResizeImage (LPCTSTR pszFileSource, LPCTSTR pszFileTarget)
{ HRESULT hr; IShellImageDataFactory *pImagingFactory;
hr = CoCreateInstance(CLSID_ShellImageDataFactory, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IShellImageDataFactory, &pImagingFactory)); if (SUCCEEDED(hr)) { IShellImageData *pImage;
hr = pImagingFactory->CreateImageFromFile(pszFileSource, &pImage); if (SUCCEEDED(hr)) { hr = pImage->Decode(SHIMGDEC_DEFAULT, 0, 0);
if (SUCCEEDED(hr)) { SIZE sizeImg; ULONG cxDest, cyDest; HDC hdc; HBITMAP hbm; DWORD dwErr;
// The default dimensions are based on the screen resolution
hdc = GetDC(NULL); if (hdc != NULL) { // Make it 1/2 inch square by default
cxDest = GetDeviceCaps(hdc, LOGPIXELSX) / 2; cyDest = GetDeviceCaps(hdc, LOGPIXELSY) / 2; ReleaseDC(NULL, hdc); } else { // Most common display modes run at 96dpi ("small fonts")
cxDest = cyDest = 48; }
// Get the current image dimensions so we can maintain aspect ratio
if ( SUCCEEDED(pImage->GetSize(&sizeImg)) ) { // Don't want to make small images bigger
cxDest = min(cxDest, (ULONG)sizeImg.cx); cyDest = min(cyDest, (ULONG)sizeImg.cy);
// If it's not square, scale the smaller dimension
// to maintain the aspect ratio.
if (sizeImg.cx > sizeImg.cy) { cyDest = MulDiv(cxDest, sizeImg.cy, sizeImg.cx); } else if (sizeImg.cx < sizeImg.cy) { cxDest = MulDiv(cyDest, sizeImg.cx, sizeImg.cy); } }
// Resize the image
// Note that this gives better results than scaling while drawing
// into the DIB section (see MakeDIBSection).
//
// However, it doesn't always work. For example, animated images
// result in E_NOTVALIDFORANIMATEDIMAGE. So ignore the return
// value and the scaling will be done in MakeDIBSection if necessary.
pImage->Scale(cxDest, cyDest, 0);
hbm = MakeDIBSection(pImage, cxDest, cyDest);
if (hbm) { // Save the DIB section to disk
if (!SaveDIBSectionToFile(hbm, NULL, pszFileTarget)) { dwErr = GetLastError(); hr = HRESULT_FROM_WIN32(dwErr); }
DeleteObject(hbm); } else { dwErr = GetLastError(); hr = HRESULT_FROM_WIN32(dwErr); } }
pImage->Release(); }
pImagingFactory->Release(); }
return(hr); }
// --------------------------------------------------------------------------
// SetExplicitAccessToObject
//
// Arguments: pszTarget = Target object
// seType = Type of object
// pszUser = User to grant access to
// dwMask = Permissions granted
// dwFlags = Inheritance flags
//
// Returns: BOOL
//
// Purpose: Grants Read/Write/Execute/Delete access to the
// specified user on the specified file.
//
// Note that this stomps existing explicit entries in the DACL.
// Multiple calls are not cumulative.
//
// History: 2000-05-19 jeffreys created
// --------------------------------------------------------------------------
DWORD SetExplicitAccessToObject(LPTSTR pszTarget, SE_OBJECT_TYPE seType, LPCTSTR pszUser, DWORD dwMask, DWORD dwFlags) { BOOL bResult;
// 84 bytes
BYTE rgAclBuffer[sizeof(ACL) + (sizeof(ACCESS_ALLOWED_ACE) - sizeof(ULONG)) + (sizeof(SID) + (SID_MAX_SUB_AUTHORITIES-1)*sizeof(ULONG))];
PACL pDacl = (PACL)rgAclBuffer; if (!InitializeAcl(pDacl, sizeof(rgAclBuffer), ACL_REVISION)) return FALSE; pDacl->AceCount = 1;
PACCESS_ALLOWED_ACE pAce = (PACCESS_ALLOWED_ACE)(pDacl+1); pAce->Header.AceType = ACCESS_ALLOWED_ACE_TYPE; pAce->Header.AceFlags = (UCHAR)(dwFlags & 0xFF); pAce->Mask = dwMask;
SID_NAME_USE snu; TCHAR szDomainName[MAX_PATH]; DWORD cbDomainName = ARRAYSIZE(szDomainName); DWORD cbSid = sizeof(SID) + (SID_MAX_SUB_AUTHORITIES-1)*sizeof(ULONG);
bResult = LookupAccountName( NULL, pszUser, (PSID)&(pAce->SidStart), &cbSid, szDomainName, &cbDomainName, &snu); if (bResult) { DWORD dwErr;
// LookupAccountName doesn't return the SID length on success
cbSid = GetLengthSid((PSID)&(pAce->SidStart));
// Update the ACE size
pAce->Header.AceSize = (USHORT)(sizeof(ACCESS_ALLOWED_ACE) - sizeof(ULONG) + cbSid);
dwErr = SetNamedSecurityInfo( pszTarget, seType, DACL_SECURITY_INFORMATION | UNPROTECTED_DACL_SECURITY_INFORMATION, NULL, NULL, pDacl, NULL);
if (ERROR_SUCCESS != dwErr) { SetLastError(dwErr); bResult = FALSE; } }
return bResult; }
// --------------------------------------------------------------------------
// SetDefaultUserPicture
//
// Arguments: pszUsername = Desired user (NULL for current user).
//
// Returns: HRESULT
//
// Purpose: Picks one of the default user pictures at random and
// assigns it to the specified user.
//
// History: 2001-03-27 reinerf created
// --------------------------------------------------------------------------
HRESULT SetDefaultUserPicture(LPCTSTR pszUsername) { HRESULT hr; TCHAR szPath[MAX_PATH];
hr = SHGetUserPicturePath(NULL, SHGUPP_FLAG_DEFAULTPICSPATH, szPath); if (SUCCEEDED(hr)) { BOOL bFound = FALSE; // assume we won't find a picture
// Assume everything in the dir is a vaild image file
if (PathAppend(szPath, TEXT("*.*"))) { static DWORD dwSeed = 0; WIN32_FIND_DATA fd; HANDLE hFind = FindFirstFile(szPath, &fd);
if (dwSeed == 0) { dwSeed = GetTickCount(); }
if (hFind != INVALID_HANDLE_VALUE) { DWORD dwCount = 0; // use a probability collector algorithim (with a limit of 100 files)
do { if (!PathIsDotOrDotDot(fd.cFileName)) { dwCount++;
// although RtlRandom returns a ULONG it is distributed from 0...MAXLONG
if (RtlRandomEx(&dwSeed) <= (MAXLONG / dwCount)) { bFound = TRUE; PathRemoveFileSpec(szPath); PathAppend(szPath, fd.cFileName); } }
} while (FindNextFile(hFind, &fd) && (dwCount < 100));
FindClose(hFind); } } if (bFound) { hr = SHSetUserPicturePath(pszUsername, 0, szPath); } else { hr = E_FAIL; } } return hr; }
// --------------------------------------------------------------------------
// ::SHGetUserPicturePath
//
// Arguments: pszUsername = Desired user (NULL for current user).
// dwFlags = Flags.
// pszPath = Path to user picture.
//
// Returns: HRESULT
//
// Purpose: Returns the user's picture path (absolute). Does parameter
// validation as well. This function only supports .bmp files.
//
// Use SHGUPP_FLAG_BASEPATH to return the base to the pictures
// directory.
//
// Use SHGUPP_FLAG_DEFAULTPICSPATH to return the path to the
// default pictures directory.
//
// Use SHGUPP_FLAG_CREATE to create the user picture directory.
//
// If neither SHGUPP_FLAG_BASEPATH or SHGUPP_FLAG_DEFAULTPICSPATH
// is specified, and the user has no picture, SHGUPP_FLAG_CREATE
// will select one of the default pictures at random.
//
// History: 2000-02-22 vtan created
// 2000-03-24 vtan moved from folder.cpp
// --------------------------------------------------------------------------
#define UASTR_PATH_PICTURES TEXT("Microsoft\\User Account Pictures")
#define UASTR_PATH_DEFPICS UASTR_PATH_PICTURES TEXT("\\Default Pictures")
// SECURITY: Assumes pszPath is MAX_PATH
STDAPI SHGetUserPicturePath (LPCTSTR pszUsername, DWORD dwFlags, LPTSTR pszPath)
{ HRESULT hr; TCHAR szPath[MAX_PATH];
// Validate dwFlags.
if ((dwFlags & SHGUPP_FLAG_INVALID_MASK) != 0) { return(E_INVALIDARG); }
// Validate pszPath. This must not be NULL.
if (pszPath == NULL) { return(E_INVALIDARG); }
// Start by getting the base picture path
hr = SHGetFolderPathAndSubDir(NULL, (dwFlags & SHGUPP_FLAG_CREATE) ? (CSIDL_COMMON_APPDATA | CSIDL_FLAG_CREATE) : CSIDL_COMMON_APPDATA, NULL, SHGFP_TYPE_CURRENT, (dwFlags & SHGUPP_FLAG_DEFAULTPICSPATH) ? UASTR_PATH_DEFPICS : UASTR_PATH_PICTURES, szPath);
// If the base path is requested this function is done.
if (S_OK == hr && 0 == (dwFlags & (SHGUPP_FLAG_BASEPATH | SHGUPP_FLAG_DEFAULTPICSPATH))) { TCHAR szUsername[UNLEN + sizeof('\0')];
if (pszUsername == NULL) { DWORD dwUsernameSize;
dwUsernameSize = ARRAYSIZE(szUsername); if (GetUserName(szUsername, &dwUsernameSize) != FALSE) { pszUsername = szUsername; } else { hr = HRESULT_FROM_WIN32(GetLastError()); } } if (pszUsername != NULL) { // Append the user name to the picture path. Then look for
// <username>.bmp. This function only supports bmp.
PathAppend(szPath, pszUsername); lstrcatn(szPath, TEXT(".bmp"), ARRAYSIZE(szPath)); if (PathFileExistsAndAttributes(szPath, NULL) != FALSE) { hr = S_OK; } else if (dwFlags & SHGUPP_FLAG_CREATE) { // No picture has been set for this user. Select one
// of the default pictures at random.
hr = SetDefaultUserPicture(pszUsername); ASSERT(FAILED(hr) || PathFileExistsAndAttributes(szPath, NULL)); } else { hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); } } }
if (S_OK == hr) { hr = StringCchCopy(pszPath, MAX_PATH, szPath); }
return(hr); }
// --------------------------------------------------------------------------
// ::SHSetUserPicturePath
//
// Arguments: pszUsername = Desired user (NULL for current user).
// dwFlags = Flags.
// pszPath = Path to NEW user picture.
//
// Returns: HRESULT
//
// Purpose: Sets the specified user's picture as a copy of the given
// image file. The image file may be any supported standard image
// file (.gif / .jpg / .bmp). The file is converted to a 96x96
// .bmp file in the user picture directory.
//
// History: 2000-02-22 vtan created
// 2000-03-24 vtan moved from folder.cpp
// 2000-04-27 jeffreys restore old image on conversion failure
// --------------------------------------------------------------------------
STDAPI SHSetUserPicturePath (LPCTSTR pszUsername, DWORD dwFlags, LPCTSTR pszPath)
{ HRESULT hr; TCHAR szPath[MAX_PATH]; TCHAR szUsername[UNLEN + sizeof('\0')]; DWORD dwUsernameSize;
hr = E_FAIL;
// Validate dwFlags. Currently no valid flags so this must be 0x00000000.
if ((dwFlags & SHSUPP_FLAG_INVALID_MASK) != 0) { return(E_INVALIDARG); }
dwUsernameSize = ARRAYSIZE(szUsername); if (GetUserName(szUsername, &dwUsernameSize) == FALSE) { return(HRESULT_FROM_WIN32(GetLastError())); }
if (pszUsername != NULL) { // Privilege check. Must be an administrator to use this function when
// pszUsername is not NULL (i.e. for somebody else).
if ((lstrcmpi(pszUsername, szUsername) != 0) && (SHTestTokenMembership(NULL, DOMAIN_ALIAS_RID_ADMINS) == FALSE)) { static const SID c_SystemSid = {SID_REVISION,1,SECURITY_NT_AUTHORITY,{SECURITY_LOCAL_SYSTEM_RID}}; BOOL bSystem = FALSE;
// One more check. Allow local system through since we may
// get called from the logon screen.
if (!CheckTokenMembership(NULL, (PSID)&c_SystemSid, &bSystem) || !bSystem) { return(E_ACCESSDENIED); } } } else { pszUsername = szUsername; }
// Start by getting the base picture path
hr = SHGetFolderPathAndSubDir(NULL, CSIDL_COMMON_APPDATA | CSIDL_FLAG_CREATE, NULL, SHGFP_TYPE_CURRENT, UASTR_PATH_PICTURES, szPath); if (S_OK == hr) { // Before attempt to delete what's there try to access the
// new file. If this fails deleting what's currently installed
// could leave the user without a picture. Fail the API before
// anything is lost.
if ((pszPath == NULL) || (PathFileExistsAndAttributes(pszPath, NULL) != FALSE)) { TCHAR szTemp[MAX_PATH];
PathAppend(szPath, pszUsername); hr = StringCchCopy(szTemp, ARRAYSIZE(szTemp), szPath); if (SUCCEEDED(hr)) { hr = StringCchCat(szPath, ARRAYSIZE(szPath), TEXT(".bmp")); if (SUCCEEDED(hr)) { hr = StringCchCat(szTemp, ARRAYSIZE(szTemp), TEXT(".tmp")); if (SUCCEEDED(hr)) { if ((pszPath == NULL) || lstrcmpi(pszPath, szPath) != 0) { // If present, rename <username>.Bmp to <username>.Tmp.
// First reset the attributes so file ops work. Don't use
// trace macros because failure is expected.
(BOOL)SetFileAttributes(szPath, 0); (BOOL)SetFileAttributes(szTemp, 0); (BOOL)MoveFileEx(szPath, szTemp, MOVEFILE_REPLACE_EXISTING);
// Convert the given image to a bmp and resize it
// using the helper function which does all the goo.
if (pszPath != NULL) { hr = ConvertAndResizeImage(pszPath, szPath);
if (SUCCEEDED(hr)) { // Since this may be an admin setting someone else's
// picture, we need to grant that person access to
// modify/delete the file so they can change it
// themselves later.
(BOOL)SetExplicitAccessToObject(szPath, SE_FILE_OBJECT, pszUsername, GENERIC_READ | GENERIC_EXECUTE | GENERIC_WRITE | DELETE, 0); } } else { hr = S_OK; }
if (SUCCEEDED(hr)) { // Delete the old picture
(BOOL)DeleteFile(szTemp); } else { // Restore the old picture
(BOOL)MoveFileEx(szTemp, szPath, MOVEFILE_REPLACE_EXISTING); } // Notify everyone that a user picture has changed
SHChangeDWORDAsIDList dwidl; dwidl.cb = SIZEOF(dwidl) - SIZEOF(dwidl.cbZero); dwidl.dwItem1 = SHCNEE_USERINFOCHANGED; dwidl.dwItem2 = 0; dwidl.cbZero = 0;
SHChangeNotify(SHCNE_EXTENDED_EVENT, SHCNF_FLUSH, (LPCITEMIDLIST)&dwidl, NULL); } else { // Source and destination are the same, nothing to do.
hr = S_FALSE; } } } } } else { hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); } }
return(hr); }
#pragma warning(pop)
|