| 
									
										
										
										
											2023-11-10 19:29:26 +01:00
										 |  |  | // GoToSocial | 
					
						
							|  |  |  | // Copyright (C) GoToSocial Authors admin@gotosocial.org | 
					
						
							|  |  |  | // SPDX-License-Identifier: AGPL-3.0-or-later | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // This program is free software: you can redistribute it and/or modify | 
					
						
							|  |  |  | // it under the terms of the GNU Affero General Public License as published by | 
					
						
							|  |  |  | // the Free Software Foundation, either version 3 of the License, or | 
					
						
							|  |  |  | // (at your option) any later version. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // This program is distributed in the hope that it will be useful, | 
					
						
							|  |  |  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | 
					
						
							|  |  |  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
					
						
							|  |  |  | // GNU Affero General Public License for more details. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // You should have received a copy of the GNU Affero General Public License | 
					
						
							|  |  |  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | package media | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-12 09:39:47 +00:00
										 |  |  | import ( | 
					
						
							|  |  |  | 	"errors" | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							|  |  |  | 	"io" | 
					
						
							| 
									
										
										
										
											2024-08-30 14:03:59 +02:00
										 |  |  | 	"io/fs" | 
					
						
							| 
									
										
										
										
											2024-07-12 09:39:47 +00:00
										 |  |  | 	"os" | 
					
						
							| 
									
										
										
										
											2024-08-30 14:03:59 +02:00
										 |  |  | 	"path" | 
					
						
							| 
									
										
										
										
											2024-07-12 09:39:47 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-26 15:34:10 +02:00
										 |  |  | 	"code.superseriousbusiness.org/gotosocial/internal/gtserror" | 
					
						
							| 
									
										
										
										
											2024-07-12 09:39:47 +00:00
										 |  |  | 	"codeberg.org/gruf/go-bytesize" | 
					
						
							|  |  |  | 	"codeberg.org/gruf/go-iotools" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-30 14:03:59 +02:00
										 |  |  | // file represents one file | 
					
						
							|  |  |  | // with the given flag and perms. | 
					
						
							|  |  |  | type file struct { | 
					
						
							|  |  |  | 	abs  string | 
					
						
							|  |  |  | 	flag int | 
					
						
							|  |  |  | 	perm os.FileMode | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // allowFiles implements fs.FS to allow | 
					
						
							|  |  |  | // access to a specified slice of files. | 
					
						
							|  |  |  | type allowFiles []file | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Open implements fs.FS. | 
					
						
							|  |  |  | func (af allowFiles) Open(name string) (fs.File, error) { | 
					
						
							|  |  |  | 	for _, file := range af { | 
					
						
							|  |  |  | 		var ( | 
					
						
							|  |  |  | 			abs  = file.abs | 
					
						
							|  |  |  | 			flag = file.flag | 
					
						
							|  |  |  | 			perm = file.perm | 
					
						
							|  |  |  | 		) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Allowed to open file | 
					
						
							|  |  |  | 		// at absolute path. | 
					
						
							|  |  |  | 		if name == file.abs { | 
					
						
							|  |  |  | 			return os.OpenFile(abs, flag, perm) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Check for other valid reads. | 
					
						
							|  |  |  | 		thisDir, thisFile := path.Split(file.abs) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Allowed to read directory itself. | 
					
						
							|  |  |  | 		if name == thisDir || name == "." { | 
					
						
							|  |  |  | 			return os.OpenFile(thisDir, flag, perm) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Allowed to read file | 
					
						
							|  |  |  | 		// itself (at relative path). | 
					
						
							|  |  |  | 		if name == thisFile { | 
					
						
							|  |  |  | 			return os.OpenFile(abs, flag, perm) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return nil, os.ErrPermission | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-08 17:12:13 +00:00
										 |  |  | // getExtension splits file extension from path. | 
					
						
							|  |  |  | func getExtension(path string) string { | 
					
						
							|  |  |  | 	for i := len(path) - 1; i >= 0 && path[i] != '/'; i-- { | 
					
						
							|  |  |  | 		if path[i] == '.' { | 
					
						
							|  |  |  | 			return path[i+1:] | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return "" | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-12 09:39:47 +00:00
										 |  |  | // drainToTmp drains data from given reader into a new temp file | 
					
						
							|  |  |  | // and closes it, returning the path of the resulting temp file. | 
					
						
							| 
									
										
										
										
											2023-11-10 19:29:26 +01:00
										 |  |  | // | 
					
						
							| 
									
										
										
										
											2024-07-12 09:39:47 +00:00
										 |  |  | // Note that this function specifically makes attempts to unwrap the | 
					
						
							|  |  |  | // io.ReadCloser as much as it can to underlying type, to maximise | 
					
						
							|  |  |  | // chance that Linux's sendfile syscall can be utilised for optimal | 
					
						
							|  |  |  | // draining of data source to temporary file storage. | 
					
						
							|  |  |  | func drainToTmp(rc io.ReadCloser) (string, error) { | 
					
						
							| 
									
										
										
										
											2024-08-02 14:11:24 +00:00
										 |  |  | 	defer rc.Close() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Open new temporary file. | 
					
						
							|  |  |  | 	tmp, err := os.CreateTemp( | 
					
						
							|  |  |  | 		os.TempDir(), | 
					
						
							|  |  |  | 		"gotosocial-*", | 
					
						
							|  |  |  | 	) | 
					
						
							| 
									
										
										
										
											2024-07-12 09:39:47 +00:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return "", err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	defer tmp.Close() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Extract file path. | 
					
						
							|  |  |  | 	path := tmp.Name() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Limited reader (if any). | 
					
						
							|  |  |  | 	var lr *io.LimitedReader | 
					
						
							|  |  |  | 	var limit int64 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Reader type to use | 
					
						
							|  |  |  | 	// for draining to tmp. | 
					
						
							|  |  |  | 	rd := (io.Reader)(rc) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Check if reader is actually wrapped, | 
					
						
							|  |  |  | 	// (as our http client wraps close func). | 
					
						
							|  |  |  | 	rct, ok := rc.(*iotools.ReadCloserType) | 
					
						
							|  |  |  | 	if ok { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Get unwrapped. | 
					
						
							|  |  |  | 		rd = rct.Reader | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Extract limited reader if wrapped. | 
					
						
							|  |  |  | 		lr, limit = iotools.GetReaderLimit(rd) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Drain reader into tmp. | 
					
						
							|  |  |  | 	_, err = tmp.ReadFrom(rd) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return path, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Check to see if limit was reached, | 
					
						
							|  |  |  | 	// (produces more useful error messages). | 
					
						
							| 
									
										
										
										
											2024-09-27 11:15:53 +00:00
										 |  |  | 	if lr != nil && lr.N <= 0 { | 
					
						
							| 
									
										
										
										
											2024-10-16 14:13:58 +02:00
										 |  |  | 		err := fmt.Errorf("reached read limit %s", bytesize.Size(limit)) // #nosec G115 -- Just logging | 
					
						
							| 
									
										
										
										
											2024-09-27 11:15:53 +00:00
										 |  |  | 		return path, gtserror.SetLimitReached(err) | 
					
						
							| 
									
										
										
										
											2024-07-12 09:39:47 +00:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return path, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // remove only removes paths if not-empty. | 
					
						
							|  |  |  | func remove(paths ...string) error { | 
					
						
							|  |  |  | 	var errs []error | 
					
						
							|  |  |  | 	for _, path := range paths { | 
					
						
							|  |  |  | 		if path != "" { | 
					
						
							|  |  |  | 			if err := os.Remove(path); err != nil { | 
					
						
							|  |  |  | 				errs = append(errs, fmt.Errorf("error removing %s: %w", path, err)) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return errors.Join(errs...) | 
					
						
							| 
									
										
										
										
											2023-11-10 19:29:26 +01:00
										 |  |  | } |