windows-iso-downloader/backend/main.go
Shekhar Vaidya 8b036e94ba feat: route Microsoft API calls through Cloudflare Worker to avoid IP blocks
- Add cloudflare-worker/worker.js: edge proxy forwarding requests to allowed
  Microsoft hosts from Cloudflare distributed IPs. Set CF_WORKER_URL env var
  to activate; omit to keep direct-to-Microsoft behaviour (self-hosters unaffected).
- Wrap all outbound Microsoft URLs in proxyURL() in main.go so the Worker
  sits transparently in front of every session and download-link request.
- Fix Back button navigating to external referrer when user lands directly
  on a product URL (#5) — use window.history.state?.idx to detect whether
  there is prior in-app history before calling navigate(-1).

Closes #5, Closes #6

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 12:01:03 +05:30

471 lines
13 KiB
Go

package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"net/url"
"os"
"regexp"
"strconv"
"sync"
"time"
"github.com/google/uuid"
)
const (
UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
PROFILE = "606624d44113"
LOCALE = "en-US"
ORG_ID = "y6jn8c31"
CUSTOMER_ID = "560dc9f3-1aa5-4a2f-b63c-9e18f8d0e175"
PORT = ":3002" // Different port to run alongside Node
)
type SessionEntry struct {
SessionID string
CreatedAt time.Time
}
var (
sessionCache = make(map[string]SessionEntry)
cacheMutex sync.RWMutex
SESSION_TTL = 15 * time.Minute
workerSecret = os.Getenv("CF_WORKER_SECRET")
)
func setWorkerSecret(req *http.Request) {
if workerSecret != "" {
req.Header.Set("X-Worker-Secret", workerSecret)
}
}
// proxyURL rewrites a Microsoft URL to go through the CF Worker when CF_WORKER_URL is set.
// The Worker receives the original host via ?host= and forwards the request from Cloudflare's edge.
func proxyURL(msURL string) string {
workerBase := os.Getenv("CF_WORKER_URL")
if workerBase == "" {
return msURL
}
parsed, err := url.Parse(msURL)
if err != nil {
return msURL
}
workerParsed, err := url.Parse(workerBase)
if err != nil {
return msURL
}
q := parsed.Query()
q.Set("host", parsed.Hostname())
workerParsed.Path = parsed.Path
workerParsed.RawQuery = q.Encode()
return workerParsed.String()
}
// Map product ID to referer URL
func getReferer(productId string) string {
id, err := strconv.Atoi(productId)
if err != nil {
return "https://www.microsoft.com/en-us/software-download/windows8ISO"
}
if id >= 2935 {
return "https://www.microsoft.com/en-us/software-download/windows11"
}
if id >= 2618 {
return "https://www.microsoft.com/en-us/software-download/windows10ISO"
}
return "https://www.microsoft.com/en-us/software-download/windows8ISO"
}
// Replicate Fido session tracking
func setupSession() (string, error) {
sessionID := uuid.New().String()
client := &http.Client{Timeout: 10 * time.Second}
// Step 1: Register session
q1 := url.Values{}
q1.Set("org_id", ORG_ID)
q1.Set("session_id", sessionID)
req1, _ := http.NewRequest("GET", proxyURL("https://vlscppe.microsoft.com/tags?"+q1.Encode()), nil)
req1.Header.Set("User-Agent", UA)
setWorkerSecret(req1)
client.Do(req1) // Ignore errors intentionally
// Step 2: Fetch tracking JS
q2 := url.Values{}
q2.Set("instanceId", CUSTOMER_ID)
q2.Set("PageId", "si")
q2.Set("session_id", sessionID)
req2, _ := http.NewRequest("GET", proxyURL("https://ov-df.microsoft.com/mdt.js?"+q2.Encode()), nil)
req2.Header.Set("User-Agent", UA)
setWorkerSecret(req2)
resp2, err := client.Do(req2)
if err != nil {
return sessionID, nil // Proceed anyway
}
defer resp2.Body.Close()
bodyBytes, _ := io.ReadAll(resp2.Body)
mdtText := string(bodyBytes)
// Regex to extract w and rticks
reW := regexp.MustCompile(`[&?]w=([^&"'\s]+)`)
reRt := regexp.MustCompile(`rticks[="]+\+?\s*(\d{10,})`)
wMatch := reW.FindStringSubmatch(mdtText)
rtMatch := reRt.FindStringSubmatch(mdtText)
// Step 3: Send fingerprint response
if len(wMatch) > 1 && len(rtMatch) > 1 {
wVal := wMatch[1]
rtVal := rtMatch[1]
mdt := time.Now().UnixMilli()
q3 := url.Values{}
q3.Set("session_id", sessionID)
q3.Set("CustomerId", CUSTOMER_ID)
q3.Set("PageId", "si")
q3.Set("w", wVal)
q3.Set("mdt", fmt.Sprintf("%d", mdt))
q3.Set("rticks", rtVal)
fpURL := proxyURL("https://ov-df.microsoft.com/?" + q3.Encode())
req3, _ := http.NewRequest("GET", fpURL, nil)
req3.Header.Set("User-Agent", UA)
setWorkerSecret(req3)
client.Do(req3)
}
return sessionID, nil
}
// Map DownloadType code to string
func mapDownloadType(typeNum int) string {
switch typeNum {
case 0:
return "x86"
case 1:
return "x64"
case 2:
return "ARM64"
default:
return fmt.Sprintf("type_%d", typeNum)
}
}
// Background cleanup for stale sessions
func cleanupSessions() {
for {
time.Sleep(1 * time.Minute)
cacheMutex.Lock()
now := time.Now()
for key, entry := range sessionCache {
if now.Sub(entry.CreatedAt) > SESSION_TTL {
delete(sessionCache, key)
}
}
cacheMutex.Unlock()
}
}
// Middleware for CORS
func enableCORS(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
origin := r.Header.Get("Origin")
// Allowed origins
if origin == "https://msdl.tech-latest.com" || origin == "http://localhost:5173" || origin == "http://localhost:3000" {
w.Header().Set("Access-Control-Allow-Origin", origin)
}
w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
next.ServeHTTP(w, r)
})
}
func respondJSONError(w http.ResponseWriter, status int, message string) {
log.Printf("ERROR [%d]: %s\n", status, message)
w.WriteHeader(status)
json.NewEncoder(w).Encode(map[string]string{"error": message})
}
// --- /skuinfo endpoint ---
func handleSkuInfo(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
productID := r.URL.Query().Get("product_id")
if productID == "" {
respondJSONError(w, http.StatusBadRequest, "product_id query parameter is required")
return
}
if _, err := strconv.Atoi(productID); err != nil {
respondJSONError(w, http.StatusBadRequest, "product_id must be a numeric value")
return
}
sessionID, _ := setupSession()
cacheMutex.Lock()
sessionCache[productID] = SessionEntry{SessionID: sessionID, CreatedAt: time.Now()}
cacheMutex.Unlock()
skuQ := url.Values{}
skuQ.Set("profile", PROFILE)
skuQ.Set("productEditionId", productID)
skuQ.Set("SKU", "undefined")
skuQ.Set("friendlyFileName", "undefined")
skuQ.Set("Locale", LOCALE)
skuQ.Set("sessionID", sessionID)
reqURL := proxyURL("https://www.microsoft.com/software-download-connector/api/getskuinformationbyproductedition?" + skuQ.Encode())
client := &http.Client{Timeout: 15 * time.Second}
var finalData map[string]interface{}
// Retry logic
for attempt := 0; attempt < 3; attempt++ {
if attempt > 0 {
time.Sleep(2 * time.Second)
}
req, _ := http.NewRequest("GET", reqURL, nil)
req.Header.Set("User-Agent", UA)
req.Header.Set("Referer", getReferer(productID))
req.Header.Set("Accept", "application/json")
setWorkerSecret(req)
resp, err := client.Do(req)
if err != nil {
if attempt == 2 {
respondJSONError(w, http.StatusBadGateway, "Request failed: "+err.Error())
return
}
continue
}
bodyBytes, _ := io.ReadAll(resp.Body)
resp.Body.Close()
if resp.StatusCode != http.StatusOK {
if attempt == 2 {
respondJSONError(w, http.StatusBadGateway, fmt.Sprintf("Microsoft API returned HTTP %d: %s", resp.StatusCode, string(bodyBytes)))
return
}
continue
}
// Double encoded decode logic
if len(bodyBytes) > 0 && bodyBytes[0] == '"' {
var unquoted string
json.Unmarshal(bodyBytes, &unquoted)
bodyBytes = []byte(unquoted)
}
var data map[string]interface{}
json.Unmarshal(bodyBytes, &data)
// Check for specific MS errors
if errs, ok := data["Errors"].([]interface{}); ok && len(errs) > 0 {
if attempt == 2 {
msg := "Microsoft API error"
if errMap, ok := errs[0].(map[string]interface{}); ok {
if val, exists := errMap["Value"]; exists {
msg = val.(string)
}
}
respondJSONError(w, http.StatusBadGateway, msg)
return
}
continue
}
finalData = data
break
}
skus, ok := finalData["Skus"].([]interface{})
if !ok || len(skus) == 0 {
respondJSONError(w, http.StatusNotFound, "No languages found for this product ID.")
return
}
log.Printf("/skuinfo: product_id=%s -> %d languages (session=%s)\n", productID, len(skus), sessionID[:8])
json.NewEncoder(w).Encode(finalData)
}
// --- /proxy endpoint ---
func handleProxy(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
productID := r.URL.Query().Get("product_id")
skuID := r.URL.Query().Get("sku_id")
if productID == "" || skuID == "" {
respondJSONError(w, http.StatusBadRequest, "product_id and sku_id query parameters are required")
return
}
if _, err := strconv.Atoi(productID); err != nil {
respondJSONError(w, http.StatusBadRequest, "product_id must be a numeric value")
return
}
if _, err := strconv.Atoi(skuID); err != nil {
respondJSONError(w, http.StatusBadRequest, "sku_id must be a numeric value")
return
}
var sessionID string
cacheMutex.RLock()
cached, exists := sessionCache[productID]
cacheMutex.RUnlock()
if exists && time.Since(cached.CreatedAt) < SESSION_TTL {
sessionID = cached.SessionID
log.Printf("/proxy: Reusing session %s for product_id=%s\n", sessionID[:8], productID)
} else {
log.Printf("/proxy: No valid session for product_id=%s, creating new one...\n", productID)
sessionID, _ = setupSession()
// Warmup with SKU info request
warmupQ := url.Values{}
warmupQ.Set("profile", PROFILE)
warmupQ.Set("productEditionId", productID)
warmupQ.Set("SKU", "undefined")
warmupQ.Set("friendlyFileName", "undefined")
warmupQ.Set("Locale", LOCALE)
warmupQ.Set("sessionID", sessionID)
warmupURL := proxyURL("https://www.microsoft.com/software-download-connector/api/getskuinformationbyproductedition?" + warmupQ.Encode())
client := &http.Client{Timeout: 10 * time.Second}
req, _ := http.NewRequest("GET", warmupURL, nil)
req.Header.Set("User-Agent", UA)
req.Header.Set("Referer", getReferer(productID))
req.Header.Set("Accept", "application/json")
setWorkerSecret(req)
client.Do(req)
cacheMutex.Lock()
sessionCache[productID] = SessionEntry{SessionID: sessionID, CreatedAt: time.Now()}
cacheMutex.Unlock()
}
proxyQ := url.Values{}
proxyQ.Set("profile", PROFILE)
proxyQ.Set("productEditionId", "undefined")
proxyQ.Set("SKU", skuID)
proxyQ.Set("friendlyFileName", "undefined")
proxyQ.Set("Locale", LOCALE)
proxyQ.Set("sessionID", sessionID)
reqURL := proxyURL("https://www.microsoft.com/software-download-connector/api/GetProductDownloadLinksBySku?" + proxyQ.Encode())
client := &http.Client{Timeout: 15 * time.Second}
req, _ := http.NewRequest("GET", reqURL, nil)
req.Header.Set("User-Agent", UA)
req.Header.Set("Referer", getReferer(productID))
req.Header.Set("Accept", "application/json")
setWorkerSecret(req)
resp, err := client.Do(req)
if err != nil {
respondJSONError(w, http.StatusBadGateway, "Request failed: "+err.Error())
return
}
defer resp.Body.Close()
bodyBytes, _ := io.ReadAll(resp.Body)
if resp.StatusCode != http.StatusOK {
respondJSONError(w, http.StatusBadGateway, fmt.Sprintf("Microsoft API returned HTTP %d: %s", resp.StatusCode, string(bodyBytes)))
return
}
// Double encoded decode
if len(bodyBytes) > 0 && bodyBytes[0] == '"' {
var unquoted string
json.Unmarshal(bodyBytes, &unquoted)
bodyBytes = []byte(unquoted)
}
var data map[string]interface{}
json.Unmarshal(bodyBytes, &data)
// Check for Microsoft errors
if errs, ok := data["Errors"].([]interface{}); ok && len(errs) > 0 {
if errMap, ok := errs[0].(map[string]interface{}); ok {
if typeNum, exists := errMap["Type"].(float64); exists && typeNum == 9 {
respondJSONError(w, http.StatusTooManyRequests, "Your IP has been temporarily blocked by Microsoft. Please try again later. (Code 715-123130)")
return
}
if val, exists := errMap["Value"].(string); exists {
respondJSONError(w, http.StatusBadGateway, val)
return
}
}
respondJSONError(w, http.StatusBadGateway, "Microsoft API error")
return
}
optsRaw, exists := data["ProductDownloadOptions"]
if !exists {
respondJSONError(w, http.StatusNotFound, "No download links found for this SKU.")
return
}
opts, ok := optsRaw.([]interface{})
if !ok || len(opts) == 0 {
respondJSONError(w, http.StatusNotFound, "No download links found for this SKU.")
return
}
for _, optRaw := range opts {
if optMap, ok := optRaw.(map[string]interface{}); ok {
if arch, hasArch := optMap["Architecture"]; !hasArch || arch == nil {
if dType, hasDType := optMap["DownloadType"].(float64); hasDType {
optMap["Architecture"] = mapDownloadType(int(dType))
}
}
}
}
log.Printf("/proxy: product_id=%s, sku_id=%s -> %d links\n", productID, skuID, len(opts))
// Write pure JSON
var buf bytes.Buffer
enc := json.NewEncoder(&buf)
enc.SetEscapeHTML(false) // Don't encode & to & in URLs!
enc.Encode(data)
w.Write(buf.Bytes())
}
func main() {
go cleanupSessions()
mux := http.NewServeMux()
// API Routing
mux.HandleFunc("/skuinfo", handleSkuInfo)
mux.HandleFunc("/proxy", handleProxy)
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(`{"status":"ok"}`))
})
// Plain root response
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
w.Write([]byte("MSDL API v3 is running"))
})
handler := enableCORS(mux)
log.Printf("Go Backend running on http://localhost%s\n", PORT)
err := http.ListenAndServe(PORT, handler)
if err != nil {
log.Fatal(err)
}
}