diff --git a/fileserver/fileop.go b/fileserver/fileop.go index b188cbfa..a30ef171 100644 --- a/fileserver/fileop.go +++ b/fileserver/fileop.go @@ -527,9 +527,9 @@ func setCommonHeaders(rsp http.ResponseWriter, r *http.Request, operation, fileN operation == "downloadblks" { // Since the file name downloaded by safari will be garbled, we need to encode the filename. // Safari cannot parse unencoded utf8 characters. - contFileName = fmt.Sprintf("attachment;filename*=\"utf-8' '%s\"", url.PathEscape(fileName)) + contFileName = fmt.Sprintf("attachment;filename*=utf-8''%s;filename=\"%s\"", url.PathEscape(fileName), fileName) } else { - contFileName = fmt.Sprintf("inline;filename*=\"utf-8' '%s\"", url.PathEscape(fileName)) + contFileName = fmt.Sprintf("inline;filename*=utf-8''%s;filename=\"%s\"", url.PathEscape(fileName), fileName) } rsp.Header().Set("Content-Disposition", contFileName) @@ -725,7 +725,7 @@ func downloadZipFile(rsp http.ResponseWriter, r *http.Request, data, repoID, use // The zip name downloaded by safari will be garbled if we encode the zip name, // because we download zip file using chunk encoding. - contFileName := fmt.Sprintf("attachment;filename=\"%s\"", zipName) + contFileName := fmt.Sprintf("attachment;filename=\"%s\";filename*=utf-8''%s", zipName, url.PathEscape(zipName)) rsp.Header().Set("Content-Disposition", contFileName) rsp.Header().Set("Content-Type", "application/octet-stream") @@ -744,7 +744,7 @@ func downloadZipFile(rsp http.ResponseWriter, r *http.Request, data, repoID, use zipName := fmt.Sprintf("documents-export-%d-%d-%d.zip", now.Year(), now.Month(), now.Day()) setCommonHeaders(rsp, r, "download", zipName) - contFileName := fmt.Sprintf("attachment;filename=\"%s\"", zipName) + contFileName := fmt.Sprintf("attachment;filename=\"%s\";filename*=utf8''%s", zipName, url.PathEscape(zipName)) rsp.Header().Set("Content-Disposition", contFileName) rsp.Header().Set("Content-Type", "application/octet-stream") @@ -1948,9 +1948,8 @@ func notifRepoUpdate(repoID string, commitID string) error { } header := map[string][]string{ "Authorization": {"Token " + token}, - "Content-Type": {"application/json"}, } - _, _, err = httpCommon("POST", url, header, bytes.NewReader(msg)) + _, _, err = utils.HttpCommon("POST", url, header, bytes.NewReader(msg)) if err != nil { log.Printf("failed to send repo update event: %v", err) return err @@ -1959,30 +1958,6 @@ func notifRepoUpdate(repoID string, commitID string) error { return nil } -func httpCommon(method, url string, header map[string][]string, reader io.Reader) (int, []byte, error) { - req, err := http.NewRequest(method, url, reader) - if err != nil { - return -1, nil, err - } - req.Header = header - - rsp, err := http.DefaultClient.Do(req) - if err != nil { - return -1, nil, err - } - defer rsp.Body.Close() - - if rsp.StatusCode != http.StatusOK { - return rsp.StatusCode, nil, fmt.Errorf("bad response %d for %s", rsp.StatusCode, url) - } - body, err := io.ReadAll(rsp.Body) - if err != nil { - return rsp.StatusCode, nil, err - } - - return rsp.StatusCode, body, nil -} - func doPostMultiFiles(repo *repomgr.Repo, rootID, parentDir string, dents []*fsmgr.SeafDirent, user string, replace bool, names *[]string) (string, error) { if parentDir[0] == '/' { parentDir = parentDir[1:] @@ -3499,7 +3474,7 @@ type ShareLinkInfo struct { ShareType string `json:"share_type"` } -func queryShareLinkInfo(token, opType string) (*ShareLinkInfo, *appError) { +func queryShareLinkInfo(token, cookie, opType string) (*ShareLinkInfo, *appError) { claims := SeahubClaims{ time.Now().Add(time.Second * 300).Unix(), true, @@ -3512,10 +3487,13 @@ func queryShareLinkInfo(token, opType string) (*ShareLinkInfo, *appError) { err := fmt.Errorf("failed to sign jwt token: %v", err) return nil, &appError{err, "", http.StatusInternalServerError} } - url := fmt.Sprintf("%s?token=%s&type=%s", seahubURL+"/share-link-info/", token, opType) + url := fmt.Sprintf("%s?token=%s&type=%s", seahubURL+"/check-share-link-access/", token, opType) header := map[string][]string{ "Authorization": {"Token " + tokenString}, } + if cookie != "" { + header["Cookie"] = []string{cookie} + } status, body, err := utils.HttpCommon("GET", url, header, nil) if err != nil { err := fmt.Errorf("failed to get share link info: %v", err) @@ -3548,7 +3526,8 @@ func accessLinkCB(rsp http.ResponseWriter, r *http.Request) *appError { return &appError{nil, msg, http.StatusBadRequest} } token := parts[1] - info, appErr := queryShareLinkInfo(token, "file") + cookie := r.Header.Get("Cookie") + info, appErr := queryShareLinkInfo(token, cookie, "file") if appErr != nil { return appErr } diff --git a/fileserver/merge.go b/fileserver/merge.go index d57a74af..dcd6b488 100644 --- a/fileserver/merge.go +++ b/fileserver/merge.go @@ -419,7 +419,6 @@ func postGetNickName(modifier string) string { header := map[string][]string{ "Authorization": {"Token " + tokenString}, - "Content-Type": {"application/json"}, } data, err := json.Marshal(map[string]interface{}{ diff --git a/fileserver/utils/http.go b/fileserver/utils/http.go index b1fe79c1..bde68b2e 100644 --- a/fileserver/utils/http.go +++ b/fileserver/utils/http.go @@ -6,10 +6,9 @@ import ( "io" "net/http" "strings" + "time" ) -var HttpReqContext, HttpReqCancel = context.WithCancel(context.Background()) - func GetAuthorizationToken(h http.Header) string { auth := h.Get("Authorization") splitResult := strings.Split(auth, " ") @@ -20,7 +19,11 @@ func GetAuthorizationToken(h http.Header) string { } func HttpCommon(method, url string, header map[string][]string, reader io.Reader) (int, []byte, error) { - req, err := http.NewRequestWithContext(HttpReqContext, method, url, reader) + header["Content-Type"] = []string{"application/json"} + header["User-Agent"] = []string{"Seafile Server"} + ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second) + defer cancel() + req, err := http.NewRequestWithContext(ctx, method, url, reader) if err != nil { return -1, nil, err } @@ -35,6 +38,11 @@ func HttpCommon(method, url string, header map[string][]string, reader io.Reader if rsp.StatusCode == http.StatusNotFound { return rsp.StatusCode, nil, fmt.Errorf("url %s not found", url) } + + if rsp.StatusCode != http.StatusOK { + return rsp.StatusCode, nil, fmt.Errorf("bad response %d for %s", rsp.StatusCode, url) + } + body, err := io.ReadAll(rsp.Body) if err != nil { return rsp.StatusCode, nil, err diff --git a/server/access-file.c b/server/access-file.c index b36a2c4a..1842edec 100644 --- a/server/access-file.c +++ b/server/access-file.c @@ -601,20 +601,17 @@ do_file(evhtp_request_t *req, SeafRepo *repo, const char *file_id, evhtp_headers_add_header (req->headers_out, evhtp_header_new("Content-Length", file_size, 1, 1)); + char *esc_filename = g_uri_escape_string(filename, NULL, FALSE); if (strcmp(operation, "download") == 0 || strcmp(operation, "download-link") == 0) { /* Safari doesn't support 'utf8', 'utf-8' is compatible with most of browsers. */ snprintf(cont_filename, SEAF_PATH_MAX, - "attachment;filename*=\"utf-8\' \'%s\"", filename); + "attachment;filename*=utf-8''%s;filename=\"%s\"", esc_filename, filename); } else { - if (test_firefox (req)) { - snprintf(cont_filename, SEAF_PATH_MAX, - "inline;filename*=\"utf-8\' \'%s\"", filename); - } else { - snprintf(cont_filename, SEAF_PATH_MAX, - "inline;filename=\"%s\"", filename); - } + snprintf(cont_filename, SEAF_PATH_MAX, + "inline;filename*=utf-8''%s;filename=\"%s\"", esc_filename, filename); } + g_free (esc_filename); evhtp_headers_add_header(req->headers_out, evhtp_header_new("Content-Disposition", cont_filename, 1, 1)); @@ -896,27 +893,20 @@ set_resp_disposition (evhtp_request_t *req, const char *operation, const char *filename) { char *cont_filename = NULL; + char *esc_filename = g_uri_escape_string(filename, NULL, FALSE); if (strcmp(operation, "download") == 0) { - if (test_firefox (req)) { - cont_filename = g_strdup_printf("attachment;filename*=\"utf-8\' \'%s\"", - filename); - - } else { - cont_filename = g_strdup_printf("attachment;filename=\"%s\"", filename); - } + cont_filename = g_strdup_printf("attachment;filename*=utf-8''%s;filename=\"%s\"", + esc_filename, filename); } else { - if (test_firefox (req)) { - cont_filename = g_strdup_printf("inline;filename*=\"utf-8\' \'%s\"", - filename); - } else { - cont_filename = g_strdup_printf("inline;filename=\"%s\"", filename); - } + cont_filename = g_strdup_printf("inline;filename*=utf-8''%s;filename=\"%s\"", + esc_filename, filename); } evhtp_headers_add_header(req->headers_out, evhtp_header_new("Content-Disposition", cont_filename, 0, 1)); + g_free (esc_filename); g_free (cont_filename); } @@ -1061,8 +1051,14 @@ start_download_zip_file (evhtp_request_t *req, const char *token, evhtp_headers_add_header (req->headers_out, evhtp_header_new("Content-Length", file_size, 1, 1)); + char *zippath = g_strdup_printf("%s.zip", zipname); + char *esc_zippath = g_uri_escape_string(zippath, NULL, FALSE); + snprintf(cont_filename, SEAF_PATH_MAX, - "attachment;filename=\"%s.zip\"", zipname); + "attachment;filename*=utf-8''%s;filename=\"%s\"", esc_zippath, zippath); + + g_free (zippath); + g_free (esc_zippath); evhtp_headers_add_header(req->headers_out, evhtp_header_new("Content-Disposition", cont_filename, 1, 1)); @@ -1682,7 +1678,8 @@ access_link_cb(evhtp_request_t *req, void *arg) token = parts[1]; - info = http_tx_manager_query_share_link_info (token, "file"); + const char *cookie = evhtp_kv_find (req->headers_in, "Cookie"); + info = http_tx_manager_query_share_link_info (token, cookie, "file"); if (!info) { error_str = "Link token not found\n"; error_code = EVHTP_RES_FORBIDDEN; diff --git a/server/http-tx-mgr.c b/server/http-tx-mgr.c index 0204d2e0..d99c51d2 100644 --- a/server/http-tx-mgr.c +++ b/server/http-tx-mgr.c @@ -17,22 +17,6 @@ #define DEBUG_FLAG SEAFILE_DEBUG_TRANSFER #include "log.h" -#ifndef SEAFILE_CLIENT_VERSION -#define SEAFILE_CLIENT_VERSION PACKAGE_VERSION -#endif - -#ifdef WIN32 -#define USER_AGENT_OS "Windows NT" -#endif - -#ifdef __APPLE__ -#define USER_AGENT_OS "Apple OS X" -#endif - -#ifdef __linux__ -#define USER_AGENT_OS "Linux" -#endif - /* Http connection and connection pool. */ struct _Connection { @@ -365,7 +349,7 @@ http_post (Connection *conn, const char *url, const char *token, curl = conn->curl; - headers = curl_slist_append (headers, "User-Agent: Seafile/"SEAFILE_CLIENT_VERSION" ("USER_AGENT_OS")"); + headers = curl_slist_append (headers, "User-Agent: Seafile Server"); if (token) { token_header = g_strdup_printf ("Authorization: Token %s", token); @@ -506,7 +490,7 @@ http_tx_manager_get_nickname (const char *modifier) json_decref (content); curl = conn->curl; - headers = curl_slist_append (headers, "User-Agent: Seafile/"SEAFILE_CLIENT_VERSION" ("USER_AGENT_OS")"); + headers = curl_slist_append (headers, "User-Agent: Seafile Server"); token_header = g_strdup_printf ("Authorization: Token %s", jwt_token); headers = curl_slist_append (headers, token_header); headers = curl_slist_append (headers, "Content-Type: application/json"); @@ -580,10 +564,11 @@ parse_share_link_info (const char *rsp_content, int rsp_size) } SeafileShareLinkInfo * -http_tx_manager_query_share_link_info (const char *token, const char *type) +http_tx_manager_query_share_link_info (const char *token, const char *cookie, const char *type) { Connection *conn = NULL; char *token_header; + char *cookie_header; struct curl_slist *headers = NULL; int ret = 0; CURL *curl; @@ -607,14 +592,19 @@ http_tx_manager_query_share_link_info (const char *token, const char *type) } curl = conn->curl; - headers = curl_slist_append (headers, "User-Agent: Seafile/"SEAFILE_CLIENT_VERSION" ("USER_AGENT_OS")"); + headers = curl_slist_append (headers, "User-Agent: Seafile Server"); token_header = g_strdup_printf ("Authorization: Token %s", jwt_token); headers = curl_slist_append (headers, token_header); - headers = curl_slist_append (headers, "Content-Type: application/json"); g_free (token_header); + if (cookie) { + cookie_header = g_strdup_printf ("Cookie: %s", cookie); + headers = curl_slist_append (headers, cookie_header); + g_free (cookie_header); + } + headers = curl_slist_append (headers, "Content-Type: application/json"); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); - url = g_strdup_printf("%s/share-link-info/?token=%s&type=%s", seaf->seahub_url, token, type); + url = g_strdup_printf("%s/check-share-link-access/?token=%s&type=%s", seaf->seahub_url, token, type); ret = http_get_common (curl, url, jwt_token, &rsp_status, &rsp_content, &rsp_size, NULL, NULL, TRUE); if (ret < 0) { diff --git a/server/http-tx-mgr.h b/server/http-tx-mgr.h index 3db2bdd7..daabc0bc 100644 --- a/server/http-tx-mgr.h +++ b/server/http-tx-mgr.h @@ -50,5 +50,5 @@ char * http_tx_manager_get_nickname (const char *modifier); SeafileShareLinkInfo * -http_tx_manager_query_share_link_info (const char *token, const char *type); +http_tx_manager_query_share_link_info (const char *token, const char *cookie, const char *type); #endif diff --git a/server/seaf-server.c b/server/seaf-server.c index f04cc4db..b9cf7632 100644 --- a/server/seaf-server.c +++ b/server/seaf-server.c @@ -1317,10 +1317,6 @@ main (int argc, char **argv) seafile_debug_set_flags_string (debug_str); private_key = g_getenv("JWT_PRIVATE_KEY"); - if (!private_key) { - seaf_warning ("Failed to read JWT_PRIVATE_KEY.\n"); - exit (1); - } if (seafile_dir == NULL) seafile_dir = g_build_filename (ccnet_dir, "seafile", NULL); @@ -1344,6 +1340,11 @@ main (int argc, char **argv) exit (0); } + if (!private_key) { + seaf_warning ("Failed to read JWT_PRIVATE_KEY.\n"); + exit (1); + } + seaf = seafile_session_new (central_config_dir, seafile_dir, ccnet_dir, private_key); if (!seaf) { seaf_warning ("Failed to create seafile session.\n");