windows-nt/Source/XPSP1/NT/shell/shell32/userpict.cpp
2020-09-26 16:20:57 +08:00

795 lines
25 KiB
C++

// --------------------------------------------------------------------------
// 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
#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')
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")
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) || IsBadWritePtr(pszPath, MAX_PATH * sizeof(TCHAR)))
{
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)
{
lstrcpyn(pszPath, szPath, MAX_PATH);
}
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);
lstrcpyn(szTemp, szPath, ARRAYSIZE(szTemp));
lstrcatn(szPath, TEXT(".bmp"), ARRAYSIZE(szPath));
lstrcatn(szTemp, TEXT(".tmp"), ARRAYSIZE(szTemp));
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)