home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 35 Internet / 35-Internet.zip / rxiutl16.zip / urlget.cmd < prev    next >
OS/2 REXX Batch file  |  2000-01-11  |  62KB  |  1,947 lines

  1. /* Fetch documents via HTTP or FTP by URL */
  2. call RxFuncAdd 'SysLoadFuncs','RexxUtil','SysLoadFuncs'
  3. call SysLoadFuncs
  4. call RxFuncAdd 'SockLoadFuncs','RxSock','SockLoadFuncs'
  5. signal on SYNTAX
  6. nosock=1
  7. call SockLoadFuncs 'q'
  8. nosock=0
  9. signal off SYNTAX
  10.  
  11. numeric digits 10
  12. parse version rxversion
  13. if word(rxversion,1)\='OBJREXX' then do
  14.     say ''
  15.     say 'HTTPGET.CMD requires Object REXX to function.'
  16.     say ''
  17.     say 'You are running: '||rxversion
  18.     say ''
  19.     say 'README.TXT contains more information.'
  20.     exit 6
  21. end
  22. rxversion=rxversion~word(2)
  23.  
  24. do until stream(rfile,'c','query exists')=''
  25.     tmp1=random(0,65535)
  26.     tmp2=random(0,65535)
  27.     rfile=tmp1~d2x~right(4,'0')||tmp2~d2x~right(4,'0')||'.tmp'
  28. end
  29.  
  30. '@ver > '||rfile
  31. call SysFileSearch 'Windows 95',rfile,'chk1.'
  32. call SysFileSearch 'Win95',rfile,'chk2.'
  33.  
  34. if chk1.0>0 | chk2.0>0 then win95=.true
  35. else win95=.false
  36.  
  37. call SysFileDelete rfile
  38.  
  39. .local['RXIVERSION']=0.16
  40. /* The following is for writing quick debug messages */
  41. .local['DEBUG']=.stream~new('debug.log')
  42. exitval=-1
  43. probtype=0
  44. success=0
  45. failure=0
  46. sockfail=0
  47. servfail=0
  48.  
  49. signal on HALT
  50. signal on SYNTAX
  51. signal on ERROR
  52. signal on NOVALUE
  53. signal on FAILURE
  54.  
  55. .local['XFS']='XFER_FINISHED'
  56. .local['STFS']='STATUS_THREAD_EXIT'
  57. parse source progpath
  58. progpath=progpath~subword(3)
  59. pdrive=filespec('drive',progpath)
  60. ppath=filespec('path',progpath)
  61. inifile=.stream~new(pdrive||ppath||'urlget.ini')
  62. getem=.false
  63. getfw=.false
  64. getlog=.false
  65. emadd=''
  66. firewall=''
  67. .local['LOGGING']=.false
  68.  
  69. emadd=SysIni(inifile~qualify, 'URLGET', 'EMAIL_ADDRESS')
  70. if emadd='ERROR:' then getem=.true
  71. firewall=SysIni(inifile~qualify, 'URLGET', 'FIREWALL')
  72. if firewall='ERROR:' then getfw=.true
  73. .local['LOGGING']=SysIni(inifile~qualify, 'URLGET', 'LOGGING')
  74. if .logging='ERROR:' then getlog=.true
  75.  
  76. if getem then do
  77.     ok=5
  78.     do until ok=1
  79.         call SysCls
  80.         call SysCurState 'off'
  81.         if ok=0 then do
  82.             call SysCurPos 10,0
  83.             say 'Invalid e-mail address (must contain "@").'
  84.         end
  85.         call SysCurPos 12,0
  86.         say 'Please enter your e-mail address for anonymous FTP transfers:'
  87.         call SysCurState 'on'
  88.         parse pull emadd
  89.         if emadd~pos('@')=0 then ok=0
  90.         else ok=1
  91.     end
  92.     call SysIni inifile~qualify,'URLGET','EMAIL_ADDRESS',emadd
  93. end
  94. if getfw then do
  95.     ok=5
  96.     do until ok=1
  97.         call SysCls
  98.         call SysCurState 'off'
  99.         if ok=0 then do
  100.             call SysCurPos 10,0
  101.             say 'Answer only Y or N.'
  102.         end
  103.         call SysCurPos 12,0
  104.         say 'Are you connected to the Internet through a firewall or proxy server? (Y/N)'
  105.         ans=SysGetKey('noecho')
  106.         if ans~translate\='Y' & ans~translate\='N' then ok=0
  107.         else do
  108.             ok=1
  109.             if ans~translate='Y' then firewall=.true
  110.             else firewall=.false
  111.         end
  112.     end
  113.     call SysIni inifile~qualify,'URLGET','FIREWALL',firewall
  114. end
  115. if getlog then do
  116.     ok=5
  117.     do until ok=1
  118.         call SysCls
  119.         call SysCurState 'off'
  120.         if ok=0 then do
  121.             call SysCurPos 10,0
  122.             say 'Answer only Y or N.'
  123.         end
  124.         call SysCurPos 12,0
  125.         say 'Enable logging? (Y/N)'
  126.         ans=SysGetKey('noecho')
  127.         if ans~translate\='Y' & ans~translate\='N' then ok=0
  128.         else do
  129.             ok=1
  130.             if ans~translate='Y' then .local['LOGGING']=.true
  131.             else .local['LOGGING']=.false
  132.         end
  133.     end
  134.     call SysIni inifile~qualify,'URLGET','LOGGING',.logging
  135. end
  136.  
  137. if \win95 then .local['STINTERVAL']=50
  138. else .local['STINTERVAL']=100
  139. .local['BLOCKSIZE']=10240
  140. .local['MAXRETRIES']=1000
  141. if \win95 then .local['COMMANDDELAY']=0.1
  142. else .local['COMMANDDELAY']=1
  143. .local['OVERWRITE']=.true
  144.  
  145. .local['NOERROR']=0
  146. .local['SOCKERROR']=1
  147. .local['SERVERROR']=2
  148. .local['PATHERROR']=3
  149. .local['AUTHERROR']=4
  150. .local['NOIMPLEMENT']=5
  151. .local['OTHERERROR']=6
  152. .local['ARGERROR']=7
  153. .local['USERABORT']=8
  154. .local['OPTION']=9
  155.  
  156. urlstring=''
  157. resume=.false
  158. ignorepassive=.false
  159. forceresume=.false
  160. overwrite=.true
  161. dupecheck=.true
  162. quietmode=.false
  163. .local['SUPERQUIET']=.false
  164. addtourl=.false
  165. parse arg argstring
  166. if argstring~strip='' then do
  167.     call usage 
  168.     exit 2
  169. end
  170. if argstring~pos('"')>0 then do
  171.     parse var argstring before '"' urlstring '"' after
  172.     urlstring=urlstring~strip
  173.     argstring=before~strip||' '||after~strip
  174. end
  175. do i=1 to argstring~words
  176.     badvar=.false
  177.     currarg=argstring~word(i)~strip
  178.     select
  179.         when addtourl then do
  180.             urlstring=urlstring||currarg
  181.             addtourl=.false
  182.         end
  183.         when (currarg~left(7)~translate='HTTP://' |,
  184.               currarg~left(6)~translate='FTP://') then do
  185.             urlstring=currarg
  186.             if currarg~right(1)=';' then addtourl=.true
  187.         end
  188.         when currarg~left(1)='@' then do
  189.             parse var currarg '@' urllist
  190.             urllist=urllist~strip
  191.             ulist=.stream~new(urllist)
  192.             if ulist~query('exists')='' then do
  193.                 say ''
  194.                 say 'URL list file not found!'
  195.                 exit 2
  196.             end
  197.             else urlstring='fromfile'
  198.         end
  199.         when (currarg~left(1)='/' | currarg~left(1)='-') then do
  200.             select
  201.                 when currarg~length=2 then do
  202.                     select
  203.                         when currarg~right(1)~translate='Q' then quietmode=.true
  204.                         when currarg~right(1)~translate='R' then resume=.true
  205.                         when currarg~right(1)~translate='N' then overwrite=.false
  206.                         when currarg~right(1)~translate='C' then dupecheck=.false
  207.                         when currarg~right(1)~translate='P' then ignorepassive=.true
  208.                         when currarg~right(1)~translate='L' then .local['LOGGING']=.true
  209.                         when currarg~right(1)~translate='W' then firewall=.true
  210.                         when currarg~right(1)~translate='F' then do
  211.                             resume=.true
  212.                             forceresume=.true
  213.                         end
  214.                         otherwise do
  215.                             say ''
  216.                             say 'Invalid switch, '||currarg||'.'
  217.                             call usage
  218.                             exit 2
  219.                         end
  220.                     end
  221.                 end
  222.                 when currarg~length=3 & currarg~right(2)~translate='QQ' then do
  223.                     .local['SUPERQUIET']=.true
  224.                     quietmode=.true
  225.                 end
  226.                 when currarg~left(2)~right(1)~translate='D' then do
  227.                     .local['COMMANDDELAY']=currarg~right(currarg~length-2)
  228.                     if .commanddelay~datatype('num')=0 then badvar=.true
  229.                     else do
  230.                         if win95 then do
  231.                             if .commanddelay~datatype('w')=0 then badvar=.true
  232.                             else if (.commanddelay<0 | .commanddelay>5) then badvar=.true
  233.                         end
  234.                         else if (.commanddelay<0 | .commanddelay>5) then badvar=.true
  235.                     end
  236.                     if badvar then do
  237.                         say ''
  238.                         say 'Invalid command delay, '||.commanddelay||'.'
  239.                         exit 2
  240.                     end
  241.                 end
  242.                 when currarg~left(2)~right(1)~translate='B' then do
  243.                     .local['BLOCKSIZE']=currarg~right(currarg~length-2)
  244.                     if .blocksize~datatype('W')=0 then badvar=.true
  245.                     else if (.blocksize<512 | .blocksize>65535) then badvar=.true
  246.                     if badvar then do
  247.                         say ''
  248.                         say 'Invalid blocksize, '||.blocksize||'.'
  249.                         exit 2
  250.                     end
  251.                 end
  252.                 when currarg~left(2)~right(1)~translate='T' then do
  253.                     .local['STINTERVAL']=currarg~right(currarg~length-2)
  254.                     if .blocksize~datatype('W')=0 then badvar=.true
  255.                     else do
  256.                         if win95 then do
  257.                             if (.stinterval<1 | .stinterval>5) then badvar=.true
  258.                             else .local['STINTERVAL']=.stinterval*100
  259.                         end
  260.                         else if (.stinterval<1 | .stinterval>500) then badvar=.true
  261.                     end
  262.                     if badvar then do
  263.                         say ''
  264.                         say 'Invalid status time interval, '||.stinterval||'.'
  265.                         exit 2
  266.                     end
  267.                 end
  268.                 when currarg~left(2)~right(1)~translate='M' then do
  269.                     .local['MAXRETRIES']=currarg~right(currarg~length-2)
  270.                     if .maxretries~datatype('W')=0 then badvar=.true
  271.                     else if (.maxretries<0 | .maxretries>9999999999) then badvar=.true
  272.                     if badvar then do
  273.                         say ''
  274.                         say 'Invalid maximum retries, '||.maxretries||'.'
  275.                         exit 2
  276.                     end
  277.                 end
  278.                 otherwise do
  279.                     call qasay ''
  280.                     call qasay 'Invalid parameter ('||currarg||').'
  281.                     call usage 
  282.                     exit 2
  283.                 end
  284.             end
  285.         end
  286.         when currarg~left(1)=';' then do
  287.             if currarg~length=1 then do
  288.                 addtourl=.true
  289.                 urlstring=urlstring||currarg
  290.             end
  291.             else urlstring=urlstring||currarg
  292.         end
  293.         otherwise do
  294.             call qasay ''
  295.             call qasay 'Invalid parameter ('||currarg||').'
  296.             call usage 
  297.             exit 2
  298.         end
  299.     end
  300. end
  301.     
  302. urls=.queue~new
  303.  
  304. if urlstring='fromfile' then do 
  305.     do while ulist~lines>0
  306.         turl=ulist~linein~strip
  307.         select
  308.             when turl~left(2)='//' then nop
  309.             when turl~left(1)='#' then nop
  310.             when turl~left(1)=';' then nop
  311.             when turl~left(2)='/*' then nop
  312.             otherwise urls~queue(turl)
  313.         end
  314.     end
  315.     ulist~close
  316. end
  317. else urls~queue(urlstring)
  318. urls=urls~makearray
  319.     
  320. currdrive=directory()~left(2)
  321. fatcheck=SysFileSystemType(currdrive)
  322.  
  323. srow=-1
  324. .local['CRLF']='0d0a'x
  325. .local['LF']='0a'x
  326.  
  327. if \.superquiet then call SysCurState 'off'
  328. .local['LOG']=.logfile~new(pdrive||ppath||'\urlget.log')
  329. .local['CON']=.estream~new('stdout')
  330. if \quietmode then scs=.scrstatus~new
  331. .local['PADDR']=''
  332. .local['PDIR']=''
  333. .local['PPROT']=''
  334. .local['PUSER']=''
  335.  
  336. do i=1 to urls~items
  337.     if \.superquiet then call SysCls
  338.     passivefail=.false
  339.     .local['SIZE']=0
  340.     if \quietmode then .local['SQ']=.queue~new
  341.     if \quietmode then .local['TQ']=.queue~new
  342.     currxfail=.false
  343.     retries=0
  344.     .local['RESTART']=0
  345.     newname=0
  346.     headend=.array~new
  347.     headend[1]=.crlf||.crlf
  348.     headend[2]=.lf||.lf
  349.     parse value urls[i] with ustring ';' .
  350.     .local['CURRURL']=ustring
  351.     parse value urls[i] with prot '://' host '/' path ';' lfile
  352.     prot=prot~strip~translate
  353.     if prot\='HTTP' & prot\='FTP' then do
  354.         if .logging then .log~failed(.argerror,'invalid URL')
  355.         if urls~items>1 then do
  356.             failure=1
  357.             iterate i
  358.         end
  359.         else do
  360.             call usage
  361.             exit 2
  362.         end
  363.     end
  364.     host=host~strip
  365.     if host='' | host~pos(' ')>0 then do
  366.         if .logging then .log~failed(.argerror,'invalid URL')
  367.         if urls~items>1 then do
  368.             failure=1
  369.             iterate i
  370.         end
  371.         else do
  372.             call usage
  373.             exit 2
  374.         end
  375.     end
  376.     if prot='HTTP' then do
  377.         parse var host host ':' port
  378.         port=port~strip
  379.         if port='' then port=80
  380.     end
  381.     else do
  382.         if host~pos('@')>0 then do
  383.             parse var host userpass '@' host
  384.             if userpass~pos(':')>0 then parse var userpass user ':' pass
  385.             else do
  386.                 if .logging then .log~failed(.argerror,'invalid FTP URL')
  387.                 if urls~items>1 then do 
  388.                     failure=1
  389.                     iterate i
  390.                 end
  391.                 else do
  392.                     call qasay ''
  393.                     call qasay 'Invalid FTP URL supplied.'
  394.                     exit 2
  395.                 end
  396.             end
  397.             user=user~strip
  398.             pass=pass~strip
  399.             parse value .currurl with before (pass) after
  400.             .local['CURRURL']=before||'*'~copies(pass~length)||after
  401.             parse var host host ':' port
  402.             port=port~strip
  403.             if port='' then port=21
  404.             host=host~strip
  405.         end
  406.         else do
  407.             user='anonymous'
  408.             pass=emadd
  409.             parse var host host ':' port
  410.             port=port~strip
  411.             if port='' then port=21
  412.             host=host~strip
  413.         end
  414.     end
  415.     if path~pos('/')>0 then do
  416.         rpath=path~reverse
  417.         parse var rpath file '/' dirpath
  418.         file=file~reverse~strip
  419.         dirpath='/'||dirpath~reverse~strip
  420.     end
  421.     else do
  422.         file=path~strip
  423.         dirpath='/'
  424.     end
  425.     lfile=lfile~strip
  426.     if file='' then do
  427.         if lfile='' then do
  428.             if .logging then .log~failed(.argerror,'invalid URL')
  429.             if urls~items>1 then do
  430.                 failure=1
  431.                 iterate i
  432.             end
  433.             else do
  434.                 call usage
  435.                 exit 2
  436.             end
  437.         end
  438.     end
  439.     if lfile='' then lfile=file
  440.     if dirpath~pos(' ')>0 & prot='HTTP' then do until dirpath~pos(' ')=0
  441.         parse var dirpath before ' ' after
  442.         dirpath=before||'%20'||after
  443.     end
  444.     longea=0
  445.     dfile=file
  446.     if file~pos(' ')>0 & prot='HTTP' then do until file~pos(' ')=0
  447.         parse var file before ' ' after
  448.         file=before||'%20'||after
  449.     end
  450.     ldrive=filespec('drive',lfile)
  451.     if ldrive\='' then do
  452.         fatcheck=SysFileSystemType(ldrive)
  453.     end
  454.     if fatcheck='FAT' & win95=.false then do
  455.         ldrive=filespec('drive',lfile)
  456.         if ldrive='' then ldrive='DEF'
  457.         lpath=filespec('path',lfile)
  458.         if lpath='' then lpath='DEF'
  459.         lname=filespec('name',lfile)
  460.         parse var lname basename '.' ext
  461.         parse value lname~reverse with ext2 '.' basename2
  462.         select 
  463.             when ext\=ext2~reverse then newname=1
  464.             when basename\=basename2~reverse then newname=1
  465.             when basename~length>8 then newname=1
  466.             when ext~length>3 then newname=1
  467.             when basename~pos(' ')>0 then newname=1
  468.             when ext~pos(' ')>0 then newname=1
  469.             otherwise newname=0
  470.         end
  471.         if newname=1 then do
  472.             if ext~length<=3 then shext='.'||ext
  473.             else shext='.UGF'
  474.         end
  475.         done=0
  476.         if newname=1 then do until done=1
  477.             p1=right(d2x(random(65535)),4,'0')
  478.             p2=right(d2x(random(65535)),4,'0')
  479.             select
  480.                 when ldrive='DEF' & lpath='DEF' then ltot=p1||p2||shext
  481.                 when ldrive='DEF' then ltot=lpath||p1||p2||shext
  482.                 when lpath='DEF' then ltot=ldrive||p1||p2||shext
  483.                 otherwise ltot=ldrive||lpath||p1||p2||shext
  484.             end
  485.             outfile=.stream~new(ltot)
  486.             if outfile~query('exists')='' then do
  487.                 done=1
  488.                 longea=1
  489.                 longval='FDFF'x||filespec('name',lfile)~length~d2c||,
  490.                         '00'x||filespec('name',lfile)
  491.                 lname=filespec('name',outfile~qualify)
  492.                 select
  493.                     when ldrive='DEF' & lpath='DEF' then lfile=lname
  494.                     when ldrive='DEF' then lfile=lpath||lname
  495.                     when lpath='DEF' then lfile=ldrive||lname
  496.                     otherwise lfile=ldrive||lpath||lname
  497.                 end
  498.             end
  499.         end
  500.         else outfile=.stream~new(lfile)
  501.     end
  502.     else outfile=.stream~new(lfile)
  503.  
  504.     if \overwrite & \(prot='FTP' & resume) then do
  505.         if outfile~query('exists')\='' then do
  506.             call qasay ''
  507.             call qasay 'Local file exists, not overwriting.'
  508.             if .logging then .log~failed(.option,'overwrite disabled, file exists')
  509.             iterate i
  510.         end
  511.     end
  512.  
  513.     servport=host||':'||port
  514.  
  515.     if prot='HTTP' then user=''
  516.  
  517.     if (servport=.paddr & prot='FTP' & user=.puser) then cconn=.true
  518.     else do
  519.         cconn=.false
  520.         if .pprot='FTP' then do
  521.             if fs~defaultname='a FTPSESSION' then do
  522.                 foobar=fs~logoff
  523.                 csock~shutdown('both')
  524.                 csock~close
  525.             end
  526.         end
  527.     end
  528.  
  529.     .local['PPROT']=prot
  530.     .local['PADDR']=servport
  531.     if prot='FTP' then .local['PUSER']=user
  532.  
  533.     if cconn then do
  534.         call qasay ''
  535.         call qasay ''
  536.         .con~~charout('Using current connection...'||.crlf)~flush
  537.         if dirpath=.pdir then do
  538.             .con~~charout('Current directory is correct...'||.crlf)~flush
  539.         end
  540.         else do
  541.             .con~~charout('Changing to directory '||dirpath||'... ')~flush
  542.             rval=fs~chdir(dirpath)
  543.             if rval='HALT' then signal HALT
  544.             if rval\=.noerror then do
  545.                 call qasay ''
  546.                 call qasay 'Failed to change working directory.'
  547.                 failure=1
  548.                 select
  549.                     when rval=.sockerror then sockfail=1
  550.                     when rval=.patherror then do
  551.                         servfail=1
  552.                         call qasay 'Directory does not exist or access denied.'
  553.                         iterate i
  554.                     end
  555.                     otherwise do
  556.                         call qasay 'Error = '||rval
  557.                         iterate i
  558.                     end
  559.                 end
  560.             end
  561.             else do
  562.                 .con~~charout('Done'||.crlf)~flush
  563.                 .local['PDIR']=dirpath
  564.             end
  565.         end
  566.     end
  567.  
  568.     if \cconn then do
  569.  
  570.         csock=.socket~new
  571.  
  572.         call qasay ''
  573.         call qasay ''
  574.         .con~~charout('Connecting to host '||host||' on port '||port||'... ')~flush
  575.         check=csock~connect(servport)
  576.         if check='HALT' then signal HALT
  577.         if check=-1 then do
  578.             if .logging then .log~failed(.sockerror,'host connection failed')
  579.             .con~~charout('Failed!'||.crlf)~flush
  580.             call sockclean('control')
  581.             sockfail=1
  582.             failure=1
  583.             iterate i
  584.         end
  585.         else .con~~charout('Done'||.crlf)~flush
  586.     
  587.         if prot='HTTP' then httpsendstring='GET '||dirpath||'/'||file||' HTTP/1.0'||.crlf||,
  588.                            'Accept: */*'||.crlf||,
  589.                            'Accept-Encoding: */*'||.crlf||,
  590.                            'User-Agent: URLGet/'||.rxiversion||'  Object REXX/'||rxversion||.crlf||,
  591.                            .crlf
  592.     end
  593.     
  594.     if prot='HTTP' then do
  595.         .con~~charout('Sending request string... ')~flush
  596.         check=csock~send(httpsendstring)
  597.         if check='HALT' then signal HALT
  598.         if check=-1 then do
  599.             if .logging then .log~failed(.sockerror,'HTTP send request failed')
  600.             .con~~charout('Failed!'||.crlf)~flush
  601.             call sockclean('control')
  602.             sockfail=1
  603.             failure=1
  604.             iterate i            
  605.         end
  606.         else .con~~charout('Done'||.crlf)~flush
  607.     end
  608.  
  609.     else do 
  610.         if \cconn then do
  611.             fs=.FTPSession~new(csock)
  612.             .con~~charout('Logging in')~flush
  613.             if user='anonymous' then .con~~charout('... ')~flush
  614.             else .con~~charout(' as '||user||'... ')~flush
  615.             rval=fs~login(user,pass)
  616.             if rval='HALT' then signal HALT
  617.             if rval\=.noerror then do
  618.                 failure=1
  619.                 .con~~charout('Failed!'||.crlf)~flush
  620.                 call qasay ''
  621.                 select
  622.                     when rval=.sockerror then do
  623.                         sockfail=1
  624.                         call qasay 'Socket error logging in.'
  625.                     end
  626.                     when rval=.serverror then do
  627.                         servfail=1
  628.                         call qasay 'Login not allowed.'
  629.                     end
  630.                     otherwise nop
  631.                 end
  632.                 iterate i
  633.             end
  634.             else .con~~charout('Done'||.crlf)~flush
  635.             .con~~charout('Setting binary transfer mode... ')~flush
  636.             rval=fs~setbinary
  637.             if rval='HALT' then signal HALT
  638.             if rval\=.noerror then do
  639.                 .con~~charout('Failed!'||.crlf)~flush
  640.                 call qasay ''
  641.                 call qasay 'Failed to set binary transfer mode.'
  642.                 failure=1
  643.                 if rval=.sockerror then sockfail=1
  644.                 else servfail=1
  645.                 iterate i
  646.             end
  647.             else .con~~charout('Done'||.crlf)~flush
  648.             .con~~charout('Changing to directory '||dirpath||'... ')~flush
  649.             rval=fs~chdir(dirpath)
  650.             if rval='HALT' then signal HALT
  651.             if rval\=.noerror then do
  652.                 call qasay ''
  653.                 call qasay 'Failed to change working directory.'
  654.                 failure=1
  655.                 select
  656.                     when rval=.sockerror then sockfail=1
  657.                     when rval=.patherror then do
  658.                         servfail=1
  659.                         call qasay 'Directory does not exist or access denied.'
  660.                         iterate i
  661.                     end
  662.                 end
  663.             end
  664.             else do
  665.                 .con~~charout('Done'||.crlf)~flush
  666.                 .local['PDIR']=dirpath
  667.             end
  668.         end
  669.         if file\='' then do
  670.             .con~~charout('Retrieving file size... ')~flush
  671.             rval=fs~getsize(file)
  672.             if rval='HALT' then signal HALT
  673.             if rval\=.noerror then do
  674.                 select
  675.                     when rval=.sockerror then do
  676.                         sockfail=1
  677.                         failure=1
  678.                         call qasay ''
  679.                         call qasay 'Socket error while getting file size.'
  680.                         iterate i
  681.                     end
  682.                     when rval=.falseimplement then do
  683.                         if forceresume then do
  684.                             servfail=1
  685.                             failure=1
  686.                             if .logging then .log~failed(.serverror,'resume required, cannot determine remote file size')
  687.                             call qasay ''
  688.                             call qasay 'Cannot determine remote size.'
  689.                             iterate i
  690.                         end
  691.                         resume=.false
  692.                         fs~setresume(.false)
  693.                         .local['SIZE']=0
  694.                     end
  695.                     otherwise .con~~charout('Failed!'||.crlf)~flush
  696.                 end
  697.             end
  698.             else do 
  699.                 .local['SIZE']=fs~size 
  700.                 .con~~charout('Done'||.crlf)~flush
  701.                 if .size=outfile~query('size') & dupecheck then do
  702.                     call qasay ''
  703.                     call qasay 'Local file exists, and is not smaller than server copy.'
  704.                     call qasay 'Not overwriting.'
  705.                     if .logging then .log~failed(.option,'dupe checking enabled, local copy same size as server copy')
  706.                     iterate i
  707.                 end
  708.             end
  709.         end
  710.         else .local['size']=0
  711.         if firewall then do
  712.             .con~~charout('Setting passive mode... ')~flush
  713.             rval=fs~setpassive
  714.             if rval='HALT' then signal HALT
  715.             if rval\=.noerror then do
  716.                 select
  717.                     when rval=.sockerror then do
  718.                         sockfail=1
  719.                         call qasay 'Socket error setting passive mode.'
  720.                     end
  721.                     when rval=.autherror then do
  722.                         if ignorepassive then do
  723.                             passivefail=.true
  724.                             .con~~charout('Failed!'||.crlf)~flush
  725.                         end
  726.                         else do
  727.                             failure=1
  728.                             servfail=1
  729.                             if .logging then .log~failed(.serverror,'passive mode required, but denied: '||.passdenycode)
  730.                             call qasay ''
  731.                             call qasay 'Server refused passive connection.'
  732.                             iterate i
  733.                         end
  734.                     end
  735.                     otherwise .con~~charout('Failed!'||.crlf)~flush
  736.                 end
  737.             end
  738.             else .con~~charout('Done'||.crlf)~flush
  739.         end
  740.         if (\firewall | (firewall & passivefail)) then do
  741.             .con~~charout('Sending data port... ')~flush
  742.             rval=fs~sendport
  743.             if rval='HALT' then signal HALT
  744.             if rval\=.noerror then do
  745.                 failure=1
  746.                 call qasay ''
  747.                 select
  748.                     when rval=.sockerror then do
  749.                         sockfail=1
  750.                         call qasay 'Socket error sending data port.'
  751.                     end
  752.                     otherwise do
  753.                         servfail=1
  754.                         call qasay 'Unable to send data port.'
  755.                     end
  756.                 end
  757.                 iterate i
  758.             end
  759.             else .con~~charout('Done'||.crlf)~flush
  760.         end
  761.         if resume & file\='' then do
  762.             if outfile~query('exists')\='' then do
  763.                 .con~~charout('Setting restart marker... ')~flush
  764.                 rval=fs~setrestart(outfile)
  765.                 if rval='HALT' then signal HALT
  766.                 if rval\=.noerror then do
  767.                     select
  768.                         when rval=.sockerror then do
  769.                             sockfail=1
  770.                             failure=1
  771.                             .con~~charout('Failed!'||.crlf)~flush
  772.                             call qasay ''
  773.                             call qasay 'Socket error setting restart marker.'
  774.                             iterate i
  775.                         end
  776.                         otherwise do
  777.                             if forceresume then do
  778.                                 failure=1
  779.                                 servfail=1    
  780.                                 if .logging then .log~failed(.serverror,'resume required, not supported by server')
  781.                                 .con~~charout('Failed!'||.crlf)~flush
  782.                                 call qasay ''
  783.                                 call qasay 'Server does not support resume, aborting transfer.'
  784.                                 iterate i
  785.                             end
  786.                             else .con~~charout('Failed!'||.crlf)~flush
  787.                         end
  788.                     end
  789.                 end
  790.                 else do
  791.                     .local['RESTART']=fs~restart
  792.                     .con~~charout('Done'||.crlf)~flush
  793.                 end
  794.             end
  795.         end
  796.         .con~~charout('Sending retrieve request... ')~flush
  797.         if file='' then rval=fs~retrievelisting
  798.         else rval=fs~retrieve
  799.         if rval='HALT' then signal HALT
  800.         if rval~objectname\='an Array' then do
  801.             failure=1
  802.             call qasay ''
  803.             select
  804.                 when rval=.sockerror then do
  805.                     sockfail=1
  806.                     call qasay 'Socket error on retrieve.'
  807.                 end
  808.                 otherwise do
  809.                     servfail=1
  810.                     call qasay 'Unable to retrieve.'
  811.                 end
  812.             end
  813.             iterate i
  814.         end
  815.         else do
  816.             dsock=rval[2]
  817.             if dsock~objectname~translate\='A SOCKET' then do
  818.                 failure=1
  819.                 if .logging then .log~failed(.othererror,'error with data socket')
  820.                 call qasay ''
  821.                 call qasay 'Error with data socket.'
  822.                 iterate i
  823.             end
  824.             else .con~~charout('Done'||.crlf)~flush
  825.         end 
  826.     end        
  827.  
  828.     total=0
  829.     if \quietmode then .sq~queue(total)
  830.     nofile=0     
  831.     headgone=0
  832.     security=0
  833.     dumbserver=0
  834.     .local['FINISHED']=.false
  835.     fileopen=.false
  836.     call time 'r'
  837.  
  838.     do until .finished
  839.         if prot='HTTP' then check=csock~receive(.blocksize)
  840.         else check=dsock~receive(.blocksize)
  841.         if check='HALT' then signal HALT
  842.         rvar=check[2]
  843.         if rvar<0 then do
  844.             if \headgone then do
  845.                 if retries>.maxretries then .local['FINISHED']=.true
  846.                 else retries=retries+1
  847.             end
  848.             else do
  849.                 if total+resume < size then do
  850.                     if retries>.maxretries then .local['FINISHED']=.true
  851.                     else retries=retries+1
  852.                 end
  853.                 else .local['FINISHED']=.true
  854.             end
  855.         end
  856.         else if rvar=0 then .local['FINISHED']=.true
  857.         data=check[1]
  858.         if \headgone then do
  859.             if prot='HTTP' & data~word(2)='404' then nofile=1
  860.             else if prot='HTTP' & data~word(2)='403' then security=1
  861.             else if prot='HTTP' & data~word(2)='406' then dumbserver=1
  862.             else if (data~pos(headend[1])>0 | data~pos(headend[2])>0) |,
  863.                      prot='FTP' then do
  864.                 headgone=1
  865.                 if prot='HTTP' then do
  866.                     if data~pos(headend[1])>0 then do
  867.                         he=headend[1]
  868.                         le=.crlf
  869.                     end
  870.                     else do
  871.                         he=headend[2]
  872.                         le=.lf
  873.                     end
  874.                     parse var data header (he) bufdata
  875.                     if header~pos('ength:')>0 then do
  876.                         header=header||le
  877.                         parse var header 'ength:' stemp (le)
  878.                         .local['SIZE']=stemp~strip
  879.                         if .size~datatype='NUM' then nop
  880.                         else .local['SIZE']=0
  881.                     end
  882.                     else .local['SIZE']=0
  883.                 end
  884.                 else bufdata=data
  885.                 if .size\=0 then do
  886.                     if .size=outfile~query('size') & dupecheck then do
  887.                         call qasay ''
  888.                         call qasay 'Local file exists, and is not smaller than server copy.'
  889.                         call qasay 'Not overwriting.'
  890.                         if .logging then .log~failed(.option,'dupe checking enabled, local copy same size as server copy')
  891.                         iterate i
  892.                     end
  893.                 end
  894.                 total=total+bufdata~length
  895.                 if \quietmode then .sq~queue(total)
  896.             end
  897.         end
  898.         else do
  899.             if \fileopen then do
  900.                 if (prot='HTTP' | (prot='FTP' & .restart=0)) then do
  901.                     if outfile~open('write replace')\='READY:' then do
  902.                         call qasay ''
  903.                         call qasay ''
  904.                         call qasay 'Unable to open file '||outfile~qualify||' for writing!'
  905.                         failure=1
  906.                         iterate i
  907.                     end
  908.                 end
  909.                 else do
  910.                     if outfile~open('write append')\='READY:' then do
  911.                         call qasay ''
  912.                         call qasay ''
  913.                         call qasay 'Unable to open file '||outfile~qualify||' for writing!'
  914.                         failure=1
  915.                         iterate i
  916.                     end
  917.                 end
  918.                 fileopen=.true
  919.                 call qasay 'Receiving URL:'
  920.                 call qasay ''
  921.                 if .currurl~length>79 then call qasay .currurl~left(78)||'>'
  922.                 else call qasay .currurl
  923.                 call qasay ''
  924.                 if lfile\=file then do
  925.                     call qasay 'Stored locally as '||lfile||'...'
  926.                     call qasay ''
  927.                 end
  928.                 if (prot='FTP' & .restart>0) then do
  929.                     call qasay 'Resuming from byte '||codel(.restart)||...
  930.                     call qasay ''
  931.                 end
  932.                 if \quietmode then scs~template
  933.                 if \quietmode then do
  934.                     if .size>0 then scs~size(.size)
  935.                     else scs~sizeunknown
  936.                 end
  937.                 else do
  938.                     if .size=0 then call qasay 'Total size: UNKNOWN'
  939.                     else call qasay 'Total size: '||codel(.size)
  940.                     call qasay ''
  941.                 end
  942.                 if \quietmode then scs~begin
  943.                 if outfile~charout(bufdata)\=0 then do
  944.                     if \quietmode then do
  945.                         .sq~queue(.xfs)
  946.                         stcheck=''
  947.                         do until stcheck=.stfs
  948.                             stcheck=.tq~pull
  949.                         end
  950.                     end
  951.                     call qasay ''
  952.                     call qasay ''
  953.                     call qasay 'Error writing data to '||outfile~qualify||'.'
  954.                     outfile~close
  955.                     failure=1
  956.                     iterate i
  957.                 end
  958.             end
  959.             if outfile~charout(data)\=0 then do
  960.                 if \quietmode then do
  961.                     .sq~queue(.xfs)
  962.                     stcheck=''
  963.                     do until stcheck=.stfs
  964.                         stcheck=.tq~pull
  965.                     end
  966.                 end
  967.                 call qasay ''
  968.                 call qasay ''
  969.                 call qasay 'Error writing data to '||outfile~qualify||'.'
  970.                 outfile~close
  971.                 failure=1
  972.                 iterate i
  973.             end
  974.             total=total+data~length
  975.             if \quietmode then .sq~queue(total)
  976.         end
  977.         if .finished then do
  978.             ct=time('e')
  979.             if ct=0 then ct=0.01
  980.             if \quietmode then .sq~queue(total)
  981.             if \quietmode then .sq~queue(.xfs)
  982.             if \quietmode then do
  983.                 stcheck=''
  984.                 do until stcheck=.stfs
  985.                     stcheck=.tq~pull
  986.                 end
  987.             end
  988.             if .size>0 then do
  989.                 if (total+.restart)<.size then do
  990.                     if .logging then .log~failed(.sockerror,'maximum retries exceeded')
  991.                     call qasay ''
  992.                     call qasay ''
  993.                     call qasay 'Maximum retries reached - transfer failed!'
  994.                     sockfail=1
  995.                     failure=1
  996.                     currxfail=.true
  997.                 end
  998.             end
  999.             rate=total%ct
  1000.             if \quietmode then do
  1001.                 call qasay ''
  1002.                 call qasay ''
  1003.             end
  1004.             call qasay codel(total)||' bytes received in '||codel(ct~format(,2))||,
  1005.                     ' seconds ('||codel(rate)||' bytes/sec.)'
  1006.             if .logging & \currxfail then do
  1007.                 if .restart>0 then .log~transfer(total,ct,.restart)
  1008.                 else .log~transfer(total,ct)
  1009.             end
  1010.         end
  1011.         if nofile=1 then do
  1012.             if .logging then .log~failed(.serverror,"remote file doesn't exist")
  1013.             call qasay ''
  1014.             call qasay ''
  1015.             call qasay "Remote file doesn't exist!"
  1016.             longea=0
  1017.         end
  1018.         if dumbserver=1 then do
  1019.             if .logging then .log~failed(.serverror,'improper server returned 406')
  1020.             call qasay ''
  1021.             call qasay ''
  1022.             call qasay "Incorrect protocol usage at remote server, returned 406!"
  1023.             longea=0
  1024.         end
  1025.         if security=1 then do
  1026.             if .logging then .log~failed(.serverror,'no access or other error')
  1027.             call qasay ''
  1028.             call qasay ''
  1029.             call qasay "No permission or other access error!"
  1030.             longea=0
  1031.         end
  1032.         if nofile=1 | dumbserver=1 | security=1 then leave
  1033.     end
  1034.     if nofile\=1 then do
  1035.         success=1
  1036.         outfile~close 
  1037.     end
  1038.     if longea=1 then call SysPutEA outfile~qualify,'.LONGNAME',longval
  1039.  
  1040.     if prot='FTP' then do
  1041.         fs~transconf
  1042.         dsock~shutdown('both')
  1043.         dsock~close
  1044.         if i=urls~items then do
  1045.             foobar=fs~logoff
  1046.             csock~shutdown('both')
  1047.             csock~close
  1048.         end
  1049.     end
  1050.     if prot='HTTP' then do
  1051.         csock~shutdown('both')
  1052.         csock~close
  1053.     end
  1054. end
  1055.  
  1056. select 
  1057.     when success=1 & failure=0 then exitval=0
  1058.     when success=1 & failure=1 then exitval=1
  1059.     when urls~items=1 & sockfail=1 then exitval=3
  1060.     when urls~items=1 & servfail=1 then exitval=4
  1061.     when urls~items>1 & sockfail=1 & servfail=1 & success=0 then exitval=30
  1062.     otherwise exitval=255
  1063. end
  1064.  
  1065. exit exitval
  1066.  
  1067. SYNTAX:
  1068. if nosock=1 then do
  1069.     call qasay ''
  1070.     call qasay 'Failed to load REXX Socket functions.'
  1071.     call qasay ''
  1072.     call qasay 'Program cannot continue.'
  1073.     exit 6
  1074. end
  1075. probtype='SYNTAX'
  1076. probline=sigl
  1077. signal handler
  1078. ERROR:
  1079. probtype='ERROR'
  1080. probline=sigl
  1081. signal handler
  1082. NOVALUE:
  1083. probtype='NOVALUE'
  1084. probline=sigl
  1085. signal handler
  1086. FAILURE:
  1087. probtype='FAILURE'
  1088. probline=sigl
  1089. signal handler
  1090. handler:
  1091. call qasay ''
  1092. call qasay ''
  1093. call qasay 'Problem of type '||probtype||' on source line #'||probline||'!'
  1094. call qasay ''
  1095. if symbol('rc')='VAR' then call qasay 'Cause of error: '||rc||' - '||errortext(rc)
  1096. call qasay ''
  1097. call qasay 'Source line content:'
  1098. call qasay sourceline(probline)
  1099. call qasay ''
  1100. call qasay 'Please notify thanny@home.com about this, with as much possible detail.'
  1101. exitval=6
  1102. HALT: 
  1103. if symbol('quietmode')='VAR' then if quietmode\=1 then do
  1104.     if srow=-1 | datatype(srow,'num')=0 then do
  1105.         parse value SysCurPos() with row col
  1106.         srow=row+2
  1107.     end
  1108.     call SysCurPos srow,75
  1109. end
  1110. else do
  1111.     call qasay ''
  1112.     call qasay ''
  1113. end
  1114. call qasay ''
  1115. call qasay ''
  1116. call qasay 'Aborting!'
  1117. if symbol('.log')='VAR' & probtype=0 then .log~failed(.userabort,'program halted')
  1118. call sockclean
  1119. if exitval=-1 then do
  1120.     select
  1121.         when success=0 then exitval=5
  1122.         when success=1 then do
  1123.             select
  1124.                 when failure=0 then exitval=0
  1125.                 when symbol('urls')='VAR' then do
  1126.                     if symbol('i')='VAR' then do
  1127.                         if urls~items>=i then exitval=1
  1128.                     end
  1129.                     else exitval=0
  1130.                 end
  1131.             end
  1132.         end
  1133.     end
  1134. end
  1135. if \quietmode then .sq~queue(.xfs)
  1136. exit exitval
  1137.  
  1138. sockclean: procedure expose csock dsock
  1139.  
  1140. if symbol('csock')='VAR' then if csock~objectname~translate='A SOCKET' then do
  1141.     csock~shutdown('both')
  1142.     csock~close
  1143. end
  1144. if symbol('dsock')='VAR' then if dsock~objectname~translate='A SOCKET' then do
  1145.     dsock~shutdown('both')
  1146.     dsock~close
  1147. end
  1148. return
  1149.  
  1150. /* Socket class - work in progress **************************************/
  1151. /*                                                                      */
  1152. ::class socket
  1153.  
  1154. ::method init
  1155. expose portnum
  1156. use arg type,proto
  1157. if symbol('type')='LIT' then type='SOCK_STREAM'
  1158. if symbol('proto')='LIT' then proto=0
  1159. if type~datatype('num')=1 then portnum=type
  1160. else portnum=SockSocket('AF_INET',type,proto)
  1161. return
  1162.  
  1163. ::method sock
  1164. expose portnum
  1165. return portnum
  1166.  
  1167. ::method connect
  1168. use arg servname
  1169. signal on HALT
  1170. parse var servname hostname ':' host.!port
  1171. parse var hostname o1 '.' o2 '.' o3 '.' o4
  1172. if o1~datatype='NUM' & o2~datatype='NUM' & o3~datatype='NUM' &,
  1173.    o4~datatype='NUM' then do
  1174.     if o1~datatype('w')=1 & o2~datatype('w')=1 & o3~datatype('w')=1 &,
  1175.        o4~datatype('w')=1 then do
  1176.         if (o1>=0 & o1<=255) & (o2>=0 & o2<=255) & (o3>=0 & o3<=255) &,
  1177.            (o4>=0 & o4<=255) then do
  1178.             stype='IP'
  1179.             host.!addr=hostname
  1180.         end
  1181.     end
  1182. end
  1183. else stype='NAME'            
  1184. if stype='NAME' then call SockGetHostByName hostname,'host.!'
  1185. host.!family='AF_INET'
  1186. rc=SockConnect(self~sock,'host.!')
  1187. return rc
  1188. HALT:
  1189. return 'HALT'
  1190.  
  1191. ::method send
  1192. use arg sendstring
  1193. signal on HALT
  1194. rc=SockSend(self~sock,sendstring)
  1195. return rc
  1196. HALT:
  1197. return 'HALT'
  1198.  
  1199. ::method receive
  1200. use arg length
  1201. signal on HALT
  1202. if length='LENGTH' then length=1024
  1203. check=SockRecv(self~sock,'sockdata',length)
  1204. return .array~new~~put(sockdata,1)~~put(check,2)
  1205. HALT:
  1206. return 'HALT'
  1207.  
  1208. ::method bind
  1209. host.!addr=SockGetHostId()
  1210. host.!port=self~sock
  1211. host.!family='AF_INET'
  1212. rval=SockBind(self~sock,'host.!')
  1213. return rval
  1214.  
  1215. ::method listen
  1216. rval=SockListen(self~sock, 65536)
  1217. return rval
  1218.  
  1219. ::method accept
  1220. use arg goodaddr
  1221. newsock=SockAccept(self~sock)
  1222. return newsock
  1223.  
  1224. ::method shutdown
  1225. use arg how
  1226. select
  1227.     when how~translate='FROM' then how=0
  1228.     when how~translate='TO' then how=1
  1229.     otherwise how=2
  1230. end
  1231. call SockShutDown self~sock,how
  1232. return
  1233.  
  1234. ::method close
  1235. call SockClose self~sock
  1236. return
  1237. /*                                                                      */
  1238. /************************************************************************/
  1239.  
  1240. /* FTP session object ***************************************************/
  1241. /*                                                                      */
  1242. ::class FTPSession
  1243.  
  1244. ::method init
  1245. expose fsize marker dsock lsock csock passivemode
  1246. signal on HALT
  1247. use arg csock
  1248. marker=0
  1249. passivemode=.false
  1250. return
  1251.  
  1252. ::method size
  1253. expose fsize
  1254. signal on HALT
  1255. return fsize
  1256.  
  1257. ::method login
  1258. use arg user,pass
  1259. signal on HALT
  1260. rval=self~receive(.blocksize)
  1261. select
  1262.     when rval='HALT' then return 'HALT'
  1263.     when rval[1]=-1 then do
  1264.         if .logging then .log~failed(.sockerror,'waiting for FTP server')
  1265.         return .sockerror
  1266.     end
  1267.     when rval[2]\='220' then do
  1268.         if .logging then .log~failed(.serverror,'not accepting users: '||rval[2])
  1269.         return .serverror
  1270.     end
  1271.     otherwise nop
  1272. end
  1273. rval=self~send('USER '||user)
  1274. select
  1275.     when rval='HALT' then return 'HALT'
  1276.     when rval=-1 then do
  1277.         if .logging then .log~failed(.sockerror,'sending user name')
  1278.         return .sockerror
  1279.     end
  1280.     otherwise nop
  1281. end
  1282. rval=self~receive(.blocksize)
  1283. select
  1284.     when rval='HALT' then return 'HALT'    
  1285.     when rval[1]=-1 then do
  1286.         if .logging then .log~failed(.sockerror,'awaiting password request')
  1287.         return .sockerror
  1288.     end
  1289.     when rval[2]\='331' then do
  1290.         if .logging then .log~failed(.serverror,'user not allowed: '||rval[2])
  1291.         return .serverror
  1292.     end
  1293.     otherwise nop
  1294. end
  1295. rval=self~send('PASS '||pass)
  1296. select
  1297.     when rval='HALT' then return 'HALT'
  1298.     when rval=-1 then do
  1299.         if .logging then .log~failed(.sockerror,'sending password')
  1300.         return .sockerror
  1301.     end
  1302.     otherwise nop
  1303. end
  1304. rval=self~receive(.blocksize)
  1305. select
  1306.     when rval='HALT' then return 'HALT'
  1307.     when rval[1]=-1 then do
  1308.         if .logging then .log~failed(.sockerror,'awaiting password acceptance')
  1309.         return .sockerror
  1310.     end
  1311.     when rval[2]\='230' then do 
  1312.         if .logging then .log~failed(.serverror,'password denied: '||rval[2])
  1313.         return .autherror
  1314.     end
  1315.     otherwise return .noerror
  1316. end
  1317.  
  1318. ::method logoff
  1319. signal on HALT
  1320. rval=self~send('QUIT')
  1321. select
  1322.     when rval='HALT' then return 'HALT'
  1323.     when rval=-1 then return .sockerror
  1324.     otherwise nop
  1325. end
  1326. rval=self~receive(.blocksize)
  1327. select
  1328.     when rval='HALT' then return 'HALT'    
  1329.     when rval[1]=-1 then return .sockerror
  1330.     when rval[2]\='226' then return .serverror
  1331.     otherwise return .noerror
  1332. end
  1333.  
  1334. ::method transconf
  1335. signal on HALT
  1336. rval=self~receive(.blocksize)
  1337. select
  1338.     when rval='HALT' then return 'HALT'
  1339.     otherwise nop
  1340. end
  1341.  
  1342. ::method setbinary
  1343. signal on HALT
  1344. rval=self~send('TYPE I')
  1345. select
  1346.     when rval='HALT' then return 'HALT'
  1347.     when rval=-1 then do
  1348.         if .logging then .log~failed(.sockerror,'sending binary mode request')
  1349.         return .sockerror
  1350.     end
  1351.     otherwise nop
  1352. end
  1353. rval=self~receive(.blocksize)
  1354. select
  1355.     when rval='HALT' then return 'HALT'
  1356.     when rval[1]=-1 then do
  1357.         if .logging then .log~failed(.sockerror,'awaiting binary mode confirmation')
  1358.         return .sockerror
  1359.     end
  1360.     when rval[2]\='200' then do
  1361.         if .logging then .log~failed(.serverror,'could not set binary transfer mode: '||rval[2])
  1362.         return .serverror
  1363.     end
  1364.     otherwise return .noerror
  1365. end
  1366.  
  1367. ::method chdir
  1368. use arg dirpath
  1369. signal on HALT
  1370. rval=self~send('CWD '||dirpath)
  1371. select
  1372.     when rval='HALT' then return 'HALT'
  1373.     when rval=-1 then do
  1374.         if .logging then .log~failed(.sockerror,'sending directory change')
  1375.         return .sockerror
  1376.     end
  1377.     otherwise nop
  1378. end
  1379. rval=self~receive(.blocksize)
  1380. select
  1381.     when rval='HALT' then return 'HALT'
  1382.     when rval[1]=-1 then do
  1383.         if .logging then .log~failed(.sockerror,'awaiting directory change confirmation')
  1384.         return .sockerror
  1385.     end
  1386.     when (rval[2]='550' | rval[2]='450') then do
  1387.         if .logging then .log~failed(.serverror,'bad path or no access: '||rval[2])
  1388.         return .patherror
  1389.     end
  1390.     when rval[2]='250' then return .noerror
  1391.     otherwise do
  1392.         return .othererror
  1393.     end        
  1394. end
  1395.  
  1396. ::method setpassive
  1397. expose passivemode dsock
  1398. signal on HALT
  1399. rval=self~send('PASV')
  1400. select
  1401.     when rval='HALT' then return 'HALT'
  1402.     when rval=-1 then do
  1403.         if .logging then .log~failed(.sockerror,'sending passive mode request')
  1404.         return .sockerror
  1405.     end
  1406.     otherwise nop
  1407. end
  1408. rval=self~receive(.blocksize)
  1409. select
  1410.     when rval='HALT' then return 'HALT'
  1411.     when rval[1]=-1 then do
  1412.         if .logging then .log~failed(.sockerror,'awaiting passive mode confirmation')
  1413.         return .sockerror
  1414.     end
  1415.     when rval[2]='425' then do
  1416.         .local['PASSDENYCODE']=rval[2]
  1417.         return .autherror
  1418.     end
  1419.     when rval[2]='227' then do
  1420.         recd=rval[3]
  1421.         parse var recd . '(' hostport ')' .
  1422.         parse var hostport h1 ',' h2 ',' h3 ',' h4 ',' p1 ',' p2
  1423.         host=h1||'.'||h2||'.'||h3||'.'||h4
  1424.         port=(p1~d2c||p2~d2c)~c2d
  1425.         servport=host||':'||port
  1426.         dsock=.socket~new
  1427.         check=dsock~connect(servport)                                                                                             
  1428.         if check='HALT' then return HALT                                    
  1429.         if check=-1 then do
  1430.             if .logging then .log~failed(.sockerror,'could not establish data connection')
  1431.             return .sockerror
  1432.         end
  1433.         passivemode=.true
  1434.         return .noerror
  1435.     end
  1436.     otherwise return .othererror
  1437. end
  1438.  
  1439. ::method setrestart
  1440. expose resume marker 
  1441. signal on HALT
  1442. use arg ofile
  1443. if ofile='' then return .othererror
  1444. if fsize='' | fsize=0 then return .othererror
  1445. if ofile~query('exists')\='' then do
  1446.     marker=ofile~query('size')
  1447.     if fsize<=marker then return .falseimplement
  1448. end
  1449. rval=self~send('REST '||marker)
  1450. select
  1451.     when rval='HALT' then return 'HALT'
  1452.     when rval=-1 then do
  1453.         if .logging then .log~failed(.sockerror,'sending restart marker')
  1454.         return .sockerror
  1455.     end
  1456.     otherwise nop
  1457. end
  1458. rval=self~receive(.blocksize)
  1459. select
  1460.     when rval='HALT' then return 'HALT'
  1461.     when rval[1]=-1 then do
  1462.         if .logging then .log~failed(.sockerror,'receiving restart confirmation')
  1463.         return .sockerror
  1464.     end
  1465.     when rval[2]='350' then return .noerror
  1466.     otherwise return .falseimplement
  1467. end
  1468.  
  1469. ::method restart
  1470. expose marker
  1471. signal on HALT
  1472. return marker
  1473.  
  1474. ::method getsize
  1475. expose fsize file
  1476. use arg file
  1477. signal on HALT
  1478. rval=self~send('SIZE '||file)
  1479. select
  1480.     when rval='HALT' then return 'HALT'
  1481.     when rval=-1 then do
  1482.         if .logging then .log~failed(.sockerror,'requesting file size')
  1483.         return .sockerror
  1484.     end
  1485.     otherwise nop
  1486. end
  1487. rval=self~receive(.blocksize)
  1488. select
  1489.     when rval='HALT' then return 'HALT'
  1490.     when rval[1]=-1 then do
  1491.         if .logging then .log~failed(.sockerror,'receiving file size info')
  1492.         return .sockerror
  1493.     end
  1494.     when rval[2]='213' then do
  1495.         parse value rval[3] with '213 ' fsize (.crlf)
  1496.         fsize=fsize~strip
  1497.         if fsize~datatype('W')=0 then fsize=0
  1498.     end
  1499.     otherwise nop
  1500. end
  1501. if fsize\=0 then return .noerror        
  1502. rval=self~send('STAT '||file)
  1503. select
  1504.     when rval='HALT' then return 'HALT'
  1505.     when rval=-1 then do
  1506.         if .logging then .log~failed(.sockerror,'requesting file status info')
  1507.         return .sockerror
  1508.     end        
  1509.     otherwise nop
  1510. end
  1511. rval=self~receive(.blocksize)
  1512. select
  1513.     when rval='HALT' then return 'HALT'
  1514.     when rval[1]=-1 then do
  1515.         if .logging then .log~failed(.sockerror,'receiving file status info')
  1516.         return .sockerror
  1517.     end
  1518.     when rval[2]='213' then do
  1519.         recd=rval[3]
  1520.         do until recd~pos(.crlf)=0
  1521.             parse var recd begin (.crlf) recd
  1522.             if begin~translate~pos(file~translate)>0 then do
  1523.                 if begin~left(4)=sval||'-' then nop
  1524.                 else do
  1525.                     fsize=begin~word(5)
  1526.                     if fsize~datatype('W')=0 then fsize=begin~word(4)
  1527.                     recd=''
  1528.                 end
  1529.             end
  1530.         end
  1531.         if fsize~datatype('W')=0 then fsize=0
  1532.     end
  1533.     otherwise nop
  1534. end
  1535. if fsize\=0 then return .noerror
  1536. else return .falseimplement
  1537.         
  1538. ::method sendport
  1539. expose lsock
  1540. signal on HALT
  1541. lsock=.socket~new
  1542. lsock~bind
  1543. lsock~listen
  1544. haddr=SockGetHostId()
  1545. parse var haddr p1 '.' p2 '.' p3 '.' p4
  1546. haddr=p1||','||p2||','||p3||','||p4
  1547. lport=lsock~sock
  1548. lport=lport~d2x~right(4,'0')
  1549. lport=lport~left(2)~x2d||','||lport~right(2)~x2d
  1550. pstring=haddr||','||lport
  1551. rval=self~send('PORT '||pstring)
  1552. select
  1553.     when rval='HALT' then return 'HALT'
  1554.     when rval=-1 then do
  1555.         if .logging then .log~failed(.sockerror,'sending data port')
  1556.         return .sockerror
  1557.     end
  1558.     otherwise nop
  1559. end
  1560. rval=self~receive(.blocksize)
  1561. select
  1562.     when rval='HALT' then return 'HALT'
  1563.     when rval[1]=-1 then do
  1564.         if .logging then .log~failed(.sockerror,'receiving data port confirmation')
  1565.         return .sockerror
  1566.     end
  1567.     when rval[2]='200' then return .noerror
  1568.     otherwise do
  1569.         if .logging then .log~failed(.serverror,'data port not accepted: '||rval[2])
  1570.         return .serverror
  1571.     end
  1572. end
  1573.  
  1574. ::method retrieve
  1575. expose file dsock lsock passivemode
  1576. signal on HALT
  1577. rval=self~send('RETR '||file)
  1578. select
  1579.     when rval='HALT' then return 'HALT'
  1580.     when rval=-1 then do
  1581.         if .logging then .log~failed(.sockerror,'sending retrieve request')
  1582.         return .sockerror
  1583.     end
  1584.     otherwise nop
  1585. end
  1586. rval=self~receive(.blocksize)
  1587. select
  1588.     when rval='HALT' then return 'HALT'
  1589.     when rval[1]=-1 then do
  1590.         if .logging then .log~failed(.sockerror,'receiving retrieve request confirmation')
  1591.         return .sockerror
  1592.     end
  1593.     when (rval[2]='125' | rval[2]='150') then do
  1594.         if \passivemode then dsock=.socket~new(lsock~accept)
  1595.         ret=.queue~new
  1596.         ret~queue(.noerror)
  1597.         ret~queue(dsock)
  1598.         ret=ret~makearray
  1599.         return ret
  1600.     end
  1601.     otherwise do
  1602.         if .logging then .log~failed(.serverror,'retrieve request denied: '||rval[2])
  1603.         return .serverror
  1604.     end
  1605. end
  1606.  
  1607. ::method retrievelisting
  1608. expose dsock lsock passivemode
  1609. signal on HALT
  1610. rval=self~send('LIST')
  1611. select
  1612.     when rval='HALT' then return 'HALT'
  1613.     when rval=-1 then do
  1614.         if .logging then .log~failed(.sockerror,'sending retrieve request')
  1615.         return .sockerror
  1616.     end
  1617.     otherwise nop
  1618. end
  1619. rval=self~receive(.blocksize)
  1620. select
  1621.     when rval='HALT' then return 'HALT'
  1622.     when rval[1]=-1 then do
  1623.         if .logging then .log~failed(.sockerror,'receiving retrieve request confirmation')
  1624.         return .sockerror
  1625.     end
  1626.     when (rval[2]='125' | rval[2]='150') then do
  1627.         if \passivemode then dsock=.socket~new(lsock~accept)
  1628.         ret=.queue~new
  1629.         ret~queue(.noerror)
  1630.         ret~queue(dsock)
  1631.         ret=ret~makearray
  1632.         return ret
  1633.     end
  1634.     otherwise do
  1635.         if .logging then .log~failed(.serverror,'retrieve request denied: '||rval[2])
  1636.         return .serverror
  1637.     end
  1638. end
  1639.  
  1640. ::method send
  1641. expose csock
  1642. signal on HALT
  1643. use arg sendstring
  1644. if sendstring='' then return .othererror
  1645. call SysSleep .commanddelay
  1646. check=csock~send(sendstring||.crlf)
  1647. return check
  1648.  
  1649. ::method receive
  1650. expose csock
  1651. signal on HALT
  1652. call SysSleep .commanddelay
  1653. check=csock~receive(.blocksize)
  1654. ret=.queue~new
  1655. if check='HALT' then return 'HALT'
  1656. if check[2]=-1 then do
  1657.     ret~queue(check[2])
  1658.     ret=ret~makearray
  1659.     return ret
  1660. end
  1661. sval=check[1]~left(3)
  1662. ret~queue(check[2])
  1663. ret~queue(sval)
  1664. recd=check[1]
  1665. if check[1]~left(4)=sval||'-' then do until done=1
  1666.     bend=sval||' '
  1667.     parse var recd (bend) stuff
  1668.     if stuff\='' & stuff~right(2)=.crlf then leave        
  1669.     scrap=check[1]
  1670.     call SysSleep .commanddelay
  1671.     check=csock~receive(.blocksize)
  1672.     if check='HALT' then return HALT
  1673.     if check[2]=-1 then return .sockerror
  1674.     recd=recd||check[1]
  1675.     scrap=scrap||check[1]
  1676.     do until scrap~pos(.crlf)=0
  1677.         parse var scrap begin (.crlf) scrap
  1678.         if begin~left(4)=sval||' ' then pdone=1
  1679.         else pdone=0
  1680.     end
  1681.     if scrap='' & pdone=1 then done=1
  1682. end
  1683. ret~queue(recd)
  1684. ret=ret~makearray
  1685. return ret
  1686.  
  1687. HALT:
  1688. return 'HALT'
  1689. /*                                                                      */
  1690. /************************************************************************/
  1691.  
  1692. /* Log object ***********************************************************/
  1693. /*                                                                      */
  1694. ::class logfile
  1695.  
  1696. ::method init
  1697. expose lf filename
  1698. use arg filename
  1699. if symbol('filename')\='VAR' then filename='urlget.log'
  1700. lf=.stream~new(filename)
  1701. return
  1702.  
  1703. ::method transfer
  1704. use arg size,time,restart
  1705. wstring='['||date()||' - '||time()||'] - Successful transfer for:'||.crlf
  1706. wstring=wstring||.currurl||.crlf
  1707. wstring=wstring||'Received '||codel(size)||' bytes in '||codel(time~format(,2))||' seconds'
  1708. wstring=wstring||' ('||codel(size%time)||' bytes/second).'||.crlf
  1709. if symbol('restart')='VAR' then do
  1710.     wstring=wstring||'Total size is '||codel(size+restart)||' bytes, resumed '
  1711.     wstring=wstring||'from byte '||codel(restart)||'.'||.crlf
  1712. end
  1713. wstring=wstring||'-'~copies(75)||.crlf
  1714. self~write(wstring)
  1715. return
  1716.  
  1717. ::method failed
  1718. use arg reason,detail
  1719. wstring='['||date()||' - '||time()||'] - Transfer failed for:'||.crlf
  1720. wstring=wstring||.currurl||.crlf
  1721. wstring=wstring||'Failure cause: '
  1722. select
  1723.     when reason=.sockerror then wstring=wstring||'socket error'
  1724.     when reason=.serverror then wstring=wstring||'server error'
  1725.     when reason=.argerror then wstring=wstring||'argument error'
  1726.     when reason=.userabort then wstring=wstring||'transfer aborted'
  1727.     when reason=.option then wstring=wstring||'option prevents transfer'
  1728.     otherwise wstring=wstring||'unknown error'
  1729. end
  1730. wstring=wstring||' ('||detail||').'||.crlf
  1731. wstring=wstring||'-'~copies(75)||.crlf
  1732. self~write(wstring)
  1733. return
  1734.  
  1735. ::method write
  1736. expose lf
  1737. use arg wstring
  1738. reply
  1739. written=.false
  1740. do until written
  1741.     if lf~open('write')\='READY:' then iterate
  1742.     else do
  1743.         lf~charout(wstring)
  1744.         lf~close
  1745.         written=.true
  1746.     end
  1747. end
  1748. return
  1749. /*                                                                      */
  1750. /************************************************************************/
  1751.  
  1752. /* Screen status object *************************************************/
  1753. /*                                                                      */
  1754. ::class scrstatus
  1755.  
  1756. ::method init
  1757. expose row srow col
  1758. return
  1759.  
  1760. ::method template
  1761. expose row srow col
  1762. parse value SysCurPos() with row col
  1763. srow=row+2
  1764. call SysCurPos row,0
  1765. .con~~charout('Bytes stored: ')~flush
  1766. call SysCurPos row,32
  1767. .con~~charout('Bytes/second: ')~~charout(.crlf||.crlf)~flush
  1768. call SysCurPos srow,0
  1769. .con~~charout('Total size: ')~flush
  1770. call SysCurPos srow,36
  1771. .con~~charout('Progress: ')~flush
  1772. return
  1773.  
  1774. ::method sizeunknown
  1775. expose row srow col
  1776. call SysCurPos srow,12
  1777. .con~~charout('UNKNOWN')~flush
  1778. call SysCurPos srow,46
  1779. .con~~charout('UNKNOWN')~flush
  1780. return
  1781.  
  1782. ::method size
  1783. expose row srow col
  1784. use arg size
  1785. call SysCurPos srow,12
  1786. .con~~charout(codel(size)||' bytes')~flush
  1787. call SysCurPos srow,49
  1788. .con~~charout('%')~flush
  1789. return
  1790.  
  1791. ::method begin
  1792. expose srow
  1793. reply
  1794. lastt=0.01
  1795. currt=0.01
  1796. tdiff=0.01
  1797. lastb=-1
  1798. currb=0
  1799. ctotal=0
  1800. pval=0
  1801. lastp=-1
  1802. stdone=.false
  1803. do until stdone
  1804.     do while .sq~items>0
  1805.         ctotal=.sq~pull
  1806.         if ctotal=.xfs then do
  1807.             stdone=.true
  1808.             ctotal=lntotal
  1809.         end
  1810.         else lntotal=ctotal           
  1811.     end
  1812.     if time('e')<0.1 then iterate
  1813.     if ctotal>lastb then self~stored(ctotal+.restart)
  1814.     currt=time('e')
  1815.     if currt=0 then currt=0.01
  1816.     currb=ctotal-lastb
  1817.     tdiff=currt-lastt
  1818.     if tdiff=0 then tdiff=0.01
  1819.     crate=currb%(tdiff)
  1820.     self~rate(crate)
  1821.     lastt=currt
  1822.     lastb=ctotal
  1823.     if .size>0 then do
  1824.         pval=(ctotal/.size*100)~format(,0)
  1825.         if pval>lastp then self~percent(pval)
  1826.         lastp=pval
  1827.     end
  1828.     call SysSleep (.stinterval/100)
  1829. end
  1830. call SysCurPos srow,0
  1831. .tq~queue(.stfs)
  1832. return
  1833.     
  1834. ::method stored
  1835. expose row srow col
  1836. use arg bstored
  1837. call SysCurPos row,14
  1838. .con~~charout(codel(bstored)~left(12))~flush
  1839. return
  1840.  
  1841. ::method rate
  1842. expose row srow col
  1843. use arg rval
  1844. call SysCurPos row,46
  1845. .con~~charout(codel(rval)~left(12))~flush
  1846. return
  1847.  
  1848. ::method percent
  1849. expose row srow col
  1850. use arg pval
  1851. call SysCurPos srow,46
  1852. .con~~charout(pval~right(3))~flush
  1853. return
  1854. /*                                                                      */
  1855. /************************************************************************/
  1856.  
  1857. /* Comma deliminator routine ********************************************/
  1858. /*                                                                      */
  1859. ::routine codel
  1860. use arg RNum
  1861. if RNum~length<3 then return RNum
  1862. if RNum~pos('.')>0 then do
  1863.     parse var RNum RNum '.' Fract
  1864.     Fract='.'||Fract
  1865. end
  1866. else Fract=''
  1867. RNum=RNum~reverse
  1868. FNum=''
  1869. do while RNum~length>3
  1870.     parse var RNum TriDig +3 RNum
  1871.     FNum=FNum||TriDig||','
  1872. end
  1873. FNum=FNum||RNum
  1874. return FNum~reverse||Fract
  1875. /*                                                                      */
  1876. /************************************************************************/
  1877.  
  1878. /* Display usage information ********************************************/
  1879. /*                                                                      */
  1880. ::routine usage
  1881. call qasay ''
  1882. call qasay 'URL file fetcher v'||.rxiversion
  1883. call qasay ''
  1884. call qasay 'Usage:'
  1885. call qasay ''
  1886. call qasay 'urlget.cmd [options] <URL[ ; local name] | @filename> [options]'
  1887. call qasay ''
  1888. call qasay 'URL - URL of file to get'
  1889. call qasay ''
  1890. call qasay 'local name - local name to store file as'
  1891. call qasay ''
  1892. call qasay "@filename - text file with multiple URL's, one on a line"
  1893. call qasay ''
  1894. call qasay 'Options:'
  1895. call qasay ''
  1896. call qasay '/q - operate in quiet mode'
  1897. call qasay '/qq - operate in super quiet mode (no output at all)'
  1898. call qasay '/r - attempt FTP resume if file exists and is smaller than server copy'
  1899. call qasay '/f - attempt FTP resume, and abort transfer if not supported'
  1900. call qasay "/n - don't overwrite local files (no comparison with server copy)"
  1901. call qasay '/c - clobber local files even if same size as server copy (default is to abort)'
  1902. call qasay '/p - attempt transfer even if passive mode denied while behind firewall'
  1903. call qasay '/w - act as if behind firewall (use passive mode)'
  1904. call qasay '/l - enable logging, regardless of configuration setting'
  1905. if \win95 then call qasay '/d# - delay time for FTP commands in seconds (0.0 - 5.0); default=0.1'
  1906. else call qasay '/d# - delay time for FTP commands in seconds (0 - 5); default=1' 
  1907. call qasay '/b# - transfer block size (512-65535); default=10240'
  1908. if \win95 then call qasay '/t# - status timing interval in 1/100th seconds (1-100); default=50'
  1909. else call qasay '/t# - status timing interval in seconds (1-5); default=1' 
  1910. call qasay '/m# - maximum retries on error (0-9999999999); default=1000'
  1911. call qasay ''
  1912. call qasay 'e.g. urlget http://www.somewhere.com/index.html'
  1913. return
  1914. /*                                                                      */
  1915. /************************************************************************/
  1916.  
  1917. /* Enhanced stream object ***********************************************/
  1918. /*                                                                      */
  1919. ::class estream subclass stream
  1920.  
  1921. ::method charout
  1922. use arg string,start
  1923. if .superquiet then return 0
  1924. else do
  1925.     if symbol('START')='VAR' then return self~charout:super(string,start)
  1926.     else return self~charout:super(string)
  1927. end
  1928.  
  1929. ::method lineout
  1930. use arg string,line
  1931. if .superquiet then return 0
  1932. else do
  1933.     if symbol('LINE')='VAR' then return self~lineout:super(string,line)
  1934.     else return self~lineout:super(string)
  1935. end
  1936. /*                                                                      */
  1937. /************************************************************************/
  1938.  
  1939. /* Quiet aware say ******************************************************/
  1940. /*                                                                      */
  1941. ::routine qasay
  1942. use arg string
  1943. if .superquiet then return
  1944. else say string
  1945. /*                                                                      */
  1946. /************************************************************************/
  1947.