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.

739 lines
23 KiB

  1. #include <urlmon.h>
  2. #include <mshtmdid.h>
  3. #include <mshtml.h>
  4. #include <shlobj.h>
  5. #include "htmparse.h"
  6. CHTMLParser::CHTMLParser()
  7. {
  8. m_cRef = 1;
  9. m_hrConnected = CONNECT_E_CANNOTCONNECT;
  10. m_dwCookie = 0;
  11. m_pCP = NULL;
  12. m_pMSHTML = NULL;
  13. m_hEventTridentDone = 0;
  14. }
  15. CHTMLParser::~CHTMLParser()
  16. {
  17. if (m_pMSHTML)
  18. m_pMSHTML->Release();
  19. }
  20. /////////////////////////////////////////////////////////////
  21. /////////////////////////////////////////////////////////////
  22. /////////////////////////////////////////////////////////////
  23. /////// IUnknown implementation
  24. ///////
  25. ///////
  26. STDMETHODIMP CHTMLParser::QueryInterface(REFIID riid, LPVOID* ppv)
  27. {
  28. *ppv = NULL;
  29. if (IID_IUnknown == riid || IID_IPropertyNotifySink == riid)
  30. {
  31. *ppv = (LPUNKNOWN)(IPropertyNotifySink*)this;
  32. AddRef();
  33. return NOERROR;
  34. }
  35. else if (IID_IOleClientSite == riid)
  36. {
  37. *ppv = (IOleClientSite*)this;
  38. AddRef();
  39. return NOERROR;
  40. }
  41. else if (IID_IDispatch == riid)
  42. {
  43. *ppv = (IDispatch*)this;
  44. AddRef();
  45. return NOERROR;
  46. }
  47. else
  48. {
  49. return E_NOTIMPL;
  50. }
  51. }
  52. STDMETHODIMP_(ULONG) CHTMLParser::AddRef()
  53. {
  54. return ++m_cRef;
  55. }
  56. STDMETHODIMP_(ULONG) CHTMLParser::Release()
  57. {
  58. if (!(--m_cRef))
  59. delete this;
  60. return m_cRef;
  61. }
  62. /////////////////////////////////////////////////////////////
  63. /////////////////////////////////////////////////////////////
  64. /////////////////////////////////////////////////////////////
  65. /////// IPropertyNotifySink implementation
  66. ///////
  67. ///////
  68. // Fired on change of the value of a 'bindable' property
  69. STDMETHODIMP CHTMLParser::OnChanged(DISPID dispID)
  70. {
  71. if (DISPID_READYSTATE == dispID)
  72. {
  73. EXCEPINFO excepInfo;
  74. UINT uArgErr;
  75. VARIANT varResult = {0};
  76. DISPPARAMS dispparams = {NULL, NULL, 0, 0};
  77. // check the value of the readystate property
  78. assert(m_pMSHTML);
  79. if (SUCCEEDED(m_pMSHTML->Invoke(DISPID_READYSTATE,
  80. IID_NULL,
  81. LOCALE_SYSTEM_DEFAULT,
  82. DISPATCH_PROPERTYGET,
  83. &dispparams,
  84. &varResult,
  85. &excepInfo,
  86. &uArgErr)))
  87. {
  88. assert(VT_I4 == V_VT(&varResult));
  89. if (READYSTATE_COMPLETE == (READYSTATE)V_I4(&varResult))
  90. SetEvent(m_hEventTridentDone);
  91. VariantClear(&varResult);
  92. }
  93. }
  94. return NOERROR;
  95. }
  96. STDMETHODIMP CHTMLParser::OnRequestEdit(DISPID dispID)
  97. {
  98. return NOERROR;
  99. }
  100. /////////////////////////////////////////////////////////////
  101. /////////////////////////////////////////////////////////////
  102. /////////////////////////////////////////////////////////////
  103. /////// IOleClientSite implementation
  104. ///////
  105. ///////
  106. STDMETHODIMP CHTMLParser::SaveObject()
  107. {
  108. return E_NOTIMPL;
  109. }
  110. STDMETHODIMP CHTMLParser::GetMoniker(DWORD dwAssign, DWORD dwWhichMoniker, IMoniker** ppmk)
  111. {
  112. return E_NOTIMPL;
  113. }
  114. STDMETHODIMP CHTMLParser::GetContainer(IOleContainer** ppContainer)
  115. {
  116. return E_NOTIMPL;
  117. }
  118. STDMETHODIMP CHTMLParser::ShowObject()
  119. {
  120. return E_NOTIMPL;
  121. }
  122. STDMETHODIMP CHTMLParser::OnShowWindow(BOOL fShow)
  123. {
  124. return E_NOTIMPL;
  125. }
  126. STDMETHODIMP CHTMLParser::RequestNewObjectLayout()
  127. {
  128. return E_NOTIMPL;
  129. }
  130. /////////////////////////////////////////////////////////////
  131. /////////////////////////////////////////////////////////////
  132. /////////////////////////////////////////////////////////////
  133. /////// IDispatch implementation
  134. ///////
  135. ///////
  136. STDMETHODIMP CHTMLParser::GetTypeInfoCount(UINT* pctinfo)
  137. {
  138. return E_NOTIMPL;
  139. }
  140. STDMETHODIMP CHTMLParser::GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo** ppTInfo)
  141. {
  142. return E_NOTIMPL;
  143. }
  144. STDMETHODIMP CHTMLParser::GetIDsOfNames(REFIID riid, LPOLESTR* rgszNames, UINT cNames, LCID lcid, DISPID* rgDispId)
  145. {
  146. return E_NOTIMPL;
  147. }
  148. // MSHTML Queries for the IDispatch interface of the host through the IOleClientSite
  149. // interface that MSHTML is passed through its implementation of IOleObject::SetClientSite()
  150. STDMETHODIMP CHTMLParser::Invoke(DISPID dispIdMember,
  151. REFIID riid,
  152. LCID lcid,
  153. WORD wFlags,
  154. DISPPARAMS* pDispParams,
  155. VARIANT* pvarResult,
  156. EXCEPINFO* pExcepInfo,
  157. UINT* puArgErr)
  158. {
  159. if (!pvarResult)
  160. return E_POINTER;
  161. switch(dispIdMember)
  162. {
  163. case DISPID_AMBIENT_DLCONTROL:
  164. {
  165. // respond to this ambient to indicate that we only want to
  166. // download the page, but we don't want to run scripts,
  167. // Java applets, or ActiveX controls
  168. V_VT(pvarResult) = VT_I4;
  169. V_I4(pvarResult) = DLCTL_DOWNLOADONLY |
  170. DLCTL_NO_SCRIPTS |
  171. DLCTL_NO_JAVA |
  172. DLCTL_NO_DLACTIVEXCTLS |
  173. DLCTL_NO_RUNACTIVEXCTLS;
  174. break;
  175. }
  176. default:
  177. return DISP_E_MEMBERNOTFOUND;
  178. }
  179. return NOERROR;
  180. }
  181. // A more traditional form of persistence.
  182. // MSHTML performs this asynchronously as well.
  183. HRESULT CHTMLParser::LoadURLFromFile(BSTR bstrURL)
  184. {
  185. HRESULT hr;
  186. LPPERSISTFILE pPF;
  187. // MSHTML supports file persistence for ordinary files.
  188. if ( SUCCEEDED(hr = m_pMSHTML->QueryInterface(IID_IPersistFile, (LPVOID*) &pPF)))
  189. {
  190. hr = pPF->Load(bstrURL, 0);
  191. pPF->Release();
  192. }
  193. return hr;
  194. }
  195. // This function will attached trient to a location FILE: URL, and ensure that it is ready
  196. // to be walked
  197. HRESULT CHTMLParser::InitForMSHTML()
  198. {
  199. HRESULT hr;
  200. LPCONNECTIONPOINTCONTAINER pCPC = NULL;
  201. LPOLEOBJECT pOleObject = NULL;
  202. LPOLECONTROL pOleControl = NULL;
  203. // Create an instance of an dynamic HTML document
  204. if (FAILED(hr = CoCreateInstance( CLSID_HTMLDocument, NULL,
  205. CLSCTX_INPROC_SERVER, IID_IHTMLDocument2,
  206. (LPVOID*)&m_pTrident )))
  207. {
  208. goto Error;
  209. }
  210. if (FAILED(hr = m_pTrident->QueryInterface(IID_IOleObject, (LPVOID*)&pOleObject)))
  211. {
  212. goto Error;
  213. }
  214. hr = pOleObject->SetClientSite((IOleClientSite*)this);
  215. pOleObject->Release();
  216. if (FAILED(hr = m_pTrident->QueryInterface(IID_IOleControl, (LPVOID*)&pOleControl)))
  217. {
  218. goto Error;
  219. }
  220. hr = pOleControl->OnAmbientPropertyChange(DISPID_AMBIENT_DLCONTROL);
  221. pOleControl->Release();
  222. // Hook up sink to catch ready state property change
  223. if (FAILED(hr = m_pTrident->QueryInterface(IID_IConnectionPointContainer, (LPVOID*)&pCPC)))
  224. {
  225. goto Error;
  226. }
  227. if (FAILED(hr = pCPC->FindConnectionPoint(IID_IPropertyNotifySink, &m_pCP)))
  228. {
  229. goto Error;
  230. }
  231. m_hrConnected = m_pCP->Advise((LPUNKNOWN)(IPropertyNotifySink*)this, &m_dwCookie);
  232. Error:
  233. if (pCPC)
  234. pCPC->Release();
  235. return hr;
  236. }
  237. // Clean up connection point
  238. HRESULT CHTMLParser::TermForMSHTML()
  239. {
  240. HRESULT hr = NOERROR;
  241. // Disconnect from property change notifications
  242. if (SUCCEEDED(m_hrConnected))
  243. {
  244. hr = m_pCP->Unadvise(m_dwCookie);
  245. }
  246. // Release the connection point
  247. if (m_pCP)
  248. m_pCP->Release();
  249. if (m_pTrident)
  250. m_pTrident->Release();
  251. return hr;
  252. }
  253. HRESULT CHTMLParser::AttachToMSHTML(BSTR bstrURL)
  254. {
  255. HRESULT hr;
  256. // Release any previous instance of the HTML document pointer we might be holding on to
  257. if(m_pMSHTML)
  258. {
  259. m_pMSHTML->Release();
  260. m_pMSHTML = NULL;
  261. }
  262. m_pMSHTML = m_pTrident;
  263. m_pMSHTML->AddRef();
  264. m_hEventTridentDone = CreateEvent(NULL, TRUE, FALSE, NULL);
  265. hr = LoadURLFromFile(bstrURL);
  266. if (SUCCEEDED(hr) || (E_PENDING == hr))
  267. {
  268. if (m_hEventTridentDone)
  269. {
  270. MSG msg;
  271. DWORD dwRetCode;
  272. HANDLE hEventList[1];
  273. hEventList[0] = m_hEventTridentDone;
  274. while (TRUE)
  275. {
  276. // We will wait on window messages and also the named event.
  277. dwRetCode = MsgWaitForMultipleObjects(1,
  278. &hEventList[0],
  279. FALSE,
  280. 300000, // 5 minutes
  281. QS_ALLINPUT);
  282. // Determine why we came out of MsgWaitForMultipleObjects(). If
  283. // we timed out then let's do some TrialWatcher work. Otherwise
  284. // process the message that woke us up.
  285. if (WAIT_TIMEOUT == dwRetCode)
  286. {
  287. break;
  288. }
  289. else if (WAIT_OBJECT_0 == dwRetCode)
  290. {
  291. break;
  292. }
  293. else if (WAIT_OBJECT_0 + 1 == dwRetCode)
  294. {
  295. // Process all messages in the Queue, since MsgWaitForMultipleObjects
  296. // will not do this for us
  297. while (TRUE)
  298. {
  299. if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
  300. {
  301. if (WM_QUIT == msg.message)
  302. {
  303. break;
  304. }
  305. else
  306. {
  307. TranslateMessage(&msg);
  308. DispatchMessage(&msg);
  309. }
  310. }
  311. else
  312. {
  313. break;
  314. }
  315. }
  316. }
  317. }
  318. CloseHandle(m_hEventTridentDone);
  319. m_hEventTridentDone = 0;
  320. }
  321. else
  322. {
  323. // If we were pending, and we could not wait, we got a problem...
  324. if(E_PENDING == hr)
  325. hr = E_FAIL;
  326. }
  327. }
  328. return (hr);
  329. }
  330. HRESULT CHTMLParser::AttachToDocument(IWebBrowser2 *lpWebBrowser)
  331. {
  332. HRESULT hr;
  333. LPDISPATCH pDisp;
  334. // Release any previous instance of the HTML document pointer we might be holding on to
  335. if(m_pMSHTML)
  336. {
  337. // If the m_pMSHMTL is NOT our internal Trident object (for walking files)
  338. // then sombody did not do a detach, so we need to release the previous
  339. // MSHTML object
  340. if (m_pMSHTML != m_pTrident)
  341. m_pMSHTML->Release();
  342. m_pMSHTML = NULL;
  343. }
  344. // Make sure we have a webbrowser to grab onto
  345. assert(lpWebBrowser);
  346. // Get the document pointer from this webbrowser.
  347. if (SUCCEEDED(hr = lpWebBrowser->get_Document(&pDisp)))
  348. {
  349. if (pDisp)
  350. {
  351. hr = pDisp->QueryInterface( IID_IHTMLDocument2, (LPVOID*)&m_pMSHTML );
  352. // Paranoia, but trident/shdocvw might say OK, but really not give us a document
  353. if (!m_pMSHTML)
  354. hr = E_FAIL;
  355. pDisp->Release();
  356. }
  357. else
  358. {
  359. hr = E_FAIL;
  360. }
  361. }
  362. return (hr);
  363. }
  364. HRESULT CHTMLParser::Detach()
  365. {
  366. if(m_pMSHTML)
  367. {
  368. // If the m_pMSHMTL is NOT our internal Trident object (for walking files)
  369. // then sombody did not do a detach, so we need to release the previous
  370. // MSHTML object
  371. if (m_pMSHTML != m_pTrident)
  372. m_pMSHTML->Release();
  373. m_pMSHTML = NULL;
  374. }
  375. return S_OK;
  376. }
  377. HRESULT CHTMLParser::ConcatURLValue(BSTR bstrValue, BSTR bstrName, WCHAR* lpszQuery)
  378. {
  379. if(bstrName)
  380. {
  381. // Append the Name
  382. lstrcat(lpszQuery, bstrName);
  383. lstrcat(lpszQuery, cszEquals);
  384. if(bstrValue)
  385. {
  386. //we need to be three times as big since 1 char decoded == 3 char encoded
  387. size_t cch = (lstrlen(bstrValue) + 1) * 3;
  388. WCHAR* szVal = (WCHAR*)malloc(BYTES_REQUIRED_BY_CCH(cch));
  389. lstrcpy(szVal, bstrValue);
  390. URLEncode(szVal, cch);
  391. lstrcat(lpszQuery, szVal);
  392. free(szVal);
  393. SysFreeString(bstrValue);
  394. }
  395. lstrcat(lpszQuery, cszAmpersand);
  396. SysFreeString(bstrName);
  397. }
  398. return S_OK;
  399. }
  400. HRESULT CHTMLParser::CreateQueryString
  401. (
  402. IHTMLFormElement *pForm,
  403. LPWSTR lpszQuery
  404. )
  405. {
  406. VARIANT vIndex;
  407. HRESULT hr = E_FAIL;
  408. long lFormLength = 0;
  409. VARIANT var2 = { 0 };
  410. LPDISPATCH pDisp = NULL;
  411. IHTMLButtonElement* pButton = NULL;
  412. IHTMLInputButtonElement* pInputButton = NULL;
  413. IHTMLInputFileElement* pInputFile = NULL;
  414. IHTMLInputHiddenElement* pInputHidden = NULL;
  415. IHTMLInputTextElement* pInputText = NULL;
  416. IHTMLSelectElement* pSelect = NULL;
  417. IHTMLTextAreaElement* pTextArea = NULL;
  418. IHTMLOptionButtonElement* pOptionButton = NULL;
  419. BSTR bstrName = NULL;
  420. BSTR bstrValue = NULL;
  421. vIndex.vt = VT_UINT;
  422. if (SUCCEEDED(pForm->get_length(&lFormLength)))
  423. {
  424. for (int i = 0; i < lFormLength; i++)
  425. {
  426. vIndex.lVal = i;
  427. if (SUCCEEDED(hr = pForm->item( vIndex, var2, &pDisp )))
  428. {
  429. if (SUCCEEDED(hr = pDisp->QueryInterface( IID_IHTMLInputHiddenElement, (LPVOID*)&pInputHidden )))
  430. {
  431. //We could take out the repetative calls to get_name/get_value but that would require
  432. //us to make a sketchy cast
  433. if (SUCCEEDED(pInputHidden->get_name(&bstrName)) &&
  434. SUCCEEDED(pInputHidden->get_value(&bstrValue)))
  435. {
  436. ConcatURLValue(bstrValue, bstrName, lpszQuery);
  437. }
  438. // Release the interface pointer
  439. pInputHidden->Release();
  440. continue;
  441. }
  442. if (SUCCEEDED(hr = pDisp->QueryInterface( IID_IHTMLInputTextElement, (LPVOID*)&pInputText )))
  443. {
  444. if (SUCCEEDED(pInputText->get_name(&bstrName)) &&
  445. SUCCEEDED(pInputText->get_value(&bstrValue)) )
  446. {
  447. ConcatURLValue(bstrValue, bstrName, lpszQuery);
  448. }
  449. // Release the interface pointer
  450. pInputText->Release();
  451. continue;
  452. }
  453. if (SUCCEEDED(hr = pDisp->QueryInterface( IID_IHTMLSelectElement, (LPVOID*)&pSelect )))
  454. {
  455. if (SUCCEEDED(pSelect->get_name(&bstrName)) &&
  456. SUCCEEDED(pSelect->get_value(&bstrValue)) )
  457. {
  458. ConcatURLValue(bstrValue, bstrName, lpszQuery);
  459. }
  460. // Release the interface pointer
  461. pSelect->Release();
  462. continue;
  463. }
  464. if (SUCCEEDED(hr = pDisp->QueryInterface( IID_IHTMLTextAreaElement, (LPVOID*)&pTextArea )))
  465. {
  466. if (SUCCEEDED(pTextArea->get_name(&bstrName)) &&
  467. SUCCEEDED(pTextArea->get_value(&bstrValue)) )
  468. {
  469. ConcatURLValue(bstrValue, bstrName, lpszQuery);
  470. }
  471. // Release the interface pointer
  472. pTextArea->Release();
  473. }
  474. // First check to see if this is an OptionButton.
  475. if (SUCCEEDED(hr = pDisp->QueryInterface( IID_IHTMLOptionButtonElement, (LPVOID*)&pOptionButton )))
  476. {
  477. BSTR bstr = NULL;
  478. // See if it is a Radio or a CheckBox
  479. if (SUCCEEDED(pOptionButton->get_type(&bstr)))
  480. {
  481. LPWSTR lpszType = bstr;
  482. if ((lstrcmpi(lpszType, L"radio") == 0) || (lstrcmpi(lpszType, L"checkbox") == 0))
  483. {
  484. short bChecked;
  485. // See if the button is checked. If it is, then it needs to be
  486. // added to the query string
  487. if (SUCCEEDED(pOptionButton->get_checked(&bChecked)))
  488. {
  489. if(bChecked)
  490. {
  491. if ( SUCCEEDED(pOptionButton->get_name(&bstrName)) &&
  492. SUCCEEDED(pOptionButton->get_value(&bstrValue)) )
  493. {
  494. ConcatURLValue(bstrValue, bstrName, lpszQuery);
  495. }
  496. }
  497. }
  498. }
  499. SysFreeString(bstr);
  500. }
  501. // Release the interface
  502. pOptionButton->Release();
  503. continue;
  504. }
  505. // For the rest we need to form Name=Value pairs
  506. if (SUCCEEDED(hr = pDisp->QueryInterface( IID_IHTMLButtonElement, (LPVOID*)&pButton )))
  507. {
  508. if (SUCCEEDED(pButton->get_name(&bstrName)) &&
  509. SUCCEEDED(pButton->get_value(&bstrValue)) )
  510. {
  511. ConcatURLValue(bstrValue, bstrName, lpszQuery);
  512. }
  513. // Release the interface pointer
  514. pButton->Release();
  515. continue;
  516. }
  517. if (SUCCEEDED(hr = pDisp->QueryInterface( IID_IHTMLInputFileElement, (LPVOID*)&pInputFile )))
  518. {
  519. if (SUCCEEDED(pInputFile->get_name(&bstrName)) &&
  520. SUCCEEDED(pInputFile->get_value(&bstrValue)) )
  521. {
  522. ConcatURLValue(bstrValue, bstrName, lpszQuery);
  523. }
  524. // Release the interface pointer
  525. pInputFile->Release();
  526. continue;
  527. }
  528. pDisp->Release();
  529. }
  530. }
  531. }
  532. // Null out the last Ampersand, since we don't know when we added the last pair, so we got
  533. // a trailing ampersand
  534. lpszQuery[lstrlen(lpszQuery)-1] = L'\0';
  535. return S_OK;
  536. }
  537. HRESULT CHTMLParser::get_QueryStringForForm(IDispatch* pDisp, WCHAR* szUrl)
  538. {
  539. HRESULT hr = E_FAIL; //don't assume succeess
  540. WCHAR szQuery [MAX_PATH*7] = L"\0";
  541. IHTMLFormElement* pForm = NULL;
  542. BSTR bstrAction = NULL;
  543. if (!pDisp)
  544. return (E_FAIL);
  545. if(SUCCEEDED(pDisp->QueryInterface(IID_IHTMLFormElement, (void**)&pForm)) && pForm)
  546. {
  547. // Get the Action for the Next Form
  548. if (SUCCEEDED(pForm->get_action(&bstrAction)) && bstrAction)
  549. {
  550. lstrcpy(szUrl, bstrAction);
  551. lstrcat(szUrl, cszQuestion);
  552. SysFreeString(bstrAction);
  553. // Get the Query String
  554. if (SUCCEEDED(CreateQueryString(pForm, szQuery)))
  555. {
  556. lstrcat(szUrl, szQuery);
  557. }
  558. }
  559. }
  560. return hr;
  561. }
  562. void CHTMLParser::URLEncode(WCHAR* pszUrl, size_t cchUrlMax)
  563. {
  564. assert(pszUrl);
  565. WCHAR* pszEncoded = NULL;
  566. WCHAR* pchEncoded = NULL;
  567. WCHAR* pchUrl = pszUrl + lstrlen(pszUrl);
  568. int cchUrl = (int)(pchUrl-pszUrl);
  569. WCHAR c;
  570. if ((size_t)(cchUrl * 3) < cchUrlMax)
  571. {
  572. pszEncoded = (WCHAR*)malloc(BYTES_REQUIRED_BY_CCH(cchUrl * 3 + 1));
  573. if(pszEncoded)
  574. {
  575. ZeroMemory(pszEncoded, BYTES_REQUIRED_BY_CCH(cchUrl * 3 + 1));
  576. for(pchUrl = pszUrl, pchEncoded = pszEncoded;
  577. L'\0' != *pchUrl;
  578. pchUrl++
  579. )
  580. {
  581. switch(*pchUrl)
  582. {
  583. case L' ': //SPACE
  584. lstrcpyn(pchEncoded, L"+", 1);
  585. pchEncoded+=1;
  586. break;
  587. case L'#':
  588. lstrcpyn(pchEncoded, L"%23", 3);
  589. pchEncoded+=3;
  590. break;
  591. case L'&':
  592. lstrcpyn(pchEncoded, L"%26", 3);
  593. pchEncoded+=3;
  594. break;
  595. case L'%':
  596. lstrcpyn(pchEncoded, L"%25", 3);
  597. pchEncoded+=3;
  598. break;
  599. case L'=':
  600. lstrcpyn(pchEncoded, L"%3D", 3);
  601. pchEncoded+=3;
  602. break;
  603. case L'<':
  604. lstrcpyn(pchEncoded, L"%3C", 3);
  605. pchEncoded+=3;
  606. break;
  607. case L'+':
  608. lstrcpyn(pchEncoded, L"%2B", 3);
  609. pchEncoded += 3;
  610. break;
  611. default:
  612. *pchEncoded++ = *pchUrl;
  613. break;
  614. }
  615. }
  616. // String should be null-terminated since the buffer was zeroed
  617. //
  618. ASSERT(L'\0' == *pchEncoded);
  619. // Did we overflow the buffer?
  620. //
  621. ASSERT(pchEncoded - pszEncoded < cchUrlMax);
  622. lstrcpy(pszUrl , pszEncoded);
  623. free(pszEncoded);
  624. }
  625. }
  626. }