Fix check for content-type
[tclrobot.git] / robot.tcl
1 #!/usr/bin/tclsh 
2 # $Id: robot.tcl,v 1.34 2002/06/18 19:57:53 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             set al [array names parm]
521             foreach a $al {
522                 set al [string tolower $a]
523                 puts -nonewline $out " $al"
524                 puts -nonewline $out {="}
525                 puts -nonewline $out $parm($a)
526                 puts -nonewline $out {"}
527                 switch -- $al {
528                     "name" {
529                         set metaname [string tolower $parm($a)]
530                     }
531                     "content" {
532                         set metacontent $parm($a)
533                     }
534                 }
535                 unset parm($al)
536             }
537             puts $out "></meta>"
538             # go through robots directives (af any)
539             if {![string compare $metaname robots]} {
540                 set direcs [split [string tolower $metacontent] ,]
541                 if {[lsearch $direcs noindex] >= 0} {
542                     set noindex 1
543                 }
544                 if {[lsearch $direcs nofollow] >= 0} {
545                     set nofollow 1
546                 }
547             }
548         } body {
549             # don't print title of document content if noindex is used
550             if {!$noindex} {
551                 puts $out "<title>$title</title>"
552                 regsub -all {<!--[^-]*-->} $body { } abody
553                 regsub -all -nocase {<script[^<]*</script>} $abody {} bbody
554                 regsub -all {<[^\>]+>} $bbody {} nbody
555                 puts $out "<documentcontent>"
556                 puts $out $nbody
557                 puts $out "</documentcontent>"
558             }
559         } -nonest base {
560             # <base href=.. >
561             if {![info exists parm(href)]} {
562                 continue
563             }
564             set href [string trim $parm(href)]
565             if {![RobotHref $url href host path]} continue
566             set URL($url,bpath) $path
567         } a {
568             # <a href="...."> .. </a> 
569             # we're not using nonest - otherwise body isn't set
570             if {$nofollow} continue
571             if {![info exists parm(href)]} {
572                 continue
573             }
574             link $url $out [string trim $parm(href)] $body $distance
575         } -nonest area {
576             if {$nofollow} continue
577             if {![info exists parm(href)]} {
578                 continue
579             }
580             link $url $out [string trim $parm(href)] $body $distance
581         } -nonest frame {
582             if {![info exists parm(src)]} {
583                 continue
584             }
585             link $url $out [string trim $parm(src)] $body $fdistance
586         }
587 }
588
589 proc RobotsTxt {url} {
590     global agent URL
591
592     RobotsTxt0 URL(URL($url,hostport),robots) $URL($url,buf)
593 }
594
595 proc RobotsTxt0 {v buf} {
596     global URL agent
597     set section 0
598     foreach l [split $buf \n] {
599         if {[regexp {([-A-Za-z]+):[ ]*([^\# ]+)} $l match cmd arg]} {
600             puts "cmd=$cmd arg=$arg"
601             switch -- [string tolower $cmd] {
602                 user-agent {
603                     if {$section} break
604                     set pat [string tolower $arg]*
605                     set section [string match $pat $agent]
606                 }
607                 disallow {
608                     if {$section} {
609                         puts "rule [list 0 $arg]"
610                         lappend $v [list 0 $arg]
611                     }
612                 }
613                 allow {
614                     if {$section} {
615                         puts "rule [list 1 $arg]"
616                         lappend $v [list 1 $arg]
617                     }
618                 }
619             }
620         }
621     }
622 }
623
624 proc RobotTextPlain {url out} {
625     global URL
626
627     puts $out "<documentcontent>"
628     regsub -all {<} $URL($url,buf) {\&lt;} content
629     puts $out $content
630     puts $out "</documentcontent>"
631
632     if {![string compare $URL($url,path) /robots.txt]} {
633         RobotsTxt $url
634     }
635 }
636
637 proc RobotWriteMetadata {url out} {
638     global URL domains
639
640     puts $out "<zmbot>"
641
642     set distance 1000
643     if {[RobotFileExist unvisited $URL($url,hostport) $URL($url,path)]} {
644         set inf [RobotFileOpen unvisited $URL($url,hostport) $URL($url,path) r]
645         RobotReadRecord $inf fromurl distance
646         RobotFileClose $inf
647     }
648     set URL($url,dist) $distance
649     puts $out "<distance>"
650     puts $out "  $distance"
651     puts $out "</distance>"
652     headSave $url $out
653     puts "Parsing $url distance=$distance"
654     switch $URL($url,head,content-type) {
655         text/html {
656             if {[string length $distance]} {
657                 RobotTextHtml $url $out
658             }
659         }
660         text/plain {
661             RobotTextPlain $url $out
662         }
663     }
664     puts $out "</zmbot>"
665 }
666
667 proc Robot200 {url} {
668     global URL domains
669     
670     set out [RobotFileOpen raw $URL($url,hostport) $URL($url,path)]
671     puts -nonewline $out $URL($url,buf)
672     RobotFileClose $out
673
674     set out [RobotFileOpen visited $URL($url,hostport) $URL($url,path)]
675     RobotWriteMetadata $url $out
676     RobotFileClose $out
677
678     RobotFileUnlink unvisited $URL($url,hostport) $URL($url,path)
679 }
680
681 proc RobotReadContent {url sock binary} {
682     global URL
683
684     set buffer [read $sock 16384]
685     set readCount [string length $buffer]
686
687     if {$readCount <= 0} {
688         Robot200 $url
689         RobotRestart $url $sock
690     } elseif {!$binary && [string first \0 $buffer] >= 0} {
691         Robot200 $url
692         RobotRestart $url $sock
693     } else {
694         # puts "Got $readCount bytes"
695         set URL($url,buf) $URL($url,buf)$buffer
696     }
697 }
698
699 proc RobotReadHeader {url sock} {
700     global URL debuglevel
701
702     if {$debuglevel > 1} {
703         puts "HTTP head $url"
704     }
705     if {[catch {set buffer [read $sock 2148]}]} {
706         RobotError $url 404
707         RobotRestart $url $sock
708         return
709     }
710     set readCount [string length $buffer]
711     
712     if {$readCount <= 0} {
713         RobotError $url 404
714         RobotRestart $url $sock
715     } else {
716         # puts "Got $readCount bytes"
717         set URL($url,buf) $URL($url,buf)$buffer
718         
719         set n [string first \r\n\r\n $URL($url,buf)]
720         if {$n > 1} {
721             set code 0
722             set version {}
723             set headbuf [string range $URL($url,buf) 0 $n]
724             incr n 4
725             set URL($url,buf) [string range $URL($url,buf) $n end]
726             
727             regexp {^HTTP/([0-9.]+)[ ]+([0-9]+)} $headbuf x version code
728             set lines [split $headbuf \n]
729             foreach line $lines {
730                 if {[regexp {^([^:]+):[ ]+([^;]*)} $line x name value]} {
731                     set URL($url,head,[string tolower $name]) [string trim $value]
732                 }
733             }
734             puts "HTTP CODE $code"
735             set URL($url,state) skip
736             switch $code {
737                 301 {
738                     RobotRedirect $url $URL($url,head,location) 301
739                     RobotRestart $url $sock
740                 }
741                 302 {
742                     RobotRedirect $url $URL($url,head,location) 302
743                     RobotRestart $url $sock
744                 }
745                 200 {
746                     if {![info exists URL($url,head,content-type)]} {
747                         set URL($url,head,content-type) {}
748                     }
749                     set binary 1
750                     switch -glob -- $URL($url,head,content-type) {
751                         text/* {
752                             set binary 0
753                         }
754                     }
755                     if {![regexp {/robots.txt$} $url]} {
756                         if {![checkrule mime $URL($url,head,content-type)]} {
757                             RobotError $url mimedeny
758                             RobotRestart $url $sock
759                             return
760                         }
761                     }
762                     fileevent $sock readable [list RobotReadContent $url $sock $binary]
763                 }
764                 default {
765                     RobotError $url $code
766                     RobotRestart $url $sock
767                 }
768             }
769         }
770     }
771 }
772
773 proc RobotSockCancel {url sock} {
774
775     puts "RobotSockCancel sock=$sock url=$url"
776     RobotError $url 401
777     RobotRestart $url $sock
778 }
779
780 proc RobotConnect {url sock} {
781     global URL agent acceptLanguage
782
783     fconfigure $sock -translation {lf crlf} -blocking 0
784     fileevent $sock readable [list RobotReadHeader $url $sock]
785     puts $sock "GET $URL($url,path) HTTP/1.0"
786     puts $sock "Host: $URL($url,host)"
787     puts $sock "User-Agent: $agent"
788     if {[string length $acceptLanguage]} {
789         puts $sock "Accept-Language: $acceptLanguage"
790     }
791     puts $sock ""
792     flush $sock
793     set URL($sock,cancel) [after 30000 [list RobotSockCancel $url $sock]]
794 }
795
796 proc RobotNop {} {
797
798 }
799
800 proc RobotGetUrl {url phost} {
801     global URL robotsRunning
802     flush stdout
803     puts "Retrieve $robotsRunning url=$url"
804     if {![regexp {([^:]+)://([^/]+)(.*)} $url x method hostport path]} {
805         return -1
806     }
807     if {![regexp {([^:]+):([0-9]+)} $hostport x host port]} {
808         set port 80
809         set host $hostport
810     }
811     set URL($url,method) $method
812     set URL($url,host) $host
813     set URL($url,hostport) $hostport
814     set URL($url,path) $path
815     set URL($url,state) head
816     set URL($url,buf) {}
817
818     if {[string compare $path /robots.txt]} {
819         set ok 1
820         if {![info exists URL($hostport,robots)]} {
821             puts "READING robots.txt for host $hostport"
822             if {[RobotFileExist visited $hostport /robots.txt]} {
823                 set inf [RobotFileOpen visited $hostport /robots.txt r]
824                 set buf [read $inf 32768]
825                 close $inf
826             } else {
827                 set buf "User-agent: *\nAllow: /\n"
828             }
829             RobotsTxt0 URL($hostport,robots) $buf
830         }
831         if {[info exists URL($hostport,robots)]} {
832             foreach l $URL($hostport,robots) {
833                 if {[string first [lindex $l 1] $path] == 0} {
834                     set ok [lindex $l 0]
835                     break
836                 }
837             }
838         }
839         if {!$ok} {
840             puts "skipped due to robots.txt"
841             return -1
842         }
843     }
844     if [catch {set sock [socket -async $host $port]}] {
845         return -1
846     }
847     RobotConnect $url $sock
848
849     return 0
850 }
851
852 if {![llength [info commands htmlSwitch]]} {
853     set e [info sharedlibextension]
854     if {[catch {load ./tclrobot$e}]} {
855         load tclrobot$e
856     }
857 }
858
859 set agent "zmbot/0.1"
860 if {![catch {set os [exec uname -s -r]}]} {
861     set agent "$agent ($os)"
862 }
863
864 puts "agent: $agent"
865
866 proc bgerror {m} {
867     global errorInfo
868     puts "BGERROR $m"
869     puts $errorInfo
870 }
871
872 set robotsRunning 0
873 set robotSeq 0
874 set workdir [pwd]
875 set idletime 60000
876 set acceptLanguage {}
877 set debuglevel 0
878 set status(unvisited) 0
879 set status(visited) 0
880 set status(bad) 0
881 set status(raw) 0
882
883
884 # Rules: allow, deny, url
885
886 proc checkrule {type this} {
887     global alrules
888     global debuglevel
889
890     if {$debuglevel > 3} {
891         puts "CHECKRULE $type $this"
892     }
893     if {[info exist alrules]} {
894         foreach l $alrules {
895             if {$debuglevel > 3} {
896                 puts "consider $l"
897             }
898             # consider type
899             if {[lindex $l 1] != $type} continue
900             # consider mask (! negates)
901             set masks [lindex $l 2]
902             set ok 0
903             foreach mask $masks {       
904                 if {$debuglevel > 4} {
905                     puts "consider single mask $mask"
906                 }
907                 if {[string index $mask 0] == "!"} {
908                     set mask [string range $mask 1 end]
909                     if {[string match $mask $this]}  continue
910                 } else {
911                     if {![string match $mask $this]} continue
912                 }
913                 set ok 1
914             }
915             if {$debuglevel > 4} {
916                 puts "ok = $ok"
917             }
918             if {!$ok} continue
919             # OK, we have a match
920             if {[lindex $l 0] == "allow"} {
921                 if {$debuglevel > 3} {
922                     puts "CHECKRULE MATCH OK"
923                 }
924                 return 1
925             } else {
926                 if {$debuglevel > 3} {
927                     puts "CHECKFULE MATCH FAIL"
928                 }
929                 return 0
930             }
931         }
932     }
933     if {$debuglevel > 3} {
934         puts "CHECKRULE MATCH OK"
935     }
936     return 1
937 }
938
939
940 proc url {href} {
941     global debuglevel
942
943     if {[RobotHref http://www.indexdata.dk/ href host path]} {
944         if {![RobotFileExist visited $host $path]} {
945             set outf [RobotFileOpen unvisited $host $path]
946             RobotWriteRecord $outf href 0
947             RobotFileClose $outf
948         }
949     }
950 }
951
952 proc deny {type stuff} {
953     global alrules
954
955     lappend alrules [list deny $type $stuff]
956 }
957
958 proc allow {type stuff} {
959     global alrules
960
961     lappend alrules [list allow $type $stuff]
962 }
963
964 proc debug {level} {
965     global debuglevel
966
967     set debuglevel $level
968 }
969
970 # Parse options
971
972 set i 0
973 set l [llength $argv]
974
975 if {$l < 2} {
976     puts {tclrobot: usage:}
977     puts {tclrobot [-j jobs] [-i idle] [-c count] [-d domain] [-r rules] [url ..]}
978     puts " Example: -c 3 -d '*.dk' http://www.indexdata.dk/"
979
980     exit 1
981 }
982 while  {$i < $l} {
983     set arg [lindex $argv $i]
984     switch -glob -- $arg {
985         -j* {
986             set robotsMax [string range $arg 2 end]
987             if {![string length $robotsMax]} {
988                 set robotsMax [lindex $argv [incr i]]
989             }
990         }
991         -c* {
992             set maxdistance [string range $arg 2 end]
993             if {![string length $maxdistance]} {
994                 set maxdistance [lindex $argv [incr i]]
995             }
996         }
997         -d* {
998             set dom [string range $arg 2 end]
999             if {![string length $dom]} {
1000                 set dom [lindex $argv [incr i]]
1001             }
1002             lappend domains $dom
1003         }
1004         -i* {
1005             set idletime [string range $arg 2 end]
1006             if {![string length $idletime]} {
1007                 set idletime [lindex $argv [incr i]]
1008             }
1009         }
1010         -l* {
1011             set acceptLanguage [string range $arg 2 end]
1012             if {![string length $acceptLanguage]} {
1013                 set acceptLanguage [lindex $argv [incr i]]
1014             }
1015         }
1016         -r* {
1017             set rfile [string range $arg 2 end]
1018             if {![string length $rfile]} {
1019                 set rfile [lindex $argv [incr i]]
1020             }
1021             source $rfile
1022         }
1023         default {
1024             set href $arg
1025             if {[RobotHref http://www.indexdata.dk/ href host path]} {
1026                 if {![RobotFileExist visited $host $path]} {
1027                     set outf [RobotFileOpen unvisited $host $path]
1028                     RobotWriteRecord $outf href 0
1029                     RobotFileClose $outf
1030                 }
1031             }
1032         }
1033     }
1034     incr i
1035 }
1036
1037 if {![info exist domains]} {
1038     set domains {*}
1039 }
1040 if {![info exist maxdistance]} {
1041     set maxdistance 50
1042 }
1043 if {![info exist robotsMax]} {
1044     set robotsMax 5
1045 }
1046
1047 puts "domains=$domains"
1048 puts "max distance=$maxdistance"
1049 puts "max jobs=$robotsMax"
1050
1051
1052 RobotStart
1053
1054
1055 while {$robotsRunning} {
1056     vwait robotsRunning
1057 }
1058
1059 set statusfile [open status w]
1060 puts $statusfile "$status(unvisited) $status(bad) $status(visited)"
1061 close $statusfile
1062