@@ -62,10 +62,25 @@ } return $file } + + proc _isHash {value} { + set value [string tolower $value] + + if {[string length $value] != 40} { + return false + } + + if {![regexp {^[0-9a-f]*$} $value]} { + return false + } + + return true + } + proc _db {args} { return [uplevel 1 [list ::appfs::db {*}$args]] } proc init {} { @@ -79,11 +94,11 @@ file mkdir $::appfs::cachedir sqlite3 ::appfs::db [file join $::appfs::cachedir cache.db] } - _db eval {CREATE TABLE IF NOT EXISTS sites(hostname PRIMARY KEY, lastUpdate);} + _db eval {CREATE TABLE IF NOT EXISTS sites(hostname PRIMARY KEY, lastUpdate, ttl);} _db eval {CREATE TABLE IF NOT EXISTS packages(hostname, sha1, package, version, os, cpuArch, isLatest, haveManifest);} _db eval {CREATE TABLE IF NOT EXISTS files(package_sha1, type, time, source, size, perms, file_sha1, file_name, file_directory);} } proc download {hostname hash {method sha1}} { @@ -98,18 +113,20 @@ } proc getindex {hostname} { set now [clock seconds] - set lastUpdates [_db eval {SELECT lastUpdate FROM sites WHERE hostname = $hostname LIMIT 1;}] + set lastUpdates [_db eval {SELECT lastUpdate, ttl FROM sites WHERE hostname = $hostname LIMIT 1;}] if {[llength $lastUpdates] == 0} { set lastUpdate 0 + set ttl 0 } else { set lastUpdate [lindex $lastUpdates 0] + set ttl [lindex $lastUpdates 1] } - if {$now < ($lastUpdate + $::appfs::ttl)} { + if {$now < ($lastUpdate + $ttl)} { return COMPLETE } if {[string match "*\[/~\]*" $hostname]} { return -code error "Invalid hostname" @@ -125,21 +142,28 @@ ::http::reset $token $token cleanup } if {![info exists indexhash_data]} { + # Cache this result for 60 seconds + _db eval {INSERT OR REPLACE INTO sites (hostname, lastUpdate, ttl) VALUES ($hostname, $now, 60);} + return -code error "Unable to fetch $url" } set indexhash [lindex [split $indexhash_data ","] 0] + + if {![_isHash $indexhash]} { + return -code error "Invalid hash: $indexhash" + } set file [download $hostname $indexhash] set fd [open $file] set data [read $fd] close $fd - array set packages [list] + set curr_packages [list] foreach line [split $data "\n"] { set line [string trim $line] if {[string match "*/*" $line]} { continue @@ -158,19 +182,15 @@ set pkgInfo(cpuArch) [lindex $work 3] set pkgInfo(hash) [string tolower [lindex $work 4]] set pkgInfo(hash_type) "sha1" set pkgInfo(isLatest) [expr {!![lindex $work 5]}] - if {[string length $pkgInfo(hash)] != 40} { + if {![_isHash $pkgInfo(hash)]} { continue } - if {![regexp {^[0-9a-f]*$} $pkgInfo(hash)]} { - continue - } - - set packages($pkgInfo(package)) [array get pkgInfo] + lappend curr_packages $pkgInfo(hash) # Do not do any additional work if we already have this package set existing_packages [_db eval {SELECT package FROM packages WHERE hostname = $hostname AND sha1 = $pkgInfo(hash);}] if {[lsearch -exact $existing_packages $pkgInfo(package)] != -1} { continue @@ -179,14 +199,27 @@ if {$pkgInfo(isLatest)} { _db eval {UPDATE packages SET isLatest = 0 WHERE hostname = $hostname AND package = $pkgInfo($package) AND os = $pkgInfo($package) AND cpuArch = $pkgInfo(cpuArch);} } _db eval {INSERT INTO packages (hostname, sha1, package, version, os, cpuArch, isLatest, haveManifest) VALUES ($hostname, $pkgInfo(hash), $pkgInfo(package), $pkgInfo(version), $pkgInfo(os), $pkgInfo(cpuArch), $pkgInfo(isLatest), 0);} + } + + # Look for packages that have been deleted + set found_packages [_db eval {SELECT sha1 FROM packages WHERE hostname = $hostname;}] + foreach package $found_packages { + set found_packages_arr($package) 1 + } + + foreach package $curr_packages { + unset -nocomplain found_packages_arr($package) + } + foreach package [array names found_packages_arr] { + _db eval {DELETE FROM packages WHERE hostname = $hostname AND sha1 = $package;} } - _db eval {INSERT OR REPLACE INTO sites (hostname, lastUpdate) VALUES ($hostname, $now);} + _db eval {INSERT OR REPLACE INTO sites (hostname, lastUpdate, ttl) VALUES ($hostname, $now, $::appfs::ttl);} return COMPLETE } proc getpkgmanifest {hostname package_sha1} { @@ -194,10 +227,14 @@ set haveManifest [lindex $haveManifests 0] if {$haveManifest} { return COMPLETE } + + if {![_isHash $package_sha1]} { + return FAIL + } set file [download $hostname $package_sha1] set fd [open $file] set pkgdata [read $fd] close $fd