Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in / Register
Toggle navigation
T
Titanium-Web-Proxy
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Administrator
Titanium-Web-Proxy
Commits
3f66d38c
Commit
3f66d38c
authored
Feb 10, 2018
by
Honfika
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
PROPFIND method is not supported (#367)
parent
788deefa
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
82 additions
and
19 deletions
+82
-19
StringExtensions.cs
Titanium.Web.Proxy/Extensions/StringExtensions.cs
+7
-1
HttpHelper.cs
Titanium.Web.Proxy/Helpers/HttpHelper.cs
+3
-2
Tcp.cs
Titanium.Web.Proxy/Helpers/Tcp.cs
+28
-3
HttpWebClient.cs
Titanium.Web.Proxy/Http/HttpWebClient.cs
+5
-4
KnownHeaders.cs
Titanium.Web.Proxy/Http/KnownHeaders.cs
+1
-0
Request.cs
Titanium.Web.Proxy/Http/Request.cs
+28
-2
Response.cs
Titanium.Web.Proxy/Http/Response.cs
+1
-1
TcpConnectionFactory.cs
Titanium.Web.Proxy/Network/Tcp/TcpConnectionFactory.cs
+5
-3
ProxyAuthorizationHandler.cs
Titanium.Web.Proxy/ProxyAuthorizationHandler.cs
+2
-1
RequestHandler.cs
Titanium.Web.Proxy/RequestHandler.cs
+2
-2
No files found.
Titanium.Web.Proxy/Extensions/StringExtensions.cs
View file @
3f66d38c
using
System.Globalization
;
using
System
;
using
System.Globalization
;
namespace
Titanium.Web.Proxy.Extensions
namespace
Titanium.Web.Proxy.Extensions
{
{
internal
static
class
StringExtensions
internal
static
class
StringExtensions
{
{
internal
static
bool
EqualsIgnoreCase
(
this
string
str
,
string
value
)
{
return
str
.
Equals
(
value
,
StringComparison
.
CurrentCultureIgnoreCase
);
}
internal
static
bool
ContainsIgnoreCase
(
this
string
str
,
string
value
)
internal
static
bool
ContainsIgnoreCase
(
this
string
str
,
string
value
)
{
{
return
CultureInfo
.
CurrentCulture
.
CompareInfo
.
IndexOf
(
str
,
value
,
CompareOptions
.
IgnoreCase
)
>=
0
;
return
CultureInfo
.
CurrentCulture
.
CompareInfo
.
IndexOf
(
str
,
value
,
CompareOptions
.
IgnoreCase
)
>=
0
;
...
...
Titanium.Web.Proxy/Helpers/HttpHelper.cs
View file @
3f66d38c
using
System
;
using
System
;
using
System.Text
;
using
System.Text
;
using
Titanium.Web.Proxy.Extensions
;
using
Titanium.Web.Proxy.Http
;
using
Titanium.Web.Proxy.Http
;
using
Titanium.Web.Proxy.Shared
;
using
Titanium.Web.Proxy.Shared
;
...
@@ -29,7 +30,7 @@ namespace Titanium.Web.Proxy.Helpers
...
@@ -29,7 +30,7 @@ namespace Titanium.Web.Proxy.Helpers
foreach
(
string
parameter
in
parameters
)
foreach
(
string
parameter
in
parameters
)
{
{
var
split
=
parameter
.
Split
(
ProxyConstants
.
EqualSplit
,
2
);
var
split
=
parameter
.
Split
(
ProxyConstants
.
EqualSplit
,
2
);
if
(
split
.
Length
==
2
&&
split
[
0
].
Trim
().
Equals
(
KnownHeaders
.
ContentTypeCharset
,
StringComparison
.
CurrentCultureIgnoreCase
))
if
(
split
.
Length
==
2
&&
split
[
0
].
Trim
().
Equals
IgnoreCase
(
KnownHeaders
.
ContentTypeCharset
))
{
{
string
value
=
split
[
1
];
string
value
=
split
[
1
];
if
(
value
.
Equals
(
"x-user-defined"
,
StringComparison
.
OrdinalIgnoreCase
))
if
(
value
.
Equals
(
"x-user-defined"
,
StringComparison
.
OrdinalIgnoreCase
))
...
@@ -66,7 +67,7 @@ namespace Titanium.Web.Proxy.Helpers
...
@@ -66,7 +67,7 @@ namespace Titanium.Web.Proxy.Helpers
foreach
(
string
parameter
in
parameters
)
foreach
(
string
parameter
in
parameters
)
{
{
var
split
=
parameter
.
Split
(
ProxyConstants
.
EqualSplit
,
2
);
var
split
=
parameter
.
Split
(
ProxyConstants
.
EqualSplit
,
2
);
if
(
split
.
Length
==
2
&&
split
[
0
].
Trim
().
Equals
(
KnownHeaders
.
ContentTypeBoundary
,
StringComparison
.
CurrentCultureIgnoreCase
))
if
(
split
.
Length
==
2
&&
split
[
0
].
Trim
().
Equals
IgnoreCase
(
KnownHeaders
.
ContentTypeBoundary
))
{
{
string
value
=
split
[
1
];
string
value
=
split
[
1
];
if
(
value
.
Length
>
2
&&
value
[
0
]
==
'"'
&&
value
[
value
.
Length
-
1
]
==
'"'
)
if
(
value
.
Length
>
2
&&
value
[
0
]
==
'"'
&&
value
[
value
.
Length
-
1
]
==
'"'
)
...
...
Titanium.Web.Proxy/Helpers/Tcp.cs
View file @
3f66d38c
...
@@ -36,7 +36,8 @@ namespace Titanium.Web.Proxy.Helpers
...
@@ -36,7 +36,8 @@ namespace Titanium.Web.Proxy.Helpers
try
try
{
{
tcpTable
=
Marshal
.
AllocHGlobal
(
tcpTableLength
);
tcpTable
=
Marshal
.
AllocHGlobal
(
tcpTableLength
);
if
(
NativeMethods
.
GetExtendedTcpTable
(
tcpTable
,
ref
tcpTableLength
,
true
,
ipVersionValue
,
(
int
)
NativeMethods
.
TcpTableType
.
OwnerPidAll
,
0
)
==
0
)
if
(
NativeMethods
.
GetExtendedTcpTable
(
tcpTable
,
ref
tcpTableLength
,
true
,
ipVersionValue
,
(
int
)
NativeMethods
.
TcpTableType
.
OwnerPidAll
,
0
)
==
0
)
{
{
var
table
=
(
NativeMethods
.
TcpTable
)
Marshal
.
PtrToStructure
(
tcpTable
,
typeof
(
NativeMethods
.
TcpTable
));
var
table
=
(
NativeMethods
.
TcpTable
)
Marshal
.
PtrToStructure
(
tcpTable
,
typeof
(
NativeMethods
.
TcpTable
));
...
@@ -77,7 +78,8 @@ namespace Titanium.Web.Proxy.Helpers
...
@@ -77,7 +78,8 @@ namespace Titanium.Web.Proxy.Helpers
try
try
{
{
tcpTable
=
Marshal
.
AllocHGlobal
(
tcpTableLength
);
tcpTable
=
Marshal
.
AllocHGlobal
(
tcpTableLength
);
if
(
NativeMethods
.
GetExtendedTcpTable
(
tcpTable
,
ref
tcpTableLength
,
true
,
ipVersionValue
,
(
int
)
NativeMethods
.
TcpTableType
.
OwnerPidAll
,
0
)
==
0
)
if
(
NativeMethods
.
GetExtendedTcpTable
(
tcpTable
,
ref
tcpTableLength
,
true
,
ipVersionValue
,
(
int
)
NativeMethods
.
TcpTableType
.
OwnerPidAll
,
0
)
==
0
)
{
{
var
table
=
(
NativeMethods
.
TcpTable
)
Marshal
.
PtrToStructure
(
tcpTable
,
typeof
(
NativeMethods
.
TcpTable
));
var
table
=
(
NativeMethods
.
TcpTable
)
Marshal
.
PtrToStructure
(
tcpTable
,
typeof
(
NativeMethods
.
TcpTable
));
...
@@ -142,7 +144,8 @@ namespace Titanium.Web.Proxy.Helpers
...
@@ -142,7 +144,8 @@ namespace Titanium.Web.Proxy.Helpers
}
}
}
}
private
static
void
BeginRead
(
Stream
inputStream
,
Stream
outputStream
,
byte
[]
buffer
,
CancellationTokenSource
cts
,
Action
<
byte
[],
int
,
int
>
onCopy
,
Action
<
Exception
>
exceptionFunc
)
private
static
void
BeginRead
(
Stream
inputStream
,
Stream
outputStream
,
byte
[]
buffer
,
CancellationTokenSource
cts
,
Action
<
byte
[],
int
,
int
>
onCopy
,
Action
<
Exception
>
exceptionFunc
)
{
{
if
(
cts
.
IsCancellationRequested
)
if
(
cts
.
IsCancellationRequested
)
{
{
...
@@ -231,5 +234,27 @@ namespace Titanium.Web.Proxy.Helpers
...
@@ -231,5 +234,27 @@ namespace Titanium.Web.Proxy.Helpers
await
Task
.
WhenAll
(
sendRelay
,
receiveRelay
);
await
Task
.
WhenAll
(
sendRelay
,
receiveRelay
);
}
}
/// <summary>
/// relays the input clientStream to the server at the specified host name and port with the given httpCmd and headers as prefix
/// Usefull for websocket requests
/// </summary>
/// <param name="clientStream"></param>
/// <param name="serverStream"></param>
/// <param name="bufferSize"></param>
/// <param name="onDataSend"></param>
/// <param name="onDataReceive"></param>
/// <param name="exceptionFunc"></param>
/// <returns></returns>
internal
static
Task
SendRaw
(
Stream
clientStream
,
Stream
serverStream
,
int
bufferSize
,
Action
<
byte
[],
int
,
int
>
onDataSend
,
Action
<
byte
[],
int
,
int
>
onDataReceive
,
Action
<
Exception
>
exceptionFunc
)
{
#if NET45
return
SendRawApm
(
clientStream
,
serverStream
,
bufferSize
,
onDataSend
,
onDataReceive
,
exceptionFunc
);
#else
// todo: Apm hangs in dotnet core
return
SendRawTap
(
clientStream
,
serverStream
,
bufferSize
,
onDataSend
,
onDataReceive
,
exceptionFunc
);
#endif
}
}
}
}
}
\ No newline at end of file
Titanium.Web.Proxy/Http/HttpWebClient.cs
View file @
3f66d38c
...
@@ -2,6 +2,7 @@ using System;
...
@@ -2,6 +2,7 @@ using System;
using
System.IO
;
using
System.IO
;
using
System.Net
;
using
System.Net
;
using
System.Threading.Tasks
;
using
System.Threading.Tasks
;
using
Titanium.Web.Proxy.Extensions
;
using
Titanium.Web.Proxy.Models
;
using
Titanium.Web.Proxy.Models
;
using
Titanium.Web.Proxy.Network.Tcp
;
using
Titanium.Web.Proxy.Network.Tcp
;
...
@@ -123,13 +124,13 @@ namespace Titanium.Web.Proxy.Http
...
@@ -123,13 +124,13 @@ namespace Titanium.Web.Proxy.Http
//find if server is willing for expect continue
//find if server is willing for expect continue
if
(
responseStatusCode
==
(
int
)
HttpStatusCode
.
Continue
if
(
responseStatusCode
==
(
int
)
HttpStatusCode
.
Continue
&&
responseStatusDescription
.
Equals
(
"continue"
,
StringComparison
.
CurrentCultureIgnoreCase
))
&&
responseStatusDescription
.
Equals
IgnoreCase
(
"continue"
))
{
{
Request
.
Is100Continue
=
true
;
Request
.
Is100Continue
=
true
;
await
ServerConnection
.
StreamReader
.
ReadLineAsync
();
await
ServerConnection
.
StreamReader
.
ReadLineAsync
();
}
}
else
if
(
responseStatusCode
==
(
int
)
HttpStatusCode
.
ExpectationFailed
else
if
(
responseStatusCode
==
(
int
)
HttpStatusCode
.
ExpectationFailed
&&
responseStatusDescription
.
Equals
(
"expectation failed"
,
StringComparison
.
CurrentCultureIgnoreCase
))
&&
responseStatusDescription
.
Equals
IgnoreCase
(
"expectation failed"
))
{
{
Request
.
ExpectationFailed
=
true
;
Request
.
ExpectationFailed
=
true
;
await
ServerConnection
.
StreamReader
.
ReadLineAsync
();
await
ServerConnection
.
StreamReader
.
ReadLineAsync
();
...
@@ -168,7 +169,7 @@ namespace Titanium.Web.Proxy.Http
...
@@ -168,7 +169,7 @@ namespace Titanium.Web.Proxy.Http
//For HTTP 1.1 comptibility server may send expect-continue even if not asked for it in request
//For HTTP 1.1 comptibility server may send expect-continue even if not asked for it in request
if
(
Response
.
StatusCode
==
(
int
)
HttpStatusCode
.
Continue
if
(
Response
.
StatusCode
==
(
int
)
HttpStatusCode
.
Continue
&&
Response
.
StatusDescription
.
Equals
(
"continue"
,
StringComparison
.
CurrentCultureIgnoreCase
))
&&
Response
.
StatusDescription
.
Equals
IgnoreCase
(
"continue"
))
{
{
//Read the next line after 100-continue
//Read the next line after 100-continue
Response
.
Is100Continue
=
true
;
Response
.
Is100Continue
=
true
;
...
@@ -181,7 +182,7 @@ namespace Titanium.Web.Proxy.Http
...
@@ -181,7 +182,7 @@ namespace Titanium.Web.Proxy.Http
}
}
if
(
Response
.
StatusCode
==
(
int
)
HttpStatusCode
.
ExpectationFailed
if
(
Response
.
StatusCode
==
(
int
)
HttpStatusCode
.
ExpectationFailed
&&
Response
.
StatusDescription
.
Equals
(
"expectation failed"
,
StringComparison
.
CurrentCultureIgnoreCase
))
&&
Response
.
StatusDescription
.
Equals
IgnoreCase
(
"expectation failed"
))
{
{
//read next line after expectation failed response
//read next line after expectation failed response
Response
.
ExpectationFailed
=
true
;
Response
.
ExpectationFailed
=
true
;
...
...
Titanium.Web.Proxy/Http/KnownHeaders.cs
View file @
3f66d38c
...
@@ -33,6 +33,7 @@ namespace Titanium.Web.Proxy.Http
...
@@ -33,6 +33,7 @@ namespace Titanium.Web.Proxy.Http
public
const
string
Host
=
"host"
;
public
const
string
Host
=
"host"
;
public
const
string
ProxyAuthorization
=
"Proxy-Authorization"
;
public
const
string
ProxyAuthorization
=
"Proxy-Authorization"
;
public
const
string
ProxyAuthorizationBasic
=
"basic"
;
public
const
string
ProxyConnection
=
"Proxy-Connection"
;
public
const
string
ProxyConnection
=
"Proxy-Connection"
;
public
const
string
ProxyConnectionClose
=
"close"
;
public
const
string
ProxyConnectionClose
=
"close"
;
...
...
Titanium.Web.Proxy/Http/Request.cs
View file @
3f66d38c
...
@@ -58,7 +58,33 @@ namespace Titanium.Web.Proxy.Http
...
@@ -58,7 +58,33 @@ namespace Titanium.Web.Proxy.Http
/// <summary>
/// <summary>
/// Has request body?
/// Has request body?
/// </summary>
/// </summary>
public
bool
HasBody
=>
Method
==
"POST"
||
Method
==
"PUT"
||
Method
==
"PATCH"
;
public
bool
HasBody
{
get
{
long
contentLength
=
ContentLength
;
//If content length is set to 0 the request has no body
if
(
contentLength
==
0
)
{
return
false
;
}
//Has body only if request is chunked or content length >0
if
(
IsChunked
||
contentLength
>
0
)
{
return
true
;
}
//has body if POST and when version is http/1.0
if
(
Method
==
"POST"
&&
HttpVersion
==
HttpHeader
.
Version10
)
{
return
true
;
}
return
false
;
}
}
/// <summary>
/// <summary>
/// Http hostname header value if exists
/// Http hostname header value if exists
...
@@ -248,7 +274,7 @@ namespace Titanium.Web.Proxy.Http
...
@@ -248,7 +274,7 @@ namespace Titanium.Web.Proxy.Http
return
false
;
return
false
;
}
}
return
headerValue
.
Equals
(
KnownHeaders
.
UpgradeWebsocket
,
StringComparison
.
CurrentCultureIgnoreCase
);
return
headerValue
.
Equals
IgnoreCase
(
KnownHeaders
.
UpgradeWebsocket
);
}
}
}
}
...
...
Titanium.Web.Proxy/Http/Response.cs
View file @
3f66d38c
...
@@ -98,7 +98,7 @@ namespace Titanium.Web.Proxy.Http
...
@@ -98,7 +98,7 @@ namespace Titanium.Web.Proxy.Http
if
(
headerValue
!=
null
)
if
(
headerValue
!=
null
)
{
{
if
(
headerValue
.
Contain
sIgnoreCase
(
KnownHeaders
.
ConnectionClose
))
if
(
headerValue
.
Equal
sIgnoreCase
(
KnownHeaders
.
ConnectionClose
))
{
{
return
false
;
return
false
;
}
}
...
...
Titanium.Web.Proxy/Network/Tcp/TcpConnectionFactory.cs
View file @
3f66d38c
...
@@ -86,9 +86,12 @@ namespace Titanium.Web.Proxy.Network.Tcp
...
@@ -86,9 +86,12 @@ namespace Titanium.Web.Proxy.Network.Tcp
using
(
var
reader
=
new
CustomBinaryReader
(
stream
,
server
.
BufferSize
))
using
(
var
reader
=
new
CustomBinaryReader
(
stream
,
server
.
BufferSize
))
{
{
string
result
=
await
reader
.
ReadLineAsync
();
string
httpStatus
=
await
reader
.
ReadLineAsync
();
if
(!
new
[]
{
"200 OK"
,
"connection established"
}.
Any
(
s
=>
result
.
ContainsIgnoreCase
(
s
)))
Response
.
ParseResponseLine
(
httpStatus
,
out
var
version
,
out
int
statusCode
,
out
string
statusDescription
);
if
(!
statusDescription
.
EqualsIgnoreCase
(
"200 OK"
)
&&
!
statusDescription
.
EqualsIgnoreCase
(
"connection established"
))
{
{
throw
new
Exception
(
"Upstream proxy failed to create a secure tunnel"
);
throw
new
Exception
(
"Upstream proxy failed to create a secure tunnel"
);
}
}
...
@@ -99,7 +102,6 @@ namespace Titanium.Web.Proxy.Network.Tcp
...
@@ -99,7 +102,6 @@ namespace Titanium.Web.Proxy.Network.Tcp
if
(
isHttps
)
if
(
isHttps
)
{
{
var
sslStream
=
new
SslStream
(
stream
,
false
,
server
.
ValidateServerCertificate
,
server
.
SelectClientCertificate
);
var
sslStream
=
new
SslStream
(
stream
,
false
,
server
.
ValidateServerCertificate
,
server
.
SelectClientCertificate
);
stream
=
new
CustomBufferedStream
(
sslStream
,
server
.
BufferSize
);
stream
=
new
CustomBufferedStream
(
sslStream
,
server
.
BufferSize
);
...
...
Titanium.Web.Proxy/ProxyAuthorizationHandler.cs
View file @
3f66d38c
...
@@ -5,6 +5,7 @@ using System.Text;
...
@@ -5,6 +5,7 @@ using System.Text;
using
System.Threading.Tasks
;
using
System.Threading.Tasks
;
using
Titanium.Web.Proxy.EventArguments
;
using
Titanium.Web.Proxy.EventArguments
;
using
Titanium.Web.Proxy.Exceptions
;
using
Titanium.Web.Proxy.Exceptions
;
using
Titanium.Web.Proxy.Extensions
;
using
Titanium.Web.Proxy.Helpers
;
using
Titanium.Web.Proxy.Helpers
;
using
Titanium.Web.Proxy.Http
;
using
Titanium.Web.Proxy.Http
;
using
Titanium.Web.Proxy.Models
;
using
Titanium.Web.Proxy.Models
;
...
@@ -33,7 +34,7 @@ namespace Titanium.Web.Proxy
...
@@ -33,7 +34,7 @@ namespace Titanium.Web.Proxy
}
}
var
headerValueParts
=
header
.
Value
.
Split
(
ProxyConstants
.
SpaceSplit
);
var
headerValueParts
=
header
.
Value
.
Split
(
ProxyConstants
.
SpaceSplit
);
if
(
headerValueParts
.
Length
!=
2
||
!
headerValueParts
[
0
].
Equals
(
"basic"
,
StringComparison
.
CurrentCultureIgnoreCase
))
if
(
headerValueParts
.
Length
!=
2
||
!
headerValueParts
[
0
].
Equals
IgnoreCase
(
KnownHeaders
.
ProxyAuthorizationBasic
))
{
{
//Return not authorized
//Return not authorized
session
.
WebSession
.
Response
=
await
SendAuthentication407Response
(
clientStreamWriter
,
"Proxy Authentication Invalid"
);
session
.
WebSession
.
Response
=
await
SendAuthentication407Response
(
clientStreamWriter
,
"Proxy Authentication Invalid"
);
...
...
Titanium.Web.Proxy/RequestHandler.cs
View file @
3f66d38c
...
@@ -196,7 +196,7 @@ namespace Titanium.Web.Proxy
...
@@ -196,7 +196,7 @@ namespace Titanium.Web.Proxy
((
ConnectResponse
)
connectArgs
.
WebSession
.
Response
).
ServerHelloInfo
=
serverHelloInfo
;
((
ConnectResponse
)
connectArgs
.
WebSession
.
Response
).
ServerHelloInfo
=
serverHelloInfo
;
}
}
await
TcpHelper
.
SendRaw
Apm
(
clientStream
,
connection
.
Stream
,
BufferSize
,
await
TcpHelper
.
SendRaw
(
clientStream
,
connection
.
Stream
,
BufferSize
,
(
buffer
,
offset
,
count
)
=>
{
connectArgs
.
OnDataSent
(
buffer
,
offset
,
count
);
},
(
buffer
,
offset
,
count
)
=>
{
connectArgs
.
OnDataSent
(
buffer
,
offset
,
count
);
},
(
buffer
,
offset
,
count
)
=>
{
connectArgs
.
OnDataReceived
(
buffer
,
offset
,
count
);
},
(
buffer
,
offset
,
count
)
=>
{
connectArgs
.
OnDataReceived
(
buffer
,
offset
,
count
);
},
ExceptionFunc
);
ExceptionFunc
);
...
@@ -475,7 +475,7 @@ namespace Titanium.Web.Proxy
...
@@ -475,7 +475,7 @@ namespace Titanium.Web.Proxy
await
BeforeResponse
.
InvokeAsync
(
this
,
args
,
ExceptionFunc
);
await
BeforeResponse
.
InvokeAsync
(
this
,
args
,
ExceptionFunc
);
}
}
await
TcpHelper
.
SendRaw
Apm
(
clientStream
,
connection
.
Stream
,
BufferSize
,
await
TcpHelper
.
SendRaw
(
clientStream
,
connection
.
Stream
,
BufferSize
,
(
buffer
,
offset
,
count
)
=>
{
args
.
OnDataSent
(
buffer
,
offset
,
count
);
},
(
buffer
,
offset
,
count
)
=>
{
args
.
OnDataSent
(
buffer
,
offset
,
count
);
},
(
buffer
,
offset
,
count
)
=>
{
args
.
OnDataReceived
(
buffer
,
offset
,
count
);
},
(
buffer
,
offset
,
count
)
=>
{
args
.
OnDataReceived
(
buffer
,
offset
,
count
);
},
ExceptionFunc
);
ExceptionFunc
);
...
...
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