github.com/jgarto/itcv@v0.0.0-20180826224514-4eea09c1aa0d/_vendor/src/golang.org/x/tools/godoc/dl/dl.go (about) 1 // Copyright 2015 The Go Authors. All rights reserved. 2 // Use of this source code is governed by the Apache 2.0 3 // license that can be found in the LICENSE file. 4 5 // +build appengine 6 7 // Package dl implements a simple downloads frontend server. 8 // 9 // It accepts HTTP POST requests to create a new download metadata entity, and 10 // lists entities with sorting and filtering. 11 // It is designed to run only on the instance of godoc that serves golang.org. 12 package dl 13 14 import ( 15 "crypto/hmac" 16 "crypto/md5" 17 "encoding/json" 18 "fmt" 19 "html/template" 20 "io" 21 "net/http" 22 "regexp" 23 "sort" 24 "strconv" 25 "strings" 26 "sync" 27 "time" 28 29 "golang.org/x/net/context" 30 31 "google.golang.org/appengine" 32 "google.golang.org/appengine/datastore" 33 "google.golang.org/appengine/log" 34 "google.golang.org/appengine/memcache" 35 ) 36 37 const ( 38 downloadBaseURL = "https://dl.google.com/go/" 39 cacheKey = "download_list_3" // increment if listTemplateData changes 40 cacheDuration = time.Hour 41 ) 42 43 func RegisterHandlers(mux *http.ServeMux) { 44 mux.Handle("/dl", http.RedirectHandler("/dl/", http.StatusFound)) 45 mux.HandleFunc("/dl/", getHandler) // also serves listHandler 46 mux.HandleFunc("/dl/upload", uploadHandler) 47 mux.HandleFunc("/dl/init", initHandler) 48 } 49 50 type File struct { 51 Filename string `json:"filename"` 52 OS string `json:"os"` 53 Arch string `json:"arch"` 54 Version string `json:"-"` 55 Checksum string `json:"-" datastore:",noindex"` // SHA1; deprecated 56 ChecksumSHA256 string `json:"sha256" datastore:",noindex"` 57 Size int64 `json:"size" datastore:",noindex"` 58 Kind string `json:"kind"` // "archive", "installer", "source" 59 Uploaded time.Time `json:"-"` 60 } 61 62 func (f File) ChecksumType() string { 63 if f.ChecksumSHA256 != "" { 64 return "SHA256" 65 } 66 return "SHA1" 67 } 68 69 func (f File) PrettyChecksum() string { 70 if f.ChecksumSHA256 != "" { 71 return f.ChecksumSHA256 72 } 73 return f.Checksum 74 } 75 76 func (f File) PrettyOS() string { 77 if f.OS == "darwin" { 78 switch { 79 case strings.Contains(f.Filename, "osx10.8"): 80 return "OS X 10.8+" 81 case strings.Contains(f.Filename, "osx10.6"): 82 return "OS X 10.6+" 83 } 84 } 85 return pretty(f.OS) 86 } 87 88 func (f File) PrettySize() string { 89 const mb = 1 << 20 90 if f.Size == 0 { 91 return "" 92 } 93 if f.Size < mb { 94 // All Go releases are >1mb, but handle this case anyway. 95 return fmt.Sprintf("%v bytes", f.Size) 96 } 97 return fmt.Sprintf("%.0fMB", float64(f.Size)/mb) 98 } 99 100 var primaryPorts = map[string]bool{ 101 "darwin/amd64": true, 102 "linux/386": true, 103 "linux/amd64": true, 104 "linux/armv6l": true, 105 "windows/386": true, 106 "windows/amd64": true, 107 } 108 109 func (f File) PrimaryPort() bool { 110 if f.Kind == "source" { 111 return true 112 } 113 return primaryPorts[f.OS+"/"+f.Arch] 114 } 115 116 func (f File) Highlight() bool { 117 switch { 118 case f.Kind == "source": 119 return true 120 case f.Arch == "amd64" && f.OS == "linux": 121 return true 122 case f.Arch == "amd64" && f.Kind == "installer": 123 switch f.OS { 124 case "windows": 125 return true 126 case "darwin": 127 if !strings.Contains(f.Filename, "osx10.6") { 128 return true 129 } 130 } 131 } 132 return false 133 } 134 135 func (f File) URL() string { 136 return downloadBaseURL + f.Filename 137 } 138 139 type Release struct { 140 Version string `json:"version"` 141 Stable bool `json:"stable"` 142 Files []File `json:"files"` 143 Visible bool `json:"-"` // show files on page load 144 SplitPortTable bool `json:"-"` // whether files should be split by primary/other ports. 145 } 146 147 type Feature struct { 148 // The File field will be filled in by the first stable File 149 // whose name matches the given fileRE. 150 File 151 fileRE *regexp.Regexp 152 153 Platform string // "Microsoft Windows", "Apple macOS", "Linux" 154 Requirements string // "Windows XP and above, 64-bit Intel Processor" 155 } 156 157 // featuredFiles lists the platforms and files to be featured 158 // at the top of the downloads page. 159 var featuredFiles = []Feature{ 160 { 161 Platform: "Microsoft Windows", 162 Requirements: "Windows XP SP3 or later, Intel 64-bit processor", 163 fileRE: regexp.MustCompile(`\.windows-amd64\.msi$`), 164 }, 165 { 166 Platform: "Apple macOS", 167 Requirements: "macOS 10.8 or later, Intel 64-bit processor", 168 fileRE: regexp.MustCompile(`\.darwin-amd64(-osx10\.8)?\.pkg$`), 169 }, 170 { 171 Platform: "Linux", 172 Requirements: "Linux 2.6.23 or later, Intel 64-bit processor", 173 fileRE: regexp.MustCompile(`\.linux-amd64\.tar\.gz$`), 174 }, 175 { 176 Platform: "Source", 177 fileRE: regexp.MustCompile(`\.src\.tar\.gz$`), 178 }, 179 } 180 181 // data to send to the template; increment cacheKey if you change this. 182 type listTemplateData struct { 183 Featured []Feature 184 Stable, Unstable, Archive []Release 185 } 186 187 var ( 188 listTemplate = template.Must(template.New("").Funcs(templateFuncs).Parse(templateHTML)) 189 templateFuncs = template.FuncMap{"pretty": pretty} 190 ) 191 192 func listHandler(w http.ResponseWriter, r *http.Request) { 193 if r.Method != "GET" { 194 http.Error(w, "method not allowed", http.StatusMethodNotAllowed) 195 return 196 } 197 var ( 198 c = appengine.NewContext(r) 199 d listTemplateData 200 ) 201 if _, err := memcache.Gob.Get(c, cacheKey, &d); err != nil { 202 if err == memcache.ErrCacheMiss { 203 log.Debugf(c, "cache miss") 204 } else { 205 log.Errorf(c, "cache get error: %v", err) 206 } 207 208 var fs []File 209 _, err := datastore.NewQuery("File").Ancestor(rootKey(c)).GetAll(c, &fs) 210 if err != nil { 211 log.Errorf(c, "error listing: %v", err) 212 return 213 } 214 d.Stable, d.Unstable, d.Archive = filesToReleases(fs) 215 if len(d.Stable) > 0 { 216 d.Featured = filesToFeatured(d.Stable[0].Files) 217 } 218 219 item := &memcache.Item{Key: cacheKey, Object: &d, Expiration: cacheDuration} 220 if err := memcache.Gob.Set(c, item); err != nil { 221 log.Errorf(c, "cache set error: %v", err) 222 } 223 } 224 225 if r.URL.Query().Get("mode") == "json" { 226 w.Header().Set("Content-Type", "application/json") 227 enc := json.NewEncoder(w) 228 enc.SetIndent("", " ") 229 if err := enc.Encode(d.Stable); err != nil { 230 log.Errorf(c, "failed rendering JSON for releases: %v", err) 231 } 232 return 233 } 234 235 if err := listTemplate.ExecuteTemplate(w, "root", d); err != nil { 236 log.Errorf(c, "error executing template: %v", err) 237 } 238 } 239 240 func filesToFeatured(fs []File) (featured []Feature) { 241 for _, feature := range featuredFiles { 242 for _, file := range fs { 243 if feature.fileRE.MatchString(file.Filename) { 244 feature.File = file 245 featured = append(featured, feature) 246 break 247 } 248 } 249 } 250 return 251 } 252 253 func filesToReleases(fs []File) (stable, unstable, archive []Release) { 254 sort.Sort(fileOrder(fs)) 255 256 var r *Release 257 var stableMaj, stableMin int 258 add := func() { 259 if r == nil { 260 return 261 } 262 if !r.Stable { 263 if len(unstable) != 0 { 264 // Only show one (latest) unstable version. 265 return 266 } 267 maj, min, _ := parseVersion(r.Version) 268 if maj < stableMaj || maj == stableMaj && min <= stableMin { 269 // Display unstable version only if newer than the 270 // latest stable release. 271 return 272 } 273 unstable = append(unstable, *r) 274 } 275 276 // Reports whether the release is the most recent minor version of the 277 // two most recent major versions. 278 shouldAddStable := func() bool { 279 if len(stable) >= 2 { 280 // Show up to two stable versions. 281 return false 282 } 283 if len(stable) == 0 { 284 // Most recent stable version. 285 stableMaj, stableMin, _ = parseVersion(r.Version) 286 return true 287 } 288 if maj, _, _ := parseVersion(r.Version); maj == stableMaj { 289 // Older minor version of most recent major version. 290 return false 291 } 292 // Second most recent stable version. 293 return true 294 } 295 if !shouldAddStable() { 296 archive = append(archive, *r) 297 return 298 } 299 300 // Split the file list into primary/other ports for the stable releases. 301 // NOTE(cbro): This is only done for stable releases because maintaining the historical 302 // nature of primary/other ports for older versions is infeasible. 303 // If freebsd is considered primary some time in the future, we'd not want to 304 // mark all of the older freebsd binaries as "primary". 305 // It might be better if we set that as a flag when uploading. 306 r.SplitPortTable = true 307 r.Visible = true // Toggle open all stable releases. 308 stable = append(stable, *r) 309 } 310 for _, f := range fs { 311 if r == nil || f.Version != r.Version { 312 add() 313 r = &Release{ 314 Version: f.Version, 315 Stable: isStable(f.Version), 316 } 317 } 318 r.Files = append(r.Files, f) 319 } 320 add() 321 return 322 } 323 324 // isStable reports whether the version string v is a stable version. 325 func isStable(v string) bool { 326 return !strings.Contains(v, "beta") && !strings.Contains(v, "rc") 327 } 328 329 type fileOrder []File 330 331 func (s fileOrder) Len() int { return len(s) } 332 func (s fileOrder) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 333 func (s fileOrder) Less(i, j int) bool { 334 a, b := s[i], s[j] 335 if av, bv := a.Version, b.Version; av != bv { 336 return versionLess(av, bv) 337 } 338 if a.OS != b.OS { 339 return a.OS < b.OS 340 } 341 if a.Arch != b.Arch { 342 return a.Arch < b.Arch 343 } 344 if a.Kind != b.Kind { 345 return a.Kind < b.Kind 346 } 347 return a.Filename < b.Filename 348 } 349 350 func versionLess(a, b string) bool { 351 // Put stable releases first. 352 if isStable(a) != isStable(b) { 353 return isStable(a) 354 } 355 maja, mina, ta := parseVersion(a) 356 majb, minb, tb := parseVersion(b) 357 if maja == majb { 358 if mina == minb { 359 return ta >= tb 360 } 361 return mina >= minb 362 } 363 return maja >= majb 364 } 365 366 func parseVersion(v string) (maj, min int, tail string) { 367 if i := strings.Index(v, "beta"); i > 0 { 368 tail = v[i:] 369 v = v[:i] 370 } 371 if i := strings.Index(v, "rc"); i > 0 { 372 tail = v[i:] 373 v = v[:i] 374 } 375 p := strings.Split(strings.TrimPrefix(v, "go1."), ".") 376 maj, _ = strconv.Atoi(p[0]) 377 if len(p) < 2 { 378 return 379 } 380 min, _ = strconv.Atoi(p[1]) 381 return 382 } 383 384 func uploadHandler(w http.ResponseWriter, r *http.Request) { 385 if r.Method != "POST" { 386 http.Error(w, "method not allowed", http.StatusMethodNotAllowed) 387 return 388 } 389 c := appengine.NewContext(r) 390 391 // Authenticate using a user token (same as gomote). 392 user := r.FormValue("user") 393 if !validUser(user) { 394 http.Error(w, "bad user", http.StatusForbidden) 395 return 396 } 397 if r.FormValue("key") != userKey(c, user) { 398 http.Error(w, "bad key", http.StatusForbidden) 399 return 400 } 401 402 var f File 403 defer r.Body.Close() 404 if err := json.NewDecoder(r.Body).Decode(&f); err != nil { 405 log.Errorf(c, "error decoding upload JSON: %v", err) 406 http.Error(w, "Something broke", http.StatusInternalServerError) 407 return 408 } 409 if f.Filename == "" { 410 http.Error(w, "Must provide Filename", http.StatusBadRequest) 411 return 412 } 413 if f.Uploaded.IsZero() { 414 f.Uploaded = time.Now() 415 } 416 k := datastore.NewKey(c, "File", f.Filename, 0, rootKey(c)) 417 if _, err := datastore.Put(c, k, &f); err != nil { 418 log.Errorf(c, "putting File entity: %v", err) 419 http.Error(w, "could not put File entity", http.StatusInternalServerError) 420 return 421 } 422 if err := memcache.Delete(c, cacheKey); err != nil { 423 log.Errorf(c, "cache delete error: %v", err) 424 } 425 io.WriteString(w, "OK") 426 } 427 428 func getHandler(w http.ResponseWriter, r *http.Request) { 429 name := strings.TrimPrefix(r.URL.Path, "/dl/") 430 if name == "" { 431 listHandler(w, r) 432 return 433 } 434 if !fileRe.MatchString(name) { 435 http.NotFound(w, r) 436 return 437 } 438 http.Redirect(w, r, downloadBaseURL+name, http.StatusFound) 439 } 440 441 func validUser(user string) bool { 442 switch user { 443 case "adg", "bradfitz", "cbro", "andybons": 444 return true 445 } 446 return false 447 } 448 449 func userKey(c context.Context, user string) string { 450 h := hmac.New(md5.New, []byte(secret(c))) 451 h.Write([]byte("user-" + user)) 452 return fmt.Sprintf("%x", h.Sum(nil)) 453 } 454 455 var fileRe = regexp.MustCompile(`^go[0-9a-z.]+\.[0-9a-z.-]+\.(tar\.gz|pkg|msi|zip)$`) 456 457 func initHandler(w http.ResponseWriter, r *http.Request) { 458 var fileRoot struct { 459 Root string 460 } 461 c := appengine.NewContext(r) 462 k := rootKey(c) 463 err := datastore.RunInTransaction(c, func(c context.Context) error { 464 err := datastore.Get(c, k, &fileRoot) 465 if err != nil && err != datastore.ErrNoSuchEntity { 466 return err 467 } 468 _, err = datastore.Put(c, k, &fileRoot) 469 return err 470 }, nil) 471 if err != nil { 472 http.Error(w, err.Error(), 500) 473 return 474 } 475 io.WriteString(w, "OK") 476 } 477 478 // rootKey is the ancestor of all File entities. 479 func rootKey(c context.Context) *datastore.Key { 480 return datastore.NewKey(c, "FileRoot", "root", 0, nil) 481 } 482 483 // pretty returns a human-readable version of the given OS, Arch, or Kind. 484 func pretty(s string) string { 485 t, ok := prettyStrings[s] 486 if !ok { 487 return s 488 } 489 return t 490 } 491 492 var prettyStrings = map[string]string{ 493 "darwin": "macOS", 494 "freebsd": "FreeBSD", 495 "linux": "Linux", 496 "windows": "Windows", 497 498 "386": "x86", 499 "amd64": "x86-64", 500 "armv6l": "ARMv6", 501 "arm64": "ARMv8", 502 503 "archive": "Archive", 504 "installer": "Installer", 505 "source": "Source", 506 } 507 508 // Code below copied from x/build/app/key 509 510 var theKey struct { 511 sync.RWMutex 512 builderKey 513 } 514 515 type builderKey struct { 516 Secret string 517 } 518 519 func (k *builderKey) Key(c context.Context) *datastore.Key { 520 return datastore.NewKey(c, "BuilderKey", "root", 0, nil) 521 } 522 523 func secret(c context.Context) string { 524 // check with rlock 525 theKey.RLock() 526 k := theKey.Secret 527 theKey.RUnlock() 528 if k != "" { 529 return k 530 } 531 532 // prepare to fill; check with lock and keep lock 533 theKey.Lock() 534 defer theKey.Unlock() 535 if theKey.Secret != "" { 536 return theKey.Secret 537 } 538 539 // fill 540 if err := datastore.Get(c, theKey.Key(c), &theKey.builderKey); err != nil { 541 if err == datastore.ErrNoSuchEntity { 542 // If the key is not stored in datastore, write it. 543 // This only happens at the beginning of a new deployment. 544 // The code is left here for SDK use and in case a fresh 545 // deployment is ever needed. "gophers rule" is not the 546 // real key. 547 if !appengine.IsDevAppServer() { 548 panic("lost key from datastore") 549 } 550 theKey.Secret = "gophers rule" 551 datastore.Put(c, theKey.Key(c), &theKey.builderKey) 552 return theKey.Secret 553 } 554 panic("cannot load builder key: " + err.Error()) 555 } 556 557 return theKey.Secret 558 }