aboutsummaryrefslogtreecommitdiff
path: root/forged/internal/unsorted/http_server.go
diff options
context:
space:
mode:
Diffstat (limited to 'forged/internal/unsorted/http_server.go')
-rw-r--r--forged/internal/unsorted/http_server.go276
1 files changed, 276 insertions, 0 deletions
diff --git a/forged/internal/unsorted/http_server.go b/forged/internal/unsorted/http_server.go
new file mode 100644
index 0000000..f6a1794
--- /dev/null
+++ b/forged/internal/unsorted/http_server.go
@@ -0,0 +1,276 @@
+// SPDX-License-Identifier: AGPL-3.0-only
+// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>
+
+package unsorted
+
+import (
+ "errors"
+ "log/slog"
+ "net/http"
+ "net/url"
+ "strconv"
+ "strings"
+
+ "github.com/jackc/pgx/v5"
+ "go.lindenii.runxiyu.org/forge/forged/internal/misc"
+ "go.lindenii.runxiyu.org/forge/forged/internal/web"
+)
+
+// ServeHTTP handles all incoming HTTP requests and routes them to the correct
+// location.
+//
+// TODO: This function is way too large.
+func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
+ var remoteAddr string
+ if s.config.HTTP.ReverseProxy {
+ remoteAddrs, ok := request.Header["X-Forwarded-For"]
+ if ok && len(remoteAddrs) == 1 {
+ remoteAddr = remoteAddrs[0]
+ } else {
+ remoteAddr = request.RemoteAddr
+ }
+ } else {
+ remoteAddr = request.RemoteAddr
+ }
+ slog.Info("incoming http", "addr", remoteAddr, "method", request.Method, "uri", request.RequestURI)
+
+ var segments []string
+ var err error
+ var sepIndex int
+ params := make(map[string]any)
+
+ if segments, _, err = misc.ParseReqURI(request.RequestURI); err != nil {
+ web.ErrorPage400(s.templates, writer, params, "Error parsing request URI: "+err.Error())
+ return
+ }
+ dirMode := false
+ if segments[len(segments)-1] == "" {
+ dirMode = true
+ segments = segments[:len(segments)-1]
+ }
+
+ params["url_segments"] = segments
+ params["dir_mode"] = dirMode
+ params["global"] = s.globalData
+ var userID int // 0 for none
+ userID, params["username"], err = s.getUserFromRequest(request)
+ params["user_id"] = userID
+ if err != nil && !errors.Is(err, http.ErrNoCookie) && !errors.Is(err, pgx.ErrNoRows) {
+ web.ErrorPage500(s.templates, writer, params, "Error getting user info from request: "+err.Error())
+ return
+ }
+
+ if userID == 0 {
+ params["user_id_string"] = ""
+ } else {
+ params["user_id_string"] = strconv.Itoa(userID)
+ }
+
+ for _, v := range segments {
+ if strings.Contains(v, ":") {
+ web.ErrorPage400Colon(s.templates, writer, params)
+ return
+ }
+ }
+
+ if len(segments) == 0 {
+ s.httpHandleIndex(writer, request, params)
+ return
+ }
+
+ if segments[0] == "-" {
+ if len(segments) < 2 {
+ web.ErrorPage404(s.templates, writer, params)
+ return
+ } else if len(segments) == 2 && misc.RedirectDir(writer, request) {
+ return
+ }
+
+ switch segments[1] {
+ case "static":
+ s.staticHandler.ServeHTTP(writer, request)
+ return
+ case "source":
+ s.sourceHandler.ServeHTTP(writer, request)
+ return
+ }
+ }
+
+ if segments[0] == "-" {
+ switch segments[1] {
+ case "login":
+ s.httpHandleLogin(writer, request, params)
+ return
+ case "users":
+ s.httpHandleUsers(writer, request, params)
+ return
+ default:
+ web.ErrorPage404(s.templates, writer, params)
+ return
+ }
+ }
+
+ sepIndex = -1
+ for i, part := range segments {
+ if part == "-" {
+ sepIndex = i
+ break
+ }
+ }
+
+ params["separator_index"] = sepIndex
+
+ var groupPath []string
+ var moduleType string
+ var moduleName string
+
+ if sepIndex > 0 {
+ groupPath = segments[:sepIndex]
+ } else {
+ groupPath = segments
+ }
+ params["group_path"] = groupPath
+
+ switch {
+ case sepIndex == -1:
+ if misc.RedirectDir(writer, request) {
+ return
+ }
+ s.httpHandleGroupIndex(writer, request, params)
+ case len(segments) == sepIndex+1:
+ web.ErrorPage404(s.templates, writer, params)
+ return
+ case len(segments) == sepIndex+2:
+ web.ErrorPage404(s.templates, writer, params)
+ return
+ default:
+ moduleType = segments[sepIndex+1]
+ moduleName = segments[sepIndex+2]
+ switch moduleType {
+ case "repos":
+ params["repo_name"] = moduleName
+
+ if len(segments) > sepIndex+3 {
+ switch segments[sepIndex+3] {
+ case "info":
+ if err = s.httpHandleRepoInfo(writer, request, params); err != nil {
+ web.ErrorPage500(s.templates, writer, params, err.Error())
+ }
+ return
+ case "git-upload-pack":
+ if err = s.httpHandleUploadPack(writer, request, params); err != nil {
+ web.ErrorPage500(s.templates, writer, params, err.Error())
+ }
+ return
+ }
+ }
+
+ if params["ref_type"], params["ref_name"], err = misc.GetParamRefTypeName(request); err != nil {
+ if errors.Is(err, misc.ErrNoRefSpec) {
+ params["ref_type"] = ""
+ } else {
+ web.ErrorPage400(s.templates, writer, params, "Error querying ref type: "+err.Error())
+ return
+ }
+ }
+
+ if params["repo"], params["repo_description"], params["repo_id"], _, err = s.openRepo(request.Context(), groupPath, moduleName); err != nil {
+ web.ErrorPage500(s.templates, writer, params, "Error opening repo: "+err.Error())
+ return
+ }
+
+ repoURLRoot := "/"
+ for _, part := range segments[:sepIndex+3] {
+ repoURLRoot = repoURLRoot + url.PathEscape(part) + "/"
+ }
+ params["repo_url_root"] = repoURLRoot
+ params["repo_patch_mailing_list"] = repoURLRoot[1:len(repoURLRoot)-1] + "@" + s.config.LMTP.Domain
+ params["http_clone_url"] = s.genHTTPRemoteURL(groupPath, moduleName)
+ params["ssh_clone_url"] = s.genSSHRemoteURL(groupPath, moduleName)
+
+ if len(segments) == sepIndex+3 {
+ if misc.RedirectDir(writer, request) {
+ return
+ }
+ s.httpHandleRepoIndex(writer, request, params)
+ return
+ }
+
+ repoFeature := segments[sepIndex+3]
+ switch repoFeature {
+ case "tree":
+ if misc.AnyContain(segments[sepIndex+4:], "/") {
+ web.ErrorPage400(s.templates, writer, params, "Repo tree paths may not contain slashes in any segments")
+ return
+ }
+ if dirMode {
+ params["rest"] = strings.Join(segments[sepIndex+4:], "/") + "/"
+ } else {
+ params["rest"] = strings.Join(segments[sepIndex+4:], "/")
+ }
+ if len(segments) < sepIndex+5 && misc.RedirectDir(writer, request) {
+ return
+ }
+ s.httpHandleRepoTree(writer, request, params)
+ case "branches":
+ if misc.RedirectDir(writer, request) {
+ return
+ }
+ s.httpHandleRepoBranches(writer, request, params)
+ return
+ case "raw":
+ if misc.AnyContain(segments[sepIndex+4:], "/") {
+ web.ErrorPage400(s.templates, writer, params, "Repo tree paths may not contain slashes in any segments")
+ return
+ }
+ if dirMode {
+ params["rest"] = strings.Join(segments[sepIndex+4:], "/") + "/"
+ } else {
+ params["rest"] = strings.Join(segments[sepIndex+4:], "/")
+ }
+ if len(segments) < sepIndex+5 && misc.RedirectDir(writer, request) {
+ return
+ }
+ s.httpHandleRepoRaw(writer, request, params)
+ case "log":
+ if len(segments) > sepIndex+4 {
+ web.ErrorPage400(s.templates, writer, params, "Too many parameters")
+ return
+ }
+ if misc.RedirectDir(writer, request) {
+ return
+ }
+ s.httpHandleRepoLog(writer, request, params)
+ case "commit":
+ if len(segments) != sepIndex+5 {
+ web.ErrorPage400(s.templates, writer, params, "Incorrect number of parameters")
+ return
+ }
+ if misc.RedirectNoDir(writer, request) {
+ return
+ }
+ params["commit_id"] = segments[sepIndex+4]
+ s.httpHandleRepoCommit(writer, request, params)
+ case "contrib":
+ if misc.RedirectDir(writer, request) {
+ return
+ }
+ switch len(segments) {
+ case sepIndex + 4:
+ s.httpHandleRepoContribIndex(writer, request, params)
+ case sepIndex + 5:
+ params["mr_id"] = segments[sepIndex+4]
+ s.httpHandleRepoContribOne(writer, request, params)
+ default:
+ web.ErrorPage400(s.templates, writer, params, "Too many parameters")
+ }
+ default:
+ web.ErrorPage404(s.templates, writer, params)
+ return
+ }
+ default:
+ web.ErrorPage404(s.templates, writer, params)
+ return
+ }
+ }
+}