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.

811 lines
20 KiB

  1. /*
  2. * @doc INTERNAL
  3. *
  4. * @module URLSUP.CPP URL detection support |
  5. *
  6. * Author: alexgo 4/3/96
  7. *
  8. * Copyright (c) 1995-1997, Microsoft Corporation. All rights reserved.
  9. */
  10. #include "_common.h"
  11. #include "_edit.h"
  12. #include "_urlsup.h"
  13. #include "_m_undo.h"
  14. #include "_select.h"
  15. #include "_clasfyc.h"
  16. ASSERTDATA
  17. // Arrays for URL detection. The first array is the protocols
  18. // we support, followed by the "size" of the array.
  19. // NB!! Do _not_ modify these arrays without first making sure
  20. // that the code in ::IsURL is updated appropriately.
  21. /*
  22. FUTURE (keithcu)
  23. We should generalize our support to recognize URLs of the following type:
  24. Maybe we should do autocorrect so that:
  25. keithcu@microsoft.com converts to mailto:keithcu@microsoft.com
  26. Should we put this code in PutChar rather than here?
  27. What about URLs of the form "seattle.sidewalk.com"? Word doesn't support this yet.
  28. It is hard because do you look for the .com? What happens when .com, .edu, .gov,
  29. etc. aren't the only suffixes anymore?
  30. What about the interaction with notifications?
  31. We should add support for purple text. CFE_LINKVISITED
  32. */
  33. //Includes both types of URLs
  34. const int MAXURLHDRSIZE = 9;
  35. //Most of these can just be passed right to the client--but some need a prefix.
  36. //Can we automatically add that tag when it needs it?
  37. const LPCWSTR rgszURL[] = {
  38. L"http:",
  39. L"file:",
  40. L"mailto:",
  41. L"ftp:",
  42. L"https:",
  43. L"gopher:",
  44. L"nntp:",
  45. L"prospero:",
  46. L"telnet:",
  47. L"news:",
  48. L"wais:",
  49. L"outlook:",
  50. L"callto:"
  51. };
  52. const char rgcchURL[] = {
  53. 5,
  54. 5,
  55. 7,
  56. 4,
  57. 6,
  58. 7,
  59. 5,
  60. 9,
  61. 7,
  62. 5,
  63. 5,
  64. 8,
  65. 7
  66. };
  67. #define NUMURLHDR sizeof(rgcchURL)
  68. //
  69. //The XXX. URLs
  70. //
  71. const LPCWSTR rgszDOTURL[] = {
  72. L"www.",
  73. L"ftp.",
  74. };
  75. const char rgcchDOTURL[] = {
  76. 4,
  77. 4,
  78. };
  79. #define NUMDOTURLHDR sizeof(rgcchDOTURL)
  80. inline BOOL IsURLWhiteSpace(WCHAR ch)
  81. {
  82. if (IsWhiteSpace(ch))
  83. return TRUE;
  84. // See RAID 6304. MSKK doesn't want CJK in URLs. We do what we did in 2.0
  85. if ( ch >= 0x03000 && !IsKorean(ch) )
  86. return TRUE;
  87. // GetKinsokuClass() takes an lcid for Win95, but for this case it doesn't
  88. // seem to be worth the perf loss since different code pages shouldn't
  89. // result in different results.
  90. INT iset = GetKinsokuClass(ch);
  91. return iset == 10 || (iset == 14 && ch != WCH_EMBEDDING);
  92. }
  93. /*
  94. * CDetectURL::CDetectURL (ped)
  95. *
  96. * @mfunc constructor; registers this class in the notification manager.
  97. *
  98. * @rdesc void
  99. */
  100. CDetectURL::CDetectURL(
  101. CTxtEdit *ped) //@parm edit context to use
  102. {
  103. CNotifyMgr *pnm = ped->GetNotifyMgr();
  104. if(pnm)
  105. pnm->Add((ITxNotify *)this);
  106. _ped = ped;
  107. }
  108. /*
  109. * CDetectURL::~CDetectURL
  110. *
  111. * @mfunc destructor; removes ths class from the notification manager
  112. */
  113. CDetectURL::~CDetectURL()
  114. {
  115. CNotifyMgr *pnm = _ped->GetNotifyMgr();
  116. if(pnm)
  117. pnm->Remove((ITxNotify *)this);
  118. }
  119. //
  120. // ITxNotify methods
  121. //
  122. /*
  123. * CDetectURL::OnPreRelaceRange(cp, cchDel, cchNew, cpFormatMin, cpFormatMax, pNotifyData)
  124. *
  125. * @mfunc called before a change is made
  126. */
  127. void CDetectURL::OnPreReplaceRange(
  128. LONG cp, //@parm cp where ReplaceRange starts ("cpMin")
  129. LONG cchDel, //@parm Count of chars after cp that are deleted
  130. LONG cchNew, //@parm Count of chars inserted after cp
  131. LONG cpFormatMin, //@parm cpMin for a formatting change
  132. LONG cpFormatMax, //@parm cpMost for a formatting change
  133. NOTIFY_DATA *pNotifyData) //@parm special data to indicate changes
  134. {
  135. ; // don't need to do anything here
  136. }
  137. /*
  138. * CDetectURL::OnPostReplaceRange(cp, cchDel, cchNew, cpFormatMin, cpFormatMax, pNotifyData)
  139. *
  140. * @mfunc called after a change has been made to the backing store. We
  141. * simply need to accumulate all such changes
  142. */
  143. void CDetectURL::OnPostReplaceRange(
  144. LONG cp, //@parm cp where ReplaceRange starts ("cpMin")
  145. LONG cchDel, //@parm Count of chars after cp that are deleted
  146. LONG cchNew, //@parm Count of chars inserted after cp
  147. LONG cpFormatMin, //@parm cpMin for a formatting change
  148. LONG cpFormatMax, //@parm cpMost for a formatting change
  149. NOTIFY_DATA *pNotifyData) //@parm special data to indicate changes
  150. {
  151. // We don't need to worry about format changes; just data changes
  152. // to the backing store
  153. if(cp != CP_INFINITE)
  154. {
  155. Assert(cp != CONVERT_TO_PLAIN);
  156. _adc.UpdateRecalcRegion(cp, cchDel, cchNew);
  157. }
  158. }
  159. /*
  160. * CDetectURL::Zombie ()
  161. *
  162. * @mfunc
  163. * Turn this object into a zombie
  164. */
  165. void CDetectURL::Zombie ()
  166. {
  167. }
  168. /*
  169. * CDetectURL::ScanAndUpdate(publdr)
  170. *
  171. * @mfunc scans the affect text, detecting new URL's and removing old ones.
  172. *
  173. * @comm The algorithm we use is straightforward: <nl>
  174. *
  175. * 1. find the update region and expand out to whitespace in either
  176. * direction. <nl>
  177. *
  178. * 2. Scan through region word by word (where word is contiguous
  179. * non-whitespace).
  180. *
  181. * 3. Strip these words off punctuation marks. This may be a bit
  182. * tricky as some of the punctuation may be part of the URL itself.
  183. * We assume that generally it's not, and if it is, one has to enclose
  184. * the URL in quotes, brackets or such. We stop stripping the
  185. * punctuation off the end as soon as we find the matching bracket.
  186. *
  187. * 4. If it's a URL, enable the effects, if it's
  188. * incorrectly labelled as a URL, disabled the effects.
  189. *
  190. * Note that this algorithm will only remove
  191. */
  192. void CDetectURL::ScanAndUpdate(
  193. IUndoBuilder *publdr) //@parm undo context to use
  194. {
  195. LONG cpStart, cpEnd, cp;
  196. CTxtSelection *psel = _ped->GetSel();
  197. CTxtRange rg(*psel);
  198. BOOL fCleanedThisURL;
  199. BOOL fCleanedSomeURL = FALSE;
  200. // Clear away some unnecessary features of the range that will
  201. // just slow us down.
  202. rg.SetIgnoreFormatUpdate(TRUE);
  203. rg._rpPF.SetToNull();
  204. if(!GetScanRegion(cpStart, cpEnd))
  205. return;
  206. rg.Set(cpStart, 0);
  207. while((cp = rg.GetCp()) < cpEnd)
  208. {
  209. Assert(rg.GetCch() == 0);
  210. if(rg.GetCF()->_dwEffects & CFE_LINKPROTECTED)
  211. {
  212. rg.Move(rg._rpCF.GetCchLeft(), FALSE); // Bypass run
  213. continue;
  214. }
  215. LONG cchAdvance;
  216. ExpandToURL(rg, cchAdvance);
  217. if(rg.GetCch() && IsURL(rg._rpTX, -rg.GetCch(), NULL))
  218. {
  219. SetURLEffects(rg, publdr);
  220. LONG cpNew = rg.GetCp() - rg.GetCch();
  221. // Anything before detected URL did not really belong to it
  222. if (rg.GetCp() > cp)
  223. {
  224. rg.Set(cp, cp - rg.GetCp());
  225. CheckAndCleanBogusURL(rg, fCleanedThisURL, publdr);
  226. fCleanedSomeURL |= fCleanedThisURL;
  227. }
  228. // Collapse to end of URL range so that ExpandToURL will
  229. // find next candidate.
  230. rg.Set(cpNew, 0);
  231. // skip to the end of word; this can't be another URL!
  232. cp = cpNew;
  233. cchAdvance = -MoveByDelimiters(rg._rpTX, 1, URL_STOPATWHITESPACE, 0);
  234. }
  235. if(cchAdvance)
  236. {
  237. rg.Set(cp, cchAdvance);
  238. CheckAndCleanBogusURL(rg, fCleanedThisURL, publdr);
  239. fCleanedSomeURL |= fCleanedThisURL;
  240. // Collapse to end of scanned range so that ExpandToURL will
  241. // find next candidate.
  242. rg.Set(cp - cchAdvance, 0);
  243. }
  244. }
  245. // If we cleaned some URL, we might need to reset the default format
  246. if(fCleanedSomeURL && !psel->GetCch())
  247. psel->Update_iFormat(-1);
  248. }
  249. //
  250. // PRIVATE methods
  251. //
  252. /*
  253. * CDetectURL::GetScanRegion (&rcpStart, &rcpEnd)
  254. *
  255. * @mfunc Gets the region of text to scan for new URLs by expanding the
  256. * changed region to be bounded by whitespace
  257. *
  258. * @rdesc BOOL
  259. */
  260. BOOL CDetectURL::GetScanRegion(
  261. LONG& rcpStart, //@parm where to put start of range
  262. LONG& rcpEnd) //@parm where to put end of range
  263. {
  264. LONG cp, cch;
  265. LONG cchExpand;
  266. WCHAR chBracket;
  267. CRchTxtPtr rtp(_ped, 0);
  268. _adc.GetUpdateRegion(&cp, NULL, &cch);
  269. if(cp == CP_INFINITE)
  270. return FALSE;
  271. // First find start of region
  272. rtp.SetCp(cp);
  273. rcpStart = cp;
  274. rcpEnd = cp + cch;
  275. // Now let's see if we need to expand to the nearest quotation mark
  276. // we do if we have quotes in our region or we have the LINK bit set
  277. // on either side of the region that we might need or not need to clear
  278. BOOL fExpandToBrackets = (rcpEnd - rcpStart ?
  279. GetAngleBracket(rtp._rpTX, rcpEnd - rcpStart) : 0);
  280. BOOL fKeepGoing = TRUE;
  281. while(fKeepGoing)
  282. {
  283. fKeepGoing = FALSE;
  284. // Expand left to the entire word
  285. rtp.SetCp(rcpStart);
  286. rcpStart += MoveByDelimiters(rtp._rpTX, -1, URL_STOPATWHITESPACE, 0);
  287. // Now the other end
  288. rtp.SetCp(rcpEnd);
  289. rcpEnd += MoveByDelimiters(rtp._rpTX, 1, URL_STOPATWHITESPACE, 0);
  290. // If we have LINK formatting around, we'll need to expand to nearest quotes
  291. rtp.SetCp(rcpStart);
  292. rtp._rpCF.AdjustBackward();
  293. fExpandToBrackets = fExpandToBrackets ||
  294. (_ped->GetCharFormat(rtp._rpCF.GetFormat())->_dwEffects & CFE_LINK);
  295. rtp.SetCp(rcpEnd);
  296. rtp._rpCF.AdjustForward();
  297. fExpandToBrackets = fExpandToBrackets ||
  298. (_ped->GetCharFormat(rtp._rpCF.GetFormat())->_dwEffects & CFE_LINK);
  299. if (fExpandToBrackets)
  300. // We have to expand to nearest angle brackets in both directions
  301. {
  302. rtp.SetCp(rcpStart);
  303. chBracket = LEFTANGLEBRACKET;
  304. cchExpand = MoveByDelimiters(rtp._rpTX, -1, URL_STOPATCHAR, &chBracket);
  305. // Did we really hit a bracket?
  306. if(chBracket == LEFTANGLEBRACKET)
  307. {
  308. rcpStart += cchExpand;
  309. fKeepGoing = TRUE;
  310. }
  311. // Same thing, different direction
  312. rtp.SetCp(rcpEnd);
  313. chBracket = RIGHTANGLEBRACKET;
  314. cchExpand = MoveByDelimiters(rtp._rpTX, 1, URL_STOPATCHAR, &chBracket);
  315. if(chBracket == RIGHTANGLEBRACKET)
  316. {
  317. rcpEnd += cchExpand;
  318. fKeepGoing = TRUE;
  319. }
  320. fExpandToBrackets = FALSE;
  321. }
  322. }
  323. LONG cchAdj = _ped->GetAdjustedTextLength();
  324. if(rcpEnd > cchAdj)
  325. rcpEnd = cchAdj;
  326. return TRUE;
  327. }
  328. /*
  329. * CDetectURL::ExpandToURL(&rg, &cchAdvance)
  330. *
  331. * @mfunc skips white space and sets the range to the very next
  332. * block of non-white space text. Strips this block off
  333. * punctuation marks
  334. */
  335. void CDetectURL::ExpandToURL(
  336. CTxtRange& rg, //@parm range to move
  337. LONG &cchAdvance//@parm how much to advance to the next URL from the current cp
  338. )
  339. {
  340. LONG cp;
  341. LONG cch;
  342. Assert(rg.GetCch() == 0);
  343. cp = rg.GetCp();
  344. // Skip white space first, record the advance
  345. cp -= (cchAdvance = -MoveByDelimiters(rg._rpTX, 1,
  346. URL_EATWHITESPACE|URL_STOPATNONWHITESPACE, 0));
  347. rg.Set(cp, 0);
  348. // Strip off punctuation marks
  349. WCHAR chStopChar = URL_INVALID_DELIMITER;
  350. // Skip all punctuation from the beginning of the word
  351. LONG cchHead = MoveByDelimiters(rg._rpTX, 1,
  352. URL_STOPATWHITESPACE|URL_STOPATNONPUNCT,
  353. &chStopChar);
  354. // Now skip up to white space (i.e. expand to the end of the word).
  355. cch = MoveByDelimiters(rg._rpTX, 1, URL_STOPATWHITESPACE|URL_EATNONWHITESPACE, 0);
  356. // This is how much we want to advance to start loking for the next URL
  357. // if this does not turn out to be one: one word
  358. // We increment/decrement the advance so we can accumulate changes in there
  359. cchAdvance -= cch;
  360. WCHAR chLeftDelimiter = chStopChar;
  361. // Check if anything left; if not, it's not interesting -- just return
  362. Assert(cchHead <= cch);
  363. if(cch == cchHead)
  364. {
  365. rg.Set(cp, -cch);
  366. return;
  367. }
  368. // Set to the end of range
  369. rg.Set(cp + cch, 0);
  370. // Get the space after so we always clear white space between words
  371. // cchAdvance -= MoveByDelimiters(rg._rpTX, 1,
  372. // URL_EATWHITESPACE|URL_STOPATNONWHITESPACE, 0);
  373. // and go back while skipping punctuation marks and not finding a match
  374. // to the left-side encloser
  375. chStopChar = BraceMatch(chStopChar);
  376. LONG cchTail = MoveByDelimiters(rg._rpTX, -1,
  377. URL_STOPATWHITESPACE|URL_STOPATNONPUNCT|URL_STOPATCHAR,
  378. &chStopChar);
  379. // Something should be left of the word, assert that
  380. Assert(cch - cchHead + cchTail > 0);
  381. if(chLeftDelimiter == LEFTANGLEBRACKET)
  382. {
  383. //If we stopped at a quote: go forward looking for the enclosing
  384. //quote, even if there are spaces.
  385. // move to the beginning
  386. rg.Set(cp + cchHead, 0);
  387. chStopChar = RIGHTANGLEBRACKET;
  388. if(GetAngleBracket(rg._rpTX) < 0) // closing bracket
  389. {
  390. LONG cchExtend = MoveByDelimiters(rg._rpTX, 1, URL_STOPATCHAR, &chStopChar);
  391. Assert(cchExtend <= URL_MAX_SIZE);
  392. // did we really get the closing bracket?
  393. if(chStopChar == RIGHTANGLEBRACKET)
  394. {
  395. rg.Set(cp + cchHead, -(cchExtend - 1));
  396. return;
  397. }
  398. }
  399. // Otherwise the quotes did not work out; fall through to
  400. // the general case
  401. }
  402. rg.Set(cp + cchHead, -(cch - cchHead + cchTail));
  403. return;
  404. }
  405. /*
  406. * CDetectURL::IsURL(&tp, cchrg, pfURLLeadin)
  407. *
  408. * @mfunc
  409. * If the cchrg chars starting at tp.GetCp() are a valid URL start,
  410. * return TRUE. We assume that these chars constitute a block of
  411. * non-white space text.
  412. *
  413. * @rdesc
  414. * TRUE iff tp points at a valid URL start of cchrg characters
  415. */
  416. BOOL CDetectURL::IsURL(
  417. CTxtPtr & tp, //@parm Start of text to check
  418. LONG cchrg, //@parm cch to check
  419. BOOL * pfURLLeadin) //@parm Returns whether valid URL leadin string
  420. {
  421. LONG i, j;
  422. WCHAR szBuf[MAXURLHDRSIZE + 1];
  423. if(pfURLLeadin)
  424. *pfURLLeadin = FALSE;
  425. if(cchrg <= 0)
  426. return FALSE;
  427. LONG cch = tp.GetText(MAXURLHDRSIZE, szBuf);
  428. szBuf[cch] = L'\0';
  429. // First, see if the word contains '\\' because that is a UNC
  430. // convention and it's cheap to check.
  431. if (szBuf[0] == L'\\' && szBuf[1] == L'\\' && cchrg > 2)
  432. return TRUE;
  433. // Scan the buffer to see if we have one of ':.' since
  434. // all URLs must contain that. wcsnicmp is a fairly expensive
  435. // call to be making frequently.
  436. for(i = 0; i < cch; i++)
  437. {
  438. switch (szBuf[i])
  439. {
  440. default:
  441. break;
  442. case '.':
  443. for(j = 0; j < NUMDOTURLHDR; j++)
  444. {
  445. // The strings must match
  446. if(W32->wcsnicmp(szBuf, rgszDOTURL[j], rgcchDOTURL[j]) == 0)
  447. {
  448. if(cchrg >= rgcchDOTURL[j])
  449. {
  450. if(pfURLLeadin)
  451. *pfURLLeadin = TRUE;
  452. return TRUE;
  453. }
  454. break;
  455. }
  456. }
  457. return FALSE;
  458. case ':':
  459. for(j = 0; j < NUMURLHDR; j++)
  460. {
  461. if(W32->wcsnicmp(szBuf, rgszURL[j], rgcchURL[j]) == 0)
  462. {
  463. LONG cchURL = rgcchURL[j];
  464. if(cchrg >= cchURL)
  465. {
  466. if(pfURLLeadin)
  467. *pfURLLeadin = TRUE;
  468. return j || cchrg > cchURL && szBuf[cchURL] == '/';
  469. }
  470. break;
  471. }
  472. }
  473. return FALSE;
  474. }
  475. }
  476. return FALSE;
  477. }
  478. /*
  479. * CDetectURL::SetURLEffects (&rg, publdr)
  480. *
  481. * @mfunc Sets URL effects for the given range.
  482. *
  483. * @comm The URL effects currently are blue text, underline, with
  484. * CFE_LINK.
  485. */
  486. void CDetectURL::SetURLEffects(
  487. CTxtRange & rg, //@parm Range on which to set the effects
  488. IUndoBuilder *publdr) //@parm Undo context to use
  489. {
  490. CCharFormat CF;
  491. CF._dwEffects = CFE_LINK;
  492. // NB! The undo system should have already figured out what should
  493. // happen with the selection by now. We just want to modify the
  494. // formatting and not worry where the selection should go on undo/redo.
  495. rg.SetCharFormat(&CF, SCF_IGNORESELAE, publdr, CFM_LINK, CFM2_CHARFORMAT);
  496. }
  497. /*
  498. * CDetectURL::CheckAndCleanBogusURL(rg, fDidClean, publdr)
  499. *
  500. * @mfunc checks the given range to see if it has CFE_LINK set,
  501. * and if so, removes is. We assume that the range is already
  502. * _not_ a well-formed URL string.
  503. */
  504. void CDetectURL::CheckAndCleanBogusURL(
  505. CTxtRange& rg, //@parm range to use
  506. BOOL &fDidClean, //@parm return TRUE if we actually did some cleaning
  507. IUndoBuilder *publdr) //@parm undo context to use
  508. {
  509. LONG cch = -rg.GetCch();
  510. Assert(cch > 0);
  511. CCharFormat CF;
  512. CFormatRunPtr rp(rg._rpCF);
  513. fDidClean = FALSE;
  514. // If there are no format runs, nothing to do
  515. if(!rp.IsValid())
  516. return;
  517. rp.AdjustForward();
  518. // Run through the format runs in this range; if there is no
  519. // link bit set, then just return.
  520. while(cch > 0)
  521. {
  522. DWORD dwEffects = _ped->GetCharFormat(rp.GetFormat())->_dwEffects;
  523. if((dwEffects & CFE_LINK) && !(dwEffects & CFE_LINKPROTECTED))
  524. break;
  525. cch -= rp.GetCchLeft();
  526. rp.NextRun();
  527. }
  528. // If there is no link bit set on any part of the range, just return
  529. if(cch <= 0)
  530. return;
  531. // Uh-oh, it's a bogus link. Turn off the link bit.
  532. fDidClean = TRUE;
  533. CF._dwEffects = 0;
  534. // NB! The undo system should have already figured out what should
  535. // happen with the selection by now. We just want to modify the
  536. // formatting and not worry where the selection should go on undo/redo.
  537. rg.SetCharFormat(&CF, SCF_IGNORESELAE, publdr, CFM_LINK, CFM2_CHARFORMAT);
  538. }
  539. /*
  540. * CDetectURL::MoveByDelimiters(&tpRef, iDir, grfDelimeters, pchStopChar)
  541. *
  542. * @mfunc returns the signed number of characters until the next delimiter
  543. * character in the given direction.
  544. *
  545. * @rdesc signed number of characters until next delimite
  546. */
  547. LONG CDetectURL::MoveByDelimiters(
  548. const CTxtPtr& tpRef, //@parm cp/tp to start looking from
  549. LONG iDir, //@parm Direction to look, must be 1 or -1
  550. DWORD grfDelimiters, //@parm Eat or stop at different types of
  551. // characters. Use one of URL_EATWHITESPACE,
  552. // URL_EATNONWHITESPACE, URL_STOPATWHITESPACE
  553. // URL_STOPATNONWHITESPACE, URL_STOPATPUNCT,
  554. // URL_STOPATNONPUNCT ot URL_STOPATCHAR
  555. WCHAR *pchStopChar) // @parm Out: delimiter we stopped at
  556. // In: additional char that stops us
  557. // when URL_STOPATCHAR is specified
  558. {
  559. LONG cch = 0;
  560. LONG cchMax = (grfDelimiters & URL_EATWHITESPACE) // Use huge # if
  561. ? tomForward : URL_MAX_SIZE; // eating whitesp
  562. LONG cchvalid = 0;
  563. WCHAR chScanned = URL_INVALID_DELIMITER;
  564. LONG i;
  565. const WCHAR *pch;
  566. CTxtPtr tp(tpRef);
  567. // Determine the scan mode: do we stop at white space, at punctuation,
  568. // at a stop character?
  569. BOOL fWhiteSpace = (0 != (grfDelimiters & URL_STOPATWHITESPACE));
  570. BOOL fNonWhiteSpace = (0 != (grfDelimiters & URL_STOPATNONWHITESPACE));
  571. BOOL fPunct = (0 != (grfDelimiters & URL_STOPATPUNCT));
  572. BOOL fNonPunct = (0 != (grfDelimiters & URL_STOPATNONPUNCT));
  573. BOOL fStopChar = (0 != (grfDelimiters & URL_STOPATCHAR));
  574. Assert(iDir == 1 || iDir == -1);
  575. Assert(fWhiteSpace || fNonWhiteSpace || (!fPunct && !fNonPunct));
  576. Assert(!fStopChar || NULL != pchStopChar);
  577. // Break anyway if we scanned more than URL_MAX_SIZE chars
  578. for (LONG cchScanned = 0; cchScanned < cchMax;)
  579. {
  580. // Get the text
  581. if(iDir == 1)
  582. {
  583. i = 0;
  584. pch = tp.GetPch(cchvalid);
  585. }
  586. else
  587. {
  588. i = -1;
  589. pch = tp.GetPchReverse(cchvalid);
  590. // This is a bit odd, but basically compensates for the
  591. // backwards loop running one-off from the forwards loop
  592. cchvalid++;
  593. }
  594. if(!pch)
  595. goto exit;
  596. // Loop until we hit a character within criteria. Note that for
  597. // our purposes, the embedding character counts as whitespace.
  598. while(abs(i) < cchvalid && cchScanned < cchMax
  599. && (IsURLWhiteSpace(pch[i]) ? !fWhiteSpace : !fNonWhiteSpace)
  600. && (IsURLDelimiter(pch[i]) ? !fPunct : !fNonPunct)
  601. && !(fStopChar && (*pchStopChar == chScanned) && (chScanned != URL_INVALID_DELIMITER))
  602. && ((chScanned != CR && chScanned != LF) || fNonWhiteSpace))
  603. {
  604. chScanned = pch[i];
  605. i += iDir;
  606. ++cchScanned;
  607. }
  608. // If we're going backwards, i will be off by one; adjust
  609. if(iDir == -1)
  610. {
  611. Assert(i < 0 && cchvalid > 0);
  612. i++;
  613. cchvalid--;
  614. }
  615. cch += i;
  616. if(abs(i) < cchvalid)
  617. break;
  618. tp.Move(i);
  619. }
  620. exit:
  621. // Stop char parameter is present, fill it in
  622. // with the last character scanned and accepted
  623. if (pchStopChar)
  624. *pchStopChar = chScanned;
  625. return cch;
  626. }
  627. /*
  628. * CDetectURL::BraceMatch (chEnclosing)
  629. *
  630. * @mfunc returns the matching bracket to the one passed in.
  631. * if the symbol passed in is not a bracket it returns
  632. * URL_INVALID_DELIMITER
  633. *
  634. * @rdesc returns bracket that matches chEnclosing
  635. */
  636. WCHAR CDetectURL::BraceMatch(
  637. WCHAR chEnclosing)
  638. {
  639. // We're matching "standard" roman braces only. Thus only them may be used
  640. // to enclose URLs. This should be fine (after all, only Latin letters are allowed
  641. // inside URLs, right?).
  642. // I hope that the compiler converts this into some efficient code
  643. switch(chEnclosing)
  644. {
  645. case(TEXT('\"')):
  646. case(TEXT('\'')): return chEnclosing;
  647. case(TEXT('(')): return TEXT(')');
  648. case(TEXT('<')): return TEXT('>');
  649. case(TEXT('[')): return TEXT(']');
  650. case(TEXT('{')): return TEXT('}');
  651. default: return URL_INVALID_DELIMITER;
  652. }
  653. }
  654. /*
  655. * CDetectURL::GetAngleBracket (&tpRef, cchMax)
  656. *
  657. * @mfunc Goes forward as long as the current paragraph
  658. * or URL_SCOPE_MAX not finding quotation marks and counts
  659. * those quotation marks
  660. * returns their parity
  661. *
  662. * @rdesc LONG
  663. */
  664. LONG CDetectURL::GetAngleBracket(
  665. CTxtPtr &tpRef,
  666. LONG cchMax)
  667. {
  668. CTxtPtr tp(tpRef);
  669. LONG cchvalid = 0;
  670. const WCHAR *pch;
  671. Assert (cchMax >= 0);
  672. if(!cchMax)
  673. cchMax = URL_MAX_SIZE;
  674. // Break anyway if we scanned more than cchLimit chars
  675. for (LONG cchScanned = 0; cchScanned < cchMax; NULL)
  676. {
  677. pch = tp.GetPch(cchvalid);
  678. if(!cchvalid)
  679. return 0;
  680. for (LONG i = 0; (i < cchvalid); ++i)
  681. {
  682. if(pch[i] == CR || pch[i] == LF || cchScanned >= cchMax)
  683. return 0;
  684. if(pch[i] == LEFTANGLEBRACKET)
  685. return 1;
  686. if(pch[i] == RIGHTANGLEBRACKET)
  687. return -1;
  688. ++cchScanned;
  689. }
  690. tp.Move(i);
  691. }
  692. return 0;
  693. }