diff --git a/.gitignore b/.gitignore index 7cc2439..e1efbdc 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,6 @@ main.exe fpproxy.csr Flashpoint Proxy.exe Flashpoint Game Server.exe -fpProxy.exe \ No newline at end of file +fpProxy.exe +testdata/htdocs +testdata/cgi-bin \ No newline at end of file diff --git a/legacyServer.go b/legacyServer.go index fbe59b5..ff03eb5 100644 --- a/legacyServer.go +++ b/legacyServer.go @@ -2,281 +2,275 @@ package main import ( "bytes" - "errors" "fmt" "io" - "io/ioutil" "net/http" "net/url" "os" "path" "path/filepath" + "strconv" "strings" + "time" - "github.com/elazarl/goproxy" + "github.com/krum110487/zipfs" ) -var legacyProxy *goproxy.ProxyHttpServer +// Tries to serve a legacy file if available +func ServeLegacy(w http.ResponseWriter, r *http.Request) { -func init() { - legacyProxy = goproxy.NewProxyHttpServer() - legacyProxy.Verbose = proxySettings.VerboseLogging -} - -func getLegacyProxy() *goproxy.ProxyHttpServer { - legacyProxy.OnRequest().DoFunc(func(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) { - var localFile *os.File = nil + // @TODO PERFORM REQUEST MODIFICATION HERE - errResp := goproxy.NewResponse(r, goproxy.ContentTypeText, http.StatusNotFound, "404 Not Found") - reqUrl := *r.URL - extensions := []string{".html", ".htm"} - legacyHTDOCS := proxySettings.LegacyHTDOCSPath - fmt.Printf("Legacy Request Started: %s", reqUrl.String()) + relPath := filepath.ToSlash(path.Join(r.URL.Host, r.URL.Path)) + relPathWithQuery := filepath.ToSlash(path.Join(r.URL.Host, r.URL.Path+url.PathEscape("?"+r.URL.RawQuery))) + hasQuery := r.URL.RawQuery != "" - //Normalize the path... - newPath := path.Join(reqUrl.Host, reqUrl.Path) - fp, err := normalizePath(legacyHTDOCS, newPath, false) + // Returns the data from the matching method, finishing the response + successCloser := func(reader io.ReadCloser, fileName string, lastModified string) { + w.Header().Set("Last-Modified", lastModified) + w.Header().Set("ZIPSVR_FILENAME", fileName) + size, err := io.Copy(w, reader) if err != nil { - //TODO: Throw Error here - } - - //Try to open the Local File including the indexes which may exist... - localFile, err = openIfExists(fp, extensions) - if err == nil { - //We found a file, so we can create a response! - proxyResp := goproxy.NewResponse(r, goproxy.ContentTypeText, http.StatusOK, "") - proxyResp.Header.Set("ZIPSVR_FILENAME", localFile.Name()) - contents, err := readFile(localFile) - if err != nil { - return r, errResp - } - proxyResp.Body = contents - return r, proxyResp - } - - //Only if Mad4FP is enabled... - if proxySettings.UseMad4FP { - resp, err := getLiveRemoteFile(legacyHTDOCS, *r) - if err != nil { - fmt.Printf("Mad4FP failed with \"%s\"", err) - } - if resp.StatusCode < 400 { - return r, resp - } + fmt.Printf("Error copying file to response: %s\n", err) + http.Error(w, err.Error(), http.StatusInternalServerError) } else { - efp := proxySettings.ExternalFilePaths - resp, err := getRemoteFile(efp, legacyHTDOCS, extensions, *r) - if err != nil { - fmt.Printf("getRemoteFile failed with \"%s\"", err) - } - if resp.StatusCode < 400 { - return r, resp - } + w.Header().Set("Content-Length", fmt.Sprintf("%d", size)) + w.WriteHeader(http.StatusOK) } - - //Return an error if nothing above worked. - return r, errResp - }) - - return legacyProxy -} - -func normalizePath(rootPath string, pathOrURL string, useCWD bool) (string, error) { - normPath := "" - - //Step0: Check if path is a URL - u, err := url.Parse(pathOrURL) - if err == nil && (strings.HasPrefix(pathOrURL, "http://") || strings.HasPrefix(pathOrURL, "https://")) { - pathOrURL, _ = url.JoinPath(u.Host, u.Path) } - //Step1: Normalize all of the slashes - pathOrURL = strings.Replace(pathOrURL, "\\", "/", -1) - if rootPath != "" { - rootPath = strings.Replace(rootPath, "\\", "/", -1) + /** Overview + * Create groups of paths: + * 1. Exact Files + * 2. Directory Index Files + * + * Peform each check in order, checking each group in order within, to find a match: + * 1. Local File + * 2. Online Server + * 3. Special Behaviour (MAD4FP) + */ + + // Building groups of paths + + // Local = All Paths + // Online = All non-override paths + // Special = Exact content path only (with index consideration) + + // Override paths + + exactContentPath := path.Join(serverSettings.LegacyHTDOCSPath, "content", relPath) + exactFilePaths := []string{} + exactOverrideFilePaths := []string{} + indexFilePaths := []string{} + indexOverrideFilePaths := []string{} + + // 1. Exact Files + if hasQuery { + for _, override := range serverSettings.LegacyOverridePaths { + exactOverrideFilePaths = append(exactOverrideFilePaths, path.Join(serverSettings.LegacyHTDOCSPath, override, relPathWithQuery)) + } + exactFilePaths = append(exactFilePaths, path.Join(serverSettings.LegacyHTDOCSPath, relPathWithQuery)) } - - //Step2: Join the path - if !filepath.IsAbs(pathOrURL) { - normPath = filepath.Join(rootPath, pathOrURL) - } else { - return pathOrURL, nil + for _, override := range serverSettings.LegacyOverridePaths { + exactOverrideFilePaths = append(exactOverrideFilePaths, path.Join(serverSettings.LegacyHTDOCSPath, override, relPath)) } + exactFilePaths = append(exactFilePaths, path.Join(serverSettings.LegacyHTDOCSPath, relPath)) - //Step4: Check if absolute - if !filepath.IsAbs(normPath) { - rPath, _ := os.Getwd() - if !useCWD { - rPath, _ = os.Executable() + // CGI bin for scripts + if isScriptUrl(r.URL) { + if hasQuery { + exactFilePaths = append(exactFilePaths, path.Join(serverSettings.LegacyCGIBINPath, relPathWithQuery)) } - dir := filepath.Dir(strings.Replace(rPath, "\\", "/", -1)) - normPath = filepath.Join(dir, normPath) + exactFilePaths = append(exactFilePaths, path.Join(serverSettings.LegacyCGIBINPath, relPath)) } - // Clean the path to prevent multiple slashes - return filepath.Clean(normPath), nil -} - -func openIfExists(filePath string, indexExts []string) (*os.File, error) { - fi, err := os.Stat(filePath) - extn := filepath.Ext(filePath) + // 2. Directory Index Files + for _, ext := range serverSettings.ExtIndexTypes { + for _, override := range serverSettings.LegacyOverridePaths { + indexOverrideFilePaths = append(indexOverrideFilePaths, path.Join(serverSettings.LegacyHTDOCSPath, override, relPath, "index."+ext)) + } + indexFilePaths = append(indexFilePaths, path.Join(serverSettings.LegacyHTDOCSPath, relPath, "index."+ext)) + } - //Only do this if the path is found and is a dir OR - //path is not found and the extension is blank. - if (err == nil && fi.IsDir()) || (err != nil && extn == "") { - //Loop through exts and try to find index. - for _, ext := range indexExts { - indexFilePath := path.Join(filePath, "/index."+ext) - fii, inErr := os.Stat(indexFilePath) - if inErr != nil { - fi = fii - break + // Try and find a valid file to respond with + + // 1. Local File + for _, filePath := range append(append(append(exactFilePaths, exactOverrideFilePaths...), indexFilePaths...), indexOverrideFilePaths...) { + // Check if file exists + stats, err := os.Stat(filePath) + if err == nil && !stats.IsDir() { + // If it's a PHP file, let CGI handle instead + for _, ext := range serverSettings.ExtScriptTypes { + if filepath.Ext(filePath) == "."+ext { + fmt.Printf("[Legacy] Executing script file: %s\n", filepath.ToSlash(filePath)) + zipfs.Cgi(w, r, serverSettings.PhpCgiPath, filePath) + return + } } - err = inErr + // File exists and is static, serve + f, err := os.Open(filePath) + if err != nil { + // File exists but failed to open, server error + fmt.Printf("[Legacy] Error reading file '%s': %s\n", filePath, err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer f.Close() + fmt.Printf("[Legacy] Serving exact file: %s\n", filepath.ToSlash(filePath)) + successCloser(io.NopCloser(f), filePath, stats.ModTime().Format(time.RFC1123)) + return } } - //Index was not found, path is a valid dir. - if fi != nil && fi.IsDir() { - return nil, errors.New(fmt.Sprintf("Index cannot be found inside directory %s", filePath)) + client := &http.Client{ + Timeout: time.Second * 10, } - //File was found and is NOT a directory, we can serve it. - if err == nil { - // File exists, open it - file, err := os.Open(filePath) - if err != nil { - return nil, err + // 2. Online Server + if serverSettings.UseInfinityServer { + serverUrl := strings.TrimRight(serverSettings.InfinityServerURL, "/") + for _, filePath := range append(exactFilePaths, indexFilePaths...) { + // Do not attempt to run server side scripts + if isScriptFile(filePath) { + continue + } + // Create a new request to the online server + relPath, err := filepath.Rel(serverSettings.LegacyHTDOCSPath, filePath) + if err != nil { + fmt.Printf("[Legacy] Error getting relative path for Infinity request: %s\n", err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + url := serverUrl + "/" + strings.ReplaceAll(relPath, string([]rune{'\\'}), "/") + liveReq, err := http.NewRequest("GET", url, nil) + if err != nil { + fmt.Printf("[Legacy] Error creating Infinity request: %s\n", err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + liveReq.Header.Set("User-Agent", "Flashpoint Game Server") + // Perform request + resp, err := DoWebRequest(liveReq, client, 0) + // If 200, serve and save + if err == nil { + serveLiveResponse(w, resp, filePath, successCloser, "Infinity") + return + } } - - return file, nil - } else if os.IsNotExist(err) { - // File does not exist - return nil, err - } else { - // Some other error occurred - return nil, err } -} -func readFile(file *os.File) (io.ReadCloser, error) { - // Get the file size and rewind the file pointer to the beginning - size, err := file.Seek(0, io.SeekEnd) - if err != nil { - return nil, err - } - _, err = file.Seek(0, io.SeekStart) - if err != nil { - return nil, err + // 3. Special Behaviour (MAD4FP) + if serverSettings.UseMad4FP { + // Do not attempt to run server side scripts + if !isScriptUrl(r.URL) { + // Clone the entire request, to keep headers intact for better scraping + liveReq := r.Clone(r.Context()) + liveReq.Header.Set("User-Agent", "Flashpoint Game Server MAD4FP") + // Perform request + resp, err := DoWebRequest(liveReq, client, 0) + // If 200, serve and save + if err == nil { + serveLiveResponse(w, resp, exactContentPath, successCloser, "MAD4FP") + return + } + } } - // Create a byte slice with the same size as the file - contents := make([]byte, size) + // No response from any method, assume not found + fmt.Printf("[Legacy] 404 Not Found: %s\n", r.URL) + http.NotFound(w, r) +} - // Read the entire file contents into the byte slice - _, err = file.Read(contents) +// Serves a response from a live server, and saves the response body to the originally requested file +func serveLiveResponse(w http.ResponseWriter, resp *http.Response, filePath string, successCloser func(io.ReadCloser, string, string), sourceName string) { + defer resp.Body.Close() + lastModified := resp.Header.Get("Last-Modified") + // Duplicate response body + contents, err := io.ReadAll(resp.Body) if err != nil { - return nil, err + fmt.Printf("[Legacy] Error reading %s response body: %s\n", sourceName, err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return } - - // Convert the byte slice to an io.ReadCloser object - reader := bytes.NewReader(contents) - return ioutil.NopCloser(reader), nil -} - -func saveLocalFile(resp *http.Response, localRootDir string, u url.URL) error { - // Get the filename from the URL - urlPath := path.Join(u.Host, "/", u.Path) - localPath, _ := normalizePath(localRootDir, urlPath, false) - resp.Header.Set("ZIPSVR_FILENAME", localPath) - - // Get the directory path from the URL and create any missing directories - dirPath := filepath.Dir(localPath) - err := os.MkdirAll(dirPath, 0755) + // Save file + err = os.MkdirAll(filepath.Dir(filePath), os.ModePerm) if err != nil { - return err + fmt.Printf("[Legacy] Error saving %s response, cannot make directory: %s\n", sourceName, err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return } - - // Create the output file - file, err := os.Create(localPath) + file, err := os.Create(filePath) if err != nil { - return err + fmt.Printf("[Legacy] Error saving %s response, cannot create file: %s\n", sourceName, err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return } defer file.Close() - - _, err = io.Copy(file, resp.Body) + _, err = io.Copy(file, bytes.NewReader(contents)) if err != nil { - return err + fmt.Printf("[Legacy] Error saving %s response, cannot write file: %s\n", sourceName, err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return } - - return nil -} - -func getLiveRemoteFile(localRootDir string, originalRequest http.Request) (*http.Response, error) { - return getRemoteFile([]string{}, localRootDir, []string{}, originalRequest) -} - -func requestFileAndSave(client *http.Client, prefix string, localRootDir string, u url.URL, origReq http.Request) (*http.Response, error) { - newURL := u.String() - if prefix != "" { - newURL, _ = url.JoinPath(prefix, u.Host, u.Path) + if resp.Header.Get("Last-Modified") != "" { + // Convert header to real time + modifiedTime, err := time.Parse(time.RFC1123, lastModified) + if err == nil { + // Date converted successfuly, set time on file and return with response + err = os.Chtimes(filePath, time.Now(), modifiedTime) + if err != nil { + fmt.Printf("[Legacy] Error saving %s response, cannot set modified time: %s\n", sourceName, err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + fmt.Printf("[Legacy] Serving %s file: %s\n", sourceName, filepath.ToSlash(filePath)) + successCloser(io.NopCloser(bytes.NewReader(contents)), filePath, lastModified) + return + } } + // No last modified found, just use current time + successCloser(io.NopCloser(bytes.NewReader(contents)), filePath, time.Now().Format(time.RFC1123)) +} - remoteReq, err := http.NewRequest(origReq.Method, newURL, origReq.Body) - response, err := client.Do(remoteReq) +// Treats non-200 responses as errors, and handles 429 responses with a retry timer +func DoWebRequest(r *http.Request, client *http.Client, depth int) (*http.Response, error) { + resp, err := client.Do(r) if err != nil { return nil, err } - - defer response.Body.Close() - if response.StatusCode == http.StatusOK { - err := saveLocalFile(response, localRootDir, u) + if resp.StatusCode == 429 { + if depth > 3 { + return resp, fmt.Errorf("Too many 429 responses") + } + // Rate limited, wait and try again + timeToSleep := resp.Header.Get("Retry-After") + timeToSleepInt, err := strconv.Atoi(timeToSleep) if err != nil { - return nil, err + timeToSleepInt = 2 } - return response, nil + time.Sleep(time.Duration(timeToSleepInt) * time.Second) + return DoWebRequest(r, client, depth+1) } - - return nil, errors.New("File was not found!") + if resp.StatusCode == 200 { + return resp, nil + } + return resp, fmt.Errorf(resp.Status) } -func getRemoteFile(urlPrefix []string, localRootDir string, indexExts []string, origReq http.Request) (*http.Response, error) { - client := &http.Client{} - errResp := goproxy.NewResponse(&origReq, goproxy.ContentTypeText, http.StatusNotFound, "404 Not Found") - var extErr error = nil - - //If the prefix is not set, we are going on the LIVE internet! - if len(urlPrefix) < 1 { - //GOING TO THAT LIVE INTERWEBZ! - resp, err := requestFileAndSave(client, "", localRootDir, *origReq.URL, origReq) - extErr = err - if err == nil { - return resp, nil +func isScriptFile(filePath string) bool { + for _, ext := range serverSettings.ExtScriptTypes { + if filepath.Ext(filePath) == "."+ext { + return true } - } else { - //Loop through all the given prefixes - for _, prefix := range urlPrefix { - //Try the Original url with the prefix. - resp, err := requestFileAndSave(client, prefix, localRootDir, *origReq.URL, origReq) - extErr = err - if err == nil { - return resp, nil - } - - //If the file wasn't found, we append the indexes until it is found. - for _, ext := range indexExts { - //Generate url from Remote HTDOCS - indexURLstr, _ := url.JoinPath(origReq.URL.String(), "/index."+ext) - newIndexURL, _ := url.Parse(indexURLstr) + } + return false +} - //Get the files. - resp, err := requestFileAndSave(client, prefix, localRootDir, *newIndexURL, origReq) - extErr = err - if err == nil { - return resp, nil - } - } +func isScriptUrl(u *url.URL) bool { + for _, ext := range serverSettings.ExtScriptTypes { + if filepath.Ext(u.Path) == "."+ext { + return true } } - return errResp, extErr + return false } diff --git a/legacyServer_test.go b/legacyServer_test.go new file mode 100644 index 0000000..c5e4e8c --- /dev/null +++ b/legacyServer_test.go @@ -0,0 +1,434 @@ +package main + +import ( + "bytes" + "fmt" + "io" + "net/http" + "net/http/httptest" + "net/url" + "os" + "path" + "strconv" + "strings" + "testing" +) + +type legacyServerTestResponse struct { + statusCode int + headers *http.Header + body []byte + savedFile *string +} + +type legacyServerTest struct { + request *http.Request + response *legacyServerTestResponse +} + +var testServerSettingsBuilt = false +var testServerSettings = ServerSettings{ + UseMad4FP: false, + UseInfinityServer: false, + InfinityServerURL: "https://infinity.unstable.life/Flashpoint/Legacy/htdocs/", + LegacyHTDOCSPath: "", + LegacyCGIBINPath: "", + ExtScriptTypes: []string{ + "php", + }, + ExtIndexTypes: []string{ + "htm", "html", "php", + }, + PhpCgiPath: `J:\Data\Flashpoint\Legacy\php-cgi.exe`, +} + +func setup(settings *ServerSettings) { + if !testServerSettingsBuilt { + testServerSettingsBuilt = true + cwd, err := os.Getwd() + if err != nil { + panic(err) + } + testServerSettings.LegacyHTDOCSPath = path.Join(cwd, "testdata", "htdocs") + settings.LegacyHTDOCSPath = testServerSettings.LegacyHTDOCSPath + testServerSettings.LegacyCGIBINPath = path.Join(cwd, "testdata", "cgi-bin") + settings.LegacyCGIBINPath = testServerSettings.LegacyCGIBINPath + // Create directories if missing + err = os.MkdirAll(testServerSettings.LegacyHTDOCSPath, os.ModePerm) + if err != nil { + panic(err) + } + err = os.MkdirAll(testServerSettings.LegacyCGIBINPath, os.ModePerm) + if err != nil { + panic(err) + } + } + // Cleanup and remake htdocs + err := os.RemoveAll(testServerSettings.LegacyHTDOCSPath) + if err != nil { + panic(err) + } + err = os.MkdirAll(testServerSettings.LegacyHTDOCSPath, os.ModePerm) + if err != nil { + panic(err) + } + serverSettings = *settings +} + +func (test *legacyServerTest) run() error { + writer := httptest.NewRecorder() + ServeLegacy(writer, test.request) + res := writer.Result() + body, err := io.ReadAll(res.Body) + if err != nil { + return fmt.Errorf("failed to read body %s", err) + } + defer res.Body.Close() + if len(test.response.body) != 0 { + // Make sure body matches + if !bytes.Equal(body, test.response.body) { + return fmt.Errorf("expected body %s, got %s", test.response.body, body) + } + } + if test.response.statusCode != -1 && res.StatusCode != test.response.statusCode { + // Make sure status code matches + return fmt.Errorf("expected status code %d, got %d", test.response.statusCode, res.StatusCode) + } + if test.response.headers != nil { + lowerCaseHeader := make(http.Header) + for key, values := range res.Header { + // Convert the key to lowercase + lowerKey := strings.ToLower(key) + // Copy the values to the new header + lowerCaseHeader[lowerKey] = values + } + // Make sure all headers are present + for key, values := range *test.response.headers { + lowerKey := strings.ToLower(key) + if lowerKey == "content-length" { + val, err := strconv.Atoi(values[0]) + if err != nil { + return fmt.Errorf("invalid test content length %s: %s", values[0], err) + } + if res.ContentLength == -1 { + // Unknown length, must read body? + if len(body) != val { + return fmt.Errorf("expected content length %d, got %d", val, len(body)) + } + } else { + if res.ContentLength != int64(val) { + return fmt.Errorf("expected content length %d, got %d", val, res.ContentLength) + } + } + } else { + if lowerCaseHeader[lowerKey] == nil || lowerCaseHeader[key][0] != values[0] { + badValue := "" + if lowerCaseHeader[lowerKey] != nil { + badValue = lowerCaseHeader[lowerKey][0] + } + return fmt.Errorf("expected header %s to be %s, got %s", key, values[0], badValue) + } + } + } + } + if test.response.savedFile != nil { + // Check the file exists + _, err := os.Stat(*test.response.savedFile) + if err != nil { + return fmt.Errorf("expected file %s to exist, got error %s", *test.response.savedFile, err) + } + } + return nil +} + +func TestServeLegacy404(t *testing.T) { + setup(&testServerSettings) + + test := &legacyServerTest{ + request: makeNewRequest("GET", "https://example.com/404-example", nil), + response: &legacyServerTestResponse{ + statusCode: http.StatusNotFound, + }, + } + + err := test.run() + if err != nil { + t.Error(err) + } +} + +func TestServeLegacy200(t *testing.T) { + setup(&testServerSettings) + + // Write a test file + testData := []byte("success") + testFile := path.Join(testServerSettings.LegacyHTDOCSPath, "example.com", "test.txt") + // Make directory path + err := os.MkdirAll(path.Dir(testFile), os.ModePerm) + if err != nil { + t.Error(err) + } + err = os.WriteFile(testFile, testData, os.ModePerm) + if err != nil { + t.Error(err) + } + + headers := &http.Header{} + headers.Set("Content-Length", strconv.Itoa(len(testData))) + test := &legacyServerTest{ + request: makeNewRequest("GET", "http://example.com/test.txt", nil), + response: &legacyServerTestResponse{ + statusCode: http.StatusOK, + headers: headers, + }, + } + + err = test.run() + if err != nil { + t.Error(err) + } +} + +func TestServeLegacy200WithQuery(t *testing.T) { + setup(&testServerSettings) + + // Write 2 test files, to validate that the query is being used + testData := []byte("success") + testDataQuery := []byte("success query") + testFile := path.Join(testServerSettings.LegacyHTDOCSPath, "example.com", "test.txt") + // Assume all filenames are url encoded + testFileQuery := path.Join(testServerSettings.LegacyHTDOCSPath, "example.com", url.PathEscape("test.txt?query=true")) + + // Make directory path + err := os.MkdirAll(path.Dir(testFile), os.ModePerm) + if err != nil { + t.Error(err) + } + err = os.WriteFile(testFile, testData, os.ModePerm) + if err != nil { + t.Error(err) + } + err = os.WriteFile(testFileQuery, testDataQuery, os.ModePerm) + if err != nil { + t.Error(err) + } + + headers := &http.Header{} + headers.Set("Content-Length", strconv.Itoa(len(testDataQuery))) + test := &legacyServerTest{ + request: makeNewRequest("GET", "http://example.com/test.txt?query=true", nil), + response: &legacyServerTestResponse{ + statusCode: http.StatusOK, + headers: headers, + }, + } + + err = test.run() + if err != nil { + t.Error(err) + } +} + +func TestServeLegacy200Index(t *testing.T) { + setup(&testServerSettings) + + // Write a test file + testData := []byte("success") + testFile := path.Join(testServerSettings.LegacyHTDOCSPath, "example.com", "index.htm") + // Make directory path + err := os.MkdirAll(path.Dir(testFile), os.ModePerm) + if err != nil { + t.Error(err) + } + err = os.WriteFile(testFile, testData, os.ModePerm) + if err != nil { + t.Error(err) + } + + headers := &http.Header{} + headers.Set("Content-Length", strconv.Itoa(len(testData))) + test := &legacyServerTest{ + request: makeNewRequest("GET", "http://example.com/", nil), + response: &legacyServerTestResponse{ + statusCode: http.StatusOK, + headers: headers, + }, + } + + err = test.run() + if err != nil { + t.Error(err) + } +} + +func TestServeLegacy200Script(t *testing.T) { + setup(&testServerSettings) + + // Write a test file + testStr := "success" + testData := []byte(fmt.Sprintf("", testStr)) + testFile := path.Join(testServerSettings.LegacyCGIBINPath, "example.com", "index.php") + // Make directory path + err := os.MkdirAll(path.Dir(testFile), os.ModePerm) + if err != nil { + t.Error(err) + } + err = os.WriteFile(testFile, testData, os.ModePerm) + if err != nil { + t.Error(err) + } + + headers := &http.Header{} + headers.Set("Content-Length", strconv.Itoa(len(testStr))) + test := &legacyServerTest{ + request: makeNewRequest("GET", "http://example.com/index.php", nil), + response: &legacyServerTestResponse{ + statusCode: http.StatusOK, + headers: headers, + }, + } + + err = test.run() + if err != nil { + t.Error(err) + } +} + +func TestServeLegacy200ScriptPost(t *testing.T) { + setup(&testServerSettings) + + // Write a test file + testStr := "success" + testData := []byte(``) + testFile := path.Join(testServerSettings.LegacyCGIBINPath, "example.com", "index.php") + // Make directory path + err := os.MkdirAll(path.Dir(testFile), os.ModePerm) + if err != nil { + t.Error(err) + } + err = os.WriteFile(testFile, testData, os.ModePerm) + if err != nil { + t.Error(err) + } + + headers := &http.Header{} + headers.Set("Content-Length", strconv.Itoa(len(testStr))) + test := &legacyServerTest{ + request: makeNewRequest("POST", "http://example.com/index.php", bytes.NewBuffer([]byte(fmt.Sprintf(`data=%s`, testStr)))), + response: &legacyServerTestResponse{ + statusCode: http.StatusOK, + headers: headers, + }, + } + test.request.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + err = test.run() + if err != nil { + t.Error(err) + } +} + +func TestServeLegacyDisabledOnline(t *testing.T) { + setup(&testServerSettings) + + test := &legacyServerTest{ + request: makeNewRequest("GET", "http://andkon.com/grey/grey.swf", nil), + response: &legacyServerTestResponse{ + statusCode: http.StatusNotFound, + }, + } + + err := test.run() + if err != nil { + t.Error(err) + } +} + +func TestServeLegacyOnline200(t *testing.T) { + settings := testServerSettings + settings.UseInfinityServer = true + setup(&settings) + + savedFile := path.Join(settings.LegacyHTDOCSPath, "andkon.com", "grey", "grey.swf") + headers := &http.Header{} + headers.Set("Content-Length", "1065015") + test := &legacyServerTest{ + request: makeNewRequest("GET", "http://andkon.com/grey/grey.swf", nil), + response: &legacyServerTestResponse{ + statusCode: http.StatusOK, + savedFile: &savedFile, + headers: headers, + }, + } + + err := test.run() + if err != nil { + t.Error(err) + } +} + +func TestServeLegacyOnline200Index(t *testing.T) { + settings := testServerSettings + settings.UseInfinityServer = true + setup(&settings) + + savedFile := path.Join(settings.LegacyHTDOCSPath, "kongregate.com", "ChuckTheSheep", "index.htm") + test := &legacyServerTest{ + request: makeNewRequest("GET", "http://kongregate.com/ChuckTheSheep/", nil), + response: &legacyServerTestResponse{ + statusCode: http.StatusOK, + savedFile: &savedFile, + }, + } + + err := test.run() + if err != nil { + t.Error(err) + } +} + +func TestServeLegacyDisabledMad4fp(t *testing.T) { + setup(&testServerSettings) + + test := &legacyServerTest{ + request: makeNewRequest("GET", "http://flashpointarchive.org/images/logo.svg", nil), + response: &legacyServerTestResponse{ + statusCode: http.StatusNotFound, + }, + } + + err := test.run() + if err != nil { + t.Error(err) + } +} + +func TestServeLegacyMad4fp200(t *testing.T) { + settings := testServerSettings + settings.UseMad4FP = true + setup(&settings) + + savedFile := path.Join(settings.LegacyHTDOCSPath, "content", "flashpointarchive.org", "images", "logo.svg") + test := &legacyServerTest{ + request: makeNewRequest("GET", "http://flashpointarchive.org/images/logo.svg", nil), + response: &legacyServerTestResponse{ + statusCode: http.StatusOK, + savedFile: &savedFile, + }, + } + + err := test.run() + if err != nil { + t.Error(err) + } +} + +func makeNewRequest(method string, url string, body io.Reader) *http.Request { + request, err := http.NewRequest(method, url, body) + if err != nil { + panic(err) + } + return request +} diff --git a/main.go b/main.go index 7695525..725331b 100644 --- a/main.go +++ b/main.go @@ -1,63 +1,61 @@ package main import ( - "bufio" "bytes" "context" "crypto/tls" "encoding/json" "flag" "fmt" + "io" "io/ioutil" "log" "net" "net/http" + "net/http/httptest" "net/url" "os" - "os/exec" - "os/signal" "path" "path/filepath" "strconv" "strings" - "sync" "time" "github.com/elazarl/goproxy" "github.com/krum110487/zipfs" ) -type ProxySettings struct { - AllowCrossDomain bool `json:"allowCrossDomain"` - VerboseLogging bool `json:"verboseLogging"` - ProxyPort string `json:"proxyPort"` - ServerHTTPPort string `json:"serverHTTPPort"` - ServerHTTPSPort string `json:"serverHTTPSPort"` - GameRootPath string `json:"gameRootPath"` - ApiPrefix string `json:"apiPrefix"` - ExternalFilePaths []string `json:"externalFilePaths"` - ExtScriptTypes []string `json:"extScriptTypes"` - ExtIndexTypes []string `json:"extIndexTypes"` - ExtMimeTypes map[string]string `json:"extMimeTypes"` - ExtGzippeddTypes []string `json:"extGzippedTypes"` - UseMad4FP bool `json:"useMad4FP"` - LegacyGoPort string `json:"legacyGoPort"` - LegacyPHPPort string `json:"legacyPHPPort"` - LegacyPHPPath string `json:"legacyPHPPath"` - LegacyUsePHPServer bool `json:"legacyUsePHPServer"` - LegacyHTDOCSPath string `json:"legacyHTDOCSPath"` - RootPath string `json:"rootPath"` - LegacyCGIBINPath string `json:"legacyCGIBINPath"` - PhpCgiPath string `json:"phpCgiPath"` - OverridePaths []string `json:"overridePaths"` +type ServerSettings struct { + AllowCrossDomain bool `json:"allowCrossDomain"` + VerboseLogging bool `json:"verboseLogging"` + ProxyPort string `json:"proxyPort"` + ServerHTTPPort string `json:"serverHTTPPort"` + GameRootPath string `json:"gameRootPath"` + ApiPrefix string `json:"apiPrefix"` + ExternalFilePaths []string `json:"externalFilePaths"` + ExtScriptTypes []string `json:"extScriptTypes"` + ExtIndexTypes []string `json:"extIndexTypes"` + ExtMimeTypes map[string]string `json:"extMimeTypes"` + ExtGzippeddTypes []string `json:"extGzippedTypes"` + UseMad4FP bool `json:"useMad4FP"` + HandleLegacyRequests bool `json:"handleLegacyRequests"` + ExternalLegacyPort string `json:"externalLegacyPort"` + LegacyHTDOCSPath string `json:"legacyHTDOCSPath"` + RootPath string `json:"rootPath"` + LegacyCGIBINPath string `json:"legacyCGIBINPath"` + PhpCgiPath string `json:"phpCgiPath"` + OverridePaths []string `json:"overridePaths"` + LegacyOverridePaths []string `json:"legacyOverridePaths"` + UseInfinityServer bool `json:"useInfinityServer"` + InfinityServerURL string `json:"infinityServerURL"` } // ExtApplicationTypes is a map that holds the content types of different file extensions -var proxySettings ProxySettings +var serverSettings ServerSettings var proxy *goproxy.ProxyHttpServer var cwd string -func init() { +func initServer() { // Load the content types from the JSON file data, err := os.ReadFile("proxySettings.json") if err != nil { @@ -65,7 +63,7 @@ func init() { } // Unmarshal the JSON data into a Config struct - err = json.Unmarshal(data, &proxySettings) + err = json.Unmarshal(data, &serverSettings) if err != nil { panic(err) } @@ -83,65 +81,70 @@ func init() { verboseLogging := flag.Bool("v", false, "should every proxy request be logged to stdout") proxyPort := flag.Int("proxyPort", 22500, "proxy listen port") serverHTTPPort := flag.Int("serverHttpPort", 22501, "zip server http listen port") - serverHTTPSPort := flag.Int("serverHttpsPort", 22502, "zip server https listen port") - gameRootPath := flag.String("gameRootPath", "D:\\Flashpoint\\Data\\Games", "This is the path where to find the zips") + gameRootPath := flag.String("gameRootPath", serverSettings.GameRootPath, "This is the path where to find the zips") rootPath := flag.String("rootPath", "D:\\Flashpoint", "The path that other relative paths use as a base") apiPrefix := flag.String("apiPrefix", "/fpProxy/api", "apiPrefix is used to prefix any API call.") useMad4FP := flag.Bool("UseMad4FP", false, "flag to turn on/off Mad4FP.") - legacyGoPort := flag.Int("legacyGoPort", 22601, "port that the legacy GO server listens on") - legacyPHPPort := flag.Int("legacyPHPPort", 22600, "port that the legacy PHP server listens on") - legacyPHPPath := flag.String("legacyPHPPath", "D:\\Flashpoint\\Legacy", "This is the path for HTDOCS") - legacyUsePHPServer := flag.Bool("legacyUsePHPServer", true, "This will run the original PHP script in parallel") - legacyHTDOCSPath := flag.String("legacyHTDOCSPath", "D:\\Flashpoint\\Legacy\\htdocs", "This is the path for HTDOCS") - phpCgiPath := flag.String("phpCgiPath", "D:\\Flashpoint\\Legacy\\php-cgi.exe", "Path to PHP CGI executable") + externalLegacyPort := flag.String("externalLegacyPort", "22600", "The port that the external legacy server is running on (if handling legacy is disabled).") + legacyHTDOCSPath := flag.String("legacyHTDOCSPath", serverSettings.LegacyHTDOCSPath, "This is the path for HTDOCS") + phpCgiPath := flag.String("phpCgiPath", serverSettings.PhpCgiPath, "Path to PHP CGI executable") + useInfinityServer := flag.Bool("useInfinityServer", false, "Whether to use the infinity server or not") + infinityServerURL := flag.String("infinityServerURL", serverSettings.InfinityServerURL, "The URL of the infinity server") + legacyCGIBINPath := flag.String("legacyCGIBINPath", serverSettings.LegacyCGIBINPath, "This is the path for CGI-BIN") + handleLegacyRequests := flag.Bool("handleLegacyRequests", false, "Whether to handle legacy requests internally (true) or externally (false)") + flag.Parse() //Apply all of the flags to the settings - proxySettings.VerboseLogging = *verboseLogging - proxySettings.RootPath, err = filepath.Abs(strings.Trim(*rootPath, "\"")) + serverSettings.VerboseLogging = *verboseLogging + serverSettings.RootPath, err = filepath.Abs(strings.Trim(*rootPath, "\"")) if err != nil { fmt.Printf("Failed to get absolute game root path") return } - proxySettings.ProxyPort = strconv.Itoa(*proxyPort) - proxySettings.ServerHTTPPort = strconv.Itoa(*serverHTTPPort) - proxySettings.ServerHTTPSPort = strconv.Itoa(*serverHTTPSPort) - proxySettings.ApiPrefix = *apiPrefix - proxySettings.UseMad4FP = *useMad4FP - proxySettings.LegacyGoPort = strconv.Itoa(*legacyGoPort) - proxySettings.LegacyPHPPort = strconv.Itoa(*legacyPHPPort) - proxySettings.LegacyPHPPath = *legacyPHPPath - proxySettings.LegacyUsePHPServer = *legacyUsePHPServer - proxySettings.LegacyHTDOCSPath, err = filepath.Abs(path.Join(proxySettings.RootPath, strings.Trim(*legacyHTDOCSPath, "\""))) + serverSettings.ProxyPort = strconv.Itoa(*proxyPort) + serverSettings.ServerHTTPPort = strconv.Itoa(*serverHTTPPort) + serverSettings.ApiPrefix = *apiPrefix + serverSettings.UseMad4FP = *useMad4FP + serverSettings.ExternalLegacyPort = *externalLegacyPort + serverSettings.LegacyCGIBINPath, err = filepath.Abs(path.Join(serverSettings.RootPath, strings.Trim(*legacyCGIBINPath, "\""))) + if err != nil { + fmt.Printf("Failed to get absolute cgi-bin path") + return + } + serverSettings.LegacyHTDOCSPath, err = filepath.Abs(path.Join(serverSettings.RootPath, strings.Trim(*legacyHTDOCSPath, "\""))) if err != nil { fmt.Printf("Failed to get absolute htdocs path") return } - proxySettings.GameRootPath, err = filepath.Abs(path.Join(proxySettings.RootPath, strings.Trim(*gameRootPath, "\""))) + serverSettings.GameRootPath, err = filepath.Abs(path.Join(serverSettings.RootPath, strings.Trim(*gameRootPath, "\""))) if err != nil { fmt.Printf("Failed to get absolute game root path") return } - proxySettings.PhpCgiPath, err = filepath.Abs(path.Join(proxySettings.RootPath, strings.Trim(*phpCgiPath, "\""))) + serverSettings.PhpCgiPath, err = filepath.Abs(path.Join(serverSettings.RootPath, strings.Trim(*phpCgiPath, "\""))) if err != nil { fmt.Printf("Failed to get absolute php cgi path") return } + serverSettings.UseInfinityServer = *useInfinityServer + serverSettings.InfinityServerURL = *infinityServerURL + serverSettings.HandleLegacyRequests = *handleLegacyRequests // Print out all path settings - fmt.Printf("Root Path: %s\n", proxySettings.RootPath) - fmt.Printf("PHP CGI Path: %s\n", proxySettings.PhpCgiPath) - fmt.Printf("Legacy HTDOCS Path: %s\n", proxySettings.LegacyHTDOCSPath) + fmt.Printf("Root Path: %s\n", serverSettings.RootPath) + fmt.Printf("PHP CGI Path: %s\n", serverSettings.PhpCgiPath) + fmt.Printf("Legacy HTDOCS Path: %s\n", serverSettings.LegacyHTDOCSPath) + fmt.Printf("Legacy CGI BIN Path: %s\n", serverSettings.LegacyCGIBINPath) + fmt.Printf("Games Path: %s\n", serverSettings.GameRootPath) //Setup the proxy. proxy = goproxy.NewProxyHttpServer() - proxy.Verbose = proxySettings.VerboseLogging - gamePath, _ := normalizePath("", proxySettings.GameRootPath, false) - fmt.Printf("Proxy Server Started on port %s\n", proxySettings.ProxyPort) - fmt.Printf("Zip Server Started\n\tHTTP Port: %s\n\tHTTPS Port: %s\n\tGame Root: %s\n", - proxySettings.ServerHTTPPort, - proxySettings.ServerHTTPSPort, - gamePath) + proxy.Verbose = serverSettings.VerboseLogging + fmt.Printf("Proxy Server Started on port %s\n", serverSettings.ProxyPort) + fmt.Printf("Zip Server Started\n\tHTTP Port: %s\n\tGame Root: %s\n", + serverSettings.ServerHTTPPort, + serverSettings.GameRootPath) } func setContentType(r *http.Request, resp *http.Response) { @@ -157,13 +160,13 @@ func setContentType(r *http.Request, resp *http.Response) { // If the request already has an extension, fetch the mime via extension if ext != "" { - resp.Header.Set("Content-Type", proxySettings.ExtMimeTypes[ext[1:]]) - mime = proxySettings.ExtMimeTypes[ext[1:]] + resp.Header.Set("Content-Type", serverSettings.ExtMimeTypes[ext[1:]]) + mime = serverSettings.ExtMimeTypes[ext[1:]] if mime != "" && len(ext) > 1 { resp.Header.Set("Content-Type", mime) e := ext[1:] // If pre-compressed set encoding type - for _, element := range proxySettings.ExtGzippeddTypes { + for _, element := range serverSettings.ExtGzippeddTypes { if element == e { resp.Header.Set("Content-Encoding", "gzip") break // String found, no need to continue iterating @@ -174,13 +177,13 @@ func setContentType(r *http.Request, resp *http.Response) { // If the response has an extension, try and fetch the mime for that via extension if mime == "" && rext != "" { - resp.Header.Set("Content-Type", proxySettings.ExtMimeTypes[rext[1:]]) - mime = proxySettings.ExtMimeTypes[rext[1:]] + resp.Header.Set("Content-Type", serverSettings.ExtMimeTypes[rext[1:]]) + mime = serverSettings.ExtMimeTypes[rext[1:]] if mime != "" && len(rext) > 1 { resp.Header.Set("Content-Type", mime) e := rext[1:] // If pre-compressed set encoding type - for _, element := range proxySettings.ExtGzippeddTypes { + for _, element := range serverSettings.ExtGzippeddTypes { if element == e { resp.Header.Set("Content-Encoding", "gzip") break // String found, no need to continue iterating @@ -198,23 +201,22 @@ func setContentType(r *http.Request, resp *http.Response) { func handleRequest(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) { // Remove port from host if exists (old apps don't clean it before sending requests?) r.URL.Host = strings.Split(r.URL.Host, ":")[0] + // Clone the body into both requests by reading and making 2 new readers + contents, _ := ioutil.ReadAll(r.Body) // Copy the original request gamezipRequest := &http.Request{ Method: r.Method, URL: &url.URL{ Scheme: "http", - Host: "127.0.0.1:" + proxySettings.ServerHTTPPort, + Host: "127.0.0.1:" + serverSettings.ServerHTTPPort, Path: "content/" + r.URL.Host + r.URL.Path, RawQuery: r.URL.RawQuery, }, Header: r.Header, - Body: r.Body, + Body: nil, } - - // Clone the body into both requests by reading and making 2 new readers - contents, _ := ioutil.ReadAll(r.Body) - gamezipRequest.Body = ioutil.NopCloser(bytes.NewReader(contents)) + gamezipRequest.Body = io.NopCloser(bytes.NewReader(contents)) // Make the request to the zip server. client := &http.Client{} @@ -241,36 +243,41 @@ func handleRequest(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http RawQuery: r.URL.RawQuery, }, Header: r.Header, - Body: r.Body, + Body: nil, } // Copy in a new body reader legacyRequest.Body = ioutil.NopCloser(bytes.NewReader(contents)) - port := proxySettings.LegacyPHPPort - - // Set the Proxy URL and apply it to the Transpor layer so that the request respects the proxy. - proxyURL, _ := url.Parse("http://127.0.0.1:" + port) - proxy := http.ProxyURL(proxyURL) - transport := &http.Transport{Proxy: proxy} - - // A custom Dialer is required for the "localflash" urls, instead of using the DNS, we use this. - transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) { - //Set Dialer timeout and keepalive to 30 seconds and force the address to localhost. - dialer := &net.Dialer{Timeout: 30 * time.Second, KeepAlive: 30 * time.Second} - addr = "127.0.0.1:" + port - return dialer.DialContext(ctx, network, addr) - } - - // Make the request with the custom transport - client := &http.Client{Transport: transport, Timeout: 300 * time.Second} - legacyResp, err := client.Do(legacyRequest) - // An error occured, log it for debug purposes - if err == nil { - fmt.Printf("\tServing from Legacy...\n") - proxyResp = legacyResp + // Choose which legacy method we're using + if serverSettings.HandleLegacyRequests { + // If internal, skip actual networking + resRecorder := httptest.NewRecorder() + ServeLegacy(resRecorder, legacyRequest) + proxyResp = resRecorder.Result() } else { - fmt.Printf("UNHANDLED LEGACY ERROR: %s\n", err) - fmt.Printf("\tfailure legacy\n") + // Set the Proxy URL and apply it to the Transpor layer so that the request respects the proxy. + proxyURL, _ := url.Parse("http://127.0.0.1:" + serverSettings.ExternalLegacyPort) + proxy := http.ProxyURL(proxyURL) + transport := &http.Transport{Proxy: proxy} + + // A custom Dialer is required for the "localflash" urls, instead of using the DNS, we use this. + transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) { + //Set Dialer timeout and keepalive to 30 seconds and force the address to localhost. + dialer := &net.Dialer{Timeout: 30 * time.Second, KeepAlive: 30 * time.Second} + addr = "127.0.0.1:" + serverSettings.ExternalLegacyPort + return dialer.DialContext(ctx, network, addr) + } + + // Make the request with the custom transport + client := &http.Client{Transport: transport, Timeout: 300 * time.Second} + legacyResp, err := client.Do(legacyRequest) + // An error occured, log it for debug purposes + if err == nil { + fmt.Printf("\tServing from External Legacy...\n") + proxyResp = legacyResp + } else { + fmt.Printf("UNHANDLED EXTERNAL LEGACY ERROR: %s\n", err) + } } } @@ -278,6 +285,8 @@ func handleRequest(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http setContentType(r, proxyResp) // Add extra headers + proxyResp.Header.Set("Access-Control-Allow-Headers", "*") + proxyResp.Header.Set("Access-Control-Allow-Methods", "*") proxyResp.Header.Set("Access-Control-Allow-Origin", "*") // Keep Alive if strings.ToLower(r.Header.Get("Connection")) == "keep-alive" { @@ -289,6 +298,7 @@ func handleRequest(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http } func main() { + initServer() // To create CA cert, refer to https://wiki.mozilla.org/SecurityEngineering/x509Certs#Self_Signed_Certs // Replace CA in GoProxy certData := []byte(`-----BEGIN CERTIFICATE----- @@ -334,78 +344,25 @@ XgVWIMrKj4T7p86bcxq4jdWDYUYpRd/2Og== return handleRequest(r, ctx) }) - //Start ZIP server + // Start ZIP server go func() { //TODO: Update these to be modifiable in the properties json. //TODO: Also update the "fpProxy/api/" to be in the properties json. - log.Fatal(http.ListenAndServe("127.0.0.1:"+proxySettings.ServerHTTPPort, + log.Fatal(http.ListenAndServe("127.0.0.1:"+serverSettings.ServerHTTPPort, zipfs.EmptyFileServer( - proxySettings.ApiPrefix, + serverSettings.ApiPrefix, "", - proxySettings.VerboseLogging, - proxySettings.ExtIndexTypes, - proxySettings.GameRootPath, - proxySettings.PhpCgiPath, - proxySettings.ExtMimeTypes, - proxySettings.OverridePaths, - proxySettings.LegacyHTDOCSPath, + serverSettings.VerboseLogging, + serverSettings.ExtIndexTypes, + serverSettings.GameRootPath, + serverSettings.PhpCgiPath, + serverSettings.ExtMimeTypes, + serverSettings.OverridePaths, + serverSettings.LegacyHTDOCSPath, ), )) }() - /** DISABLED WHILE NOT FUNCTIONING */ - // //Start Legacy server - // go func() { - // if proxySettings.LegacyUsePHPServer { - // runLegacyPHP() - // } else { - // log.Fatal(http.ListenAndServe("127.0.0.1:"+proxySettings.LegacyGoPort, getLegacyProxy())) - // } - // }() - - //Start proxy server - log.Fatal(http.ListenAndServe("127.0.0.1:"+proxySettings.ProxyPort, http.AllowQuerySemicolons(proxy))) -} - -func runLegacyPHP() { - phpPath := filepath.Join(proxySettings.LegacyPHPPath, "php") - cmd := exec.Command(phpPath, "-S", "127.0.0.1:"+proxySettings.LegacyPHPPort, "router.php") - cmd.Dir = proxySettings.LegacyPHPPath - stdout, _ := cmd.StdoutPipe() - stderr, _ := cmd.StderrPipe() - cmd.Start() - - c := make(chan os.Signal, 2) - signal.Notify(c, os.Interrupt, os.Kill) - go func() { - <-c - // cleanup - cmd.Process.Kill() - os.Exit(1) - }() - - var wg sync.WaitGroup - wg.Add(1) - go func() { - s := bufio.NewScanner(stdout) - for s.Scan() { - fmt.Println(s.Text()) - } - wg.Done() - }() - - wg.Add(1) - go func() { - s := bufio.NewScanner(stderr) - for s.Scan() { - fmt.Println(s.Text()) - } - wg.Done() - }() - - wg.Wait() -} - -func serveOverrideFile(w http.ResponseWriter, r *http.Request) { - + // Start proxy server + log.Fatal(http.ListenAndServe("127.0.0.1:"+serverSettings.ProxyPort, http.AllowQuerySemicolons(proxy))) } diff --git a/proxySettings.json b/proxySettings.json index 4cdc7d4..41e3d25 100644 --- a/proxySettings.json +++ b/proxySettings.json @@ -1,8 +1,11 @@ { + "rootPath": "../", "legacyHTDOCSPath": "Legacy/htdocs/", "legacyCGIBINPath": "Legacy/cgi-bin/", "legacyPHPPath": "Legacy/", - "rootPath": "../", + "gameRootPath": "Data/Games/", + "phpCgiPath": "Legacy/php-cgi.exe", + "infinityServerURL": "https://infinity.unstable.life/Flashpoint/Legacy/htdocs/", "overridePaths": [ "../Legacy/middleware_overrides/" ],