@@ -4,14 +4,19 @@ import (
44 "context"
55 "fmt"
66 "io/ioutil"
7+ "log"
78 "net/http"
89 "net/url"
910 "path"
11+ "regexp"
12+ "sort"
1013 "strconv"
1114 "strings"
1215 "time"
1316
17+ "github.com/distr1/distri"
1418 "github.com/protocolbuffers/txtpbfmt/ast"
19+ "golang.org/x/mod/semver"
1520)
1621
1722func checkDebian (debianPackagesURL , source string ) (remoteSource , remoteHash , remoteVersion string , _ error ) {
@@ -71,6 +76,80 @@ func checkDebian(debianPackagesURL, source string) (remoteSource, remoteHash, re
7176 return "" , "" , "" , fmt .Errorf ("package not found in Debian Packages file" )
7277}
7378
79+ func maybeV (v string ) string {
80+ if strings .HasPrefix (v , "v" ) {
81+ return v
82+ }
83+ return "v" + v
84+ }
85+
86+ func checkHeuristic (upstreamVersion , source string ) (remoteSource , remoteHash , remoteVersion string , _ error ) {
87+ u , err := url .Parse (source )
88+ u .Path = path .Dir (u .Path )
89+ resp , err := http .Get (u .String ())
90+ if err != nil {
91+ return "" , "" , "" , err
92+ }
93+ if resp .StatusCode != http .StatusOK {
94+ return "" , "" , "" , fmt .Errorf ("unexpected HTTP response: got %v, want 200 OK" , resp .Status )
95+ }
96+ b , err := ioutil .ReadAll (resp .Body )
97+ if err != nil {
98+ return "" , "" , "" , err
99+ }
100+ // TODO: add special casing for parsing apache directory index?
101+ log .Printf ("base: %v" , path .Base (source ))
102+ pattern := strings .Replace (path .Base (source ), upstreamVersion , `([^"]*)` , 1 )
103+ if pattern == path .Base (source ) {
104+ return "" , "" , "" , fmt .Errorf ("could not derive regexp pattern, specify manually" )
105+ }
106+ log .Printf ("pattern: %v" , pattern )
107+ re , err := regexp .Compile (pattern )
108+ if err != nil {
109+ return "" , "" , "" , err
110+ }
111+ log .Printf ("re: %v" , re )
112+ matches := re .FindAllStringSubmatch (string (b ), - 1 )
113+ log .Printf ("matches: %v" , matches )
114+ versions := func () []string {
115+ v := make (map [string ]bool )
116+ for _ , m := range matches {
117+ if m [1 ] == "latest" {
118+ continue // skip common latest symlink
119+ }
120+ v [m [1 ]] = true
121+ }
122+ result := make ([]string , 0 , len (v ))
123+ for version := range v {
124+ result = append (result , version )
125+ }
126+ valid := true
127+ for _ , r := range result {
128+ if ! semver .IsValid (maybeV (r )) {
129+ valid = false
130+ break
131+ }
132+ }
133+ if ! valid {
134+ // Prefer a string sort when the versions aren’t semver, it’s better
135+ // than semver.Compare.
136+ sort .Sort (sort .Reverse (sort .StringSlice (result )))
137+ } else {
138+ sort .Slice (result , func (i , j int ) bool {
139+ v , w := result [i ], result [j ]
140+ v , w = maybeV (v ), maybeV (w )
141+ return semver .Compare (v , w ) >= 0 // reverse
142+ })
143+ }
144+ return result
145+ }()
146+ if len (versions ) == 0 {
147+ return "" , "" , "" , fmt .Errorf ("not yet implemented" )
148+ }
149+ u .Path = path .Join (u .Path , strings .Replace (path .Base (source ), upstreamVersion , versions [0 ], 1 ))
150+ return u .String (), "TODO" , versions [0 ], nil
151+ }
152+
74153func Check (build []* ast.Node ) (source , hash , version string , _ error ) {
75154 stringVal := func (path ... string ) (string , error ) {
76155 nodes := ast .GetFromPath (build , path )
@@ -95,5 +174,13 @@ func Check(build []*ast.Node) (source, hash, version string, _ error) {
95174 }
96175 return checkDebian (u , source )
97176 }
98- return "" , "" , "" , fmt .Errorf ("not yet implemented" )
177+
178+ version , err = stringVal ("version" )
179+ if err != nil {
180+ return "" , "" , "" , err
181+ }
182+
183+ pv := distri .ParseVersion (version )
184+ // fall back: see if we can obtain an index page
185+ return checkHeuristic (pv .Upstream , source )
99186}
0 commit comments