Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

805 lines
20 KiB

  1. #include <pch.h>
  2. #pragma hdrstop
  3. #include <httpext.h>
  4. #include <httpfilt.h>
  5. #include <wininet.h>
  6. #include <msxml.h>
  7. #include <oleauto.h>
  8. #include "ssdpapi.h"
  9. #include "ncbase.h"
  10. #include "updiag.h"
  11. #include "ncxml.h"
  12. BOOL g_fInited = FALSE;
  13. HANDLE g_hMapFile = NULL;
  14. SHARED_DATA * g_pdata = NULL;
  15. HANDLE g_hEvent = NULL;
  16. HANDLE g_hEventRet = NULL;
  17. HANDLE g_hMutex = NULL;
  18. BOOL g_fTurnOff = FALSE;
  19. BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpv)
  20. {
  21. return TRUE;
  22. }
  23. BOOL FInit()
  24. {
  25. SECURITY_ATTRIBUTES sa = {0};
  26. SECURITY_DESCRIPTOR sd = {0};
  27. InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION);
  28. SetSecurityDescriptorDacl(&sd, TRUE, NULL, FALSE);
  29. sa.nLength = sizeof(SECURITY_ATTRIBUTES);
  30. sa.bInheritHandle = FALSE;
  31. sa.lpSecurityDescriptor = &sd;
  32. InitializeDebugging();
  33. TraceTag(ttidIsapiCtl, "Initializing...");
  34. g_hEvent = CreateEvent(&sa, FALSE, FALSE, c_szSharedEvent);
  35. if (g_hEvent)
  36. {
  37. if (GetLastError() != ERROR_ALREADY_EXISTS)
  38. {
  39. TraceTag(ttidIsapiCtl, "Event wasn't already created!");
  40. goto cleanup;
  41. }
  42. else
  43. {
  44. TraceTag(ttidIsapiCtl, "Created event...");
  45. }
  46. }
  47. else
  48. {
  49. TraceTag(ttidIsapiCtl, "Could not create event! Error = %d.",
  50. GetLastError());
  51. goto cleanup;
  52. }
  53. g_hEventRet = CreateEvent(&sa, FALSE, FALSE, c_szSharedEventRet);
  54. if (g_hEventRet)
  55. {
  56. if (GetLastError() != ERROR_ALREADY_EXISTS)
  57. {
  58. TraceTag(ttidIsapiCtl, "Return event wasn't already created!");
  59. goto cleanup;
  60. }
  61. else
  62. {
  63. TraceTag(ttidIsapiCtl, "Created return event...");
  64. }
  65. }
  66. else
  67. {
  68. TraceTag(ttidIsapiCtl, "Could not create return event! Error = %d.",
  69. GetLastError());
  70. goto cleanup;
  71. }
  72. g_hMutex = CreateMutex(&sa, FALSE, c_szSharedMutex);
  73. if (g_hMutex)
  74. {
  75. if (GetLastError() != ERROR_ALREADY_EXISTS)
  76. {
  77. TraceTag(ttidIsapiCtl, "Mutex wasn't already created!");
  78. goto cleanup;
  79. }
  80. else
  81. {
  82. TraceTag(ttidIsapiCtl, "Created mutex...");
  83. }
  84. }
  85. else
  86. {
  87. TraceTag(ttidIsapiCtl, "Could not create event! Error = %d.",
  88. GetLastError());
  89. goto cleanup;
  90. }
  91. g_hMapFile = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, c_szSharedData);
  92. if (g_hMapFile)
  93. {
  94. TraceTag(ttidIsapiCtl, "Opened file mapping...");
  95. g_pdata = (SHARED_DATA *)MapViewOfFile(g_hMapFile, FILE_MAP_ALL_ACCESS,
  96. 0, 0, 0);
  97. if (g_pdata)
  98. {
  99. TraceTag(ttidIsapiCtl, "Shared data successful at 0x%08X.", g_pdata);
  100. TraceTag(ttidIsapiCtl, "ISAPICTL is initialized.");
  101. g_fInited = TRUE;
  102. return TRUE;
  103. }
  104. else
  105. {
  106. TraceTag(ttidIsapiCtl, "Failed to map file. Error %d.", GetLastError());
  107. goto cleanup;
  108. }
  109. }
  110. else
  111. {
  112. TraceTag(ttidIsapiCtl, "Failed to open file mapping. Error %d.", GetLastError());
  113. goto cleanup;
  114. }
  115. cleanup:
  116. if (g_pdata)
  117. {
  118. UnmapViewOfFile((LPVOID)g_pdata);
  119. }
  120. if (g_hMapFile)
  121. {
  122. CloseHandle(g_hMapFile);
  123. }
  124. if (g_hEvent)
  125. {
  126. CloseHandle(g_hEvent);
  127. }
  128. if (g_hEventRet)
  129. {
  130. CloseHandle(g_hEvent);
  131. }
  132. if (g_hMutex)
  133. {
  134. CloseHandle(g_hMutex);
  135. }
  136. return FALSE;
  137. }
  138. BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO * pver)
  139. {
  140. if (!g_fInited && !g_fTurnOff)
  141. {
  142. if (!FInit())
  143. {
  144. TraceTag(ttidIsapiCtl, "Failed to initialize. Aborting!");
  145. SsdpCleanup();
  146. g_fTurnOff = TRUE;
  147. TraceTag(ttidIsapiCtl, "Turning off.");
  148. //return FALSE;
  149. }
  150. }
  151. pver->dwExtensionVersion = MAKELONG(1, 0);
  152. lstrcpyA(pver->lpszExtensionDesc, "UPnP ISAPI Control Extension");
  153. TraceTag(ttidIsapiCtl, "ISAPICTL: Extension version: %s.",
  154. pver->lpszExtensionDesc);
  155. return TRUE;
  156. }
  157. HRESULT
  158. HrParseHeader(
  159. IXMLDOMNode * pxdnHeader)
  160. {
  161. HRESULT hr = S_OK;
  162. IXMLDOMNode * pxdnChild = NULL;
  163. hr = pxdnHeader->get_firstChild(&pxdnChild);
  164. while (SUCCEEDED(hr) && pxdnChild)
  165. {
  166. IXMLDOMNode * pxdnNextSibling = NULL;
  167. BSTR bstrBaseName = NULL;
  168. hr = pxdnChild->get_baseName(&bstrBaseName);
  169. if (SUCCEEDED(hr) && bstrBaseName)
  170. {
  171. if (wcscmp(bstrBaseName, L"sequenceNumber") == 0)
  172. {
  173. TraceTag(ttidIsapiCtl,
  174. "HrParseHeader(): "
  175. "Parsing sequence number node");
  176. BSTR bstrSeqNumberText = NULL;
  177. hr = pxdnChild->get_text(&bstrSeqNumberText);
  178. if (SUCCEEDED(hr) && bstrSeqNumberText)
  179. {
  180. LONG lSeqNumber = _wtol(bstrSeqNumberText);
  181. g_pdata->dwSeqNumber = (DWORD) lSeqNumber;
  182. TraceTag(ttidIsapiCtl,
  183. "HrParseHeader(): "
  184. "Sequence number is %d\n",
  185. g_pdata->dwSeqNumber);
  186. SysFreeString(bstrSeqNumberText);
  187. }
  188. else
  189. {
  190. if (SUCCEEDED(hr))
  191. {
  192. hr = E_FAIL;
  193. }
  194. TraceTag(ttidIsapiCtl,
  195. "HrParseHeader(): "
  196. "Failed to get sequence number text");
  197. }
  198. }
  199. else if (wcscmp(bstrBaseName, L"SID") == 0)
  200. {
  201. TraceTag(ttidIsapiCtl,
  202. "HrParseHeader(): "
  203. "Parsing SID node");
  204. BSTR bstrSIDText = NULL;
  205. hr = pxdnChild->get_text(&bstrSIDText);
  206. if (SUCCEEDED(hr) && bstrSIDText)
  207. {
  208. DWORD cch;
  209. cch = SysStringLen(bstrSIDText)+1;
  210. WideCharToMultiByte(CP_ACP, 0, bstrSIDText,
  211. cch,
  212. (LPSTR)g_pdata->szSID,
  213. cch, NULL, NULL);
  214. TraceTag(ttidIsapiCtl,
  215. "HrParseHeader(): "
  216. "SID is %s\n",
  217. g_pdata->szSID);
  218. SysFreeString(bstrSIDText);
  219. }
  220. else
  221. {
  222. if (SUCCEEDED(hr))
  223. {
  224. hr = E_FAIL;
  225. }
  226. TraceTag(ttidIsapiCtl,
  227. "HrParseHeader(): "
  228. "Failed to get SID text");
  229. }
  230. }
  231. else
  232. {
  233. // Found an unknown node. This SOAP request is not valid.
  234. TraceTag(ttidIsapiCtl,
  235. "HrParseHeader(): "
  236. "Found unknown node \"%S\"",
  237. bstrBaseName);
  238. hr = E_FAIL;
  239. }
  240. SysFreeString(bstrBaseName);
  241. }
  242. else
  243. {
  244. if (SUCCEEDED(hr))
  245. {
  246. hr = E_FAIL;
  247. }
  248. TraceError("HrParseHeader(): "
  249. "Failed to get node base name",
  250. hr);
  251. }
  252. if (SUCCEEDED(hr))
  253. {
  254. hr = pxdnChild->get_nextSibling(&pxdnNextSibling);
  255. pxdnChild->Release();
  256. pxdnChild = pxdnNextSibling;
  257. }
  258. else
  259. {
  260. pxdnChild->Release();
  261. pxdnChild = NULL;
  262. }
  263. };
  264. if (SUCCEEDED(hr))
  265. {
  266. // Last success return code out of the loop would have
  267. // been S_FALSE.
  268. hr = S_OK;
  269. }
  270. TraceError("HrParseHeader(): "
  271. "Exiting",
  272. hr);
  273. return hr;
  274. }
  275. HRESULT
  276. HrParseBody(
  277. IXMLDOMNode * pxdnBody)
  278. {
  279. HRESULT hr = S_OK;
  280. // Find the action node. This is the first child of the <Body> node.
  281. IXMLDOMNode * pxdnAction = NULL;
  282. hr = pxdnBody->get_firstChild(&pxdnAction);
  283. if (SUCCEEDED(hr) && pxdnAction)
  284. {
  285. BSTR bstrActionName = NULL;
  286. hr = pxdnAction->get_baseName(&bstrActionName);
  287. if (SUCCEEDED(hr) && bstrActionName)
  288. {
  289. // Copy the action name into the shared data.
  290. DWORD cch;
  291. cch = SysStringLen(bstrActionName) + 1;
  292. WideCharToMultiByte(CP_ACP, 0, bstrActionName,
  293. cch,
  294. (LPSTR)g_pdata->szAction,
  295. cch, NULL, NULL);
  296. // Copy each of the action arguments into the shared data.
  297. IXMLDOMNode * pxdnArgument = NULL;
  298. hr = pxdnAction->get_firstChild(&pxdnArgument);
  299. while (SUCCEEDED(hr) && pxdnArgument)
  300. {
  301. BSTR bstrArgText = NULL;
  302. hr = pxdnArgument->get_text(&bstrArgText);
  303. if (SUCCEEDED(hr) && bstrArgText)
  304. {
  305. DWORD cch;
  306. cch = SysStringLen(bstrArgText) + 1;
  307. WideCharToMultiByte(CP_ACP, 0, bstrArgText,
  308. cch,
  309. (LPSTR)g_pdata->rgArgs[g_pdata->cArgs].szValue,
  310. cch, NULL, NULL);
  311. g_pdata->cArgs++;
  312. SysFreeString(bstrArgText);
  313. }
  314. else
  315. {
  316. if (SUCCEEDED(hr))
  317. {
  318. hr = E_FAIL;
  319. }
  320. TraceError("HrParseBody(): "
  321. "Failed to get argument text",
  322. hr);
  323. }
  324. if (SUCCEEDED(hr))
  325. {
  326. IXMLDOMNode * pxdnNextArgument = NULL;
  327. hr = pxdnArgument->get_nextSibling(&pxdnNextArgument);
  328. pxdnArgument->Release();
  329. pxdnArgument = pxdnNextArgument;
  330. }
  331. else
  332. {
  333. pxdnArgument->Release();
  334. pxdnArgument = NULL;
  335. }
  336. }
  337. if (SUCCEEDED(hr))
  338. {
  339. hr = S_OK;
  340. }
  341. SysFreeString(bstrActionName);
  342. }
  343. else
  344. {
  345. if (SUCCEEDED(hr))
  346. {
  347. hr = E_FAIL;
  348. }
  349. TraceError("HrParseBody(): "
  350. "Failed to get action name",
  351. hr);
  352. }
  353. pxdnAction->Release();
  354. }
  355. else
  356. {
  357. if (SUCCEEDED(hr))
  358. {
  359. hr = E_FAIL;
  360. }
  361. TraceError("HrParseBody(): "
  362. "Failed to get action node",
  363. hr);
  364. }
  365. TraceError("HrParseBody(): "
  366. "Exiting",
  367. hr);
  368. return hr;
  369. }
  370. HRESULT
  371. HrParseAction(
  372. IXMLDOMNode * pxdnSOAPEnvelope)
  373. {
  374. HRESULT hr = S_OK;
  375. IXMLDOMNode * pxdnChild = NULL;
  376. hr = pxdnSOAPEnvelope->get_firstChild(&pxdnChild);
  377. while (SUCCEEDED(hr) && pxdnChild)
  378. {
  379. IXMLDOMNode * pxdnNextSibling = NULL;
  380. BSTR bstrBaseName = NULL;
  381. hr = pxdnChild->get_baseName(&bstrBaseName);
  382. if (SUCCEEDED(hr) && bstrBaseName)
  383. {
  384. if (wcscmp(bstrBaseName, L"Header") == 0)
  385. {
  386. TraceTag(ttidIsapiCtl,
  387. "HrParseAction(): "
  388. "Parsing Header node");
  389. hr = HrParseHeader(pxdnChild);
  390. }
  391. else if (wcscmp(bstrBaseName, L"Body") == 0)
  392. {
  393. TraceTag(ttidIsapiCtl,
  394. "HrParseAction(): "
  395. "Parsing Body node");
  396. hr = HrParseBody(pxdnChild);
  397. }
  398. else
  399. {
  400. // Found an unknown node. This SOAP request is not valid.
  401. TraceTag(ttidIsapiCtl,
  402. "HrParseAction(): "
  403. "Found unknown node \"%S\"",
  404. bstrBaseName);
  405. hr = E_FAIL;
  406. }
  407. SysFreeString(bstrBaseName);
  408. }
  409. else
  410. {
  411. if (SUCCEEDED(hr))
  412. {
  413. hr = E_FAIL;
  414. }
  415. TraceError("HrParseAction(): "
  416. "Failed to get node base name",
  417. hr);
  418. }
  419. if (SUCCEEDED(hr))
  420. {
  421. hr = pxdnChild->get_nextSibling(&pxdnNextSibling);
  422. pxdnChild->Release();
  423. pxdnChild = pxdnNextSibling;
  424. }
  425. else
  426. {
  427. pxdnChild->Release();
  428. pxdnChild = NULL;
  429. }
  430. };
  431. if (SUCCEEDED(hr))
  432. {
  433. // Last success return code out of the loop would have
  434. // been S_FALSE.
  435. hr = S_OK;
  436. }
  437. TraceError("HrParseAction(): "
  438. "Exiting",
  439. hr);
  440. return hr;
  441. }
  442. HRESULT HrLoadArgsFromXml(LPCWSTR szXml)
  443. {
  444. VARIANT_BOOL vbSuccess;
  445. HRESULT hr = S_OK;
  446. IXMLDOMDocument * pxmlDoc;
  447. IXMLDOMNodeList * pNodeList = NULL;
  448. IXMLDOMNode * pNode = NULL;
  449. IXMLDOMNode * pNext = NULL;
  450. hr = CoCreateInstance(CLSID_DOMDocument30, NULL, CLSCTX_INPROC_SERVER,
  451. IID_IXMLDOMDocument, (LPVOID *)&pxmlDoc);
  452. if (SUCCEEDED(hr))
  453. {
  454. hr = pxmlDoc->put_async(VARIANT_FALSE);
  455. if (SUCCEEDED(hr))
  456. {
  457. hr = pxmlDoc->loadXML((BSTR)szXml, &vbSuccess);
  458. if (SUCCEEDED(hr))
  459. {
  460. IXMLDOMElement * pxde;
  461. hr = pxmlDoc->get_documentElement(&pxde);
  462. if (S_OK == hr)
  463. {
  464. hr = HrParseAction(pxde);
  465. ReleaseObj(pxde);
  466. }
  467. }
  468. }
  469. ReleaseObj(pxmlDoc);
  470. }
  471. TraceError("HrLoadArgsFromXml", hr);
  472. return hr;
  473. }
  474. DWORD DwProcessXoapRequest(LPSTR szUri, DWORD cbData, LPBYTE pbData)
  475. {
  476. LPSTR szData = (LPSTR)pbData;
  477. UPNP_PROPERTY * rgProps;
  478. DWORD cProps;
  479. DWORD dwReturn = 0;
  480. HRESULT hr;
  481. // Must acquire shared-memory mutex first
  482. //
  483. if (WAIT_OBJECT_0 == WaitForSingleObject(g_hMutex, INFINITE))
  484. {
  485. TraceTag(ttidIsapiCtl, "Acquired mutex...");
  486. ZeroMemory(g_pdata, sizeof(SHARED_DATA));
  487. LPSTR szAnsi;
  488. LPWSTR wszXmlBody;
  489. szAnsi = new CHAR[cbData + 1];
  490. CopyMemory(szAnsi, pbData, cbData);
  491. szAnsi[cbData] = 0;
  492. wszXmlBody = WszFromSz(szAnsi);
  493. TraceTag(ttidIsapiCtl, "URI = %s: Data = %s.", szUri, szAnsi);
  494. delete [] szAnsi;
  495. hr = HrLoadArgsFromXml(wszXmlBody);
  496. TraceError("DwProcessXoapRequest: HrLoadArgsFromXml", hr);
  497. // Done with changes to shared memory
  498. TraceTag(ttidIsapiCtl, "Releasing mutex...");
  499. ReleaseMutex(g_hMutex);
  500. if (S_OK == hr)
  501. {
  502. // Copy in event source URI
  503. lstrcpyA(g_pdata->szEventSource, szUri);
  504. TraceTag(ttidIsapiCtl, "Setting event...");
  505. // Now tell the device we're ready for it to process
  506. SetEvent(g_hEvent);
  507. TraceTag(ttidIsapiCtl, "Waiting for return event...");
  508. // Immediately wait on event again for return response
  509. if (WAIT_OBJECT_0 == WaitForSingleObject(g_hEventRet, INFINITE))
  510. {
  511. dwReturn = g_pdata->dwReturn;
  512. TraceTag(ttidIsapiCtl, "Setting return value to %d.", dwReturn);
  513. }
  514. }
  515. else
  516. {
  517. // On failure, we don't even need to signal the device. This
  518. // XOAP request wasn't properly formed or we couldn't process
  519. // it anyway
  520. //
  521. TraceError("DwProcessXoapRequest - failed to load args from XML", hr);
  522. dwReturn = DwWin32ErrorFromHr(hr);
  523. }
  524. // And we're done!
  525. }
  526. return dwReturn;
  527. }
  528. HRESULT HrComposeXoapResponse(DWORD dwValue, LPSTR *pszOut)
  529. {
  530. HRESULT hr = S_OK;
  531. LPSTR szOut;
  532. CHAR szBuffer[1024];
  533. Assert(pszOut);
  534. // $ BUGBUG SPATHER Response should be namespace-qualified.
  535. wsprintfA(szBuffer,
  536. "<SOAP:Envelope xmlns:SOAP=\"urn:schemas-xmlsoap-org:soap.v1\">\r\n"
  537. " <SOAP:Body>\r\n"
  538. " <%sResponse>\r\n"
  539. " <return>%d</return>\r\n"
  540. " </%sResponse>\r\n"
  541. " </SOAP:Body>\r\n"
  542. "</SOAP:Envelope>",
  543. g_pdata->szAction,
  544. dwValue,
  545. g_pdata->szAction);
  546. szOut = SzaDupSza(szBuffer);
  547. if (szOut)
  548. {
  549. *pszOut = szOut;
  550. }
  551. else
  552. {
  553. hr = E_OUTOFMEMORY;
  554. }
  555. TraceError("HrComposeXoapResponse", hr);
  556. return hr;
  557. }
  558. DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK pecb)
  559. {
  560. DWORD hseStatus = HSE_STATUS_SUCCESS;
  561. if (g_fInited)
  562. {
  563. DWORD dwReturn;
  564. HRESULT hr;
  565. if (!lstrcmpiA(pecb->lpszMethod, "M-POST"))
  566. {
  567. LPSTR szResponse;
  568. DWORD cbResponse;
  569. // This was a post request so it's a XOAP control request
  570. TraceTag(ttidIsapiCtl, "Received 'M-POST' request");
  571. // The URI of the event source will be the query string
  572. dwReturn = DwProcessXoapRequest(pecb->lpszQueryString,
  573. pecb->cbAvailable,
  574. pecb->lpbData);
  575. // Send XOAP response with dwReturn
  576. TraceTag(ttidIsapiCtl, "Sending XOAP response for %d.", dwReturn);
  577. hr = HrComposeXoapResponse(dwReturn, &szResponse);
  578. if (S_OK == hr)
  579. {
  580. cbResponse = lstrlenA(szResponse);
  581. TraceTag(ttidIsapiCtl, "Writing XOAP response: %s.", szResponse);
  582. pecb->WriteClient(pecb->ConnID, (LPVOID)szResponse,
  583. &cbResponse, 0);
  584. free(szResponse);
  585. }
  586. }
  587. else if (!lstrcmpiA(pecb->lpszMethod, "POST"))
  588. {
  589. HSE_SEND_HEADER_EX_INFO hse;
  590. LPSTR szResponse = "";
  591. DWORD cbResponse;
  592. LPSTR szStatus = "405 Method Not Allowed";
  593. DWORD cbStatus;
  594. TraceTag(ttidIsapiCtl, "Received 'POST' request");
  595. TraceTag(ttidIsapiCtl, "Data = %s.", pecb->lpbData);
  596. ZeroMemory(&hse, sizeof(HSE_SEND_HEADER_EX_INFO));
  597. cbResponse = lstrlenA(szResponse);
  598. cbStatus = lstrlenA(szStatus);
  599. hse.pszStatus = szStatus;// here you should print "405 Method Not Allowed"
  600. hse.pszHeader = szResponse; // this one should be empty for http errors
  601. hse.cchStatus = cbStatus;
  602. hse.cchHeader = cbResponse;
  603. hse.fKeepConn = FALSE;
  604. pecb->dwHttpStatusCode = HTTP_STATUS_BAD_METHOD;
  605. pecb->ServerSupportFunction(pecb->ConnID,
  606. HSE_REQ_SEND_RESPONSE_HEADER_EX,
  607. (LPVOID)&hse,
  608. NULL,
  609. NULL);
  610. }
  611. else
  612. {
  613. TraceTag(ttidIsapiCtl, "Data = %s.", pecb->lpbData);
  614. pecb->dwHttpStatusCode = HTTP_STATUS_BAD_METHOD;
  615. TraceTag(ttidIsapiCtl, "ISAPICTL: Received bad method '%s' request.",
  616. pecb->lpszMethod);
  617. hseStatus = HSE_STATUS_ERROR;
  618. }
  619. }
  620. else
  621. {
  622. TraceTag(ttidIsapiCtl, "Not initialized!");
  623. }
  624. return hseStatus;
  625. }
  626. BOOL WINAPI TerminateExtension(DWORD dwFlags)
  627. {
  628. TraceTag(ttidIsapiCtl, "TerminateExtension: Exiting...");
  629. if (g_pdata)
  630. {
  631. UnmapViewOfFile((LPVOID)g_pdata);
  632. }
  633. if (g_hMapFile)
  634. {
  635. CloseHandle(g_hMapFile);
  636. }
  637. if (g_hEvent)
  638. {
  639. CloseHandle(g_hEvent);
  640. }
  641. if (g_hEventRet)
  642. {
  643. CloseHandle(g_hEvent);
  644. }
  645. if (g_hMutex)
  646. {
  647. CloseHandle(g_hMutex);
  648. }
  649. return TRUE;
  650. }