Merge branch 'main' into clamav-prescan
This commit is contained in:
14
.github/workflows/test.yml
vendored
14
.github/workflows/test.yml
vendored
@ -29,3 +29,17 @@ jobs:
|
||||
go version
|
||||
go vet ./...
|
||||
go test ./...
|
||||
golangci:
|
||||
name: Linting
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: 1.17
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v2
|
||||
with:
|
||||
version: latest
|
||||
skip-go-installation: true
|
||||
args: "--config .golangci.yml"
|
||||
|
20
.golangci.yml
Normal file
20
.golangci.yml
Normal file
@ -0,0 +1,20 @@
|
||||
run:
|
||||
deadline: 10m
|
||||
issues-exit-code: 1
|
||||
tests: true
|
||||
|
||||
output:
|
||||
format: colored-line-number
|
||||
print-issued-lines: true
|
||||
print-linter-name: true
|
||||
|
||||
linters:
|
||||
disable:
|
||||
- deadcode
|
||||
- unused
|
||||
|
||||
issues:
|
||||
max-issues-per-linter: 0
|
||||
max-same-issues: 0
|
||||
new: false
|
||||
exclude-use-default: false
|
12
main.go
12
main.go
@ -1,8 +1,16 @@
|
||||
package main
|
||||
|
||||
import "github.com/dutchcoders/transfer.sh/cmd"
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/dutchcoders/transfer.sh/cmd"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := cmd.New()
|
||||
app.RunAndExitOnError()
|
||||
err := app.Run(os.Args)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
@ -94,7 +94,7 @@ func initHTMLTemplates() *html_template.Template {
|
||||
}
|
||||
|
||||
func healthHandler(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "Approaching Neutral Zone, all systems normal and functioning.")
|
||||
_, _ = w.Write([]byte("Approaching Neutral Zone, all systems normal and functioning."))
|
||||
}
|
||||
|
||||
func canContainsXSS(contentType string) bool {
|
||||
@ -389,7 +389,7 @@ func (s *Server) postHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
filename = url.PathEscape(filename)
|
||||
relativeURL, _ := url.Parse(path.Join(s.proxyPath, token, filename))
|
||||
fmt.Fprintln(w, getURL(r, s.proxyPort).ResolveReference(relativeURL).String())
|
||||
_, _ = w.Write([]byte(getURL(r, s.proxyPort).ResolveReference(relativeURL).String()))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -454,7 +454,7 @@ func (s *Server) putHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
contentLength := r.ContentLength
|
||||
|
||||
defer r.Body.Close()
|
||||
defer CloseCheck(r.Body.Close)
|
||||
|
||||
file, err := ioutil.TempFile(s.tempPath, "transfer-")
|
||||
defer s.cleanTmpFile(file)
|
||||
@ -552,7 +552,7 @@ func (s *Server) putHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
w.Header().Set("X-Url-Delete", resolveURL(r, deleteURL, s.proxyPort))
|
||||
|
||||
fmt.Fprint(w, resolveURL(r, relativeURL, s.proxyPort))
|
||||
_, _ = w.Write([]byte(resolveURL(r, relativeURL, s.proxyPort)))
|
||||
}
|
||||
|
||||
func resolveURL(r *http.Request, u *url.URL, proxyPort string) string {
|
||||
@ -562,13 +562,9 @@ func resolveURL(r *http.Request, u *url.URL, proxyPort string) string {
|
||||
}
|
||||
|
||||
func resolveKey(key, proxyPath string) string {
|
||||
if strings.HasPrefix(key, "/") {
|
||||
key = key[1:]
|
||||
}
|
||||
key = strings.TrimPrefix(key, "/")
|
||||
|
||||
if strings.HasPrefix(key, proxyPath) {
|
||||
key = key[len(proxyPath):]
|
||||
}
|
||||
key = strings.TrimPrefix(key, proxyPath)
|
||||
|
||||
key = strings.Replace(key, "\\", "/", -1)
|
||||
|
||||
@ -576,18 +572,18 @@ func resolveKey(key, proxyPath string) string {
|
||||
}
|
||||
|
||||
func resolveWebAddress(r *http.Request, proxyPath string, proxyPort string) string {
|
||||
url := getURL(r, proxyPort)
|
||||
rUrl := getURL(r, proxyPort)
|
||||
|
||||
var webAddress string
|
||||
|
||||
if len(proxyPath) == 0 {
|
||||
webAddress = fmt.Sprintf("%s://%s/",
|
||||
url.ResolveReference(url).Scheme,
|
||||
url.ResolveReference(url).Host)
|
||||
rUrl.ResolveReference(rUrl).Scheme,
|
||||
rUrl.ResolveReference(rUrl).Host)
|
||||
} else {
|
||||
webAddress = fmt.Sprintf("%s://%s/%s",
|
||||
url.ResolveReference(url).Scheme,
|
||||
url.ResolveReference(url).Host,
|
||||
rUrl.ResolveReference(rUrl).Scheme,
|
||||
rUrl.ResolveReference(rUrl).Host,
|
||||
proxyPath)
|
||||
}
|
||||
|
||||
@ -647,7 +643,7 @@ func (metadata metadata) remainingLimitHeaderValues() (remainingDownloads, remai
|
||||
if metadata.MaxDate.IsZero() {
|
||||
remainingDays = "n/a"
|
||||
} else {
|
||||
timeDifference := metadata.MaxDate.Sub(time.Now())
|
||||
timeDifference := time.Until(metadata.MaxDate)
|
||||
remainingDays = strconv.Itoa(int(timeDifference.Hours()/24) + 1)
|
||||
}
|
||||
|
||||
@ -666,8 +662,6 @@ func (s *Server) lock(token, filename string) {
|
||||
lock, _ := s.locks.LoadOrStore(key, &sync.Mutex{})
|
||||
|
||||
lock.(*sync.Mutex).Lock()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Server) unlock(token, filename string) {
|
||||
@ -685,12 +679,12 @@ func (s *Server) checkMetadata(token, filename string, increaseDownload bool) (m
|
||||
var metadata metadata
|
||||
|
||||
r, _, err := s.storage.Get(token, fmt.Sprintf("%s.metadata", filename))
|
||||
defer CloseCheck(r.Close)
|
||||
|
||||
if err != nil {
|
||||
return metadata, err
|
||||
}
|
||||
|
||||
defer r.Close()
|
||||
|
||||
if err := json.NewDecoder(r).Decode(&metadata); err != nil {
|
||||
return metadata, err
|
||||
} else if metadata.MaxDownloads != -1 && metadata.Downloads >= metadata.MaxDownloads {
|
||||
@ -721,14 +715,14 @@ func (s *Server) checkDeletionToken(deletionToken, token, filename string) error
|
||||
var metadata metadata
|
||||
|
||||
r, _, err := s.storage.Get(token, fmt.Sprintf("%s.metadata", filename))
|
||||
defer CloseCheck(r.Close)
|
||||
|
||||
if s.storage.IsNotExist(err) {
|
||||
return errors.New("metadata doesn't exist")
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer r.Close()
|
||||
|
||||
if err := json.NewDecoder(r).Decode(&metadata); err != nil {
|
||||
return err
|
||||
} else if metadata.DeletionToken != deletionToken {
|
||||
@ -742,9 +736,9 @@ func (s *Server) purgeHandler() {
|
||||
ticker := time.NewTicker(s.purgeInterval)
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
<-ticker.C
|
||||
err := s.storage.Purge(s.purgeDays)
|
||||
if err != nil {
|
||||
s.logger.Printf("error cleaning up expired files: %v", err)
|
||||
}
|
||||
}
|
||||
@ -800,6 +794,7 @@ func (s *Server) zipHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
reader, _, err := s.storage.Get(token, filename)
|
||||
defer CloseCheck(reader.Close)
|
||||
|
||||
if err != nil {
|
||||
if s.storage.IsNotExist(err) {
|
||||
@ -812,13 +807,11 @@ func (s *Server) zipHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
defer reader.Close()
|
||||
|
||||
header := &zip.FileHeader{
|
||||
Name: strings.Split(key, "/")[1],
|
||||
Method: zip.Store,
|
||||
ModifiedTime: uint16(time.Now().UnixNano()),
|
||||
ModifiedDate: uint16(time.Now().UnixNano()),
|
||||
|
||||
Modified: time.Now().UTC(),
|
||||
}
|
||||
|
||||
fw, err := zw.CreateHeader(header)
|
||||
@ -854,11 +847,11 @@ func (s *Server) tarGzHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", tarfilename))
|
||||
w.Header().Set("Connection", "close")
|
||||
|
||||
os := gzip.NewWriter(w)
|
||||
defer os.Close()
|
||||
gw := gzip.NewWriter(w)
|
||||
defer CloseCheck(gw.Close)
|
||||
|
||||
zw := tar.NewWriter(os)
|
||||
defer zw.Close()
|
||||
zw := tar.NewWriter(gw)
|
||||
defer CloseCheck(zw.Close)
|
||||
|
||||
for _, key := range strings.Split(files, ",") {
|
||||
key = resolveKey(key, s.proxyPath)
|
||||
@ -872,6 +865,8 @@ func (s *Server) tarGzHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
reader, contentLength, err := s.storage.Get(token, filename)
|
||||
defer CloseCheck(reader.Close)
|
||||
|
||||
if err != nil {
|
||||
if s.storage.IsNotExist(err) {
|
||||
http.Error(w, "File not found", 404)
|
||||
@ -883,8 +878,6 @@ func (s *Server) tarGzHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
defer reader.Close()
|
||||
|
||||
header := &tar.Header{
|
||||
Name: strings.Split(key, "/")[1],
|
||||
Size: int64(contentLength),
|
||||
@ -917,7 +910,7 @@ func (s *Server) tarHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Connection", "close")
|
||||
|
||||
zw := tar.NewWriter(w)
|
||||
defer zw.Close()
|
||||
defer CloseCheck(zw.Close)
|
||||
|
||||
for _, key := range strings.Split(files, ",") {
|
||||
key = resolveKey(key, s.proxyPath)
|
||||
@ -931,6 +924,8 @@ func (s *Server) tarHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
reader, contentLength, err := s.storage.Get(token, filename)
|
||||
defer CloseCheck(reader.Close)
|
||||
|
||||
if err != nil {
|
||||
if s.storage.IsNotExist(err) {
|
||||
http.Error(w, "File not found", 404)
|
||||
@ -942,8 +937,6 @@ func (s *Server) tarHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
defer reader.Close()
|
||||
|
||||
header := &tar.Header{
|
||||
Name: strings.Split(key, "/")[1],
|
||||
Size: int64(contentLength),
|
||||
@ -1015,6 +1008,8 @@ func (s *Server) getHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
contentType := metadata.ContentType
|
||||
reader, contentLength, err := s.storage.Get(token, filename)
|
||||
defer CloseCheck(reader.Close)
|
||||
|
||||
if s.storage.IsNotExist(err) {
|
||||
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||
return
|
||||
@ -1024,8 +1019,6 @@ func (s *Server) getHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
defer reader.Close()
|
||||
|
||||
var disposition string
|
||||
|
||||
if action == "inline" {
|
||||
@ -1049,14 +1042,14 @@ func (s *Server) getHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if w.Header().Get("Range") != "" || strings.HasPrefix(metadata.ContentType, "video") || strings.HasPrefix(metadata.ContentType, "audio") {
|
||||
file, err := ioutil.TempFile(s.tempPath, "range-")
|
||||
defer s.cleanTmpFile(file)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Printf("%s", err.Error())
|
||||
http.Error(w, "Error occurred copying to output stream", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
defer s.cleanTmpFile(file)
|
||||
|
||||
_, err = io.Copy(file, reader)
|
||||
if err != nil {
|
||||
s.logger.Printf("%s", err.Error())
|
||||
@ -1073,8 +1066,6 @@ func (s *Server) getHandler(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, "Error occurred copying to output stream", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// RedirectHandler handles redirect
|
||||
@ -1129,7 +1120,6 @@ func ipFilterHandler(h http.Handler, ipFilterOptions *IPFilterOptions) http.Hand
|
||||
} else {
|
||||
WrapIPFilter(h, *ipFilterOptions).ServeHTTP(w, r)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@ -1143,13 +1133,13 @@ func (s *Server) basicAuthHandler(h http.Handler) http.HandlerFunc {
|
||||
w.Header().Set("WWW-Authenticate", "Basic realm=\"Restricted\"")
|
||||
|
||||
username, password, authOK := r.BasicAuth()
|
||||
if authOK == false {
|
||||
http.Error(w, "Not authorized", 401)
|
||||
if !authOK {
|
||||
http.Error(w, "Not authorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
if username != s.AuthUser || password != s.AuthPass {
|
||||
http.Error(w, "Not authorized", 401)
|
||||
http.Error(w, "Not authorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -401,7 +401,7 @@ func (s *Server) Run() {
|
||||
go func() {
|
||||
s.logger.Println("Profiled listening at: :6060")
|
||||
|
||||
http.ListenAndServe(":6060", nil)
|
||||
_ = http.ListenAndServe(":6060", nil)
|
||||
}()
|
||||
}
|
||||
|
||||
@ -432,8 +432,14 @@ func (s *Server) Run() {
|
||||
s.logger.Panicf("Unable to parse: path=%s, err=%s", path, err)
|
||||
}
|
||||
|
||||
htmlTemplates.New(stripPrefix(path)).Parse(string(bytes))
|
||||
textTemplates.New(stripPrefix(path)).Parse(string(bytes))
|
||||
_, err = htmlTemplates.New(stripPrefix(path)).Parse(string(bytes))
|
||||
if err != nil {
|
||||
s.logger.Panicln("Unable to parse template")
|
||||
}
|
||||
_, err = textTemplates.New(stripPrefix(path)).Parse(string(bytes))
|
||||
if err != nil {
|
||||
s.logger.Panicln("Unable to parse template")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -501,7 +507,7 @@ func (s *Server) Run() {
|
||||
|
||||
r.NotFoundHandler = http.HandlerFunc(s.notFoundHandler)
|
||||
|
||||
mime.AddExtensionType(".md", "text/x-markdown")
|
||||
_ = mime.AddExtensionType(".md", "text/x-markdown")
|
||||
|
||||
s.logger.Printf("Transfer.sh server started.\nusing temp folder: %s\nusing storage provider: %s", s.tempPath, s.storage.Type())
|
||||
|
||||
@ -540,7 +546,7 @@ func (s *Server) Run() {
|
||||
s.logger.Printf("listening on port: %v\n", s.ListenerString)
|
||||
|
||||
go func() {
|
||||
srvr.ListenAndServe()
|
||||
_ = srvr.ListenAndServe()
|
||||
}()
|
||||
}
|
||||
|
||||
|
@ -99,7 +99,7 @@ func (s *LocalStorage) Get(token string, filename string) (reader io.ReadCloser,
|
||||
// Delete removes a file from storage
|
||||
func (s *LocalStorage) Delete(token string, filename string) (err error) {
|
||||
metadata := filepath.Join(s.basedir, token, fmt.Sprintf("%s.metadata", filename))
|
||||
os.Remove(metadata)
|
||||
_ = os.Remove(metadata)
|
||||
|
||||
path := filepath.Join(s.basedir, token, filename)
|
||||
err = os.Remove(path)
|
||||
@ -148,12 +148,13 @@ func (s *LocalStorage) Put(token string, filename string, reader io.Reader, cont
|
||||
return err
|
||||
}
|
||||
|
||||
if f, err = os.OpenFile(filepath.Join(path, filename), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600); err != nil {
|
||||
f, err = os.OpenFile(filepath.Join(path, filename), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
|
||||
defer CloseCheck(f.Close)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer f.Close()
|
||||
|
||||
if _, err = io.Copy(f, reader); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -337,7 +338,8 @@ func NewGDriveStorage(clientJSONFilepath string, localConfigPath string, basedir
|
||||
return nil, err
|
||||
}
|
||||
|
||||
srv, err := drive.New(getGDriveClient(config, localConfigPath, logger))
|
||||
// ToDo: Upgrade deprecated version
|
||||
srv, err := drive.New(getGDriveClient(config, localConfigPath, logger)) // nolint: staticcheck
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -427,7 +429,7 @@ func (s *GDrive) findID(filename string, token string) (string, error) {
|
||||
if filename == "" {
|
||||
return tokenID, nil
|
||||
} else if tokenID == "" {
|
||||
return "", fmt.Errorf("Cannot find file %s/%s", token, filename)
|
||||
return "", fmt.Errorf("cannot find file %s/%s", token, filename)
|
||||
}
|
||||
|
||||
q = fmt.Sprintf("'%s' in parents and name='%s' and mimeType!='%s' and trashed=false", tokenID, filename, gdriveDirectoryMimeType)
|
||||
@ -454,7 +456,7 @@ func (s *GDrive) findID(filename string, token string) (string, error) {
|
||||
}
|
||||
|
||||
if fileID == "" {
|
||||
return "", fmt.Errorf("Cannot find file %s/%s", token, filename)
|
||||
return "", fmt.Errorf("cannot find file %s/%s", token, filename)
|
||||
}
|
||||
|
||||
return fileID, nil
|
||||
@ -493,8 +495,11 @@ func (s *GDrive) Get(token string, filename string) (reader io.ReadCloser, conte
|
||||
|
||||
var fi *drive.File
|
||||
fi, err = s.service.Files.Get(fileID).Fields("size", "md5Checksum").Do()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !s.hasChecksum(fi) {
|
||||
err = fmt.Errorf("Cannot find file %s/%s", token, filename)
|
||||
err = fmt.Errorf("cannot find file %s/%s", token, filename)
|
||||
return
|
||||
}
|
||||
|
||||
@ -515,7 +520,7 @@ func (s *GDrive) Get(token string, filename string) (reader io.ReadCloser, conte
|
||||
// Delete removes a file from storage
|
||||
func (s *GDrive) Delete(token string, filename string) (err error) {
|
||||
metadata, _ := s.findID(fmt.Sprintf("%s.metadata", filename), token)
|
||||
s.service.Files.Delete(metadata).Do()
|
||||
_ = s.service.Files.Delete(metadata).Do()
|
||||
|
||||
var fileID string
|
||||
fileID, err = s.findID(filename, token)
|
||||
@ -584,6 +589,7 @@ func (s *GDrive) Put(token string, filename string, reader io.Reader, contentTyp
|
||||
Name: token,
|
||||
Parents: []string{s.rootID},
|
||||
MimeType: gdriveDirectoryMimeType,
|
||||
Size: int64(contentLength),
|
||||
}
|
||||
|
||||
di, err := s.service.Files.Create(dir).Fields("id").Do()
|
||||
@ -644,7 +650,7 @@ func getGDriveTokenFromWeb(config *oauth2.Config, logger *log.Logger) *oauth2.To
|
||||
// Retrieves a token from a local file.
|
||||
func gDriveTokenFromFile(file string) (*oauth2.Token, error) {
|
||||
f, err := os.Open(file)
|
||||
defer f.Close()
|
||||
defer CloseCheck(f.Close)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -657,12 +663,15 @@ func gDriveTokenFromFile(file string) (*oauth2.Token, error) {
|
||||
func saveGDriveToken(path string, token *oauth2.Token, logger *log.Logger) {
|
||||
logger.Printf("Saving credential file to: %s\n", path)
|
||||
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
|
||||
defer f.Close()
|
||||
defer CloseCheck(f.Close)
|
||||
if err != nil {
|
||||
logger.Fatalf("Unable to cache oauth token: %v", err)
|
||||
}
|
||||
|
||||
json.NewEncoder(f).Encode(token)
|
||||
err = json.NewEncoder(f).Encode(token)
|
||||
if err != nil {
|
||||
logger.Fatalf("Unable to encode oauth token: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// StorjStorage is a storage backed by Storj
|
||||
|
@ -279,3 +279,9 @@ func formatSize(size int64) string {
|
||||
getSuffix := suffixes[int(math.Floor(base))]
|
||||
return fmt.Sprintf("%s %s", strconv.FormatFloat(newVal, 'f', -1, 64), getSuffix)
|
||||
}
|
||||
|
||||
func CloseCheck(f func() error) {
|
||||
if err := f(); err != nil {
|
||||
fmt.Println("Received close error:", err)
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,6 @@ package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
@ -49,9 +48,7 @@ func (s *Server) virusTotalHandler(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, err.Error(), 500)
|
||||
}
|
||||
|
||||
var reader io.Reader
|
||||
|
||||
reader = r.Body
|
||||
reader := r.Body
|
||||
|
||||
result, err := vt.Scan(filename, reader)
|
||||
if err != nil {
|
||||
@ -59,5 +56,5 @@ func (s *Server) virusTotalHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
s.logger.Println(result)
|
||||
w.Write([]byte(fmt.Sprintf("%v\n", result.Permalink)))
|
||||
_, _ = w.Write([]byte(fmt.Sprintf("%v\n", result.Permalink)))
|
||||
}
|
||||
|
Reference in New Issue
Block a user