Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
caddy
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Łukasz Nowak
caddy
Commits
e4b50aa8
Commit
e4b50aa8
authored
May 24, 2015
by
Zac Bergquist
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Fix more lint warnings
parent
fd8490c6
Changes
21
Show whitespace changes
Inline
Side-by-side
Showing
21 changed files
with
164 additions
and
171 deletions
+164
-171
app/app.go
app/app.go
+5
-5
config/config.go
config/config.go
+2
-1
config/parse/lexer.go
config/parse/lexer.go
+1
-2
config/setup/git.go
config/setup/git.go
+27
-27
config/setup/git_test.go
config/setup/git_test.go
+11
-11
config/setup/log.go
config/setup/log.go
+6
-5
middleware/basicauth/basicauth.go
middleware/basicauth/basicauth.go
+3
-5
middleware/basicauth/basicauth_test.go
middleware/basicauth/basicauth_test.go
+5
-13
middleware/browse/browse.go
middleware/browse/browse.go
+2
-0
middleware/extensions/ext.go
middleware/extensions/ext.go
+1
-1
middleware/fastcgi/fcgiclient.go
middleware/fastcgi/fcgiclient.go
+46
-47
middleware/git/git.go
middleware/git/git.go
+18
-18
middleware/git/git_test.go
middleware/git/git_test.go
+10
-10
middleware/gzip/gzip.go
middleware/gzip/gzip.go
+1
-2
middleware/internal/internal.go
middleware/internal/internal.go
+2
-3
middleware/log/log.go
middleware/log/log.go
+4
-2
middleware/markdown/markdown.go
middleware/markdown/markdown.go
+7
-8
middleware/markdown/metadata.go
middleware/markdown/metadata.go
+6
-6
middleware/path.go
middleware/path.go
+2
-0
server/fileserver.go
server/fileserver.go
+4
-3
server/server.go
server/server.go
+1
-2
No files found.
app/app.go
View file @
e4b50aa8
...
...
@@ -16,10 +16,10 @@ import (
)
const
(
//
P
rogram name
//
Name is the p
rogram name
Name
=
"Caddy"
//
P
rogram version
//
Version is the p
rogram version
Version
=
"0.6.0"
)
...
...
@@ -27,13 +27,13 @@ var (
// Servers is a list of all the currently-listening servers
Servers
[]
*
server
.
Server
//
This m
utex protects the Servers slice during changes
//
ServersM
utex protects the Servers slice during changes
ServersMutex
sync
.
Mutex
// W
aiting on Wg will block until all listeners have shut down.
// W
g is used to wait for all servers to shut down
Wg
sync
.
WaitGroup
//
W
hether HTTP2 is enabled or not
//
Http2 indicates w
hether HTTP2 is enabled or not
Http2
bool
// TODO: temporary flag until http2 is standard
// Quiet mode hides non-error initialization output
...
...
config/config.go
View file @
e4b50aa8
...
...
@@ -19,7 +19,8 @@ const (
DefaultPort
=
"2015"
DefaultRoot
=
"."
// The default configuration file to load if none is specified
// DefaultConfigFile is the name of the configuration file that is loaded
// by default if no other file is specified.
DefaultConfigFile
=
"Caddyfile"
)
...
...
config/parse/lexer.go
View file @
e4b50aa8
...
...
@@ -58,9 +58,8 @@ func (l *lexer) next() bool {
}
if
err
==
io
.
EOF
{
return
false
}
else
{
panic
(
err
)
}
panic
(
err
)
}
if
quoted
{
...
...
config/setup/git.go
View file @
e4b50aa8
...
...
@@ -57,7 +57,7 @@ func gitParse(c *Controller) (*git.Repo, error) {
repo
.
Path
=
filepath
.
Clean
(
c
.
Root
+
string
(
filepath
.
Separator
)
+
args
[
1
])
fallthrough
case
1
:
repo
.
U
rl
=
args
[
0
]
repo
.
U
RL
=
args
[
0
]
}
for
c
.
NextBlock
()
{
...
...
@@ -66,7 +66,7 @@ func gitParse(c *Controller) (*git.Repo, error) {
if
!
c
.
NextArg
()
{
return
nil
,
c
.
ArgErr
()
}
repo
.
U
rl
=
c
.
Val
()
repo
.
U
RL
=
c
.
Val
()
case
"path"
:
if
!
c
.
NextArg
()
{
return
nil
,
c
.
ArgErr
()
...
...
@@ -103,19 +103,19 @@ func gitParse(c *Controller) (*git.Repo, error) {
}
// if repo is not specified, return error
if
repo
.
U
rl
==
""
{
if
repo
.
U
RL
==
""
{
return
nil
,
c
.
ArgErr
()
}
// if private key is not specified, convert repository
url
to https
// if private key is not specified, convert repository
URL
to https
// to avoid ssh authentication
// else validate git
url
// else validate git
URL
// Note: private key support not yet available on Windows
var
err
error
if
repo
.
KeyPath
==
""
{
repo
.
U
rl
,
repo
.
Host
,
err
=
sanitizeHttp
(
repo
.
Url
)
repo
.
U
RL
,
repo
.
Host
,
err
=
sanitizeHTTP
(
repo
.
URL
)
}
else
{
repo
.
U
rl
,
repo
.
Host
,
err
=
sanitizeGit
(
repo
.
Url
)
repo
.
U
RL
,
repo
.
Host
,
err
=
sanitizeGit
(
repo
.
URL
)
// TODO add Windows support for private repos
if
runtime
.
GOOS
==
"windows"
{
return
nil
,
fmt
.
Errorf
(
"Private repository not yet supported on Windows"
)
...
...
@@ -134,12 +134,12 @@ func gitParse(c *Controller) (*git.Repo, error) {
return
repo
,
repo
.
Prepare
()
}
// sanitizeH
ttp cleans up repository url
and converts to https format
// sanitizeH
TTP cleans up repository URL
and converts to https format
// if currently in ssh format.
// Returns sanitized url, hostName (e.g. github.com, bitbucket.com)
// and possible error
func
sanitizeH
ttp
(
repoUrl
string
)
(
string
,
string
,
error
)
{
url
,
err
:=
url
.
Parse
(
repoU
rl
)
func
sanitizeH
TTP
(
repoURL
string
)
(
string
,
string
,
error
)
{
url
,
err
:=
url
.
Parse
(
repoU
RL
)
if
err
!=
nil
{
return
""
,
""
,
err
}
...
...
@@ -148,46 +148,46 @@ func sanitizeHttp(repoUrl string) (string, string, error) {
url
.
Path
=
url
.
Path
[
len
(
"git@"
)
:
]
i
:=
strings
.
Index
(
url
.
Path
,
":"
)
if
i
<
0
{
return
""
,
""
,
fmt
.
Errorf
(
"Invalid git url %s"
,
repoU
rl
)
return
""
,
""
,
fmt
.
Errorf
(
"Invalid git url %s"
,
repoU
RL
)
}
url
.
Host
=
url
.
Path
[
:
i
]
url
.
Path
=
"/"
+
url
.
Path
[
i
+
1
:
]
}
repoU
rl
=
"https://"
+
url
.
Host
+
url
.
Path
repoU
RL
=
"https://"
+
url
.
Host
+
url
.
Path
// add .git suffix if missing
if
!
strings
.
HasSuffix
(
repoU
rl
,
".git"
)
{
repoU
rl
+=
".git"
if
!
strings
.
HasSuffix
(
repoU
RL
,
".git"
)
{
repoU
RL
+=
".git"
}
return
repoU
rl
,
url
.
Host
,
nil
return
repoU
RL
,
url
.
Host
,
nil
}
// sanitizeGit cleans up repository url and converts to ssh format for private
// repositories if required.
// Returns sanitized url, hostName (e.g. github.com, bitbucket.com)
// and possible error
func
sanitizeGit
(
repoU
rl
string
)
(
string
,
string
,
error
)
{
repoU
rl
=
strings
.
TrimSpace
(
repoUrl
)
func
sanitizeGit
(
repoU
RL
string
)
(
string
,
string
,
error
)
{
repoU
RL
=
strings
.
TrimSpace
(
repoURL
)
// check if valid ssh format
if
!
strings
.
HasPrefix
(
repoU
rl
,
"git@"
)
||
strings
.
Index
(
repoUrl
,
":"
)
<
len
(
"git@a:"
)
{
if
!
strings
.
HasPrefix
(
repoU
RL
,
"git@"
)
||
strings
.
Index
(
repoURL
,
":"
)
<
len
(
"git@a:"
)
{
// check if valid http format and convert to ssh
if
url
,
err
:=
url
.
Parse
(
repoU
rl
);
err
==
nil
&&
strings
.
HasPrefix
(
url
.
Scheme
,
"http"
)
{
repoU
rl
=
fmt
.
Sprintf
(
"git@%v:%v"
,
url
.
Host
,
url
.
Path
[
1
:
])
if
url
,
err
:=
url
.
Parse
(
repoU
RL
);
err
==
nil
&&
strings
.
HasPrefix
(
url
.
Scheme
,
"http"
)
{
repoU
RL
=
fmt
.
Sprintf
(
"git@%v:%v"
,
url
.
Host
,
url
.
Path
[
1
:
])
}
else
{
return
""
,
""
,
fmt
.
Errorf
(
"Invalid git url %s"
,
repoU
rl
)
return
""
,
""
,
fmt
.
Errorf
(
"Invalid git url %s"
,
repoU
RL
)
}
}
hostU
rl
:=
repoUrl
[
len
(
"git@"
)
:
]
i
:=
strings
.
Index
(
hostU
rl
,
":"
)
host
:=
hostU
rl
[
:
i
]
hostU
RL
:=
repoURL
[
len
(
"git@"
)
:
]
i
:=
strings
.
Index
(
hostU
RL
,
":"
)
host
:=
hostU
RL
[
:
i
]
// add .git suffix if missing
if
!
strings
.
HasSuffix
(
repoU
rl
,
".git"
)
{
repoU
rl
+=
".git"
if
!
strings
.
HasSuffix
(
repoU
RL
,
".git"
)
{
repoU
RL
+=
".git"
}
return
repoU
rl
,
host
,
nil
return
repoU
RL
,
host
,
nil
}
config/setup/git_test.go
View file @
e4b50aa8
...
...
@@ -32,29 +32,29 @@ func TestGitParse(t *testing.T) {
expected
*
git
.
Repo
}{
{
`git git@github.com:user/repo`
,
false
,
&
git
.
Repo
{
U
rl
:
"https://github.com/user/repo.git"
,
U
RL
:
"https://github.com/user/repo.git"
,
}},
{
`git github.com/user/repo`
,
false
,
&
git
.
Repo
{
U
rl
:
"https://github.com/user/repo.git"
,
U
RL
:
"https://github.com/user/repo.git"
,
}},
{
`git git@github.com/user/repo`
,
true
,
nil
},
{
`git http://github.com/user/repo`
,
false
,
&
git
.
Repo
{
U
rl
:
"https://github.com/user/repo.git"
,
U
RL
:
"https://github.com/user/repo.git"
,
}},
{
`git https://github.com/user/repo`
,
false
,
&
git
.
Repo
{
U
rl
:
"https://github.com/user/repo.git"
,
U
RL
:
"https://github.com/user/repo.git"
,
}},
{
`git http://github.com/user/repo {
key ~/.key
}`
,
false
,
&
git
.
Repo
{
KeyPath
:
"~/.key"
,
U
rl
:
"git@github.com:user/repo.git"
,
U
RL
:
"git@github.com:user/repo.git"
,
}},
{
`git git@github.com:user/repo {
key ~/.key
}`
,
false
,
&
git
.
Repo
{
KeyPath
:
"~/.key"
,
U
rl
:
"git@github.com:user/repo.git"
,
U
RL
:
"git@github.com:user/repo.git"
,
}},
{
`git `
,
true
,
nil
},
{
`git {
...
...
@@ -66,7 +66,7 @@ func TestGitParse(t *testing.T) {
key ~/.key
}`
,
false
,
&
git
.
Repo
{
KeyPath
:
"~/.key"
,
U
rl
:
"git@github.com:user/repo.git"
,
U
RL
:
"git@github.com:user/repo.git"
,
}},
{
`git {
repo git@github.com:user/repo
...
...
@@ -74,7 +74,7 @@ func TestGitParse(t *testing.T) {
interval 600
}`
,
false
,
&
git
.
Repo
{
KeyPath
:
"~/.key"
,
U
rl
:
"git@github.com:user/repo.git"
,
U
RL
:
"git@github.com:user/repo.git"
,
Interval
:
time
.
Second
*
600
,
}},
{
`git {
...
...
@@ -82,7 +82,7 @@ func TestGitParse(t *testing.T) {
branch dev
}`
,
false
,
&
git
.
Repo
{
Branch
:
"dev"
,
U
rl
:
"https://github.com/user/repo.git"
,
U
RL
:
"https://github.com/user/repo.git"
,
}},
{
`git {
key ~/.key
...
...
@@ -93,7 +93,7 @@ func TestGitParse(t *testing.T) {
then echo hello world
}`
,
false
,
&
git
.
Repo
{
KeyPath
:
"~/.key"
,
U
rl
:
"git@github.com:user/repo.git"
,
U
RL
:
"git@github.com:user/repo.git"
,
Then
:
"echo hello world"
,
}},
}
...
...
@@ -137,7 +137,7 @@ func reposEqual(expected, repo *git.Repo) bool {
if
expected
.
Then
!=
""
&&
expected
.
Then
!=
repo
.
Then
{
return
false
}
if
expected
.
U
rl
!=
""
&&
expected
.
Url
!=
repo
.
Url
{
if
expected
.
U
RL
!=
""
&&
expected
.
URL
!=
repo
.
URL
{
return
false
}
return
true
...
...
config/setup/log.go
View file @
e4b50aa8
...
...
@@ -8,6 +8,7 @@ import (
caddylog
"github.com/mholt/caddy/middleware/log"
)
// Log sets up the logging middleware.
func
Log
(
c
*
Controller
)
(
middleware
.
Middleware
,
error
)
{
rules
,
err
:=
logParse
(
c
)
if
err
!=
nil
{
...
...
@@ -42,22 +43,22 @@ func Log(c *Controller) (middleware.Middleware, error) {
},
nil
}
func
logParse
(
c
*
Controller
)
([]
caddylog
.
Log
Rule
,
error
)
{
var
rules
[]
caddylog
.
Log
Rule
func
logParse
(
c
*
Controller
)
([]
caddylog
.
Rule
,
error
)
{
var
rules
[]
caddylog
.
Rule
for
c
.
Next
()
{
args
:=
c
.
RemainingArgs
()
if
len
(
args
)
==
0
{
// Nothing specified; use defaults
rules
=
append
(
rules
,
caddylog
.
Log
Rule
{
rules
=
append
(
rules
,
caddylog
.
Rule
{
PathScope
:
"/"
,
OutputFile
:
caddylog
.
DefaultLogFilename
,
Format
:
caddylog
.
DefaultLogFormat
,
})
}
else
if
len
(
args
)
==
1
{
// Only an output file specified
rules
=
append
(
rules
,
caddylog
.
Log
Rule
{
rules
=
append
(
rules
,
caddylog
.
Rule
{
PathScope
:
"/"
,
OutputFile
:
args
[
0
],
Format
:
caddylog
.
DefaultLogFormat
,
...
...
@@ -78,7 +79,7 @@ func logParse(c *Controller) ([]caddylog.LogRule, error) {
}
}
rules
=
append
(
rules
,
caddylog
.
Log
Rule
{
rules
=
append
(
rules
,
caddylog
.
Rule
{
PathScope
:
args
[
0
],
OutputFile
:
args
[
1
],
Format
:
format
,
...
...
middleware/basicauth/basicauth.go
View file @
e4b50aa8
...
...
@@ -46,13 +46,11 @@ func (a BasicAuth) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error
if
!
isAuthenticated
{
w
.
Header
()
.
Set
(
"WWW-Authenticate"
,
"Basic"
)
return
http
.
StatusUnauthorized
,
nil
}
else
{
}
// "It's an older code, sir, but it checks out. I was about to clear them."
return
a
.
Next
.
ServeHTTP
(
w
,
r
)
}
}
// Pass-thru when no paths match
return
a
.
Next
.
ServeHTTP
(
w
,
r
)
}
...
...
middleware/basicauth/basicauth_test.go
View file @
e4b50aa8
...
...
@@ -27,13 +27,10 @@ func TestBasicAuth(t *testing.T) {
{
"/testing"
,
http
.
StatusUnauthorized
,
"ttest:test"
},
{
"/testing"
,
http
.
StatusOK
,
"test:ttest"
},
{
"/testing"
,
http
.
StatusUnauthorized
,
""
},
}
for
i
,
test
:=
range
tests
{
req
,
err
:=
http
.
NewRequest
(
"GET"
,
test
.
from
,
nil
)
if
err
!=
nil
{
t
.
Fatalf
(
"Test %d: Could not create HTTP request %v"
,
i
,
err
)
...
...
@@ -61,12 +58,10 @@ func TestBasicAuth(t *testing.T) {
}
}
}
}
func
TestMultipleOverlappingRules
(
t
*
testing
.
T
)
{
rw
:=
BasicAuth
{
Next
:
middleware
.
HandlerFunc
(
contentHandler
),
...
...
@@ -89,7 +84,6 @@ func TestMultipleOverlappingRules(t *testing.T) {
{
"/t"
,
http
.
StatusUnauthorized
,
"t1:p2"
},
}
for
i
,
test
:=
range
tests
{
req
,
err
:=
http
.
NewRequest
(
"GET"
,
test
.
from
,
nil
)
...
...
@@ -113,8 +107,6 @@ func TestMultipleOverlappingRules(t *testing.T) {
}
func
contentHandler
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
(
int
,
error
)
{
fmt
.
Fprintf
(
w
,
r
.
URL
.
String
())
return
http
.
StatusOK
,
nil
...
...
middleware/browse/browse.go
View file @
e4b50aa8
...
...
@@ -55,10 +55,12 @@ type FileInfo struct {
Mode
os
.
FileMode
}
// HumanSize returns the size of the file as a human-readable string.
func
(
fi
FileInfo
)
HumanSize
()
string
{
return
humanize
.
Bytes
(
uint64
(
fi
.
Size
))
}
// HumanModTime returns the modified time of the file as a human-readable string.
func
(
fi
FileInfo
)
HumanModTime
(
format
string
)
string
{
return
fi
.
ModTime
.
Format
(
format
)
}
...
...
middleware/extensions/ext.go
View file @
e4b50aa8
// Package extension
i
s middleware for clean URLs.
// Package extension
s contain
s middleware for clean URLs.
//
// The root path of the site is passed in as well as possible extensions
// to try internally for paths requested that don't match an existing
...
...
middleware/fastcgi/fcgiclient.go
View file @
e4b50aa8
...
...
@@ -89,10 +89,10 @@ type header struct {
// not synchronized because we don't care what the contents are
var
pad
[
maxPad
]
byte
func
(
h
*
header
)
init
(
recType
uint8
,
reqI
d
uint16
,
contentLength
int
)
{
func
(
h
*
header
)
init
(
recType
uint8
,
reqI
D
uint16
,
contentLength
int
)
{
h
.
Version
=
1
h
.
Type
=
recType
h
.
Id
=
reqI
d
h
.
Id
=
reqI
D
h
.
ContentLength
=
uint16
(
contentLength
)
h
.
PaddingLength
=
uint8
(
-
contentLength
&
7
)
}
...
...
@@ -135,7 +135,7 @@ type FCGIClient struct {
reqId
uint16
}
//
C
onnects to the fcgi responder at the specified network address.
//
Dial c
onnects to the fcgi responder at the specified network address.
// See func net.Dial for a description of the network and address parameters.
func
Dial
(
network
,
address
string
)
(
fcgi
*
FCGIClient
,
err
error
)
{
var
conn
net
.
Conn
...
...
@@ -154,43 +154,43 @@ func Dial(network, address string) (fcgi *FCGIClient, err error) {
return
}
// Close fcgi connnection
func
(
this
*
FCGIClient
)
Close
()
{
this
.
rwc
.
Close
()
// Close
closes
fcgi connnection
func
(
c
*
FCGIClient
)
Close
()
{
c
.
rwc
.
Close
()
}
func
(
this
*
FCGIClient
)
writeRecord
(
recType
uint8
,
content
[]
byte
)
(
err
error
)
{
this
.
mutex
.
Lock
()
defer
this
.
mutex
.
Unlock
()
this
.
buf
.
Reset
()
this
.
h
.
init
(
recType
,
this
.
reqId
,
len
(
content
))
if
err
:=
binary
.
Write
(
&
this
.
buf
,
binary
.
BigEndian
,
this
.
h
);
err
!=
nil
{
func
(
c
*
FCGIClient
)
writeRecord
(
recType
uint8
,
content
[]
byte
)
(
err
error
)
{
c
.
mutex
.
Lock
()
defer
c
.
mutex
.
Unlock
()
c
.
buf
.
Reset
()
c
.
h
.
init
(
recType
,
c
.
reqId
,
len
(
content
))
if
err
:=
binary
.
Write
(
&
c
.
buf
,
binary
.
BigEndian
,
c
.
h
);
err
!=
nil
{
return
err
}
if
_
,
err
:=
this
.
buf
.
Write
(
content
);
err
!=
nil
{
if
_
,
err
:=
c
.
buf
.
Write
(
content
);
err
!=
nil
{
return
err
}
if
_
,
err
:=
this
.
buf
.
Write
(
pad
[
:
this
.
h
.
PaddingLength
]);
err
!=
nil
{
if
_
,
err
:=
c
.
buf
.
Write
(
pad
[
:
c
.
h
.
PaddingLength
]);
err
!=
nil
{
return
err
}
_
,
err
=
this
.
rwc
.
Write
(
this
.
buf
.
Bytes
())
_
,
err
=
c
.
rwc
.
Write
(
c
.
buf
.
Bytes
())
return
err
}
func
(
this
*
FCGIClient
)
writeBeginRequest
(
role
uint16
,
flags
uint8
)
error
{
func
(
c
*
FCGIClient
)
writeBeginRequest
(
role
uint16
,
flags
uint8
)
error
{
b
:=
[
8
]
byte
{
byte
(
role
>>
8
),
byte
(
role
),
flags
}
return
this
.
writeRecord
(
FCGI_BEGIN_REQUEST
,
b
[
:
])
return
c
.
writeRecord
(
FCGI_BEGIN_REQUEST
,
b
[
:
])
}
func
(
this
*
FCGIClient
)
writeEndRequest
(
appStatus
int
,
protocolStatus
uint8
)
error
{
func
(
c
*
FCGIClient
)
writeEndRequest
(
appStatus
int
,
protocolStatus
uint8
)
error
{
b
:=
make
([]
byte
,
8
)
binary
.
BigEndian
.
PutUint32
(
b
,
uint32
(
appStatus
))
b
[
4
]
=
protocolStatus
return
this
.
writeRecord
(
FCGI_END_REQUEST
,
b
)
return
c
.
writeRecord
(
FCGI_END_REQUEST
,
b
)
}
func
(
this
*
FCGIClient
)
writePairs
(
recType
uint8
,
pairs
map
[
string
]
string
)
error
{
w
:=
newWriter
(
this
,
recType
)
func
(
c
*
FCGIClient
)
writePairs
(
recType
uint8
,
pairs
map
[
string
]
string
)
error
{
w
:=
newWriter
(
c
,
recType
)
b
:=
make
([]
byte
,
8
)
nn
:=
0
for
k
,
v
:=
range
pairs
{
...
...
@@ -333,32 +333,32 @@ func (w *streamReader) Read(p []byte) (n int, err error) {
// Do made the request and returns a io.Reader that translates the data read
// from fcgi responder out of fcgi packet before returning it.
func
(
this
*
FCGIClient
)
Do
(
p
map
[
string
]
string
,
req
io
.
Reader
)
(
r
io
.
Reader
,
err
error
)
{
err
=
this
.
writeBeginRequest
(
uint16
(
FCGI_RESPONDER
),
0
)
func
(
c
*
FCGIClient
)
Do
(
p
map
[
string
]
string
,
req
io
.
Reader
)
(
r
io
.
Reader
,
err
error
)
{
err
=
c
.
writeBeginRequest
(
uint16
(
FCGI_RESPONDER
),
0
)
if
err
!=
nil
{
return
}
err
=
this
.
writePairs
(
FCGI_PARAMS
,
p
)
err
=
c
.
writePairs
(
FCGI_PARAMS
,
p
)
if
err
!=
nil
{
return
}
body
:=
newWriter
(
this
,
FCGI_STDIN
)
body
:=
newWriter
(
c
,
FCGI_STDIN
)
if
req
!=
nil
{
io
.
Copy
(
body
,
req
)
}
body
.
Close
()
r
=
&
streamReader
{
c
:
this
}
r
=
&
streamReader
{
c
:
c
}
return
}
// Request returns a HTTP Response with Header and Body
// from fcgi responder
func
(
this
*
FCGIClient
)
Request
(
p
map
[
string
]
string
,
req
io
.
Reader
)
(
resp
*
http
.
Response
,
err
error
)
{
func
(
c
*
FCGIClient
)
Request
(
p
map
[
string
]
string
,
req
io
.
Reader
)
(
resp
*
http
.
Response
,
err
error
)
{
r
,
err
:=
this
.
Do
(
p
,
req
)
r
,
err
:=
c
.
Do
(
p
,
req
)
if
err
!=
nil
{
return
}
...
...
@@ -394,40 +394,39 @@ func (this *FCGIClient) Request(p map[string]string, req io.Reader) (resp *http.
}
else
{
resp
.
Body
=
ioutil
.
NopCloser
(
rb
)
}
return
}
// Get issues a GET request to the fcgi responder.
func
(
this
*
FCGIClient
)
Get
(
p
map
[
string
]
string
)
(
resp
*
http
.
Response
,
err
error
)
{
func
(
c
*
FCGIClient
)
Get
(
p
map
[
string
]
string
)
(
resp
*
http
.
Response
,
err
error
)
{
p
[
"REQUEST_METHOD"
]
=
"GET"
p
[
"CONTENT_LENGTH"
]
=
"0"
return
this
.
Request
(
p
,
nil
)
return
c
.
Request
(
p
,
nil
)
}
// Head issues a HEAD request to the fcgi responder.
func
(
this
*
FCGIClient
)
Head
(
p
map
[
string
]
string
)
(
resp
*
http
.
Response
,
err
error
)
{
func
(
c
*
FCGIClient
)
Head
(
p
map
[
string
]
string
)
(
resp
*
http
.
Response
,
err
error
)
{
p
[
"REQUEST_METHOD"
]
=
"HEAD"
p
[
"CONTENT_LENGTH"
]
=
"0"
return
this
.
Request
(
p
,
nil
)
return
c
.
Request
(
p
,
nil
)
}
// Options issues an OPTIONS request to the fcgi responder.
func
(
this
*
FCGIClient
)
Options
(
p
map
[
string
]
string
)
(
resp
*
http
.
Response
,
err
error
)
{
func
(
c
*
FCGIClient
)
Options
(
p
map
[
string
]
string
)
(
resp
*
http
.
Response
,
err
error
)
{
p
[
"REQUEST_METHOD"
]
=
"OPTIONS"
p
[
"CONTENT_LENGTH"
]
=
"0"
return
this
.
Request
(
p
,
nil
)
return
c
.
Request
(
p
,
nil
)
}
// Post issues a POST request to the fcgi responder. with request body
// in the format that bodyType specified
func
(
this
*
FCGIClient
)
Post
(
p
map
[
string
]
string
,
bodyType
string
,
body
io
.
Reader
,
l
int
)
(
resp
*
http
.
Response
,
err
error
)
{
func
(
c
*
FCGIClient
)
Post
(
p
map
[
string
]
string
,
bodyType
string
,
body
io
.
Reader
,
l
int
)
(
resp
*
http
.
Response
,
err
error
)
{
if
len
(
p
[
"REQUEST_METHOD"
])
==
0
||
p
[
"REQUEST_METHOD"
]
==
"GET"
{
p
[
"REQUEST_METHOD"
]
=
"POST"
...
...
@@ -439,44 +438,44 @@ func (this *FCGIClient) Post(p map[string]string, bodyType string, body io.Reade
p
[
"CONTENT_TYPE"
]
=
"application/x-www-form-urlencoded"
}
return
this
.
Request
(
p
,
body
)
return
c
.
Request
(
p
,
body
)
}
// Put issues a PUT request to the fcgi responder.
func
(
this
*
FCGIClient
)
Put
(
p
map
[
string
]
string
,
bodyType
string
,
body
io
.
Reader
,
l
int
)
(
resp
*
http
.
Response
,
err
error
)
{
func
(
c
*
FCGIClient
)
Put
(
p
map
[
string
]
string
,
bodyType
string
,
body
io
.
Reader
,
l
int
)
(
resp
*
http
.
Response
,
err
error
)
{
p
[
"REQUEST_METHOD"
]
=
"PUT"
return
this
.
Post
(
p
,
bodyType
,
body
,
l
)
return
c
.
Post
(
p
,
bodyType
,
body
,
l
)
}
// Patch issues a PATCH request to the fcgi responder.
func
(
this
*
FCGIClient
)
Patch
(
p
map
[
string
]
string
,
bodyType
string
,
body
io
.
Reader
,
l
int
)
(
resp
*
http
.
Response
,
err
error
)
{
func
(
c
*
FCGIClient
)
Patch
(
p
map
[
string
]
string
,
bodyType
string
,
body
io
.
Reader
,
l
int
)
(
resp
*
http
.
Response
,
err
error
)
{
p
[
"REQUEST_METHOD"
]
=
"PATCH"
return
this
.
Post
(
p
,
bodyType
,
body
,
l
)
return
c
.
Post
(
p
,
bodyType
,
body
,
l
)
}
// Delete issues a DELETE request to the fcgi responder.
func
(
this
*
FCGIClient
)
Delete
(
p
map
[
string
]
string
,
bodyType
string
,
body
io
.
Reader
,
l
int
)
(
resp
*
http
.
Response
,
err
error
)
{
func
(
c
*
FCGIClient
)
Delete
(
p
map
[
string
]
string
,
bodyType
string
,
body
io
.
Reader
,
l
int
)
(
resp
*
http
.
Response
,
err
error
)
{
p
[
"REQUEST_METHOD"
]
=
"DELETE"
return
this
.
Post
(
p
,
bodyType
,
body
,
l
)
return
c
.
Post
(
p
,
bodyType
,
body
,
l
)
}
// PostForm issues a POST to the fcgi responder, with form
// as a string key to a list values (url.Values)
func
(
this
*
FCGIClient
)
PostForm
(
p
map
[
string
]
string
,
data
url
.
Values
)
(
resp
*
http
.
Response
,
err
error
)
{
func
(
c
*
FCGIClient
)
PostForm
(
p
map
[
string
]
string
,
data
url
.
Values
)
(
resp
*
http
.
Response
,
err
error
)
{
body
:=
bytes
.
NewReader
([]
byte
(
data
.
Encode
()))
return
this
.
Post
(
p
,
"application/x-www-form-urlencoded"
,
body
,
body
.
Len
())
return
c
.
Post
(
p
,
"application/x-www-form-urlencoded"
,
body
,
body
.
Len
())
}
// PostFile issues a POST to the fcgi responder in multipart(RFC 2046) standard,
// with form as a string key to a list values (url.Values),
// and/or with file as a string key to a list file path.
func
(
this
*
FCGIClient
)
PostFile
(
p
map
[
string
]
string
,
data
url
.
Values
,
file
map
[
string
]
string
)
(
resp
*
http
.
Response
,
err
error
)
{
func
(
c
*
FCGIClient
)
PostFile
(
p
map
[
string
]
string
,
data
url
.
Values
,
file
map
[
string
]
string
)
(
resp
*
http
.
Response
,
err
error
)
{
buf
:=
&
bytes
.
Buffer
{}
writer
:=
multipart
.
NewWriter
(
buf
)
bodyType
:=
writer
.
FormDataContentType
()
...
...
@@ -509,7 +508,7 @@ func (this *FCGIClient) PostFile(p map[string]string, data url.Values, file map[
return
}
return
this
.
Post
(
p
,
bodyType
,
buf
,
buf
.
Len
())
return
c
.
Post
(
p
,
bodyType
,
buf
,
buf
.
Len
())
}
// Checks whether chunked is part of the encodings stack
...
...
middleware/git/git.go
View file @
e4b50aa8
...
...
@@ -28,7 +28,7 @@ var shell string
// initMutex prevents parallel attempt to validate
// git requirements.
var
initMutex
sync
.
Mutex
=
sync
.
Mutex
{}
var
initMutex
=
sync
.
Mutex
{}
// Logger is used to log errors; if nil, the default log.Logger is used.
var
Logger
*
log
.
Logger
...
...
@@ -44,7 +44,7 @@ func logger() *log.Logger {
// Repo is the structure that holds required information
// of a git repository.
type
Repo
struct
{
U
rl
string
// Repository URL
U
RL
string
// Repository URL
Path
string
// Directory to pull to
Host
string
// Git domain host e.g. github.com
Branch
string
// Git branch
...
...
@@ -94,7 +94,7 @@ func (r *Repo) Pull() error {
// Pull performs git clone, or git pull if repository exists
func
(
r
*
Repo
)
pull
()
error
{
params
:=
[]
string
{
"clone"
,
"-b"
,
r
.
Branch
,
r
.
U
rl
,
r
.
Path
}
params
:=
[]
string
{
"clone"
,
"-b"
,
r
.
Branch
,
r
.
U
RL
,
r
.
Path
}
if
r
.
pulled
{
params
=
[]
string
{
"pull"
,
"origin"
,
r
.
Branch
}
}
...
...
@@ -113,7 +113,7 @@ func (r *Repo) pull() error {
if
err
=
runCmd
(
gitBinary
,
params
,
dir
);
err
==
nil
{
r
.
pulled
=
true
r
.
lastPull
=
time
.
Now
()
logger
()
.
Printf
(
"%v pulled.
\n
"
,
r
.
U
rl
)
logger
()
.
Printf
(
"%v pulled.
\n
"
,
r
.
U
RL
)
r
.
lastCommit
,
err
=
r
.
getMostRecentCommit
()
}
return
err
...
...
@@ -122,11 +122,11 @@ func (r *Repo) pull() error {
// pullWithKey is used for private repositories and requires an ssh key.
// Note: currently only limited to Linux and OSX.
func
(
r
*
Repo
)
pullWithKey
(
params
[]
string
)
error
{
var
gitS
sh
,
script
gitos
.
File
var
gitS
SH
,
script
gitos
.
File
// ensure temporary files deleted after usage
defer
func
()
{
if
gitS
sh
!=
nil
{
gos
.
Remove
(
gitS
sh
.
Name
())
if
gitS
SH
!=
nil
{
gos
.
Remove
(
gitS
SH
.
Name
())
}
if
script
!=
nil
{
gos
.
Remove
(
script
.
Name
())
...
...
@@ -135,13 +135,13 @@ func (r *Repo) pullWithKey(params []string) error {
var
err
error
// write git.sh script to temp file
gitS
sh
,
err
=
writeScriptFile
(
gitWrapperScript
())
gitS
SH
,
err
=
writeScriptFile
(
gitWrapperScript
())
if
err
!=
nil
{
return
err
}
// write git clone bash script to file
script
,
err
=
writeScriptFile
(
bashScript
(
gitS
sh
.
Name
(),
r
,
params
))
script
,
err
=
writeScriptFile
(
bashScript
(
gitS
SH
.
Name
(),
r
,
params
))
if
err
!=
nil
{
return
err
}
...
...
@@ -154,7 +154,7 @@ func (r *Repo) pullWithKey(params []string) error {
if
err
=
runCmd
(
script
.
Name
(),
nil
,
dir
);
err
==
nil
{
r
.
pulled
=
true
r
.
lastPull
=
time
.
Now
()
logger
()
.
Printf
(
"%v pulled.
\n
"
,
r
.
U
rl
)
logger
()
.
Printf
(
"%v pulled.
\n
"
,
r
.
U
RL
)
r
.
lastCommit
,
err
=
r
.
getMostRecentCommit
()
}
return
err
...
...
@@ -181,13 +181,13 @@ func (r *Repo) Prepare() error {
if
isGit
{
// check if same repository
var
repoU
rl
string
if
repoU
rl
,
err
=
r
.
getRepoUrl
();
err
==
nil
{
var
repoU
RL
string
if
repoU
RL
,
err
=
r
.
getRepoURL
();
err
==
nil
{
// add .git suffix if missing for adequate comparison.
if
!
strings
.
HasSuffix
(
repoU
rl
,
".git"
)
{
repoU
rl
+=
".git"
if
!
strings
.
HasSuffix
(
repoU
RL
,
".git"
)
{
repoU
RL
+=
".git"
}
if
repoU
rl
==
r
.
Url
{
if
repoU
RL
==
r
.
URL
{
r
.
pulled
=
true
return
nil
}
...
...
@@ -195,7 +195,7 @@ func (r *Repo) Prepare() error {
if
err
!=
nil
{
return
fmt
.
Errorf
(
"Cannot retrieve repo url for %v Error: %v"
,
r
.
Path
,
err
)
}
return
fmt
.
Errorf
(
"Another git repo '%v' exists at %v"
,
repoU
rl
,
r
.
Path
)
return
fmt
.
Errorf
(
"Another git repo '%v' exists at %v"
,
repoU
RL
,
r
.
Path
)
}
return
fmt
.
Errorf
(
"Cannot git clone into %v, directory not empty."
,
r
.
Path
)
}
...
...
@@ -211,8 +211,8 @@ func (r *Repo) getMostRecentCommit() (string, error) {
return
runCmdOutput
(
c
,
args
,
r
.
Path
)
}
// getRepoU
rl
retrieves remote origin url for the git repository at path
func
(
r
*
Repo
)
getRepoU
rl
()
(
string
,
error
)
{
// getRepoU
RL
retrieves remote origin url for the git repository at path
func
(
r
*
Repo
)
getRepoU
RL
()
(
string
,
error
)
{
_
,
err
:=
gos
.
Stat
(
r
.
Path
)
if
err
!=
nil
{
return
""
,
err
...
...
middleware/git/git_test.go
View file @
e4b50aa8
...
...
@@ -63,7 +63,7 @@ func TestGit(t *testing.T) {
// prepare
repos
:=
[]
*
Repo
{
nil
,
&
Repo
{
Path
:
"gitdir"
,
U
rl
:
"success.git"
},
&
Repo
{
Path
:
"gitdir"
,
U
RL
:
"success.git"
},
}
for
_
,
r
:=
range
repos
{
repo
:=
createRepo
(
r
)
...
...
@@ -79,26 +79,26 @@ func TestGit(t *testing.T) {
output
string
}{
{
&
Repo
{
Path
:
"gitdir"
,
U
rl
:
"git@github.com:user/repo.git"
,
KeyPath
:
"~/.key"
,
Then
:
"echo Hello"
},
&
Repo
{
Path
:
"gitdir"
,
U
RL
:
"git@github.com:user/repo.git"
,
KeyPath
:
"~/.key"
,
Then
:
"echo Hello"
},
`git@github.com:user/repo.git pulled.
Command echo Hello successful.
`
,
},
{
&
Repo
{
Path
:
"gitdir"
,
U
rl
:
"https://github.com/user/repo.git"
,
Then
:
"echo Hello"
},
&
Repo
{
Path
:
"gitdir"
,
U
RL
:
"https://github.com/user/repo.git"
,
Then
:
"echo Hello"
},
`https://github.com/user/repo.git pulled.
Command echo Hello successful.
`
,
},
{
&
Repo
{
U
rl
:
"git@github.com:user/repo"
},
&
Repo
{
U
RL
:
"git@github.com:user/repo"
},
`git@github.com:user/repo pulled.
`
,
},
}
for
i
,
test
:=
range
tests
{
gittest
.
CmdOutput
=
test
.
repo
.
U
rl
gittest
.
CmdOutput
=
test
.
repo
.
U
RL
test
.
repo
=
createRepo
(
test
.
repo
)
...
...
@@ -117,8 +117,8 @@ Command echo Hello successful.
// pull with error
repos
=
[]
*
Repo
{
&
Repo
{
Path
:
"gitdir"
,
U
rl
:
"http://github.com:u/repo.git"
},
&
Repo
{
Path
:
"gitdir"
,
U
rl
:
"https://github.com/user/repo.git"
,
Then
:
"echo Hello"
},
&
Repo
{
Path
:
"gitdir"
,
U
RL
:
"http://github.com:u/repo.git"
},
&
Repo
{
Path
:
"gitdir"
,
U
RL
:
"https://github.com/user/repo.git"
,
Then
:
"echo Hello"
},
&
Repo
{
Path
:
"gitdir"
},
&
Repo
{
Path
:
"gitdir"
,
KeyPath
:
".key"
},
}
...
...
@@ -143,7 +143,7 @@ Command echo Hello successful.
func
createRepo
(
r
*
Repo
)
*
Repo
{
repo
:=
&
Repo
{
U
rl
:
"git@github.com/user/test"
,
U
RL
:
"git@github.com/user/test"
,
Path
:
"."
,
Host
:
"github.com"
,
Branch
:
"master"
,
...
...
@@ -170,8 +170,8 @@ func createRepo(r *Repo) *Repo {
if
r
.
Then
!=
""
{
repo
.
Then
=
r
.
Then
}
if
r
.
U
rl
!=
""
{
repo
.
U
rl
=
r
.
Url
if
r
.
U
RL
!=
""
{
repo
.
U
RL
=
r
.
URL
}
return
repo
...
...
middleware/gzip/gzip.go
View file @
e4b50aa8
...
...
@@ -45,9 +45,8 @@ func (g Gzip) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
gz
.
WriteHeader
(
status
)
fmt
.
Fprintf
(
gz
,
"%d %s"
,
status
,
http
.
StatusText
(
status
))
return
0
,
err
}
else
{
return
status
,
err
}
return
status
,
err
}
// gzipResponeWriter wraps the underlying Write method
...
...
middleware/internal/internal.go
View file @
e4b50aa8
//
The p
ackage internal provides a simple middleware that (a) prevents access
//
P
ackage internal provides a simple middleware that (a) prevents access
// to internal locations and (b) allows to return files from internal location
// by setting a special header, e.g. in a proxy response.
package
internal
...
...
@@ -85,7 +85,6 @@ func (w internalResponseWriter) WriteHeader(code int) {
func
(
w
internalResponseWriter
)
Write
(
b
[]
byte
)
(
int
,
error
)
{
if
isInternalRedirect
(
w
)
{
return
0
,
nil
}
else
{
return
w
.
ResponseWriter
.
Write
(
b
)
}
return
w
.
ResponseWriter
.
Write
(
b
)
}
middleware/log/log.go
View file @
e4b50aa8
...
...
@@ -8,9 +8,10 @@ import (
"github.com/mholt/caddy/middleware"
)
// Logger is a basic request logging middleware.
type
Logger
struct
{
Next
middleware
.
Handler
Rules
[]
Log
Rule
Rules
[]
Rule
}
func
(
l
Logger
)
ServeHTTP
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
(
int
,
error
)
{
...
...
@@ -26,7 +27,8 @@ func (l Logger) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
return
l
.
Next
.
ServeHTTP
(
w
,
r
)
}
type
LogRule
struct
{
// Rule configures the logging middleware.
type
Rule
struct
{
PathScope
string
OutputFile
string
Format
string
...
...
middleware/markdown/markdown.go
View file @
e4b50aa8
...
...
@@ -31,9 +31,9 @@ type Markdown struct {
IndexFiles
[]
string
}
//
Helper function to check
if a file is an index file
func
(
m
Markdown
)
IsIndexFile
(
file
string
)
bool
{
for
_
,
f
:=
range
m
.
IndexFiles
{
//
IsIndexFile checks to see
if a file is an index file
func
(
m
d
Markdown
)
IsIndexFile
(
file
string
)
bool
{
for
_
,
f
:=
range
m
d
.
IndexFiles
{
if
f
==
file
{
return
true
}
...
...
@@ -105,7 +105,7 @@ func (md Markdown) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error
if
html
,
err
:=
ioutil
.
ReadFile
(
filepath
);
err
==
nil
{
w
.
Write
(
html
)
return
http
.
StatusOK
,
nil
}
else
{
}
if
os
.
IsPermission
(
err
)
{
return
http
.
StatusForbidden
,
err
}
...
...
@@ -113,7 +113,6 @@ func (md Markdown) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error
}
}
}
}
body
,
err
:=
ioutil
.
ReadAll
(
f
)
if
err
!=
nil
{
...
...
middleware/markdown/metadata.go
View file @
e4b50aa8
...
...
@@ -88,8 +88,8 @@ func (j *JSONMetadataParser) Parse(b []byte) ([]byte, error) {
return
buf
[
:
n
],
nil
}
//
Parsed metadata.
//
Should be called after a call to Parse returns no error
//
Metadata returns parsed metadata. It should be called
//
only after a call to Parse returns without error.
func
(
j
*
JSONMetadataParser
)
Metadata
()
Metadata
{
return
j
.
metadata
}
...
...
@@ -123,8 +123,8 @@ func (t *TOMLMetadataParser) Parse(b []byte) ([]byte, error) {
return
markdown
,
nil
}
//
Parsed metadata.
//
Should be called after a call to Parse returns no error
//
Metadata returns parsed metadata. It should be called
//
only after a call to Parse returns without error.
func
(
t
*
TOMLMetadataParser
)
Metadata
()
Metadata
{
return
t
.
metadata
}
...
...
@@ -171,8 +171,8 @@ func (y *YAMLMetadataParser) Parse(b []byte) ([]byte, error) {
return
markdown
,
nil
}
//
Parsed metadata.
//
Should be called after a call to Parse returns no error
//
Metadata returns parsed metadata. It should be called
//
only after a call to Parse returns without error.
func
(
y
*
YAMLMetadataParser
)
Metadata
()
Metadata
{
return
y
.
metadata
}
...
...
middleware/path.go
View file @
e4b50aa8
...
...
@@ -5,6 +5,8 @@ import "strings"
// Path represents a URI path, maybe with pattern characters.
type
Path
string
// Matches checks to see if other matches p.
//
// Path matching will probably not always be a direct
// comparison; this method assures that paths can be
// easily and consistently matched.
...
...
server/fileserver.go
View file @
e4b50aa8
...
...
@@ -10,7 +10,7 @@ import (
"github.com/mholt/caddy/middleware/browse"
)
//
This
FileServer is adapted from the one in net/http by
// FileServer is adapted from the one in net/http by
// the Go authors. Significant modifications have been made.
//
//
...
...
@@ -28,15 +28,16 @@ type fileHandler struct {
hide
[]
string
// list of files to treat as "Not Found"
}
func
(
f
*
fileHandler
)
ServeHTTP
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
(
int
,
error
)
{
func
(
f
h
*
fileHandler
)
ServeHTTP
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
(
int
,
error
)
{
upath
:=
r
.
URL
.
Path
if
!
strings
.
HasPrefix
(
upath
,
"/"
)
{
upath
=
"/"
+
upath
r
.
URL
.
Path
=
upath
}
return
f
.
serveFile
(
w
,
r
,
path
.
Clean
(
upath
))
return
f
h
.
serveFile
(
w
,
r
,
path
.
Clean
(
upath
))
}
// serveFile writes the specified file to the HTTP response.
// name is '/'-separated, not filepath.Separator.
func
(
fh
*
fileHandler
)
serveFile
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
,
name
string
)
(
int
,
error
)
{
f
,
err
:=
fh
.
root
.
Open
(
name
)
...
...
server/server.go
View file @
e4b50aa8
...
...
@@ -97,9 +97,8 @@ func (s *Server) Serve() error {
tlsConfigs
=
append
(
tlsConfigs
,
vh
.
config
.
TLS
)
}
return
ListenAndServeTLSWithSNI
(
server
,
tlsConfigs
)
}
else
{
return
server
.
ListenAndServe
()
}
return
server
.
ListenAndServe
()
}
// ListenAndServeTLSWithSNI serves TLS with Server Name Indication (SNI) support, which allows
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment