eudora-mac/url.c

1 line
73 KiB
C
Executable File

/* Copyright (c) 2017, Computer History Museum
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted (subject to
the limitations in the disclaimer below) provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of Computer History Museum nor the names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE
COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE. */
#define FILE_NUM 66
/* Copyright (c) 1994 by QUALCOMM Incorporated */
#pragma segment URL
#include "url.h"
#include <LaunchServices.h>
Boolean gHelpWinClick = false;
typedef struct
{
Str31 scheme;
Boolean isSite;
Str127 site;
Str255 path;
Boolean isQuery;
Str255 query;
Boolean isFragment;
Str255 fragment;
} DeepURL, *DeepURLPtr, **DeepURLHandle;
OSErr SFURLApp(PStr proto,AliasHandle *alias);
OSErr SFURLAppStd(PStr proto,AliasHandle *alias, SFTypeList types, Str255 prompt, FSSpec *spec, URLHookOptionsPtr optionsPtr, Boolean *good);
OSErr BuildGURL(ProcessSerialNumberPtr psn,PStr url,AppleEvent *ae,Boolean newWindow);
OSErr BuildGURLPtr(ProcessSerialNumberPtr psn,UPtr url,short length,AppleEvent *ae,Boolean newWindow);
pascal OSErr HandleAEURL(AppleEvent *event,AppleEvent *reply,long refCon);
Boolean IsURLCharLo(Byte c);
OSErr MailtoURL(PStr query,AEDescList *dox,Boolean spool);
OSErr FileURL(PStr path);
void DeepURLParse(PStr url, DeepURLHandle duh);
PStr URLCombinePaths(PStr into,PStr base,PStr rel);
PStr RemLastComponent(PStr path);
PStr DuhString(PStr url,DeepURLHandle duh);
Byte URLToken(PStr url,PStr token,UPtr *spot);
Boolean TrailingDotDot(PStr string);
PStr URLPathEscape(PStr path);
OSErr ClearLink(PETEHandle pte,Boolean clear,long start, long end,long *urlStart, long *newStart, long *newEnd,PStr oldURL);
Handle URLVarSub(Ptr *string, long *len);
Boolean IsHostname(PStr string);
OSErr HostnameURL(PStr hostname);
OSErr JumpURL(PStr action);
void TurnBareLFs2BareCRs(Ptr s,long len);
void SubVar(Handle h,PStr find,PStr replace);
pascal Boolean URLDlgFilter(DialogPtr dgPtr,EventRecord *event,short *item);
Boolean PeteLinkAt(PETEHandle pte,long offset,Handle *hURL,PStr link);
void URLHelpTag(PETEHandle pte,Point mouse);
short URLIsNaughty(PStr linkText,Handle hURL,Boolean interact);
short URLIsNaughtyLo(PStr linkText,Handle hURL,PStr urlHost,PStr linkHost);
PStr StripCountryFromHost(PStr stripped,PStr clothed);
#define IsSchemeChar(x) (IsWordChar[x] || x==':' || x=='.' || x=='-' || x=='+' || '0'<=x && x<='9')
#define IsURLChar(x) (IsWordChar[x] || IsURLCharLo(x))
#define IsSlackURLChar(x) (IsURLChar(x)&&!IsLWSP(x)&&(x)!='('&&(x)!=')')
#define IsHostChar(x) (IsSchemeChar(x)) // This used to disallow colon, too. I'm not sure why, but it messed up host:port syntax
#define kW3Class 'WWW!'
#define kOpenURL 'OURL'
#define keyW3Window 'WIND'
/**********************************************************************
*
**********************************************************************/
Boolean IsURLCharLo(Byte c)
{
static Str63 urlOK;
if (!*urlOK) GetRString(urlOK,URL_IN_OK);
return(IsLWSP(c) || '0'<=c && c<='9' || PIndex(urlOK,c));
}
/************************************************************************
* HandleAEURL - handle a URL event
************************************************************************/
pascal OSErr HandleAEURL(AppleEvent *event,AppleEvent *reply,long refCon)
{
OSErr err;
AEDesc text, list;
Str63 proto;
Handle hURL;
Handle result;
ProcessSerialNumber psn;
extern void SystemEudoraFolder(void);
Boolean spool;
FRONTIER;
if (ModalWindow) return(userCanceledErr);
if (!SettingsRefN) SystemEudoraFolder();
NullADList(&text,&list,nil);
if (!(err = AEGetParamDesc(event,keyDirectObject,typeChar,&text)))
{
AEGetParamDesc(event,kURLDocumentList,typeAEList,&list);
spool = GetAEDefaultBool(event,keyEuSpool,false);
#ifdef NETSCAPE_WERE_NOT_BRAINDEAD
if (!(err=GotAERequired(event)))
#endif
{
AEDesc coerced;
Boolean wasCoerced;
UPtr pURL;
uLong lenURL;
if (!(hURL = GetAEText(&text,&coerced,&wasCoerced)))
err = errAEEventNotHandled;
else
{
pURL = LDRef(hURL);
lenURL = GetHandleSize(hURL);
if (lenURL && pURL[0]=='<')
{
// Get rid of brackets
pURL++;
lenURL--;
if (lenURL && pURL[lenURL-1]=='>') lenURL--;
}
if (!(err=ParseProtocolFromURLPtr(pURL,lenURL,proto)))
{
err=OpenLocalURLPtr(pURL,lenURL,&result,&list,spool);
if (!err)
{
if (refCon==kURLFetch)
{
err = AEPutParamPtr(reply,keyAEResult,typeChar,LDRef(result),GetHandleSize(result));
UL(result);
}
}
else
#ifdef TWO
err = (refCon==kURLFetch||err!=fnfErr) ? err : OpenOtherURLPtr(proto,pURL,lenURL);
#else
err = errAEEventNotHandled;
#endif
}
if (wasCoerced) AEDisposeDesc(&coerced);
AEDisposeDescDataHandle(hURL);
}
}
}
DisposeADList(&text,&list,nil);
/*
* frontify ourselves
*/
if (!err) SetFrontProcess(CurrentPSN(&psn));
return(err);
}
/**********************************************************************
* OpenLocalURLPtr - open a URL in Eudora
**********************************************************************/
OSErr OpenLocalURLPtr(PStr url,long len,Handle *result,AEDescList *dox,Boolean spool)
{
ProtocolEnum protocol;
Str255 proto,host,query;
Ptr queryPtr;
long queryLen;
OSErr err;
// weed out the payment urls
MakePStr(proto,url,len);
if (EqualStrRes(proto,BUYING_BY_HTTP) || EqualStrRes(proto,BUYING_BY_MAIL))
{
StartPaymentProcess();
return noErr;
}
if (!(err=ParseURLPtr(url,len,proto,host,&queryPtr,&queryLen)))
{
if (queryPtr && queryLen)
MakePPtr(query,queryPtr,queryLen);
else
*query = 0;
protocol = FindSTRNIndex(ProtocolStrn,proto);
FixURLString(host);
if (protocol!=proMail && protocol!=proFile && protocol!=proCompFile) FixURLString(query);
if ((protocol==proFinger||protocol==proMail) && *host && !*query)
{
PCopy(query,host);
*host = 0;
}
err = fnfErr;
if (protocol)
{
err = 1;
if (*query || protocol==proPh || protocol==proPh2 || protocol==proLDAP || protocol==proMail)
{
if (protocol==proFinger || protocol==proPh || protocol==proPh2 || protocol==proLDAP)
{
/*
* open the finger/ph window
*/
if (!(err = OpenPh(nil)))
{
err = PhURLLookup(protocol,host,query,result);
}
}
else if (protocol==proMail)
err = MailtoURLPtr(queryPtr,queryLen,dox,spool);
else if (protocol==proFile || protocol==proCompFile)
err = FileURL(query);
else if (protocol==proSetting)
err = SettingURL(query);
else if (protocol==proXEudoraJump)
err = JumpURL(query);
}
if (err && err!=userCanceledErr) WarnUser(URL_PARSE,err);
}
}
gHelpWinClick = false;
return(err);
}
/**********************************************************************
* OpenLocalURLLo - open a URL in Eudora
**********************************************************************/
OSErr OpenLocalURLLo(PStr url,Handle *result,AEDescList *dox,Boolean spool)
{
return OpenLocalURLPtr(url+1,*url,result,dox,spool);
}
/**********************************************************************
* MailtoURL - handle a mailto url
**********************************************************************/
OSErr MailtoURL(PStr query,AEDescList *dox,Boolean spool)
{
Str255 scratch;
PCopy(scratch,query); // Make copy. It may get changed.
return MailtoURLPtr(scratch+1,*scratch,dox,spool);
}
/**********************************************************************
* MailtoURL - handle a mailto url
**********************************************************************/
OSErr MailtoURLPtr(Ptr srcQuery,long queryLen,AEDescList *dox,Boolean spool)
{
OSErr err=noErr;
Ptr head,hVal,hdrName;
long headLen,hValLen,hdrNameLen;
Str255 hName;
Str255 val;
UPtr spot;
char qMark[3];
char eSign[2];
UPtr equal;
short hid;
HeadSpec hs;
MyWindowPtr win;
WindowPtr winWP;
PersHandle pers = nil;
Handle valHdl;
Ptr query;
// Make a copy of url. We may modify it while decoding.
if (!(query = NuPtr(queryLen))) return MemError();
BMD(srcQuery,query,queryLen);
winWP = FrontWindow_();
win = GetWindowMyWindowPtr (winWP);
if (MainEvent.what != kHighLevelEvent && (GetWindowKind(winWP)==MESS_WIN || GetWindowKind(winWP)==COMP_WIN))
pers = MESS_TO_PERS(Win2MessH(win));
DoMenu(winWP,(MESSAGE_MENU<<16)|MESSAGE_NEW_ITEM,0);
if (winWP==FrontWindow_()) err = errAEEventNotHandled;
else
{
win = GetWindowMyWindowPtr (FrontWindow_());
if(gHelpWinClick)
{
gHelpWinClick = false;
SetMessOpt(Win2MessH(win),OPT_SEND_REGINFO);
}
if (pers) SetPers((*Win2MessH(win))->tocH,(*Win2MessH(win))->sumNum,pers,true);
qMark[0] = '?';
qMark[1] = '&';
eSign[1] = qMark[2] = 0;
eSign[0] = '=';
spot = query;
if (TokenPtr(query,queryLen,&hVal,&hValLen,&spot,qMark))
{
// grab the to address
if (hValLen)
{
FixURLPtr(hVal,&hValLen);
if (CompHeadFind(Win2MessH(win),TO_HEAD,&hs))
{
valHdl = URLVarSub(&hVal,&hValLen);
err = CompHeadSetPtr((*Win2MessH(win))->bodyPTE,&hs,hVal,hValLen);
ZapHandle(valHdl);
if (!err)
err = NickExpandAndCacheHead (Win2MessH(win), TO_HEAD, true);
}
}
// now, the rest of the headers
while (!err && TokenPtr(query,queryLen,&head,&headLen,&spot,qMark))
{
equal = head;
if (TokenPtr(head,headLen,&hdrName,&hdrNameLen,&equal,eSign))
{
if (TokenPtr(head,headLen,&hVal,&hValLen,&equal,eSign))
{
MakePPtr(hName,hdrName,hdrNameLen);
FixURLString(hName);
FixURLPtr(hVal,&hValLen);
TurnBareLFs2BareCRs(hVal,hValLen);
hValLen = StripChar(hVal,hValLen,'\012');
if (EqualStrRes(hName,URL_BODY))
CompHeadFind(Win2MessH(win),0,&hs);
else
{
PCatC(hName,':');
hid = FindSTRNIndex(HEADER_STRN,hName);
if (hid && hid<=BCC_HEAD)
CompHeadFind(Win2MessH(win),hid,&hs);
else
{
MakePPtr(val,hVal,hValLen);
switch (hid)
{
case PERSONA_HEAD:
{
PersHandle pers = FindPersByName(val);
if (pers) SetPers((*Win2MessH(win))->tocH,(*Win2MessH(win))->sumNum,pers,true);
}
break;
case SIG_HEAD:
{
if (!*val)
SetSig((*Win2MessH(win))->tocH,(*Win2MessH(win))->sumNum,SIG_NONE);
else
{
MyLowerStr(val);
SetSig((*Win2MessH(win))->tocH,(*Win2MessH(win))->sumNum,Hash(val));
}
}
break;
}
continue;
}
}
// Do variable substitution.
valHdl = URLVarSub(&hVal,&hValLen);
err = CompHeadSetPtr((*Win2MessH(win))->bodyPTE,&hs,hVal,hValLen);
ZapHandle(valHdl);
}
}
else break;
}
}
// now, attach dox
if (!err && dox && dox->descriptorType!='null')
{
long i, n;
FSSpec spec;
DescType junkKW, junkType;
long junkSize;
if (err = AECountItems(dox,&n)) goto done;
for (i=1;i<=n;i++)
{
// grab the spec
if (err = AEGetNthPtr(dox,i,typeFSS,&junkKW,&junkType,&spec,sizeof(spec),&junkSize))
break;
// better be there
if (err=FSpExists(&spec)) {if (err==fnfErr) err=errAEEventNotHandled; break;}
// we don't do folders, sorry
if (FSpIsItAFolder(&spec)) {err=errAEEventNotHandled;break;}
// attach it
CompAttachSpec(win,&spec);
// spool it if need be
if (spool)
if (err=SpoolIndAttachment(Win2MessH(win),i)) break;
}
}
if (win) CompActivateAppropriate(Win2MessH(win));
}
done:
ZapPtr(query); // dispose of URL copy
return(err);
}
/**********************************************************************
* FixURLPtr - undo escaped stuff in URL's
**********************************************************************/
void FixURLPtr(Ptr url,long *len)
{
UPtr spot, end;
UPtr romanized;
long romanLen;
spot = url;
end = url+*len;
for (;spot<end;spot++) if (*spot=='%') *spot = lowerDelta;
CaptureHexPtr(url,url,len);
// convert utf8 if we can
if (romanized = NuPtr(*len))
{
romanLen = *len;
BMD(url,romanized,*len);
if (UTF8ToRoman(romanized,&romanLen,romanLen))
{
BMD(romanized,url,romanLen);
*len = romanLen;
}
ZapPtr(romanized);
}
}
/**********************************************************************
* TurnBareLFs2BareCRs - turn standalone LF's into standalone CR's
**********************************************************************/
void TurnBareLFs2BareCRs(Ptr s,long len)
{
UPtr spot;
for (spot=s;spot<s+len;spot++)
if (*spot=='\012' && (spot==s||spot[-1]!='\015'))
*spot = '\015';
return;
}
/**********************************************************************
* SettingURL - handle a settings url
**********************************************************************/
OSErr SettingURL(PStr query)
{
Str31 numStr;
Str255 val, s;
Str31 pers;
UPtr spot, persSpot;
long num;
DialogPtr dgPtr;
MyWindowPtr dgPtrWin;
short item;
extern ModalFilterUPP DlgFilterUPP;
short forbidden[] = {PREF_PASS_TEXT, PREF_AUXPW, PREF_ACAP_PASS};
*pers = 0;
spot = query+1;
if (PToken(query,numStr,&spot,"="))
{
if (PIndex(numStr,'@'))
{
PCopy(s,numStr);
persSpot = s+1;
PToken(s,numStr,&persSpot,"@");
PToken(s,pers,&persSpot,"\377");
if (!FindPersByName(pers))
{
Aprintf(OK_ALRT,Note,NO_SUCH_PERSONALITY,pers);
return userCanceledErr;
}
}
StringToNum(numStr,&num);
for (item=0;item<sizeof(forbidden)/sizeof(forbidden[0]);item++)
if (num==forbidden[item])
{
WarnUser(FORBIDDEN_SETTING,0);
return userCanceledErr;
}
if (!MommyMommy(ATTENTION,nil)) return(userCanceledErr);
if ((dgPtrWin = GetNewMyDialog(URL_SETTING_CONFIRM_DLOG,nil,nil,InFront))==nil)
{
WarnUser(GENERAL,MemError());
return(userCanceledErr);
}
dgPtr = GetMyWindowDialogPtr (dgPtrWin);
// if no =, use current value
if (*pers) PushPers(FindPersByName(pers));
NoProxify = true;
if (spot[-1]=='=') PToken(query,val,&spot,"\377");
else GetPref(val,num);
if (*pers) ComposeRString(s,FOR_PERSONALITY,pers);
else *s = 0;
ParamText(numStr,s,"","");
SetDIText(dgPtr,uscNewValueItem,val);
SetDIText(dgPtr,uscCurValueItem,GetPref(s,num));
SetDIText(dgPtr,uscHelpItem,*PrefHelpString(num,s,true)?s:GetRString(s,NO_SETTING_HELP));
SetDIText(dgPtr,uscDefValueItem,GetDefPref(s,num));
SelectDialogItemText(dgPtr,uscNewValueItem,0,REAL_BIG);
NoProxify = false;
AutoSizeDialog(dgPtr);
{
// AutoSizeDialog doesn't seem to move the text edit field under OS X.
Rect teR, cbR;
GetControlBounds(GetDItemCtl(dgPtr,uscCancelItem),&cbR);
GetControlBounds(GetDItemCtl(dgPtr,uscNewValueItem),&teR);
MoveMyCntl(GetDItemCtl(dgPtr,uscNewValueItem),teR.left,cbR.top-(teR.bottom-teR.top)-10,0,0);
}
ShowWindow(GetDialogWindow(dgPtr));
SetMyCursor(arrowCursor);
StartMovableModal(dgPtr);
do
{
MovableModalDialog(dgPtr,DlgFilterUPP,&item);
}
while (item>3);
EndMovableModal(dgPtr);
if (item==uscRevertItem) GetDIText(dgPtr,uscDefValueItem,val);
else GetDIText(dgPtr,uscNewValueItem,val);
DisposDialog_(dgPtr);
if (item==uscCancelItem || item==CANCEL_ITEM) {if (*pers) PopPers();return(userCanceledErr);}
SetPref(num,val);
if (*pers) PopPers();
return(noErr);
}
return(fnfErr);
}
/************************************************************************
* SubVar - substitute variables
************************************************************************/
void SubVar(Handle h,PStr find,PStr replace)
{
long offset = 0;
for (offset=0;(offset = Munger(h,offset,find+1,*find,replace+1,*replace))>0;offset += *replace);
}
/************************************************************************
* URLVarSub - substitute variables
************************************************************************/
Handle URLVarSub(Ptr *string, long *len)
{
Str31 var;
Str255 subMe;
Handle h = nil;
if (h = NuDHTempBetter(*string,*len))
{
SubVar(h,GetRString(var,NAME_VAR),GetPref(subMe,PREF_REALNAME));
SubVar(h,GetRString(var,ADDR_VAR),GetReturnAddr(subMe,true));
GetPOPInfo(subMe,nil);
SubVar(h,GetRString(var,USER_VAR),subMe);
*string = LDRef(h);
*len = GetHandleSize(h);
}
return(h);
}
//
// OpenOtherURLPtr
//
// Pointer version of OpenOtherURL so we can handle URL's in excess of 255 characters.
// Eventually, this should either replace (or be called by) OpenOtherURL, but for now,
// Let's be safe.
//
OSErr OpenOtherURLPtr(PStr proto,UPtr url,short length)
{
short err = noErr;
LaunchParamBlockRec lpb;
FSSpec spec;
ProcessSerialNumber psn, myPSN;
AppleEvent ae;
AEDesc launchDesc;
AliasHandle appAlias = nil;
Boolean isRunning;
Boolean wasChanged;
Boolean same;
Boolean dontFront = (MainEvent.modifiers&cmdKey) && PrefIsSet(PREF_CMD_DONT_FRONT);
if (HaveTheDiseaseCalledOSX() && !PrefIsSet(PREF_USE_OWN_URL_HELPERS))
{
OSErr err = fnfErr;
CFURLRef theRef = CFURLCreateWithBytes(nil,url,length,kCFStringEncodingUTF8,nil);
if (theRef)
{
err = LSOpenCFURLRef(theRef,nil);
CFRelease(theRef);
if (!err) return noErr;
}
}
NullADList(&ae,&launchDesc,nil);
GetCurrentProcess(&myPSN);
/*
* figure out what should open it
*/
if (MainEvent.modifiers & optionKey) err = SFURLApp(proto,&appAlias);
else (err=FindURLApp(proto,&appAlias,kWildCardOK));
if (err) return(err);
/*
* is that thing running?
*/
isRunning = !FindPSNByAlias(&psn,appAlias);
/*
* Do we need to be online for this URL, and are we?
*/
if ((*(long*)(proto+1)=='http') && PreventOfflineLink(&err, false)) return (err);
/*
* build the GURL
*/
MyLowerStr(proto);
if (err=BuildGURLPtr(&psn,url,length,&ae,*(long*)(proto+1)=='http'&&(MainEvent.modifiers&cmdKey)&&PrefIsSet(PREF_CMD_DONT_FRONT))) return(err);
/*
* if running, cake
*/
if (isRunning)
{
// don't send ourselves a GURL event.
// This may seem a bit drastic (sending yourself events is supposed to be
// great and wonderful, I know). However, the current code will chain
// infinitely if send a GURL for a protocol it can't handle if the helper
// app is set to Eudora; safest to avoid this for now
if (SameProcess(&psn,&myPSN,&same) || same) return(errAEEventNotHandled);
//if (RunType==Debugging) DebugStr("\pAlready running!");
err = MyAESend(&ae,nil,kAEQueueReply|kAECanInteract|kAECanSwitchLayer,
kAENormalPriority,kAEDefaultTimeout);
if (!err && !dontFront) SetFrontProcess(&psn);
}
else
{
/* not running; gotta launch */
/*
* coerce to proper descriptor, like DTS says
*/
err = AECoerceDesc(&ae,typeAppParameters,&launchDesc);
if (err) WarnUser(AE_TROUBLE,err);
/*
* let's launch it
*/
if (!err)
{
Handle hAppParms;
Zero(lpb);
AEGetDescDataHandle(&launchDesc,&hAppParms);
lpb.launchAppParameters = (AppParametersPtr)LDRef(hAppParms);
lpb.launchBlockID = extendedBlock;
lpb.launchEPBLength = extendedBlockLen;
lpb.launchControlFlags = launchContinue | launchNoFileFlags;
if (dontFront) lpb.launchControlFlags |= launchDontSwitch;
err = ResolveAlias(nil,appAlias,&spec,&wasChanged);
//if (RunType==Debugging) DebugStr(spec.name);
if (!err)
{
lpb.launchAppSpec = &spec;
WriteZero(&lpb.launchProcessSN,sizeof(lpb.launchProcessSN));
if (err = LaunchApplication(&lpb)) WarnUser(COULDNT_LAUNCH,err);
}
AEDisposeDescDataHandle(hAppParms);
}
}
DisposeADList(&ae,&launchDesc,nil);
return(err);
}
/**********************************************************************
* FindURLApp - find a map for a URL
**********************************************************************/
OSErr FindURLApp(PStr proto,AliasHandle *alias,Boolean mayWildCard)
{
AliasHandle mapAlias=nil;
*alias = nil;
/*
* first, let's see if we know about this type in particular
*/
if (mapAlias = (void*)GetNamedResource(URLMAP_TYPE,proto))
HNoPurge((Handle)mapAlias);
/*
* how about a wildcard?
*/
if (!mapAlias && mayWildCard)
{
if (mapAlias = (void*)GetNamedResource(URLMAP_TYPE,"\p****"))
HNoPurge((Handle)mapAlias);
}
/*
* do we have any idea?
*/
if (mapAlias)
{
// search by creator?
if ((!PrefIsSet(PREF_NO_FUZZY_HELPERS) || SimpleResolveAliasNoUI(mapAlias,nil)) && (*mapAlias)->userType && !TypeIsOnList((*mapAlias)->userType,APPLET_LIST_TYPE))
CreatorToApp((*mapAlias)->userType,alias);
// search by alias only
else if (LocateApp(mapAlias))
{ReleaseResource_(mapAlias);mapAlias=nil;}
}
/*
* have we filled it in yet?
*/
if (!*alias)
{
// do we know what it should be? If not, ask the user
if (!mapAlias) SFURLApp(proto,&mapAlias);
ASSERT(CurResFile()==SettingsRefN);
// push has come to shove. Either we have it now or we give up
if (mapAlias)
{
*alias = mapAlias;
if (MyHandToHand((Handle*)alias)) *alias = nil;
PurgeIfClean((Handle)mapAlias);
return(noErr);
}
}
return(*alias?noErr : fnfErr);
}
/************************************************************************
* BuildGURL - build a GURL event
************************************************************************/
OSErr BuildGURL(ProcessSerialNumberPtr psn,PStr url,AppleEvent *ae,Boolean newWindow)
{
OSErr err;
AEAddressDesc target;
if (!(err=AECreateDesc(typeProcessSerialNumber,psn,sizeof(*psn),&target)))
{
if (!(err=AECreateAppleEvent(newWindow?kW3Class:kURLClass,newWindow?kOpenURL:kURLGet,&target,
kAutoGenerateReturnID,kAnyTransactionID,ae)))
{
err=AEPutParamPtr(ae,keyDirectObject,typeChar,url+1,*url);
// add newWindow parameter as optional param
if (newWindow && !err)
{
AEDesc optD;
OSType keyword = keyW3Window;
NullADList(&optD,nil);
if (!(err = AECreateList(nil,0,False,&optD)))
if (!(err = AEPutPtr(&optD,1,typeKeyword,&keyword,sizeof(keyword))))
if (!(err = AEPutAttributeDesc(ae,keyOptionalKeywordAttr,&optD)))
err = AEPutLong(ae,keyW3Window,0);
DisposeADList(&optD,nil);
}
}
AEDisposeDesc(&target);
}
if (err) WarnUser(AE_TROUBLE,err);
return(err);
}
//
// BuildGURLPtr
//
// Pointer version of BuildGURL so we can handle URL's in excess of 255 characters.
// Eventually, this should either replace (or be called by) BuildGURL, but for now,
// Let's be safe.
//
OSErr BuildGURLPtr(ProcessSerialNumberPtr psn,UPtr url,short length,AppleEvent *ae,Boolean newWindow)
{
OSErr err;
AEAddressDesc target;
if (!(err=AECreateDesc(typeProcessSerialNumber,psn,sizeof(*psn),&target)))
{
if (!(err=AECreateAppleEvent(newWindow?kW3Class:kURLClass,newWindow?kOpenURL:kURLGet,&target,
kAutoGenerateReturnID,kAnyTransactionID,ae)))
{
err=AEPutParamPtr(ae,keyDirectObject,typeChar,url,length);
// add newWindow parameter as optional param
if (newWindow && !err)
{
AEDesc optD;
OSType keyword = keyW3Window;
NullADList(&optD,nil);
if (!(err = AECreateList(nil,0,False,&optD)))
if (!(err = AEPutPtr(&optD,1,typeKeyword,&keyword,sizeof(keyword))))
if (!(err = AEPutAttributeDesc(ae,keyOptionalKeywordAttr,&optD)))
err = AEPutLong(ae,keyW3Window,0);
DisposeADList(&optD,nil);
}
}
AEDisposeDesc(&target);
}
if (err) WarnUser(AE_TROUBLE,err);
return(err);
}
/************************************************************************
* SFURLApp - ask the user what app to use for a URL
************************************************************************/
OSErr SFURLApp (PStr proto, AliasHandle *alias)
{
URLHookOptions options;
SFTypeList types;
FSSpec spec;
Str255 prompt;
OSErr theError;
Boolean good;
types[0] = 'APPL';
types[1] = 'adrp';
types[2] = 'appe';
types[3] = 0;
options.permanently = True;
options.protocol = FindSTRNIndex (ProtocolStrn, proto);
ComposeRString (prompt, CHOOSE_URL_APP, proto);
theError = SFURLAppNav (proto, alias, types, prompt, &spec, &options, &good);
if (!theError && good) {
theError = MakeHelperAlias (&spec, alias);
if (!theError && options.permanently)
SaveURLPref (proto, *alias);
}
else
if (!theError || theError == userCanceledErr)
theError = fnfErr;
return (theError);
}
/************************************************************************
* MakeHelperAlias - return an alias to a helper app
************************************************************************/
OSErr MakeHelperAlias(FSSpecPtr app,AliasHandle *alias)
{
OSType creator;
OSErr err;
*alias = nil;
err = NewAlias(nil,app,alias);
if (*alias && !err)
{
creator = FileCreatorOf(app);
(**alias)->userType = creator;
}
return(err);
}
/**********************************************************************
* SaveURLPref - save the application we want to use to open url's
**********************************************************************/
OSErr SaveURLPref(PStr proto,AliasHandle alias)
{
Handle oldH;
while (oldH = GetNamedResource(URLMAP_TYPE,proto))
{
if (HomeResFile(oldH)!=SettingsRefN) break;
RemoveResource(oldH); if (ResError()) break;
UpdateResFile(SettingsRefN); if (ResError()) break;
DisposeHandle(oldH);
}
if (alias)
AddMyResource_(alias,URLMAP_TYPE,MyUniqueID(URLMAP_TYPE),proto);
return(ResError());
}
/**********************************************************************
* URLIsSelected - is the current selection in a URL?
**********************************************************************/
URLEnum URLIsSelected(MyWindowPtr win,PETEHandle pte,long startWith,long endWith,short what,long *uStart,long *uEnd)
{
Handle textH=nil;
UPtr text;
UPtr start,stop,end;
Str255 proto,host,scratch,linkText;
Boolean point;
long selStart, selEnd;
Byte state;
Byte startChar=' ';
UPtr spot;
UPtr colon;
Boolean chiral=False;
PETEDocInfo info;
Boolean rude = False;
PETEStyleEntry pse;
URLEnum result = urlNot;
long size;
Boolean colorThem = 0!=(what&urlColor);
Boolean selectThem = 0!=(what&urlSelect);
Boolean openThem = 0!=(what&urlOpen);
Handle hURL = nil;
OSErr err;
Size urlSize,queryLen;
Ptr urlPtr,queryPtr;
short i;
Boolean bowelSection = false;
uLong linkStart,linkStop;
if (!pte) return(urlNot);
if (PeteGetTextAndSelection(pte,&textH,&selStart,&selEnd)) return(urlNot);
size = GetHandleSize(textH);
if (!size) return(urlNot);
state = HGetState(textH);
text = LDRef(textH);
start = text+((startWith<0)?selStart:startWith);
stop = text+((endWith<0)?selEnd:endWith);
size = MIN(start-text+253,size);
end = text+size;
point = ClickType==Double || start==stop;
if (!point) end=stop;
*linkText = 0;
PETEGetDocInfo(PETE,pte,&info);
PeteStyleAt(pte,startWith,&pse);
if (openThem || pse.psStyle.textStyle.tsLabel&pLinkLabel)
{
if (pse.psStyle.textStyle.tsLabel&(pURLLabel|pLinkLabel))
{
if (point || pse.psStyle.textStyle.tsLabel&pLinkLabel)
{
if (pse.psStyle.textStyle.tsLabel&pLinkLabel)
{
PETEFindLabelRun(PETE,pte,(info.styleRunStart+info.styleRunStop)/2,
&info.styleRunStart,&info.styleRunStop,pLinkLabel,pLinkLabel);
PETEFindLabelRun(PETE,pte,info.styleRunStop-1,&linkStart,&linkStop,pLinkLabel,pLinkLabel|pURLLabel);
MakePStr(linkText,text+linkStart+1,linkStop-linkStart-1);
}
PETEFindLabelRun(PETE,pte,info.styleRunStart+2,
&info.styleRunStart,&info.styleRunStop,pURLLabel,pURLLabel);
start = text + info.styleRunStart;
stop = text + info.styleRunStop;
}
}
else if (point) goto fail;
}
if (!openThem && pse.psStyle.textStyle.tsLabel&pLinkLabel)
{
if (uStart) *uStart = info.styleRunStart;
if (uEnd) *uEnd = info.styleRunStop;
HSetState(textH,state);
return(urlGood);
}
if (point)
{
if (!openThem)
{
/*
* scan backward for colon
*/
for (colon=MIN(start,end-1);colon>text;colon--) if (*colon==':') break;
/*
* nope. scan forward
*/
if (*colon!=':') for (colon=start;colon<end;colon++) if (*colon==':') break;
/*
* if no colon, done
*/
if (colon<text || colon>=end || *colon!=':') goto fail;
/*
* Found a colon. Back up to next non-scheme character
*/
start = colon;
bowelSection = colon < end-2 && colon[1]=='/' && colon[2]=='/';
while (start>=text && IsSchemeChar(*start)) start--;
/*
* note the character that began the "URL"
*/
startChar = (start<text || IsAnySP(*start) || *start=='>') ? ' ' : *start;
/*
* if the start char is chiral, mirror-image
*/
GetRString(scratch,URL_LEFT);
if (spot=PIndex(scratch,startChar))
{
chiral = True;
GetRString(scratch,URL_RIGHT);
startChar = *spot;
}
/*
* the first char of the URL is actually *after* the start char
*/
start++;
/*
* now, scan for ending character
*/
if (startChar==' ' || !chiral)
{
while (stop<end && !IsAnySP(*stop) && *stop!=startChar && IsURLChar(*stop)) stop++;
if (startChar==' ')
{
if (stop<end && !(IsAnySP(*stop))) goto fail;
}
else if (stop==end || *stop!=startChar) goto fail;
}
else
{
while (stop<end && *stop!=startChar && IsURLChar(*stop)) stop++;
if (stop==end || *stop!=startChar) goto fail;
}
/*
* vacuuity
*/
if (start==colon || end-colon==1) goto fail;
}
}
rude = (start>text) && (start[-1]!='<') && point;
if (rude)
while(stop>start && PIndex(GetRString(scratch,URL_TRAIL_IGNORE),stop[-1])) stop--;
/*
* make a URL handle out of it
*/
if (start<stop && start>=text && stop<=end)
{
if (err = PtrToHand(start,&hURL,stop-start))
{
WarnUser(GENERAL,err);
goto fail;
}
}
else
return(urlNot);
selStart = start-*textH;
selEnd = stop-*textH;
HSetState(textH,state);
/*
* GRRRRRRRRR! Strip out some characters such as returns
*/
GetRString(scratch,URL_STRIP_CHARS);
for (i=1;i<=*scratch;i++)
RemoveCharHandle(scratch[i],hURL);
/*
* remove URL:
*/
GetRString(scratch,URL);
PCatC(scratch,':');
if (*scratch && StartsWithPtr(*hURL,GetHandleSize(hURL),scratch))
Munger(hURL,0,nil,*scratch,scratch,0);
/*
* is it really a url?
*/
urlPtr = LDRef(hURL);
urlSize = GetHandleSize(hURL);
if (GetHandleSize(hURL))
{
/* allow no newlines */
if (!ParseURLPtr(urlPtr,urlSize,proto,host,&queryPtr,&queryLen))
{
#ifdef URL_PROTECTION
if (openThem && URLIsNaughty(linkText,hURL,true))
{
ZapHandle(hURL);
ZapPtr(queryPtr);
return urlNaughty;
}
#endif //URL_PROTECTION
if (selectThem)
{
PeteSelect(nil,pte,selStart,selEnd);
PeteSetDirty(pte);
}
if (colorThem && (queryLen||*host||startChar=='>'||bowelSection) && (openThem || startChar=='>' || FindSTRNIndex(UrlColorStrn,proto) || bowelSection))
{
result = rude ? urlRude : urlGood;
URLStyle(pte,selStart,selEnd,rude);
}
else result = urlMaybe;
if (openThem)
{
OSErr urlOpenErr = noErr;
gHelpWinClick = (win && GetWindowKind(GetMyWindowWindowPtr(win)) == HELP_WIN);
//if (rude) PlaySoundId(1003);
if (((MainEvent.modifiers&optionKey)&&!EqualStrRes(proto,ProtocolStrn+proFile)) || (fnfErr==(urlOpenErr=OpenLocalURLPtr(urlPtr,urlSize,nil,nil,false))))
urlOpenErr = OpenOtherURLPtr(proto,urlPtr,urlSize);
// Add the URL to the history list, if we ought to.
if (!PrefIsSet(PREF_NO_LINK_HISTORY))
{
MakePStr(scratch,urlPtr,urlSize);
AddURLToMainHistory(scratch, nil, urlOpenErr);
}
}
if (uStart) *uStart = selStart;
if (uEnd) *uEnd = selEnd;
ZapHandle(hURL);
return(result);
}
}
fail:
ZapHandle(hURL);
if (textH) UL(textH);
return(urlNot);
}
/**********************************************************************
* SlackURL - is the current selection something we can try to open as URL?
**********************************************************************/
URLEnum SlackURL(MyWindowPtr win,PETEHandle pte,long startWith,long endWith,short what,long *uStart,long *uEnd)
{
Handle textH=nil;
UPtr text;
UPtr start,stop,end;
Boolean point;
long selStart, selEnd;
Byte state;
Byte startChar=' ';
UPtr spot;
Boolean chiral=False;
long size;
Boolean colorThem = 0!=(what&urlColor);
Boolean selectThem = 0!=(what&urlSelect);
Boolean openThem = 0!=(what&urlOpen);
Str255 url;
FSSpec spec;
if (!PeteIsValid(pte)) return(urlNot);
if (PeteGetTextAndSelection(pte,&textH,&selStart,&selEnd)) return(urlNot);
size = GetHandleSize(textH);
if (!size) return(urlNot);
state = HGetState(textH);
text = LDRef(textH);
start = text+((startWith<0)?selStart:startWith);
stop = text+((endWith<0)?selEnd:endWith);
size = MIN(start-text+253,size);
end = text+size;
point = start==stop;
if (!point) end=stop;
if (point)
{
/*
* expand to delimitters
*/
// back up start
while (start > text && IsSlackURLChar(start[-1])) start--;
// figure out what the start char was, so we know what the end char will be
startChar = (start==text || IsAnySP(start[-1]) || start[-1]=='>') ? ' ' : start[-1];
// if the start char is chiral, mirror-image
GetRString(url,URL_LEFT);
if (spot=PIndex(url,startChar))
{
chiral = True;
GetRString(url,URL_RIGHT);
startChar = *spot;
}
// move stop forward until we hit the delimitter
for (;stop < end;stop++) if (*stop==startChar || startChar==' ' && IsAnySP(*stop)) break;
}
// trim bogus trailers
if (!chiral)
{
GetRString(url,URL_TRAIL_IGNORE);
while(stop>start && PIndex(url,stop[-1])) stop--;
}
/*
* make a string out of it
*/
*url = 0;
if (start<stop && start>=text && stop<=end) MakePStr(url,start,stop-start);
selStart = start-*textH;
selEnd = stop-*textH;
HSetState(textH,state);
/*
* did we get something?
*/
if (*url)
{
// is it an email address?
if (PIndex(url,'@'))
{
if (openThem) MailtoURL(url,nil,false);
}
// is it a mailbox name in the filter report?
else if (GetWindowKind(GetMyWindowWindowPtr(win))==TEXT_WIN && win->ro && !*(*(TextDHandle)GetMyWindowPrivateData(win))->spec.name && !BoxSpecByName(&spec,url))
{
if (openThem) OpenMailbox(&spec,true,nil);
}
else if (IsHostname(url))
{
HostnameURL(url);
}
else
*url = 0;
// if we found a URL, select it and return maybe
if (*url)
{
if (selectThem && *url)
{
PeteSelect(nil,pte,selStart,selEnd);
PeteSetDirty(pte);
}
return(urlMaybe);
}
}
fail:
if (textH) UL(textH);
return(urlNot);
}
/**********************************************************************
* IsHostname - is something a host name?
**********************************************************************/
Boolean IsHostname(PStr string)
{
UPtr spot, end;
short dots = 0;
// trim off trailing punctuation (but leave :port syntax alone)
while (!(IsWordChar[string[*string]] || isdigit(string[*string]) || ':'==string[*string])) --*string;
end = string+*string+1;
// make sure all chars are ok, and count dots
for (spot=string+1;spot<end;spot++)
{
if (!IsHostChar(*spot)) return(false);
if (*spot=='.') dots++;
}
// we need at least one dot
return(dots>0);
}
/**********************************************************************
* HostnameURL - launch a URL that is a hostname
**********************************************************************/
OSErr HostnameURL(PStr hostname)
{
Str255 url;
UPtr spot;
Str31 proto;
OSErr err;
// first component might be a URL protocol
spot = hostname+1;
PToken(hostname,url,&spot,".");
if (!FindSTRNIndex(UrlColorStrn,url))
GetRString(url,HTTP); // if not, assume web
PSCopy(proto,url);
PCatC(url,':');
PCatC(url,'/');
PCatC(url,'/');
PSCat(url,hostname);
err = OpenLocalURL(url,nil);
if (err==fnfErr) err = OpenOtherURLPtr(proto,url+1,*url);
return(err);
}
/**********************************************************************
* URLStyle - set some text in the URL style
**********************************************************************/
void URLStyle(PETEHandle pte, long selStart,long selEnd,Boolean rude)
{
short peteDirtyWas;
Boolean winDirtyWas;
peteDirtyWas = PeteIsDirty(pte);
winDirtyWas = (*PeteExtra(pte))->win->isDirty;
PeteLabel(pte,selStart,selEnd,pURLLabel,pSpellLabel|pURLLabel|pTightAnalLabel|pLooseAnalLabel);
PETEMarkDocDirty(PETE,pte,peteDirtyWas);
(*PeteExtra(pte))->win->isDirty = winDirtyWas;
}
/**********************************************************************
* PeteURLScan - scan for & hilite URL's in text
**********************************************************************/
void PeteURLScan(MyWindowPtr win,PETEHandle pte)
{
long colon=-1;
Handle text;
long plainStart = (*PeteExtra(pte))->urlScanned;
long plainEnd = plainStart;
Boolean peteDirtyWas = PeteIsDirty(pte)!=0;
Boolean winDirtyWas = (*PeteExtra(pte))->win->isDirty;
PETEStyleEntry pse;
long lEnd, sLen;
Zero(pse);
if (plainStart >= PETEGetTextLen(PETE,pte))
{
(*PeteExtra(pte))->urlScanned = -1; // don't scan again
return;
}
if ((*PeteExtra(pte))->urlScanned==-1) return;
// First, see if we have a graphic, which we'd better
// not mark as a URL
PETEGetStyle(PETE,pte,plainStart,&sLen,&pse);
if (pse.psGraphic)
{
(*PeteExtra(pte))->urlScanned = sLen + pse.psStartChar;
return;
}
NoScannerResets++; // these changes don't really count
// If we're in the middle of a URL, back up to the
// beginning of the URL
if (pse.psStyle.textStyle.tsLabel&pURLLabel)
{
PETEFindLabelRun(PETE,pte,plainStart,&plainStart,&lEnd,pURLLabel,pURLLabel);
}
// Now, find the colon that might be the scheme character for a URL
PETEGetRawText(PETE,pte,&text);
if ((colon = SearchPtrHandle(":",1,text,plainStart,True,False,nil))>0) //if (!(err=PETEFindText(PETE,pte,":",1,(*PeteExtra(pte))->urlScanned,0x7fffffff,&colon,smSystemScript)))
{
// If we're at the end of the text, or if
// we have a double-colon, this can't be a URL
if (colon>=GetHandleSize(text)-1 || (*text)[colon+1]==':')
{
plainEnd = colon+2;
(*PeteExtra(pte))->urlScanned = colon+2;
}
// If we've landed in the middle of a graphic, don't
// recognize a URL, but do "clean" everything before us.
else if (PETEGetStyle(PETE,pte,colon,&sLen,&pse), pse.psGraphic)
{
plainEnd = pse.psStartChar;
(*PeteExtra(pte))->urlScanned = sLen + pse.psStartChar;
}
// If the thing we found a URL in is in a link, set the
// scanner past it, but clear url to before it
else if (pse.psStyle.textStyle.tsLabel&pLinkLabel)
{
PETEFindLabelRun(PETE,pte,lEnd,&plainEnd,&lEnd,pLinkLabel,pLinkLabel);
(*PeteExtra(pte))->urlScanned = lEnd;
}
// Ok, now find out if we have a URL here
else if (AttIsSelected(win,pte,colon,colon,attColor,&plainEnd,&lEnd) ||
urlMaybe<URLIsSelected(win,pte,colon,colon,urlColor,&plainEnd,&lEnd))
{
plainEnd--;
(*PeteExtra(pte))->urlScanned = lEnd+1;
PeteNoLabel(pte,lEnd+1,lEnd+2,pURLLabel);
}
else
{
// Nope, not URL. Set scanner past it
plainEnd = colon+1;
(*PeteExtra(pte))->urlScanned = colon+1;
}
}
else
{
// found no colons; clear url style all the way to the end
plainEnd = 0x7ffffff;
(*PeteExtra(pte))->urlScanned = -1; // don't scan again
}
// Clear label from stuff we've determined is plain
if (plainStart<plainEnd) PeteNoLabel(pte,plainStart,plainEnd,pURLLabel);
// Reset dirty bits
if (!peteDirtyWas) PETEMarkDocDirty(PETE,pte,False);
(*PeteExtra(pte))->win->isDirty = winDirtyWas;
// If there are more changes, they do count
NoScannerResets--;
}
/**********************************************************************
* ParseURL - parse a URL
* protocol://[host/]query
**********************************************************************/
OSErr ParseURL(PStr url,PStr proto,PStr host,PStr query)
{
OSErr err = noErr;
Ptr queryPtr;
long queryLen;
err = ParseURLPtr(url+1,*url,proto,host,&queryPtr,&queryLen);
if (err!=fnfErr)
{
if (query)
{
if (queryPtr && queryLen)
MakePPtr(query,queryPtr,queryLen);
else
*query = 0;
}
}
return(err);
}
/**********************************************************************
* ParseURL - parse a URL
* protocol://[host/]query
**********************************************************************/
OSErr ParseURLPtr(PStr url,long length,PStr proto,PStr host,Ptr *queryPtr,long *queryLen)
{
UPtr spot = url;
UPtr oldSpot;
Str255 temp;
char slash[2];
Boolean slashyScheme;
short missingSlashes = 0;
slash[0] = '/'; slash[1] = 0;
/*
* protocol is first bit
*/
if (IndexPtr(url,length,':') && PTokenPtr(url,length,temp,&spot,":") &&
(!EqualStrRes(temp,URL) || PTokenPtr(url,length,temp,&spot,":")))
{
if (proto) PCopy(proto,temp);
if (!*temp) return(fnfErr); // no proto
if ('0'<=temp[1] && temp[1]<='9') return(fnfErr); // digits not allowed
if (!IsWordChar[temp[1]]) return(fnfErr); // must be alpha char
slashyScheme = StrIsItemFromRes(temp,SLASHY_SCHEMES,",");
if (slashyScheme || spot[0] == spot[1] && spot[1] == '/')
{
/*
* ignore run of slashes
*/
missingSlashes = 2;
while (spot<url+length && *spot=='/')
{
missingSlashes--;
spot++;
if (!slashyScheme && !missingSlashes) break; // only worry about extra slashes
// for slashy schemes. This especially
// excludes file:, which can legitimately
// have more than two slashes
}
/*
* Extract next component
*/
if (PTokenPtr(url,length,temp,&spot,slash))
{
if (host) PCopy(host,temp);
oldSpot = spot;
if (PTokenPtr(url,length,temp,&spot,slash))
{
/*
* reparse from old spot to end of url, to pick up slashes in query
*/
if (queryPtr)
{
*queryPtr = oldSpot;
*queryLen = url + length - oldSpot;
if (*queryLen <= 0)
{
*queryPtr = nil;
*queryLen = 0;
}
}
}
else
{
/*
* oops, no query
*/
if (queryPtr)
{
*queryPtr = nil;
*queryLen = 0;
}
}
return(missingSlashes ? kNSLBadURLSyntax : noErr);
}
}
else
{
if (host) *host = 0;
if (queryPtr)
{
*queryPtr = spot;
*queryLen = url + length - spot;
if (*queryLen <= 0)
{
*queryPtr = nil;
*queryLen = 0;
}
}
return(missingSlashes ? kNSLBadURLSyntax : noErr);
}
}
return(fnfErr);
}
//
// ParseProtocolFromURLPtr
//
// Like ParseURL except we handle URL's in excess of 255 characters -- and only
// care about the protocol. This is a quickie function written to support long
// URL's for adware. It's extremely lax, but servicable in our (supposedly)
// glasshouse environment of canned URL's.
//
OSErr ParseProtocolFromURLPtr (UPtr url, short length, PStr proto)
{
UPtr spot = url+1;
Str255 temp;
// Don't search more than 255 characters (we do limit the protocol string)
length = MIN(length, sizeof (temp) -1);
for (spot = url; spot < url + length; spot++)
if (*spot == ':') {
temp[0] = spot - url;
BlockMoveData (url, &temp[1], temp[0]);
if (temp[0]) {
if (proto)
PCopy(proto,temp);
if (('0' <= temp[1] && temp[1] <= '9') || !IsWordChar[temp[1]])
return(fnfErr); // digits not allowed
return (noErr);
}
}
return (fnfErr);
}
/**********************************************************************
* ParsePortFromHost - grab server and port number from a host str
* Assumes host came from a URL parsed by ParseURL, and that URL was
* in the form:
* protocol://host[:portNo]/[query]
* If no port number was specified, *port is set to zero, and the
* function returns false.
**********************************************************************/
Boolean ParsePortFromHost(PStr host,PStr server,long *port)
{
Str255 serverStr;
Str255 portStr;
UPtr spot;
long urlPort;
if (server && (server != host))
*server = 0;
if (port)
*port = 0;
spot = host+1;
if (!PToken(host,serverStr,&spot,":"))
goto NoPort;
if (!PToken(host,portStr,&spot,"/") || !portStr[0])
goto NoPort;
StringToNum(portStr,&urlPort);
if (server)
PCopy(server,serverStr);
if (port)
*port = urlPort;
return true;
NoPort:
if (server)
PCopy(server,serverStr);
return false;
}
/************************************************************************
* FileURL - open a file URL
************************************************************************/
OSErr FileURL(PStr path)
{
FSSpec spec;
OSErr err;
Str255 local;
FInfo info;
PCopy(local,path);
TrLo(local+1,*local,"/",":");
FixURLString(local);
err = FSMakeFSSpec(Root.vRef,Root.dirId,local,&spec);
if (!err)
{
err = FSpGetFInfo(&spec,&info);
if (!err && info.fdType!=SETTINGS_TYPE && info.fdCreator==CREATOR)
// It's a Eudora document. Just open it directly.
err = OpenOneDoc(nil,&spec,&info);
else
err = OpenOtherDoc(&spec,(MainEvent.modifiers&controlKey)!=0,false,nil);
}
if (err == fnfErr)
{
FileSystemError(BINHEX_OPEN,local,err);
return(errAENoSuchObject);
}
return(err);
}
/************************************************************************
* JumpURL - open a jump URL
************************************************************************/
OSErr JumpURL(PStr action)
{
switch (FindSTRNIndex(URLActionStrn,action))
{
case actionSupport:
OpenAdwareURL (GetNagState (), TECH_SUPPORT_SITE, actionSupport, supportQuery, 0);
return 0;
}
return fnfErr;
}
/************************************************************************
* URLCombine - combine a base and a relative url
************************************************************************/
PStr URLCombine(PStr result,PStr base,PStr rel)
{
DeepURLHandle baseURL=NewZH(DeepURL);
DeepURLHandle relURL=NewZH(DeepURL);
Str255 localResult;
// move the relative url in; it may or may not stay
PCopy(localResult,rel);
if (baseURL && relURL)
{
DeepURLParse(rel,relURL);
// if the relative URL has a scheme, we're done already
if (!*(*relURL)->scheme)
{
DeepURLParse(base,baseURL);
// if the base URL doesn't have a scheme, we're done
if (*(*baseURL)->scheme)
{
// sigh. Here we go.
// first, we copy the scheme, since to get here the base must
// have a scheme and the relative must not
PCopy((*relURL)->scheme,(*baseURL)->scheme);
// if the site is defined, we're pretty much done
if (!(*relURL)->isSite)
{
// nope. inherit the site
if ((*relURL)->isSite = (*baseURL)->isSite)
PCopy((*relURL)->site,(*baseURL)->site);
// Combine the paths
URLCombinePaths(LDRef(relURL)->path,LDRef(baseURL)->path,LDRef(relURL)->path);
}
DuhString(localResult,relURL);
}
}
}
ZapHandle(baseURL);
ZapHandle(relURL);
return(PCopy(result,localResult));
}
/************************************************************************
* DuhString - convert a deep url handle to a string
************************************************************************/
PStr DuhString(PStr url,DeepURLHandle duh)
{
*url = 0;
// All duh's have schemes
PCat(url,(*duh)->scheme);
PCatC(url,':');
// May or may not have a site. If we do, add // and site
if ((*duh)->isSite)
{
PCatC(url,'/');
PCatC(url,'/');
PCat(url,(*duh)->site);
}
// May or may not have a path. (Weird, but...)
// if we do, concatenate. Will have leading slash already
if ((*duh)->path)
{
PCat(url,(*duh)->path);
}
// May or may not have a query. If we do, add ? and query
if ((*duh)->isQuery)
{
PCatC(url,'?');
PCat(url,(*duh)->query);
}
// May or may not have a fragment id. If we do, add # and fragment
if ((*duh)->isFragment)
{
PCatC(url,'#');
PCat(url,(*duh)->fragment);
}
return(url);
}
/************************************************************************
* URLCombinePaths - weld two paths into one
************************************************************************/
PStr URLCombinePaths(PStr into,PStr base,PStr rel)
{
Str255 localInto;
Str255 token;
UPtr spot;
if (!*rel)
{
PCopy(localInto,base); // empty rel
RemLastComponent(localInto);
if (*localInto && localInto[*localInto]!='/') PCatC(localInto,'/');
}
else if (!*base) PCopy(localInto,rel); // empty base
else if (rel[1]=='/') PCopy(localInto,rel); // absolute rel
else
{
// Sigh. Do it the hard way
PCopy(localInto,base);
RemLastComponent(localInto); // delete everything after last /
//if (*localInto && localInto[*localInto]!='/') PCatC(localInto,'/'); // make sure there is a trailing /
if (*rel>=2 && rel[*rel]=='.' && rel[*rel-1]=='/') --*rel; // delete dot from trailing /.'s
while (*rel>=3 && rel[*rel]=='/' && rel[*rel-1]=='.' && rel[*rel-2]=='/') *rel -= 2; // delete ./ from trailing /./'s
for (spot=rel+1;PToken(rel,token,&spot,Slash+1);)
{
if (*localInto>1) // we have a non-zero component somewhere
{
if (*token==1 && token[1]=='.')
{
; // ignore current directory specification
if (localInto[*localInto]!='/') PCatC(localInto,'/'); // but make sure path ends in /
}
else if (*localInto>1 && *token==2 && token[1]==token[2] && token[2]=='.' && !TrailingDotDot(localInto))
{
if (localInto[*localInto]=='/' && *localInto>1) --*localInto;
RemLastComponent(localInto);
if (localInto[*localInto]!='/') PCatC(localInto,'/'); // make sure path ends in /
}
else
{
if (localInto[*localInto]!='/') PCatC(localInto,'/');
PSCat(localInto,token);
}
}
else
{
if (localInto[*localInto]!='/') PCatC(localInto,'/');
PSCat(localInto,token);
}
}
if (rel[*rel]=='/' && localInto[*localInto]!='/') PCatC(localInto,'/');
}
PCopy(into,localInto);
return(into);
}
/************************************************************************
* TrailingDotDot - does the string end in a component of ".."?
************************************************************************/
Boolean TrailingDotDot(PStr string)
{
Str255 local;
PCopy(local,string);
if (local[*local]=='/') --*local; // trim trailing /
if (*local<2) return(False);
if (local[*local]!='.') return(False);
if (local[*local-1]!='.') return(False);
if (*local==2 || local[*local-2]=='/') return(True);
return(False);
}
/************************************************************************
* RemLastComponent - remove the last component of a url, unless that is
* a single slash
************************************************************************/
PStr RemLastComponent(PStr path)
{
UPtr spot;
for (spot=path+*path;spot>path;spot--)
if (*spot=='/')
{
*path = spot-path-1;
break;
}
return(path);
}
/************************************************************************
* DeepURLParse - parse a url into lots of components
************************************************************************/
typedef enum
{
urlsNot, // we don't know what we have
urlsScheme, // just saw a scheme
urlsColon, // just saw the colon after the scheme
urlsFirstSlash, // just saw our first slash
urlsSecondSlash, // just saw a second slash
urlsSite, // just saw a site component
urlsQuestion, // just saw a question mark
urlsPoundSign, // just saw a pound sign
urlsAllDone
} URLStateEnum;
void DeepURLParse(PStr url, DeepURLHandle duh)
{
UPtr spot, end;
Str255 token;
URLStateEnum state=urlsNot;
Byte c;
*url = RemoveChar(' ',url+1,*url);
*url = RemoveChar('\015',url+1,*url);
*url = RemoveChar('\t',url+1,*url);
end = url+*url+1;
for (spot=url+1;c=URLToken(url,token,&spot);)
{
switch (state)
{
case urlsNot:
switch (c)
{
case '/': state=urlsFirstSlash; break;
case '?': state=urlsQuestion; (*duh)->isQuery=True; break;
case '#': state=urlsPoundSign; (*duh)->isFragment=True; break;
default:
if ( ('a'<=c && c<='z' || 'A'<=c && c<='Z' || // legal scheme chars
'0'<=c && c<='9' || c=='+' || c=='-' || c=='.') // legal scheme chars
&& spot<end && *spot==':') // followed by colon
{
// we have a scheme
spot++; // skip colon
PCopy((*duh)->scheme,token);
state = urlsScheme;
}
else
{
PCopy((*duh)->path,token);
state = urlsSite;
}
break;
}
break;
case urlsScheme:
switch(c)
{
case '/': state=urlsFirstSlash; break;
case '?': state=urlsQuestion; (*duh)->isQuery=True; break;
case '#': state=urlsPoundSign; (*duh)->isFragment=True; break;
default:
// must be a path
PCopy((*duh)->path,token);
state = urlsSite;
break;
}
break;
case urlsFirstSlash:
if (c=='/')
{
state = urlsSecondSlash;
(*duh)->isSite = True;
}
else
{
PCatC((*duh)->path,'/');
switch(c)
{
case '?': state=urlsQuestion; (*duh)->isQuery=True; break;
case '#': state=urlsPoundSign; (*duh)->isFragment=True; break;
default:
state = urlsSite;
PCat((*duh)->path,token);
break;
}
}
break;
case urlsSecondSlash:
state = urlsSite;
switch(c)
{
case '/': PCopy((*duh)->path,token); break; // empty site
case '?': state=urlsQuestion; (*duh)->isQuery=True; break;
case '#': state=urlsPoundSign; (*duh)->isFragment=True; break;
default: PCopy((*duh)->site,token); break;
}
break;
case urlsSite:
switch(c)
{
case '?': state=urlsQuestion; (*duh)->isQuery=True; break;
case '#': state=urlsPoundSign; (*duh)->isFragment=True; break;
default: PCat((*duh)->path,token); break;
}
break;
case urlsQuestion:
if (c=='#') {state=urlsPoundSign; (*duh)->isFragment=True;}
else PCat((*duh)->query,token);
break;
case urlsPoundSign:
PCat((*duh)->fragment,token);
break;
default:
ASSERT(0); // we don't belong here!
break;
}
}
}
/************************************************************************
* URLToken - tokenize a url
************************************************************************/
Byte URLToken(PStr url,PStr token,UPtr *spot)
{
UPtr end = url+*url+1;
Byte c;
token[0] = token[1] = 0;
if (*spot<end)
{
// pop off first char
c = **spot;
++*spot;
PCatC(token,c);
if (c=='/' || c=='?' || c==':' || c=='#')
; // we're done
else
for (c=**spot;!(c=='/' || c=='?' || c==':' || c=='#') && *spot<end;c=*++*spot)
PCatC(token,c);
}
return(token[1]);
}
/************************************************************************
* URLEscape - escape "dangerous" characters in a url
************************************************************************/
PStr URLEscape(PStr url)
{
return URLEscapeLo(url, false);
}
PStr URLEscapeLo(PStr url,Boolean allPercents)
{
Str255 local;
UPtr into, from, end;
TrimAllWhite(url);
into = local+1;
end = url + *url + 1;
for (from=url+1;from<end && into<local+sizeof(local)-1;from++)
{
switch (*from)
{
case '%':
if (!allPercents && end-from>2 && IsHexDig(from[1]) && IsHexDig(from[2]))
{
// add the percent, because it's (cross your fingers) part of a %-literal
*into++ = '%';
break;
}
// else fall through and escape
case ' ':
case '<':
case '>':
case '"':
if (into-local<sizeof(local)-4)
{
*into++ = '%';
Bytes2Hex(from,1,into);
into += 2;
}
break;
default:
*into++ = *from;
break;
}
}
*local = into-local-1;
PCopy(url,local);
return(url);
}
/************************************************************************
* URLPathEscape - escape "dangerous" characters in a path component
************************************************************************/
PStr URLPathEscape(PStr path)
{
Str255 local;
UPtr into, from, end;
into = local+1;
end = path + *path + 1;
for (from=path+1;from<end && into<local+sizeof(local)-1;from++)
{
if (*from>'~' || !IsWordChar[*from] && ('0'>*from || '9'<*from))
{
if (into-local<sizeof(local)-4)
{
*into++ = '%';
Bytes2Hex(from,1,into);
into += 2;
}
}
else
*into++ = *from;
}
*local = into-local-1;
PCopy(path,local);
return(path);
}
/************************************************************************
* URLQueryEscape - escape "dangerous" characters in a query component parameter
************************************************************************/
PStr URLQueryEscape(PStr query)
{
Str255 local;
UPtr into, from, end;
into = local+1;
end = query + *query + 1;
for (from=query+1;from<end && into<local+sizeof(local)-1;from++)
{
switch (*from)
{
case '%':
case '&':
case '?':
case ':':
case ' ':
if (into-local<sizeof(local)-4)
{
*into++ = '%';
Bytes2Hex(from,1,into);
into += 2;
}
break;
default:
if (*from & 0x80)
{
*into++ = '%';
Bytes2Hex(from,1,into);
into += 2;
}
else
*into++ = *from;
break;
}
}
*local = into-local-1;
PCopy(query,local);
return(query);
}
/************************************************************************
* URLSubstitute - change one url into another for MHTML. Returns fnfErr if
* no file exists for URL
************************************************************************/
OSErr URLSubstitute(PStr resultURL,PStr mhtmlIDStr,PStr origURL,PETEHandle pte)
{
short item;
MyWindowPtr win = (*PeteExtra(pte))->win;
StackHandle parts;
uLong hash;
PartDesc pd;
Str255 cid;
UPtr spot;
if (GetWindowKind(GetMyWindowWindowPtr(win))==MESS_WIN)
{
parts = (*PeteExtra(pte))->partStack;
StringToNum(mhtmlIDStr,&hash);
spot = origURL+1;
PToken(origURL,cid,&spot,":");
if (EqualStrRes(cid,ProtocolStrn+proCID))
{
PCopy(cid,origURL);
// remove six chars; cid://
*cid -= 6;
BMD(cid+7,cid+1,*cid);
MyLowerStr(cid);
hash = HashWithSeed(cid,hash);
}
else
{
MyLowerStr(origURL);
hash = HashWithSeed(origURL,hash);
}
for (item=0;item<(*parts)->elCount;item++)
{
StackItem(&pd,item,parts);
if (pd.absURL==hash || pd.cid==hash)
{
MakeFileURL(resultURL,&pd.spec,0);
return(noErr);
}
}
}
return(fnfErr);
}
/************************************************************************
* function - purpose
************************************************************************/
PStr MakeFileURL(PStr url,FSSpecPtr spec,short proto)
{
Str255 name;
long dirID;
CInfoPBRec hfi;
OSErr err;
/*
* filename
*/
PCopy(name,spec->name);
URLPathEscape(name);
PCopy(url,name);
/*
* folders
*/
for (dirID = spec->parID; dirID!=2; dirID = hfi.dirInfo.ioDrParID)
{
Zero(hfi);
*name = 0;
hfi.dirInfo.ioNamePtr = name;
hfi.dirInfo.ioFDirIndex = -1;
hfi.dirInfo.ioVRefNum = spec->vRefNum;
hfi.dirInfo.ioDrDirID = dirID;
if (err=PBGetCatInfoSync(&hfi))
hfi.dirInfo.ioDrParID = 2;
URLPathEscape(name);
PCatC(name,Slash[1]);
PInsert(url,255,name,url+1);
}
GetMyVolName(spec->vRefNum,name);
URLPathEscape(name);
PCatC(name,Slash[1]);
PInsert(url,255,name,url+1);
GetRString(name,proto ? proto : ProtocolStrn+proFile);
PCatC(name,':');
PCatC(name,Slash[1]);
PCatC(name,Slash[1]);
PCatC(name,Slash[1]);
PInsert(url,255,name,url+1);
return(url);
}
/************************************************************************
* URLOkHere - is it ok to put a URL here?
************************************************************************/
Boolean URLOkHere(PETEHandle pte)
{
return((*PeteExtra(pte))->win->hasSelection);
}
Boolean RemoveOk;
/************************************************************************
* URLDlgFilter - filter proc for url dialog
************************************************************************/
pascal Boolean URLDlgFilter(DialogPtr dgPtr,EventRecord *event,short *item)
{
ControlHandle cntl;
Str255 text;
extern pascal Boolean DlgFilter(DialogPtr dgPtr,EventRecord *event,short *item);
GetDIText(dgPtr,urldText,text);
GetDialogItemAsControl(dgPtr,urldOK,&cntl);
if (cntl) ActivateMyControl(cntl,*text!=0);
GetDialogItemAsControl(dgPtr,urldRemove,&cntl);
if (cntl) ActivateMyControl(cntl,RemoveOk);
return(DlgFilter(dgPtr,event,item));
}
/************************************************************************
* InsertURL - ask the user for a url and insert it under the current selection
************************************************************************/
OSErr InsertURL(PETEHandle pte)
{
long start, end;
Str255 url;
Str255 proto;
DialogPtr dgPtr;
MyWindowPtr dgPtrWin;
short item;
extern ModalFilterUPP DlgFilterUPP;
OSErr err = noErr;
DECLARE_UPP(URLDlgFilter,ModalFilter);
INIT_UPP(URLDlgFilter,ModalFilter);
PeteGetTextAndSelection(pte,nil,&start,&end);
ClearLink(pte,false,start,end,nil,&start,&end,url);
RemoveOk = *url != 0;
if (dgPtrWin = GetNewMyDialog(URL_DLOG,nil,nil,InFront))
{
dgPtr = GetMyWindowDialogPtr(dgPtrWin);
SetDIText(dgPtr,urldText,url);
StartMovableModal(dgPtr);
ShowWindow(GetDialogWindow(dgPtr));
HiliteButtonOne(dgPtr);
do
{
MovableModalDialog(dgPtr,URLDlgFilterUPP,&item);
GetDIText(dgPtr,urldText,url);
}
while (item==urldOK && (ParseURL(url,proto,nil,nil) || !*proto) && ComposeStdAlert(kAlertNoteAlert,NOT_URL)==2);
EndMovableModal(dgPtr);
CloseMyWindow(GetDialogWindow(dgPtr));
if (item==urldRemove)
{
*url = 0;
item = urldOK;
}
if (item==urldOK)
{
URLEscape(url);
InsertURLLo(pte,start,end,url);
}
}
else err = ResError();
return(err);
}
/************************************************************************
* InsertURLLo - insert a specific url at a specific spot
* We're supposed to wind up with:
* <http://bite/me>BITE ME
* The <>'s will be linkLabel, as will the BITE ME
* The url will be linkLabel|URLLabel
* There will be a nil graphic over the <>'s and url
************************************************************************/
OSErr InsertURLLo(PETEHandle pte,long start,long end,PStr url)
{
PETEStyleEntry pse;
OSErr err;
Str255 localURL;
long urlStart;
PeteCalcOff(pte);
urlStart = start; // in case no url there already, use start
ClearLink(pte,false,start,end,&urlStart,&start,&end,nil);
PetePrepareUndo(pte,peUndoMoreLink,urlStart,end,nil,nil);
ClearLink(pte,true,start,end,nil,&start,&end,nil);
*localURL = 0;
/*
* ok, let's insert the URL now
*/
if (*url)
{
PeteStyleAt(pte,start,&pse);
*localURL = 0;
if (url[1]!='<') PCatC(localURL,'<');
PCat(localURL,url);
if (localURL[*localURL]!='>') PCatC(localURL,'>');
// Make the link link-labelled
err = PeteLabel(pte,start,end,pLinkLabel,pLinkLabel|pURLLabel); ASSERT(!err);
// Insert the url
if (!err)
{
pse.psStartChar = 0;
pse.psGraphic = 1;
pse.psStyle.graphicStyle.graphicInfo = nil;
**Pslh = pse;
err = PETEInsertTextPtr(PETE,pte,start,localURL+1,*localURL,Pslh); ASSERT(!err);
urlStart = start;
}
// Set the labels
if (!err) err = PeteLabel(pte,start,start+*localURL,pLinkLabel,pLinkLabel|pURLLabel); ASSERT(!err);
if (!err) err = PeteLabel(pte,start+1,start+*localURL-1,pLinkLabel|pURLLabel,pLinkLabel|pURLLabel); ASSERT(!err);
}
// and cleanup
done:
if (err) PeteKillUndo(pte);
else
{
PeteGetTextAndSelection(pte,nil,nil,&end);
PeteFinishUndo(pte,peUndoMoreLink,urlStart,end);
}
PeteCalcOn(pte);
return(err);
}
/************************************************************************
* ClearLink - Clear all links in a selection
************************************************************************/
OSErr ClearLink(PETEHandle pte,Boolean clear,long start, long end, long *urlStartP, long *newStart, long *newEnd,PStr oldURL)
{
long linkStart, linkEnd;
long urlStart, urlEnd;
Boolean foundLink = false;
Handle text;
Str255 url;
if (oldURL) *oldURL = 0;
/*
* is there a link there already?
*/
for (linkStart = start;
!PETEFindLabelRun(PETE,pte,linkStart,&linkStart,&linkEnd,pLinkLabel,pLinkLabel) && linkStart < end;
linkStart = linkEnd)
{
if (urlStartP) {*urlStartP=linkStart; urlStartP=nil;}
// we have a link
if (!PETEFindLabelRun(PETE,pte,linkStart,&urlStart,&urlEnd,pLinkLabel|pURLLabel,pLinkLabel|pURLLabel))
{
// we have a url in the link
// shall we copy it?
if (oldURL && !*oldURL)
{
PeteGetTextAndSelection(pte,&text,nil,nil);
MakePStr(url,*text+urlStart,urlEnd-urlStart);
PCopy(oldURL,url);
}
if (!clear) return(noErr);
// Delete the URL
urlStart--; urlEnd++;
ASSERT(urlStart==linkStart);
ASSERT(urlEnd<linkEnd);
PeteDelete(pte,urlStart,urlEnd);
// adjust pointers
if (start>urlEnd) start -= urlEnd - urlStart;
else if (start>urlStart) start -= start-urlStart;
linkEnd -= urlEnd-urlStart;
end -= urlEnd-urlStart;
// expand insertion point to link
if (start==end)
{
start = linkStart;
end = linkEnd;
}
// clear the linkness
PeteLabel(pte,linkStart,linkEnd,0,pLinkLabel|pURLLabel);
// record the very first link spot
if (newStart) {*newStart = start; newStart=nil;}
}
}
// if we adjusted the end, return it
if (newEnd) *newEnd = end;
return(noErr);
}
/**********************************************************************
* URLIsNaughty - is this a bad url? Asks the user to see for sure
**********************************************************************/
short URLIsNaughty(PStr linkText,Handle hURL,Boolean interact)
{
Str255 urlHost, linkHost;
Str255 s;
short result = URLIsNaughtyLo(linkText,hURL,urlHost,linkHost);
if (result && interact)
{
ComposeRString(s,URLNaughtyReasonStrn+result,urlHost,linkHost);
if (kAlertStdAlertCancelButton!=ComposeStdAlert(Caution,NAUGHTY_URL_ALERT,s)) return 0;
}
return result;
}
/**********************************************************************
* URLIsNaughtyLo - is this a bad url?
**********************************************************************/
short URLIsNaughtyLo(PStr linkText,Handle hURL,PStr urlHost,PStr linkHost)
{
Str255 url, urlProto, s;
Str255 linkProto;
OSErr err = noErr;
// copy as much of it as will fit into a string
MakePStr(url,*hURL,GetHandleSize(hURL));
// Let's start by parsing the URL, shall we?
if (ParseURL(url,urlProto,urlHost,s)) return kURLNSyntax; // not a url
if (urlHost[*urlHost]=='.') --*urlHost; // ignore trailing "."
// We try to do these tests in reverse order of badness, so that the
// worst problem is given to the user
// Try to parse the link text as a url
// Make a local copy of the link text
PCopy(s,linkText);
// semicolon, shmemicolon
if (PIndex(s,';') && (!PIndex(s,':') || PIndex(s,';')<PIndex(s,':')))
*PIndex(s,';') = ':';
// Kill whitespace; it's not significant here, and can be used to fool us
CollapseLWSP(TrimAllWhite(s));
PStripChar(s,' ');
if (fnfErr==ParseURL(s,linkProto,linkHost,s))
{
GetRString(s,HTTP);
PCat(s,"\p://");
PInsert(linkText,255,s,linkText+1);
err = ParseURL(linkText,linkProto,linkHost,s);
if (linkHost[*linkHost]=='.') --*linkHost; // ignore trailing "."
}
// if the link is a URL, it better be similar to the actual URL!
if (!err && StrIsItemFromRes(urlProto,URL_HOST_CHECK_PROTOS,","))
{
if (IsHostname(linkHost))
{
// strip noise prefixes from url hosts
StripLeadingItems(linkHost,URL_NOTNAUGHTY_PREFIXES);
StripLeadingItems(urlHost,URL_NOTNAUGHTY_PREFIXES);
if (!StringSame(urlHost,linkHost)) return kURLNLinkMismatch;
}
}
#ifdef DARN_EUROPEENS_HAD_CREATIVITY
// Does the host contain any tld's?
if (!EndsWithItem(urlHost,URL_NAUGHTY_EXCEPTIONS))
{
StripCountryFromHost(s,urlHost);
if (ItemFromResAppearsInStr(NAUGHTY_URL_TLDS,s,",")) return kURLNTLD;
}
#endif // DARN_EUROPEENS_HAD_CREATIVITY
// Is the host numeric?
PCopy(s,urlHost);
PStripChar(s,'.');
if (*s && AllDigits(s+1,*s)) return kURLNIP;
// Is the host encoded?
if (PIndex(urlHost,'%')) return kURLNEncoded;
// couldn't find anything
return 0;
}
/**********************************************************************
* StripCountryFromHost - get country-code off a hostname
**********************************************************************/
PStr StripCountryFromHost(PStr stripped,PStr clothed)
{
Str255 s;
Str63 token;
UPtr spot;
GetRString(s,COUNTRY_DOMAINS);
spot = s+1;
PCopy(stripped,clothed);
while (PToken(s,token,&spot,","))
{
PInsertC(token,sizeof(token),'.',token+1);
if (EndsWith(stripped,token))
{
*stripped -= *token;
break;
}
}
return stripped;
}
/**********************************************************************
* URLHelpTagList - Display the url and maybe a warning if the mouse is
* over a link
**********************************************************************/
Boolean URLHelpTagList(PETEHandle pte,Point mouse)
{
EventRecord event;
for (;pte;pte=PeteNext(pte))
{
if (PtInPETE(mouse,pte))
{
OSEventAvail(nil,&event);
URLHelpTag(pte,mouse);
return(True);
}
}
MyHMHideTag();
return(False);
}
/**********************************************************************
* URLHelpTag - Display the url and maybe a warning if the mouse is
* over a link
**********************************************************************/
void URLHelpTag(PETEHandle pte,Point mouse)
{
Str255 link, warning;
Handle hURL = nil;
HMHelpContentRec hmr;
static PETEHandle oldPTE;
static long oldOffset;
long offset;
*link = *warning = 0;
// if different PTE, get rid of old tag
if (pte!=oldPTE)
{
oldOffset = -1;
MyHMHideTag();
}
oldPTE = pte;
// are we in the region?
if (PtInPETE(mouse,pte) && !PETEPositionToOffset(PETE,pte,mouse,&offset))
{
// if we're at the same place, nevermind
if (oldOffset != offset)
{
MyHMHideTag();
oldOffset = offset;
// got something under the cursor?
if ( (!PrefIsSet(PREF_NO_SCAMWATCH_TIPS)) && (PeteLinkAt(pte,offset,&hURL,link)) )
{
Str255 urlHost, linkHost;
short result;
LHElement lh;
Point pt = mouse;
// jump through hoops for the brain-dead help manager
Zero(hmr);
hmr.version = kMacHelpVersion;
hmr.tagSide = kHMDefaultSide;
PETEOffsetToPosition(PETE,pte,offset,&pt,&lh);
LocalToGlobal(&mouse);
LocalToGlobal(&pt);
SetRect(&hmr.absHotRect,mouse.h-1,pt.v,mouse.h+1,pt.v+lh.lhHeight);
hmr.content[kHMMinimumContentIndex].contentType = kHMPascalStrContent;
// generate a warning, if we need to
if (result=URLIsNaughtyLo(link,hURL,urlHost,linkHost))
{
ComposeRString(warning,URLNaughtyReasonStrn+result,urlHost,linkHost);
PCatC(warning,'\015');
PCatC(warning,'\015');
}
else
{
// cut them slack if anchor and href are the same
MakePStr(warning,LDRef(hURL),GetHandleSize(hURL));
if (StringSame(warning,link))
{
ZapHandle(hURL);
return;
}
*warning = 0;
}
// copy the URL text to the link
MakePStr(link,LDRef(hURL),GetHandleSize(hURL));
// stick it all in and display it
PSCopy(hmr.content[kHMMinimumContentIndex].u.tagString,warning);
PSCat(hmr.content[kHMMinimumContentIndex].u.tagString,link);
hmr.content[kHMMaximumContentIndex].contentType = kHMNoContent;
MyHMDisplayTag(&hmr);
ZapHandle(hURL);
}
}
}
}
/**********************************************************************
* PeteLinkAt - is there a link here?
* If there is, we'll return the URL and the link text
**********************************************************************/
Boolean PeteLinkAt(PETEHandle pte,long offset,Handle *hURL,PStr link)
{
PETEStyleEntry pse;
uLong urlStart, urlStop;
uLong linkStart, linkStop;
Str255 linkText;
Handle text;
if (!PeteStyleAt(pte,offset,&pse) && (pse.psStyle.textStyle.tsLabel&pLinkLabel))
// whole think is link, get it all
if (!PETEFindLabelRun(PETE,pte,offset,&urlStart,&urlStop,pLinkLabel,pLinkLabel))
// get just the link part, which is at the end
if (!PETEFindLabelRun(PETE,pte,urlStop-1,&linkStart,&linkStop,pLinkLabel,pLinkLabel|pURLLabel))
if (!PeteGetTextAndSelection(pte,&text,nil,nil))
{
// the actual link starts after a '>' character, so skip first char
MakePStr(linkText,*text+linkStart+1,linkStop-linkStart-1);
PCopy(link,linkText);
// the url starts at the second char of the whole thing
if (!PETEFindLabelRun(PETE,pte,urlStart+2,&urlStart,&urlStop,pURLLabel,pURLLabel))
if (!PETEGetText(PETE,pte,urlStart,urlStop,hURL))
return true;
}
return false;
}
#pragma segment Main
/**********************************************************************
* URLScan - scan open texts for URL's and hilite them
* Finds one URL at a time
**********************************************************************/
void URLScan(void)
{
WindowPtr winWP;
MyWindowPtr win;
PETEHandle pte;
Str255 title;
PETEDocInfo info;
for (winWP=FrontWindow_();winWP;winWP=GetNextWindow(winWP))
{
if (IsKnownWindowMyWindow(winWP))
{
win = GetWindowMyWindowPtr(winWP);
for (pte=win->pteList;pte;pte=PeteNext(pte))
{
if (errAECorruptData==PETEGetDocInfo(PETE,pte,&info))
{
WindowPtr pteWinWP = GetMyWindowWindowPtr((*PeteExtra(pte))->win);
*title = 0;
GetWTitle(pteWinWP,title);
ComposeStdAlert(kAlertCautionAlert,DOC_DAMAGED_FMT_ASTR+ALRTStringsOnlyStrn,title);
CloseMyWindow(pteWinWP);
return;
}
while ((*PeteExtra(pte))->urlScanned >= 0)
{
UsingWindow(winWP);
PeteURLScan(win,pte);
if (NEED_YIELD) return;
}
}
}
}
}