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

804 lines
22 KiB
C++

/*
* url.cpp - IUniformResourceLocator implementation for InternetShortcut class.
*/
/* Headers
**********/
#include "project.hpp"
#pragma hdrstop
#include "assoc.h"
#include "resource.h"
#define DECL_CRTFREE
#include <crtfree.h>
/* Module Constants
*******************/
#pragma data_seg(DATA_SEG_READ_ONLY)
PRIVATE_DATA const char s_cszURLSeparator[] = ":";
PRIVATE_DATA const char s_cchURLSuffixSlash = '/';
PRIVATE_DATA const char s_cszURLPrefixesKey[] = "Software\\Microsoft\\Windows\\CurrentVersion\\URL\\Prefixes";
PRIVATE_DATA const char s_cszDefaultURLPrefixKey[] = "Software\\Microsoft\\Windows\\CurrentVersion\\URL\\DefaultPrefix";
#pragma data_seg()
/***************************** Private Functions *****************************/
PRIVATE_CODE PCSTR SkipLeadingSlashes(PCSTR pcszURL)
{
PCSTR pcszURLStart;
ASSERT(IS_VALID_STRING_PTR(pcszURL, CSTR));
// Skip two leading slashes.
if (pcszURL[0] == s_cchURLSuffixSlash &&
pcszURL[1] == s_cchURLSuffixSlash)
{
pcszURLStart = CharNext(pcszURL);
pcszURLStart = CharNext(pcszURLStart);
}
else
pcszURLStart = pcszURL;
ASSERT(IS_VALID_STRING_PTR(pcszURL, CSTR) &&
EVAL(IsStringContained(pcszURL, pcszURLStart)));
return(pcszURLStart);
}
PRIVATE_CODE HRESULT ApplyURLPrefix(PCSTR pcszURL, PSTR *ppszTranslatedURL)
{
HRESULT hr = S_FALSE;
HKEY hkeyPrefixes;
ASSERT(IS_VALID_STRING_PTR(pcszURL, CSTR));
ASSERT(IS_VALID_WRITE_PTR(ppszTranslatedURL, PSTR));
*ppszTranslatedURL = NULL;
if (RegOpenKey(g_hkeyURLSettings, s_cszURLPrefixesKey, &hkeyPrefixes)
== ERROR_SUCCESS)
{
PCSTR pcszURLStart;
DWORD dwiValue;
char rgchValueName[MAX_PATH_LEN];
DWORD dwcbValueNameLen;
DWORD dwType;
char rgchPrefix[MAX_PATH_LEN];
DWORD dwcbPrefixLen;
pcszURLStart = SkipLeadingSlashes(pcszURL);
dwcbValueNameLen = sizeof(rgchValueName);
dwcbPrefixLen = sizeof(rgchPrefix);
for (dwiValue = 0;
RegEnumValue(hkeyPrefixes, dwiValue, rgchValueName,
&dwcbValueNameLen, NULL, &dwType, (PBYTE)rgchPrefix,
&dwcbPrefixLen) == ERROR_SUCCESS;
dwiValue++)
{
if (! lstrnicmp(pcszURLStart, rgchValueName, dwcbValueNameLen))
{
DWORD cbUrl = lstrlen(pcszURLStart);
// If the url==prefix, then we're calling the executable.
if (cbUrl >= dwcbPrefixLen)
{
// dwcbPrefixLen includes null terminator.
*ppszTranslatedURL = new(char[dwcbPrefixLen + cbUrl]);
if (*ppszTranslatedURL)
{
lstrcpy(*ppszTranslatedURL, rgchPrefix);
lstrcat(*ppszTranslatedURL, pcszURLStart);
// (+ 1) for null terminator.
ASSERT(lstrlen(*ppszTranslatedURL) + 1 == (int)dwcbPrefixLen + lstrlen(pcszURLStart));
hr = S_OK;
}
else
hr = E_OUTOFMEMORY;
}
break;
}
dwcbValueNameLen = sizeof(rgchValueName);
dwcbPrefixLen = sizeof(rgchPrefix);
}
RegCloseKey(hkeyPrefixes);
switch (hr)
{
case S_OK:
TRACE_OUT(("ApplyURLPrefix(): Prefix %s prepended to prefix %s of %s to yield URL %s.",
rgchValueName,
rgchPrefix,
pcszURL,
*ppszTranslatedURL));
break;
case S_FALSE:
TRACE_OUT(("ApplyURLPrefix(): No matching prefix found for URL %s.",
pcszURL));
break;
default:
ASSERT(hr == E_OUTOFMEMORY);
break;
}
}
else
TRACE_OUT(("ApplyURLPrefix(): No URL prefixes registered."));
ASSERT((hr == S_OK &&
IS_VALID_STRING_PTR(*ppszTranslatedURL, STR)) ||
((hr == S_FALSE ||
hr == E_OUTOFMEMORY) &&
! *ppszTranslatedURL));
return(hr);
}
PRIVATE_CODE HRESULT ApplyDefaultURLPrefix(PCSTR pcszURL,
PSTR *ppszTranslatedURL)
{
HRESULT hr;
char rgchDefaultURLPrefix[MAX_PATH_LEN];
DWORD dwcbDefaultURLPrefixLen;
ASSERT(IS_VALID_STRING_PTR(pcszURL, CSTR));
ASSERT(IS_VALID_WRITE_PTR(ppszTranslatedURL, PSTR));
*ppszTranslatedURL = NULL;
dwcbDefaultURLPrefixLen = sizeof(rgchDefaultURLPrefix);
if (GetDefaultRegKeyValue(g_hkeyURLSettings, s_cszDefaultURLPrefixKey,
rgchDefaultURLPrefix, &dwcbDefaultURLPrefixLen)
== ERROR_SUCCESS)
{
// (+ 1) for null terminator.
*ppszTranslatedURL = new(char[dwcbDefaultURLPrefixLen +
lstrlen(pcszURL) + 1]);
if (*ppszTranslatedURL)
{
PCSTR pcszURLStart;
pcszURLStart = SkipLeadingSlashes(pcszURL);
lstrcpy(*ppszTranslatedURL, rgchDefaultURLPrefix);
lstrcat(*ppszTranslatedURL, pcszURLStart);
// (+ 1) for null terminator.
ASSERT(lstrlen(*ppszTranslatedURL) + 1 == (int)dwcbDefaultURLPrefixLen + lstrlen(pcszURLStart));
hr = S_OK;
}
else
hr = E_OUTOFMEMORY;
}
else
hr = S_FALSE;
switch (hr)
{
case S_OK:
TRACE_OUT(("ApplyDefaultURLPrefix(): Default prefix %s prepended to %s to yield URL %s.",
rgchDefaultURLPrefix,
pcszURL,
*ppszTranslatedURL));
break;
case S_FALSE:
TRACE_OUT(("ApplyDefaultURLPrefix(): No default URL prefix registered."));
break;
default:
ASSERT(hr == E_OUTOFMEMORY);
break;
}
ASSERT((hr == S_OK &&
IS_VALID_STRING_PTR(*ppszTranslatedURL, STR)) ||
((hr == S_FALSE ||
hr == E_OUTOFMEMORY) &&
! *ppszTranslatedURL));
return(hr);
}
PRIVATE_CODE HRESULT MyTranslateURL(PCSTR pcszURL, DWORD dwFlags,
PSTR *ppszTranslatedURL)
{
HRESULT hr;
PARSEDURL pu;
ASSERT(IS_VALID_STRING_PTR(pcszURL, CSTR));
ASSERT(FLAGS_ARE_VALID(dwFlags, ALL_TRANSLATEURL_FLAGS));
ASSERT(IS_VALID_WRITE_PTR(ppszTranslatedURL, PSTR));
// Check URL syntax.
pu.cbSize = sizeof(pu);
if (ParseURL(pcszURL, &pu) == S_OK)
{
*ppszTranslatedURL = NULL;
hr = S_FALSE;
}
else
{
if (IS_FLAG_SET(dwFlags, TRANSLATEURL_FL_GUESS_PROTOCOL))
hr = ApplyURLPrefix(pcszURL, ppszTranslatedURL);
else
hr = S_FALSE;
if (hr == S_FALSE &&
IS_FLAG_SET(dwFlags, TRANSLATEURL_FL_USE_DEFAULT_PROTOCOL))
hr = ApplyDefaultURLPrefix(pcszURL, ppszTranslatedURL);
}
ASSERT((hr == S_OK &&
IS_VALID_STRING_PTR(*ppszTranslatedURL, STR)) ||
((hr == S_FALSE ||
hr == E_OUTOFMEMORY) &&
! *ppszTranslatedURL));
return(hr);
}
#ifdef DEBUG
PRIVATE_CODE IsValidPCURLINVOKECOMMANDINFO(PCURLINVOKECOMMANDINFO pcurlici)
{
return(IS_VALID_READ_PTR(pcurlici, CURLINVOKECOMMANDINFO) &&
EVAL(pcurlici->dwcbSize >= sizeof(*pcurlici)) &&
FLAGS_ARE_VALID(pcurlici->dwFlags, ALL_IURL_INVOKECOMMAND_FLAGS) &&
(IS_FLAG_CLEAR(pcurlici->dwFlags, IURL_INVOKECOMMAND_FL_ALLOW_UI) ||
IS_VALID_HANDLE(pcurlici->hwndParent, WND)) &&
(IS_FLAG_SET(pcurlici->dwFlags, IURL_INVOKECOMMAND_FL_USE_DEFAULT_VERB) ||
IS_VALID_STRING_PTR(pcurlici->pcszVerb, CSTR)));
}
#endif
/********************************** Methods **********************************/
HRESULT STDMETHODCALLTYPE InternetShortcut::RegisterProtocolHandler(
HWND hwndParent,
PSTR pszAppBuf,
UINT ucAppBufLen)
{
HRESULT hr;
DWORD dwFlags = 0;
DebugEntry(InternetShortcut::RegisterProtocolHandler);
ASSERT(! hwndParent ||
IS_VALID_HANDLE(hwndParent, WND));
ASSERT(IS_VALID_WRITE_BUFFER_PTR(pszAppBuf, STR, ucAppBufLen));
ASSERT(IS_VALID_STRING_PTR(m_pszURL, STR));
SET_FLAG(dwFlags, URLASSOCDLG_FL_REGISTER_ASSOC);
if (! m_pszFile)
SET_FLAG(dwFlags, URLASSOCDLG_FL_USE_DEFAULT_NAME);
hr = URLAssociationDialog(hwndParent, dwFlags, m_pszFile, m_pszURL,
pszAppBuf, ucAppBufLen);
switch (hr)
{
case S_FALSE:
TRACE_OUT(("InternetShortcut::RegisterProtocolHandler(): One time execution of %s via %s requested.",
m_pszURL,
pszAppBuf));
break;
case S_OK:
TRACE_OUT(("InternetShortcut::RegisterProtocolHandler(): Protocol handler registered for %s.",
m_pszURL));
break;
default:
ASSERT(FAILED(hr));
break;
}
ASSERT(! ucAppBufLen ||
(IS_VALID_STRING_PTR(pszAppBuf, STR) &&
(UINT)lstrlen(pszAppBuf) < ucAppBufLen));
DebugExitHRESULT(InternetShortcut::RegisterProtocolHandler, hr);
return(hr);
}
HRESULT STDMETHODCALLTYPE InternetShortcut::SetURL(PCSTR pcszURL,
DWORD dwInFlags)
{
HRESULT hr;
BOOL bChanged;
PSTR pszNewURL = NULL;
DebugEntry(InternetShortcut::SetURL);
ASSERT(IS_VALID_STRUCT_PTR(this, CInternetShortcut));
ASSERT(! pcszURL ||
IS_VALID_STRING_PTR(pcszURL, CSTR));
ASSERT(FLAGS_ARE_VALID(dwInFlags, ALL_IURL_SETURL_FLAGS));
bChanged = ! ((! pcszURL && ! m_pszURL) ||
(pcszURL && m_pszURL &&
! lstrcmp(pcszURL, m_pszURL)));
if (bChanged && pcszURL)
{
DWORD dwTranslateURLFlags;
PSTR pszTranslatedURL;
dwTranslateURLFlags = 0;
if (IS_FLAG_SET(dwInFlags, IURL_SETURL_FL_GUESS_PROTOCOL))
SET_FLAG(dwTranslateURLFlags, TRANSLATEURL_FL_GUESS_PROTOCOL);
if (IS_FLAG_SET(dwInFlags, IURL_SETURL_FL_USE_DEFAULT_PROTOCOL))
SET_FLAG(dwTranslateURLFlags, TRANSLATEURL_FL_USE_DEFAULT_PROTOCOL);
hr = TranslateURL(pcszURL, dwTranslateURLFlags, &pszTranslatedURL);
if (SUCCEEDED(hr))
{
PCSTR pcszURLToUse;
// Still different?
if (hr == S_OK)
{
bChanged = (lstrcmp(pszTranslatedURL, m_pszURL) != 0);
pcszURLToUse = pszTranslatedURL;
}
else
{
ASSERT(hr == S_FALSE);
pcszURLToUse = pcszURL;
}
if (bChanged)
{
PARSEDURL pu;
// Validate URL syntax.
pu.cbSize = sizeof(pu);
hr = ParseURL(pcszURLToUse, &pu);
if (hr == S_OK)
{
pszNewURL = new(char[lstrlen(pcszURLToUse) + 1]);
if (pszNewURL)
lstrcpy(pszNewURL, pcszURLToUse);
else
hr = E_OUTOFMEMORY;
}
}
else
hr = S_OK;
}
if (pszTranslatedURL)
{
LocalFree(pszTranslatedURL);
pszTranslatedURL = NULL;
}
}
else
hr = S_OK;
if (hr == S_OK)
{
if (bChanged)
{
if (m_pszURL)
delete m_pszURL;
m_pszURL = pszNewURL;
Dirty(TRUE);
TRACE_OUT(("InternetShortcut::SetURL(): Set URL to %s.",
CHECK_STRING(m_pszURL)));
}
else
TRACE_OUT(("InternetShortcut::SetURL(): URL already %s.",
CHECK_STRING(m_pszURL)));
}
ASSERT(IS_VALID_STRUCT_PTR(this, CInternetShortcut));
ASSERT(hr == S_OK ||
hr == E_OUTOFMEMORY ||
hr == URL_E_INVALID_SYNTAX);
DebugExitHRESULT(InternetShortcut::SetURL, hr);
return(hr);
}
HRESULT STDMETHODCALLTYPE InternetShortcut::GetURL(PSTR *ppszURL)
{
HRESULT hr;
DebugEntry(InternetShortcut::GetURL);
ASSERT(IS_VALID_STRUCT_PTR(this, CInternetShortcut));
ASSERT(IS_VALID_WRITE_PTR(ppszURL, PSTR));
*ppszURL = NULL;
if (m_pszURL)
{
// (+ 1) for null terminator.
*ppszURL = (PSTR)SHAlloc(lstrlen(m_pszURL) + 1);
if (*ppszURL)
{
lstrcpy(*ppszURL, m_pszURL);
hr = S_OK;
TRACE_OUT(("InternetShortcut::GetURL(): Got URL %s.",
*ppszURL));
}
else
hr = E_OUTOFMEMORY;
}
else
// No URL.
hr = S_FALSE;
ASSERT(IS_VALID_STRUCT_PTR(this, CInternetShortcut));
ASSERT((hr == S_OK &&
IS_VALID_STRING_PTR(*ppszURL, STR)) ||
((hr == S_FALSE ||
hr == E_OUTOFMEMORY) &&
! *ppszURL));
DebugExitHRESULT(InternetShortcut::GetURL, hr);
return(hr);
}
HRESULT STDMETHODCALLTYPE InternetShortcut::InvokeCommand(
PURLINVOKECOMMANDINFO purlici)
{
HRESULT hr = E_INVALIDARG;
char szOneShotApp[MAX_PATH_LEN];
BOOL bExecFailedWhine = FALSE;
DebugEntry(InternetShortcut::InvokeCommand);
ASSERT(IS_VALID_STRUCT_PTR(this, CInternetShortcut));
ASSERT(IS_VALID_STRUCT_PTR(purlici, CURLINVOKECOMMANDINFO));
if (purlici && EVAL(sizeof(*purlici) == purlici->dwcbSize))
{
if (m_pszURL)
{
PSTR pszProtocol;
hr = CopyURLProtocol(m_pszURL, &pszProtocol);
if (hr == S_OK)
{
hr = IsProtocolRegistered(pszProtocol);
if (hr == URL_E_UNREGISTERED_PROTOCOL &&
IS_FLAG_SET(purlici->dwFlags, IURL_INVOKECOMMAND_FL_ALLOW_UI))
{
TRACE_OUT(("InternetShortcut::InvokeCommand(): Unregistered URL protocol %s. Invoking URL protocol handler association dialog.",
pszProtocol));
hr = RegisterProtocolHandler(purlici->hwndParent, szOneShotApp,
sizeof(szOneShotApp));
}
switch (hr)
{
case S_OK:
{
SHELLEXECUTEINFO sei;
char szDefaultVerb[MAX_PATH_LEN];
// Execute URL via registered protocol handler.
ZeroMemory(&sei, sizeof(sei));
sei.fMask = (SEE_MASK_CLASSNAME | SEE_MASK_NO_HOOKS);
if (IS_FLAG_CLEAR(purlici->dwFlags,
IURL_INVOKECOMMAND_FL_ALLOW_UI))
SET_FLAG(sei.fMask, SEE_MASK_FLAG_NO_UI);
if (IS_FLAG_CLEAR(purlici->dwFlags,
IURL_INVOKECOMMAND_FL_USE_DEFAULT_VERB))
sei.lpVerb = purlici->pcszVerb;
else
{
if (GetClassDefaultVerb(pszProtocol, szDefaultVerb,
sizeof(szDefaultVerb)))
sei.lpVerb = szDefaultVerb;
else
ASSERT(! sei.lpVerb);
}
sei.cbSize = sizeof(sei);
sei.hwnd = purlici->hwndParent;
sei.lpFile = m_pszURL;
sei.lpDirectory = m_pszWorkingDirectory;
sei.nShow = m_nShowCmd;
sei.lpClass = pszProtocol;
TRACE_OUT(("InternetShortcut::InvokeCommand(): Invoking %s verb on URL %s.",
sei.lpVerb ? sei.lpVerb : "open",
sei.lpFile));
hr = ShellExecuteEx(&sei) ? S_OK : IS_E_EXEC_FAILED;
if (hr == S_OK)
TRACE_OUT(("InternetShortcut::InvokeCommand(): ShellExecuteEx() via registered protcol handler succeeded for %s.",
m_pszURL));
else
WARNING_OUT(("InternetShortcut::InvokeCommand(): ShellExecuteEx() via registered protcol handler failed for %s.",
m_pszURL));
break;
}
case S_FALSE:
hr = MyExecute(szOneShotApp, m_pszURL, 0);
switch (hr)
{
case E_FAIL:
bExecFailedWhine = TRUE;
hr = IS_E_EXEC_FAILED;
break;
default:
break;
}
break;
default:
ASSERT(FAILED(hr));
break;
}
delete pszProtocol;
pszProtocol = NULL;
}
}
else
// No URL. Not an error.
hr = S_FALSE;
if (FAILED(hr) &&
IS_FLAG_SET(purlici->dwFlags, IURL_INVOKECOMMAND_FL_ALLOW_UI))
{
int nResult;
switch (hr)
{
case IS_E_EXEC_FAILED:
if (bExecFailedWhine)
{
ASSERT(IS_VALID_STRING_PTR(szOneShotApp, STR));
if (MyMsgBox(purlici->hwndParent,
MAKEINTRESOURCE(IDS_SHORTCUT_ERROR_TITLE),
MAKEINTRESOURCE(IDS_EXEC_FAILED),
(MB_OK | MB_ICONEXCLAMATION), &nResult,
szOneShotApp)) {
ASSERT(nResult == IDOK);
}
}
break;
case URL_E_INVALID_SYNTAX:
ASSERT(IS_VALID_STRING_PTR(m_pszURL, STR));
if (MyMsgBox(purlici->hwndParent,
MAKEINTRESOURCE(IDS_SHORTCUT_ERROR_TITLE),
MAKEINTRESOURCE(IDS_EXEC_INVALID_SYNTAX),
(MB_OK | MB_ICONEXCLAMATION), &nResult, m_pszURL)) {
ASSERT(nResult == IDOK);
}
break;
case URL_E_UNREGISTERED_PROTOCOL:
{
PSTR pszProtocol;
ASSERT(IS_VALID_STRING_PTR(m_pszURL, STR));
if (CopyURLProtocol(m_pszURL, &pszProtocol) == S_OK)
{
if (MyMsgBox(purlici->hwndParent,
MAKEINTRESOURCE(IDS_SHORTCUT_ERROR_TITLE),
MAKEINTRESOURCE(IDS_EXEC_UNREGISTERED_PROTOCOL),
(MB_OK | MB_ICONEXCLAMATION), &nResult,
pszProtocol)) {
ASSERT(nResult == IDOK);
}
delete pszProtocol;
pszProtocol = NULL;
}
break;
}
case E_OUTOFMEMORY:
if (MyMsgBox(purlici->hwndParent, MAKEINTRESOURCE(IDS_SHORTCUT_ERROR_TITLE),
MAKEINTRESOURCE(IDS_EXEC_OUT_OF_MEMORY),
(MB_OK | MB_ICONEXCLAMATION), &nResult)) {
ASSERT(nResult == IDOK);
}
break;
default:
ASSERT(hr == E_ABORT);
break;
}
}
}
ASSERT(IS_VALID_STRUCT_PTR(this, CInternetShortcut));
ASSERT(hr == S_OK ||
hr == E_ABORT ||
hr == E_OUTOFMEMORY ||
hr == E_INVALIDARG ||
hr == URL_E_INVALID_SYNTAX ||
hr == URL_E_UNREGISTERED_PROTOCOL ||
hr == IS_E_EXEC_FAILED);
DebugExitHRESULT(InternetShortcut::InvokeCommand, hr);
return(hr);
}
/***************************** Exported Functions ****************************/
INTSHCUTAPI HRESULT WINAPI TranslateURLA(PCSTR pcszURL, DWORD dwInFlags,
PSTR *ppszTranslatedURL)
{
HRESULT hr;
DebugEntry(TranslateURLA);
*ppszTranslatedURL = NULL;
#ifdef EXPV
/* Verify parameters. */
if (IS_VALID_STRING_PTR(pcszURL, CSTR) &&
IS_VALID_WRITE_PTR(ppszTranslatedURL, PSTR))
{
if (FLAGS_ARE_VALID(dwInFlags, ALL_TRANSLATEURL_FLAGS))
#endif
{
PSTR pszTempTranslatedURL;
hr = MyTranslateURL(pcszURL, dwInFlags, &pszTempTranslatedURL);
if (hr == S_OK)
{
// (+ 1) for null terminator.
*ppszTranslatedURL = (PSTR)LocalAlloc(
LMEM_FIXED,
lstrlen(pszTempTranslatedURL) + 1);
if (*ppszTranslatedURL)
{
lstrcpy(*ppszTranslatedURL, pszTempTranslatedURL);
ASSERT(lstrlen(*ppszTranslatedURL) == lstrlen(pszTempTranslatedURL));
}
else
hr = E_OUTOFMEMORY;
delete pszTempTranslatedURL;
pszTempTranslatedURL = NULL;
}
}
#ifdef EXPV
else
hr = E_FLAGS;
}
else
hr = E_POINTER;
#endif
switch (hr)
{
case S_FALSE:
TRACE_OUT(("TranslateURLA(): URL %s does not require translation.",
pcszURL));
break;
case S_OK:
TRACE_OUT(("TranslateURLA(): URL %s translated to URL %s.",
pcszURL,
*ppszTranslatedURL));
break;
default:
ASSERT(hr == E_OUTOFMEMORY ||
hr == E_POINTER);
break;
}
ASSERT((hr == S_OK &&
IS_VALID_STRING_PTR(*ppszTranslatedURL, STR)) ||
((hr == S_FALSE ||
hr == E_OUTOFMEMORY ||
hr == E_POINTER ||
hr == E_FLAGS) &&
! *ppszTranslatedURL));
DebugExitHRESULT(TranslateURLA, hr);
return(hr);
}
#pragma warning(disable:4100) /* "unreferenced formal parameter" warning */
INTSHCUTAPI HRESULT WINAPI TranslateURLW(PCWSTR pcszURL, DWORD dwInFlags,
PWSTR UNALIGNED *ppszTranslatedURL)
{
HRESULT hr;
DebugEntry(TranslateURLW);
SetLastError(ERROR_NOT_SUPPORTED);
hr = E_NOTIMPL;
DebugExitHRESULT(TranslateURLW, hr);
return(hr);
}
#pragma warning(default:4100) /* "unreferenced formal parameter" warning */