Remove code that skips ?'s in URL
[tclrobot.git] / robot.tcl
1 #!/usr/bin/tclsh 
2 # $Id: robot.tcl,v 1.33 2002/03/25 16:13:21 adam Exp $
3 #
4 proc RobotFileNext1 {area lead} {
5     # puts "RobotFileNext1 area=$area lead=$lead"
6     if {[catch {set ns [glob ${area}/*]}]} {
7         return {}
8     }
9     foreach n $ns {
10         if {[file isfile $n]} {
11             set off [string last / $n]
12             incr off 2
13             return $lead/[string range $n $off end]
14         }
15     }
16     foreach n $ns {
17         if {[file isdirectory $n]} {
18             set off [string last / $n]
19             incr off 2
20             set sb [RobotFileNext1 $n $lead/[string range $n $off end]]
21             if {[string length $sb]} {
22                 return $sb
23             }
24         }
25     }
26     return {}
27 }
28
29 proc RobotWriteRecord {outf fromurl distance} {
30     puts $outf "<zmbot>"
31     puts $outf "<distance>"
32     puts $outf $distance
33     puts $outf "</distance>"
34     puts $outf "<fromurl>"
35     puts $outf $fromurl
36     puts $outf "</fromurl>"
37     puts $outf "</zmbot>"
38 }
39
40 proc RobotReadRecord {inf fromurlx distancex} {
41     upvar $fromurlx fromurl
42     upvar $distancex distance
43     gets $inf
44     gets $inf
45     set distance [string trim [gets $inf]]
46     # puts "got distance = $distance"
47     gets $inf
48     gets $inf
49     set fromurl [string trim [gets $inf]]
50 }
51
52 proc RobotFileNext {area} {
53     global robotSeq
54     global idletime ns
55     global status
56
57     # puts "RobotFileNext robotSeq=$robotSeq"
58     if {$robotSeq < 0} {
59         return {}
60     }
61     if {$robotSeq == 0} {
62         if {[catch {set ns [glob ${area}/*]}]} {
63             return {}
64         }
65     }
66     set off [string length $area]
67     incr off
68     set n [lindex $ns $robotSeq]
69     if {![string length $n]} {
70         set robotSeq -1
71         flush stdout
72         set statusfile [open status w]
73         puts $statusfile "$status(unvisited) $status(bad) $status(visited)"
74         close $statusfile
75         return wait
76     }
77     incr robotSeq
78     if {[file isfile $n/frobots.txt]} {
79         puts "ok returning http://[string range $n $off end]/robots.txt"
80         return http://[string range $n $off end]/robots.txt
81     } elseif {[file isdirectory $n]} {
82         set sb [RobotFileNext1 $n http://[string range $n $off end]]
83         if {[string length $sb]} {
84             return $sb
85         }
86     }
87     puts "no more work at end of RobotFileNext n=$n"
88     puts "ns=$ns"
89     return {}
90 }
91
92
93 proc RobotFileExist {area host path} {
94     global debuglevel
95
96     if {$debuglevel > 3} {
97         puts "RobotFileExist begin area=$area host=$host path=$path"
98     }
99     set lpath [split $path /]
100     set l [llength $lpath]
101     incr l -1
102     set t [lindex $lpath $l]
103     incr l -1
104     set npath $area/$host[join [lrange $lpath 0 $l] /d]/f$t
105     if {$debuglevel > 3} {
106         puts "RobotFileExist end npath=$npath"
107     }
108     return [file exists $npath]
109 }
110
111 proc RobotFileUnlink {area host path} {
112     global status
113     # puts "RobotFileUnlink begin"
114     # puts "area=$area host=$host path=$path"
115     set lpath [split $path /]
116     set l [llength $lpath]
117     incr l -1
118     set t [lindex $lpath $l]
119     incr l -1
120     set npath $area/$host[join [lrange $lpath 0 $l] /d]/f$t
121     # puts "npath=$npath"
122     set comp [split $npath /]
123     if {[catch {exec rm [join $comp /]}]} return
124
125     set l [llength $comp]
126     incr l -1
127     incr l -1
128     incr status($area) -1
129     for {set i $l} {$i > 0} {incr i -1} {
130         set path [join [lrange $comp 0 $i] /]
131         if {![catch {glob $path/*}]} return
132         exec rmdir ./$path
133     }
134     # puts "RobotFileUnlink end"
135 }
136
137 proc RobotFileClose {out} {
138     if [string compare $out stdout] {
139         close $out
140     }
141 }
142
143 proc RobotFileOpen {area host path {mode w}} {
144     set orgPwd [pwd]
145     global workdir
146     global status
147     global debuglevel
148
149     if {![info exists workdir]} {
150         return stdout
151     }
152     if {$debuglevel > 3} {
153         puts "RobotFileOpen orgPwd=$orgPwd area=$area host=$host path=$path mode=$mode"
154     }
155     if {[string compare $orgPwd $workdir]} {
156         puts "ooops. RobotFileOpen failed"
157         puts "workdir = $workdir"
158         puts "pwd = $orgPwd"
159         exit 1
160     }
161     set comp [split $area/$host$path /]
162     set len [llength $comp]
163     incr len -1
164     for {set i 0} {$i < $len} {incr i} {
165         if {$i > 1} {
166             set d "d[lindex $comp $i]" 
167         } else {
168             set d [lindex $comp $i]
169         }
170         if {[catch {cd ./$d}]} {
171             exec mkdir $d
172             cd ./$d
173             if {![string compare $area unvisited] && $i == 1 && $mode == "w"} {
174                 if {[string compare $path /robots.txt]} {
175                     set out [open frobots.txt w]
176                     puts "creating robots.txt in $d"
177                     close $out
178                     incr status(unvisited)
179                 }
180             }
181         }
182     }
183     set d [lindex $comp $len]
184     if {[string length $d]} {
185         set out [open f$d $mode]
186     } else {
187         set out [open f $mode]
188     }
189     if {$mode == "w"} {
190         incr status($area)
191     }
192     cd $orgPwd
193     return $out
194 }
195
196 proc RobotRR {} {
197     global robotSeq robotsRunning
198
199     incr robotsRunning -1
200     while {$robotsRunning} {
201         vwait robotsRunning
202     }
203     set robotSeq 0
204     RobotStart
205 }
206
207 proc RobotRestart {url sock} {
208     global URL robotsRunning
209
210     close $sock
211     after cancel $URL($sock,cancel) 
212
213     foreach v [array names URL $url,*] {
214         unset URL($v)
215     }
216
217     incr robotsRunning -1
218     RobotStart
219 }
220
221 proc RobotStart {} {
222     global URL
223     global robotsRunning robotsMax idletime
224   
225     # puts "RobotStart"
226     while {1} {
227         set url [RobotFileNext unvisited]
228         if {![string length $url]} {
229             return
230         }
231         incr robotsRunning
232         if {[string compare $url wait] == 0} {
233             after $idletime RobotRR
234             return
235         }
236         set r [RobotGetUrl $url {}]
237         if {!$r} {
238             if {$robotsRunning >= $robotsMax} return
239         } else {
240             incr robotsRunning -1
241             if {![RobotFileExist bad $URL($url,hostport) $URL($url,path)]} {
242                 set outf [RobotFileOpen bad $URL($url,hostport) $URL($url,path)]
243                 RobotFileClose $outf
244             }
245             RobotFileUnlink unvisited $URL($url,hostport) $URL($url,path)
246         }
247     }
248 }
249
250 proc headSave {url out} {
251     global URL
252     
253     if {[info exists URL($url,head,last-modified)]} {
254         puts $out "<lastmodified>$URL($url,head,last-modified)</lastmodified>"
255     }
256     puts $out {<si>}
257     if {[info exists URL($url,head,date)]} {
258         puts $out " <date>$URL($url,head,date)</date>"
259     }
260     if {[info exists URL($url,head,content-length)]} {
261         puts $out " <by>$URL($url,head,content-length)</by>"
262     }
263     if {[info exists URL($url,head,server)]} {
264         puts $out " <format>$URL($url,head,server)</format>"
265     }
266     puts $out {</si>}
267     puts $out {<publisher>}
268     puts $out " <identifier>$url</identifier>"
269     if {[info exists URL($url,head,content-type)]} {
270         puts $out " <type>$URL($url,head,content-type)</type>"
271     }
272     puts $out {</publisher>}
273 }
274
275 proc RobotHref {url hrefx hostx pathx} {
276     global URL domains debuglevel
277     upvar $hrefx href
278     upvar $hostx host
279     upvar $pathx path
280
281     if {$debuglevel > 1} {
282         puts "Ref input url = $url href=$href"
283     }
284
285     if {[string first { } $href] >= 0} {
286         return 0
287     }
288     if {[string length $href] > 256} {
289         return 0
290     }
291
292 #   Skip pages that have ? in them
293 #    if {[string first {?} $url] >= 0 && [string first {?} $href] >= 0} {
294 #       return 0
295 #    }
296     # get method (if any)
297     if {![regexp {^([^/:]+):(.*)} $href x method hpath]} {
298         set hpath $href
299         set method http
300     } else {
301         if {[string compare $method http]} {
302             return 0
303         }
304     }
305     # get host (if any)
306     if {[regexp {^//([^/]+)([^\#]*)} $hpath x host surl]} {
307         if {![string length $surl]} {
308             set surl /
309         }
310         if {[info exist domains]} {
311             set ok 0
312             foreach domain $domains {
313                 if {[string match $domain $host]} {
314                     set ok 1
315                     break
316                  }
317             }
318             if {!$ok} {
319                 return 0
320             }
321         }
322     } else {
323         regexp {^([^\#]*)} $hpath x surl
324         set host $URL($url,hostport)
325     }
326     if {![string length $surl]} {
327         return 0
328     }
329     if {[string first / $surl]} {
330         # relative path
331         set curpath $URL($url,path)
332         if {[info exists URL($url,bpath)]} {
333             set curpath $URL($url,bpath)
334         }
335         regexp {^([^\#?]*)} $curpath x dpart
336         set l [string last / $dpart]
337         if {[expr $l >= 0]} {
338             set surl [string range $dpart 0 $l]$surl
339         } else {
340             set surl $dpart/$surl
341         }
342     }
343     set surllist [split $surl /]
344     catch {unset path}
345     set pathl 0
346     foreach c $surllist {
347         switch -- $c {
348             .. {
349                 if {$pathl > 1} {
350                     incr pathl -2
351                     set path [lrange $path 0 $pathl]
352                     incr pathl
353                 }
354             }
355             . {
356
357             }
358             default {
359                 incr pathl
360                 lappend path $c
361             }
362         }
363     }
364     if {$debuglevel > 4} {
365         puts "pathl=$pathl output path=$path"
366     }
367     set path [join $path /]
368     if {![string length $path]} {
369         set path /
370     }
371     regsub -all {~} $path {%7E} path
372     set href "$method://$host$path"
373
374     if {$debuglevel > 1} {
375         puts "Ref result = $href"
376     }
377     return [checkrule url $href]
378 }
379
380 proc RobotError {url code} {
381     global URL
382
383     puts "Bad URL $url (code $code)"
384     set fromurl {}
385     set distance -1
386     if {[RobotFileExist unvisited $URL($url,hostport) $URL($url,path)]} {
387         set inf [RobotFileOpen unvisited $URL($url,hostport) $URL($url,path) r]
388         RobotReadRecord $inf fromurl distance
389         RobotFileClose $inf
390     }
391     RobotFileUnlink unvisited $URL($url,hostport) $URL($url,path)
392     if {![RobotFileExist bad $URL($url,hostport) $URL($url,path)]} {
393         set outf [RobotFileOpen bad $URL($url,hostport) $URL($url,path)]
394         RobotWriteRecord $outf $fromurl $distance
395         RobotFileClose $outf
396     }
397 }
398
399 proc RobotRedirect {url tourl code} {
400     global URL
401
402     puts "Redirecting from $url to $tourl"
403
404     set distance {}
405     set fromurl {}
406     if {[RobotFileExist unvisited $URL($url,hostport) $URL($url,path)]} {
407         set inf [RobotFileOpen unvisited $URL($url,hostport) $URL($url,path) r]
408         RobotReadRecord $inf fromurl distance
409         RobotFileClose $inf
410     }
411     if {![RobotFileExist bad $URL($url,hostport) $URL($url,path)]} {
412         set outf [RobotFileOpen bad $URL($url,hostport) $URL($url,path)]
413         RobotWriteRecord $outf $fromurl $distance
414         RobotFileClose $outf
415     }
416     if {[RobotHref $url tourl host path]} {
417         if {![RobotFileExist visited $host $path]} {
418             if {![RobotFileExist unvisited $host $path]} {
419                 set outf [RobotFileOpen unvisited $host $path]
420                 RobotWriteRecord $outf $fromurl $distance
421                 RobotFileClose $outf
422             }
423         } else {
424             set olddistance {}
425             set inf [RobotFileOpen visited $host $path r]
426             RobotReadRecord $inf oldurl olddistance
427             RobotFileClose $inf
428             if {[string length $olddistance] == 0} {
429                 set olddistance 1000
430             }
431             if {[string length $distance] == 0} {
432                 set distance 1000
433             }
434             puts "distance=$distance olddistance=$olddistance"
435             if {[expr $distance < $olddistance]} {
436                 set outf [RobotFileOpen unvisited $host $path]
437                 RobotWriteRecord $outf $tourl $distance
438                 RobotFileClose $outf
439             }
440         }
441     }
442     if {[catch {RobotFileUnlink unvisited $URL($url,hostport) $URL($url,path)}]} {
443         puts "unlink failed"
444         exit 1
445     }
446 }
447
448 proc link {url out href body distance} {
449     global URL maxdistance
450     if {[expr $distance > $maxdistance]} return
451     
452     if {![RobotHref $url href host path]} return
453     
454     puts $out "<cr>"
455     puts $out "<identifier>$href</identifier>"
456     puts $out "<description>$body</description>"
457     puts $out "</cr>"
458     
459     if {![RobotFileExist visited $host $path]} {
460         set olddistance 1000
461         if {![RobotFileExist bad $host $path]} {
462             if {[RobotFileExist unvisited $host $path]} {
463                 set inf [RobotFileOpen unvisited $host $path r]
464                 RobotReadRecord $inf oldurl olddistance
465                 RobotFileClose $inf
466             }
467         } else {
468             set olddistance 0
469         }
470         if {[string length $olddistance] == 0} {
471             set olddistance 1000
472         }
473         if {[expr $distance < $olddistance]} {
474             set outf [RobotFileOpen unvisited $host $path]
475             RobotWriteRecord $outf $url $distance
476             RobotFileClose $outf
477         }
478     } elseif {[string compare $href $url]} {
479         set inf [RobotFileOpen visited $host $path r]
480         RobotReadRecord $inf xurl olddistance
481         close $inf
482         if {[string length $olddistance] == 0} {
483             set olddistance 1000
484         }
485         if {[expr $distance < $olddistance]} {
486             puts "OK remarking url=$url href=$href"
487             puts "olddistance = $olddistance"
488             puts "newdistance = $distance"
489             set outf [RobotFileOpen unvisited $host $path]
490             RobotWriteRecord $outf $url $distance
491             RobotFileClose $outf
492         }
493     }
494 }
495
496 proc RobotTextHtml {url out} {
497     global URL maxdistance
498
499     # set title so we can emit it for the body
500     set title {}
501     # if true, nothing will be indexed
502     set noindex 0
503     # if true, nothing will be followed
504     set nofollow 0
505
506     set distance 0
507     set fdistance 0
508     if {$maxdistance < 1000 && [info exists URL($url,dist)]} {
509         set fdistance $URL($url,dist)
510         set distance [expr $fdistance + 1]
511     }
512     htmlSwitch $URL($url,buf) \
513         title {
514             set title $body
515         } -nonest meta {
516             # collect metadata and save NAME= CONTENT=..
517             set metaname {}
518             set metacontent {}
519             puts -nonewline $out "<meta"
520             foreach a [array names parm] {
521                 set al [string tolower $a]
522                 puts -nonewline $out " $al"
523                 puts -nonewline $out {="}
524                 puts -nonewline $out $parm($a)
525                 puts -nonewline $out {"}
526                 switch -- $al {
527                     "name" {
528                         set metaname [string tolower $parm($a)]
529                     }
530                     "content" {
531                         set metacontent $parm($a)
532                     }
533                 }
534             }
535             puts $out "></meta>"
536             # go through robots directives (af any)
537             if {![string compare $metaname robots]} {
538                 set direcs [split [string tolower $metacontent] ,]
539                 if {[lsearch $direcs noindex] >= 0} {
540                     set noindex 1
541                 }
542                 if {[lsearch $direcs nofollow] >= 0} {
543                     set nofollow 1
544                 }
545             }
546         } body {
547             # don't print title of document content if noindex is used
548             if {!$noindex} {
549                 puts $out "<title>$title</title>"
550                 regsub -all {<!--[^-]*-->} $body { } abody
551                 regsub -all -nocase {<script[^<]*</script>} $abody {} bbody
552                 regsub -all {<[^\>]+>} $bbody {} nbody
553                 puts $out "<documentcontent>"
554                 puts $out $nbody
555                 puts $out "</documentcontent>"
556             }
557         } -nonest base {
558             # <base href=.. >
559             if {![info exists parm(href)]} {
560                 continue
561             }
562             set href [string trim $parm(href)]
563             if {![RobotHref $url href host path]} continue
564             set URL($url,bpath) $path
565         } a {
566             # <a href="...."> .. </a> 
567             # we're not using nonest - otherwise body isn't set
568             if {$nofollow} continue
569             if {![info exists parm(href)]} {
570                 continue
571             }
572             link $url $out [string trim $parm(href)] $body $distance
573         } -nonest area {
574             if {$nofollow} continue
575             if {![info exists parm(href)]} {
576                 continue
577             }
578             link $url $out [string trim $parm(href)] $body $distance
579         } -nonest frame {
580             if {![info exists parm(src)]} {
581                 continue
582             }
583             link $url $out [string trim $parm(src)] $body $fdistance
584         }
585 }
586
587 proc RobotsTxt {url} {
588     global agent URL
589
590     RobotsTxt0 URL(URL($url,hostport),robots) $URL($url,buf)
591 }
592
593 proc RobotsTxt0 {v buf} {
594     global URL agent
595     set section 0
596     foreach l [split $buf \n] {
597         if {[regexp {([-A-Za-z]+):[ ]*([^\# ]+)} $l match cmd arg]} {
598             puts "cmd=$cmd arg=$arg"
599             switch -- [string tolower $cmd] {
600                 user-agent {
601                     if {$section} break
602                     set pat [string tolower $arg]*
603                     set section [string match $pat $agent]
604                 }
605                 disallow {
606                     if {$section} {
607                         puts "rule [list 0 $arg]"
608                         lappend $v [list 0 $arg]
609                     }
610                 }
611                 allow {
612                     if {$section} {
613                         puts "rule [list 1 $arg]"
614                         lappend $v [list 1 $arg]
615                     }
616                 }
617             }
618         }
619     }
620 }
621
622 proc RobotTextPlain {url out} {
623     global URL
624
625     puts $out "<documentcontent>"
626     regsub -all {<} $URL($url,buf) {\&lt;} content
627     puts $out $content
628     puts $out "</documentcontent>"
629
630     if {![string compare $URL($url,path) /robots.txt]} {
631         RobotsTxt $url
632     }
633 }
634
635 proc RobotWriteMetadata {url out} {
636     global URL domains
637
638     puts $out "<zmbot>"
639
640     set distance 1000
641     if {[RobotFileExist unvisited $URL($url,hostport) $URL($url,path)]} {
642         set inf [RobotFileOpen unvisited $URL($url,hostport) $URL($url,path) r]
643         RobotReadRecord $inf fromurl distance
644         RobotFileClose $inf
645     }
646     set URL($url,dist) $distance
647     puts $out "<distance>"
648     puts $out "  $distance"
649     puts $out "</distance>"
650     headSave $url $out
651     puts "Parsing $url distance=$distance"
652     switch $URL($url,head,content-type) {
653         text/html {
654             if {[string length $distance]} {
655                 RobotTextHtml $url $out
656             }
657         }
658         text/plain {
659             RobotTextPlain $url $out
660         }
661     }
662     puts $out "</zmbot>"
663 }
664
665 proc Robot200 {url} {
666     global URL domains
667     
668     set out [RobotFileOpen raw $URL($url,hostport) $URL($url,path)]
669     puts -nonewline $out $URL($url,buf)
670     RobotFileClose $out
671
672     set out [RobotFileOpen visited $URL($url,hostport) $URL($url,path)]
673     RobotWriteMetadata $url $out
674     RobotFileClose $out
675
676     RobotFileUnlink unvisited $URL($url,hostport) $URL($url,path)
677 }
678
679 proc RobotReadContent {url sock binary} {
680     global URL
681
682     set buffer [read $sock 16384]
683     set readCount [string length $buffer]
684
685     if {$readCount <= 0} {
686         Robot200 $url
687         RobotRestart $url $sock
688     } elseif {!$binary && [string first \0 $buffer] >= 0} {
689         Robot200 $url
690         RobotRestart $url $sock
691     } else {
692         # puts "Got $readCount bytes"
693         set URL($url,buf) $URL($url,buf)$buffer
694     }
695 }
696
697 proc RobotReadHeader {url sock} {
698     global URL debuglevel
699
700     if {$debuglevel > 1} {
701         puts "HTTP head $url"
702     }
703     if {[catch {set buffer [read $sock 2148]}]} {
704         RobotError $url 404
705         RobotRestart $url $sock
706         return
707     }
708     set readCount [string length $buffer]
709     
710     if {$readCount <= 0} {
711         RobotError $url 404
712         RobotRestart $url $sock
713     } else {
714         # puts "Got $readCount bytes"
715         set URL($url,buf) $URL($url,buf)$buffer
716         
717         set n [string first \r\n\r\n $URL($url,buf)]
718         if {$n > 1} {
719             set code 0
720             set version {}
721             set headbuf [string range $URL($url,buf) 0 $n]
722             incr n 4
723             set URL($url,buf) [string range $URL($url,buf) $n end]
724             
725             regexp {^HTTP/([0-9.]+)[ ]+([0-9]+)} $headbuf x version code
726             set lines [split $headbuf \n]
727             foreach line $lines {
728                 if {[regexp {^([^:]+):[ ]+([^;]*)} $line x name value]} {
729                     set URL($url,head,[string tolower $name]) [string trim $value]
730                 }
731             }
732             puts "HTTP CODE $code"
733             set URL($url,state) skip
734             switch $code {
735                 301 {
736                     RobotRedirect $url $URL($url,head,location) 301
737                     RobotRestart $url $sock
738                 }
739                 302 {
740                     RobotRedirect $url $URL($url,head,location) 302
741                     RobotRestart $url $sock
742                 }
743                 200 {
744                     if {![info exists URL($url,head,content-type)]} {
745                         set URL($url,head,content-type) {}
746                     }
747                     set binary 1
748                     switch -glob -- $URL($url,head,content-type) {
749                         text/* {
750                             set binary 0
751                         }
752                     }
753                     if {![regexp {/robots.txt$} $url]} {
754                         if {![checkrule mime $URL($url,head,content-type)]} {
755                             RobotError $url mimedeny
756                             RobotRestart $url $sock
757                             return
758                         }
759                     }
760                     fileevent $sock readable [list RobotReadContent $url $sock $binary]
761                 }
762                 default {
763                     RobotError $url $code
764                     RobotRestart $url $sock
765                 }
766             }
767         }
768     }
769 }
770
771 proc RobotSockCancel {url sock} {
772
773     puts "RobotSockCancel sock=$sock url=$url"
774     RobotError $url 401
775     RobotRestart $url $sock
776 }
777
778 proc RobotConnect {url sock} {
779     global URL agent acceptLanguage
780
781     fconfigure $sock -translation {lf crlf} -blocking 0
782     fileevent $sock readable [list RobotReadHeader $url $sock]
783     puts $sock "GET $URL($url,path) HTTP/1.0"
784     puts $sock "Host: $URL($url,host)"
785     puts $sock "User-Agent: $agent"
786     if {[string length $acceptLanguage]} {
787         puts $sock "Accept-Language: $acceptLanguage"
788     }
789     puts $sock ""
790     flush $sock
791     set URL($sock,cancel) [after 30000 [list RobotSockCancel $url $sock]]
792 }
793
794 proc RobotNop {} {
795
796 }
797
798 proc RobotGetUrl {url phost} {
799     global URL robotsRunning
800     flush stdout
801     puts "Retrieve $robotsRunning url=$url"
802     if {![regexp {([^:]+)://([^/]+)(.*)} $url x method hostport path]} {
803         return -1
804     }
805     if {![regexp {([^:]+):([0-9]+)} $hostport x host port]} {
806         set port 80
807         set host $hostport
808     }
809     set URL($url,method) $method
810     set URL($url,host) $host
811     set URL($url,hostport) $hostport
812     set URL($url,path) $path
813     set URL($url,state) head
814     set URL($url,buf) {}
815
816     if {[string compare $path /robots.txt]} {
817         set ok 1
818         if {![info exists URL($hostport,robots)]} {
819             puts "READING robots.txt for host $hostport"
820             if {[RobotFileExist visited $hostport /robots.txt]} {
821                 set inf [RobotFileOpen visited $hostport /robots.txt r]
822                 set buf [read $inf 32768]
823                 close $inf
824             } else {
825                 set buf "User-agent: *\nAllow: /\n"
826             }
827             RobotsTxt0 URL($hostport,robots) $buf
828         }
829         if {[info exists URL($hostport,robots)]} {
830             foreach l $URL($hostport,robots) {
831                 if {[string first [lindex $l 1] $path] == 0} {
832                     set ok [lindex $l 0]
833                     break
834                 }
835             }
836         }
837         if {!$ok} {
838             puts "skipped due to robots.txt"
839             return -1
840         }
841     }
842     if [catch {set sock [socket -async $host $port]}] {
843         return -1
844     }
845     RobotConnect $url $sock
846
847     return 0
848 }
849
850 if {![llength [info commands htmlSwitch]]} {
851     set e [info sharedlibextension]
852     if {[catch {load ./tclrobot$e}]} {
853         load tclrobot$e
854     }
855 }
856
857 set agent "zmbot/0.1"
858 if {![catch {set os [exec uname -s -r]}]} {
859     set agent "$agent ($os)"
860 }
861
862 puts "agent: $agent"
863
864 proc bgerror {m} {
865     global errorInfo
866     puts "BGERROR $m"
867     puts $errorInfo
868 }
869
870 set robotsRunning 0
871 set robotSeq 0
872 set workdir [pwd]
873 set idletime 60000
874 set acceptLanguage {}
875 set debuglevel 0
876 set status(unvisited) 0
877 set status(visited) 0
878 set status(bad) 0
879 set status(raw) 0
880
881
882 # Rules: allow, deny, url
883
884 proc checkrule {type this} {
885     global alrules
886     global debuglevel
887
888     if {$debuglevel > 3} {
889         puts "CHECKRULE $type $this"
890     }
891     if {[info exist alrules]} {
892         foreach l $alrules {
893             if {$debuglevel > 3} {
894                 puts "consider $l"
895             }
896             # consider type
897             if {[lindex $l 1] != $type} continue
898             # consider mask (! negates)
899             set masks [lindex $l 2]
900             set ok 0
901             foreach mask $masks {       
902                 if {$debuglevel > 4} {
903                     puts "consider single mask $mask"
904                 }
905                 if {[string index $mask 0] == "!"} {
906                     set mask [string range $mask 1 end]
907                     if {[string match $mask $this]}  continue
908                 } else {
909                     if {![string match $mask $this]} continue
910                 }
911                 set ok 1
912             }
913             if {$debuglevel > 4} {
914                 puts "ok = $ok"
915             }
916             if {!$ok} continue
917             # OK, we have a match
918             if {[lindex $l 0] == "allow"} {
919                 if {$debuglevel > 3} {
920                     puts "CHECKRULE MATCH OK"
921                 }
922                 return 1
923             } else {
924                 if {$debuglevel > 3} {
925                     puts "CHECKFULE MATCH FAIL"
926                 }
927                 return 0
928             }
929         }
930     }
931     if {$debuglevel > 3} {
932         puts "CHECKRULE MATCH OK"
933     }
934     return 1
935 }
936
937
938 proc url {href} {
939     global debuglevel
940
941     if {[RobotHref http://www.indexdata.dk/ href host path]} {
942         if {![RobotFileExist visited $host $path]} {
943             set outf [RobotFileOpen unvisited $host $path]
944             RobotWriteRecord $outf href 0
945             RobotFileClose $outf
946         }
947     }
948 }
949
950 proc deny {type stuff} {
951     global alrules
952
953     lappend alrules [list deny $type $stuff]
954 }
955
956 proc allow {type stuff} {
957     global alrules
958
959     lappend alrules [list allow $type $stuff]
960 }
961
962 proc debug {level} {
963     global debuglevel
964
965     set debuglevel $level
966 }
967
968 # Parse options
969
970 set i 0
971 set l [llength $argv]
972
973 if {$l < 2} {
974     puts {tclrobot: usage:}
975     puts {tclrobot [-j jobs] [-i idle] [-c count] [-d domain] [-r rules] [url ..]}
976     puts " Example: -c 3 -d '*.dk' http://www.indexdata.dk/"
977
978     exit 1
979 }
980 while  {$i < $l} {
981     set arg [lindex $argv $i]
982     switch -glob -- $arg {
983         -j* {
984             set robotsMax [string range $arg 2 end]
985             if {![string length $robotsMax]} {
986                 set robotsMax [lindex $argv [incr i]]
987             }
988         }
989         -c* {
990             set maxdistance [string range $arg 2 end]
991             if {![string length $maxdistance]} {
992                 set maxdistance [lindex $argv [incr i]]
993             }
994         }
995         -d* {
996             set dom [string range $arg 2 end]
997             if {![string length $dom]} {
998                 set dom [lindex $argv [incr i]]
999             }
1000             lappend domains $dom
1001         }
1002         -i* {
1003             set idletime [string range $arg 2 end]
1004             if {![string length $idletime]} {
1005                 set idletime [lindex $argv [incr i]]
1006             }
1007         }
1008         -l* {
1009             set acceptLanguage [string range $arg 2 end]
1010             if {![string length $acceptLanguage]} {
1011                 set acceptLanguage [lindex $argv [incr i]]
1012             }
1013         }
1014         -r* {
1015             set rfile [string range $arg 2 end]
1016             if {![string length $rfile]} {
1017                 set rfile [lindex $argv [incr i]]
1018             }
1019             source $rfile
1020         }
1021         default {
1022             set href $arg
1023             if {[RobotHref http://www.indexdata.dk/ href host path]} {
1024                 if {![RobotFileExist visited $host $path]} {
1025                     set outf [RobotFileOpen unvisited $host $path]
1026                     RobotWriteRecord $outf href 0
1027                     RobotFileClose $outf
1028                 }
1029             }
1030         }
1031     }
1032     incr i
1033 }
1034
1035 if {![info exist domains]} {
1036     set domains {*}
1037 }
1038 if {![info exist maxdistance]} {
1039     set maxdistance 50
1040 }
1041 if {![info exist robotsMax]} {
1042     set robotsMax 5
1043 }
1044
1045 puts "domains=$domains"
1046 puts "max distance=$maxdistance"
1047 puts "max jobs=$robotsMax"
1048
1049
1050 RobotStart
1051
1052
1053 while {$robotsRunning} {
1054     vwait robotsRunning
1055 }
1056
1057 set statusfile [open status w]
1058 puts $statusfile "$status(unvisited) $status(bad) $status(visited)"
1059 close $statusfile
1060