From d87f80d3e14990e5d9fc83990ff04e29affa3a94 Mon Sep 17 00:00:00 2001 From: Runxi Yu Date: Thu, 13 Feb 2025 00:31:48 +0800 Subject: *.go: Mass rename files for clarity --- git_ref.go | 46 +++++++++++++++++ global.go | 6 --- handle_group_index.go | 42 --------------- handle_index.go | 40 --------------- handle_repo_commit.go | 112 ---------------------------------------- handle_repo_index.go | 52 ------------------- handle_repo_info.go | 10 ---- handle_repo_log.go | 38 -------------- handle_repo_raw.go | 90 -------------------------------- handle_repo_tree.go | 115 ----------------------------------------- http_global.go | 6 +++ http_handle_group_index.go | 42 +++++++++++++++ http_handle_index.go | 40 +++++++++++++++ http_handle_repo_commit.go | 112 ++++++++++++++++++++++++++++++++++++++++ http_handle_repo_index.go | 52 +++++++++++++++++++ http_handle_repo_info.go | 10 ++++ http_handle_repo_log.go | 38 ++++++++++++++ http_handle_repo_raw.go | 90 ++++++++++++++++++++++++++++++++ http_handle_repo_tree.go | 115 +++++++++++++++++++++++++++++++++++++++++ http_render_readme.go | 61 ++++++++++++++++++++++ http_server.go | 125 +++++++++++++++++++++++++++++++++++++++++++++ http_template_funcs.go | 15 ++++++ ref.go | 46 ----------------- render_readme.go | 61 ---------------------- router_http.go | 125 --------------------------------------------- router_ssh.go | 56 -------------------- ssh.go | 96 ---------------------------------- ssh_server.go | 56 ++++++++++++++++++++ ssh_utils.go | 96 ++++++++++++++++++++++++++++++++++ template_funcs.go | 15 ------ url.go | 65 +++++++++++++++++++++++ url_misc.go | 65 ----------------------- 32 files changed, 969 insertions(+), 969 deletions(-) create mode 100644 git_ref.go delete mode 100644 global.go delete mode 100644 handle_group_index.go delete mode 100644 handle_index.go delete mode 100644 handle_repo_commit.go delete mode 100644 handle_repo_index.go delete mode 100644 handle_repo_info.go delete mode 100644 handle_repo_log.go delete mode 100644 handle_repo_raw.go delete mode 100644 handle_repo_tree.go create mode 100644 http_global.go create mode 100644 http_handle_group_index.go create mode 100644 http_handle_index.go create mode 100644 http_handle_repo_commit.go create mode 100644 http_handle_repo_index.go create mode 100644 http_handle_repo_info.go create mode 100644 http_handle_repo_log.go create mode 100644 http_handle_repo_raw.go create mode 100644 http_handle_repo_tree.go create mode 100644 http_render_readme.go create mode 100644 http_server.go create mode 100644 http_template_funcs.go delete mode 100644 ref.go delete mode 100644 render_readme.go delete mode 100644 router_http.go delete mode 100644 router_ssh.go delete mode 100644 ssh.go create mode 100644 ssh_server.go create mode 100644 ssh_utils.go delete mode 100644 template_funcs.go create mode 100644 url.go delete mode 100644 url_misc.go diff --git a/git_ref.go b/git_ref.go new file mode 100644 index 0000000..08d757b --- /dev/null +++ b/git_ref.go @@ -0,0 +1,46 @@ +package main + +import ( + "errors" + + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing" + "go.lindenii.runxiyu.org/lindenii-common/misc" +) + +var ( + err_getting_tag_reference = errors.New("Error getting tag reference") + err_getting_branch_reference = errors.New("Error getting branch reference") + err_getting_head = errors.New("Error getting HEAD") +) + +func get_ref_hash_from_type_and_name(repo *git.Repository, ref_type, ref_name string) (ref_hash plumbing.Hash, ret_err error) { + switch ref_type { + case "head": + head, err := repo.Head() + if err != nil { + ret_err = misc.Wrap_one_error(err_getting_head, err) + return + } + ref_hash = head.Hash() + case "commit": + ref_hash = plumbing.NewHash(ref_name) + case "branch": + ref, err := repo.Reference(plumbing.NewBranchReferenceName(ref_name), true) + if err != nil { + ret_err = misc.Wrap_one_error(err_getting_branch_reference, err) + return + } + ref_hash = ref.Hash() + case "tag": + ref, err := repo.Reference(plumbing.NewTagReferenceName(ref_name), true) + if err != nil { + ret_err = misc.Wrap_one_error(err_getting_tag_reference, err) + return + } + ref_hash = ref.Hash() + default: + panic("Invalid ref type " + ref_type) + } + return +} diff --git a/global.go b/global.go deleted file mode 100644 index 3e06171..0000000 --- a/global.go +++ /dev/null @@ -1,6 +0,0 @@ -package main - -var global_data = map[string]any { - "server_public_key_string": &server_public_key_string, - "server_public_key_fingerprint": &server_public_key_fingerprint, -} diff --git a/handle_group_index.go b/handle_group_index.go deleted file mode 100644 index 2a8e1ca..0000000 --- a/handle_group_index.go +++ /dev/null @@ -1,42 +0,0 @@ -package main - -import ( - "net/http" -) - -func handle_group_repos(w http.ResponseWriter, r *http.Request, params map[string]string) { - data := make(map[string]any) - data["global"] = global_data - group_name := params["group_name"] - data["group_name"] = group_name - - var names []string - rows, err := database.Query(r.Context(), "SELECT r.name FROM repos r JOIN groups g ON r.group_id = g.id WHERE g.name = $1;", group_name) - if err != nil { - _, _ = w.Write([]byte("Error getting groups: " + err.Error())) - return - } - defer rows.Close() - - for rows.Next() { - var name string - if err := rows.Scan(&name); err != nil { - _, _ = w.Write([]byte("Error scanning row: " + err.Error())) - return - } - names = append(names, name) - } - - if err := rows.Err(); err != nil { - _, _ = w.Write([]byte("Error iterating over rows: " + err.Error())) - return - } - - data["repos"] = names - - err = templates.ExecuteTemplate(w, "group_repos", data) - if err != nil { - _, _ = w.Write([]byte("Error rendering template: " + err.Error())) - return - } -} diff --git a/handle_index.go b/handle_index.go deleted file mode 100644 index bf36f98..0000000 --- a/handle_index.go +++ /dev/null @@ -1,40 +0,0 @@ -package main - -import ( - "net/http" -) - -func handle_index(w http.ResponseWriter, r *http.Request) { - data := make(map[string]any) - data["global"] = global_data - - rows, err := database.Query(r.Context(), "SELECT name FROM groups") - if err != nil { - _, _ = w.Write([]byte("Error querying groups: " + err.Error())) - return - } - defer rows.Close() - - groups := []string{} - for rows.Next() { - var groupName string - if err := rows.Scan(&groupName); err != nil { - _, _ = w.Write([]byte("Error scanning group name: " + err.Error())) - return - } - groups = append(groups, groupName) - } - - if err := rows.Err(); err != nil { - _, _ = w.Write([]byte("Error iterating over rows: " + err.Error())) - return - } - - data["groups"] = groups - - err = templates.ExecuteTemplate(w, "index", data) - if err != nil { - _, _ = w.Write([]byte("Error rendering template: " + err.Error())) - return - } -} diff --git a/handle_repo_commit.go b/handle_repo_commit.go deleted file mode 100644 index d79b088..0000000 --- a/handle_repo_commit.go +++ /dev/null @@ -1,112 +0,0 @@ -package main - -import ( - "net/http" - "strings" - - "github.com/go-git/go-git/v5/plumbing" - "github.com/go-git/go-git/v5/plumbing/filemode" - "github.com/go-git/go-git/v5/plumbing/format/diff" - "go.lindenii.runxiyu.org/lindenii-common/misc" -) - -type usable_file_patch struct { - From diff.File - To diff.File - Chunks []diff.Chunk -} - -func handle_repo_commit(w http.ResponseWriter, r *http.Request, params map[string]string) { - data := make(map[string]any) - data["global"] = global_data - group_name, repo_name, commit_id_specified_string := params["group_name"], params["repo_name"], params["commit_id"] - data["group_name"], data["repo_name"] = group_name, repo_name - repo, err := open_git_repo(r.Context(), group_name, repo_name) - if err != nil { - _, _ = w.Write([]byte("Error opening repo: " + err.Error())) - return - } - commit_id_specified_string_without_suffix := strings.TrimSuffix(commit_id_specified_string, ".patch") - commit_id := plumbing.NewHash(commit_id_specified_string_without_suffix) - commit_object, err := repo.CommitObject(commit_id) - if err != nil { - _, _ = w.Write([]byte("Error getting commit object: " + err.Error())) - return - } - if commit_id_specified_string_without_suffix != commit_id_specified_string { - patch, err := format_patch_from_commit(commit_object) - if err != nil { - _, _ = w.Write([]byte("Error formatting patch: " + err.Error())) - return - } - _, _ = w.Write([]byte(patch)) - return - } - commit_id_string := commit_object.Hash.String() - - if commit_id_string != commit_id_specified_string { - http.Redirect(w, r, commit_id_string, http.StatusSeeOther) - return - } - - data["commit_object"] = commit_object - data["commit_id"] = commit_id_string - - parent_commit_hash, patch, err := get_patch_from_commit(commit_object) - if err != nil { - _, _ = w.Write([]byte("Error getting patch from commit: " + err.Error())) - return - } - data["parent_commit_hash"] = parent_commit_hash.String() - data["patch"] = patch - - // TODO: Remove unnecessary context - // TODO: Prepend "+"/"-"/" " instead of solely distinguishing based on color - usable_file_patches := make([]usable_file_patch, 0) - for _, file_patch := range patch.FilePatches() { - from, to := file_patch.Files() - if from == nil { - from = fake_diff_file_null - } - if to == nil { - to = fake_diff_file_null - } - usable_file_patch := usable_file_patch{ - Chunks: file_patch.Chunks(), - From: from, - To: to, - } - usable_file_patches = append(usable_file_patches, usable_file_patch) - } - data["file_patches"] = usable_file_patches - - err = templates.ExecuteTemplate(w, "repo_commit", data) - if err != nil { - _, _ = w.Write([]byte("Error rendering template: " + err.Error())) - return - } -} - -type fake_diff_file struct { - hash plumbing.Hash - mode filemode.FileMode - path string -} - -func (f fake_diff_file) Hash() plumbing.Hash { - return f.hash -} - -func (f fake_diff_file) Mode() filemode.FileMode { - return f.mode -} - -func (f fake_diff_file) Path() string { - return f.path -} - -var fake_diff_file_null = fake_diff_file{ - hash: plumbing.NewHash("e69de29bb2d1d6434b8b29ae775ad8c2e48c5391"), - mode: misc.First_or_panic(filemode.New("100644")), - path: "", -} diff --git a/handle_repo_index.go b/handle_repo_index.go deleted file mode 100644 index 0c7b570..0000000 --- a/handle_repo_index.go +++ /dev/null @@ -1,52 +0,0 @@ -package main - -import ( - "net/http" - "net/url" -) - -func handle_repo_index(w http.ResponseWriter, r *http.Request, params map[string]string) { - data := make(map[string]any) - data["global"] = global_data - group_name, repo_name := params["group_name"], params["repo_name"] - data["group_name"], data["repo_name"] = group_name, repo_name - repo, err := open_git_repo(r.Context(), group_name, repo_name) - if err != nil { - _, _ = w.Write([]byte("Error opening repo: " + err.Error())) - return - } - head, err := repo.Head() - if err != nil { - _, _ = w.Write([]byte("Error getting repo HEAD: " + err.Error())) - return - } - data["ref"] = head.Name().Short() - head_hash := head.Hash() - recent_commits, err := get_recent_commits(repo, head_hash, 3) - if err != nil { - _, _ = w.Write([]byte("Error getting recent commits: " + err.Error())) - return - } - data["commits"] = recent_commits - commit_object, err := repo.CommitObject(head_hash) - if err != nil { - _, _ = w.Write([]byte("Error getting commit object: " + err.Error())) - return - } - tree, err := commit_object.Tree() - if err != nil { - _, _ = w.Write([]byte("Error getting file tree: " + err.Error())) - return - } - - data["readme_filename"], data["readme"] = render_readme_at_tree(tree) - data["files"] = build_display_git_tree(tree) - - data["clone_url"] = "ssh://" + r.Host + "/" + url.PathEscape(params["group_name"]) + "/:/repos/" + url.PathEscape(params["repo_name"]) - - err = templates.ExecuteTemplate(w, "repo_index", data) - if err != nil { - _, _ = w.Write([]byte("Error rendering template: " + err.Error())) - return - } -} diff --git a/handle_repo_info.go b/handle_repo_info.go deleted file mode 100644 index 5dd92e9..0000000 --- a/handle_repo_info.go +++ /dev/null @@ -1,10 +0,0 @@ -package main - -import ( - "net/http" - "net/url" -) - -func handle_repo_info(w http.ResponseWriter, r *http.Request, params map[string]string) { - http.Error(w, "\x1b[1;93mHi! We do not support Git operations over HTTP yet.\x1b[0m\n\x1b[1;93mMeanwhile, please use ssh by simply replacing the scheme with \"ssh://\":\x1b[0m\n\x1b[1;93mssh://"+r.Host+"/"+url.PathEscape(params["group_name"])+"/:/repos/"+url.PathEscape(params["repo_name"])+"\x1b[0m", http.StatusNotImplemented) -} diff --git a/handle_repo_log.go b/handle_repo_log.go deleted file mode 100644 index 6a3f446..0000000 --- a/handle_repo_log.go +++ /dev/null @@ -1,38 +0,0 @@ -package main - -import ( - "net/http" - - "github.com/go-git/go-git/v5/plumbing" -) - -// TODO: I probably shouldn't include *all* commits here... -func handle_repo_log(w http.ResponseWriter, r *http.Request, params map[string]string) { - data := make(map[string]any) - data["global"] = global_data - group_name, repo_name, ref_name := params["group_name"], params["repo_name"], params["ref"] - data["group_name"], data["repo_name"], data["ref"] = group_name, repo_name, ref_name - repo, err := open_git_repo(r.Context(), group_name, repo_name) - if err != nil { - _, _ = w.Write([]byte("Error opening repo: " + err.Error())) - return - } - ref, err := repo.Reference(plumbing.NewBranchReferenceName(ref_name), true) - if err != nil { - _, _ = w.Write([]byte("Error getting repo reference: " + err.Error())) - return - } - ref_hash := ref.Hash() - commits, err := get_recent_commits(repo, ref_hash, -1) - if err != nil { - _, _ = w.Write([]byte("Error getting recent commits: " + err.Error())) - return - } - data["commits"] = commits - - err = templates.ExecuteTemplate(w, "repo_log", data) - if err != nil { - _, _ = w.Write([]byte("Error rendering template: " + err.Error())) - return - } -} diff --git a/handle_repo_raw.go b/handle_repo_raw.go deleted file mode 100644 index 24b5794..0000000 --- a/handle_repo_raw.go +++ /dev/null @@ -1,90 +0,0 @@ -package main - -import ( - "errors" - "net/http" - "path" - "strings" - - "github.com/go-git/go-git/v5/plumbing/object" -) - -func handle_repo_raw(w http.ResponseWriter, r *http.Request, params map[string]string) { - data := make(map[string]any) - data["global"] = global_data - raw_path_spec := params["rest"] - group_name, repo_name, path_spec := params["group_name"], params["repo_name"], strings.TrimSuffix(raw_path_spec, "/") - - ref_type, ref_name, err := get_param_ref_and_type(r) - if err != nil { - if errors.Is(err, err_no_ref_spec) { - ref_type = "head" - } else { - _, _ = w.Write([]byte("Error querying ref type: " + err.Error())) - return - } - } - - data["ref_type"], data["ref"], data["group_name"], data["repo_name"], data["path_spec"] = ref_type, ref_name, group_name, repo_name, path_spec - - repo, err := open_git_repo(r.Context(), group_name, repo_name) - if err != nil { - _, _ = w.Write([]byte("Error opening repo: " + err.Error())) - return - } - - ref_hash, err := get_ref_hash_from_type_and_name(repo, ref_type, ref_name) - if err != nil { - _, _ = w.Write([]byte("Error getting ref hash: " + err.Error())) - return - } - - commit_object, err := repo.CommitObject(ref_hash) - if err != nil { - _, _ = w.Write([]byte("Error getting commit object: " + err.Error())) - return - } - tree, err := commit_object.Tree() - if err != nil { - _, _ = w.Write([]byte("Error getting file tree: " + err.Error())) - return - } - - var target *object.Tree - if path_spec == "" { - target = tree - } else { - target, err = tree.Tree(path_spec) - if err != nil { - file, err := tree.File(path_spec) - if err != nil { - _, _ = w.Write([]byte("Error retrieving path: " + err.Error())) - return - } - if len(raw_path_spec) != 0 && raw_path_spec[len(raw_path_spec)-1] == '/' { - http.Redirect(w, r, "../"+path_spec, http.StatusSeeOther) - return - } - file_contents, err := file.Contents() - if err != nil { - _, _ = w.Write([]byte("Error reading file: " + err.Error())) - return - } - _, _ = w.Write([]byte(file_contents)) - return - } - } - - if len(raw_path_spec) != 0 && raw_path_spec[len(raw_path_spec)-1] != '/' { - http.Redirect(w, r, path.Base(path_spec)+"/", http.StatusSeeOther) - return - } - - data["files"] = build_display_git_tree(target) - - err = templates.ExecuteTemplate(w, "repo_raw_dir", data) - if err != nil { - _, _ = w.Write([]byte("Error rendering template: " + err.Error())) - return - } -} diff --git a/handle_repo_tree.go b/handle_repo_tree.go deleted file mode 100644 index 1dc06a8..0000000 --- a/handle_repo_tree.go +++ /dev/null @@ -1,115 +0,0 @@ -package main - -import ( - "bytes" - "errors" - "html/template" - "net/http" - "path" - "strings" - - chroma_formatters_html "github.com/alecthomas/chroma/v2/formatters/html" - chroma_lexers "github.com/alecthomas/chroma/v2/lexers" - chroma_styles "github.com/alecthomas/chroma/v2/styles" - "github.com/go-git/go-git/v5/plumbing/object" -) - -func handle_repo_tree(w http.ResponseWriter, r *http.Request, params map[string]string) { - data := make(map[string]any) - data["global"] = global_data - raw_path_spec := params["rest"] - group_name, repo_name, path_spec := params["group_name"], params["repo_name"], strings.TrimSuffix(raw_path_spec, "/") - ref_type, ref_name, err := get_param_ref_and_type(r) - if err != nil { - if errors.Is(err, err_no_ref_spec) { - ref_type = "head" - } else { - _, _ = w.Write([]byte("Error querying ref type: " + err.Error())) - return - } - } - data["ref_type"], data["ref"], data["group_name"], data["repo_name"], data["path_spec"] = ref_type, ref_name, group_name, repo_name, path_spec - repo, err := open_git_repo(r.Context(), group_name, repo_name) - if err != nil { - _, _ = w.Write([]byte("Error opening repo: " + err.Error())) - return - } - ref_hash, err := get_ref_hash_from_type_and_name(repo, ref_type, ref_name) - if err != nil { - _, _ = w.Write([]byte("Error getting ref hash: " + err.Error())) - return - } - commit_object, err := repo.CommitObject(ref_hash) - if err != nil { - _, _ = w.Write([]byte("Error getting commit object: " + err.Error())) - return - } - tree, err := commit_object.Tree() - if err != nil { - _, _ = w.Write([]byte("Error getting file tree: " + err.Error())) - return - } - - var target *object.Tree - if path_spec == "" { - target = tree - } else { - target, err = tree.Tree(path_spec) - if err != nil { - file, err := tree.File(path_spec) - if err != nil { - _, _ = w.Write([]byte("Error retrieving path: " + err.Error())) - return - } - if len(raw_path_spec) != 0 && raw_path_spec[len(raw_path_spec)-1] == '/' { - http.Redirect(w, r, "../"+path_spec, http.StatusSeeOther) - return - } - file_contents, err := file.Contents() - if err != nil { - _, _ = w.Write([]byte("Error reading file: " + err.Error())) - return - } - lexer := chroma_lexers.Match(path_spec) - if lexer == nil { - lexer = chroma_lexers.Fallback - } - iterator, err := lexer.Tokenise(nil, file_contents) - if err != nil { - _, _ = w.Write([]byte("Error tokenizing code: " + err.Error())) - return - } - var formatted_unencapsulated bytes.Buffer - style := chroma_styles.Get("autumn") - formatter := chroma_formatters_html.New(chroma_formatters_html.WithClasses(true), chroma_formatters_html.TabWidth(8)) - err = formatter.Format(&formatted_unencapsulated, style, iterator) - if err != nil { - _, _ = w.Write([]byte("Error formatting code: " + err.Error())) - return - } - formatted_encapsulated := template.HTML(formatted_unencapsulated.Bytes()) - data["file_contents"] = formatted_encapsulated - - err = templates.ExecuteTemplate(w, "repo_tree_file", data) - if err != nil { - _, _ = w.Write([]byte("Error rendering template: " + err.Error())) - return - } - return - } - } - - if len(raw_path_spec) != 0 && raw_path_spec[len(raw_path_spec)-1] != '/' { - http.Redirect(w, r, path.Base(path_spec)+"/", http.StatusSeeOther) - return - } - - data["readme_filename"], data["readme"] = render_readme_at_tree(target) - data["files"] = build_display_git_tree(target) - - err = templates.ExecuteTemplate(w, "repo_tree_dir", data) - if err != nil { - _, _ = w.Write([]byte("Error rendering template: " + err.Error())) - return - } -} diff --git a/http_global.go b/http_global.go new file mode 100644 index 0000000..3e06171 --- /dev/null +++ b/http_global.go @@ -0,0 +1,6 @@ +package main + +var global_data = map[string]any { + "server_public_key_string": &server_public_key_string, + "server_public_key_fingerprint": &server_public_key_fingerprint, +} diff --git a/http_handle_group_index.go b/http_handle_group_index.go new file mode 100644 index 0000000..2a8e1ca --- /dev/null +++ b/http_handle_group_index.go @@ -0,0 +1,42 @@ +package main + +import ( + "net/http" +) + +func handle_group_repos(w http.ResponseWriter, r *http.Request, params map[string]string) { + data := make(map[string]any) + data["global"] = global_data + group_name := params["group_name"] + data["group_name"] = group_name + + var names []string + rows, err := database.Query(r.Context(), "SELECT r.name FROM repos r JOIN groups g ON r.group_id = g.id WHERE g.name = $1;", group_name) + if err != nil { + _, _ = w.Write([]byte("Error getting groups: " + err.Error())) + return + } + defer rows.Close() + + for rows.Next() { + var name string + if err := rows.Scan(&name); err != nil { + _, _ = w.Write([]byte("Error scanning row: " + err.Error())) + return + } + names = append(names, name) + } + + if err := rows.Err(); err != nil { + _, _ = w.Write([]byte("Error iterating over rows: " + err.Error())) + return + } + + data["repos"] = names + + err = templates.ExecuteTemplate(w, "group_repos", data) + if err != nil { + _, _ = w.Write([]byte("Error rendering template: " + err.Error())) + return + } +} diff --git a/http_handle_index.go b/http_handle_index.go new file mode 100644 index 0000000..bf36f98 --- /dev/null +++ b/http_handle_index.go @@ -0,0 +1,40 @@ +package main + +import ( + "net/http" +) + +func handle_index(w http.ResponseWriter, r *http.Request) { + data := make(map[string]any) + data["global"] = global_data + + rows, err := database.Query(r.Context(), "SELECT name FROM groups") + if err != nil { + _, _ = w.Write([]byte("Error querying groups: " + err.Error())) + return + } + defer rows.Close() + + groups := []string{} + for rows.Next() { + var groupName string + if err := rows.Scan(&groupName); err != nil { + _, _ = w.Write([]byte("Error scanning group name: " + err.Error())) + return + } + groups = append(groups, groupName) + } + + if err := rows.Err(); err != nil { + _, _ = w.Write([]byte("Error iterating over rows: " + err.Error())) + return + } + + data["groups"] = groups + + err = templates.ExecuteTemplate(w, "index", data) + if err != nil { + _, _ = w.Write([]byte("Error rendering template: " + err.Error())) + return + } +} diff --git a/http_handle_repo_commit.go b/http_handle_repo_commit.go new file mode 100644 index 0000000..d79b088 --- /dev/null +++ b/http_handle_repo_commit.go @@ -0,0 +1,112 @@ +package main + +import ( + "net/http" + "strings" + + "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/plumbing/filemode" + "github.com/go-git/go-git/v5/plumbing/format/diff" + "go.lindenii.runxiyu.org/lindenii-common/misc" +) + +type usable_file_patch struct { + From diff.File + To diff.File + Chunks []diff.Chunk +} + +func handle_repo_commit(w http.ResponseWriter, r *http.Request, params map[string]string) { + data := make(map[string]any) + data["global"] = global_data + group_name, repo_name, commit_id_specified_string := params["group_name"], params["repo_name"], params["commit_id"] + data["group_name"], data["repo_name"] = group_name, repo_name + repo, err := open_git_repo(r.Context(), group_name, repo_name) + if err != nil { + _, _ = w.Write([]byte("Error opening repo: " + err.Error())) + return + } + commit_id_specified_string_without_suffix := strings.TrimSuffix(commit_id_specified_string, ".patch") + commit_id := plumbing.NewHash(commit_id_specified_string_without_suffix) + commit_object, err := repo.CommitObject(commit_id) + if err != nil { + _, _ = w.Write([]byte("Error getting commit object: " + err.Error())) + return + } + if commit_id_specified_string_without_suffix != commit_id_specified_string { + patch, err := format_patch_from_commit(commit_object) + if err != nil { + _, _ = w.Write([]byte("Error formatting patch: " + err.Error())) + return + } + _, _ = w.Write([]byte(patch)) + return + } + commit_id_string := commit_object.Hash.String() + + if commit_id_string != commit_id_specified_string { + http.Redirect(w, r, commit_id_string, http.StatusSeeOther) + return + } + + data["commit_object"] = commit_object + data["commit_id"] = commit_id_string + + parent_commit_hash, patch, err := get_patch_from_commit(commit_object) + if err != nil { + _, _ = w.Write([]byte("Error getting patch from commit: " + err.Error())) + return + } + data["parent_commit_hash"] = parent_commit_hash.String() + data["patch"] = patch + + // TODO: Remove unnecessary context + // TODO: Prepend "+"/"-"/" " instead of solely distinguishing based on color + usable_file_patches := make([]usable_file_patch, 0) + for _, file_patch := range patch.FilePatches() { + from, to := file_patch.Files() + if from == nil { + from = fake_diff_file_null + } + if to == nil { + to = fake_diff_file_null + } + usable_file_patch := usable_file_patch{ + Chunks: file_patch.Chunks(), + From: from, + To: to, + } + usable_file_patches = append(usable_file_patches, usable_file_patch) + } + data["file_patches"] = usable_file_patches + + err = templates.ExecuteTemplate(w, "repo_commit", data) + if err != nil { + _, _ = w.Write([]byte("Error rendering template: " + err.Error())) + return + } +} + +type fake_diff_file struct { + hash plumbing.Hash + mode filemode.FileMode + path string +} + +func (f fake_diff_file) Hash() plumbing.Hash { + return f.hash +} + +func (f fake_diff_file) Mode() filemode.FileMode { + return f.mode +} + +func (f fake_diff_file) Path() string { + return f.path +} + +var fake_diff_file_null = fake_diff_file{ + hash: plumbing.NewHash("e69de29bb2d1d6434b8b29ae775ad8c2e48c5391"), + mode: misc.First_or_panic(filemode.New("100644")), + path: "", +} diff --git a/http_handle_repo_index.go b/http_handle_repo_index.go new file mode 100644 index 0000000..0c7b570 --- /dev/null +++ b/http_handle_repo_index.go @@ -0,0 +1,52 @@ +package main + +import ( + "net/http" + "net/url" +) + +func handle_repo_index(w http.ResponseWriter, r *http.Request, params map[string]string) { + data := make(map[string]any) + data["global"] = global_data + group_name, repo_name := params["group_name"], params["repo_name"] + data["group_name"], data["repo_name"] = group_name, repo_name + repo, err := open_git_repo(r.Context(), group_name, repo_name) + if err != nil { + _, _ = w.Write([]byte("Error opening repo: " + err.Error())) + return + } + head, err := repo.Head() + if err != nil { + _, _ = w.Write([]byte("Error getting repo HEAD: " + err.Error())) + return + } + data["ref"] = head.Name().Short() + head_hash := head.Hash() + recent_commits, err := get_recent_commits(repo, head_hash, 3) + if err != nil { + _, _ = w.Write([]byte("Error getting recent commits: " + err.Error())) + return + } + data["commits"] = recent_commits + commit_object, err := repo.CommitObject(head_hash) + if err != nil { + _, _ = w.Write([]byte("Error getting commit object: " + err.Error())) + return + } + tree, err := commit_object.Tree() + if err != nil { + _, _ = w.Write([]byte("Error getting file tree: " + err.Error())) + return + } + + data["readme_filename"], data["readme"] = render_readme_at_tree(tree) + data["files"] = build_display_git_tree(tree) + + data["clone_url"] = "ssh://" + r.Host + "/" + url.PathEscape(params["group_name"]) + "/:/repos/" + url.PathEscape(params["repo_name"]) + + err = templates.ExecuteTemplate(w, "repo_index", data) + if err != nil { + _, _ = w.Write([]byte("Error rendering template: " + err.Error())) + return + } +} diff --git a/http_handle_repo_info.go b/http_handle_repo_info.go new file mode 100644 index 0000000..5dd92e9 --- /dev/null +++ b/http_handle_repo_info.go @@ -0,0 +1,10 @@ +package main + +import ( + "net/http" + "net/url" +) + +func handle_repo_info(w http.ResponseWriter, r *http.Request, params map[string]string) { + http.Error(w, "\x1b[1;93mHi! We do not support Git operations over HTTP yet.\x1b[0m\n\x1b[1;93mMeanwhile, please use ssh by simply replacing the scheme with \"ssh://\":\x1b[0m\n\x1b[1;93mssh://"+r.Host+"/"+url.PathEscape(params["group_name"])+"/:/repos/"+url.PathEscape(params["repo_name"])+"\x1b[0m", http.StatusNotImplemented) +} diff --git a/http_handle_repo_log.go b/http_handle_repo_log.go new file mode 100644 index 0000000..6a3f446 --- /dev/null +++ b/http_handle_repo_log.go @@ -0,0 +1,38 @@ +package main + +import ( + "net/http" + + "github.com/go-git/go-git/v5/plumbing" +) + +// TODO: I probably shouldn't include *all* commits here... +func handle_repo_log(w http.ResponseWriter, r *http.Request, params map[string]string) { + data := make(map[string]any) + data["global"] = global_data + group_name, repo_name, ref_name := params["group_name"], params["repo_name"], params["ref"] + data["group_name"], data["repo_name"], data["ref"] = group_name, repo_name, ref_name + repo, err := open_git_repo(r.Context(), group_name, repo_name) + if err != nil { + _, _ = w.Write([]byte("Error opening repo: " + err.Error())) + return + } + ref, err := repo.Reference(plumbing.NewBranchReferenceName(ref_name), true) + if err != nil { + _, _ = w.Write([]byte("Error getting repo reference: " + err.Error())) + return + } + ref_hash := ref.Hash() + commits, err := get_recent_commits(repo, ref_hash, -1) + if err != nil { + _, _ = w.Write([]byte("Error getting recent commits: " + err.Error())) + return + } + data["commits"] = commits + + err = templates.ExecuteTemplate(w, "repo_log", data) + if err != nil { + _, _ = w.Write([]byte("Error rendering template: " + err.Error())) + return + } +} diff --git a/http_handle_repo_raw.go b/http_handle_repo_raw.go new file mode 100644 index 0000000..24b5794 --- /dev/null +++ b/http_handle_repo_raw.go @@ -0,0 +1,90 @@ +package main + +import ( + "errors" + "net/http" + "path" + "strings" + + "github.com/go-git/go-git/v5/plumbing/object" +) + +func handle_repo_raw(w http.ResponseWriter, r *http.Request, params map[string]string) { + data := make(map[string]any) + data["global"] = global_data + raw_path_spec := params["rest"] + group_name, repo_name, path_spec := params["group_name"], params["repo_name"], strings.TrimSuffix(raw_path_spec, "/") + + ref_type, ref_name, err := get_param_ref_and_type(r) + if err != nil { + if errors.Is(err, err_no_ref_spec) { + ref_type = "head" + } else { + _, _ = w.Write([]byte("Error querying ref type: " + err.Error())) + return + } + } + + data["ref_type"], data["ref"], data["group_name"], data["repo_name"], data["path_spec"] = ref_type, ref_name, group_name, repo_name, path_spec + + repo, err := open_git_repo(r.Context(), group_name, repo_name) + if err != nil { + _, _ = w.Write([]byte("Error opening repo: " + err.Error())) + return + } + + ref_hash, err := get_ref_hash_from_type_and_name(repo, ref_type, ref_name) + if err != nil { + _, _ = w.Write([]byte("Error getting ref hash: " + err.Error())) + return + } + + commit_object, err := repo.CommitObject(ref_hash) + if err != nil { + _, _ = w.Write([]byte("Error getting commit object: " + err.Error())) + return + } + tree, err := commit_object.Tree() + if err != nil { + _, _ = w.Write([]byte("Error getting file tree: " + err.Error())) + return + } + + var target *object.Tree + if path_spec == "" { + target = tree + } else { + target, err = tree.Tree(path_spec) + if err != nil { + file, err := tree.File(path_spec) + if err != nil { + _, _ = w.Write([]byte("Error retrieving path: " + err.Error())) + return + } + if len(raw_path_spec) != 0 && raw_path_spec[len(raw_path_spec)-1] == '/' { + http.Redirect(w, r, "../"+path_spec, http.StatusSeeOther) + return + } + file_contents, err := file.Contents() + if err != nil { + _, _ = w.Write([]byte("Error reading file: " + err.Error())) + return + } + _, _ = w.Write([]byte(file_contents)) + return + } + } + + if len(raw_path_spec) != 0 && raw_path_spec[len(raw_path_spec)-1] != '/' { + http.Redirect(w, r, path.Base(path_spec)+"/", http.StatusSeeOther) + return + } + + data["files"] = build_display_git_tree(target) + + err = templates.ExecuteTemplate(w, "repo_raw_dir", data) + if err != nil { + _, _ = w.Write([]byte("Error rendering template: " + err.Error())) + return + } +} diff --git a/http_handle_repo_tree.go b/http_handle_repo_tree.go new file mode 100644 index 0000000..1dc06a8 --- /dev/null +++ b/http_handle_repo_tree.go @@ -0,0 +1,115 @@ +package main + +import ( + "bytes" + "errors" + "html/template" + "net/http" + "path" + "strings" + + chroma_formatters_html "github.com/alecthomas/chroma/v2/formatters/html" + chroma_lexers "github.com/alecthomas/chroma/v2/lexers" + chroma_styles "github.com/alecthomas/chroma/v2/styles" + "github.com/go-git/go-git/v5/plumbing/object" +) + +func handle_repo_tree(w http.ResponseWriter, r *http.Request, params map[string]string) { + data := make(map[string]any) + data["global"] = global_data + raw_path_spec := params["rest"] + group_name, repo_name, path_spec := params["group_name"], params["repo_name"], strings.TrimSuffix(raw_path_spec, "/") + ref_type, ref_name, err := get_param_ref_and_type(r) + if err != nil { + if errors.Is(err, err_no_ref_spec) { + ref_type = "head" + } else { + _, _ = w.Write([]byte("Error querying ref type: " + err.Error())) + return + } + } + data["ref_type"], data["ref"], data["group_name"], data["repo_name"], data["path_spec"] = ref_type, ref_name, group_name, repo_name, path_spec + repo, err := open_git_repo(r.Context(), group_name, repo_name) + if err != nil { + _, _ = w.Write([]byte("Error opening repo: " + err.Error())) + return + } + ref_hash, err := get_ref_hash_from_type_and_name(repo, ref_type, ref_name) + if err != nil { + _, _ = w.Write([]byte("Error getting ref hash: " + err.Error())) + return + } + commit_object, err := repo.CommitObject(ref_hash) + if err != nil { + _, _ = w.Write([]byte("Error getting commit object: " + err.Error())) + return + } + tree, err := commit_object.Tree() + if err != nil { + _, _ = w.Write([]byte("Error getting file tree: " + err.Error())) + return + } + + var target *object.Tree + if path_spec == "" { + target = tree + } else { + target, err = tree.Tree(path_spec) + if err != nil { + file, err := tree.File(path_spec) + if err != nil { + _, _ = w.Write([]byte("Error retrieving path: " + err.Error())) + return + } + if len(raw_path_spec) != 0 && raw_path_spec[len(raw_path_spec)-1] == '/' { + http.Redirect(w, r, "../"+path_spec, http.StatusSeeOther) + return + } + file_contents, err := file.Contents() + if err != nil { + _, _ = w.Write([]byte("Error reading file: " + err.Error())) + return + } + lexer := chroma_lexers.Match(path_spec) + if lexer == nil { + lexer = chroma_lexers.Fallback + } + iterator, err := lexer.Tokenise(nil, file_contents) + if err != nil { + _, _ = w.Write([]byte("Error tokenizing code: " + err.Error())) + return + } + var formatted_unencapsulated bytes.Buffer + style := chroma_styles.Get("autumn") + formatter := chroma_formatters_html.New(chroma_formatters_html.WithClasses(true), chroma_formatters_html.TabWidth(8)) + err = formatter.Format(&formatted_unencapsulated, style, iterator) + if err != nil { + _, _ = w.Write([]byte("Error formatting code: " + err.Error())) + return + } + formatted_encapsulated := template.HTML(formatted_unencapsulated.Bytes()) + data["file_contents"] = formatted_encapsulated + + err = templates.ExecuteTemplate(w, "repo_tree_file", data) + if err != nil { + _, _ = w.Write([]byte("Error rendering template: " + err.Error())) + return + } + return + } + } + + if len(raw_path_spec) != 0 && raw_path_spec[len(raw_path_spec)-1] != '/' { + http.Redirect(w, r, path.Base(path_spec)+"/", http.StatusSeeOther) + return + } + + data["readme_filename"], data["readme"] = render_readme_at_tree(target) + data["files"] = build_display_git_tree(target) + + err = templates.ExecuteTemplate(w, "repo_tree_dir", data) + if err != nil { + _, _ = w.Write([]byte("Error rendering template: " + err.Error())) + return + } +} diff --git a/http_render_readme.go b/http_render_readme.go new file mode 100644 index 0000000..497b3ec --- /dev/null +++ b/http_render_readme.go @@ -0,0 +1,61 @@ +package main + +import ( + "bytes" + "html" + "html/template" + "strings" + + "github.com/go-git/go-git/v5/plumbing/object" + "github.com/microcosm-cc/bluemonday" + "github.com/niklasfasching/go-org/org" + "github.com/yuin/goldmark" + "github.com/yuin/goldmark/extension" +) + +var markdown_converter = goldmark.New(goldmark.WithExtensions(extension.GFM)) + +func render_readme_at_tree(tree *object.Tree) (readme_filename string, readme_content template.HTML) { + var readme_rendered_unsafe bytes.Buffer + + readme_file, err := tree.File("README") + if err == nil { + readme_file_contents, err := readme_file.Contents() + if err != nil { + return "Error fetching README", string_escape_html("Unable to fetch contents of README: " + err.Error()) + } + return "README", template.HTML("
" + html.EscapeString(readme_file_contents) + "
") + } + + readme_file, err = tree.File("README.md") + if err == nil { + readme_file_contents, err := readme_file.Contents() + if err != nil { + return "Error fetching README", string_escape_html("Unable to fetch contents of README: " + err.Error()) + } + err = markdown_converter.Convert([]byte(readme_file_contents), &readme_rendered_unsafe) + if err != nil { + return "Error fetching README", string_escape_html("Unable to render README: " + err.Error()) + } + return "README.md", template.HTML(bluemonday.UGCPolicy().SanitizeBytes(readme_rendered_unsafe.Bytes())) + } + + readme_file, err = tree.File("README.org") + if err == nil { + readme_file_contents, err := readme_file.Contents() + if err != nil { + return "Error fetching README", string_escape_html("Unable to fetch contents of README: " + err.Error()) + } + org_html, err := org.New().Parse(strings.NewReader(readme_file_contents), readme_filename).Write(org.NewHTMLWriter()) + if err != nil { + return "Error fetching README", string_escape_html("Unable to render README: " + err.Error()) + } + return "README.org", template.HTML(bluemonday.UGCPolicy().Sanitize(org_html)) + } + + return "", "" +} + +func string_escape_html(s string) template.HTML { + return template.HTML(html.EscapeString(s)) +} diff --git a/http_server.go b/http_server.go new file mode 100644 index 0000000..fbf729b --- /dev/null +++ b/http_server.go @@ -0,0 +1,125 @@ +package main + +import ( + "errors" + "fmt" + "net/http" + "strings" +) + +type http_router_t struct{} + +func (router *http_router_t) ServeHTTP(w http.ResponseWriter, r *http.Request) { + segments, _, err := parse_request_uri(r.RequestURI) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + if segments[0] == ":" { + if len(segments) < 2 { + http.Error(w, "Blank system endpoint", http.StatusNotFound) + return + } + + switch segments[1] { + case "static": + static_handler.ServeHTTP(w, r) + case "source": + source_handler.ServeHTTP(w, r) + default: + http.Error(w, fmt.Sprintf("Unknown system module type: %s", segments[1]), http.StatusNotFound) + } + return + } + + separator_index := -1 + for i, part := range segments { + if part == ":" { + separator_index = i + break + } + } + non_empty_last_segments_len := len(segments) + dir_mode := false + if segments[len(segments)-1] == "" { + non_empty_last_segments_len-- + dir_mode = true + } + + params := make(map[string]string) + _ = params + switch { + case non_empty_last_segments_len == 0: + handle_index(w, r) + case separator_index == -1: + http.Error(w, "Group indexing hasn't been implemented yet", http.StatusNotImplemented) + case non_empty_last_segments_len == separator_index+1: + http.Error(w, "Group root hasn't been implemented yet", http.StatusNotImplemented) + case non_empty_last_segments_len == separator_index+2: + if !dir_mode { + http.Redirect(w, r, r.URL.Path+"/", http.StatusSeeOther) + return + } + module_type := segments[separator_index+1] + params["group_name"] = segments[0] + switch module_type { + case "repos": + handle_group_repos(w, r, params) + default: + http.Error(w, fmt.Sprintf("Unknown module type: %s", module_type), http.StatusNotFound) + } + default: + module_type := segments[separator_index+1] + module_name := segments[separator_index+2] + params["group_name"] = segments[0] + switch module_type { + case "repos": + params["repo_name"] = module_name + // TODO: subgroups + if non_empty_last_segments_len == separator_index+3 { + if !dir_mode { + http.Redirect(w, r, r.URL.Path+"/", http.StatusSeeOther) + return + } + handle_repo_index(w, r, params) + return + } + repo_feature := segments[separator_index+3] + switch repo_feature { + case "info": + handle_repo_info(w, r, params) + case "tree": + params["rest"] = strings.Join(segments[separator_index+4:], "/") + handle_repo_tree(w, r, params) + case "raw": + params["rest"] = strings.Join(segments[separator_index+4:], "/") + handle_repo_raw(w, r, params) + case "log": + if non_empty_last_segments_len != separator_index+5 { + http.Error(w, "Too many parameters", http.StatusBadRequest) + return + } + if dir_mode { + http.Redirect(w, r, strings.TrimSuffix(r.URL.Path, "/"), http.StatusSeeOther) + return + } + params["ref"] = segments[separator_index+4] + handle_repo_log(w, r, params) + case "commit": + if dir_mode { + http.Redirect(w, r, strings.TrimSuffix(r.URL.Path, "/"), http.StatusSeeOther) + return + } + params["commit_id"] = segments[separator_index+4] + handle_repo_commit(w, r, params) + default: + http.Error(w, fmt.Sprintf("Unknown repo feature: %s", repo_feature), http.StatusNotFound) + } + default: + http.Error(w, fmt.Sprintf("Unknown module type: %s", module_type), http.StatusNotFound) + } + } +} + +var err_bad_request = errors.New("Bad Request") diff --git a/http_template_funcs.go b/http_template_funcs.go new file mode 100644 index 0000000..19a92ec --- /dev/null +++ b/http_template_funcs.go @@ -0,0 +1,15 @@ +package main + +import ( + "path" + "strings" +) + +func first_line(s string) string { + before, _, _ := strings.Cut(s, "\n") + return before +} + +func base_name(s string) string { + return path.Base(s) +} diff --git a/ref.go b/ref.go deleted file mode 100644 index 08d757b..0000000 --- a/ref.go +++ /dev/null @@ -1,46 +0,0 @@ -package main - -import ( - "errors" - - "github.com/go-git/go-git/v5" - "github.com/go-git/go-git/v5/plumbing" - "go.lindenii.runxiyu.org/lindenii-common/misc" -) - -var ( - err_getting_tag_reference = errors.New("Error getting tag reference") - err_getting_branch_reference = errors.New("Error getting branch reference") - err_getting_head = errors.New("Error getting HEAD") -) - -func get_ref_hash_from_type_and_name(repo *git.Repository, ref_type, ref_name string) (ref_hash plumbing.Hash, ret_err error) { - switch ref_type { - case "head": - head, err := repo.Head() - if err != nil { - ret_err = misc.Wrap_one_error(err_getting_head, err) - return - } - ref_hash = head.Hash() - case "commit": - ref_hash = plumbing.NewHash(ref_name) - case "branch": - ref, err := repo.Reference(plumbing.NewBranchReferenceName(ref_name), true) - if err != nil { - ret_err = misc.Wrap_one_error(err_getting_branch_reference, err) - return - } - ref_hash = ref.Hash() - case "tag": - ref, err := repo.Reference(plumbing.NewTagReferenceName(ref_name), true) - if err != nil { - ret_err = misc.Wrap_one_error(err_getting_tag_reference, err) - return - } - ref_hash = ref.Hash() - default: - panic("Invalid ref type " + ref_type) - } - return -} diff --git a/render_readme.go b/render_readme.go deleted file mode 100644 index 497b3ec..0000000 --- a/render_readme.go +++ /dev/null @@ -1,61 +0,0 @@ -package main - -import ( - "bytes" - "html" - "html/template" - "strings" - - "github.com/go-git/go-git/v5/plumbing/object" - "github.com/microcosm-cc/bluemonday" - "github.com/niklasfasching/go-org/org" - "github.com/yuin/goldmark" - "github.com/yuin/goldmark/extension" -) - -var markdown_converter = goldmark.New(goldmark.WithExtensions(extension.GFM)) - -func render_readme_at_tree(tree *object.Tree) (readme_filename string, readme_content template.HTML) { - var readme_rendered_unsafe bytes.Buffer - - readme_file, err := tree.File("README") - if err == nil { - readme_file_contents, err := readme_file.Contents() - if err != nil { - return "Error fetching README", string_escape_html("Unable to fetch contents of README: " + err.Error()) - } - return "README", template.HTML("
" + html.EscapeString(readme_file_contents) + "
") - } - - readme_file, err = tree.File("README.md") - if err == nil { - readme_file_contents, err := readme_file.Contents() - if err != nil { - return "Error fetching README", string_escape_html("Unable to fetch contents of README: " + err.Error()) - } - err = markdown_converter.Convert([]byte(readme_file_contents), &readme_rendered_unsafe) - if err != nil { - return "Error fetching README", string_escape_html("Unable to render README: " + err.Error()) - } - return "README.md", template.HTML(bluemonday.UGCPolicy().SanitizeBytes(readme_rendered_unsafe.Bytes())) - } - - readme_file, err = tree.File("README.org") - if err == nil { - readme_file_contents, err := readme_file.Contents() - if err != nil { - return "Error fetching README", string_escape_html("Unable to fetch contents of README: " + err.Error()) - } - org_html, err := org.New().Parse(strings.NewReader(readme_file_contents), readme_filename).Write(org.NewHTMLWriter()) - if err != nil { - return "Error fetching README", string_escape_html("Unable to render README: " + err.Error()) - } - return "README.org", template.HTML(bluemonday.UGCPolicy().Sanitize(org_html)) - } - - return "", "" -} - -func string_escape_html(s string) template.HTML { - return template.HTML(html.EscapeString(s)) -} diff --git a/router_http.go b/router_http.go deleted file mode 100644 index fbf729b..0000000 --- a/router_http.go +++ /dev/null @@ -1,125 +0,0 @@ -package main - -import ( - "errors" - "fmt" - "net/http" - "strings" -) - -type http_router_t struct{} - -func (router *http_router_t) ServeHTTP(w http.ResponseWriter, r *http.Request) { - segments, _, err := parse_request_uri(r.RequestURI) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - if segments[0] == ":" { - if len(segments) < 2 { - http.Error(w, "Blank system endpoint", http.StatusNotFound) - return - } - - switch segments[1] { - case "static": - static_handler.ServeHTTP(w, r) - case "source": - source_handler.ServeHTTP(w, r) - default: - http.Error(w, fmt.Sprintf("Unknown system module type: %s", segments[1]), http.StatusNotFound) - } - return - } - - separator_index := -1 - for i, part := range segments { - if part == ":" { - separator_index = i - break - } - } - non_empty_last_segments_len := len(segments) - dir_mode := false - if segments[len(segments)-1] == "" { - non_empty_last_segments_len-- - dir_mode = true - } - - params := make(map[string]string) - _ = params - switch { - case non_empty_last_segments_len == 0: - handle_index(w, r) - case separator_index == -1: - http.Error(w, "Group indexing hasn't been implemented yet", http.StatusNotImplemented) - case non_empty_last_segments_len == separator_index+1: - http.Error(w, "Group root hasn't been implemented yet", http.StatusNotImplemented) - case non_empty_last_segments_len == separator_index+2: - if !dir_mode { - http.Redirect(w, r, r.URL.Path+"/", http.StatusSeeOther) - return - } - module_type := segments[separator_index+1] - params["group_name"] = segments[0] - switch module_type { - case "repos": - handle_group_repos(w, r, params) - default: - http.Error(w, fmt.Sprintf("Unknown module type: %s", module_type), http.StatusNotFound) - } - default: - module_type := segments[separator_index+1] - module_name := segments[separator_index+2] - params["group_name"] = segments[0] - switch module_type { - case "repos": - params["repo_name"] = module_name - // TODO: subgroups - if non_empty_last_segments_len == separator_index+3 { - if !dir_mode { - http.Redirect(w, r, r.URL.Path+"/", http.StatusSeeOther) - return - } - handle_repo_index(w, r, params) - return - } - repo_feature := segments[separator_index+3] - switch repo_feature { - case "info": - handle_repo_info(w, r, params) - case "tree": - params["rest"] = strings.Join(segments[separator_index+4:], "/") - handle_repo_tree(w, r, params) - case "raw": - params["rest"] = strings.Join(segments[separator_index+4:], "/") - handle_repo_raw(w, r, params) - case "log": - if non_empty_last_segments_len != separator_index+5 { - http.Error(w, "Too many parameters", http.StatusBadRequest) - return - } - if dir_mode { - http.Redirect(w, r, strings.TrimSuffix(r.URL.Path, "/"), http.StatusSeeOther) - return - } - params["ref"] = segments[separator_index+4] - handle_repo_log(w, r, params) - case "commit": - if dir_mode { - http.Redirect(w, r, strings.TrimSuffix(r.URL.Path, "/"), http.StatusSeeOther) - return - } - params["commit_id"] = segments[separator_index+4] - handle_repo_commit(w, r, params) - default: - http.Error(w, fmt.Sprintf("Unknown repo feature: %s", repo_feature), http.StatusNotFound) - } - default: - http.Error(w, fmt.Sprintf("Unknown module type: %s", module_type), http.StatusNotFound) - } - } -} - -var err_bad_request = errors.New("Bad Request") diff --git a/router_ssh.go b/router_ssh.go deleted file mode 100644 index 8eaaebd..0000000 --- a/router_ssh.go +++ /dev/null @@ -1,56 +0,0 @@ -package main - -import ( - "context" - "errors" - "net/url" - "strings" -) - -var err_ssh_illegal_endpoint = errors.New("Illegal endpoint during SSH access") - -func get_repo_path_from_ssh_path(ctx context.Context, ssh_path string) (repo_path string, err error) { - segments := strings.Split(strings.TrimPrefix(ssh_path, "/"), "/") - - for i, segment := range segments { - var err error - segments[i], err = url.PathUnescape(segment) - if err != nil { - return "", err - } - } - - if segments[0] == ":" { - return "", err_ssh_illegal_endpoint - } - - separator_index := -1 - for i, part := range segments { - if part == ":" { - separator_index = i - break - } - } - if segments[len(segments)-1] == "" { - segments = segments[:len(segments)-1] - } - - switch { - case separator_index == -1: - return "", err_ssh_illegal_endpoint - case len(segments) <= separator_index+2: - return "", err_ssh_illegal_endpoint - } - - group_name := segments[0] - module_type := segments[separator_index+1] - module_name := segments[separator_index+2] - switch module_type { - case "repos": - var fs_path string - err := database.QueryRow(ctx, "SELECT r.filesystem_path FROM repos r JOIN groups g ON r.group_id = g.id WHERE g.name = $1 AND r.name = $2;", group_name, module_name).Scan(&fs_path) - return fs_path, err - default: - return "", err_ssh_illegal_endpoint - } -} diff --git a/ssh.go b/ssh.go deleted file mode 100644 index a08e20c..0000000 --- a/ssh.go +++ /dev/null @@ -1,96 +0,0 @@ -package main - -import ( - "fmt" - "net" - "os" - "os/exec" - - glider_ssh "github.com/gliderlabs/ssh" - "go.lindenii.runxiyu.org/lindenii-common/clog" - go_ssh "golang.org/x/crypto/ssh" -) - -var server_public_key_string string -var server_public_key_fingerprint string -var server_public_key go_ssh.PublicKey - -func serve_ssh() error { - host_key_bytes, err := os.ReadFile(config.SSH.Key) - if err != nil { - return err - } - - host_key, err := go_ssh.ParsePrivateKey(host_key_bytes) - if err != nil { - return err - } - - server_public_key = host_key.PublicKey() - server_public_key_string = string(go_ssh.MarshalAuthorizedKey(server_public_key)) - server_public_key_fingerprint = string(go_ssh.FingerprintSHA256(server_public_key)) - - server := &glider_ssh.Server{ - Handler: func(session glider_ssh.Session) { - client_public_key := session.PublicKey() - var client_public_key_string string - if client_public_key != nil { - client_public_key_string = string(go_ssh.MarshalAuthorizedKey(client_public_key)) - } - _ = client_public_key_string - - cmd := session.Command() - - if len(cmd) < 2 { - fmt.Fprintln(session.Stderr(), "Insufficient arguments") - return - } - - if cmd[0] != "git-upload-pack" { - fmt.Fprintln(session.Stderr(), "Unsupported command") - return - } - - fs_path, err := get_repo_path_from_ssh_path(session.Context(), cmd[1]) - if err != nil { - fmt.Fprintln(session.Stderr(), "Error while getting repo path:", err) - return - } - - proc := exec.CommandContext(session.Context(), cmd[0], fs_path) - proc.Stdin = session - proc.Stdout = session - proc.Stderr = session.Stderr() - - err = proc.Start() - if err != nil { - fmt.Fprintln(session.Stderr(), "Error while starting process:", err) - return - } - err = proc.Wait() - if exit_error, ok := err.(*exec.ExitError); ok { - fmt.Fprintln(session.Stderr(), "Process exited with error", exit_error.ExitCode()) - } else if err != nil { - fmt.Fprintln(session.Stderr(), "Error while waiting for process:", err) - } - }, - PublicKeyHandler: func(ctx glider_ssh.Context, key glider_ssh.PublicKey) bool { return true }, - KeyboardInteractiveHandler: func(ctx glider_ssh.Context, challenge go_ssh.KeyboardInteractiveChallenge) bool { return true }, - } - - server.AddHostKey(host_key) - - listener, err := net.Listen(config.SSH.Net, config.SSH.Addr) - if err != nil { - return err - } - - go func() { - err = server.Serve(listener) - if err != nil { - clog.Fatal(1, "Serving SSH: "+err.Error()) - } - }() - - return nil -} diff --git a/ssh_server.go b/ssh_server.go new file mode 100644 index 0000000..8eaaebd --- /dev/null +++ b/ssh_server.go @@ -0,0 +1,56 @@ +package main + +import ( + "context" + "errors" + "net/url" + "strings" +) + +var err_ssh_illegal_endpoint = errors.New("Illegal endpoint during SSH access") + +func get_repo_path_from_ssh_path(ctx context.Context, ssh_path string) (repo_path string, err error) { + segments := strings.Split(strings.TrimPrefix(ssh_path, "/"), "/") + + for i, segment := range segments { + var err error + segments[i], err = url.PathUnescape(segment) + if err != nil { + return "", err + } + } + + if segments[0] == ":" { + return "", err_ssh_illegal_endpoint + } + + separator_index := -1 + for i, part := range segments { + if part == ":" { + separator_index = i + break + } + } + if segments[len(segments)-1] == "" { + segments = segments[:len(segments)-1] + } + + switch { + case separator_index == -1: + return "", err_ssh_illegal_endpoint + case len(segments) <= separator_index+2: + return "", err_ssh_illegal_endpoint + } + + group_name := segments[0] + module_type := segments[separator_index+1] + module_name := segments[separator_index+2] + switch module_type { + case "repos": + var fs_path string + err := database.QueryRow(ctx, "SELECT r.filesystem_path FROM repos r JOIN groups g ON r.group_id = g.id WHERE g.name = $1 AND r.name = $2;", group_name, module_name).Scan(&fs_path) + return fs_path, err + default: + return "", err_ssh_illegal_endpoint + } +} diff --git a/ssh_utils.go b/ssh_utils.go new file mode 100644 index 0000000..a08e20c --- /dev/null +++ b/ssh_utils.go @@ -0,0 +1,96 @@ +package main + +import ( + "fmt" + "net" + "os" + "os/exec" + + glider_ssh "github.com/gliderlabs/ssh" + "go.lindenii.runxiyu.org/lindenii-common/clog" + go_ssh "golang.org/x/crypto/ssh" +) + +var server_public_key_string string +var server_public_key_fingerprint string +var server_public_key go_ssh.PublicKey + +func serve_ssh() error { + host_key_bytes, err := os.ReadFile(config.SSH.Key) + if err != nil { + return err + } + + host_key, err := go_ssh.ParsePrivateKey(host_key_bytes) + if err != nil { + return err + } + + server_public_key = host_key.PublicKey() + server_public_key_string = string(go_ssh.MarshalAuthorizedKey(server_public_key)) + server_public_key_fingerprint = string(go_ssh.FingerprintSHA256(server_public_key)) + + server := &glider_ssh.Server{ + Handler: func(session glider_ssh.Session) { + client_public_key := session.PublicKey() + var client_public_key_string string + if client_public_key != nil { + client_public_key_string = string(go_ssh.MarshalAuthorizedKey(client_public_key)) + } + _ = client_public_key_string + + cmd := session.Command() + + if len(cmd) < 2 { + fmt.Fprintln(session.Stderr(), "Insufficient arguments") + return + } + + if cmd[0] != "git-upload-pack" { + fmt.Fprintln(session.Stderr(), "Unsupported command") + return + } + + fs_path, err := get_repo_path_from_ssh_path(session.Context(), cmd[1]) + if err != nil { + fmt.Fprintln(session.Stderr(), "Error while getting repo path:", err) + return + } + + proc := exec.CommandContext(session.Context(), cmd[0], fs_path) + proc.Stdin = session + proc.Stdout = session + proc.Stderr = session.Stderr() + + err = proc.Start() + if err != nil { + fmt.Fprintln(session.Stderr(), "Error while starting process:", err) + return + } + err = proc.Wait() + if exit_error, ok := err.(*exec.ExitError); ok { + fmt.Fprintln(session.Stderr(), "Process exited with error", exit_error.ExitCode()) + } else if err != nil { + fmt.Fprintln(session.Stderr(), "Error while waiting for process:", err) + } + }, + PublicKeyHandler: func(ctx glider_ssh.Context, key glider_ssh.PublicKey) bool { return true }, + KeyboardInteractiveHandler: func(ctx glider_ssh.Context, challenge go_ssh.KeyboardInteractiveChallenge) bool { return true }, + } + + server.AddHostKey(host_key) + + listener, err := net.Listen(config.SSH.Net, config.SSH.Addr) + if err != nil { + return err + } + + go func() { + err = server.Serve(listener) + if err != nil { + clog.Fatal(1, "Serving SSH: "+err.Error()) + } + }() + + return nil +} diff --git a/template_funcs.go b/template_funcs.go deleted file mode 100644 index 19a92ec..0000000 --- a/template_funcs.go +++ /dev/null @@ -1,15 +0,0 @@ -package main - -import ( - "path" - "strings" -) - -func first_line(s string) string { - before, _, _ := strings.Cut(s, "\n") - return before -} - -func base_name(s string) string { - return path.Base(s) -} diff --git a/url.go b/url.go new file mode 100644 index 0000000..86a3c6e --- /dev/null +++ b/url.go @@ -0,0 +1,65 @@ +package main + +import ( + "errors" + "net/http" + "net/url" + "strings" + + "go.lindenii.runxiyu.org/lindenii-common/misc" +) + +var ( + err_duplicate_ref_spec = errors.New("Duplicate ref spec") + err_no_ref_spec = errors.New("No ref spec") +) + +func get_param_ref_and_type(r *http.Request) (ref_type, ref string, err error) { + qr := r.URL.RawQuery + q, err := url.ParseQuery(qr) + if err != nil { + return + } + done := false + for _, _ref_type := range []string{"commit", "branch", "tag"} { + _ref, ok := q[_ref_type] + if ok { + if done { + err = err_duplicate_ref_spec + return + } else { + done = true + if len(_ref) != 1 { + err = err_duplicate_ref_spec + return + } + ref = _ref[0] + ref_type = _ref_type + } + } + } + if !done { + err = err_no_ref_spec + } + return +} + +func parse_request_uri(request_uri string) (segments []string, params url.Values, err error) { + path, params_string, _ := strings.Cut(request_uri, "?") + + segments = strings.Split(strings.TrimPrefix(path, "/"), "/") + + for i, segment := range segments { + segments[i], err = url.PathUnescape(segment) + if err != nil { + return nil, nil, misc.Wrap_one_error(err_bad_request, err) + } + } + + params, err = url.ParseQuery(params_string) + if err != nil { + return nil, nil, misc.Wrap_one_error(err_bad_request, err) + } + + return +} diff --git a/url_misc.go b/url_misc.go deleted file mode 100644 index 86a3c6e..0000000 --- a/url_misc.go +++ /dev/null @@ -1,65 +0,0 @@ -package main - -import ( - "errors" - "net/http" - "net/url" - "strings" - - "go.lindenii.runxiyu.org/lindenii-common/misc" -) - -var ( - err_duplicate_ref_spec = errors.New("Duplicate ref spec") - err_no_ref_spec = errors.New("No ref spec") -) - -func get_param_ref_and_type(r *http.Request) (ref_type, ref string, err error) { - qr := r.URL.RawQuery - q, err := url.ParseQuery(qr) - if err != nil { - return - } - done := false - for _, _ref_type := range []string{"commit", "branch", "tag"} { - _ref, ok := q[_ref_type] - if ok { - if done { - err = err_duplicate_ref_spec - return - } else { - done = true - if len(_ref) != 1 { - err = err_duplicate_ref_spec - return - } - ref = _ref[0] - ref_type = _ref_type - } - } - } - if !done { - err = err_no_ref_spec - } - return -} - -func parse_request_uri(request_uri string) (segments []string, params url.Values, err error) { - path, params_string, _ := strings.Cut(request_uri, "?") - - segments = strings.Split(strings.TrimPrefix(path, "/"), "/") - - for i, segment := range segments { - segments[i], err = url.PathUnescape(segment) - if err != nil { - return nil, nil, misc.Wrap_one_error(err_bad_request, err) - } - } - - params, err = url.ParseQuery(params_string) - if err != nil { - return nil, nil, misc.Wrap_one_error(err_bad_request, err) - } - - return -} -- cgit v1.2.3