diff --git a/AUTHORS b/AUTHORS index 55debcfd8..89b21a3b4 100644 --- a/AUTHORS +++ b/AUTHORS @@ -277,6 +277,7 @@ Oyebanji Jacob Mayowa Pablo Pascal Jungblut (pascalj) Paul Brit +Paul Donald Pawel Palenica (qepasa) Paweł Rozlach perewa @@ -327,6 +328,7 @@ Syncthing Release Automation Sébastien WENSKE Taylor Khan (nelsonkhan) Terrance +TheCreeper Thomas <9749173+uhthomas@users.noreply.github.com> Thomas Hipp Tim Abell (timabell) diff --git a/build.sh b/build.sh index 643d5b4d4..5ea6c7fc3 100755 --- a/build.sh +++ b/build.sh @@ -23,6 +23,7 @@ case "${1:-default}" in prerelease) script authors + script copyrights build weblate pushd man ; ./refresh.sh ; popd git add -A gui man AUTHORS diff --git a/cmd/syncthing/main.go b/cmd/syncthing/main.go index 9968fbfc1..b9b97d697 100644 --- a/cmd/syncthing/main.go +++ b/cmd/syncthing/main.go @@ -533,8 +533,19 @@ func (c *serveCmd) syncthingMain() { Verbose: c.Verbose, DBMaintenanceInterval: c.DBMaintenanceInterval, } - if c.Audit { - appOpts.AuditWriter = auditWriter(c.AuditFile) + + if c.Audit || cfgWrapper.Options().AuditEnabled { + l.Infoln("Auditing is enabled.") + + auditFile := cfgWrapper.Options().AuditFile + + // Ignore config option if command-line option is set + if c.AuditFile != "" { + l.Debugln("Using the audit file from the command-line parameter.") + auditFile = c.AuditFile + } + + appOpts.AuditWriter = auditWriter(auditFile) } app, err := syncthing.New(cfgWrapper, sdb, evLogger, cert, appOpts) diff --git a/gui/default/assets/lang/lang-et.json b/gui/default/assets/lang/lang-et.json index 13698f4c0..048b18fda 100644 --- a/gui/default/assets/lang/lang-et.json +++ b/gui/default/assets/lang/lang-et.json @@ -9,9 +9,15 @@ "Add Folder": "Lisa kaust", "Add new folder?": "Lisa uus kaust?", "Address": "Aadress", + "Addresses": "Aadressid", + "All Data": "Kõik andmed", + "All Time": "Kõik ajad", + "Allowed Networks": "Lubatud võrgud", "Alphabetic": "Tähestikuline", "Automatic upgrades": "Automaatsed uuendused", "Be careful!": "Ettevaatust!", + "Cancel": "Loobu", + "Changelog": "Muudatuste nimekiri", "Close": "Sulge", "Configured": "Seadistatud", "Connection Error": "Ühenduse viga", diff --git a/gui/default/syncthing/core/aboutModalView.html b/gui/default/syncthing/core/aboutModalView.html index 63363bfd5..8e82ba32f 100644 --- a/gui/default/syncthing/core/aboutModalView.html +++ b/gui/default/syncthing/core/aboutModalView.html @@ -30,7 +30,7 @@

The Syncthing Authors

-Jakob Borg, Audrius Butkevicius, Jesse Lucas, Simon Frei, Tomasz Wilczyński, Alexander Graf, Alexandre Viau, Anderson Mesquita, André Colomb, Antony Male, Ben Schulz, Caleb Callaway, Daniel Harte, Emil Lundberg, Eric P, Evgeny Kuznetsov, Lars K.W. Gohlke, Lode Hoste, Michael Ploujnikov, Nate Morrison, Philippe Schommers, Ross Smith II, Ryan Sullivan, Sergey Mishin, Stefan Tatschner, Wulf Weich, bt90, greatroar, Aaron Bieber, Adam Piggott, Adel Qalieh, Alan Pope, Alberto Donato, Aleksey Vasenev, Alessandro G., Alex Ionescu, Alex Lindeman, Alex Xu, Alexander Seiler, Alexandre Alves, Aman Gupta, Anatoli Babenia, Andreas Sommer, Andrew Dunham, Andrew Meyer, Andrew Rabert, Andrey D, Anjan Momi, Anthony Goeckner, Antoine Lamielle, Anur, Aranjedeath, Arkadiusz Tymiński, Aroun, Arthur Axel fREW Schmidt, Artur Zubilewicz, Aurélien Rainone, BAHADIR YILMAZ, Bart De Vries, Beat Reichenbach, Ben Curthoys, Ben Shepherd, Ben Sidhom, Benedikt Heine, Benedikt Morbach, Benjamin Nater, Benno Fünfstück, Benny Ng, Boqin Qin, Boris Rybalkin, Brandon Philips, Brendan Long, Brian R. Becker, Carsten Hagemann, Catfriend1, Cathryne Linenweaver, Cedric Staniewski, Chih-Hsuan Yen, Choongkyu, Chris Howie, Chris Joel, Chris Tonkinson, Christian Kujau, Christian Prescott, Colin Kennedy, Cromefire_, Cyprien Devillez, Dale Visser, Dan, Daniel Barczyk, Daniel Bergmann, Daniel Martí, Daniel Padrta, Darshil Chanpura, David Rimmer, DeflateAwning, Denis A., Dennis Wilson, DerRockWolf, Devon G. Redekopp, Dimitri Papadopoulos Orfanos, Dmitry Saveliev, Domenic Horner, Dominik Heidler, Elias Jarlebring, Elliot Huffman, Emil Hessman, Eng Zer Jun, Eric Lesiuta, Erik Meitner, Evan Spensley, Federico Castagnini, Felix, Felix Ableitner, Felix Lampe, Felix Unterpaintner, Francois-Xavier Gsell, Frank Isemann, Gahl Saraf, Gilli Sigurdsson, Gleb Sinyavskiy, Graham Miln, Greg, Gusted, Han Boetes, HansK-p, Harrison Jones, Heiko Zuerker, Hireworks, Hugo Locurcio, Iain Barnett, Ian Johnson, Ikko Ashimine, Ilya Brin, Iskander Sharipov, Jaakko Hannikainen, Jacek Szafarkiewicz, Jack Croft, Jacob, Jake Peterson, James O'Beirne, James Patterson, Jaroslav Lichtblau, Jaroslav Malec, Jaspitta, Jauder Ho, Jaya Chithra, Jaya Kumar, Jeffery To, Jens Diemer, Jerry Jacobs, Jochen Voss, Johan Andersson, Johan Vromans, John Rinehart, Jonas Thelemann, Jonathan, Jonathan Cross, Jonta, Jose Manuel Delicado, Julian Lehrhuber, Jörg Thalheim, Jędrzej Kula, K.B.Dharun Krishna, Kalle Laine, Kapil Sareen, Karol Różycki, Kebin Liu, Keith Harrison, Keith Turner, Kelong Cong, Ken'ichi Kamada, Kevin Allen, Kevin Bushiri, Kevin White, Jr., Kurt Fitzner, LSmithx2, Lars Lehtonen, Laurent Arnoud, Laurent Etiemble, Leo Arias, Liu Siyuan, Lord Landon Agahnim, Lukas Lihotzki, Luke Hamburg, Majed Abdulaziz, Marc Laporte, Marc Pujol, Marcin Dziadus, Marcus B Spencer, Marcus Legendre, Mario Majila, Mark Pulford, Martchus, Martin Polehla, Mateusz Naściszewski, Mateusz Ż, Matic Potočnik, Matt Burke, Matt Robenolt, Matteo Ruina, Maurizio Tomasi, Max, Max Schulze, MaximAL, Maxime Thirouin, Maximilian, MichaIng, Michael Jephcote, Michael Rienstra, Michael Tilli, Migelo, Mike Boone, MikeLund, MikolajTwarog, Mingxuan Lin, Naveen, Nicholas Rishel, Nick Busey, Nico Stapelbroek, Nicolas Braud-Santoni, Nicolas Perraut, Niels Peter Roest, Nils Jakobi, NinoM4ster, Nitroretro, NoLooseEnds, Oliver Freyermuth, Otiel, Oyebanji Jacob Mayowa, Pablo, Pascal Jungblut, Paul Brit, Pawel Palenica, Paweł Rozlach, Peter Badida, Peter Dave Hello, Peter Hoeg, Peter Marquardt, Phani Rithvij, Phil Davis, Phill Luby, Pier Paolo Ramon, Piotr Bejda, Pramodh KP, Quentin Hibon, Rahmi Pruitt, Richard Hartmann, Robert Carosi, Roberto Santalla, Robin Schoonover, Roman Zaynetdinov, Ruslan Yevdokymov, Ryan Qian, Sacheendra Talluri, Scott Klupfel, Sertonix, Severin von Wnuck-Lipinski, Shaarad Dalvi, Simon Mwepu, Simon Pickup, Sly_tom_cat, Sonu Kumar Saw, Stefan Kuntz, Steven Eckhoff, Suhas Gundimeda, Sven Bachmann, Sébastien WENSKE, Taylor Khan, Terrance, Thomas, Thomas Hipp, Tim Abell, Tim Howes, Tim Nordenfur, Tobias Frölich, Tobias Klauser, Tobias Nygren, Tobias Tom, Tom Jakubowski, Tommy Thorn, Tommy van der Vorst, Tully Robinson, Tyler Brazier, Tyler Kropp, Unrud, Veeti Paananen, Victor Buinsky, Vik, Vil Brekin, Vladimir Rusinov, WangXi, Will Rouesnel, William A. Kennington III, Xavier O., Yannic A., andresvia, andyleap, boomsquared, chenrui, chucic, cjc7373, cui fliter, d-volution, dashangcun, derekriemer, desbma, diemade, digital, entity0xfe, georgespatton, ghjklw, guangwu, gudvinr, ignacy123, janost, jaseg, jelle van der Waa, jtagcat, klemens, kylosus, luchenhan, luzpaz, marco-m, mathias4833, maxice8, mclang, mv1005, nf, orangekame3, otbutz, overkill, perewa, polyfloyd, red_led, rubenbe, sec65, vapatel2, villekalliomaki, wangguoliang, wouter bolsterlee, xarx00, xjtdy888, 佛跳墙, 落心 +Jakob Borg, Audrius Butkevicius, Jesse Lucas, Simon Frei, Tomasz Wilczyński, Alexander Graf, Alexandre Viau, Anderson Mesquita, André Colomb, Antony Male, Ben Schulz, Caleb Callaway, Daniel Harte, Emil Lundberg, Eric P, Evgeny Kuznetsov, Lars K.W. Gohlke, Lode Hoste, Michael Ploujnikov, Nate Morrison, Philippe Schommers, Ross Smith II, Ryan Sullivan, Sergey Mishin, Stefan Tatschner, Wulf Weich, bt90, greatroar, Aaron Bieber, Adam Piggott, Adel Qalieh, Alan Pope, Alberto Donato, Aleksey Vasenev, Alessandro G., Alex Ionescu, Alex Lindeman, Alex Xu, Alexander Seiler, Alexandre Alves, Aman Gupta, Anatoli Babenia, Andreas Sommer, Andrew Dunham, Andrew Meyer, Andrew Rabert, Andrey D, Anjan Momi, Anthony Goeckner, Antoine Lamielle, Anur, Aranjedeath, Arkadiusz Tymiński, Aroun, Arthur Axel fREW Schmidt, Artur Zubilewicz, Aurélien Rainone, BAHADIR YILMAZ, Bart De Vries, Beat Reichenbach, Ben Curthoys, Ben Shepherd, Ben Sidhom, Benedikt Heine, Benedikt Morbach, Benjamin Nater, Benno Fünfstück, Benny Ng, Boqin Qin, Boris Rybalkin, Brandon Philips, Brendan Long, Brian R. Becker, Carsten Hagemann, Catfriend1, Cathryne Linenweaver, Cedric Staniewski, Chih-Hsuan Yen, Choongkyu, Chris Howie, Chris Joel, Chris Tonkinson, Christian Kujau, Christian Prescott, Colin Kennedy, Cromefire_, Cyprien Devillez, Dale Visser, Dan, Daniel Barczyk, Daniel Bergmann, Daniel Martí, Daniel Padrta, Darshil Chanpura, David Rimmer, DeflateAwning, Denis A., Dennis Wilson, DerRockWolf, Devon G. Redekopp, Dimitri Papadopoulos Orfanos, Dmitry Saveliev, Domenic Horner, Dominik Heidler, Elias Jarlebring, Elliot Huffman, Emil Hessman, Eng Zer Jun, Eric Lesiuta, Erik Meitner, Evan Spensley, Federico Castagnini, Felix, Felix Ableitner, Felix Lampe, Felix Unterpaintner, Francois-Xavier Gsell, Frank Isemann, Gahl Saraf, Gilli Sigurdsson, Gleb Sinyavskiy, Graham Miln, Greg, Gusted, Han Boetes, HansK-p, Harrison Jones, Heiko Zuerker, Hireworks, Hugo Locurcio, Iain Barnett, Ian Johnson, Ikko Ashimine, Ilya Brin, Iskander Sharipov, Jaakko Hannikainen, Jacek Szafarkiewicz, Jack Croft, Jacob, Jake Peterson, James O'Beirne, James Patterson, Jaroslav Lichtblau, Jaroslav Malec, Jaspitta, Jauder Ho, Jaya Chithra, Jaya Kumar, Jeffery To, Jens Diemer, Jerry Jacobs, Jochen Voss, Johan Andersson, Johan Vromans, John Rinehart, Jonas Thelemann, Jonathan, Jonathan Cross, Jonta, Jose Manuel Delicado, Julian Lehrhuber, Jörg Thalheim, Jędrzej Kula, K.B.Dharun Krishna, Kalle Laine, Kapil Sareen, Karol Różycki, Kebin Liu, Keith Harrison, Keith Turner, Kelong Cong, Ken'ichi Kamada, Kevin Allen, Kevin Bushiri, Kevin White, Jr., Kurt Fitzner, LSmithx2, Lars Lehtonen, Laurent Arnoud, Laurent Etiemble, Leo Arias, Liu Siyuan, Lord Landon Agahnim, Lukas Lihotzki, Luke Hamburg, Majed Abdulaziz, Marc Laporte, Marc Pujol, Marcin Dziadus, Marcus B Spencer, Marcus Legendre, Mario Majila, Mark Pulford, Martchus, Martin Polehla, Mateusz Naściszewski, Mateusz Ż, Matic Potočnik, Matt Burke, Matt Robenolt, Matteo Ruina, Maurizio Tomasi, Max, Max Schulze, MaximAL, Maxime Thirouin, Maximilian, MichaIng, Michael Jephcote, Michael Rienstra, Michael Tilli, Migelo, Mike Boone, MikeLund, MikolajTwarog, Mingxuan Lin, Naveen, Nicholas Rishel, Nick Busey, Nico Stapelbroek, Nicolas Braud-Santoni, Nicolas Perraut, Niels Peter Roest, Nils Jakobi, NinoM4ster, Nitroretro, NoLooseEnds, Oliver Freyermuth, Otiel, Oyebanji Jacob Mayowa, Pablo, Pascal Jungblut, Paul Brit, Paul Donald, Pawel Palenica, Paweł Rozlach, Peter Badida, Peter Dave Hello, Peter Hoeg, Peter Marquardt, Phani Rithvij, Phil Davis, Phill Luby, Pier Paolo Ramon, Piotr Bejda, Pramodh KP, Quentin Hibon, Rahmi Pruitt, Richard Hartmann, Robert Carosi, Roberto Santalla, Robin Schoonover, Roman Zaynetdinov, Ruslan Yevdokymov, Ryan Qian, Sacheendra Talluri, Scott Klupfel, Sertonix, Severin von Wnuck-Lipinski, Shaarad Dalvi, Simon Mwepu, Simon Pickup, Sly_tom_cat, Sonu Kumar Saw, Stefan Kuntz, Steven Eckhoff, Suhas Gundimeda, Sven Bachmann, Sébastien WENSKE, Taylor Khan, Terrance, TheCreeper, Thomas, Thomas Hipp, Tim Abell, Tim Howes, Tim Nordenfur, Tobias Frölich, Tobias Klauser, Tobias Nygren, Tobias Tom, Tom Jakubowski, Tommy Thorn, Tommy van der Vorst, Tully Robinson, Tyler Brazier, Tyler Kropp, Unrud, Veeti Paananen, Victor Buinsky, Vik, Vil Brekin, Vladimir Rusinov, WangXi, Will Rouesnel, William A. Kennington III, Xavier O., Yannic A., andresvia, andyleap, boomsquared, chenrui, chucic, cjc7373, cui fliter, d-volution, dashangcun, derekriemer, desbma, diemade, digital, entity0xfe, georgespatton, ghjklw, guangwu, gudvinr, ignacy123, janost, jaseg, jelle van der Waa, jtagcat, klemens, kylosus, luchenhan, luzpaz, marco-m, mathias4833, maxice8, mclang, mv1005, nf, orangekame3, otbutz, overkill, perewa, polyfloyd, red_led, rubenbe, sec65, vapatel2, villekalliomaki, wangguoliang, wouter bolsterlee, xarx00, xjtdy888, 佛跳墙, 落心
@@ -38,48 +38,94 @@ Jakob Borg, Audrius Butkevicius, Jesse Lucas, Simon Frei, Tomasz Wilczyński, Al

Syncthing includes the following software or portions thereof:

diff --git a/lib/api/api_auth.go b/lib/api/api_auth.go index 791888056..8568394a8 100644 --- a/lib/api/api_auth.go +++ b/lib/api/api_auth.go @@ -18,6 +18,7 @@ import ( ldap "github.com/go-ldap/ldap/v3" "github.com/syncthing/syncthing/lib/config" "github.com/syncthing/syncthing/lib/events" + "github.com/syncthing/syncthing/lib/osutil" "github.com/syncthing/syncthing/lib/rand" ) @@ -27,15 +28,54 @@ const ( randomTokenLength = 64 ) -func emitLoginAttempt(success bool, username, address string, evLogger events.Logger) { - evLogger.Log(events.LoginAttempt, map[string]interface{}{ +func emitLoginAttempt(success bool, username string, r *http.Request, evLogger events.Logger) { + remoteAddress, proxy := remoteAddress(r) + evData := map[string]any{ "success": success, "username": username, - "remoteAddress": address, - }) - if !success { - l.Infof("Wrong credentials supplied during API authorization from %s", address) + "remoteAddress": remoteAddress, } + if proxy != "" { + evData["proxy"] = proxy + } + evLogger.Log(events.LoginAttempt, evData) + + if success { + return + } + if proxy != "" { + l.Infof("Wrong credentials supplied during API authorization from %s proxied by %s", remoteAddress, proxy) + } else { + l.Infof("Wrong credentials supplied during API authorization from %s", remoteAddress) + } +} + +func remoteAddress(r *http.Request) (remoteAddr, proxy string) { + remoteAddr = r.RemoteAddr + remoteIP := osutil.IPFromString(r.RemoteAddr) + + // parse X-Forwarded-For only if the proxy connects via unix socket, localhost or a LAN IP + var localProxy bool + if remoteIP != nil { + remoteAddr = remoteIP.String() + localProxy = remoteIP.IsLoopback() || remoteIP.IsPrivate() || remoteIP.IsLinkLocalUnicast() + } else if remoteAddr == "@" { + localProxy = true + } + + if !localProxy { + return + } + + forwardedAddr, _, _ := strings.Cut(r.Header.Get("X-Forwarded-For"), ",") + forwardedAddr = strings.TrimSpace(forwardedAddr) + forwardedIP := osutil.IPFromString(forwardedAddr) + + if forwardedIP != nil { + proxy = remoteAddr + remoteAddr = forwardedIP.String() + } + return } func antiBruteForceSleep() { @@ -152,7 +192,7 @@ func (m *basicAuthAndSessionMiddleware) passwordAuthHandler(w http.ResponseWrite return } - emitLoginAttempt(false, req.Username, r.RemoteAddr, m.evLogger) + emitLoginAttempt(false, req.Username, r, m.evLogger) antiBruteForceSleep() forbidden(w) } @@ -175,7 +215,7 @@ func attemptBasicAuth(r *http.Request, guiCfg config.GUIConfiguration, ldapCfg c return usernameFromIso, true } - emitLoginAttempt(false, username, r.RemoteAddr, evLogger) + emitLoginAttempt(false, username, r, evLogger) antiBruteForceSleep() return "", false } diff --git a/lib/api/tokenmanager.go b/lib/api/tokenmanager.go index 5bd674686..fdf9f01c9 100644 --- a/lib/api/tokenmanager.go +++ b/lib/api/tokenmanager.go @@ -189,7 +189,7 @@ func (m *tokenCookieManager) createSession(username string, persistent bool, w h Path: "/", }) - emitLoginAttempt(true, username, r.RemoteAddr, m.evLogger) + emitLoginAttempt(true, username, r, m.evLogger) } func (m *tokenCookieManager) hasValidSession(r *http.Request) bool { diff --git a/lib/config/config_test.go b/lib/config/config_test.go index daf50eb57..fd4d5250c 100644 --- a/lib/config/config_test.go +++ b/lib/config/config_test.go @@ -92,6 +92,8 @@ func TestDefaultValues(t *testing.T) { RawStunServers: []string{"default"}, AnnounceLANAddresses: true, FeatureFlags: []string{}, + AuditEnabled: false, + AuditFile: "", ConnectionPriorityTCPLAN: 10, ConnectionPriorityQUICLAN: 20, ConnectionPriorityTCPWAN: 30, @@ -295,6 +297,8 @@ func TestOverriddenValues(t *testing.T) { StunKeepaliveMinS: 900, RawStunServers: []string{"foo"}, FeatureFlags: []string{"feature"}, + AuditEnabled: true, + AuditFile: "nggyu", ConnectionPriorityTCPLAN: 40, ConnectionPriorityQUICLAN: 45, ConnectionPriorityTCPWAN: 50, diff --git a/lib/config/optionsconfiguration.go b/lib/config/optionsconfiguration.go index 1dce78c92..e97f18167 100644 --- a/lib/config/optionsconfiguration.go +++ b/lib/config/optionsconfiguration.go @@ -67,22 +67,21 @@ type OptionsConfiguration struct { AnnounceLANAddresses bool `json:"announceLANAddresses" xml:"announceLANAddresses" default:"true"` SendFullIndexOnUpgrade bool `json:"sendFullIndexOnUpgrade" xml:"sendFullIndexOnUpgrade"` FeatureFlags []string `json:"featureFlags" xml:"featureFlag"` + AuditEnabled bool `json:"auditEnabled" xml:"auditEnabled" default:"false"` + AuditFile string `json:"auditFile" xml:"auditFile"` // The number of connections at which we stop trying to connect to more // devices, zero meaning no limit. Does not affect incoming connections. ConnectionLimitEnough int `json:"connectionLimitEnough" xml:"connectionLimitEnough"` // The maximum number of connections which we will allow in total, zero // meaning no limit. Affects incoming connections and prevents // attempting outgoing connections. - ConnectionLimitMax int `json:"connectionLimitMax" xml:"connectionLimitMax"` - // When set, this allows TLS 1.2 on sync connections, where we otherwise - // default to TLS 1.3+ only. - InsecureAllowOldTLSVersions bool `json:"insecureAllowOldTLSVersions" xml:"insecureAllowOldTLSVersions"` - ConnectionPriorityTCPLAN int `json:"connectionPriorityTcpLan" xml:"connectionPriorityTcpLan" default:"10"` - ConnectionPriorityQUICLAN int `json:"connectionPriorityQuicLan" xml:"connectionPriorityQuicLan" default:"20"` - ConnectionPriorityTCPWAN int `json:"connectionPriorityTcpWan" xml:"connectionPriorityTcpWan" default:"30"` - ConnectionPriorityQUICWAN int `json:"connectionPriorityQuicWan" xml:"connectionPriorityQuicWan" default:"40"` - ConnectionPriorityRelay int `json:"connectionPriorityRelay" xml:"connectionPriorityRelay" default:"50"` - ConnectionPriorityUpgradeThreshold int `json:"connectionPriorityUpgradeThreshold" xml:"connectionPriorityUpgradeThreshold" default:"0"` + ConnectionLimitMax int `json:"connectionLimitMax" xml:"connectionLimitMax"` + ConnectionPriorityTCPLAN int `json:"connectionPriorityTcpLan" xml:"connectionPriorityTcpLan" default:"10"` + ConnectionPriorityQUICLAN int `json:"connectionPriorityQuicLan" xml:"connectionPriorityQuicLan" default:"20"` + ConnectionPriorityTCPWAN int `json:"connectionPriorityTcpWan" xml:"connectionPriorityTcpWan" default:"30"` + ConnectionPriorityQUICWAN int `json:"connectionPriorityQuicWan" xml:"connectionPriorityQuicWan" default:"40"` + ConnectionPriorityRelay int `json:"connectionPriorityRelay" xml:"connectionPriorityRelay" default:"50"` + ConnectionPriorityUpgradeThreshold int `json:"connectionPriorityUpgradeThreshold" xml:"connectionPriorityUpgradeThreshold" default:"0"` // Legacy deprecated DeprecatedUPnPEnabled bool `json:"-" xml:"upnpEnabled,omitempty"` // Deprecated: Do not use. DeprecatedUPnPLeaseM int `json:"-" xml:"upnpLeaseMinutes,omitempty"` // Deprecated: Do not use. @@ -187,7 +186,7 @@ func (opts OptionsConfiguration) StunServers() []string { case "default": _, records, err := net.LookupSRV("stun", "udp", "syncthing.net") if err != nil { - l.Warnln("Unable to resolve primary STUN servers via DNS:", err) + l.Debugf("Unable to resolve primary STUN servers via DNS:", err) } for _, record := range records { diff --git a/lib/config/testdata/overridenvalues.xml b/lib/config/testdata/overridenvalues.xml index 1c142a59f..0fb3a14b2 100644 --- a/lib/config/testdata/overridenvalues.xml +++ b/lib/config/testdata/overridenvalues.xml @@ -45,6 +45,8 @@ asdfasdf false feature + true + nggyu 40 45 50 diff --git a/lib/osutil/lowprio_linux.go b/lib/osutil/lowprio_linux.go index 5da5c7972..a76500ab6 100644 --- a/lib/osutil/lowprio_linux.go +++ b/lib/osutil/lowprio_linux.go @@ -15,32 +15,6 @@ import ( "syscall" ) -const ioprioClassShift = 13 - -type ioprioClass int - -const ( - ioprioClassRT ioprioClass = iota + 1 - ioprioClassBE - ioprioClassIdle -) - -const ( - ioprioWhoProcess = iota + 1 - ioprioWhoPGRP - ioprioWhoUser -) - -func ioprioSet(class ioprioClass, value int) error { - res, _, err := syscall.Syscall(syscall.SYS_IOPRIO_SET, - uintptr(ioprioWhoProcess), 0, - uintptr(class)<false 0 0 - false @@ -1077,6 +1076,11 @@ header do not need this setting. When this setting is disabled, the GUI will not send 401 responses so users won’t see browser popups prompting for username and password. .UNINDENT +.INDENT 0.0 +.TP +.B metricsWithoutAuth +If true, this allows access to the ‘/metrics’ without authentication. +.UNINDENT .SH LDAP ELEMENT .INDENT 0.0 .INDENT 3.5 @@ -1196,7 +1200,6 @@ Search filter for user searches. false 0 0 - false .EE .UNINDENT @@ -1533,12 +1536,6 @@ no limit. Affects incoming connections and prevents attempting outgoing connections. The mechanism is described in detail in a \fI\%separate chapter\fP\&. .UNINDENT -.INDENT 0.0 -.TP -.B insecureAllowOldTLSVersions -Only for compatibility with old versions of Syncthing on remote devices, as -detailed in \fI\%insecureAllowOldTLSVersions\fP\&. -.UNINDENT .SH DEFAULTS ELEMENT .INDENT 0.0 .INDENT 3.5 diff --git a/man/syncthing-device-ids.7 b/man/syncthing-device-ids.7 index eb380cb1b..601b9a388 100644 --- a/man/syncthing-device-ids.7 +++ b/man/syncthing-device-ids.7 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "SYNCTHING-DEVICE-IDS" "7" "Apr 04, 2025" "v1.29.3" "Syncthing" +.TH "SYNCTHING-DEVICE-IDS" "7" "Apr 21, 2025" "v1.29.3" "Syncthing" .SH NAME syncthing-device-ids \- Understanding Device IDs .sp diff --git a/man/syncthing-event-api.7 b/man/syncthing-event-api.7 index fc991ed2b..f1b2fb80c 100644 --- a/man/syncthing-event-api.7 +++ b/man/syncthing-event-api.7 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "SYNCTHING-EVENT-API" "7" "Apr 04, 2025" "v1.29.3" "Syncthing" +.TH "SYNCTHING-EVENT-API" "7" "Apr 21, 2025" "v1.29.3" "Syncthing" .SH NAME syncthing-event-api \- Event API .SH DESCRIPTION diff --git a/man/syncthing-faq.7 b/man/syncthing-faq.7 index 9eb8b0e17..2c45e8597 100644 --- a/man/syncthing-faq.7 +++ b/man/syncthing-faq.7 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "SYNCTHING-FAQ" "7" "Apr 04, 2025" "v1.29.3" "Syncthing" +.TH "SYNCTHING-FAQ" "7" "Apr 21, 2025" "v1.29.3" "Syncthing" .SH NAME syncthing-faq \- Frequently Asked Questions .INDENT 0.0 diff --git a/man/syncthing-globaldisco.7 b/man/syncthing-globaldisco.7 index fe58f0147..0c598bdda 100644 --- a/man/syncthing-globaldisco.7 +++ b/man/syncthing-globaldisco.7 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "SYNCTHING-GLOBALDISCO" "7" "Apr 04, 2025" "v1.29.3" "Syncthing" +.TH "SYNCTHING-GLOBALDISCO" "7" "Apr 21, 2025" "v1.29.3" "Syncthing" .SH NAME syncthing-globaldisco \- Global Discovery Protocol v3 .SH ANNOUNCEMENTS diff --git a/man/syncthing-localdisco.7 b/man/syncthing-localdisco.7 index 05425f543..9a5cddb55 100644 --- a/man/syncthing-localdisco.7 +++ b/man/syncthing-localdisco.7 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "SYNCTHING-LOCALDISCO" "7" "Apr 04, 2025" "v1.29.3" "Syncthing" +.TH "SYNCTHING-LOCALDISCO" "7" "Apr 21, 2025" "v1.29.3" "Syncthing" .SH NAME syncthing-localdisco \- Local Discovery Protocol v4 .SH MODE OF OPERATION diff --git a/man/syncthing-networking.7 b/man/syncthing-networking.7 index e419a71f4..a72a62b3c 100644 --- a/man/syncthing-networking.7 +++ b/man/syncthing-networking.7 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "SYNCTHING-NETWORKING" "7" "Apr 04, 2025" "v1.29.3" "Syncthing" +.TH "SYNCTHING-NETWORKING" "7" "Apr 21, 2025" "v1.29.3" "Syncthing" .SH NAME syncthing-networking \- Firewall Setup .SH ROUTER SETUP diff --git a/man/syncthing-relay.7 b/man/syncthing-relay.7 index 99b7859c6..ce61b30a8 100644 --- a/man/syncthing-relay.7 +++ b/man/syncthing-relay.7 @@ -28,7 +28,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "SYNCTHING-RELAY" "7" "Apr 04, 2025" "v1.29.3" "Syncthing" +.TH "SYNCTHING-RELAY" "7" "Apr 21, 2025" "v1.29.3" "Syncthing" .SH NAME syncthing-relay \- Relay Protocol v1 .SH WHAT IS A RELAY? diff --git a/man/syncthing-rest-api.7 b/man/syncthing-rest-api.7 index 2e3fcf345..3b8cf65c7 100644 --- a/man/syncthing-rest-api.7 +++ b/man/syncthing-rest-api.7 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "SYNCTHING-REST-API" "7" "Apr 04, 2025" "v1.29.3" "Syncthing" +.TH "SYNCTHING-REST-API" "7" "Apr 21, 2025" "v1.29.3" "Syncthing" .SH NAME syncthing-rest-api \- REST API .sp @@ -279,8 +279,7 @@ Returns the current configuration. \(dqsendFullIndexOnUpgrade\(dq: false, \(dqfeatureFlags\(dq: [], \(dqconnectionLimitEnough\(dq: 0, - \(dqconnectionLimitMax\(dq: 0, - \(dqinsecureAllowOldTLSVersions\(dq: false + \(dqconnectionLimitMax\(dq: 0 }, \(dqremoteIgnoredDevices\(dq: [ { diff --git a/man/syncthing-security.7 b/man/syncthing-security.7 index 9cc22b8ad..9e67f9430 100644 --- a/man/syncthing-security.7 +++ b/man/syncthing-security.7 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "SYNCTHING-SECURITY" "7" "Apr 04, 2025" "v1.29.3" "Syncthing" +.TH "SYNCTHING-SECURITY" "7" "Apr 21, 2025" "v1.29.3" "Syncthing" .SH NAME syncthing-security \- Security Principles .sp diff --git a/man/syncthing-stignore.5 b/man/syncthing-stignore.5 index 7925e2476..0e8148cc4 100644 --- a/man/syncthing-stignore.5 +++ b/man/syncthing-stignore.5 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "SYNCTHING-STIGNORE" "5" "Apr 04, 2025" "v1.29.3" "Syncthing" +.TH "SYNCTHING-STIGNORE" "5" "Apr 21, 2025" "v1.29.3" "Syncthing" .SH NAME syncthing-stignore \- Prevent files from being synchronized to other nodes .SH SYNOPSIS diff --git a/man/syncthing-versioning.7 b/man/syncthing-versioning.7 index 0eca9f902..967a28d6b 100644 --- a/man/syncthing-versioning.7 +++ b/man/syncthing-versioning.7 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "SYNCTHING-VERSIONING" "7" "Apr 04, 2025" "v1.29.3" "Syncthing" +.TH "SYNCTHING-VERSIONING" "7" "Apr 21, 2025" "v1.29.3" "Syncthing" .SH NAME syncthing-versioning \- Keep automatic backups of deleted files by other nodes .sp diff --git a/man/syncthing.1 b/man/syncthing.1 index 9e1b5517a..f939d5c7b 100644 --- a/man/syncthing.1 +++ b/man/syncthing.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "SYNCTHING" "1" "Apr 04, 2025" "v1.29.3" "Syncthing" +.TH "SYNCTHING" "1" "Apr 21, 2025" "v1.29.3" "Syncthing" .SH NAME syncthing \- Syncthing .SH SYNOPSIS diff --git a/script/copyrights.go b/script/copyrights.go new file mode 100644 index 000000000..f192893c2 --- /dev/null +++ b/script/copyrights.go @@ -0,0 +1,489 @@ +// Copyright (C) 2025 The Syncthing Authors. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. + +//go:build ignore +// +build ignore + +// Updates the list of software copyrights in aboutModalView.html based on the +// output of `go mod graph`. + +package main + +import ( + "bufio" + "bytes" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "log" + "net/http" + "net/url" + "os" + "os/exec" + "regexp" + "slices" + "strconv" + "strings" + "time" + + "golang.org/x/net/html" +) + +var copyrightMap = map[string]string{ + // https://github.com/aws/aws-sdk-go/blob/main/NOTICE.txt#L2 + "aws/aws-sdk-go": "Copyright © 2015 Amazon.com, Inc. or its affiliates, Copyright 2014-2015 Stripe, Inc", + // https://github.com/ccding/go-stun/blob/master/main.go#L1 + "ccding/go-stun": "Copyright © 2016 Cong Ding", + // https://github.com/search?q=repo%3Acertifi%2Fgocertifi%20copyright&type=code + // "certifi/gocertifi": "No copyrights found", + // https://github.com/search?q=repo%3Aebitengine%2Fpurego%20copyright&type=code + "ebitengine/purego": "Copyright © 2022 The Ebitengine Authors", + // https://github.com/search?q=repo%3Agoogle%2Fpprof%20copyright&type=code + "google/pprof": "Copyright © 2016 Google Inc", + // https://github.com/greatroar/blobloom/blob/master/README.md?plain=1#L74 + "greatroar/blobloom": "Copyright © 2020-2024 the Blobloom authors", + // https://github.com/jmespath/go-jmespath/blob/master/NOTICE#L2 + "jmespath/go-jmespath": "Copyright © 2015 James Saryerwinnie", + // https://github.com/maxmind/geoipupdate/blob/main/README.md?plain=1#L140 + "maxmind/geoipupdate": "Copyright © 2018-2024 by MaxMind, Inc", + // https://github.com/search?q=repo%3Apuzpuzpuz%2Fxsync%20copyright&type=code + // "puzpuzpuz/xsync": "No copyrights found", + // https://github.com/search?q=repo%3Atklauser%2Fnumcpus%20copyright&type=code + "tklauser/numcpus": "Copyright © 2018-2024 Tobias Klauser", + // https://github.com/search?q=repo%3Auber-go%2Fmock%20copyright&type=code + "go.uber.org/mock": "Copyright © 2010-2022 Google LLC", +} + +var urlMap = map[string]string{ + "fontawesome.io": "https://github.com/FortAwesome/Font-Awesome", + "go.uber.org/automaxprocs": "https://github.com/uber-go/automaxprocs", + "go.uber.org/mock": "https://github.com/uber-go/mock", + "google.golang.org/protobuf": "https://github.com/protocolbuffers/protobuf-go", + "gopkg.in/yaml.v2": "", // ignore, as gopkg.in/yaml.v3 supersedes + "gopkg.in/yaml.v3": "https://github.com/go-yaml/yaml", + "sigs.k8s.io/yaml": "https://github.com/kubernetes-sigs/yaml", +} + +const htmlFile = "gui/default/syncthing/core/aboutModalView.html" + +type Type int + +const ( + // TypeJS defines non-Go copyright notices + TypeJS Type = iota + // TypeKeep defines Go copyright notices for packages that are still used. + TypeKeep + // TypeToss defines Go copyright notices for packages that are no longer used. + TypeToss + // TypeNew defines Go copyright notices for new packages found via `go mod graph`. + TypeNew +) + +type CopyrightNotice struct { + Type Type + Name string + HTML string + Module string + URL string + Copyright string + RepoURL string + RepoCopyrights []string +} + +var copyrightRe = regexp.MustCompile(`(?s)id="copyright-notices">(.+?)`) + +func main() { + bs := readAll(htmlFile) + matches := copyrightRe.FindStringSubmatch(string(bs)) + + if len(matches) <= 1 { + log.Fatal("Cannot find id copyright-notices in ", htmlFile) + } + + modules := getModules() + + notices := parseCopyrightNotices(matches[1]) + old := len(notices) + + // match up modules to notices + matched := map[string]bool{} + removes := 0 + for i, notice := range notices { + if notice.Type == TypeJS { + continue + } + found := "" + for _, module := range modules { + if strings.Contains(module, notice.Name) { + found = module + + break + } + } + if found != "" { + matched[found] = true + notices[i].Module = found + + continue + } + removes++ + fmt.Printf("Removing: %-40s %-55s %s\n", notice.Name, notice.URL, notice.Copyright) + notices[i].Type = TypeToss + } + + // add new modules to notices + adds := 0 + for _, module := range modules { + _, ok := matched[module] + if ok { + continue + } + + adds++ + notice := CopyrightNotice{} + notice.Name = module + if strings.HasPrefix(notice.Name, "github.com/") { + notice.Name = strings.ReplaceAll(notice.Name, "github.com/", "") + } + notice.Type = TypeNew + + url, ok := urlMap[module] + if ok { + notice.URL = url + notice.RepoURL = url + } else { + notice.URL = "https://" + module + notice.RepoURL = "https://" + module + } + notices = append(notices, notice) + } + + if removes == 0 && adds == 0 { + // authors.go is quiet, so let's be quiet too. + // fmt.Printf("No changes detected in %d modules and %d notices\n", len(modules), len(notices)) + os.Exit(0) + } + + // get copyrights via Github API for new modules + notfound := 0 + for i, n := range notices { + if n.Type != TypeNew { + continue + } + copyright, ok := copyrightMap[n.Name] + if ok { + notices[i].Copyright = copyright + + continue + } + notices[i].Copyright = defaultCopyright(n) + + if strings.Contains(n.URL, "github.com/") { + notices[i].RepoURL = notices[i].URL + owner, repo := parseGitHubURL(n.URL) + licenseText := getLicenseText(owner, repo) + notices[i].RepoCopyrights = extractCopyrights(licenseText, n) + + if len(notices[i].RepoCopyrights) > 0 { + notices[i].Copyright = notices[i].RepoCopyrights[0] + } + + notices[i].HTML = fmt.Sprintf("
  • %s, %s.
  • ", n.URL, n.Name, notices[i].Copyright) + if len(notices[i].RepoCopyrights) > 0 { + continue + } + } + fmt.Printf("Copyright not found: %-30s : using %q\n", n.Name, notices[i].Copyright) + notfound++ + } + + replacements := write(notices, bs) + fmt.Printf("Removed: %3d\n", removes) + fmt.Printf("Added: %3d\n", adds) + fmt.Printf("Copyrights not found: %3d\n", notfound) + fmt.Printf("Old package count: %3d\n", old) + fmt.Printf("New package count: %3d\n", replacements) +} + +func write(notices []CopyrightNotice, bs []byte) int { + keys := make([]string, 0, len(notices)) + + noticeMap := make(map[string]CopyrightNotice, 0) + + for _, n := range notices { + if n.Type != TypeKeep && n.Type != TypeNew { + continue + } + if n.Type == TypeNew { + fmt.Printf("Adding: %-40s %-55s %s\n", n.Name, n.URL, n.Copyright) + } + keys = append(keys, n.Name) + noticeMap[n.Name] = n + } + + slices.Sort(keys) + + indent := " " + replacements := []string{} + for _, n := range notices { + if n.Type != TypeJS { + continue + } + replacements = append(replacements, indent+n.HTML) + } + + for _, k := range keys { + n := noticeMap[k] + line := fmt.Sprintf("%s
  • %s, %s.
  • ", indent, n.URL, n.Name, n.Copyright) + replacements = append(replacements, line) + } + replacement := strings.Join(replacements, "\n") + + bs = copyrightRe.ReplaceAll(bs, []byte("id=\"copyright-notices\">\n"+replacement+"\n ")) + writeFile(htmlFile, string(bs)) + + return len(replacements) +} + +func readAll(path string) []byte { + fd, err := os.Open(path) + if err != nil { + log.Fatal(err) + } + defer fd.Close() + + bs, err := io.ReadAll(fd) + if err != nil { + log.Fatal(err) + } + + return bs +} + +func writeFile(path string, data string) { + err := os.WriteFile(path, []byte(data), 0o644) + if err != nil { + log.Fatal(err) + } +} + +func getModules() []string { + cmd := exec.Command("go", "mod", "graph") + output, err := cmd.Output() + if err != nil { + log.Fatal(err) + } + + seen := make(map[string]struct{}) + scanner := bufio.NewScanner(bytes.NewReader(output)) + + for scanner.Scan() { + line := scanner.Text() + fields := strings.Fields(line) + if len(fields) == 0 { + continue + } + + if !strings.HasPrefix(fields[0], "github.com/syncthing/syncthing") { + continue + } + + // Get left-hand side of dependency pair (before '@') + mod := strings.SplitN(fields[1], "@", 2)[0] + + // Keep only first 3 path components + parts := strings.Split(mod, "/") + if len(parts) == 1 { + continue + } + short := strings.Join(parts[:min(len(parts), 3)], "/") + + if strings.HasPrefix(short, "golang.org/x") || + strings.HasPrefix(short, "github.com/prometheus") || + short == "go" { + + continue + } + + seen[short] = struct{}{} + } + + adds := make([]string, 0) + for k := range seen { + adds = append(adds, k) + } + + slices.Sort(adds) + + return adds +} + +func parseCopyrightNotices(input string) []CopyrightNotice { + doc, err := html.Parse(strings.NewReader("
      " + input + "
    ")) + if err != nil { + log.Fatal(err) + } + + var notices []CopyrightNotice + + typ := TypeJS + + var f func(*html.Node) + f = func(n *html.Node) { + if n.Type == html.ElementNode && n.Data == "li" { + var notice CopyrightNotice + var aFound bool + + for c := n.FirstChild; c != nil; c = c.NextSibling { + if c.Type == html.ElementNode && c.Data == "a" { + aFound = true + for _, attr := range c.Attr { + if attr.Key == "href" { + notice.URL = attr.Val + } + } + if c.FirstChild != nil && c.FirstChild.Type == html.TextNode { + notice.Name = strings.TrimSpace(c.FirstChild.Data) + } + } else if c.Type == html.TextNode && aFound { + // Anything after is considered the copyright + notice.Copyright = strings.TrimSpace(html.UnescapeString(c.Data)) + notice.Copyright = strings.Trim(notice.Copyright, "., ") + } + if typ == TypeJS && strings.Contains(notice.URL, "AudriusButkevicius") { + typ = TypeKeep + } + notice.Type = typ + var buf strings.Builder + _ = html.Render(&buf, n) + notice.HTML = buf.String() + } + + notice.Copyright = strings.ReplaceAll(notice.Copyright, "©", "©") + notice.HTML = strings.ReplaceAll(notice.HTML, "©", "©") + notices = append(notices, notice) + } + for c := n.FirstChild; c != nil; c = c.NextSibling { + f(c) + } + } + + f(doc) + + return notices +} + +func parseGitHubURL(u string) (string, string) { + parsed, err := url.Parse(u) + if err != nil { + log.Fatal(err) + } + parts := strings.Split(strings.Trim(parsed.Path, "/"), "/") + if len(parts) < 2 { + log.Fatal(fmt.Errorf("invalid GitHub URL: %q", parsed.Path)) + } + + return parts[0], parts[1] +} + +func getLicenseText(owner, repo string) string { + url := fmt.Sprintf("https://api.github.com/repos/%s/%s/license", owner, repo) + req, _ := http.NewRequest("GET", url, nil) + req.Header.Set("Accept", "application/vnd.github.v3+json") + + if token := os.Getenv("GITHUB_TOKEN"); token != "" { + req.Header.Set("Authorization", "Bearer "+token) + } + + resp, err := http.DefaultClient.Do(req) + if err != nil { + log.Fatal(err) + } + defer resp.Body.Close() + + var result struct { + Content string `json:"content"` + Encoding string `json:"encoding"` + } + body, _ := io.ReadAll(resp.Body) + err = json.Unmarshal(body, &result) + if err != nil { + log.Fatal(err) + } + + if result.Encoding != "base64" { + log.Fatal(fmt.Sprintf("unexpected encoding: %s", result.Encoding)) + } + + decoded, err := base64.StdEncoding.DecodeString(result.Content) + if err != nil { + log.Fatal(err) + } + + return string(decoded) +} + +func extractCopyrights(license string, notice CopyrightNotice) []string { + lines := strings.Split(license, "\n") + + re := regexp.MustCompile(`(?i)^\s*(copyright\s*(?:©|\(c\)|©|19|20).*)$`) + + copyrights := []string{} + + for _, line := range lines { + if matches := re.FindStringSubmatch(strings.TrimSpace(line)); len(matches) == 2 { + copyright := strings.TrimSpace(matches[1]) + re := regexp.MustCompile(`(?i)all rights reserved`) + copyright = re.ReplaceAllString(copyright, "") + copyright = strings.ReplaceAll(copyright, "©", "©") + copyright = strings.ReplaceAll(copyright, "(C)", "©") + copyright = strings.ReplaceAll(copyright, "(c)", "©") + copyright = strings.Trim(copyright, "., ") + copyrights = append(copyrights, copyright) + } + } + + if len(copyrights) > 0 { + return copyrights + } + + return []string{} +} + +func defaultCopyright(n CopyrightNotice) string { + year := time.Now().Format("2006") + + return fmt.Sprintf("Copyright © %v, the %s authors", year, n.Name) +} + +func writeNotices(path string, notices []CopyrightNotice) { + s := "" + for i, n := range notices { + s += "# : " + strconv.Itoa(i) + "\n" + n.String() + } + writeFile(path, s) +} + +func (n CopyrightNotice) String() string { + return fmt.Sprintf("Type : %v\nHTML : %v\nName : %v\nModule : %v\nURL : %v\nCopyright: %v\nRepoURL : %v\nRepoCopys: %v\n\n", + n.Type, n.HTML, n.Name, n.Module, n.URL, n.Copyright, n.RepoURL, strings.Join(n.RepoCopyrights, ",")) +} + +func (t Type) String() string { + switch t { + case TypeJS: + return "TypeJS" + case TypeKeep: + return "TypeKeep" + case TypeToss: + return "TypeToss" + case TypeNew: + return "TypeNew" + default: + return "unknown" + } +}