aboutsummaryrefslogtreecommitdiffstats
path: root/modules/private
diff options
context:
space:
mode:
Diffstat (limited to 'modules/private')
-rw-r--r--modules/private/hook.go125
-rw-r--r--modules/private/internal.go56
-rw-r--r--modules/private/key.go35
-rw-r--r--modules/private/mail.go34
-rw-r--r--modules/private/manager.go171
-rw-r--r--modules/private/request.go135
-rw-r--r--modules/private/restore_repo.go34
-rw-r--r--modules/private/serv.go65
8 files changed, 236 insertions, 419 deletions
diff --git a/modules/private/hook.go b/modules/private/hook.go
index 9533eaae59..0563e4d80a 100644
--- a/modules/private/hook.go
+++ b/modules/private/hook.go
@@ -5,14 +5,11 @@ package private
import (
"context"
- "errors"
"fmt"
- "net/http"
"net/url"
"strconv"
"time"
- "code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/setting"
)
@@ -99,126 +96,46 @@ type HookProcReceiveRefResult struct {
}
// HookPreReceive check whether the provided commits are allowed
-func HookPreReceive(ctx context.Context, ownerName, repoName string, opts HookOptions) (int, string) {
- reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/pre-receive/%s/%s",
- url.PathEscape(ownerName),
- url.PathEscape(repoName),
- )
- req := newInternalRequest(ctx, reqURL, "POST")
- req = req.Header("Content-Type", "application/json")
- jsonBytes, _ := json.Marshal(opts)
- req.Body(jsonBytes)
- req.SetTimeout(60*time.Second, time.Duration(60+len(opts.OldCommitIDs))*time.Second)
- resp, err := req.Response()
- if err != nil {
- return http.StatusInternalServerError, fmt.Sprintf("Unable to contact gitea: %v", err.Error())
- }
- defer resp.Body.Close()
-
- if resp.StatusCode != http.StatusOK {
- return resp.StatusCode, decodeJSONError(resp).Err
- }
-
- return http.StatusOK, ""
+func HookPreReceive(ctx context.Context, ownerName, repoName string, opts HookOptions) ResponseExtra {
+ reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/pre-receive/%s/%s", url.PathEscape(ownerName), url.PathEscape(repoName))
+ req := newInternalRequest(ctx, reqURL, "POST", opts)
+ req.SetReadWriteTimeout(time.Duration(60+len(opts.OldCommitIDs)) * time.Second)
+ _, extra := requestJSONResp(req, &responseText{})
+ return extra
}
// HookPostReceive updates services and users
-func HookPostReceive(ctx context.Context, ownerName, repoName string, opts HookOptions) (*HookPostReceiveResult, string) {
- reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/post-receive/%s/%s",
- url.PathEscape(ownerName),
- url.PathEscape(repoName),
- )
-
- req := newInternalRequest(ctx, reqURL, "POST")
- req = req.Header("Content-Type", "application/json")
- req.SetTimeout(60*time.Second, time.Duration(60+len(opts.OldCommitIDs))*time.Second)
- jsonBytes, _ := json.Marshal(opts)
- req.Body(jsonBytes)
- resp, err := req.Response()
- if err != nil {
- return nil, fmt.Sprintf("Unable to contact gitea: %v", err.Error())
- }
- defer resp.Body.Close()
-
- if resp.StatusCode != http.StatusOK {
- return nil, decodeJSONError(resp).Err
- }
- res := &HookPostReceiveResult{}
- _ = json.NewDecoder(resp.Body).Decode(res)
-
- return res, ""
+func HookPostReceive(ctx context.Context, ownerName, repoName string, opts HookOptions) (*HookPostReceiveResult, ResponseExtra) {
+ reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/post-receive/%s/%s", url.PathEscape(ownerName), url.PathEscape(repoName))
+ req := newInternalRequest(ctx, reqURL, "POST", opts)
+ req.SetReadWriteTimeout(time.Duration(60+len(opts.OldCommitIDs)) * time.Second)
+ return requestJSONResp(req, &HookPostReceiveResult{})
}
// HookProcReceive proc-receive hook
-func HookProcReceive(ctx context.Context, ownerName, repoName string, opts HookOptions) (*HookProcReceiveResult, error) {
- reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/proc-receive/%s/%s",
- url.PathEscape(ownerName),
- url.PathEscape(repoName),
- )
-
- req := newInternalRequest(ctx, reqURL, "POST")
- req = req.Header("Content-Type", "application/json")
- req.SetTimeout(60*time.Second, time.Duration(60+len(opts.OldCommitIDs))*time.Second)
- jsonBytes, _ := json.Marshal(opts)
- req.Body(jsonBytes)
- resp, err := req.Response()
- if err != nil {
- return nil, fmt.Errorf("Unable to contact gitea: %w", err)
- }
- defer resp.Body.Close()
-
- if resp.StatusCode != http.StatusOK {
- return nil, errors.New(decodeJSONError(resp).Err)
- }
- res := &HookProcReceiveResult{}
- _ = json.NewDecoder(resp.Body).Decode(res)
+func HookProcReceive(ctx context.Context, ownerName, repoName string, opts HookOptions) (*HookProcReceiveResult, ResponseExtra) {
+ reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/proc-receive/%s/%s", url.PathEscape(ownerName), url.PathEscape(repoName))
- return res, nil
+ req := newInternalRequest(ctx, reqURL, "POST", opts)
+ req.SetReadWriteTimeout(time.Duration(60+len(opts.OldCommitIDs)) * time.Second)
+ return requestJSONResp(req, &HookProcReceiveResult{})
}
// SetDefaultBranch will set the default branch to the provided branch for the provided repository
-func SetDefaultBranch(ctx context.Context, ownerName, repoName, branch string) error {
+func SetDefaultBranch(ctx context.Context, ownerName, repoName, branch string) ResponseExtra {
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/set-default-branch/%s/%s/%s",
url.PathEscape(ownerName),
url.PathEscape(repoName),
url.PathEscape(branch),
)
req := newInternalRequest(ctx, reqURL, "POST")
- req = req.Header("Content-Type", "application/json")
-
- req.SetTimeout(60*time.Second, 60*time.Second)
- resp, err := req.Response()
- if err != nil {
- return fmt.Errorf("Unable to contact gitea: %w", err)
- }
- defer resp.Body.Close()
- if resp.StatusCode != http.StatusOK {
- return fmt.Errorf("Error returned from gitea: %v", decodeJSONError(resp).Err)
- }
- return nil
+ return requestJSONUserMsg(req, "")
}
// SSHLog sends ssh error log response
func SSHLog(ctx context.Context, isErr bool, msg string) error {
reqURL := setting.LocalURL + "api/internal/ssh/log"
- req := newInternalRequest(ctx, reqURL, "POST")
- req = req.Header("Content-Type", "application/json")
-
- jsonBytes, _ := json.Marshal(&SSHLogOption{
- IsError: isErr,
- Message: msg,
- })
- req.Body(jsonBytes)
-
- req.SetTimeout(60*time.Second, 60*time.Second)
- resp, err := req.Response()
- if err != nil {
- return fmt.Errorf("unable to contact gitea: %w", err)
- }
-
- defer resp.Body.Close()
- if resp.StatusCode != http.StatusOK {
- return fmt.Errorf("Error returned from gitea: %v", decodeJSONError(resp).Err)
- }
- return nil
+ req := newInternalRequest(ctx, reqURL, "POST", &SSHLogOption{IsError: isErr, Message: msg})
+ _, extra := requestJSONResp(req, &responseText{})
+ return extra.Error
}
diff --git a/modules/private/internal.go b/modules/private/internal.go
index a8b62fdde7..9c330a24a8 100644
--- a/modules/private/internal.go
+++ b/modules/private/internal.go
@@ -11,6 +11,7 @@ import (
"net/http"
"os"
"strings"
+ "time"
"code.gitea.io/gitea/modules/httplib"
"code.gitea.io/gitea/modules/json"
@@ -19,29 +20,10 @@ import (
"code.gitea.io/gitea/modules/setting"
)
-func newRequest(ctx context.Context, url, method, sourceIP string) *httplib.Request {
- if setting.InternalToken == "" {
- log.Fatal(`The INTERNAL_TOKEN setting is missing from the configuration file: %q.
-Ensure you are running in the correct environment or set the correct configuration file with -c.`, setting.CustomConf)
- }
- return httplib.NewRequest(url, method).
- SetContext(ctx).
- Header("X-Real-IP", sourceIP).
- Header("Authorization", fmt.Sprintf("Bearer %s", setting.InternalToken))
-}
-
-// Response internal request response
+// Response is used for internal request response (for user message and error message)
type Response struct {
- Err string `json:"err"`
-}
-
-func decodeJSONError(resp *http.Response) *Response {
- var res Response
- err := json.NewDecoder(resp.Body).Decode(&res)
- if err != nil {
- res.Err = err.Error()
- }
- return &res
+ Err string `json:"err,omitempty"` // server-side error log message, it won't be exposed to end users
+ UserMsg string `json:"user_msg,omitempty"` // meaningful error message for end users, it will be shown in git client's output.
}
func getClientIP() string {
@@ -52,11 +34,21 @@ func getClientIP() string {
return strings.Fields(sshConnEnv)[0]
}
-func newInternalRequest(ctx context.Context, url, method string) *httplib.Request {
- req := newRequest(ctx, url, method, getClientIP()).SetTLSClientConfig(&tls.Config{
- InsecureSkipVerify: true,
- ServerName: setting.Domain,
- })
+func newInternalRequest(ctx context.Context, url, method string, body ...any) *httplib.Request {
+ if setting.InternalToken == "" {
+ log.Fatal(`The INTERNAL_TOKEN setting is missing from the configuration file: %q.
+Ensure you are running in the correct environment or set the correct configuration file with -c.`, setting.CustomConf)
+ }
+
+ req := httplib.NewRequest(url, method).
+ SetContext(ctx).
+ Header("X-Real-IP", getClientIP()).
+ Header("Authorization", fmt.Sprintf("Bearer %s", setting.InternalToken)).
+ SetTLSClientConfig(&tls.Config{
+ InsecureSkipVerify: true,
+ ServerName: setting.Domain,
+ })
+
if setting.Protocol == setting.HTTPUnix {
req.SetTransport(&http.Transport{
DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
@@ -90,5 +82,15 @@ func newInternalRequest(ctx context.Context, url, method string) *httplib.Reques
},
})
}
+
+ if len(body) == 1 {
+ req.Header("Content-Type", "application/json")
+ jsonBytes, _ := json.Marshal(body[0])
+ req.Body(jsonBytes)
+ } else if len(body) > 1 {
+ log.Fatal("Too many arguments for newInternalRequest")
+ }
+
+ req.SetTimeout(10*time.Second, 60*time.Second)
return req
}
diff --git a/modules/private/key.go b/modules/private/key.go
index f09d6de2bf..6f7cd87796 100644
--- a/modules/private/key.go
+++ b/modules/private/key.go
@@ -6,8 +6,6 @@ package private
import (
"context"
"fmt"
- "io"
- "net/http"
"code.gitea.io/gitea/modules/setting"
)
@@ -16,39 +14,18 @@ import (
func UpdatePublicKeyInRepo(ctx context.Context, keyID, repoID int64) error {
// Ask for running deliver hook and test pull request tasks.
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/ssh/%d/update/%d", keyID, repoID)
- resp, err := newInternalRequest(ctx, reqURL, "POST").Response()
- if err != nil {
- return err
- }
-
- defer resp.Body.Close()
-
- // All 2XX status codes are accepted and others will return an error
- if resp.StatusCode/100 != 2 {
- return fmt.Errorf("Failed to update public key: %s", decodeJSONError(resp).Err)
- }
- return nil
+ req := newInternalRequest(ctx, reqURL, "POST")
+ _, extra := requestJSONResp(req, &responseText{})
+ return extra.Error
}
// AuthorizedPublicKeyByContent searches content as prefix (leak e-mail part)
// and returns public key found.
-func AuthorizedPublicKeyByContent(ctx context.Context, content string) (string, error) {
+func AuthorizedPublicKeyByContent(ctx context.Context, content string) (string, ResponseExtra) {
// Ask for running deliver hook and test pull request tasks.
reqURL := setting.LocalURL + "api/internal/ssh/authorized_keys"
req := newInternalRequest(ctx, reqURL, "POST")
req.Param("content", content)
- resp, err := req.Response()
- if err != nil {
- return "", err
- }
-
- defer resp.Body.Close()
-
- // All 2XX status codes are accepted and others will return an error
- if resp.StatusCode != http.StatusOK {
- return "", fmt.Errorf("Failed to update public key: %s", decodeJSONError(resp).Err)
- }
- bs, err := io.ReadAll(resp.Body)
-
- return string(bs), err
+ resp, extra := requestJSONResp(req, &responseText{})
+ return resp.Text, extra
}
diff --git a/modules/private/mail.go b/modules/private/mail.go
index 6eb7c2acd0..82216b346b 100644
--- a/modules/private/mail.go
+++ b/modules/private/mail.go
@@ -5,11 +5,7 @@ package private
import (
"context"
- "fmt"
- "io"
- "net/http"
- "code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/setting"
)
@@ -21,38 +17,18 @@ type Email struct {
}
// SendEmail calls the internal SendEmail function
-//
// It accepts a list of usernames.
// If DB contains these users it will send the email to them.
-//
-// If to list == nil its supposed to send an email to every
-// user present in DB
-func SendEmail(ctx context.Context, subject, message string, to []string) (int, string) {
+// If to list == nil, it's supposed to send emails to every user present in DB
+func SendEmail(ctx context.Context, subject, message string, to []string) (string, ResponseExtra) {
reqURL := setting.LocalURL + "api/internal/mail/send"
- req := newInternalRequest(ctx, reqURL, "POST")
- req = req.Header("Content-Type", "application/json")
- jsonBytes, _ := json.Marshal(Email{
+ req := newInternalRequest(ctx, reqURL, "POST", Email{
Subject: subject,
Message: message,
To: to,
})
- req.Body(jsonBytes)
- resp, err := req.Response()
- if err != nil {
- return http.StatusInternalServerError, fmt.Sprintf("Unable to contact gitea: %v", err.Error())
- }
- defer resp.Body.Close()
- body, err := io.ReadAll(resp.Body)
- if err != nil {
- return http.StatusInternalServerError, fmt.Sprintf("Response body error: %v", err.Error())
- }
-
- users := fmt.Sprintf("%d", len(to))
- if len(to) == 0 {
- users = "all"
- }
-
- return http.StatusOK, fmt.Sprintf("Sent %s email(s) to %s users", body, users)
+ resp, extra := requestJSONResp(req, &responseText{})
+ return resp.Text, extra
}
diff --git a/modules/private/manager.go b/modules/private/manager.go
index bbf470cd7a..5853db34e4 100644
--- a/modules/private/manager.go
+++ b/modules/private/manager.go
@@ -12,44 +12,21 @@ import (
"strconv"
"time"
- "code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/setting"
)
// Shutdown calls the internal shutdown function
-func Shutdown(ctx context.Context) (int, string) {
+func Shutdown(ctx context.Context) ResponseExtra {
reqURL := setting.LocalURL + "api/internal/manager/shutdown"
-
req := newInternalRequest(ctx, reqURL, "POST")
- resp, err := req.Response()
- if err != nil {
- return http.StatusInternalServerError, fmt.Sprintf("Unable to contact gitea: %v", err.Error())
- }
- defer resp.Body.Close()
-
- if resp.StatusCode != http.StatusOK {
- return resp.StatusCode, decodeJSONError(resp).Err
- }
-
- return http.StatusOK, "Shutting down"
+ return requestJSONUserMsg(req, "Shutting down")
}
// Restart calls the internal restart function
-func Restart(ctx context.Context) (int, string) {
+func Restart(ctx context.Context) ResponseExtra {
reqURL := setting.LocalURL + "api/internal/manager/restart"
-
req := newInternalRequest(ctx, reqURL, "POST")
- resp, err := req.Response()
- if err != nil {
- return http.StatusInternalServerError, fmt.Sprintf("Unable to contact gitea: %v", err.Error())
- }
- defer resp.Body.Close()
-
- if resp.StatusCode != http.StatusOK {
- return resp.StatusCode, decodeJSONError(resp).Err
- }
-
- return http.StatusOK, "Restarting"
+ return requestJSONUserMsg(req, "Restarting")
}
// FlushOptions represents the options for the flush call
@@ -59,102 +36,41 @@ type FlushOptions struct {
}
// FlushQueues calls the internal flush-queues function
-func FlushQueues(ctx context.Context, timeout time.Duration, nonBlocking bool) (int, string) {
+func FlushQueues(ctx context.Context, timeout time.Duration, nonBlocking bool) ResponseExtra {
reqURL := setting.LocalURL + "api/internal/manager/flush-queues"
-
- req := newInternalRequest(ctx, reqURL, "POST")
+ req := newInternalRequest(ctx, reqURL, "POST", FlushOptions{Timeout: timeout, NonBlocking: nonBlocking})
if timeout > 0 {
- req.SetTimeout(timeout+10*time.Second, timeout+10*time.Second)
- }
- req = req.Header("Content-Type", "application/json")
- jsonBytes, _ := json.Marshal(FlushOptions{
- Timeout: timeout,
- NonBlocking: nonBlocking,
- })
- req.Body(jsonBytes)
- resp, err := req.Response()
- if err != nil {
- return http.StatusInternalServerError, fmt.Sprintf("Unable to contact gitea: %v", err.Error())
- }
- defer resp.Body.Close()
-
- if resp.StatusCode != http.StatusOK {
- return resp.StatusCode, decodeJSONError(resp).Err
+ req.SetReadWriteTimeout(timeout + 10*time.Second)
}
-
- return http.StatusOK, "Flushed"
+ return requestJSONUserMsg(req, "Flushed")
}
// PauseLogging pauses logging
-func PauseLogging(ctx context.Context) (int, string) {
+func PauseLogging(ctx context.Context) ResponseExtra {
reqURL := setting.LocalURL + "api/internal/manager/pause-logging"
-
req := newInternalRequest(ctx, reqURL, "POST")
- resp, err := req.Response()
- if err != nil {
- return http.StatusInternalServerError, fmt.Sprintf("Unable to contact gitea: %v", err.Error())
- }
- defer resp.Body.Close()
-
- if resp.StatusCode != http.StatusOK {
- return resp.StatusCode, decodeJSONError(resp).Err
- }
-
- return http.StatusOK, "Logging Paused"
+ return requestJSONUserMsg(req, "Logging Paused")
}
// ResumeLogging resumes logging
-func ResumeLogging(ctx context.Context) (int, string) {
+func ResumeLogging(ctx context.Context) ResponseExtra {
reqURL := setting.LocalURL + "api/internal/manager/resume-logging"
-
req := newInternalRequest(ctx, reqURL, "POST")
- resp, err := req.Response()
- if err != nil {
- return http.StatusInternalServerError, fmt.Sprintf("Unable to contact gitea: %v", err.Error())
- }
- defer resp.Body.Close()
-
- if resp.StatusCode != http.StatusOK {
- return resp.StatusCode, decodeJSONError(resp).Err
- }
-
- return http.StatusOK, "Logging Restarted"
+ return requestJSONUserMsg(req, "Logging Restarted")
}
// ReleaseReopenLogging releases and reopens logging files
-func ReleaseReopenLogging(ctx context.Context) (int, string) {
+func ReleaseReopenLogging(ctx context.Context) ResponseExtra {
reqURL := setting.LocalURL + "api/internal/manager/release-and-reopen-logging"
-
req := newInternalRequest(ctx, reqURL, "POST")
- resp, err := req.Response()
- if err != nil {
- return http.StatusInternalServerError, fmt.Sprintf("Unable to contact gitea: %v", err.Error())
- }
- defer resp.Body.Close()
-
- if resp.StatusCode != http.StatusOK {
- return resp.StatusCode, decodeJSONError(resp).Err
- }
-
- return http.StatusOK, "Logging Restarted"
+ return requestJSONUserMsg(req, "Logging Restarted")
}
// SetLogSQL sets database logging
-func SetLogSQL(ctx context.Context, on bool) (int, string) {
+func SetLogSQL(ctx context.Context, on bool) ResponseExtra {
reqURL := setting.LocalURL + "api/internal/manager/set-log-sql?on=" + strconv.FormatBool(on)
-
req := newInternalRequest(ctx, reqURL, "POST")
- resp, err := req.Response()
- if err != nil {
- return http.StatusInternalServerError, fmt.Sprintf("Unable to contact gitea: %v", err.Error())
- }
- defer resp.Body.Close()
-
- if resp.StatusCode != http.StatusOK {
- return resp.StatusCode, decodeJSONError(resp).Err
- }
-
- return http.StatusOK, "Log SQL setting set"
+ return requestJSONUserMsg(req, "Log SQL setting set")
}
// LoggerOptions represents the options for the add logger call
@@ -166,67 +82,32 @@ type LoggerOptions struct {
}
// AddLogger adds a logger
-func AddLogger(ctx context.Context, group, name, mode string, config map[string]interface{}) (int, string) {
+func AddLogger(ctx context.Context, group, name, mode string, config map[string]interface{}) ResponseExtra {
reqURL := setting.LocalURL + "api/internal/manager/add-logger"
-
- req := newInternalRequest(ctx, reqURL, "POST")
- req = req.Header("Content-Type", "application/json")
- jsonBytes, _ := json.Marshal(LoggerOptions{
+ req := newInternalRequest(ctx, reqURL, "POST", LoggerOptions{
Group: group,
Name: name,
Mode: mode,
Config: config,
})
- req.Body(jsonBytes)
- resp, err := req.Response()
- if err != nil {
- return http.StatusInternalServerError, fmt.Sprintf("Unable to contact gitea: %v", err.Error())
- }
- defer resp.Body.Close()
-
- if resp.StatusCode != http.StatusOK {
- return resp.StatusCode, decodeJSONError(resp).Err
- }
-
- return http.StatusOK, "Added"
+ return requestJSONUserMsg(req, "Added")
}
// RemoveLogger removes a logger
-func RemoveLogger(ctx context.Context, group, name string) (int, string) {
+func RemoveLogger(ctx context.Context, group, name string) ResponseExtra {
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/manager/remove-logger/%s/%s", url.PathEscape(group), url.PathEscape(name))
-
req := newInternalRequest(ctx, reqURL, "POST")
- resp, err := req.Response()
- if err != nil {
- return http.StatusInternalServerError, fmt.Sprintf("Unable to contact gitea: %v", err.Error())
- }
- defer resp.Body.Close()
-
- if resp.StatusCode != http.StatusOK {
- return resp.StatusCode, decodeJSONError(resp).Err
- }
-
- return http.StatusOK, "Removed"
+ return requestJSONUserMsg(req, "Removed")
}
// Processes return the current processes from this gitea instance
-func Processes(ctx context.Context, out io.Writer, flat, noSystem, stacktraces, json bool, cancel string) (int, string) {
+func Processes(ctx context.Context, out io.Writer, flat, noSystem, stacktraces, json bool, cancel string) ResponseExtra {
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/manager/processes?flat=%t&no-system=%t&stacktraces=%t&json=%t&cancel-pid=%s", flat, noSystem, stacktraces, json, url.QueryEscape(cancel))
req := newInternalRequest(ctx, reqURL, "GET")
- resp, err := req.Response()
- if err != nil {
- return http.StatusInternalServerError, fmt.Sprintf("Unable to contact gitea: %v", err.Error())
- }
- defer resp.Body.Close()
-
- if resp.StatusCode != http.StatusOK {
- return resp.StatusCode, decodeJSONError(resp).Err
- }
-
- _, err = io.Copy(out, resp.Body)
- if err != nil {
- return http.StatusInternalServerError, err.Error()
+ callback := func(resp *http.Response, extra *ResponseExtra) {
+ _, extra.Error = io.Copy(out, resp.Body)
}
- return http.StatusOK, ""
+ _, extra := requestJSONResp(req, &callback)
+ return extra
}
diff --git a/modules/private/request.go b/modules/private/request.go
new file mode 100644
index 0000000000..3eb8c92c1a
--- /dev/null
+++ b/modules/private/request.go
@@ -0,0 +1,135 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package private
+
+import (
+ "fmt"
+ "io"
+ "net/http"
+ "unicode"
+
+ "code.gitea.io/gitea/modules/httplib"
+ "code.gitea.io/gitea/modules/json"
+)
+
+// responseText is used to get the response as text, instead of parsing it as JSON.
+type responseText struct {
+ Text string
+}
+
+// ResponseExtra contains extra information about the response, especially for error responses.
+type ResponseExtra struct {
+ StatusCode int
+ UserMsg string
+ Error error
+}
+
+type responseCallback func(resp *http.Response, extra *ResponseExtra)
+
+func (re *ResponseExtra) HasError() bool {
+ return re.Error != nil
+}
+
+type responseError struct {
+ statusCode int
+ errorString string
+}
+
+func (re responseError) Error() string {
+ if re.errorString == "" {
+ return fmt.Sprintf("internal API error response, status=%d", re.statusCode)
+ }
+ return fmt.Sprintf("internal API error response, status=%d, err=%s", re.statusCode, re.errorString)
+}
+
+// requestJSONUserMsg sends a request to the gitea server and then parses the response.
+// If the status code is not 2xx, or any error occurs, the ResponseExtra.Error field is guaranteed to be non-nil,
+// and the ResponseExtra.UserMsg field will be set to a message for the end user.
+//
+// * If the "res" is a struct pointer, the response will be parsed as JSON
+// * If the "res" is responseText pointer, the response will be stored as text in it
+// * If the "res" is responseCallback pointer, the callback function should set the ResponseExtra fields accordingly
+func requestJSONResp[T any](req *httplib.Request, res *T) (ret *T, extra ResponseExtra) {
+ resp, err := req.Response()
+ if err != nil {
+ extra.UserMsg = "Internal Server Connection Error"
+ extra.Error = fmt.Errorf("unable to contact gitea %q: %w", req.GoString(), err)
+ return nil, extra
+ }
+ defer resp.Body.Close()
+
+ extra.StatusCode = resp.StatusCode
+
+ // if the status code is not 2xx, try to parse the error response
+ if resp.StatusCode/100 != 2 {
+ var respErr Response
+ if err := json.NewDecoder(resp.Body).Decode(&respErr); err != nil {
+ extra.UserMsg = "Internal Server Error Decoding Failed"
+ extra.Error = fmt.Errorf("unable to decode error response %q: %w", req.GoString(), err)
+ return nil, extra
+ }
+ extra.UserMsg = respErr.UserMsg
+ if extra.UserMsg == "" {
+ extra.UserMsg = "Internal Server Error (no message for end users)"
+ }
+ extra.Error = responseError{statusCode: resp.StatusCode, errorString: respErr.Err}
+ return res, extra
+ }
+
+ // now, the StatusCode must be 2xx
+ var v any = res
+ if respText, ok := v.(*responseText); ok {
+ // get the whole response as a text string
+ bs, err := io.ReadAll(resp.Body)
+ if err != nil {
+ extra.UserMsg = "Internal Server Response Reading Failed"
+ extra.Error = fmt.Errorf("unable to read response %q: %w", req.GoString(), err)
+ return nil, extra
+ }
+ respText.Text = string(bs)
+ return res, extra
+ } else if callback, ok := v.(*responseCallback); ok {
+ // pass the response to callback, and let the callback update the ResponseExtra
+ extra.StatusCode = resp.StatusCode
+ (*callback)(resp, &extra)
+ return nil, extra
+ } else if err := json.NewDecoder(resp.Body).Decode(res); err != nil {
+ // decode the response into the given struct
+ extra.UserMsg = "Internal Server Response Decoding Failed"
+ extra.Error = fmt.Errorf("unable to decode response %q: %w", req.GoString(), err)
+ return nil, extra
+ }
+
+ if respMsg, ok := v.(*Response); ok {
+ // if the "res" is Response structure, try to get the UserMsg from it and update the ResponseExtra
+ extra.UserMsg = respMsg.UserMsg
+ if respMsg.Err != "" {
+ // usually this shouldn't happen, because the StatusCode is 2xx, there should be no error.
+ // but we still handle the "err" response, in case some people return error messages by status code 200.
+ extra.Error = responseError{statusCode: resp.StatusCode, errorString: respMsg.Err}
+ }
+ }
+
+ return res, extra
+}
+
+// requestJSONUserMsg sends a request to the gitea server and then parses the response as private.Response
+// If the request succeeds, the successMsg will be used as part of ResponseExtra.UserMsg.
+func requestJSONUserMsg(req *httplib.Request, successMsg string) ResponseExtra {
+ resp, extra := requestJSONResp(req, &Response{})
+ if extra.HasError() {
+ return extra
+ }
+ if resp.UserMsg == "" {
+ extra.UserMsg = successMsg // if UserMsg is empty, then use successMsg as userMsg
+ } else if successMsg != "" {
+ // else, now UserMsg is not empty, if successMsg is not empty, then append successMsg to UserMsg
+ if unicode.IsPunct(rune(extra.UserMsg[len(extra.UserMsg)-1])) {
+ extra.UserMsg = extra.UserMsg + " " + successMsg
+ } else {
+ extra.UserMsg = extra.UserMsg + ". " + successMsg
+ }
+ }
+ return extra
+}
diff --git a/modules/private/restore_repo.go b/modules/private/restore_repo.go
index f40d914a7b..34d0f5d482 100644
--- a/modules/private/restore_repo.go
+++ b/modules/private/restore_repo.go
@@ -6,11 +6,8 @@ package private
import (
"context"
"fmt"
- "io"
- "net/http"
"time"
- "code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/setting"
)
@@ -24,39 +21,16 @@ type RestoreParams struct {
}
// RestoreRepo calls the internal RestoreRepo function
-func RestoreRepo(ctx context.Context, repoDir, ownerName, repoName string, units []string, validation bool) (int, string) {
+func RestoreRepo(ctx context.Context, repoDir, ownerName, repoName string, units []string, validation bool) ResponseExtra {
reqURL := setting.LocalURL + "api/internal/restore_repo"
- req := newInternalRequest(ctx, reqURL, "POST")
- req.SetTimeout(3*time.Second, 0) // since the request will spend much time, don't timeout
- req = req.Header("Content-Type", "application/json")
- jsonBytes, _ := json.Marshal(RestoreParams{
+ req := newInternalRequest(ctx, reqURL, "POST", RestoreParams{
RepoDir: repoDir,
OwnerName: ownerName,
RepoName: repoName,
Units: units,
Validation: validation,
})
- req.Body(jsonBytes)
- resp, err := req.Response()
- if err != nil {
- return http.StatusInternalServerError, fmt.Sprintf("Unable to contact gitea: %v, could you confirm it's running?", err.Error())
- }
- defer resp.Body.Close()
-
- if resp.StatusCode != http.StatusOK {
- ret := struct {
- Err string `json:"err"`
- }{}
- body, err := io.ReadAll(resp.Body)
- if err != nil {
- return http.StatusInternalServerError, fmt.Sprintf("Response body error: %v", err.Error())
- }
- if err := json.Unmarshal(body, &ret); err != nil {
- return http.StatusInternalServerError, fmt.Sprintf("Response body Unmarshal error: %v", err.Error())
- }
- return http.StatusInternalServerError, ret.Err
- }
-
- return http.StatusOK, fmt.Sprintf("Restore repo %s/%s successfully", ownerName, repoName)
+ req.SetTimeout(3*time.Second, 0) // since the request will spend much time, don't timeout
+ return requestJSONUserMsg(req, fmt.Sprintf("Restore repo %s/%s successfully", ownerName, repoName))
}
diff --git a/modules/private/serv.go b/modules/private/serv.go
index c176e1ddfc..480a446954 100644
--- a/modules/private/serv.go
+++ b/modules/private/serv.go
@@ -6,13 +6,11 @@ package private
import (
"context"
"fmt"
- "net/http"
"net/url"
asymkey_model "code.gitea.io/gitea/models/asymkey"
"code.gitea.io/gitea/models/perm"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/setting"
)
@@ -24,20 +22,11 @@ type KeyAndOwner struct {
// ServNoCommand returns information about the provided key
func ServNoCommand(ctx context.Context, keyID int64) (*asymkey_model.PublicKey, *user_model.User, error) {
- reqURL := setting.LocalURL + fmt.Sprintf("api/internal/serv/none/%d",
- keyID)
- resp, err := newInternalRequest(ctx, reqURL, "GET").Response()
- if err != nil {
- return nil, nil, err
- }
- defer resp.Body.Close()
- if resp.StatusCode != http.StatusOK {
- return nil, nil, fmt.Errorf("%s", decodeJSONError(resp).Err)
- }
-
- var keyAndOwner KeyAndOwner
- if err := json.NewDecoder(resp.Body).Decode(&keyAndOwner); err != nil {
- return nil, nil, err
+ reqURL := setting.LocalURL + fmt.Sprintf("api/internal/serv/none/%d", keyID)
+ req := newInternalRequest(ctx, reqURL, "GET")
+ keyAndOwner, extra := requestJSONResp(req, &KeyAndOwner{})
+ if extra.HasError() {
+ return nil, nil, extra.Error
}
return keyAndOwner.Key, keyAndOwner.Owner, nil
}
@@ -56,53 +45,19 @@ type ServCommandResults struct {
RepoID int64
}
-// ErrServCommand is an error returned from ServCommmand.
-type ErrServCommand struct {
- Results ServCommandResults
- Err string
- StatusCode int
-}
-
-func (err ErrServCommand) Error() string {
- return err.Err
-}
-
-// IsErrServCommand checks if an error is a ErrServCommand.
-func IsErrServCommand(err error) bool {
- _, ok := err.(ErrServCommand)
- return ok
-}
-
// ServCommand preps for a serv call
-func ServCommand(ctx context.Context, keyID int64, ownerName, repoName string, mode perm.AccessMode, verbs ...string) (*ServCommandResults, error) {
+func ServCommand(ctx context.Context, keyID int64, ownerName, repoName string, mode perm.AccessMode, verbs ...string) (*ServCommandResults, ResponseExtra) {
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/serv/command/%d/%s/%s?mode=%d",
keyID,
url.PathEscape(ownerName),
url.PathEscape(repoName),
- mode)
+ mode,
+ )
for _, verb := range verbs {
if verb != "" {
reqURL += fmt.Sprintf("&verb=%s", url.QueryEscape(verb))
}
}
-
- resp, err := newInternalRequest(ctx, reqURL, "GET").Response()
- if err != nil {
- return nil, err
- }
- defer resp.Body.Close()
-
- if resp.StatusCode != http.StatusOK {
- var errServCommand ErrServCommand
- if err := json.NewDecoder(resp.Body).Decode(&errServCommand); err != nil {
- return nil, err
- }
- errServCommand.StatusCode = resp.StatusCode
- return nil, errServCommand
- }
- var results ServCommandResults
- if err := json.NewDecoder(resp.Body).Decode(&results); err != nil {
- return nil, err
- }
- return &results, nil
+ req := newInternalRequest(ctx, reqURL, "GET")
+ return requestJSONResp(req, &ServCommandResults{})
}