Merge branch 'feature/files' of github.com:ONLYOFFICE/AppServer into feature/files

This commit is contained in:
Artem Tarasov 2020-08-27 13:35:29 +03:00
commit 20cefbf1e4
146 changed files with 40400 additions and 95 deletions

Binary file not shown.

View File

@ -60,6 +60,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ASC.Data.Backup", "common\s
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ASC.Files.Core", "products\ASC.Files\Core\ASC.Files.Core.csproj", "{F0A39728-940D-4DBE-A37A-05D4EB57F342}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ASC.Socket.IO.Svc", "common\services\ASC.Socket.IO.Svc\ASC.Socket.IO.Svc.csproj", "{2DADEAD3-0FE9-4199-9817-41A32E6BF450}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -174,6 +176,10 @@ Global
{F0A39728-940D-4DBE-A37A-05D4EB57F342}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F0A39728-940D-4DBE-A37A-05D4EB57F342}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F0A39728-940D-4DBE-A37A-05D4EB57F342}.Release|Any CPU.Build.0 = Release|Any CPU
{2DADEAD3-0FE9-4199-9817-41A32E6BF450}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2DADEAD3-0FE9-4199-9817-41A32E6BF450}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2DADEAD3-0FE9-4199-9817-41A32E6BF450}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2DADEAD3-0FE9-4199-9817-41A32E6BF450}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -27,6 +27,9 @@ call build\scripts\urlshortener.sh
echo "ASC.Thumbnails"
call build\scripts\thumbnails.sh
echo "ASC.Socket.IO"
call build\scripts\socket.sh
echo "ASC.Web.sln"
call dotnet build ASC.Web.sln /fl1 /flp1:LogFile=build/ASC.Web.log;Verbosity=Normal

View File

@ -140,43 +140,45 @@ RUN cd /app/onlyoffice/src/ && \
cp -f config/nginx/onlyoffice*.conf /etc/nginx/conf.d/ && \
mkdir -p /etc/nginx/includes/ && cp -f config/nginx/includes/onlyoffice*.conf /etc/nginx/includes/ && \
sed -e 's/#//' -i /etc/nginx/conf.d/onlyoffice.conf && \
dotnet restore ASC.Web.sln --configfile .nuget/NuGet.Config && \
dotnet build -r linux-x64 ASC.Web.sln && \
cd products/ASC.People/Server && \
dotnet -d publish -o /var/www/products/ASC.People/server && \
dotnet -d publish --no-build --self-contained -r linux-x64 -o /var/www/products/ASC.People/server && \
cd ../../../ && \
cd products/ASC.Files/Server && \
dotnet -d publish -o /var/www/products/ASC.Files/server && \
dotnet -d publish --no-build --self-contained -r linux-x64 -o /var/www/products/ASC.Files/server && \
cp -avrf DocStore /var/www/products/ASC.Files/server/ && \
cd ../../../ && \
cd products/ASC.Files/Service && \
dotnet -d publish -o /var/www/products/ASC.Files/service && \
dotnet -d publish --no-build --self-contained -r linux-x64 -o /var/www/products/ASC.Files/service && \
cd ../../../ && \
cd web/ASC.Web.Api && \
dotnet -d publish -o /var/www/studio/api && \
dotnet -d publish --no-build --self-contained -r linux-x64 -o /var/www/studio/api && \
cd ../../ && \
cd web/ASC.Web.Studio && \
dotnet -d publish -o /var/www/studio/server && \
dotnet -d publish --no-build --self-contained -r linux-x64 -o /var/www/studio/server && \
cd ../../ && \
cd common/services/ASC.Data.Backup && \
dotnet -d publish -o /var/www/services/backup && \
dotnet -d publish --no-build --self-contained -r linux-x64 -o /var/www/services/backup && \
cd ../../../ && \
cd common/services/ASC.Notify && \
dotnet -d publish -o /var/www/services/notify && \
dotnet -d publish --no-build --self-contained -r linux-x64 -o /var/www/services/notify && \
cd ../../../ && \
cd common/services/ASC.ApiSystem && \
dotnet -d publish -o /var/www/services/apisystem && \
dotnet -d publish --no-build --self-contained -r linux-x64 -o /var/www/services/apisystem && \
cd ../../../ && \
cd common/services/ASC.Thumbnails.Svc && \
dotnet -d publish -o /services/thumb/service && \
dotnet -d publish --no-build --self-contained -r linux-x64 -o /services/thumb/service && \
cd ../../../ && \
yarn install --cwd common/ASC.Thumbnails --frozen-lockfile && \
mkdir -p /var/www/services/thumb/client && cp -Rf common/ASC.Thumbnails/* /var/www/services/thumb/client && \
cd common/services/ASC.UrlShortener.Svc && \
dotnet -d publish -o /services/urlshortener/service && \
dotnet -d publish --no-build --self-contained -r linux-x64 -o /services/urlshortener/service && \
cd ../../../ && \
yarn install --cwd common/ASC.UrlShortener --frozen-lockfile && \
mkdir -p /var/www/services/urlshortener/client && cp -Rf common/ASC.UrlShortener/* /var/www/services/urlshortener/client && \
cd common/services/ASC.Studio.Notify && \
dotnet -d publish -o /var/www/services/studio.notify
dotnet -d publish --no-build --self-contained -r linux-x64 -o /var/www/services/studio.notify
COPY config/mysql/conf.d/mysql.cnf /etc/mysql/conf.d/mysql.cnf
COPY config/supervisor/supervisord.conf /etc/supervisor/conf.d/supervisord.conf

2
build/run/Socket.IO.bat Normal file
View File

@ -0,0 +1,2 @@
echo "RUN ASC.Socket.IO.Svc"
call dotnet run --project ..\..\common\services\ASC.Socket.IO.Svc\ASC.Socket.IO.Svc.csproj --no-build --$STORAGE_ROOT=..\..\Data --log__dir=..\..\Logs --log__name=socket

View File

@ -1,2 +1,2 @@
echo "RUN ASC.Web.Studio"
echo "RUN ASC.Thumbnails.Svc"
call dotnet run --project ..\..\common\services\ASC.Thumbnails.Svc\ASC.Thumbnails.Svc.csproj --no-build --$STORAGE_ROOT=..\..\Data --log__dir=..\..\Logs --log__name=thumbnails

1
build/scripts/socket.sh Normal file
View File

@ -0,0 +1 @@
yarn install --cwd common/ASC.Socket.IO --frozen-lockfile

View File

@ -144,7 +144,7 @@ namespace ASC.Core.Common.Configuration
IConfiguration configuration,
ICacheNotify<ConsumerCacheItem> cache,
ConsumerFactory consumerFactory,
string name, int order, Dictionary<string, string> props, Dictionary<string, string> additional)
string name, int order, Dictionary<string, string> props, Dictionary<string, string> additional = null)
: this(tenantManager, coreBaseSettings, coreSettings, configuration, cache, consumerFactory)
{
Name = name;

View File

@ -397,7 +397,7 @@ namespace ASC.Core.Notify.Signalr
{
if (services.TryAddScoped<SignalrServiceClient>())
{
services.TryAddScoped<IConfigureOptions<SignalrServiceClient>, ConfigureSignalrServiceClient>();
services.TryAddScoped<IConfigureNamedOptions<SignalrServiceClient>, ConfigureSignalrServiceClient>();
return services
.AddTenantManagerService()

View File

@ -33,9 +33,7 @@ using System.Xml.XPath;
using ASC.Common.Caching;
using ASC.Core;
using ASC.Core.Common;
using ASC.Core.Common.Configuration;
using ASC.Core.Tenants;
using Microsoft.Extensions.Configuration;
@ -72,7 +70,7 @@ namespace ASC.FederatedLogin.LoginProviders
{
}
public bool ValidateKeys(AuthContext authContext, TenantUtil tenantUtil, SecurityContext securityContext, TenantManager tenantManager, BaseCommonLinkUtility baseCommonLinkUtility)
public bool ValidateKeys()
{
try
{

View File

@ -23,15 +23,10 @@
*
*/
using ASC.Core;
using ASC.Core.Common;
using ASC.Core.Tenants;
namespace ASC.FederatedLogin.LoginProviders
{
public interface IValidateKeysProvider
{
bool ValidateKeys(AuthContext authContext, TenantUtil tenantUtil, SecurityContext securityContext, TenantManager tenantManager, BaseCommonLinkUtility baseCommonLinkUtility);
bool ValidateKeys();
}
}

View File

@ -16,9 +16,10 @@
<PackageReference Include="Microsoft.Extensions.Configuration" Version="3.1.5" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.5" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.5" />
<PackageReference Include="Microsoft.Extensions.Options" Version="3.1.7" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ASC.Common\ASC.Common.csproj" />
<ProjectReference Include="..\ASC.Core.Common\ASC.Core.Common.csproj" />

View File

@ -5,6 +5,10 @@ VisualStudioVersion = 16.0.30406.217
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ASC.Resource.Manager", "ASC.Resource.Manager.csproj", "{D93E320F-84CC-4BBF-ADBA-9080EDCB3977}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ASC.Common", "..\ASC.Common\ASC.Common.csproj", "{0EAD2A16-007A-4436-86AA-FE8765595251}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ASC.Core.Common", "..\ASC.Core.Common\ASC.Core.Common.csproj", "{A51D0454-4AFA-46DE-89D4-B03D37E1816C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -15,6 +19,14 @@ Global
{D93E320F-84CC-4BBF-ADBA-9080EDCB3977}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D93E320F-84CC-4BBF-ADBA-9080EDCB3977}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D93E320F-84CC-4BBF-ADBA-9080EDCB3977}.Release|Any CPU.Build.0 = Release|Any CPU
{0EAD2A16-007A-4436-86AA-FE8765595251}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0EAD2A16-007A-4436-86AA-FE8765595251}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0EAD2A16-007A-4436-86AA-FE8765595251}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0EAD2A16-007A-4436-86AA-FE8765595251}.Release|Any CPU.Build.0 = Release|Any CPU
{A51D0454-4AFA-46DE-89D4-B03D37E1816C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A51D0454-4AFA-46DE-89D4-B03D37E1816C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A51D0454-4AFA-46DE-89D4-B03D37E1816C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A51D0454-4AFA-46DE-89D4-B03D37E1816C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -0,0 +1,145 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">11.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
<Name>ASC.Socket.IO</Name>
<RootNamespace>ASC.Socket.IO</RootNamespace>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>325ae82f-b416-4f29-94df-c5b18fc4926d</ProjectGuid>
<ProjectHome>.</ProjectHome>
<StartupFile>app.js</StartupFile>
<SearchPath>
</SearchPath>
<WorkingDirectory>.</WorkingDirectory>
<OutputPath>.</OutputPath>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<ProjectTypeGuids>{3AF33F2E-1136-4D97-BBB7-1795711AC8B8};{349c5851-65df-11da-9384-00065b846f21};{9092AA53-FB77-4645-B42D-1CCCA6BD08BD}</ProjectTypeGuids>
<ProjectView>ShowAllFiles</ProjectView>
<NodejsPort>3000</NodejsPort>
<StartWebBrowser>true</StartWebBrowser>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<DebugSymbols>true</DebugSymbols>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DebugSymbols>true</DebugSymbols>
</PropertyGroup>
<ItemGroup>
<Compile Include="app.js" />
<Compile Include="app\apiRequestManager.js" />
<Compile Include="app\controllers\chat.js" />
<Compile Include="app\controllers\counters.js" />
<Compile Include="app\controllers\files.js" />
<Compile Include="app\controllers\index.js" />
<Compile Include="app\controllers\mail.js" />
<Compile Include="app\controllers\voip.js" />
<Compile Include="app\hubs\chat.js" />
<Compile Include="app\hubs\counters.js" />
<Compile Include="app\hubs\files.js" />
<Compile Include="app\hubs\voip.js" />
<Compile Include="app\hubs\voipListPhones.js" />
<Compile Include="app\hubs\voipPhone.js" />
<Compile Include="app\log.js" />
<Compile Include="app\middleware\auth.js" />
<Compile Include="app\middleware\authService.js" />
<Compile Include="app\portalManager.js" />
<Compile Include="config\index.js" />
<Compile Include="bin\www" />
<Content Include="config\config.json" />
<Content Include="package.json" />
<Content Include="README.md" />
<Content Include="typings.json" />
</ItemGroup>
<ItemGroup>
<Folder Include="app\middleware\" />
<Folder Include="app\hubs\" />
<Folder Include="app\controllers\" />
<Folder Include="bin\" />
<Folder Include="app\" />
<Folder Include="config\" />
<Folder Include="public\" />
<Folder Include="public\images\" />
<Folder Include="public\javascripts\" />
<Folder Include="public\stylesheets\" />
<Folder Include="typings\" />
<Folder Include="typings\globals\" />
<Folder Include="typings\globals\body-parser\" />
<Folder Include="typings\globals\cookie-parser\" />
<Folder Include="typings\globals\debug\" />
<Folder Include="typings\globals\express-serve-static-core\" />
<Folder Include="typings\globals\express\" />
<Folder Include="typings\globals\jade\" />
<Folder Include="typings\globals\mime\" />
<Folder Include="typings\globals\moment\" />
<Folder Include="typings\globals\morgan\" />
<Folder Include="typings\globals\node\" />
<Folder Include="typings\globals\serve-favicon\" />
<Folder Include="typings\globals\serve-static\" />
<Folder Include="typings\globals\socket.io\" />
<Folder Include="typings\globals\stylus\" />
<Folder Include="views\" />
</ItemGroup>
<ItemGroup>
<TypeScriptCompile Include="typings\globals\body-parser\index.d.ts" />
<TypeScriptCompile Include="typings\globals\cookie-parser\index.d.ts" />
<TypeScriptCompile Include="typings\globals\debug\index.d.ts" />
<TypeScriptCompile Include="typings\globals\express-serve-static-core\index.d.ts" />
<TypeScriptCompile Include="typings\globals\express\index.d.ts" />
<TypeScriptCompile Include="typings\globals\jade\index.d.ts" />
<TypeScriptCompile Include="typings\globals\mime\index.d.ts" />
<TypeScriptCompile Include="typings\globals\moment\index.d.ts" />
<TypeScriptCompile Include="typings\globals\morgan\index.d.ts" />
<TypeScriptCompile Include="typings\globals\node\index.d.ts" />
<TypeScriptCompile Include="typings\globals\serve-favicon\index.d.ts" />
<TypeScriptCompile Include="typings\globals\serve-static\index.d.ts" />
<TypeScriptCompile Include="typings\globals\socket.io\index.d.ts" />
<TypeScriptCompile Include="typings\globals\stylus\index.d.ts" />
<TypeScriptCompile Include="typings\index.d.ts" />
</ItemGroup>
<!-- Do not delete the following Import Project. While this appears to do nothing it is a marker for setting TypeScript properties before our import that depends on them. -->
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TypeScript\Microsoft.TypeScript.targets" Condition="False" />
<Import Project="$(VSToolsPath)\Node.js Tools\Microsoft.NodejsTools.targets" />
<ProjectExtensions>
<VisualStudio>
<FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}">
<WebProjectProperties>
<UseIIS>False</UseIIS>
<AutoAssignPort>True</AutoAssignPort>
<DevelopmentServerPort>0</DevelopmentServerPort>
<DevelopmentServerVPath>/</DevelopmentServerVPath>
<IISUrl>http://localhost:48022/</IISUrl>
<NTLMAuthentication>False</NTLMAuthentication>
<UseCustomServer>True</UseCustomServer>
<CustomServerUrl>http://localhost:1337</CustomServerUrl>
<SaveServerSettingsInUserFile>False</SaveServerSettingsInUserFile>
</WebProjectProperties>
</FlavorProperties>
<FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}" User="">
<WebProjectProperties>
<StartPageUrl>
</StartPageUrl>
<StartAction>CurrentPage</StartAction>
<AspNetDebugging>True</AspNetDebugging>
<SilverlightDebugging>False</SilverlightDebugging>
<NativeDebugging>False</NativeDebugging>
<SQLDebugging>False</SQLDebugging>
<ExternalProgram>
</ExternalProgram>
<StartExternalURL>
</StartExternalURL>
<StartCmdLineArguments>
</StartCmdLineArguments>
<StartWorkingDirectory>
</StartWorkingDirectory>
<EnableENC>False</EnableENC>
<AlwaysStartWebServerOnDebug>False</AlwaysStartWebServerOnDebug>
</WebProjectProperties>
</FlavorProperties>
</VisualStudio>
</ProjectExtensions>
</Project>

View File

@ -0,0 +1,3 @@
# ASC.Socket.IO

View File

@ -0,0 +1,92 @@
const express = require('express');
const logger = require('morgan');
const cookieParser = require('cookie-parser');
const expressSession = require("express-session");
const sharedsession = require("express-socket.io-session");
const RedisStore = require('connect-redis')(expressSession);
const MemoryStore = require('memorystore')(expressSession);
const config = require('./config');
const winston = require('./app/log.js');
const app = express();
const secret = config.get("core.machinekey") + (new Date()).getTime();
const secretCookieParser = cookieParser(secret);
const baseCookieParser = cookieParser();
winston.stream = {
write: (message) => winston.info(message)
};
const redis = {
host: config.get("redis:host"),
port: config.get("redis:port"),
ttl: 3600
}
let store;
if(redis.host && redis.port){
store = new RedisStore(redis);
} else {
store = new MemoryStore();
}
const session = expressSession({
store: store,
secret: secret,
resave: true,
saveUninitialized: true,
cookie: {
path: '/',
httpOnly: true,
secure: false,
maxAge: null
},
cookieParser: secretCookieParser,
name: "socketio.sid"
});
app.set('port', config.get('port') || 3000);
app.use(logger("dev", { "stream": winston.stream }));
app.use(session);
app.get('/', (req, res) => { res.send('<h1>Hello world</h1>'); });
const server = app.listen(app.get('port'), () => {
//log.info('Express server listening on port ' + server.address().port);
});
const io = require('socket.io')(server, {
perMessageDeflate : false,
cookie: false,
handlePreflightRequest: function (req, res) {
session(req, res, ()=>{});
res.writeHead(200, {'Content-Type': 'text/html'});
res.end();
},
allowRequest : function(req, fn){
var cookies = baseCookieParser(req, null, ()=>{});
if(!req.cookies || (!req.cookies['asc_auth_key'] && !req.cookies['authorization'])){
return fn('auth', false);
}
return io.checkRequest(req, fn);
}
});
const auth = require('./app/middleware/auth.js');
io
.use(sharedsession(session, secretCookieParser, {autoSave: true}))
.use((socket, next) => {
baseCookieParser(socket.client.request, null, next);
})
.use((socket, next) => {
auth(socket, next);
});
const countersHub = require('./app/hubs/counters.js')(io);
const voipHub = require('./app/hubs/voip.js')(io);
const chatHub = require('./app/hubs/chat.js')(io);
const filesHub = require('./app/hubs/files.js')(io);
app.use("/controller", require('./app/controllers')(countersHub, chatHub, voipHub, filesHub));
module.exports = app;

View File

@ -0,0 +1,147 @@
const apiBasePath = "/api/2.0/",
portalManager = require('./portalManager.js'),
request = require('request'),
log = require("./log.js");
function makeRequest(apiMethod, req, options, onSuccess){
makeHeaders(req, options);
options.uri = getBasePath(req) + apiMethod;
log.info(options.uri);
return new Promise((resolve, reject) => {
request(options, (error, response, body) => {
let result = {};
try {
result = typeof body === "string" ? JSON.parse(body) : body;
} catch (err) {
log.error(options.uri, err);
}
error = checkError(error, response, result);
if(error) {
log.error(options.uri, error);
if (error == 401 && req.session) {
req.session.destroy(() => reject(error));
return
} else {
reject(error);
return;
}
}
resolve(onSuccess(result));
});
});
}
function makeHeaders(req, options) {
options.headers = {};
if (req.cookies && req.cookies['asc_auth_key']) {
options.headers["Authorization"] = req.cookies['asc_auth_key'];
}
if (req.headers) {
const xRewriterUrlHeader = 'x-rewriter-url',
xForwardedForHeader = 'x-forwarded-for';
if (req.headers[xRewriterUrlHeader]) {
options.headers[xRewriterUrlHeader] = req.headers[xRewriterUrlHeader];
}
if (req.headers[xForwardedForHeader]) {
options.headers[xForwardedForHeader] = req.headers[xForwardedForHeader];
}
}
}
function getBasePath(req) {
return portalManager(req).replace(/\/$/g, '') + apiBasePath;
}
function checkError(error, response, result) {
if (error) {
return error;
}
if (result && result.error && result.error.message) {
return result.error.message;
}
if (response.statusCode > 400) {
return response.statusCode;
}
}
class RequestManager {
constructor() { }
makeRequest(apiMethod, req, options) {
return makeRequest(apiMethod, req, options, (result) => {
return typeof result.response !== "undefined" ? result.response : result;
});
}
get(apiMethod, req, body) {
if (body) {
apiMethod = `${apiMethod}?`;
for (const item in body) {
if (body.hasOwnProperty(item)) {
apiMethod = `${apiMethod}${item}=${body[item]}&`;
}
}
apiMethod = apiMethod.substring(0, apiMethod.length - 1);
}
return this.makeRequest(apiMethod, req, { method: "GET" });
}
post(apiMethod, req, body) {
return this.makeRequest(apiMethod, req, { method: "POST", body, json: true });
}
put(apiMethod, req, body) {
return this.makeRequest(apiMethod, req, { method: "PUT", body, json: true });
}
dlt(apiMethod, req, body) {
const options = { method: "DELETE" };
if (typeof body !== "undefined") {
options.body = req.body;
options.json = true;
}
return this.makeRequest(apiMethod, req, options);
}
batch(batchMethod, req){
const options = {
method: "POST",
form:{ batch: JSON.stringify(batchMethod.methods) },
json: true
};
return makeRequest("batch.json", req, options, (result) => {
const resultResponse = result.response;
if(result.response !== "undefined" && Array.isArray(result.response)) {
let data = [];
for(let i = 0, j = resultResponse.length; i < j; i++){
const dataItem = typeof resultResponse[i].data === "string" ? JSON.parse(resultResponse[i].data) : resultResponse[i].data
data.push(dataItem.response);
}
return data;
}
return result;
});
}
batchFactory(){
return new batchFactory();
}
}
class batchFactory {
constructor() {
this.methods = [];
}
get(url) {
this.methods.push({method: "get", RelativeUrl: `${apiBasePath}${url}`});
return this;
}
}
module.exports = new RequestManager();

View File

@ -0,0 +1,23 @@
module.exports = (chat) => {
const router = require('express').Router();
router
.post("/send", (req, res) => {
chat.send(req.body);
res.end();
})
.post("/sendInvite", (req, res) => {
chat.sendInvite(req.body);
res.end();
})
.post("/setState", (req, res) => {
chat.setState(req.body);
res.end();
})
.post("/sendOfflineMessages", (req, res) => {
chat.sendOfflineMessages(req.body);
res.end();
});
return router;
}

View File

@ -0,0 +1,15 @@
module.exports = (counters) => {
const router = require('express').Router();
router
.post("/sendUnreadUsers", (req, res) => {
counters.sendUnreadUsers(req.body);
res.end();
})
.post("/sendUnreadCounts", (req, res) => {
counters.sendUnreadCounts(req.body);
res.end();
});
return router;
}

View File

@ -0,0 +1,11 @@
module.exports = (files) => {
const router = require('express').Router();
router
.post("/changeEditors", (req, res) => {
files.changeEditors(req.body);
res.end();
});
return router;
};

View File

@ -0,0 +1,26 @@
module.exports = (counters, chat, voip, files) => {
const router = require('express').Router(),
bodyParser = require('body-parser'),
authService = require('../middleware/authService.js')();
router.use(bodyParser.json());
router.use(bodyParser.urlencoded({ extended: false }));
router.use(require('cookie-parser')());
router.use((req, res, next) => {
if (!authService(req)) {
res.sendStatus(403);
return;
}
next();
});
router
.use("/counters", require(`./counters.js`)(counters))
.use("/mail", require(`./mail.js`)(counters))
.use("/chat", require(`./chat.js`)(chat))
.use("/voip", require(`./voip.js`)(voip))
.use("/files", require(`./files.js`)(files));
return router;
}

View File

@ -0,0 +1,15 @@
module.exports = (counters) => {
const router = require('express').Router();
router
.post("/updateFolders", (req, res) => {
counters.updateFolders(req.body);
res.end();
})
.post("/sendMailNotification", (req, res) => {
counters.sendMailNotification(req.body);
res.end();
});
return router;
}

View File

@ -0,0 +1,32 @@
module.exports = (voip) => {
const router = require('express').Router();
router
.post("/enqueue", (req, res) => {
const { numberId, callId, agent } = req.body;
voip.enqueue(numberId, callId, agent);
res.end();
})
.post("/incoming", (req, res) => {
const { callId, agent } = req.body;
voip.incoming(callId, agent);
res.end();
})
.post("/miss", (req, res) => {
const { numberId, callId, agent } = req.body;
voip.miss(numberId, callId, agent);
res.end();
})
.post("/getAgent", (req, res) => {
const { numberId, contactsResponsibles } = req.body;
const result = voip.getAgent(numberId, contactsResponsibles);
res.send(JSON.stringify({item1: result.result, item2: result.isAnyNotOffline}));
})
.post("/reload", (req, res) => {
const { numberRoom, agentId } = req.body;
voip.reload(numberRoom, agentId);
res.end();
});
return router;
}

View File

@ -0,0 +1,219 @@
module.exports = (io, countersHub) => {
const apiRequestManager = require('../apiRequestManager.js');
const co = require('co');
const baseTalkApiUrl = 'portal/talk/';
const chat = io.of("/chat");
const onlineUsers = [];
chat.on('connection', (socket) => {
const request = socket.client.request;
if(!request.user || !request.user.id) return;
const user = request.user;
const portal = request.portal;
const userId = user.id;
const userName = user.userName.toLowerCase();
const tenantId = portal.tenantId;
const tenantDomain = portal.tenantDomain;
socket
.on('disconnect', onDisconnectUser)
.on('connectUser', onConnectUser)
.on('disconnectUser', onDisconnectUser)
.on('send', onSend)
.on('getStates', onGetStates)
.on('getContactInfo', onGetContactInfo)
.on('getInitData', onGetInitData)
.on('sendTyping', onSendTyping)
.on('sendStateToTenant', onSendStateToTenant)
.on('getRecentMessages', onGetRecentMessages)
.on('chatPing', onPing);
function onDisconnectUser(change, fn) {
co(function *() {
let state;
if (!onlineUsers[tenantId]) return;
if (!onlineUsers[tenantId][userId] || !socket.onConnectUser) {
if(typeof fn == "function") fn();
return;
}
if (change === true || onlineUsers[tenantId][userId].counter === 1) {
delete onlineUsers[tenantId][userId];
state = yield apiRequestManager.dlt(`${baseTalkApiUrl}connection?connectionId=${userId}`, request);
socket.emit("disconnectUser");
} else {
onlineUsers[tenantId][userId].counter--;
state = yield apiRequestManager.get(`${baseTalkApiUrl}state`, request, { userName });
if (state !== 4) {
// setStatus
socket.broadcast.to(`${tenantId}-${userName}`).emit('setStatus', state);
}
}
socket.broadcast.to(tenantId).emit('setState', userName, state);
})
.catch((err)=>{
if(typeof fn == "function") fn(err);
});
}
function onConnectUser(state) {
socket.join([tenantId, `${tenantId}-${userName}`]);
socket.onConnectUser = true;
if (!onlineUsers[tenantId]) {
onlineUsers[tenantId] = {};
}
var counter = onlineUsers[tenantId][userId];
if (!counter) {
counter = onlineUsers[tenantId][userId] = { counter: 1 };
} else {
counter.counter++;
}
co(function* () {
if (counter.counter === 1) {
state = yield apiRequestManager.post(`${baseTalkApiUrl}connection`, request, { connectionId: userId, state });
} else {
state = yield apiRequestManager.post(`${baseTalkApiUrl}state`, request, { state });
if (state !== 4) {
socket.broadcast.to(`${tenantId}-${userName}`).emit('setStatus', state);
}
}
socket.broadcast.to(tenantId).emit('setState', userName, state, false);
chat.to(`${tenantId}-${userName}`).emit("connectUser");
})
.catch((err)=>{
chat.to(`${tenantId}-${userName}`).emit("connectUser", err);
});
}
function onSend(calleeUserName, messageText) {
co(function* () {
const message = { u: userName, t: messageText };
if (calleeUserName) {
chat.to(`${tenantId}-${calleeUserName}`).emit('send', message, calleeUserName);
socket.broadcast.to(`${tenantId}-${userName}`).emit('send', message, calleeUserName);
}
yield apiRequestManager.post(`${baseTalkApiUrl}message`, request, { to: calleeUserName, text: messageText });
});
}
function onGetStates(fn) {
apiRequestManager.get(`${baseTalkApiUrl}states`, request)
.then((result) => {
socket.emit('statesRetrieved', result);
})
.catch((err) => {
if (typeof fn === "function") fn(err);
});;
}
function onGetContactInfo(calleeUserName, fn) {
co(function* () {
const calleeUser = yield apiRequestManager.get('people/' + calleeUserName, request);
if (!calleeUser || calleeUser.id === "4A515A15-D4D6-4b8e-828E-E0586F18F3A3") throw "Can't get UserInfo";
const calleeUserState = yield apiRequestManager.get(`${baseTalkApiUrl}state`, request, { userName: calleeUserName });
if(typeof fn === "function") fn(calleeUserName, calleeUserState);
})
.catch((err) => {
if (err && typeof fn === "function") fn(null, null, err);
});
}
function onGetInitData(fn) {
co(function* () {
const states = yield apiRequestManager.get(`${baseTalkApiUrl}states`, request);
const users = yield apiRequestManager.get('people', request);
const result = users
.filter((item) => item.id !== userId)
.sort((item1, item2) => {
if (item1.displayName < item2.displayName) return -1;
if (item1.displayName > item2.displayName) return 1;
return 0;
})
.map((item) => {
const uName = item.userName.toLowerCase();
return { u: uName, d: item.displayName, s: states[uName] || 4}
});
socket.emit('initDataRetrieved', userName, user.displayName, result, tenantId, tenantDomain);
})
.catch((err) => {
if (typeof fn === "function") fn(err);
});
}
function onSendTyping(calleeUserName) {
chat.to(`${tenantId}-${calleeUserName}`).emit('sendTypingSignal', userName);
}
function onSendStateToTenant(state) {
apiRequestManager.post(`${baseTalkApiUrl}state`, request, { state })
.then((result) => {
socket.broadcast.to(tenantId).emit('setState', userName, result, false);
});
}
function onGetRecentMessages(calleeUserName, id, fn) {
apiRequestManager.get(`${baseTalkApiUrl}recentMessages`, request, { calleeUserName, id })
.then((recentMessages) => {
if (typeof fn === "function") fn(recentMessages);
})
.catch((err) => {
if (typeof fn === "function") fn(null, err);
});
}
function onPing(state) {
apiRequestManager.post(`${baseTalkApiUrl}ping`, request, { state: state });
}
});
function tenantPlusUserRoom(tenantId, userName) {
return chat.to(`${tenantId}-${userName}`);
}
function send({ tenantId, callerUserName, calleeUserName, message, isTenantUser } = {}) {
if (typeof tenantId === "undefined" || !calleeUserName || !message) {
return;
}
tenantPlusUserRoom(tenantId, calleeUserName).emit('send', message, calleeUserName, isTenantUser);
if (!isTenantUser) {
tenantPlusUserRoom(tenantId, callerUserName).emit('send', message, calleeUserName, isTenantUser);
}
}
function sendInvite({ tenantId, calleeUserName, message } = {}) {
if (typeof tenantId === "undefined" || !calleeUserName || !message) {
return;
}
tenantPlusUserRoom(tenantId, calleeUserName).emit('sendInvite', message);
}
function setState({ tenantId, from, state } = {}) {
if (typeof tenantId === "undefined" || !from) {
return;
}
chat.to(`${tenantId}`).emit('setState', from, state);
}
function sendOfflineMessages({ tenantId, callerUserName, users } = {}) {
if (typeof tenantId === "undefined" || !callerUserName || !users) {
return;
}
tenantPlusUserRoom(tenantId, callerUserName).emit('sendOfflineMessages', users);
}
return { send, sendInvite, setState, sendOfflineMessages };
}

View File

@ -0,0 +1,209 @@
module.exports = (io) => {
const apiRequestManager = require('../apiRequestManager.js');
const co = require('co');
const counters = io.of('/counters');
const onlineUsers = [];
const uaParser = require('ua-parser-js');
counters.on('connection', (socket) => {
const request = socket.client.request;
if(!request.user || !request.user.id) return;
const userId = request.user.id;
const tenantId = request.portal.tenantId;
let ipAddress = socket.handshake.headers['x-forwarded-for'];
const userAgent = socket.request.headers['user-agent'];
const parser = new uaParser();
parser.setUA(userAgent);
const [os, browser] = [parser.getOS(), parser.getBrowser()];
const operationSystem = os.version !== undefined ? `${os.name} ${os.version}` : `${os.name}`;
const browserVersion = browser.version ? browser.version : '';
ipAddress = getCleanIP(ipAddress);
const browserName = `${browser.name} ${browserVersion}`;
const userName = (request.user.userName || "").toLowerCase();
getCityByIP(ipAddress);
socket.join([tenantId, `${tenantId}-${userId}`, `${tenantId}-${userName}`]);
getNewMessagesCount();
socket
.on('disconnect', () => {
if (!onlineUsers[tenantId]) return;
if (!onlineUsers[tenantId][userId]) return;
if (!onlineUsers[tenantId][userId].browsers) return;
if (!onlineUsers[tenantId][userId].browsers[browserName]) return;
onlineUsers[tenantId][userId].browsers[browserName].counter--;
if (onlineUsers[tenantId][userId].browsers[browserName].counter === 0) {
delete onlineUsers[tenantId][userId].browsers[browserName];
}
if (Object.keys(onlineUsers[tenantId][userId].browsers).length === 0) {
delete onlineUsers[tenantId][userId];
counters.to(tenantId).emit('renderOfflineUser', userId);
updateMailUserActivity(socket.client.request, false);
console.log(`a user ${userName} in portal ${tenantId} disconnected`);
}
})
.on('renderOnlineUsers', () => {
counters.to(tenantId).emit('renderOnlineUsers', onlineUsers[tenantId] || []);
})
.on('sendMessagesCount', (count) => {
socket.broadcast.to(`${tenantId}-${userId}`).emit('sendMessagesCount', count);
})
.on('sendFeedsCount', () => {
socket.broadcast.to(`${tenantId}-${userId}`).emit('sendFeedsCount', 0);
})
.on('updateFolders', (shouldUpdateMailBox) => {
counters.in(`${tenantId}-${userId}`).clients((error, clients) => {
if (error) return;
if (clients.length > 1) {
getMessageCount().then((count) => {
socket.broadcast.to(`${tenantId}-${userId}`).emit('updateFolders', count, shouldUpdateMailBox);
});
}
});
});
function updateMailUserActivity(request, userOnline = true) {
if(!request.mailEnabled) return;
setTimeout(function(){
if((!userOnline && typeof onlineUsers[tenantId][userId] != "undefined") ||
(userOnline && !onlineUsers[tenantId][userId])) return;
apiRequestManager.put("mail/accounts/updateuseractivity.json", request, { userOnline });
console.log(`updateuseractivity ${userOnline}`);
}, 3000);
}
function getNewMessagesCount() {
co(function* () {
let mailMessageFolders = [], feedCount = 0, messageCount = 0, mailCount = 0;
var batchRequest = apiRequestManager.batchFactory()
.get("feed/newfeedscount.json")
.get("portal/talk/unreadmessages.json");
if(!request.mailEnabled){
[feedCount, messageCount] = yield apiRequestManager.batch(batchRequest,request);
}else{
[feedCount, messageCount, mailMessageFolders] = yield apiRequestManager.batch(batchRequest.get("mail/folders.json"), request);
}
mailCount = getInreadMessageCount(mailMessageFolders);
counters.to(`${tenantId}-${userId}`).emit('getNewMessagesCount',
{
me: messageCount,
f: feedCount,
ma: mailCount
});
})
.catch((err)=>{
});
}
function getMessageCount() {
if(!request.mailEnabled) return new Promise((resolve) => { resolve(0)});
return apiRequestManager.get("mail/folders.json", request).then(getInreadMessageCount);
}
function getCityByIP (ip){
apiRequestManager.get("portal/ip/" + ip, request, false)
.then((result) => {
const city = result.city;
console.log(`a user ${userName} in portal ${tenantId} connected, IP ${ipAddress}, city ${city}, OS ${operationSystem}, browser ${browserName}`);
console.log("-----");
if (!onlineUsers[tenantId]) {
onlineUsers[tenantId] = {};
}
if (!onlineUsers[tenantId][userId]) {
onlineUsers[tenantId][userId] = {browsers: {},FirstConnection: new Date(), LastConnection: new Date() };
socket.broadcast.to(tenantId).emit('renderOnlineUser', userId);
updateMailUserActivity(socket.client.request);
}
else {
onlineUsers[tenantId][userId].LastConnection = new Date();
}
if (!onlineUsers[tenantId][userId].browsers[browserName]) {
onlineUsers[tenantId][userId].browsers[browserName] = {counter:1, ipAddress: ipAddress, city: city, operationSystem: operationSystem };
} else {
onlineUsers[tenantId][userId].browsers[browserName].counter++;
}
})
.catch((err) => {
console.log(err);
});
};
function getCleanIP (ipAddress) {
const indexOfColon = ipAddress.indexOf(':');
if (indexOfColon === -1){
return ipAddress;
} else if (indexOfColon > 3){
return ipAddress.substring(0, indexOfColon);
}
else {
return "127.0.0.1";
}
}
});
function getInreadMessageCount(mailMessageFolders){
let mailMessageFoldersCount = mailMessageFolders.length;
while (mailMessageFoldersCount--) {
const mailMessageFolder = mailMessageFolders[mailMessageFoldersCount];
if (mailMessageFolder && mailMessageFolder.id === 1) {
return mailMessageFolder.unread_messages;
}
}
return 0;
}
function sendUnreadUsers(unreadUsers) {
if (!unreadUsers) {
return;
}
for (let tenant in unreadUsers) {
if (!unreadUsers.hasOwnProperty(tenant)) continue;
for (let user in unreadUsers[tenant]) {
if (!unreadUsers[tenant].hasOwnProperty(user)) continue;
counters.to(`${tenant}-${user}`).emit('sendFeedsCount', unreadUsers[tenant][user]);
}
}
}
function sendUnreadCounts({ tenantId, unreadCounts } = {}) {
if (typeof tenantId === "undefined") {
return;
}
for (let user in unreadCounts) {
if (unreadCounts.hasOwnProperty(user)) {
counters.to(`${tenantId}-${user}`).emit('sendMessagesCount', unreadCounts[user]);
}
}
}
function updateFolders({ tenant, userId, count } = {}) {
if (typeof tenant === "undefined" || !userId || !count) {
return;
}
counters.to(`${tenant}-${userId}`).emit('updateFolders', count);
}
function sendMailNotification({ tenant, userId, state } = {}) {
if (typeof tenant === "undefined" || !userId || typeof state === "undefined") {
return;
}
counters.to(`${tenant}-${userId}`).emit('sendMailNotification', state);
}
return { sendUnreadUsers, sendUnreadCounts, updateFolders, sendMailNotification };
}

View File

@ -0,0 +1,51 @@
module.exports = (io) => {
const log = require("../log.js");
const files = io.of("/files");
files.on("connection", (socket) => {
const request = socket.client.request;
if (!request.user || !request.user.id) {
return;
}
const tenantId = request.portal.tenantId;
socket
.on("subscribeChangeEditors", (fileIds) => {
if (typeof fileIds != "object") {
fileIds = [fileIds];
}
fileIds.forEach(function(fileId) {
let room = `${tenantId}-${fileId}`;
socket.join(room);
});
});
});
function changeEditors({ tenantId, fileId, finish } = {}) {
if (typeof tenantId === "undefined" || typeof fileId === "undefined") {
log.error(`files: changeEditors without arguments`);
return;
}
let room = `${tenantId}-${fileId}`;
files.to(room).emit("changeEditors", fileId);
if (finish) {
files.in(room).clients((error, clients) => {
if (error) throw error;
clients.forEach(function(client) {
let clientSocket = files.connected[client];
if(clientSocket){
clientSocket.leave(room);
}
});
});
}
}
return { changeEditors };
};

View File

@ -0,0 +1,119 @@
module.exports = (io) => {
const phones = new (require("./voipListPhones.js"))();
const numberIdKey = "numberId";
const voip = io.of("/voip");
voip.on('connection', (socket) => {
const request = socket.client.request;
if(!request.user || !request.user.id) return;
const user = request.user;
const userId = user.id;
const numberId = getNumberId(request);
const numberRoom = request.portal.tenantId + numberId;
socket.join([numberRoom, userId]);
phones.addPhone(numberId);
socket
.on("status", status)
.on("miss", miss.bind(null, numberId))
.on("enqueue", enqueue.bind(null, numberId))
.on("Dequeue", dequeue)
.on("OnlineAgents", onlineAgents)
.on("getStatus", getStatus)
.on("getAgent", getAgent.bind(null, numberId))
.on("incoming", incoming)
.on("start", start)
.on("end", end);
function getNumberId(request) {
let result = request._query[numberIdKey];
if (!result) {
result = request.headers[numberIdKey];
}
return result || "";
}
function status(agentStatus, fn) {
voip.to(userId).emit("status", agentStatus);
switch (agentStatus) {
case 0:
socket.once('disconnect', () => {
status(2);
});
if (phones.anyCalls(numberId)) {
dequeue();
} else {
phones.addOrUpdateAgent(numberId, { id: userId, status: agentStatus });
}
break;
case 1:
phones.addOrUpdateAgent(numberId, { id: userId, status: agentStatus });
break;
case 2:
phones.removeAgent(numberId, userId);
break;
}
onlineAgents();
if(fn) fn();
}
function dequeue() {
voip.to(userId).emit('dequeue', phones.dequeueCall(numberId));
}
function onlineAgents() {
voip.to(numberRoom).emit('onlineAgents', phones.onlineAgents(numberId));
}
function getStatus(fn) {
const getStatusResult = phones.getStatus(numberId, userId);
fn(getStatusResult);
}
function start() {
voip.to(userId).emit('start');
}
function end() {
voip.to(userId).emit('end');
}
});
function enqueue(numberId, callId, agent) {
const result = phones.enqueue(numberId, callId, agent);
if (result) {
voip.to(result).emit('dequeue', callId);
}
}
function incoming(callId, agent) {
voip.to(agent).emit('dequeue', callId);
}
function miss(numberId, callId, agent) {
phones.removeCall(numberId, callId);
voip.to(agent).emit('miss', callId);
}
function getAgent(numberId, contactsResponsibles, fn) {
const result = phones.getAgent(numberId, contactsResponsibles);
if (typeof fn === "function") fn(result);
return result;
}
function reload(numberRoom, agent) {
if (agent) {
voip.to(agent).emit('reload');
}else{
voip.to(numberRoom).emit('reload');
}
}
return { enqueue, incoming, miss, getAgent, reload };
}

View File

@ -0,0 +1,74 @@
const VoipPhone = require('./voipPhone.js');
class ListVoipPhone {
constructor() {
this.phones = [];
}
addPhone(numberId) {
const existingPhone = this.phones.find((item) => item.numberId === numberId);
if (!existingPhone) {
this.phones.push(new VoipPhone(numberId));
}
}
getPhone(numberId) {
return this.phones.find((item) => item.numberId === numberId);
}
addOrUpdateAgent(numberId, agent) {
const phone = this.getPhone(numberId);
if (!phone) return;
phone.addOrUpdateAgent(agent);
}
removeAgent(numberId, agentId) {
const phone = this.getPhone(numberId);
if (!phone) return;
phone.removeAgent(agentId);
}
anyCalls(numberId) {
const phone = this.getPhone(numberId);
if (!phone) return false;
return phone.anyCalls();
}
dequeueCall(numberId) {
const phone = this.getPhone(numberId);
if (!phone) return "";
return phone.dequeueCall();
}
removeCall(numberId, callId) {
const phone = this.getPhone(numberId);
if (!phone) return;
phone.removeCall(callId);
}
enqueue(numberId, callId, agent) {
const phone = this.getPhone(numberId);
if (!phone) return "";
return phone.enqueue(callId, agent);
}
onlineAgents(numberId) {
const phone = this.getPhone(numberId);
if (!phone) return [];
return phone.onlineAgents();
}
getStatus(numberId, agentId) {
const phone = this.getPhone(numberId);
if (!phone) return 2;
return phone.getAgentStatus(agentId);
}
getAgent(numberId, contactResponsibles) {
const phone = this.getPhone(numberId);
if (!phone) return null;
return phone.getAgent(contactResponsibles);
}
}
module.exports = ListVoipPhone;

View File

@ -0,0 +1,87 @@
function findAgent(agentId, item) {
return item.id === agentId;
}
class VoipPhone {
constructor(numberId) {
this.agents = [];
this.calls = [];
this.numberId = numberId;
}
addOrUpdateAgent(agent) {
const existingAgent = this.agents.find(findAgent.bind(null, agent.Id));
if (existingAgent) {
existingAgent.status = agent.status;
} else {
this.agents.push(agent);
}
}
removeAgent(agentId) {
const existingAgent = this.agents.some(findAgent.bind(null, agentId));
if (!existingAgent) return false;
this.agents = this.agents.filter((item) => item.id !== agentId);
return true;
}
addCall(callId) {
if (this.calls.some((item) => item === callId)) return;
this.calls.push(callId);
}
removeCall(callId) {
if (!callId || !this.calls.some((item) => item === callId)) return;
this.calls = this.calls.filter((item) => item !== callId);
}
anyCalls() {
return !!this.calls.length;
}
dequeueCall() {
return this.calls.pop();
}
enqueue(callId, agent) {
if (agent && this.removeAgent(agent)) {
return agent;
}
const agents = this.agents.filter((item) =>item.status === 0 );
if (agents.length) {
return agents[0].id;
}
this.addCall(callId);
return "";
}
onlineAgents() {
return this.agents.filter((item) => item.status === 0).map((item) => item.id);
}
getAgentStatus(agentId) {
const agent = this.agents.find(findAgent.bind(null, agentId));
return agent ? agent.status : 2;
}
getAgent(contactResponsibles) {
const isAnyNotOffline = !!this.agents.length;
let result = this.agents.find((item) => {
return item.status === 0 && contactResponsibles.includes(item.id);
});
if (!result) {
result = this.agents.find((item) => item.status === 0);
}
if (result) {
result.status = 1;
}
return { result, isAnyNotOffline };
}
}
module.exports = VoipPhone;

View File

@ -0,0 +1,32 @@
const winston = require('winston');
require('winston-daily-rotate-file')
const path = require('path');
const config = require('../config');
const fs = require('fs');
const fileName = config.get("logPath") || path.join(__dirname, "..", "..", "Logs", "web.socketio.%DATE%.log");
const dirName = path.dirname(fileName);
if (!fs.existsSync(dirName)) {
fs.mkdirSync(dirName);
}
const fileTransport = new (winston.transports.DailyRotateFile)(
{
filename: fileName,
datePattern: 'MM-DD',
handleExceptions: true,
humanReadableUnhandledException: true,
zippedArchive: true,
maxSize: '50m',
maxFiles: '30d'
});
const transports = [
new (winston.transports.Console)(),
fileTransport
];
winston.handleExceptions(fileTransport);
module.exports = new winston.Logger({ transports: transports, exitOnError: false});

View File

@ -0,0 +1,49 @@
module.exports = function (socket, next) {
const apiRequestManager = require('../apiRequestManager.js');
const req = socket.client.request;
const authService = require('./authService.js')();
const co = require('co');
const session = socket.handshake.session;
if (req.user) {
next();
return;
}
if (!req.cookies || (!req.cookies['asc_auth_key'] && !req.cookies['authorization'])) {
socket.disconnect('unauthorized');
next(new Error('Authentication error'));
return;
}
if(session && session.user && session.portal && typeof(session.mailEnabled) !== "undefined") {
req.user = session.user;
req.portal = session.portal;
req.mailEnabled = session.mailEnabled;
next();
return;
}
if(req.cookies['authorization']){
if(!authService(req)){
next(new Error('Authentication error'));
} else{
next();
}
return;
}
co(function*(){
var batchRequest = apiRequestManager.batchFactory()
.get("people/@self.json?fields=id,userName,displayName")
.get("portal.json?fields=tenantId,tenantDomain")
.get("settings/security/2A923037-8B2D-487b-9A22-5AC0918ACF3F");
[session.user, session.portal, session.mailEnabled] = [req.user, req.portal, req.mailEnabled] = yield apiRequestManager.batch(batchRequest, req);
session.save();
next();
}).catch((err) => {
socket.disconnect('unauthorized');
next(new Error('Authentication error'));
});
}

View File

@ -0,0 +1,37 @@
module.exports = () => {
const
config = require('../../config'),
crypto = require('crypto'),
moment = require('moment');
const skey = config.get("core.machinekey");
const trustInterval = 5 * 60 * 1000;
function check(req) {
const authHeader = req.headers["authorization"] || req.cookies["authorization"];
if(!authHeader) return false;
const splitted = authHeader.split(':');
if (splitted.length < 3) return false;
const pkey = splitted[0].substr(4);
const date = splitted[1];
const orighash = splitted[2];
const timestamp = moment.utc(date, "YYYYMMDDHHmmss");
if (moment.utc() - timestamp > trustInterval) {
return false;
}
const hasher = crypto.createHmac('sha1', skey);
const hash = hasher.update(date + "\n" + pkey);
if (hash.digest('base64') !== orighash) {
return false;
}
return true;
}
return check;
};

View File

@ -0,0 +1,16 @@
const portalInternalUrl = require('../config').get("portal.internal.url")
module.exports = (req) => {
if(portalInternalUrl) return portalInternalUrl;
const xRewriterUrlInternalHeader = 'x-rewriter-url-internal';
if (req.headers && req.headers[xRewriterUrlInternalHeader]) {
return req.headers[xRewriterUrlInternalHeader];
}
const xRewriterUrlHeader = 'x-rewriter-url';
if (req.headers && req.headers[xRewriterUrlHeader]) {
return req.headers[xRewriterUrlHeader];
}
return "";
};

View File

@ -0,0 +1,12 @@
{
"port": 9899,
"core.machinekey": "1123askdasjklasbnd",
"portal.internal.url": "",
"redis":{
"host": "localhost",
"port": 6379,
"db": 0,
"pass":"",
"ttl":84600
}
}

View File

@ -0,0 +1,8 @@
const nconf = require('nconf');
const path = require('path');
nconf.argv()
.env()
.file({ file: path.join(__dirname, 'config.json') });
module.exports = nconf;

View File

@ -0,0 +1,32 @@
{
"name": "asc.socket.io",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "node ./bin/www"
},
"description": "ASC.Socket.IO",
"author": {
"name": "Pavel"
},
"dependencies": {
"connect-redis": "~3.4.0",
"body-parser": "~1.18.2",
"co": "^4.6.0",
"cookie-parser": "~1.4.3",
"debug": "~3.1.0",
"express": "~4.16.4",
"express-session": "~1.15.6",
"express-socket.io-session": "~1.3.5",
"memorystore": "^1.6.0",
"moment": "^2.24.0",
"morgan": "~1.9.1",
"nconf": "^0.10.0",
"redis": "^2.8.0",
"request": "^2.88.0",
"socket.io": "^2.2.0",
"ua-parser-js": "^0.7.19",
"winston": "^2.4.1",
"winston-daily-rotate-file": "^3.2.0"
}
}

View File

@ -0,0 +1,15 @@
{
"globalDependencies": {
"body-parser": "registry:dt/body-parser#0.0.0+20160317120654",
"cookie-parser": "registry:dt/cookie-parser#1.3.4+20160316155526",
"debug": "registry:dt/debug#0.0.0+20160317120654",
"express": "registry:dt/express#4.0.0+20160317120654",
"express-serve-static-core": "registry:dt/express-serve-static-core#0.0.0+20160602151406",
"jade": "registry:dt/jade#0.0.0+20160316155526",
"mime": "registry:dt/mime#0.0.0+20160316155526",
"morgan": "registry:dt/morgan#1.7.0+20160524142355",
"serve-favicon": "registry:dt/serve-favicon#0.0.0+20160316155526",
"serve-static": "registry:dt/serve-static#0.0.0+20160606155157",
"stylus": "registry:dt/stylus#0.0.0+20160317120654"
}
}

View File

@ -35,6 +35,7 @@ using ASC.Common.Caching;
using ASC.Common.Logging;
using ASC.Core;
using ASC.Core.Common;
using ASC.Core.Notify.Signalr;
using ASC.Feed.Aggregator.Modules;
using ASC.Feed.Configuration;
using ASC.Feed.Data;
@ -51,7 +52,7 @@ namespace ASC.Feed.Aggregator
public class FeedAggregatorService : IHostedService
{
private ILog Log { get; set; }
//private static readonly SignalrServiceClient signalrServiceClient = new SignalrServiceClient("counters");//TODO
private SignalrServiceClient SignalrServiceClient { get; }
private Timer aggregateTimer;
private Timer removeTimer;
@ -68,12 +69,16 @@ namespace ASC.Feed.Aggregator
IConfiguration configuration,
IServiceProvider serviceProvider,
IContainer container,
IOptionsMonitor<ILog> optionsMonitor)
IOptionsMonitor<ILog> optionsMonitor,
SignalrServiceClient signalrServiceClient,
IConfigureNamedOptions<SignalrServiceClient> configureOptions)
{
Configuration = configuration;
ServiceProvider = serviceProvider;
Container = container;
Log = optionsMonitor.Get("ASC.Feed.Agregator");
SignalrServiceClient = signalrServiceClient;
configureOptions.Configure("counters", SignalrServiceClient);
}
public Task StartAsync(CancellationToken cancellationToken)
@ -220,7 +225,7 @@ namespace ASC.Feed.Aggregator
}
}
//signalrServiceClient.SendUnreadUsers(unreadUsers);
SignalrServiceClient.SendUnreadUsers(unreadUsers);
Log.DebugFormat("Time of collecting news: {0}", DateTime.UtcNow - start);
}
@ -300,7 +305,8 @@ namespace ASC.Feed.Aggregator
.AddUserManagerService()
.AddSecurityContextService()
.AddAuthManager()
.AddFeedAggregateDataProvider();
.AddFeedAggregateDataProvider()
.AddSignalrServiceClient();
}
}
}

View File

@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<ApplicationIcon />
<OutputType>Exe</OutputType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DebugType>none</DebugType>
<DebugSymbols>false</DebugSymbols>
</PropertyGroup>
<ItemGroup>
<Folder Include="Properties\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="WebSocketSharp" Version="1.0.3-rc11" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\ASC.Common\ASC.Common.csproj" />
<ProjectReference Include="..\..\ASC.Core.Common\ASC.Core.Common.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,95 @@
/*
*
* (c) Copyright Ascensio System Limited 2010-2020
*
* This program is freeware. You can redistribute it and/or modify it under the terms of the GNU
* General Public License (GPL) version 3 as published by the Free Software Foundation (https://www.gnu.org/copyleft/gpl.html).
* In accordance with Section 7(a) of the GNU GPL its Section 15 shall be amended to the effect that
* Ascensio System SIA expressly excludes the warranty of non-infringement of any third-party rights.
*
* THIS PROGRAM IS DISTRIBUTED WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR
* FITNESS FOR A PARTICULAR PURPOSE. For more details, see GNU GPL at https://www.gnu.org/copyleft/gpl.html
*
* You can contact Ascensio System SIA by email at sales@onlyoffice.com
*
* The interactive user interfaces in modified source and object code versions of ONLYOFFICE must display
* Appropriate Legal Notices, as required under Section 5 of the GNU GPL version 3.
*
* Pursuant to Section 7 § 3(b) of the GNU GPL you must retain the original ONLYOFFICE logo which contains
* relevant author attributions when distributing the software. If the display of the logo in its graphic
* form is not reasonably feasible for technical reasons, you must include the words "Powered by ONLYOFFICE"
* in every copy of the program you distribute.
* Pursuant to Section 7 § 3(e) we decline to grant you any rights under trademark law for use of our trademarks.
*
*/
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using ASC.Common;
using ASC.Common.DependencyInjection;
using ASC.Common.Logging;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace ASC.Socket.IO.Svc
{
public class Program
{
public static async Task Main(string[] args)
{
var host = Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((hostContext, config) =>
{
var buided = config.Build();
var path = buided["pathToConf"];
if (!Path.IsPathRooted(path))
{
path = Path.GetFullPath(Path.Combine(hostContext.HostingEnvironment.ContentRootPath, path));
}
config.SetBasePath(path);
var env = hostContext.Configuration.GetValue("ENVIRONMENT", "Production");
config
.AddInMemoryCollection(new Dictionary<string, string>
{
{"pathToConf", path }
}
)
.AddJsonFile("appsettings.json")
.AddJsonFile("storage.json")
.AddJsonFile("kafka.json")
.AddJsonFile("socket.json")
.AddJsonFile($"kafka.{env}.json", true)
.AddJsonFile($"appsettings.{env}.json", true)
.AddJsonFile($"socket.{env}.json", true)
.AddEnvironmentVariables()
.AddCommandLine(args);
})
.ConfigureServices((hostContext, services) =>
{
var diHelper = new DIHelper(services);
diHelper.AddNLogManager("ASC.Socket.IO.Svc");
services.AddHostedService<SocketServiceLauncher>();
diHelper.AddSocketServiceLauncher();
services.AddAutofac(hostContext.Configuration, hostContext.HostingEnvironment.ContentRootPath, false, false);
})
.UseConsoleLifetime()
.Build();
using (host)
{
// Start the host
await host.StartAsync();
// Wait for the host to shutdown
await host.WaitForShutdownAsync();
}
}
}
}

View File

@ -0,0 +1,35 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:5017/",
"sslPort": 0
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": false,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"$STORAGE_ROOT": "../../../Data",
"log__name": "socket",
"log__dir": "../../../Logs",
"core__products__folder": "../../../products"
}
},
"ASC.Socket.IO.Svc": {
"commandName": "Project",
"launchBrowser": false,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"$STORAGE_ROOT": "../../../Data",
"log__name": "socket",
"log__dir": "../../../Logs",
"core__products__folder": "../../../products"
},
"applicationUrl": "https://localhost:5001;http://localhost:5000"
}
}
}

View File

@ -0,0 +1,253 @@
/*
*
* (c) Copyright Ascensio System Limited 2010-2018
*
* This program is freeware. You can redistribute it and/or modify it under the terms of the GNU
* General Public License (GPL) version 3 as published by the Free Software Foundation (https://www.gnu.org/copyleft/gpl.html).
* In accordance with Section 7(a) of the GNU GPL its Section 15 shall be amended to the effect that
* Ascensio System SIA expressly excludes the warranty of non-infringement of any third-party rights.
*
* THIS PROGRAM IS DISTRIBUTED WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR
* FITNESS FOR A PARTICULAR PURPOSE. For more details, see GNU GPL at https://www.gnu.org/copyleft/gpl.html
*
* You can contact Ascensio System SIA by email at sales@onlyoffice.com
*
* The interactive user interfaces in modified source and object code versions of ONLYOFFICE must display
* Appropriate Legal Notices, as required under Section 5 of the GNU GPL version 3.
*
* Pursuant to Section 7 § 3(b) of the GNU GPL you must retain the original ONLYOFFICE logo which contains
* relevant author attributions when distributing the software. If the display of the logo in its graphic
* form is not reasonably feasible for technical reasons, you must include the words "Powered by ONLYOFFICE"
* in every copy of the program you distribute.
* Pursuant to Section 7 § 3(e) we decline to grant you any rights under trademark law for use of our trademarks.
*
*/
using System;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using ASC.Common;
using ASC.Common.Utils;
using ASC.Common.Logging;
using ASC.Core;
using ASC.Core.Notify.Signalr;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using WebSocketSharp;
using Microsoft.Extensions.Logging;
namespace ASC.Socket.IO.Svc
{
public class SocketServiceLauncher : IHostedService
{
private const int PingInterval = 10000;
private Process Proc { get; set; }
private ProcessStartInfo StartInfo { get; set; }
private WebSocket WebSocket { get; set; }
private CancellationTokenSource CancellationTokenSource { get; set; }
private ILog Logger { get; set; }
private string LogDir { get; set; }
private IConfiguration Configuration { get; set; }
private CoreBaseSettings CoreBaseSettings { get; set; }
private SignalrServiceClient SignalrServiceClient { get; set; }
private IHostEnvironment HostEnvironment { get; set; }
public SocketServiceLauncher(IOptionsMonitor<ILog> options, IConfiguration configuration, CoreBaseSettings coreBaseSettings, SignalrServiceClient signalrServiceClient, IHostEnvironment hostEnvironment, IConfigureNamedOptions<SignalrServiceClient> configureOptions)
{
Logger = options.CurrentValue;
CancellationTokenSource = new CancellationTokenSource();
Configuration = configuration;
CoreBaseSettings = coreBaseSettings;
SignalrServiceClient = signalrServiceClient;
HostEnvironment = hostEnvironment;
configureOptions.Configure(SignalrServiceClient);
}
public Task StartAsync(CancellationToken cancellationToken)
{
try
{
var settings = Configuration.GetSetting<SocketSettings>("socket");
StartInfo = new ProcessStartInfo
{
CreateNoWindow = false,
UseShellExecute = false,
FileName = "node",
WindowStyle = ProcessWindowStyle.Hidden,
Arguments = string.Format("\"{0}\"", Path.GetFullPath(Path.Combine(HostEnvironment.ContentRootPath, settings.Path, "app.js"))),
WorkingDirectory = AppDomain.CurrentDomain.BaseDirectory
};
StartInfo.EnvironmentVariables.Add("core.machinekey", Configuration["core:machinekey"]);
StartInfo.EnvironmentVariables.Add("port", settings.Port);
if (!string.IsNullOrEmpty(settings.RedisHost) && !string.IsNullOrEmpty(settings.RedisPort))
{
StartInfo.EnvironmentVariables.Add("redis:host", settings.RedisHost);
StartInfo.EnvironmentVariables.Add("redis:port", settings.RedisPort);
}
if (CoreBaseSettings.Standalone)
{
StartInfo.EnvironmentVariables.Add("portal.internal.url", "http://localhost");
}
LogDir = Logger.LogDirectory;
StartInfo.EnvironmentVariables.Add("logPath", Path.Combine(LogDir, "web.socketio.log"));
StartNode();
}
catch (Exception e)
{
Logger.Error(e);
}
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
StopPing();
StopNode();
return Task.CompletedTask;
}
private void StartNode()
{
StopNode();
Proc = Process.Start(StartInfo);
var task = new Task(StartPing, CancellationTokenSource.Token, TaskCreationOptions.LongRunning);
task.Start(TaskScheduler.Default);
}
private void StopNode()
{
try
{
if (Proc != null && !Proc.HasExited)
{
Proc.Kill();
if (!Proc.WaitForExit(10000)) /* wait 10 seconds */
{
Logger.Warn("The process does not wait for completion.");
}
Proc.Close();
Proc.Dispose();
Proc = null;
}
}
catch (Exception e)
{
Logger.Error("SocketIO failed stop", e);
}
}
private void StartPing()
{
Thread.Sleep(PingInterval);
var error = false;
WebSocket = new WebSocket(string.Format("ws://127.0.0.1:{0}/socket.io/?EIO=3&transport=websocket", StartInfo.EnvironmentVariables["port"]));
WebSocket.SetCookie(new WebSocketSharp.Net.Cookie("authorization", SignalrServiceClient.CreateAuthToken()));
WebSocket.EmitOnPing = true;
WebSocket.Log.Level = WebSocketSharp.LogLevel.Trace;
WebSocket.Log.Output = (logData, filePath) =>
{
if (logData.Message.Contains("SocketException"))
{
error = true;
}
Logger.Debug(logData.Message);
};
WebSocket.OnOpen += (sender, e) =>
{
Logger.Info("Open");
error = false;
Thread.Sleep(PingInterval);
Task.Run(() =>
{
while (WebSocket.Ping())
{
Logger.Debug("Ping " + WebSocket.ReadyState);
Thread.Sleep(PingInterval);
}
Logger.Debug("Reconnect" + WebSocket.ReadyState);
}, CancellationTokenSource.Token);
};
WebSocket.OnClose += (sender, e) =>
{
Logger.Info("Close");
if (CancellationTokenSource.IsCancellationRequested) return;
if (error)
{
Process.GetCurrentProcess().Kill();
}
else
{
WebSocket.Connect();
}
};
WebSocket.OnMessage += (sender, e) =>
{
if (e.Data.Contains("error"))
{
Logger.Error("Auth error");
CancellationTokenSource.Cancel();
}
};
WebSocket.OnError += (sender, e) =>
{
Logger.Error("Error", e.Exception);
};
WebSocket.Connect();
}
private void StopPing()
{
try
{
CancellationTokenSource.Cancel();
if (WebSocket.IsAlive)
{
WebSocket.Close();
WebSocket = null;
}
}
catch (Exception)
{
Logger.Error("Ping failed stop");
}
}
}
public static class SocketServiceLauncherExtension
{
public static DIHelper AddSocketServiceLauncher(this DIHelper services)
{
services.TryAddScoped<SocketServiceLauncher>();
return services
.AddCoreBaseSettingsService()
.AddSignalrServiceClient();
}
}
}

View File

@ -0,0 +1,10 @@
namespace ASC.Socket.IO.Svc
{
public class SocketSettings
{
public string Path { get; set; }
public string Port { get; set; }
public string RedisHost { get; set; }
public string RedisPort { get; set; }
}
}

View File

@ -0,0 +1,3 @@
{
"pathToConf": "..\\..\\..\\config"
}

View File

@ -14,7 +14,7 @@
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"$STORAGE_ROOT": "../../../Data",
"log__name": "urlshortener",
"log__name": "thumbnails",
"log__dir": "../../../Logs",
"core__products__folder": "../../../products"
}
@ -25,7 +25,7 @@
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"$STORAGE_ROOT": "../../../Data",
"log__name": "urlshortener",
"log__name": "thumbnails",
"log__dir": "../../../Logs",
"core__products__folder": "../../../products"
},

View File

@ -90,8 +90,8 @@
"images": "images",
"hide-settings": "Monitoring,LdapSettings,DocService,MailService,PublicPortal,ProxyHttpContent,SpamSubscription,FullTextSearch",
"hub": {
"url": "",
"internal": ""
"url": "/socketio/socket.io/",
"internal": "http://localhost:9899/"
},
"cultures": "en-US,ru-RU",
"url-shortener": {

6
config/socket.json Normal file
View File

@ -0,0 +1,6 @@
{
"socket": {
"path": "../../ASC.Socket.IO",
"port": "9899",
}
}

View File

@ -9,6 +9,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Graph.Core", "thi
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.OneDrive.Sdk", "thirdparty\onedrive-sdk-csharp-master\src\OneDriveSdk\Microsoft.OneDrive.Sdk.csproj", "{915C6FCA-E465-49E3-9A47-2700478DB8AB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "websocket-sharp", "websocket-sharp-master\websocket-sharp\websocket-sharp.csproj", "{B357BAC7-529E-4D81-A0D2-71041B19C8DE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -27,6 +29,10 @@ Global
{915C6FCA-E465-49E3-9A47-2700478DB8AB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{915C6FCA-E465-49E3-9A47-2700478DB8AB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{915C6FCA-E465-49E3-9A47-2700478DB8AB}.Release|Any CPU.Build.0 = Release|Any CPU
{B357BAC7-529E-4D81-A0D2-71041B19C8DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B357BAC7-529E-4D81-A0D2-71041B19C8DE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B357BAC7-529E-4D81-A0D2-71041B19C8DE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B357BAC7-529E-4D81-A0D2-71041B19C8DE}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -12,6 +12,9 @@
<WarningsAsErrors></WarningsAsErrors>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Version>1.1.0.451</Version>
<PackageLicenseExpression></PackageLicenseExpression>
<AssemblyVersion>1.1.0.450</AssemblyVersion>
<FileVersion>1.1.0.450</FileVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugType>full</DebugType>

View File

@ -0,0 +1,14 @@
## Ignore build results and temporary files.
Backup*
_UpgradeReport_Files
bin
obj
*.mdb
*.pdb
*.pidb
*.suo
*.user
*.userprefs
UpgradeLog*.*

View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2010-2020 sta.blockhead
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -0,0 +1,709 @@
![Logo](websocket-sharp_logo.png)
## Welcome to websocket-sharp! ##
websocket-sharp supports:
- [RFC 6455](#supported-websocket-specifications)
- [WebSocket Client](#websocket-client) and [Server](#websocket-server)
- [Per-message Compression](#per-message-compression) extension
- [Secure Connection](#secure-connection)
- [HTTP Authentication](#http-authentication)
- [Query string, Origin header, and Cookies](#query-string-origin-header-and-cookies)
- [Connecting through the HTTP proxy server](#connecting-through-the-http-proxy-server)
- .NET Framework **3.5** or later (includes compatible environment such as [Mono])
## Branches ##
- [master] for production releases.
- [hybi-00] for older [draft-ietf-hybi-thewebsocketprotocol-00]. No longer maintained.
- [draft75] for even more old [draft-hixie-thewebsocketprotocol-75]. No longer maintained.
## Build ##
websocket-sharp is built as a single assembly, **websocket-sharp.dll**.
websocket-sharp is developed with [MonoDevelop]. So a simple way to build is to open **websocket-sharp.sln** and run build for **websocket-sharp project** with any of the build configurations (e.g. `Debug`) in MonoDevelop.
## Install ##
### Self Build ###
You should add your websocket-sharp.dll (e.g. `/path/to/websocket-sharp/bin/Debug/websocket-sharp.dll`) to the library references of your project.
If you would like to use that dll in your [Unity] project, you should add it to any folder of your project (e.g. `Assets/Plugins`) in the **Unity Editor**.
### NuGet Gallery ###
websocket-sharp is available on the [NuGet Gallery], as still a **prerelease** version.
- [NuGet Gallery: websocket-sharp]
You can add websocket-sharp to your project with the NuGet Package Manager, by using the following command in the Package Manager Console.
PM> Install-Package WebSocketSharp -Pre
### Unity Asset Store ###
websocket-sharp is available on the Unity Asset Store (Sorry, Not available now).
- [WebSocket-Sharp for Unity]
It works with **Unity Free**, but there are some limitations:
- [Security Sandbox of the Webplayer] (The server is not available in Web Player)
- [WebGL Networking] (Not available in WebGL)
- Incompatible platform (Not available for such UWP)
- Lack of dll for the System.IO.Compression (The compression extension is not available on Windows)
- .NET Socket Support for iOS/Android (iOS/Android Pro is required if your Unity is earlier than Unity 5)
- .NET API 2.0 compatibility level for iOS/Android
.NET API 2.0 compatibility level for iOS/Android may require to fix lack of some features for later than .NET Framework 2.0, such as the `System.Func<...>` delegates (so i have added them in the asset package).
And it is priced at **US$15**. I believe your $15 makes this project more better, **Thank you!**
## Usage ##
### WebSocket Client ###
```csharp
using System;
using WebSocketSharp;
namespace Example
{
public class Program
{
public static void Main (string[] args)
{
using (var ws = new WebSocket ("ws://dragonsnest.far/Laputa")) {
ws.OnMessage += (sender, e) =>
Console.WriteLine ("Laputa says: " + e.Data);
ws.Connect ();
ws.Send ("BALUS");
Console.ReadKey (true);
}
}
}
}
```
#### Step 1 ####
Required namespace.
```csharp
using WebSocketSharp;
```
The `WebSocket` class exists in the `WebSocketSharp` namespace.
#### Step 2 ####
Creating a new instance of the `WebSocket` class with the WebSocket URL to connect.
```csharp
var ws = new WebSocket ("ws://example.com");
```
The `WebSocket` class inherits the `System.IDisposable` interface, so you can create it with the `using` statement.
```csharp
using (var ws = new WebSocket ("ws://example.com")) {
...
}
```
This will **close** the WebSocket connection with status code `1001` (going away) when the control leaves the `using` block.
#### Step 3 ####
Setting the `WebSocket` events.
##### WebSocket.OnOpen Event #####
This event occurs when the WebSocket connection has been established.
```csharp
ws.OnOpen += (sender, e) => {
...
};
```
`System.EventArgs.Empty` is passed as `e`, so you do not need to use it.
##### WebSocket.OnMessage Event #####
This event occurs when the `WebSocket` receives a message.
```csharp
ws.OnMessage += (sender, e) => {
...
};
```
A `WebSocketSharp.MessageEventArgs` instance is passed as `e`.
If you would like to get the message data, you should access `e.Data` or `e.RawData` property.
`e.Data` property returns a `string`, so it is mainly used to get the **text** message data.
`e.RawData` property returns a `byte[]`, so it is mainly used to get the **binary** message data.
```csharp
if (e.IsText) {
// Do something with e.Data.
...
return;
}
if (e.IsBinary) {
// Do something with e.RawData.
...
return;
}
```
And if you would like to notify that a **ping** has been received, via this event, you should set the `WebSocket.EmitOnPing` property to `true`.
```csharp
ws.EmitOnPing = true;
ws.OnMessage += (sender, e) => {
if (e.IsPing) {
// Do something to notify that a ping has been received.
...
return;
}
};
```
##### WebSocket.OnError Event #####
This event occurs when the `WebSocket` gets an error.
```csharp
ws.OnError += (sender, e) => {
...
};
```
A `WebSocketSharp.ErrorEventArgs` instance is passed as `e`.
If you would like to get the error message, you should access `e.Message` property.
`e.Message` property returns a `string` that represents the error message.
And `e.Exception` property returns a `System.Exception` instance that represents the cause of the error if it is due to an exception.
##### WebSocket.OnClose Event #####
This event occurs when the WebSocket connection has been closed.
```csharp
ws.OnClose += (sender, e) => {
...
};
```
A `WebSocketSharp.CloseEventArgs` instance is passed as `e`.
If you would like to get the reason for the close, you should access `e.Code` or `e.Reason` property.
`e.Code` property returns a `ushort` that represents the status code for the close.
`e.Reason` property returns a `string` that represents the reason for the close.
#### Step 4 ####
Connecting to the WebSocket server.
```csharp
ws.Connect ();
```
If you would like to connect to the server asynchronously, you should use the `WebSocket.ConnectAsync ()` method.
#### Step 5 ####
Sending data to the WebSocket server.
```csharp
ws.Send (data);
```
The `WebSocket.Send` method is overloaded.
You can use the `WebSocket.Send (string)`, `WebSocket.Send (byte[])`, or `WebSocket.Send (System.IO.FileInfo)` method to send the data.
If you would like to send the data asynchronously, you should use the `WebSocket.SendAsync` method.
```csharp
ws.SendAsync (data, completed);
```
And also if you would like to do something when the send is complete, you should set `completed` to any `Action<bool>` delegate.
#### Step 6 ####
Closing the WebSocket connection.
```csharp
ws.Close (code, reason);
```
If you would like to close the connection explicitly, you should use the `WebSocket.Close` method.
The `WebSocket.Close` method is overloaded.
You can use the `WebSocket.Close ()`, `WebSocket.Close (ushort)`, `WebSocket.Close (WebSocketSharp.CloseStatusCode)`, `WebSocket.Close (ushort, string)`, or `WebSocket.Close (WebSocketSharp.CloseStatusCode, string)` method to close the connection.
If you would like to close the connection asynchronously, you should use the `WebSocket.CloseAsync` method.
### WebSocket Server ###
```csharp
using System;
using WebSocketSharp;
using WebSocketSharp.Server;
namespace Example
{
public class Laputa : WebSocketBehavior
{
protected override void OnMessage (MessageEventArgs e)
{
var msg = e.Data == "BALUS"
? "I've been balused already..."
: "I'm not available now.";
Send (msg);
}
}
public class Program
{
public static void Main (string[] args)
{
var wssv = new WebSocketServer ("ws://dragonsnest.far");
wssv.AddWebSocketService<Laputa> ("/Laputa");
wssv.Start ();
Console.ReadKey (true);
wssv.Stop ();
}
}
}
```
#### Step 1 ####
Required namespace.
```csharp
using WebSocketSharp.Server;
```
The `WebSocketBehavior` and `WebSocketServer` classes exist in the `WebSocketSharp.Server` namespace.
#### Step 2 ####
Creating the class that inherits the `WebSocketBehavior` class.
For example, if you would like to provide an echo service,
```csharp
using System;
using WebSocketSharp;
using WebSocketSharp.Server;
public class Echo : WebSocketBehavior
{
protected override void OnMessage (MessageEventArgs e)
{
Send (e.Data);
}
}
```
And if you would like to provide a chat service,
```csharp
using System;
using WebSocketSharp;
using WebSocketSharp.Server;
public class Chat : WebSocketBehavior
{
private string _suffix;
public Chat ()
: this (null)
{
}
public Chat (string suffix)
{
_suffix = suffix ?? String.Empty;
}
protected override void OnMessage (MessageEventArgs e)
{
Sessions.Broadcast (e.Data + _suffix);
}
}
```
You can define the behavior of any WebSocket service by creating the class that inherits the `WebSocketBehavior` class.
If you override the `WebSocketBehavior.OnMessage (MessageEventArgs)` method, it will be called when the `WebSocket` used in a session in the service receives a message.
And if you override the `WebSocketBehavior.OnOpen ()`, `WebSocketBehavior.OnError (ErrorEventArgs)`, and `WebSocketBehavior.OnClose (CloseEventArgs)` methods, each of them will be called when each of the `WebSocket` events (`OnOpen`, `OnError`, and `OnClose`) occurs.
The `WebSocketBehavior.Send` method can send data to the client on a session in the service.
If you would like to get the sessions in the service, you should access the `WebSocketBehavior.Sessions` property (returns a `WebSocketSharp.Server.WebSocketSessionManager`).
The `WebSocketBehavior.Sessions.Broadcast` method can send data to every client in the service.
#### Step 3 ####
Creating a new instance of the `WebSocketServer` class.
```csharp
var wssv = new WebSocketServer (4649);
wssv.AddWebSocketService<Echo> ("/Echo");
wssv.AddWebSocketService<Chat> ("/Chat");
wssv.AddWebSocketService<Chat> ("/ChatWithNyan", () => new Chat (" Nyan!"));
```
You can add any WebSocket service to your `WebSocketServer` with the specified behavior and absolute path to the service, by using the `WebSocketServer.AddWebSocketService<TBehaviorWithNew> (string)` or `WebSocketServer.AddWebSocketService<TBehavior> (string, Func<TBehavior>)` method.
The type of `TBehaviorWithNew` must inherit the `WebSocketBehavior` class, and must have a public parameterless constructor.
The type of `TBehavior` must inherit the `WebSocketBehavior` class.
So you can use a class in the above Step 2 to add the service.
If you create a new instance of the `WebSocketServer` class without a port number, it sets the port number to **80**. So it is necessary to run with root permission.
$ sudo mono example2.exe
#### Step 4 ####
Starting the WebSocket server.
```csharp
wssv.Start ();
```
#### Step 5 ####
Stopping the WebSocket server.
```csharp
wssv.Stop (code, reason);
```
The `WebSocketServer.Stop` method is overloaded.
You can use the `WebSocketServer.Stop ()`, `WebSocketServer.Stop (ushort, string)`, or `WebSocketServer.Stop (WebSocketSharp.CloseStatusCode, string)` method to stop the server.
### HTTP Server with the WebSocket ###
I have modified the `System.Net.HttpListener`, `System.Net.HttpListenerContext`, and some other classes from **[Mono]** to create an HTTP server that allows to accept the WebSocket handshake requests.
So websocket-sharp provides the `WebSocketSharp.Server.HttpServer` class.
You can add any WebSocket service to your `HttpServer` with the specified behavior and path to the service, by using the `HttpServer.AddWebSocketService<TBehaviorWithNew> (string)` or `HttpServer.AddWebSocketService<TBehavior> (string, Func<TBehavior>)` method.
```csharp
var httpsv = new HttpServer (4649);
httpsv.AddWebSocketService<Echo> ("/Echo");
httpsv.AddWebSocketService<Chat> ("/Chat");
httpsv.AddWebSocketService<Chat> ("/ChatWithNyan", () => new Chat (" Nyan!"));
```
For more information, would you see **[Example3]**?
### WebSocket Extensions ###
#### Per-message Compression ####
websocket-sharp supports the [Per-message Compression][compression] extension (but does not support it with the [context take over]).
As a WebSocket client, if you would like to enable this extension, you should set the `WebSocket.Compression` property to a compression method before calling the connect method.
```csharp
ws.Compression = CompressionMethod.Deflate;
```
And then the client will send the following header in the handshake request to the server.
Sec-WebSocket-Extensions: permessage-deflate; server_no_context_takeover; client_no_context_takeover
If the server supports this extension, it will return the same header which has the corresponding value.
So eventually this extension will be available when the client receives the header in the handshake response.
#### Ignoring the extensions ####
As a WebSocket server, if you would like to ignore the extensions requested from a client, you should set the `WebSocketBehavior.IgnoreExtensions` property to `true` in your `WebSocketBehavior` constructor or initializing it, such as the following.
```csharp
wssv.AddWebSocketService<Chat> (
"/Chat",
() =>
new Chat () {
// To ignore the extensions requested from a client.
IgnoreExtensions = true
}
);
```
If it is set to `true`, the service will not return the Sec-WebSocket-Extensions header in its handshake response.
I think this is useful when you get something error in connecting the server and exclude the extensions as a cause of the error.
### Secure Connection ###
websocket-sharp supports the secure connection with **SSL/TLS**.
As a WebSocket client, you should create a new instance of the `WebSocket` class with a **wss** scheme WebSocket URL.
```csharp
var ws = new WebSocket ("wss://example.com");
```
If you would like to set a custom validation for the server certificate, you should set the `WebSocket.SslConfiguration.ServerCertificateValidationCallback` property to a callback for it.
```csharp
ws.SslConfiguration.ServerCertificateValidationCallback =
(sender, certificate, chain, sslPolicyErrors) => {
// Do something to validate the server certificate.
...
return true; // If the server certificate is valid.
};
```
The default callback always returns `true`.
As a WebSocket server, you should create a new instance of the `WebSocketServer` or `HttpServer` class with some settings for the secure connection, such as the following.
```csharp
var wssv = new WebSocketServer (5963, true);
wssv.SslConfiguration.ServerCertificate =
new X509Certificate2 ("/path/to/cert.pfx", "password for cert.pfx");
```
### HTTP Authentication ###
websocket-sharp supports the [HTTP Authentication (Basic/Digest)][rfc2617].
As a WebSocket client, you should set a pair of user name and password for the HTTP authentication, by using the `WebSocket.SetCredentials (string, string, bool)` method before calling the connect method.
```csharp
ws.SetCredentials ("nobita", "password", preAuth);
```
If `preAuth` is `true`, the client will send the credentials for the Basic authentication in the first handshake request to the server.
Otherwise, it will send the credentials for either the Basic or Digest (determined by the unauthorized response to the first handshake request) authentication in the second handshake request to the server.
As a WebSocket server, you should set an HTTP authentication scheme, a realm, and any function to find the user credentials before calling the start method, such as the following.
```csharp
wssv.AuthenticationSchemes = AuthenticationSchemes.Basic;
wssv.Realm = "WebSocket Test";
wssv.UserCredentialsFinder = id => {
var name = id.Name;
// Return user name, password, and roles.
return name == "nobita"
? new NetworkCredential (name, "password", "gunfighter")
: null; // If the user credentials are not found.
};
```
If you would like to provide the Digest authentication, you should set such as the following.
```csharp
wssv.AuthenticationSchemes = AuthenticationSchemes.Digest;
```
### Query string, Origin header, and Cookies ###
As a WebSocket client, if you would like to send the query string in the handshake request, you should create a new instance of the `WebSocket` class with a WebSocket URL that includes the [Query] string parameters.
```csharp
var ws = new WebSocket ("ws://example.com/?name=nobita");
```
If you would like to send the Origin header in the handshake request, you should set the `WebSocket.Origin` property to an allowable value as the [Origin] header before calling the connect method.
```csharp
ws.Origin = "http://example.com";
```
And if you would like to send the cookies in the handshake request, you should set any cookie by using the `WebSocket.SetCookie (WebSocketSharp.Net.Cookie)` method before calling the connect method.
```csharp
ws.SetCookie (new Cookie ("name", "nobita"));
```
As a WebSocket server, if you would like to get the query string included in a handshake request, you should access the `WebSocketBehavior.Context.QueryString` property, such as the following.
```csharp
public class Chat : WebSocketBehavior
{
private string _name;
...
protected override void OnOpen ()
{
_name = Context.QueryString["name"];
}
...
}
```
If you would like to get the value of the Origin header included in a handshake request, you should access the `WebSocketBehavior.Context.Origin` property.
If you would like to get the cookies included in a handshake request, you should access the `WebSocketBehavior.Context.CookieCollection` property.
And if you would like to validate the Origin header, cookies, or both, you should set each validation for it with your `WebSocketBehavior`, for example, by using the `WebSocketServer.AddWebSocketService<TBehavior> (string, Func<TBehavior>)` method with initializing, such as the following.
```csharp
wssv.AddWebSocketService<Chat> (
"/Chat",
() =>
new Chat () {
OriginValidator = val => {
// Check the value of the Origin header, and return true if valid.
Uri origin;
return !val.IsNullOrEmpty ()
&& Uri.TryCreate (val, UriKind.Absolute, out origin)
&& origin.Host == "example.com";
},
CookiesValidator = (req, res) => {
// Check the cookies in 'req', and set the cookies to send to
// the client with 'res' if necessary.
foreach (Cookie cookie in req) {
cookie.Expired = true;
res.Add (cookie);
}
return true; // If valid.
}
}
);
```
### Connecting through the HTTP proxy server ###
websocket-sharp supports to connect through the HTTP proxy server.
If you would like to connect to a WebSocket server through the HTTP proxy server, you should set the proxy server URL, and if necessary, a pair of user name and password for the proxy server authentication (Basic/Digest), by using the `WebSocket.SetProxy (string, string, string)` method before calling the connect method.
```csharp
var ws = new WebSocket ("ws://example.com");
ws.SetProxy ("http://localhost:3128", "nobita", "password");
```
I have tested this with **[Squid]**. It is necessary to disable the following option in **squid.conf** (e.g. `/etc/squid/squid.conf`).
```
# Deny CONNECT to other than SSL ports
#http_access deny CONNECT !SSL_ports
```
### Logging ###
The `WebSocket` class has the own logging function.
You can use it with the `WebSocket.Log` property (returns a `WebSocketSharp.Logger`).
So if you would like to change the current logging level (`WebSocketSharp.LogLevel.Error` as the default), you should set the `WebSocket.Log.Level` property to any of the `LogLevel` enum values.
```csharp
ws.Log.Level = LogLevel.Debug;
```
The above means a log with lower than `LogLevel.Debug` cannot be outputted.
And if you would like to output a log, you should use any of the output methods. The following outputs a log with `LogLevel.Debug`.
```csharp
ws.Log.Debug ("This is a debug message.");
```
The `WebSocketServer` and `HttpServer` classes have the same logging function.
## Examples ##
Examples using websocket-sharp.
### Example ###
[Example] connects to the [Echo server].
### Example2 ###
[Example2] starts a WebSocket server.
### Example3 ###
[Example3] starts an HTTP server that allows to accept the WebSocket handshake requests.
Would you access to [http://localhost:4649](http://localhost:4649) to do **WebSocket Echo Test** with your web browser while Example3 is running?
## Supported WebSocket Specifications ##
websocket-sharp supports **RFC 6455**, and it is based on the following references:
- [The WebSocket Protocol][rfc6455]
- [The WebSocket API][api]
- [Compression Extensions for WebSocket][compression]
Thanks for translating to japanese.
- [The WebSocket Protocol 日本語訳][rfc6455_ja]
- [The WebSocket API 日本語訳][api_ja]
## License ##
websocket-sharp is provided under [The MIT License].
[Echo server]: http://www.websocket.org/echo.html
[Example]: https://github.com/sta/websocket-sharp/tree/master/Example
[Example2]: https://github.com/sta/websocket-sharp/tree/master/Example2
[Example3]: https://github.com/sta/websocket-sharp/tree/master/Example3
[Mono]: http://www.mono-project.com
[MonoDevelop]: http://monodevelop.com
[NuGet Gallery]: http://www.nuget.org
[NuGet Gallery: websocket-sharp]: http://www.nuget.org/packages/WebSocketSharp
[Origin]: http://tools.ietf.org/html/rfc6454#section-7
[Query]: http://tools.ietf.org/html/rfc3986#section-3.4
[Security Sandbox of the Webplayer]: http://docs.unity3d.com/Manual/SecuritySandbox.html
[Squid]: http://www.squid-cache.org
[The MIT License]: https://raw.github.com/sta/websocket-sharp/master/LICENSE.txt
[Unity]: http://unity3d.com
[WebGL Networking]: http://docs.unity3d.com/Manual/webgl-networking.html
[WebSocket-Sharp for Unity]: http://u3d.as/content/sta-blockhead/websocket-sharp-for-unity
[api]: http://www.w3.org/TR/websockets
[api_ja]: http://www.hcn.zaq.ne.jp/___/WEB/WebSocket-ja.html
[compression]: http://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-19
[context take over]: http://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-19#section-8.1.1
[draft-hixie-thewebsocketprotocol-75]: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75
[draft-ietf-hybi-thewebsocketprotocol-00]: http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00
[draft75]: https://github.com/sta/websocket-sharp/tree/draft75
[hybi-00]: https://github.com/sta/websocket-sharp/tree/hybi-00
[master]: https://github.com/sta/websocket-sharp/tree/master
[rfc2617]: http://tools.ietf.org/html/rfc2617
[rfc6455]: http://tools.ietf.org/html/rfc6455
[rfc6455_ja]: http://www.hcn.zaq.ne.jp/___/WEB/RFC6455-ja.html

View File

@ -0,0 +1,74 @@

Microsoft Visual Studio Solution File, Format Version 10.00
# Visual Studio 2008
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "websocket-sharp", "websocket-sharp\websocket-sharp.csproj", "{B357BAC7-529E-4D81-A0D2-71041B19C8DE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example", "Example\Example.csproj", "{52805AEC-EFB1-4F42-BB8E-3ED4E692C568}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example1", "Example1\Example1.csproj", "{390E2568-57B7-4D17-91E5-C29336368CCF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example2", "Example2\Example2.csproj", "{B81A24C8-25BB-42B2-AF99-1E1EACCE74C7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example3", "Example3\Example3.csproj", "{C648BA25-77E5-4A40-A97F-D0AA37B9FB26}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
Debug_Ubuntu|Any CPU = Debug_Ubuntu|Any CPU
Release_Ubuntu|Any CPU = Release_Ubuntu|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{390E2568-57B7-4D17-91E5-C29336368CCF}.Debug_Ubuntu|Any CPU.ActiveCfg = Debug_Ubuntu|Any CPU
{390E2568-57B7-4D17-91E5-C29336368CCF}.Debug_Ubuntu|Any CPU.Build.0 = Debug_Ubuntu|Any CPU
{390E2568-57B7-4D17-91E5-C29336368CCF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{390E2568-57B7-4D17-91E5-C29336368CCF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{390E2568-57B7-4D17-91E5-C29336368CCF}.Release_Ubuntu|Any CPU.ActiveCfg = Release_Ubuntu|Any CPU
{390E2568-57B7-4D17-91E5-C29336368CCF}.Release_Ubuntu|Any CPU.Build.0 = Release_Ubuntu|Any CPU
{390E2568-57B7-4D17-91E5-C29336368CCF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{390E2568-57B7-4D17-91E5-C29336368CCF}.Release|Any CPU.Build.0 = Release|Any CPU
{52805AEC-EFB1-4F42-BB8E-3ED4E692C568}.Debug_Ubuntu|Any CPU.ActiveCfg = Debug_Ubuntu|Any CPU
{52805AEC-EFB1-4F42-BB8E-3ED4E692C568}.Debug_Ubuntu|Any CPU.Build.0 = Debug_Ubuntu|Any CPU
{52805AEC-EFB1-4F42-BB8E-3ED4E692C568}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{52805AEC-EFB1-4F42-BB8E-3ED4E692C568}.Debug|Any CPU.Build.0 = Debug|Any CPU
{52805AEC-EFB1-4F42-BB8E-3ED4E692C568}.Release_Ubuntu|Any CPU.ActiveCfg = Release_Ubuntu|Any CPU
{52805AEC-EFB1-4F42-BB8E-3ED4E692C568}.Release_Ubuntu|Any CPU.Build.0 = Release_Ubuntu|Any CPU
{52805AEC-EFB1-4F42-BB8E-3ED4E692C568}.Release|Any CPU.ActiveCfg = Release|Any CPU
{52805AEC-EFB1-4F42-BB8E-3ED4E692C568}.Release|Any CPU.Build.0 = Release|Any CPU
{B357BAC7-529E-4D81-A0D2-71041B19C8DE}.Debug_Ubuntu|Any CPU.ActiveCfg = Debug_Ubuntu|Any CPU
{B357BAC7-529E-4D81-A0D2-71041B19C8DE}.Debug_Ubuntu|Any CPU.Build.0 = Debug_Ubuntu|Any CPU
{B357BAC7-529E-4D81-A0D2-71041B19C8DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B357BAC7-529E-4D81-A0D2-71041B19C8DE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B357BAC7-529E-4D81-A0D2-71041B19C8DE}.Release_Ubuntu|Any CPU.ActiveCfg = Release_Ubuntu|Any CPU
{B357BAC7-529E-4D81-A0D2-71041B19C8DE}.Release_Ubuntu|Any CPU.Build.0 = Release_Ubuntu|Any CPU
{B357BAC7-529E-4D81-A0D2-71041B19C8DE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B357BAC7-529E-4D81-A0D2-71041B19C8DE}.Release|Any CPU.Build.0 = Release|Any CPU
{B81A24C8-25BB-42B2-AF99-1E1EACCE74C7}.Debug_Ubuntu|Any CPU.ActiveCfg = Debug_Ubuntu|Any CPU
{B81A24C8-25BB-42B2-AF99-1E1EACCE74C7}.Debug_Ubuntu|Any CPU.Build.0 = Debug_Ubuntu|Any CPU
{B81A24C8-25BB-42B2-AF99-1E1EACCE74C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B81A24C8-25BB-42B2-AF99-1E1EACCE74C7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B81A24C8-25BB-42B2-AF99-1E1EACCE74C7}.Release_Ubuntu|Any CPU.ActiveCfg = Release_Ubuntu|Any CPU
{B81A24C8-25BB-42B2-AF99-1E1EACCE74C7}.Release_Ubuntu|Any CPU.Build.0 = Release_Ubuntu|Any CPU
{B81A24C8-25BB-42B2-AF99-1E1EACCE74C7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B81A24C8-25BB-42B2-AF99-1E1EACCE74C7}.Release|Any CPU.Build.0 = Release|Any CPU
{C648BA25-77E5-4A40-A97F-D0AA37B9FB26}.Debug_Ubuntu|Any CPU.ActiveCfg = Debug_Ubuntu|Any CPU
{C648BA25-77E5-4A40-A97F-D0AA37B9FB26}.Debug_Ubuntu|Any CPU.Build.0 = Debug_Ubuntu|Any CPU
{C648BA25-77E5-4A40-A97F-D0AA37B9FB26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C648BA25-77E5-4A40-A97F-D0AA37B9FB26}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C648BA25-77E5-4A40-A97F-D0AA37B9FB26}.Release_Ubuntu|Any CPU.ActiveCfg = Release_Ubuntu|Any CPU
{C648BA25-77E5-4A40-A97F-D0AA37B9FB26}.Release_Ubuntu|Any CPU.Build.0 = Release_Ubuntu|Any CPU
{C648BA25-77E5-4A40-A97F-D0AA37B9FB26}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C648BA25-77E5-4A40-A97F-D0AA37B9FB26}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(MonoDevelopProperties) = preSolution
StartupItem = websocket-sharp\websocket-sharp.csproj
Policies = $0
$0.TextStylePolicy = $1
$1.inheritsSet = null
$1.scope = text/x-csharp
$0.CSharpFormattingPolicy = $2
$2.inheritsSet = Mono
$2.inheritsScope = text/x-csharp
$2.scope = text/x-csharp
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,47 @@
#region License
/*
* ByteOrder.cs
*
* The MIT License
*
* Copyright (c) 2012-2015 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
using System;
namespace WebSocketSharp
{
/// <summary>
/// Specifies the byte order.
/// </summary>
public enum ByteOrder
{
/// <summary>
/// Specifies Little-endian.
/// </summary>
Little,
/// <summary>
/// Specifies Big-endian.
/// </summary>
Big
}
}

View File

@ -0,0 +1,113 @@
#region License
/*
* CloseEventArgs.cs
*
* The MIT License
*
* Copyright (c) 2012-2019 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
using System;
namespace WebSocketSharp
{
/// <summary>
/// Represents the event data for the <see cref="WebSocket.OnClose"/> event.
/// </summary>
/// <remarks>
/// <para>
/// That event occurs when the WebSocket connection has been closed.
/// </para>
/// <para>
/// If you would like to get the reason for the connection close, you should
/// access the <see cref="Code"/> or <see cref="Reason"/> property.
/// </para>
/// </remarks>
public class CloseEventArgs : EventArgs
{
#region Private Fields
private bool _clean;
private PayloadData _payloadData;
#endregion
#region Internal Constructors
internal CloseEventArgs (PayloadData payloadData, bool clean)
{
_payloadData = payloadData;
_clean = clean;
}
internal CloseEventArgs (ushort code, string reason, bool clean)
{
_payloadData = new PayloadData (code, reason);
_clean = clean;
}
#endregion
#region Public Properties
/// <summary>
/// Gets the status code for the connection close.
/// </summary>
/// <value>
/// A <see cref="ushort"/> that represents the status code for
/// the connection close if present.
/// </value>
public ushort Code {
get {
return _payloadData.Code;
}
}
/// <summary>
/// Gets the reason for the connection close.
/// </summary>
/// <value>
/// A <see cref="string"/> that represents the reason for
/// the connection close if present.
/// </value>
public string Reason {
get {
return _payloadData.Reason;
}
}
/// <summary>
/// Gets a value indicating whether the connection has been closed cleanly.
/// </summary>
/// <value>
/// <c>true</c> if the connection has been closed cleanly; otherwise,
/// <c>false</c>.
/// </value>
public bool WasClean {
get {
return _clean;
}
}
#endregion
}
}

View File

@ -0,0 +1,120 @@
#region License
/*
* CloseStatusCode.cs
*
* The MIT License
*
* Copyright (c) 2012-2016 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
using System;
namespace WebSocketSharp
{
/// <summary>
/// Indicates the status code for the WebSocket connection close.
/// </summary>
/// <remarks>
/// <para>
/// The values of this enumeration are defined in
/// <see href="http://tools.ietf.org/html/rfc6455#section-7.4">
/// Section 7.4</see> of RFC 6455.
/// </para>
/// <para>
/// "Reserved value" cannot be sent as a status code in
/// closing handshake by an endpoint.
/// </para>
/// </remarks>
public enum CloseStatusCode : ushort
{
/// <summary>
/// Equivalent to close status 1000. Indicates normal close.
/// </summary>
Normal = 1000,
/// <summary>
/// Equivalent to close status 1001. Indicates that an endpoint is
/// going away.
/// </summary>
Away = 1001,
/// <summary>
/// Equivalent to close status 1002. Indicates that an endpoint is
/// terminating the connection due to a protocol error.
/// </summary>
ProtocolError = 1002,
/// <summary>
/// Equivalent to close status 1003. Indicates that an endpoint is
/// terminating the connection because it has received a type of
/// data that it cannot accept.
/// </summary>
UnsupportedData = 1003,
/// <summary>
/// Equivalent to close status 1004. Still undefined. A Reserved value.
/// </summary>
Undefined = 1004,
/// <summary>
/// Equivalent to close status 1005. Indicates that no status code was
/// actually present. A Reserved value.
/// </summary>
NoStatus = 1005,
/// <summary>
/// Equivalent to close status 1006. Indicates that the connection was
/// closed abnormally. A Reserved value.
/// </summary>
Abnormal = 1006,
/// <summary>
/// Equivalent to close status 1007. Indicates that an endpoint is
/// terminating the connection because it has received a message that
/// contains data that is not consistent with the type of the message.
/// </summary>
InvalidData = 1007,
/// <summary>
/// Equivalent to close status 1008. Indicates that an endpoint is
/// terminating the connection because it has received a message that
/// violates its policy.
/// </summary>
PolicyViolation = 1008,
/// <summary>
/// Equivalent to close status 1009. Indicates that an endpoint is
/// terminating the connection because it has received a message that
/// is too big to process.
/// </summary>
TooBig = 1009,
/// <summary>
/// Equivalent to close status 1010. Indicates that a client is
/// terminating the connection because it has expected the server to
/// negotiate one or more extension, but the server did not return
/// them in the handshake response.
/// </summary>
MandatoryExtension = 1010,
/// <summary>
/// Equivalent to close status 1011. Indicates that a server is
/// terminating the connection because it has encountered an unexpected
/// condition that prevented it from fulfilling the request.
/// </summary>
ServerError = 1011,
/// <summary>
/// Equivalent to close status 1015. Indicates that the connection was
/// closed due to a failure to perform a TLS handshake. A Reserved value.
/// </summary>
TlsHandshakeFailure = 1015
}
}

View File

@ -0,0 +1,52 @@
#region License
/*
* CompressionMethod.cs
*
* The MIT License
*
* Copyright (c) 2013-2017 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
using System;
namespace WebSocketSharp
{
/// <summary>
/// Specifies the method for compression.
/// </summary>
/// <remarks>
/// The methods are defined in
/// <see href="https://tools.ietf.org/html/rfc7692">
/// Compression Extensions for WebSocket</see>.
/// </remarks>
public enum CompressionMethod : byte
{
/// <summary>
/// Specifies no compression.
/// </summary>
None,
/// <summary>
/// Specifies DEFLATE.
/// </summary>
Deflate
}
}

View File

@ -0,0 +1,109 @@
#region License
/*
* ErrorEventArgs.cs
*
* The MIT License
*
* Copyright (c) 2012-2016 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
#region Contributors
/*
* Contributors:
* - Frank Razenberg <frank@zzattack.org>
*/
#endregion
using System;
namespace WebSocketSharp
{
/// <summary>
/// Represents the event data for the <see cref="WebSocket.OnError"/> event.
/// </summary>
/// <remarks>
/// <para>
/// That event occurs when the <see cref="WebSocket"/> gets an error.
/// </para>
/// <para>
/// If you would like to get the error message, you should access
/// the <see cref="ErrorEventArgs.Message"/> property.
/// </para>
/// <para>
/// And if the error is due to an exception, you can get it by accessing
/// the <see cref="ErrorEventArgs.Exception"/> property.
/// </para>
/// </remarks>
public class ErrorEventArgs : EventArgs
{
#region Private Fields
private Exception _exception;
private string _message;
#endregion
#region Internal Constructors
internal ErrorEventArgs (string message)
: this (message, null)
{
}
internal ErrorEventArgs (string message, Exception exception)
{
_message = message;
_exception = exception;
}
#endregion
#region Public Properties
/// <summary>
/// Gets the exception that caused the error.
/// </summary>
/// <value>
/// An <see cref="System.Exception"/> instance that represents the cause of
/// the error if it is due to an exception; otherwise, <see langword="null"/>.
/// </value>
public Exception Exception {
get {
return _exception;
}
}
/// <summary>
/// Gets the error message.
/// </summary>
/// <value>
/// A <see cref="string"/> that represents the error message.
/// </value>
public string Message {
get {
return _message;
}
}
#endregion
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,51 @@
#region License
/*
* Fin.cs
*
* The MIT License
*
* Copyright (c) 2012-2015 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
using System;
namespace WebSocketSharp
{
/// <summary>
/// Indicates whether a WebSocket frame is the final frame of a message.
/// </summary>
/// <remarks>
/// The values of this enumeration are defined in
/// <see href="http://tools.ietf.org/html/rfc6455#section-5.2">Section 5.2</see> of RFC 6455.
/// </remarks>
internal enum Fin : byte
{
/// <summary>
/// Equivalent to numeric value 0. Indicates more frames of a message follow.
/// </summary>
More = 0x0,
/// <summary>
/// Equivalent to numeric value 1. Indicates the final frame of a message.
/// </summary>
Final = 0x1
}
}

View File

@ -0,0 +1,208 @@
#region License
/*
* HttpBase.cs
*
* The MIT License
*
* Copyright (c) 2012-2014 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Text;
using System.Threading;
using WebSocketSharp.Net;
namespace WebSocketSharp
{
internal abstract class HttpBase
{
#region Private Fields
private NameValueCollection _headers;
private const int _headersMaxLength = 8192;
private Version _version;
#endregion
#region Internal Fields
internal byte[] EntityBodyData;
#endregion
#region Protected Fields
protected const string CrLf = "\r\n";
#endregion
#region Protected Constructors
protected HttpBase (Version version, NameValueCollection headers)
{
_version = version;
_headers = headers;
}
#endregion
#region Public Properties
public string EntityBody {
get {
if (EntityBodyData == null || EntityBodyData.LongLength == 0)
return String.Empty;
Encoding enc = null;
var contentType = _headers["Content-Type"];
if (contentType != null && contentType.Length > 0)
enc = HttpUtility.GetEncoding (contentType);
return (enc ?? Encoding.UTF8).GetString (EntityBodyData);
}
}
public NameValueCollection Headers {
get {
return _headers;
}
}
public Version ProtocolVersion {
get {
return _version;
}
}
#endregion
#region Private Methods
private static byte[] readEntityBody (Stream stream, string length)
{
long len;
if (!Int64.TryParse (length, out len))
throw new ArgumentException ("Cannot be parsed.", "length");
if (len < 0)
throw new ArgumentOutOfRangeException ("length", "Less than zero.");
return len > 1024
? stream.ReadBytes (len, 1024)
: len > 0
? stream.ReadBytes ((int) len)
: null;
}
private static string[] readHeaders (Stream stream, int maxLength)
{
var buff = new List<byte> ();
var cnt = 0;
Action<int> add = i => {
if (i == -1)
throw new EndOfStreamException ("The header cannot be read from the data source.");
buff.Add ((byte) i);
cnt++;
};
var read = false;
while (cnt < maxLength) {
if (stream.ReadByte ().EqualsWith ('\r', add) &&
stream.ReadByte ().EqualsWith ('\n', add) &&
stream.ReadByte ().EqualsWith ('\r', add) &&
stream.ReadByte ().EqualsWith ('\n', add)) {
read = true;
break;
}
}
if (!read)
throw new WebSocketException ("The length of header part is greater than the max length.");
return Encoding.UTF8.GetString (buff.ToArray ())
.Replace (CrLf + " ", " ")
.Replace (CrLf + "\t", " ")
.Split (new[] { CrLf }, StringSplitOptions.RemoveEmptyEntries);
}
#endregion
#region Protected Methods
protected static T Read<T> (Stream stream, Func<string[], T> parser, int millisecondsTimeout)
where T : HttpBase
{
var timeout = false;
var timer = new Timer (
state => {
timeout = true;
stream.Close ();
},
null,
millisecondsTimeout,
-1);
T http = null;
Exception exception = null;
try {
http = parser (readHeaders (stream, _headersMaxLength));
var contentLen = http.Headers["Content-Length"];
if (contentLen != null && contentLen.Length > 0)
http.EntityBodyData = readEntityBody (stream, contentLen);
}
catch (Exception ex) {
exception = ex;
}
finally {
timer.Change (-1, -1);
timer.Dispose ();
}
var msg = timeout
? "A timeout has occurred while reading an HTTP request/response."
: exception != null
? "An exception has occurred while reading an HTTP request/response."
: null;
if (msg != null)
throw new WebSocketException (msg, exception);
return http;
}
#endregion
#region Public Methods
public byte[] ToByteArray ()
{
return Encoding.UTF8.GetBytes (ToString ());
}
#endregion
}
}

View File

@ -0,0 +1,217 @@
#region License
/*
* HttpRequest.cs
*
* The MIT License
*
* Copyright (c) 2012-2015 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
#region Contributors
/*
* Contributors:
* - David Burhans
*/
#endregion
using System;
using System.Collections.Specialized;
using System.IO;
using System.Text;
using WebSocketSharp.Net;
namespace WebSocketSharp
{
internal class HttpRequest : HttpBase
{
#region Private Fields
private CookieCollection _cookies;
private string _method;
private string _uri;
#endregion
#region Private Constructors
private HttpRequest (string method, string uri, Version version, NameValueCollection headers)
: base (version, headers)
{
_method = method;
_uri = uri;
}
#endregion
#region Internal Constructors
internal HttpRequest (string method, string uri)
: this (method, uri, HttpVersion.Version11, new NameValueCollection ())
{
Headers["User-Agent"] = "websocket-sharp/1.0";
}
#endregion
#region Public Properties
public AuthenticationResponse AuthenticationResponse {
get {
var res = Headers["Authorization"];
return res != null && res.Length > 0
? AuthenticationResponse.Parse (res)
: null;
}
}
public CookieCollection Cookies {
get {
if (_cookies == null)
_cookies = Headers.GetCookies (false);
return _cookies;
}
}
public string HttpMethod {
get {
return _method;
}
}
public bool IsWebSocketRequest {
get {
return _method == "GET"
&& ProtocolVersion > HttpVersion.Version10
&& Headers.Upgrades ("websocket");
}
}
public string RequestUri {
get {
return _uri;
}
}
#endregion
#region Internal Methods
internal static HttpRequest CreateConnectRequest (Uri uri)
{
var host = uri.DnsSafeHost;
var port = uri.Port;
var authority = String.Format ("{0}:{1}", host, port);
var req = new HttpRequest ("CONNECT", authority);
req.Headers["Host"] = port == 80 ? host : authority;
return req;
}
internal static HttpRequest CreateWebSocketRequest (Uri uri)
{
var req = new HttpRequest ("GET", uri.PathAndQuery);
var headers = req.Headers;
// Only includes a port number in the Host header value if it's non-default.
// See: https://tools.ietf.org/html/rfc6455#page-17
var port = uri.Port;
var schm = uri.Scheme;
headers["Host"] = (port == 80 && schm == "ws") || (port == 443 && schm == "wss")
? uri.DnsSafeHost
: uri.Authority;
headers["Upgrade"] = "websocket";
headers["Connection"] = "Upgrade";
return req;
}
internal HttpResponse GetResponse (Stream stream, int millisecondsTimeout)
{
var buff = ToByteArray ();
stream.Write (buff, 0, buff.Length);
return Read<HttpResponse> (stream, HttpResponse.Parse, millisecondsTimeout);
}
internal static HttpRequest Parse (string[] headerParts)
{
var requestLine = headerParts[0].Split (new[] { ' ' }, 3);
if (requestLine.Length != 3)
throw new ArgumentException ("Invalid request line: " + headerParts[0]);
var headers = new WebHeaderCollection ();
for (int i = 1; i < headerParts.Length; i++)
headers.InternalSet (headerParts[i], false);
return new HttpRequest (
requestLine[0], requestLine[1], new Version (requestLine[2].Substring (5)), headers);
}
internal static HttpRequest Read (Stream stream, int millisecondsTimeout)
{
return Read<HttpRequest> (stream, Parse, millisecondsTimeout);
}
#endregion
#region Public Methods
public void SetCookies (CookieCollection cookies)
{
if (cookies == null || cookies.Count == 0)
return;
var buff = new StringBuilder (64);
foreach (var cookie in cookies.Sorted)
if (!cookie.Expired)
buff.AppendFormat ("{0}; ", cookie.ToString ());
var len = buff.Length;
if (len > 2) {
buff.Length = len - 2;
Headers["Cookie"] = buff.ToString ();
}
}
public override string ToString ()
{
var output = new StringBuilder (64);
output.AppendFormat ("{0} {1} HTTP/{2}{3}", _method, _uri, ProtocolVersion, CrLf);
var headers = Headers;
foreach (var key in headers.AllKeys)
output.AppendFormat ("{0}: {1}{2}", key, headers[key], CrLf);
output.Append (CrLf);
var entity = EntityBody;
if (entity.Length > 0)
output.Append (entity);
return output.ToString ();
}
#endregion
}
}

View File

@ -0,0 +1,209 @@
#region License
/*
* HttpResponse.cs
*
* The MIT License
*
* Copyright (c) 2012-2014 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
using System;
using System.Collections.Specialized;
using System.IO;
using System.Text;
using WebSocketSharp.Net;
namespace WebSocketSharp
{
internal class HttpResponse : HttpBase
{
#region Private Fields
private string _code;
private string _reason;
#endregion
#region Private Constructors
private HttpResponse (string code, string reason, Version version, NameValueCollection headers)
: base (version, headers)
{
_code = code;
_reason = reason;
}
#endregion
#region Internal Constructors
internal HttpResponse (HttpStatusCode code)
: this (code, code.GetDescription ())
{
}
internal HttpResponse (HttpStatusCode code, string reason)
: this (((int) code).ToString (), reason, HttpVersion.Version11, new NameValueCollection ())
{
Headers["Server"] = "websocket-sharp/1.0";
}
#endregion
#region Public Properties
public CookieCollection Cookies {
get {
return Headers.GetCookies (true);
}
}
public bool HasConnectionClose {
get {
var comparison = StringComparison.OrdinalIgnoreCase;
return Headers.Contains ("Connection", "close", comparison);
}
}
public bool IsProxyAuthenticationRequired {
get {
return _code == "407";
}
}
public bool IsRedirect {
get {
return _code == "301" || _code == "302";
}
}
public bool IsUnauthorized {
get {
return _code == "401";
}
}
public bool IsWebSocketResponse {
get {
return ProtocolVersion > HttpVersion.Version10
&& _code == "101"
&& Headers.Upgrades ("websocket");
}
}
public string Reason {
get {
return _reason;
}
}
public string StatusCode {
get {
return _code;
}
}
#endregion
#region Internal Methods
internal static HttpResponse CreateCloseResponse (HttpStatusCode code)
{
var res = new HttpResponse (code);
res.Headers["Connection"] = "close";
return res;
}
internal static HttpResponse CreateUnauthorizedResponse (string challenge)
{
var res = new HttpResponse (HttpStatusCode.Unauthorized);
res.Headers["WWW-Authenticate"] = challenge;
return res;
}
internal static HttpResponse CreateWebSocketResponse ()
{
var res = new HttpResponse (HttpStatusCode.SwitchingProtocols);
var headers = res.Headers;
headers["Upgrade"] = "websocket";
headers["Connection"] = "Upgrade";
return res;
}
internal static HttpResponse Parse (string[] headerParts)
{
var statusLine = headerParts[0].Split (new[] { ' ' }, 3);
if (statusLine.Length != 3)
throw new ArgumentException ("Invalid status line: " + headerParts[0]);
var headers = new WebHeaderCollection ();
for (int i = 1; i < headerParts.Length; i++)
headers.InternalSet (headerParts[i], true);
return new HttpResponse (
statusLine[1], statusLine[2], new Version (statusLine[0].Substring (5)), headers);
}
internal static HttpResponse Read (Stream stream, int millisecondsTimeout)
{
return Read<HttpResponse> (stream, Parse, millisecondsTimeout);
}
#endregion
#region Public Methods
public void SetCookies (CookieCollection cookies)
{
if (cookies == null || cookies.Count == 0)
return;
var headers = Headers;
foreach (var cookie in cookies.Sorted)
headers.Add ("Set-Cookie", cookie.ToResponseString ());
}
public override string ToString ()
{
var output = new StringBuilder (64);
output.AppendFormat ("HTTP/{0} {1} {2}{3}", ProtocolVersion, _code, _reason, CrLf);
var headers = Headers;
foreach (var key in headers.AllKeys)
output.AppendFormat ("{0}: {1}{2}", key, headers[key], CrLf);
output.Append (CrLf);
var entity = EntityBody;
if (entity.Length > 0)
output.Append (entity);
return output.ToString ();
}
#endregion
}
}

View File

@ -0,0 +1,149 @@
#region License
/*
* LogData.cs
*
* The MIT License
*
* Copyright (c) 2013-2015 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
using System;
using System.Diagnostics;
using System.Text;
namespace WebSocketSharp
{
/// <summary>
/// Represents a log data used by the <see cref="Logger"/> class.
/// </summary>
public class LogData
{
#region Private Fields
private StackFrame _caller;
private DateTime _date;
private LogLevel _level;
private string _message;
#endregion
#region Internal Constructors
internal LogData (LogLevel level, StackFrame caller, string message)
{
_level = level;
_caller = caller;
_message = message ?? String.Empty;
_date = DateTime.Now;
}
#endregion
#region Public Properties
/// <summary>
/// Gets the information of the logging method caller.
/// </summary>
/// <value>
/// A <see cref="StackFrame"/> that provides the information of the logging method caller.
/// </value>
public StackFrame Caller {
get {
return _caller;
}
}
/// <summary>
/// Gets the date and time when the log data was created.
/// </summary>
/// <value>
/// A <see cref="DateTime"/> that represents the date and time when the log data was created.
/// </value>
public DateTime Date {
get {
return _date;
}
}
/// <summary>
/// Gets the logging level of the log data.
/// </summary>
/// <value>
/// One of the <see cref="LogLevel"/> enum values, indicates the logging level of the log data.
/// </value>
public LogLevel Level {
get {
return _level;
}
}
/// <summary>
/// Gets the message of the log data.
/// </summary>
/// <value>
/// A <see cref="string"/> that represents the message of the log data.
/// </value>
public string Message {
get {
return _message;
}
}
#endregion
#region Public Methods
/// <summary>
/// Returns a <see cref="string"/> that represents the current <see cref="LogData"/>.
/// </summary>
/// <returns>
/// A <see cref="string"/> that represents the current <see cref="LogData"/>.
/// </returns>
public override string ToString ()
{
var header = String.Format ("{0}|{1,-5}|", _date, _level);
var method = _caller.GetMethod ();
var type = method.DeclaringType;
#if DEBUG
var lineNum = _caller.GetFileLineNumber ();
var headerAndCaller =
String.Format ("{0}{1}.{2}:{3}|", header, type.Name, method.Name, lineNum);
#else
var headerAndCaller = String.Format ("{0}{1}.{2}|", header, type.Name, method.Name);
#endif
var msgs = _message.Replace ("\r\n", "\n").TrimEnd ('\n').Split ('\n');
if (msgs.Length <= 1)
return String.Format ("{0}{1}", headerAndCaller, _message);
var buff = new StringBuilder (String.Format ("{0}{1}\n", headerAndCaller, msgs[0]), 64);
var fmt = String.Format ("{{0,{0}}}{{1}}\n", header.Length);
for (var i = 1; i < msgs.Length; i++)
buff.AppendFormat (fmt, "", msgs[i]);
buff.Length--;
return buff.ToString ();
}
#endregion
}
}

View File

@ -0,0 +1,63 @@
#region License
/*
* LogLevel.cs
*
* The MIT License
*
* Copyright (c) 2013-2015 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
using System;
namespace WebSocketSharp
{
/// <summary>
/// Specifies the logging level.
/// </summary>
public enum LogLevel
{
/// <summary>
/// Specifies the bottom logging level.
/// </summary>
Trace,
/// <summary>
/// Specifies the 2nd logging level from the bottom.
/// </summary>
Debug,
/// <summary>
/// Specifies the 3rd logging level from the bottom.
/// </summary>
Info,
/// <summary>
/// Specifies the 3rd logging level from the top.
/// </summary>
Warn,
/// <summary>
/// Specifies the 2nd logging level from the top.
/// </summary>
Error,
/// <summary>
/// Specifies the top logging level.
/// </summary>
Fatal
}
}

View File

@ -0,0 +1,330 @@
#region License
/*
* Logger.cs
*
* The MIT License
*
* Copyright (c) 2013-2015 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
using System;
using System.Diagnostics;
using System.IO;
namespace WebSocketSharp
{
/// <summary>
/// Provides a set of methods and properties for logging.
/// </summary>
/// <remarks>
/// <para>
/// If you output a log with lower than the value of the <see cref="Logger.Level"/> property,
/// it cannot be outputted.
/// </para>
/// <para>
/// The default output action writes a log to the standard output stream and the log file
/// if the <see cref="Logger.File"/> property has a valid path to it.
/// </para>
/// <para>
/// If you would like to use the custom output action, you should set
/// the <see cref="Logger.Output"/> property to any <c>Action&lt;LogData, string&gt;</c>
/// delegate.
/// </para>
/// </remarks>
public class Logger
{
#region Private Fields
private volatile string _file;
private volatile LogLevel _level;
private Action<LogData, string> _output;
private object _sync;
#endregion
#region Public Constructors
/// <summary>
/// Initializes a new instance of the <see cref="Logger"/> class.
/// </summary>
/// <remarks>
/// This constructor initializes the current logging level with <see cref="LogLevel.Error"/>.
/// </remarks>
public Logger ()
: this (LogLevel.Error, null, null)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Logger"/> class with
/// the specified logging <paramref name="level"/>.
/// </summary>
/// <param name="level">
/// One of the <see cref="LogLevel"/> enum values.
/// </param>
public Logger (LogLevel level)
: this (level, null, null)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Logger"/> class with
/// the specified logging <paramref name="level"/>, path to the log <paramref name="file"/>,
/// and <paramref name="output"/> action.
/// </summary>
/// <param name="level">
/// One of the <see cref="LogLevel"/> enum values.
/// </param>
/// <param name="file">
/// A <see cref="string"/> that represents the path to the log file.
/// </param>
/// <param name="output">
/// An <c>Action&lt;LogData, string&gt;</c> delegate that references the method(s) used to
/// output a log. A <see cref="string"/> parameter passed to this delegate is
/// <paramref name="file"/>.
/// </param>
public Logger (LogLevel level, string file, Action<LogData, string> output)
{
_level = level;
_file = file;
_output = output ?? defaultOutput;
_sync = new object ();
}
#endregion
#region Public Properties
/// <summary>
/// Gets or sets the current path to the log file.
/// </summary>
/// <value>
/// A <see cref="string"/> that represents the current path to the log file if any.
/// </value>
public string File {
get {
return _file;
}
set {
lock (_sync) {
_file = value;
Warn (
String.Format ("The current path to the log file has been changed to {0}.", _file));
}
}
}
/// <summary>
/// Gets or sets the current logging level.
/// </summary>
/// <remarks>
/// A log with lower than the value of this property cannot be outputted.
/// </remarks>
/// <value>
/// One of the <see cref="LogLevel"/> enum values, specifies the current logging level.
/// </value>
public LogLevel Level {
get {
return _level;
}
set {
lock (_sync) {
_level = value;
Warn (String.Format ("The current logging level has been changed to {0}.", _level));
}
}
}
/// <summary>
/// Gets or sets the current output action used to output a log.
/// </summary>
/// <value>
/// <para>
/// An <c>Action&lt;LogData, string&gt;</c> delegate that references the method(s) used to
/// output a log. A <see cref="string"/> parameter passed to this delegate is the value of
/// the <see cref="Logger.File"/> property.
/// </para>
/// <para>
/// If the value to set is <see langword="null"/>, the current output action is changed to
/// the default output action.
/// </para>
/// </value>
public Action<LogData, string> Output {
get {
return _output;
}
set {
lock (_sync) {
_output = value ?? defaultOutput;
Warn ("The current output action has been changed.");
}
}
}
#endregion
#region Private Methods
private static void defaultOutput (LogData data, string path)
{
var log = data.ToString ();
Console.WriteLine (log);
if (path != null && path.Length > 0)
writeToFile (log, path);
}
private void output (string message, LogLevel level)
{
lock (_sync) {
if (_level > level)
return;
LogData data = null;
try {
data = new LogData (level, new StackFrame (2, true), message);
_output (data, _file);
}
catch (Exception ex) {
data = new LogData (LogLevel.Fatal, new StackFrame (0, true), ex.Message);
Console.WriteLine (data.ToString ());
}
}
}
private static void writeToFile (string value, string path)
{
using (var writer = new StreamWriter (path, true))
using (var syncWriter = TextWriter.Synchronized (writer))
syncWriter.WriteLine (value);
}
#endregion
#region Public Methods
/// <summary>
/// Outputs <paramref name="message"/> as a log with <see cref="LogLevel.Debug"/>.
/// </summary>
/// <remarks>
/// If the current logging level is higher than <see cref="LogLevel.Debug"/>,
/// this method doesn't output <paramref name="message"/> as a log.
/// </remarks>
/// <param name="message">
/// A <see cref="string"/> that represents the message to output as a log.
/// </param>
public void Debug (string message)
{
if (_level > LogLevel.Debug)
return;
output (message, LogLevel.Debug);
}
/// <summary>
/// Outputs <paramref name="message"/> as a log with <see cref="LogLevel.Error"/>.
/// </summary>
/// <remarks>
/// If the current logging level is higher than <see cref="LogLevel.Error"/>,
/// this method doesn't output <paramref name="message"/> as a log.
/// </remarks>
/// <param name="message">
/// A <see cref="string"/> that represents the message to output as a log.
/// </param>
public void Error (string message)
{
if (_level > LogLevel.Error)
return;
output (message, LogLevel.Error);
}
/// <summary>
/// Outputs <paramref name="message"/> as a log with <see cref="LogLevel.Fatal"/>.
/// </summary>
/// <param name="message">
/// A <see cref="string"/> that represents the message to output as a log.
/// </param>
public void Fatal (string message)
{
output (message, LogLevel.Fatal);
}
/// <summary>
/// Outputs <paramref name="message"/> as a log with <see cref="LogLevel.Info"/>.
/// </summary>
/// <remarks>
/// If the current logging level is higher than <see cref="LogLevel.Info"/>,
/// this method doesn't output <paramref name="message"/> as a log.
/// </remarks>
/// <param name="message">
/// A <see cref="string"/> that represents the message to output as a log.
/// </param>
public void Info (string message)
{
if (_level > LogLevel.Info)
return;
output (message, LogLevel.Info);
}
/// <summary>
/// Outputs <paramref name="message"/> as a log with <see cref="LogLevel.Trace"/>.
/// </summary>
/// <remarks>
/// If the current logging level is higher than <see cref="LogLevel.Trace"/>,
/// this method doesn't output <paramref name="message"/> as a log.
/// </remarks>
/// <param name="message">
/// A <see cref="string"/> that represents the message to output as a log.
/// </param>
public void Trace (string message)
{
if (_level > LogLevel.Trace)
return;
output (message, LogLevel.Trace);
}
/// <summary>
/// Outputs <paramref name="message"/> as a log with <see cref="LogLevel.Warn"/>.
/// </summary>
/// <remarks>
/// If the current logging level is higher than <see cref="LogLevel.Warn"/>,
/// this method doesn't output <paramref name="message"/> as a log.
/// </remarks>
/// <param name="message">
/// A <see cref="string"/> that represents the message to output as a log.
/// </param>
public void Warn (string message)
{
if (_level > LogLevel.Warn)
return;
output (message, LogLevel.Warn);
}
#endregion
}
}

View File

@ -0,0 +1,51 @@
#region License
/*
* Mask.cs
*
* The MIT License
*
* Copyright (c) 2012-2015 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
using System;
namespace WebSocketSharp
{
/// <summary>
/// Indicates whether the payload data of a WebSocket frame is masked.
/// </summary>
/// <remarks>
/// The values of this enumeration are defined in
/// <see href="http://tools.ietf.org/html/rfc6455#section-5.2">Section 5.2</see> of RFC 6455.
/// </remarks>
internal enum Mask : byte
{
/// <summary>
/// Equivalent to numeric value 0. Indicates not masked.
/// </summary>
Off = 0x0,
/// <summary>
/// Equivalent to numeric value 1. Indicates masked.
/// </summary>
On = 0x1
}
}

View File

@ -0,0 +1,183 @@
#region License
/*
* MessageEventArgs.cs
*
* The MIT License
*
* Copyright (c) 2012-2016 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
using System;
namespace WebSocketSharp
{
/// <summary>
/// Represents the event data for the <see cref="WebSocket.OnMessage"/> event.
/// </summary>
/// <remarks>
/// <para>
/// That event occurs when the <see cref="WebSocket"/> receives
/// a message or a ping if the <see cref="WebSocket.EmitOnPing"/>
/// property is set to <c>true</c>.
/// </para>
/// <para>
/// If you would like to get the message data, you should access
/// the <see cref="Data"/> or <see cref="RawData"/> property.
/// </para>
/// </remarks>
public class MessageEventArgs : EventArgs
{
#region Private Fields
private string _data;
private bool _dataSet;
private Opcode _opcode;
private byte[] _rawData;
#endregion
#region Internal Constructors
internal MessageEventArgs (WebSocketFrame frame)
{
_opcode = frame.Opcode;
_rawData = frame.PayloadData.ApplicationData;
}
internal MessageEventArgs (Opcode opcode, byte[] rawData)
{
if ((ulong) rawData.LongLength > PayloadData.MaxLength)
throw new WebSocketException (CloseStatusCode.TooBig);
_opcode = opcode;
_rawData = rawData;
}
#endregion
#region Internal Properties
/// <summary>
/// Gets the opcode for the message.
/// </summary>
/// <value>
/// <see cref="Opcode.Text"/>, <see cref="Opcode.Binary"/>,
/// or <see cref="Opcode.Ping"/>.
/// </value>
internal Opcode Opcode {
get {
return _opcode;
}
}
#endregion
#region Public Properties
/// <summary>
/// Gets the message data as a <see cref="string"/>.
/// </summary>
/// <value>
/// A <see cref="string"/> that represents the message data if its type is
/// text or ping and if decoding it to a string has successfully done;
/// otherwise, <see langword="null"/>.
/// </value>
public string Data {
get {
setData ();
return _data;
}
}
/// <summary>
/// Gets a value indicating whether the message type is binary.
/// </summary>
/// <value>
/// <c>true</c> if the message type is binary; otherwise, <c>false</c>.
/// </value>
public bool IsBinary {
get {
return _opcode == Opcode.Binary;
}
}
/// <summary>
/// Gets a value indicating whether the message type is ping.
/// </summary>
/// <value>
/// <c>true</c> if the message type is ping; otherwise, <c>false</c>.
/// </value>
public bool IsPing {
get {
return _opcode == Opcode.Ping;
}
}
/// <summary>
/// Gets a value indicating whether the message type is text.
/// </summary>
/// <value>
/// <c>true</c> if the message type is text; otherwise, <c>false</c>.
/// </value>
public bool IsText {
get {
return _opcode == Opcode.Text;
}
}
/// <summary>
/// Gets the message data as an array of <see cref="byte"/>.
/// </summary>
/// <value>
/// An array of <see cref="byte"/> that represents the message data.
/// </value>
public byte[] RawData {
get {
setData ();
return _rawData;
}
}
#endregion
#region Private Methods
private void setData ()
{
if (_dataSet)
return;
if (_opcode == Opcode.Binary) {
_dataSet = true;
return;
}
string data;
if (_rawData.TryGetUTF8DecodedString (out data))
_data = data;
_dataSet = true;
}
#endregion
}
}

View File

@ -0,0 +1,151 @@
#region License
/*
* AuthenticationBase.cs
*
* The MIT License
*
* Copyright (c) 2014 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
using System;
using System.Collections.Specialized;
using System.Text;
namespace WebSocketSharp.Net
{
internal abstract class AuthenticationBase
{
#region Private Fields
private AuthenticationSchemes _scheme;
#endregion
#region Internal Fields
internal NameValueCollection Parameters;
#endregion
#region Protected Constructors
protected AuthenticationBase (AuthenticationSchemes scheme, NameValueCollection parameters)
{
_scheme = scheme;
Parameters = parameters;
}
#endregion
#region Public Properties
public string Algorithm {
get {
return Parameters["algorithm"];
}
}
public string Nonce {
get {
return Parameters["nonce"];
}
}
public string Opaque {
get {
return Parameters["opaque"];
}
}
public string Qop {
get {
return Parameters["qop"];
}
}
public string Realm {
get {
return Parameters["realm"];
}
}
public AuthenticationSchemes Scheme {
get {
return _scheme;
}
}
#endregion
#region Internal Methods
internal static string CreateNonceValue ()
{
var src = new byte[16];
var rand = new Random ();
rand.NextBytes (src);
var res = new StringBuilder (32);
foreach (var b in src)
res.Append (b.ToString ("x2"));
return res.ToString ();
}
internal static NameValueCollection ParseParameters (string value)
{
var res = new NameValueCollection ();
foreach (var param in value.SplitHeaderValue (',')) {
var i = param.IndexOf ('=');
var name = i > 0 ? param.Substring (0, i).Trim () : null;
var val = i < 0
? param.Trim ().Trim ('"')
: i < param.Length - 1
? param.Substring (i + 1).Trim ().Trim ('"')
: String.Empty;
res.Add (name, val);
}
return res;
}
internal abstract string ToBasicString ();
internal abstract string ToDigestString ();
#endregion
#region Public Methods
public override string ToString ()
{
return _scheme == AuthenticationSchemes.Basic
? ToBasicString ()
: _scheme == AuthenticationSchemes.Digest
? ToDigestString ()
: String.Empty;
}
#endregion
}
}

View File

@ -0,0 +1,146 @@
#region License
/*
* AuthenticationChallenge.cs
*
* The MIT License
*
* Copyright (c) 2013-2014 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
using System;
using System.Collections.Specialized;
using System.Text;
namespace WebSocketSharp.Net
{
internal class AuthenticationChallenge : AuthenticationBase
{
#region Private Constructors
private AuthenticationChallenge (AuthenticationSchemes scheme, NameValueCollection parameters)
: base (scheme, parameters)
{
}
#endregion
#region Internal Constructors
internal AuthenticationChallenge (AuthenticationSchemes scheme, string realm)
: base (scheme, new NameValueCollection ())
{
Parameters["realm"] = realm;
if (scheme == AuthenticationSchemes.Digest) {
Parameters["nonce"] = CreateNonceValue ();
Parameters["algorithm"] = "MD5";
Parameters["qop"] = "auth";
}
}
#endregion
#region Public Properties
public string Domain {
get {
return Parameters["domain"];
}
}
public string Stale {
get {
return Parameters["stale"];
}
}
#endregion
#region Internal Methods
internal static AuthenticationChallenge CreateBasicChallenge (string realm)
{
return new AuthenticationChallenge (AuthenticationSchemes.Basic, realm);
}
internal static AuthenticationChallenge CreateDigestChallenge (string realm)
{
return new AuthenticationChallenge (AuthenticationSchemes.Digest, realm);
}
internal static AuthenticationChallenge Parse (string value)
{
var chal = value.Split (new[] { ' ' }, 2);
if (chal.Length != 2)
return null;
var schm = chal[0].ToLower ();
return schm == "basic"
? new AuthenticationChallenge (
AuthenticationSchemes.Basic, ParseParameters (chal[1]))
: schm == "digest"
? new AuthenticationChallenge (
AuthenticationSchemes.Digest, ParseParameters (chal[1]))
: null;
}
internal override string ToBasicString ()
{
return String.Format ("Basic realm=\"{0}\"", Parameters["realm"]);
}
internal override string ToDigestString ()
{
var output = new StringBuilder (128);
var domain = Parameters["domain"];
if (domain != null)
output.AppendFormat (
"Digest realm=\"{0}\", domain=\"{1}\", nonce=\"{2}\"",
Parameters["realm"],
domain,
Parameters["nonce"]);
else
output.AppendFormat (
"Digest realm=\"{0}\", nonce=\"{1}\"", Parameters["realm"], Parameters["nonce"]);
var opaque = Parameters["opaque"];
if (opaque != null)
output.AppendFormat (", opaque=\"{0}\"", opaque);
var stale = Parameters["stale"];
if (stale != null)
output.AppendFormat (", stale={0}", stale);
var algo = Parameters["algorithm"];
if (algo != null)
output.AppendFormat (", algorithm={0}", algo);
var qop = Parameters["qop"];
if (qop != null)
output.AppendFormat (", qop=\"{0}\"", qop);
return output.ToString ();
}
#endregion
}
}

View File

@ -0,0 +1,323 @@
#region License
/*
* AuthenticationResponse.cs
*
* ParseBasicCredentials is derived from System.Net.HttpListenerContext.cs of Mono
* (http://www.mono-project.com).
*
* The MIT License
*
* Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
* Copyright (c) 2013-2014 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
using System;
using System.Collections.Specialized;
using System.Security.Cryptography;
using System.Security.Principal;
using System.Text;
namespace WebSocketSharp.Net
{
internal class AuthenticationResponse : AuthenticationBase
{
#region Private Fields
private uint _nonceCount;
#endregion
#region Private Constructors
private AuthenticationResponse (AuthenticationSchemes scheme, NameValueCollection parameters)
: base (scheme, parameters)
{
}
#endregion
#region Internal Constructors
internal AuthenticationResponse (NetworkCredential credentials)
: this (AuthenticationSchemes.Basic, new NameValueCollection (), credentials, 0)
{
}
internal AuthenticationResponse (
AuthenticationChallenge challenge, NetworkCredential credentials, uint nonceCount)
: this (challenge.Scheme, challenge.Parameters, credentials, nonceCount)
{
}
internal AuthenticationResponse (
AuthenticationSchemes scheme,
NameValueCollection parameters,
NetworkCredential credentials,
uint nonceCount)
: base (scheme, parameters)
{
Parameters["username"] = credentials.Username;
Parameters["password"] = credentials.Password;
Parameters["uri"] = credentials.Domain;
_nonceCount = nonceCount;
if (scheme == AuthenticationSchemes.Digest)
initAsDigest ();
}
#endregion
#region Internal Properties
internal uint NonceCount {
get {
return _nonceCount < UInt32.MaxValue
? _nonceCount
: 0;
}
}
#endregion
#region Public Properties
public string Cnonce {
get {
return Parameters["cnonce"];
}
}
public string Nc {
get {
return Parameters["nc"];
}
}
public string Password {
get {
return Parameters["password"];
}
}
public string Response {
get {
return Parameters["response"];
}
}
public string Uri {
get {
return Parameters["uri"];
}
}
public string UserName {
get {
return Parameters["username"];
}
}
#endregion
#region Private Methods
private static string createA1 (string username, string password, string realm)
{
return String.Format ("{0}:{1}:{2}", username, realm, password);
}
private static string createA1 (
string username, string password, string realm, string nonce, string cnonce)
{
return String.Format (
"{0}:{1}:{2}", hash (createA1 (username, password, realm)), nonce, cnonce);
}
private static string createA2 (string method, string uri)
{
return String.Format ("{0}:{1}", method, uri);
}
private static string createA2 (string method, string uri, string entity)
{
return String.Format ("{0}:{1}:{2}", method, uri, hash (entity));
}
private static string hash (string value)
{
var src = Encoding.UTF8.GetBytes (value);
var md5 = MD5.Create ();
var hashed = md5.ComputeHash (src);
var res = new StringBuilder (64);
foreach (var b in hashed)
res.Append (b.ToString ("x2"));
return res.ToString ();
}
private void initAsDigest ()
{
var qops = Parameters["qop"];
if (qops != null) {
if (qops.Split (',').Contains (qop => qop.Trim ().ToLower () == "auth")) {
Parameters["qop"] = "auth";
Parameters["cnonce"] = CreateNonceValue ();
Parameters["nc"] = String.Format ("{0:x8}", ++_nonceCount);
}
else {
Parameters["qop"] = null;
}
}
Parameters["method"] = "GET";
Parameters["response"] = CreateRequestDigest (Parameters);
}
#endregion
#region Internal Methods
internal static string CreateRequestDigest (NameValueCollection parameters)
{
var user = parameters["username"];
var pass = parameters["password"];
var realm = parameters["realm"];
var nonce = parameters["nonce"];
var uri = parameters["uri"];
var algo = parameters["algorithm"];
var qop = parameters["qop"];
var cnonce = parameters["cnonce"];
var nc = parameters["nc"];
var method = parameters["method"];
var a1 = algo != null && algo.ToLower () == "md5-sess"
? createA1 (user, pass, realm, nonce, cnonce)
: createA1 (user, pass, realm);
var a2 = qop != null && qop.ToLower () == "auth-int"
? createA2 (method, uri, parameters["entity"])
: createA2 (method, uri);
var secret = hash (a1);
var data = qop != null
? String.Format ("{0}:{1}:{2}:{3}:{4}", nonce, nc, cnonce, qop, hash (a2))
: String.Format ("{0}:{1}", nonce, hash (a2));
return hash (String.Format ("{0}:{1}", secret, data));
}
internal static AuthenticationResponse Parse (string value)
{
try {
var cred = value.Split (new[] { ' ' }, 2);
if (cred.Length != 2)
return null;
var schm = cred[0].ToLower ();
return schm == "basic"
? new AuthenticationResponse (
AuthenticationSchemes.Basic, ParseBasicCredentials (cred[1]))
: schm == "digest"
? new AuthenticationResponse (
AuthenticationSchemes.Digest, ParseParameters (cred[1]))
: null;
}
catch {
}
return null;
}
internal static NameValueCollection ParseBasicCredentials (string value)
{
// Decode the basic-credentials (a Base64 encoded string).
var userPass = Encoding.Default.GetString (Convert.FromBase64String (value));
// The format is [<domain>\]<username>:<password>.
var i = userPass.IndexOf (':');
var user = userPass.Substring (0, i);
var pass = i < userPass.Length - 1 ? userPass.Substring (i + 1) : String.Empty;
// Check if 'domain' exists.
i = user.IndexOf ('\\');
if (i > -1)
user = user.Substring (i + 1);
var res = new NameValueCollection ();
res["username"] = user;
res["password"] = pass;
return res;
}
internal override string ToBasicString ()
{
var userPass = String.Format ("{0}:{1}", Parameters["username"], Parameters["password"]);
var cred = Convert.ToBase64String (Encoding.UTF8.GetBytes (userPass));
return "Basic " + cred;
}
internal override string ToDigestString ()
{
var output = new StringBuilder (256);
output.AppendFormat (
"Digest username=\"{0}\", realm=\"{1}\", nonce=\"{2}\", uri=\"{3}\", response=\"{4}\"",
Parameters["username"],
Parameters["realm"],
Parameters["nonce"],
Parameters["uri"],
Parameters["response"]);
var opaque = Parameters["opaque"];
if (opaque != null)
output.AppendFormat (", opaque=\"{0}\"", opaque);
var algo = Parameters["algorithm"];
if (algo != null)
output.AppendFormat (", algorithm={0}", algo);
var qop = Parameters["qop"];
if (qop != null)
output.AppendFormat (
", qop={0}, cnonce=\"{1}\", nc={2}", qop, Parameters["cnonce"], Parameters["nc"]);
return output.ToString ();
}
#endregion
#region Public Methods
public IIdentity ToIdentity ()
{
var schm = Scheme;
return schm == AuthenticationSchemes.Basic
? new HttpBasicIdentity (Parameters["username"], Parameters["password"]) as IIdentity
: schm == AuthenticationSchemes.Digest
? new HttpDigestIdentity (Parameters)
: null;
}
#endregion
}
}

View File

@ -0,0 +1,66 @@
#region License
/*
* AuthenticationSchemes.cs
*
* This code is derived from AuthenticationSchemes.cs (System.Net) of Mono
* (http://www.mono-project.com).
*
* The MIT License
*
* Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
* Copyright (c) 2012-2016 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
#region Authors
/*
* Authors:
* - Atsushi Enomoto <atsushi@ximian.com>
*/
#endregion
using System;
namespace WebSocketSharp.Net
{
/// <summary>
/// Specifies the scheme for authentication.
/// </summary>
public enum AuthenticationSchemes
{
/// <summary>
/// No authentication is allowed.
/// </summary>
None,
/// <summary>
/// Specifies digest authentication.
/// </summary>
Digest = 1,
/// <summary>
/// Specifies basic authentication.
/// </summary>
Basic = 8,
/// <summary>
/// Specifies anonymous authentication.
/// </summary>
Anonymous = 0x8000
}
}

View File

@ -0,0 +1,91 @@
#region License
/*
* Chunk.cs
*
* This code is derived from ChunkStream.cs (System.Net) of Mono
* (http://www.mono-project.com).
*
* The MIT License
*
* Copyright (c) 2003 Ximian, Inc (http://www.ximian.com)
* Copyright (c) 2014-2015 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
#region Authors
/*
* Authors:
* - Gonzalo Paniagua Javier <gonzalo@ximian.com>
*/
#endregion
using System;
namespace WebSocketSharp.Net
{
internal class Chunk
{
#region Private Fields
private byte[] _data;
private int _offset;
#endregion
#region Public Constructors
public Chunk (byte[] data)
{
_data = data;
}
#endregion
#region Public Properties
public int ReadLeft {
get {
return _data.Length - _offset;
}
}
#endregion
#region Public Methods
public int Read (byte[] buffer, int offset, int count)
{
var left = _data.Length - _offset;
if (left == 0)
return left;
if (count > left)
count = left;
Buffer.BlockCopy (_data, _offset, buffer, offset, count);
_offset += count;
return count;
}
#endregion
}
}

View File

@ -0,0 +1,360 @@
#region License
/*
* ChunkStream.cs
*
* This code is derived from ChunkStream.cs (System.Net) of Mono
* (http://www.mono-project.com).
*
* The MIT License
*
* Copyright (c) 2003 Ximian, Inc (http://www.ximian.com)
* Copyright (c) 2012-2015 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
#region Authors
/*
* Authors:
* - Gonzalo Paniagua Javier <gonzalo@ximian.com>
*/
#endregion
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net;
using System.Text;
namespace WebSocketSharp.Net
{
internal class ChunkStream
{
#region Private Fields
private int _chunkRead;
private int _chunkSize;
private List<Chunk> _chunks;
private bool _gotIt;
private WebHeaderCollection _headers;
private StringBuilder _saved;
private bool _sawCr;
private InputChunkState _state;
private int _trailerState;
#endregion
#region Public Constructors
public ChunkStream (WebHeaderCollection headers)
{
_headers = headers;
_chunkSize = -1;
_chunks = new List<Chunk> ();
_saved = new StringBuilder ();
}
public ChunkStream (byte[] buffer, int offset, int count, WebHeaderCollection headers)
: this (headers)
{
Write (buffer, offset, count);
}
#endregion
#region Internal Properties
internal WebHeaderCollection Headers {
get {
return _headers;
}
}
#endregion
#region Public Properties
public int ChunkLeft {
get {
return _chunkSize - _chunkRead;
}
}
public bool WantMore {
get {
return _state != InputChunkState.End;
}
}
#endregion
#region Private Methods
private int read (byte[] buffer, int offset, int count)
{
var nread = 0;
var cnt = _chunks.Count;
for (var i = 0; i < cnt; i++) {
var chunk = _chunks[i];
if (chunk == null)
continue;
if (chunk.ReadLeft == 0) {
_chunks[i] = null;
continue;
}
nread += chunk.Read (buffer, offset + nread, count - nread);
if (nread == count)
break;
}
return nread;
}
private static string removeChunkExtension (string value)
{
var idx = value.IndexOf (';');
return idx > -1 ? value.Substring (0, idx) : value;
}
private InputChunkState seekCrLf (byte[] buffer, ref int offset, int length)
{
if (!_sawCr) {
if (buffer[offset++] != 13)
throwProtocolViolation ("CR is expected.");
_sawCr = true;
if (offset == length)
return InputChunkState.DataEnded;
}
if (buffer[offset++] != 10)
throwProtocolViolation ("LF is expected.");
return InputChunkState.None;
}
private InputChunkState setChunkSize (byte[] buffer, ref int offset, int length)
{
byte b = 0;
while (offset < length) {
b = buffer[offset++];
if (_sawCr) {
if (b != 10)
throwProtocolViolation ("LF is expected.");
break;
}
if (b == 13) {
_sawCr = true;
continue;
}
if (b == 10)
throwProtocolViolation ("LF is unexpected.");
if (b == 32) // SP
_gotIt = true;
if (!_gotIt)
_saved.Append ((char) b);
if (_saved.Length > 20)
throwProtocolViolation ("The chunk size is too long.");
}
if (!_sawCr || b != 10)
return InputChunkState.None;
_chunkRead = 0;
try {
_chunkSize = Int32.Parse (
removeChunkExtension (_saved.ToString ()), NumberStyles.HexNumber);
}
catch {
throwProtocolViolation ("The chunk size cannot be parsed.");
}
if (_chunkSize == 0) {
_trailerState = 2;
return InputChunkState.Trailer;
}
return InputChunkState.Data;
}
private InputChunkState setTrailer (byte[] buffer, ref int offset, int length)
{
// Check if no trailer.
if (_trailerState == 2 && buffer[offset] == 13 && _saved.Length == 0) {
offset++;
if (offset < length && buffer[offset] == 10) {
offset++;
return InputChunkState.End;
}
offset--;
}
while (offset < length && _trailerState < 4) {
var b = buffer[offset++];
_saved.Append ((char) b);
if (_saved.Length > 4196)
throwProtocolViolation ("The trailer is too long.");
if (_trailerState == 1 || _trailerState == 3) {
if (b != 10)
throwProtocolViolation ("LF is expected.");
_trailerState++;
continue;
}
if (b == 13) {
_trailerState++;
continue;
}
if (b == 10)
throwProtocolViolation ("LF is unexpected.");
_trailerState = 0;
}
if (_trailerState < 4)
return InputChunkState.Trailer;
_saved.Length -= 2;
var reader = new StringReader (_saved.ToString ());
string line;
while ((line = reader.ReadLine ()) != null && line.Length > 0)
_headers.Add (line);
return InputChunkState.End;
}
private static void throwProtocolViolation (string message)
{
throw new WebException (message, null, WebExceptionStatus.ServerProtocolViolation, null);
}
private void write (byte[] buffer, ref int offset, int length)
{
if (_state == InputChunkState.End)
throwProtocolViolation ("The chunks were ended.");
if (_state == InputChunkState.None) {
_state = setChunkSize (buffer, ref offset, length);
if (_state == InputChunkState.None)
return;
_saved.Length = 0;
_sawCr = false;
_gotIt = false;
}
if (_state == InputChunkState.Data && offset < length) {
_state = writeData (buffer, ref offset, length);
if (_state == InputChunkState.Data)
return;
}
if (_state == InputChunkState.DataEnded && offset < length) {
_state = seekCrLf (buffer, ref offset, length);
if (_state == InputChunkState.DataEnded)
return;
_sawCr = false;
}
if (_state == InputChunkState.Trailer && offset < length) {
_state = setTrailer (buffer, ref offset, length);
if (_state == InputChunkState.Trailer)
return;
_saved.Length = 0;
}
if (offset < length)
write (buffer, ref offset, length);
}
private InputChunkState writeData (byte[] buffer, ref int offset, int length)
{
var cnt = length - offset;
var left = _chunkSize - _chunkRead;
if (cnt > left)
cnt = left;
var data = new byte[cnt];
Buffer.BlockCopy (buffer, offset, data, 0, cnt);
_chunks.Add (new Chunk (data));
offset += cnt;
_chunkRead += cnt;
return _chunkRead == _chunkSize ? InputChunkState.DataEnded : InputChunkState.Data;
}
#endregion
#region Internal Methods
internal void ResetBuffer ()
{
_chunkRead = 0;
_chunkSize = -1;
_chunks.Clear ();
}
internal int WriteAndReadBack (byte[] buffer, int offset, int writeCount, int readCount)
{
Write (buffer, offset, writeCount);
return Read (buffer, offset, readCount);
}
#endregion
#region Public Methods
public int Read (byte[] buffer, int offset, int count)
{
if (count <= 0)
return 0;
return read (buffer, offset, count);
}
public void Write (byte[] buffer, int offset, int count)
{
if (count <= 0)
return;
write (buffer, ref offset, offset + count);
}
#endregion
}
}

View File

@ -0,0 +1,211 @@
#region License
/*
* ChunkedRequestStream.cs
*
* This code is derived from ChunkedInputStream.cs (System.Net) of Mono
* (http://www.mono-project.com).
*
* The MIT License
*
* Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
* Copyright (c) 2012-2015 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
#region Authors
/*
* Authors:
* - Gonzalo Paniagua Javier <gonzalo@novell.com>
*/
#endregion
using System;
using System.IO;
namespace WebSocketSharp.Net
{
internal class ChunkedRequestStream : RequestStream
{
#region Private Fields
private const int _bufferLength = 8192;
private HttpListenerContext _context;
private ChunkStream _decoder;
private bool _disposed;
private bool _noMoreData;
#endregion
#region Internal Constructors
internal ChunkedRequestStream (
Stream stream, byte[] buffer, int offset, int count, HttpListenerContext context)
: base (stream, buffer, offset, count)
{
_context = context;
_decoder = new ChunkStream ((WebHeaderCollection) context.Request.Headers);
}
#endregion
#region Internal Properties
internal ChunkStream Decoder {
get {
return _decoder;
}
set {
_decoder = value;
}
}
#endregion
#region Private Methods
private void onRead (IAsyncResult asyncResult)
{
var rstate = (ReadBufferState) asyncResult.AsyncState;
var ares = rstate.AsyncResult;
try {
var nread = base.EndRead (asyncResult);
_decoder.Write (ares.Buffer, ares.Offset, nread);
nread = _decoder.Read (rstate.Buffer, rstate.Offset, rstate.Count);
rstate.Offset += nread;
rstate.Count -= nread;
if (rstate.Count == 0 || !_decoder.WantMore || nread == 0) {
_noMoreData = !_decoder.WantMore && nread == 0;
ares.Count = rstate.InitialCount - rstate.Count;
ares.Complete ();
return;
}
ares.Offset = 0;
ares.Count = Math.Min (_bufferLength, _decoder.ChunkLeft + 6);
base.BeginRead (ares.Buffer, ares.Offset, ares.Count, onRead, rstate);
}
catch (Exception ex) {
_context.Connection.SendError (ex.Message, 400);
ares.Complete (ex);
}
}
#endregion
#region Public Methods
public override IAsyncResult BeginRead (
byte[] buffer, int offset, int count, AsyncCallback callback, object state)
{
if (_disposed)
throw new ObjectDisposedException (GetType ().ToString ());
if (buffer == null)
throw new ArgumentNullException ("buffer");
if (offset < 0)
throw new ArgumentOutOfRangeException ("offset", "A negative value.");
if (count < 0)
throw new ArgumentOutOfRangeException ("count", "A negative value.");
var len = buffer.Length;
if (offset + count > len)
throw new ArgumentException (
"The sum of 'offset' and 'count' is greater than 'buffer' length.");
var ares = new HttpStreamAsyncResult (callback, state);
if (_noMoreData) {
ares.Complete ();
return ares;
}
var nread = _decoder.Read (buffer, offset, count);
offset += nread;
count -= nread;
if (count == 0) {
// Got all we wanted, no need to bother the decoder yet.
ares.Count = nread;
ares.Complete ();
return ares;
}
if (!_decoder.WantMore) {
_noMoreData = nread == 0;
ares.Count = nread;
ares.Complete ();
return ares;
}
ares.Buffer = new byte[_bufferLength];
ares.Offset = 0;
ares.Count = _bufferLength;
var rstate = new ReadBufferState (buffer, offset, count, ares);
rstate.InitialCount += nread;
base.BeginRead (ares.Buffer, ares.Offset, ares.Count, onRead, rstate);
return ares;
}
public override void Close ()
{
if (_disposed)
return;
_disposed = true;
base.Close ();
}
public override int EndRead (IAsyncResult asyncResult)
{
if (_disposed)
throw new ObjectDisposedException (GetType ().ToString ());
if (asyncResult == null)
throw new ArgumentNullException ("asyncResult");
var ares = asyncResult as HttpStreamAsyncResult;
if (ares == null)
throw new ArgumentException ("A wrong IAsyncResult.", "asyncResult");
if (!ares.IsCompleted)
ares.AsyncWaitHandle.WaitOne ();
if (ares.HasException)
throw new HttpListenerException (400, "I/O operation aborted.");
return ares.Count;
}
public override int Read (byte[] buffer, int offset, int count)
{
var ares = BeginRead (buffer, offset, count, null, null);
return EndRead (ares);
}
#endregion
}
}

View File

@ -0,0 +1,291 @@
#region License
/*
* ClientSslConfiguration.cs
*
* The MIT License
*
* Copyright (c) 2014 liryna
* Copyright (c) 2014-2017 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
#region Authors
/*
* Authors:
* - Liryna <liryna.stark@gmail.com>
*/
#endregion
using System;
using System.Net.Security;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
namespace WebSocketSharp.Net
{
/// <summary>
/// Stores the parameters for the <see cref="SslStream"/> used by clients.
/// </summary>
public class ClientSslConfiguration
{
#region Private Fields
private bool _checkCertRevocation;
private LocalCertificateSelectionCallback _clientCertSelectionCallback;
private X509CertificateCollection _clientCerts;
private SslProtocols _enabledSslProtocols;
private RemoteCertificateValidationCallback _serverCertValidationCallback;
private string _targetHost;
#endregion
#region Public Constructors
/// <summary>
/// Initializes a new instance of the <see cref="ClientSslConfiguration"/> class.
/// </summary>
public ClientSslConfiguration ()
{
_enabledSslProtocols = SslProtocols.Default;
}
/// <summary>
/// Initializes a new instance of the <see cref="ClientSslConfiguration"/> class
/// with the specified <paramref name="targetHost"/>.
/// </summary>
/// <param name="targetHost">
/// A <see cref="string"/> that represents the target host server name.
/// </param>
public ClientSslConfiguration (string targetHost)
{
_targetHost = targetHost;
_enabledSslProtocols = SslProtocols.Default;
}
/// <summary>
/// Copies the parameters from the specified <paramref name="configuration"/> to
/// a new instance of the <see cref="ClientSslConfiguration"/> class.
/// </summary>
/// <param name="configuration">
/// A <see cref="ClientSslConfiguration"/> from which to copy.
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="configuration"/> is <see langword="null"/>.
/// </exception>
public ClientSslConfiguration (ClientSslConfiguration configuration)
{
if (configuration == null)
throw new ArgumentNullException ("configuration");
_checkCertRevocation = configuration._checkCertRevocation;
_clientCertSelectionCallback = configuration._clientCertSelectionCallback;
_clientCerts = configuration._clientCerts;
_enabledSslProtocols = configuration._enabledSslProtocols;
_serverCertValidationCallback = configuration._serverCertValidationCallback;
_targetHost = configuration._targetHost;
}
#endregion
#region Public Properties
/// <summary>
/// Gets or sets a value indicating whether the certificate revocation
/// list is checked during authentication.
/// </summary>
/// <value>
/// <para>
/// <c>true</c> if the certificate revocation list is checked during
/// authentication; otherwise, <c>false</c>.
/// </para>
/// <para>
/// The default value is <c>false</c>.
/// </para>
/// </value>
public bool CheckCertificateRevocation {
get {
return _checkCertRevocation;
}
set {
_checkCertRevocation = value;
}
}
/// <summary>
/// Gets or sets the certificates from which to select one to
/// supply to the server.
/// </summary>
/// <value>
/// <para>
/// A <see cref="X509CertificateCollection"/> or <see langword="null"/>.
/// </para>
/// <para>
/// That collection contains client certificates from which to select.
/// </para>
/// <para>
/// The default value is <see langword="null"/>.
/// </para>
/// </value>
public X509CertificateCollection ClientCertificates {
get {
return _clientCerts;
}
set {
_clientCerts = value;
}
}
/// <summary>
/// Gets or sets the callback used to select the certificate to
/// supply to the server.
/// </summary>
/// <remarks>
/// No certificate is supplied if the callback returns
/// <see langword="null"/>.
/// </remarks>
/// <value>
/// <para>
/// A <see cref="LocalCertificateSelectionCallback"/> delegate that
/// invokes the method called for selecting the certificate.
/// </para>
/// <para>
/// The default value is a delegate that invokes a method that
/// only returns <see langword="null"/>.
/// </para>
/// </value>
public LocalCertificateSelectionCallback ClientCertificateSelectionCallback {
get {
if (_clientCertSelectionCallback == null)
_clientCertSelectionCallback = defaultSelectClientCertificate;
return _clientCertSelectionCallback;
}
set {
_clientCertSelectionCallback = value;
}
}
/// <summary>
/// Gets or sets the protocols used for authentication.
/// </summary>
/// <value>
/// <para>
/// The <see cref="SslProtocols"/> enum values that represent
/// the protocols used for authentication.
/// </para>
/// <para>
/// The default value is <see cref="SslProtocols.Default"/>.
/// </para>
/// </value>
public SslProtocols EnabledSslProtocols {
get {
return _enabledSslProtocols;
}
set {
_enabledSslProtocols = value;
}
}
/// <summary>
/// Gets or sets the callback used to validate the certificate
/// supplied by the server.
/// </summary>
/// <remarks>
/// The certificate is valid if the callback returns <c>true</c>.
/// </remarks>
/// <value>
/// <para>
/// A <see cref="RemoteCertificateValidationCallback"/> delegate that
/// invokes the method called for validating the certificate.
/// </para>
/// <para>
/// The default value is a delegate that invokes a method that
/// only returns <c>true</c>.
/// </para>
/// </value>
public RemoteCertificateValidationCallback ServerCertificateValidationCallback {
get {
if (_serverCertValidationCallback == null)
_serverCertValidationCallback = defaultValidateServerCertificate;
return _serverCertValidationCallback;
}
set {
_serverCertValidationCallback = value;
}
}
/// <summary>
/// Gets or sets the target host server name.
/// </summary>
/// <value>
/// <para>
/// A <see cref="string"/> or <see langword="null"/>
/// if not specified.
/// </para>
/// <para>
/// That string represents the name of the server that
/// will share a secure connection with a client.
/// </para>
/// </value>
public string TargetHost {
get {
return _targetHost;
}
set {
_targetHost = value;
}
}
#endregion
#region Private Methods
private static X509Certificate defaultSelectClientCertificate (
object sender,
string targetHost,
X509CertificateCollection clientCertificates,
X509Certificate serverCertificate,
string[] acceptableIssuers
)
{
return null;
}
private static bool defaultValidateServerCertificate (
object sender,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors sslPolicyErrors
)
{
return true;
}
#endregion
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,821 @@
#region License
/*
* CookieCollection.cs
*
* This code is derived from CookieCollection.cs (System.Net) of Mono
* (http://www.mono-project.com).
*
* The MIT License
*
* Copyright (c) 2004,2009 Novell, Inc. (http://www.novell.com)
* Copyright (c) 2012-2019 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
#region Authors
/*
* Authors:
* - Lawrence Pit <loz@cable.a2000.nl>
* - Gonzalo Paniagua Javier <gonzalo@ximian.com>
* - Sebastien Pouliot <sebastien@ximian.com>
*/
#endregion
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
namespace WebSocketSharp.Net
{
/// <summary>
/// Provides a collection of instances of the <see cref="Cookie"/> class.
/// </summary>
[Serializable]
public class CookieCollection : ICollection<Cookie>
{
#region Private Fields
private List<Cookie> _list;
private bool _readOnly;
private object _sync;
#endregion
#region Public Constructors
/// <summary>
/// Initializes a new instance of the <see cref="CookieCollection"/> class.
/// </summary>
public CookieCollection ()
{
_list = new List<Cookie> ();
_sync = ((ICollection) _list).SyncRoot;
}
#endregion
#region Internal Properties
internal IList<Cookie> List {
get {
return _list;
}
}
internal IEnumerable<Cookie> Sorted {
get {
var list = new List<Cookie> (_list);
if (list.Count > 1)
list.Sort (compareForSorted);
return list;
}
}
#endregion
#region Public Properties
/// <summary>
/// Gets the number of cookies in the collection.
/// </summary>
/// <value>
/// An <see cref="int"/> that represents the number of cookies in
/// the collection.
/// </value>
public int Count {
get {
return _list.Count;
}
}
/// <summary>
/// Gets a value indicating whether the collection is read-only.
/// </summary>
/// <value>
/// <para>
/// <c>true</c> if the collection is read-only; otherwise, <c>false</c>.
/// </para>
/// <para>
/// The default value is <c>false</c>.
/// </para>
/// </value>
public bool IsReadOnly {
get {
return _readOnly;
}
internal set {
_readOnly = value;
}
}
/// <summary>
/// Gets a value indicating whether the access to the collection is
/// thread safe.
/// </summary>
/// <value>
/// <para>
/// <c>true</c> if the access to the collection is thread safe;
/// otherwise, <c>false</c>.
/// </para>
/// <para>
/// The default value is <c>false</c>.
/// </para>
/// </value>
public bool IsSynchronized {
get {
return false;
}
}
/// <summary>
/// Gets the cookie at the specified index from the collection.
/// </summary>
/// <value>
/// A <see cref="Cookie"/> at the specified index in the collection.
/// </value>
/// <param name="index">
/// An <see cref="int"/> that specifies the zero-based index of the cookie
/// to find.
/// </param>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="index"/> is out of allowable range for the collection.
/// </exception>
public Cookie this[int index] {
get {
if (index < 0 || index >= _list.Count)
throw new ArgumentOutOfRangeException ("index");
return _list[index];
}
}
/// <summary>
/// Gets the cookie with the specified name from the collection.
/// </summary>
/// <value>
/// <para>
/// A <see cref="Cookie"/> with the specified name in the collection.
/// </para>
/// <para>
/// <see langword="null"/> if not found.
/// </para>
/// </value>
/// <param name="name">
/// A <see cref="string"/> that specifies the name of the cookie to find.
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="name"/> is <see langword="null"/>.
/// </exception>
public Cookie this[string name] {
get {
if (name == null)
throw new ArgumentNullException ("name");
var caseInsensitive = StringComparison.InvariantCultureIgnoreCase;
foreach (var cookie in Sorted) {
if (cookie.Name.Equals (name, caseInsensitive))
return cookie;
}
return null;
}
}
/// <summary>
/// Gets an object used to synchronize access to the collection.
/// </summary>
/// <value>
/// An <see cref="object"/> used to synchronize access to the collection.
/// </value>
public object SyncRoot {
get {
return _sync;
}
}
#endregion
#region Private Methods
private void add (Cookie cookie)
{
var idx = search (cookie);
if (idx == -1) {
_list.Add (cookie);
return;
}
_list[idx] = cookie;
}
private static int compareForSort (Cookie x, Cookie y)
{
return (x.Name.Length + x.Value.Length)
- (y.Name.Length + y.Value.Length);
}
private static int compareForSorted (Cookie x, Cookie y)
{
var ret = x.Version - y.Version;
return ret != 0
? ret
: (ret = x.Name.CompareTo (y.Name)) != 0
? ret
: y.Path.Length - x.Path.Length;
}
private static CookieCollection parseRequest (string value)
{
var ret = new CookieCollection ();
Cookie cookie = null;
var ver = 0;
var caseInsensitive = StringComparison.InvariantCultureIgnoreCase;
var pairs = value.SplitHeaderValue (',', ';').ToList ();
for (var i = 0; i < pairs.Count; i++) {
var pair = pairs[i].Trim ();
if (pair.Length == 0)
continue;
var idx = pair.IndexOf ('=');
if (idx == -1) {
if (cookie == null)
continue;
if (pair.Equals ("$port", caseInsensitive)) {
cookie.Port = "\"\"";
continue;
}
continue;
}
if (idx == 0) {
if (cookie != null) {
ret.add (cookie);
cookie = null;
}
continue;
}
var name = pair.Substring (0, idx).TrimEnd (' ');
var val = idx < pair.Length - 1
? pair.Substring (idx + 1).TrimStart (' ')
: String.Empty;
if (name.Equals ("$version", caseInsensitive)) {
if (val.Length == 0)
continue;
int num;
if (!Int32.TryParse (val.Unquote (), out num))
continue;
ver = num;
continue;
}
if (name.Equals ("$path", caseInsensitive)) {
if (cookie == null)
continue;
if (val.Length == 0)
continue;
cookie.Path = val;
continue;
}
if (name.Equals ("$domain", caseInsensitive)) {
if (cookie == null)
continue;
if (val.Length == 0)
continue;
cookie.Domain = val;
continue;
}
if (name.Equals ("$port", caseInsensitive)) {
if (cookie == null)
continue;
if (val.Length == 0)
continue;
cookie.Port = val;
continue;
}
if (cookie != null)
ret.add (cookie);
if (!Cookie.TryCreate (name, val, out cookie))
continue;
if (ver != 0)
cookie.Version = ver;
}
if (cookie != null)
ret.add (cookie);
return ret;
}
private static CookieCollection parseResponse (string value)
{
var ret = new CookieCollection ();
Cookie cookie = null;
var caseInsensitive = StringComparison.InvariantCultureIgnoreCase;
var pairs = value.SplitHeaderValue (',', ';').ToList ();
for (var i = 0; i < pairs.Count; i++) {
var pair = pairs[i].Trim ();
if (pair.Length == 0)
continue;
var idx = pair.IndexOf ('=');
if (idx == -1) {
if (cookie == null)
continue;
if (pair.Equals ("port", caseInsensitive)) {
cookie.Port = "\"\"";
continue;
}
if (pair.Equals ("discard", caseInsensitive)) {
cookie.Discard = true;
continue;
}
if (pair.Equals ("secure", caseInsensitive)) {
cookie.Secure = true;
continue;
}
if (pair.Equals ("httponly", caseInsensitive)) {
cookie.HttpOnly = true;
continue;
}
continue;
}
if (idx == 0) {
if (cookie != null) {
ret.add (cookie);
cookie = null;
}
continue;
}
var name = pair.Substring (0, idx).TrimEnd (' ');
var val = idx < pair.Length - 1
? pair.Substring (idx + 1).TrimStart (' ')
: String.Empty;
if (name.Equals ("version", caseInsensitive)) {
if (cookie == null)
continue;
if (val.Length == 0)
continue;
int num;
if (!Int32.TryParse (val.Unquote (), out num))
continue;
cookie.Version = num;
continue;
}
if (name.Equals ("expires", caseInsensitive)) {
if (val.Length == 0)
continue;
if (i == pairs.Count - 1)
break;
i++;
if (cookie == null)
continue;
if (cookie.Expires != DateTime.MinValue)
continue;
var buff = new StringBuilder (val, 32);
buff.AppendFormat (", {0}", pairs[i].Trim ());
DateTime expires;
if (
!DateTime.TryParseExact (
buff.ToString (),
new[] { "ddd, dd'-'MMM'-'yyyy HH':'mm':'ss 'GMT'", "r" },
CultureInfo.CreateSpecificCulture ("en-US"),
DateTimeStyles.AdjustToUniversal
| DateTimeStyles.AssumeUniversal,
out expires
)
)
continue;
cookie.Expires = expires.ToLocalTime ();
continue;
}
if (name.Equals ("max-age", caseInsensitive)) {
if (cookie == null)
continue;
if (val.Length == 0)
continue;
int num;
if (!Int32.TryParse (val.Unquote (), out num))
continue;
cookie.MaxAge = num;
continue;
}
if (name.Equals ("path", caseInsensitive)) {
if (cookie == null)
continue;
if (val.Length == 0)
continue;
cookie.Path = val;
continue;
}
if (name.Equals ("domain", caseInsensitive)) {
if (cookie == null)
continue;
if (val.Length == 0)
continue;
cookie.Domain = val;
continue;
}
if (name.Equals ("port", caseInsensitive)) {
if (cookie == null)
continue;
if (val.Length == 0)
continue;
cookie.Port = val;
continue;
}
if (name.Equals ("comment", caseInsensitive)) {
if (cookie == null)
continue;
if (val.Length == 0)
continue;
cookie.Comment = urlDecode (val, Encoding.UTF8);
continue;
}
if (name.Equals ("commenturl", caseInsensitive)) {
if (cookie == null)
continue;
if (val.Length == 0)
continue;
cookie.CommentUri = val.Unquote ().ToUri ();
continue;
}
if (name.Equals ("samesite", caseInsensitive)) {
if (cookie == null)
continue;
if (val.Length == 0)
continue;
cookie.SameSite = val.Unquote ();
continue;
}
if (cookie != null)
ret.add (cookie);
Cookie.TryCreate (name, val, out cookie);
}
if (cookie != null)
ret.add (cookie);
return ret;
}
private int search (Cookie cookie)
{
for (var i = _list.Count - 1; i >= 0; i--) {
if (_list[i].EqualsWithoutValue (cookie))
return i;
}
return -1;
}
private static string urlDecode (string s, Encoding encoding)
{
if (s.IndexOfAny (new[] { '%', '+' }) == -1)
return s;
try {
return HttpUtility.UrlDecode (s, encoding);
}
catch {
return null;
}
}
#endregion
#region Internal Methods
internal static CookieCollection Parse (string value, bool response)
{
try {
return response
? parseResponse (value)
: parseRequest (value);
}
catch (Exception ex) {
throw new CookieException ("It could not be parsed.", ex);
}
}
internal void SetOrRemove (Cookie cookie)
{
var idx = search (cookie);
if (idx == -1) {
if (cookie.Expired)
return;
_list.Add (cookie);
return;
}
if (cookie.Expired) {
_list.RemoveAt (idx);
return;
}
_list[idx] = cookie;
}
internal void SetOrRemove (CookieCollection cookies)
{
foreach (var cookie in cookies._list)
SetOrRemove (cookie);
}
internal void Sort ()
{
if (_list.Count > 1)
_list.Sort (compareForSort);
}
#endregion
#region Public Methods
/// <summary>
/// Adds the specified cookie to the collection.
/// </summary>
/// <param name="cookie">
/// A <see cref="Cookie"/> to add.
/// </param>
/// <exception cref="InvalidOperationException">
/// The collection is read-only.
/// </exception>
/// <exception cref="ArgumentNullException">
/// <paramref name="cookie"/> is <see langword="null"/>.
/// </exception>
public void Add (Cookie cookie)
{
if (_readOnly) {
var msg = "The collection is read-only.";
throw new InvalidOperationException (msg);
}
if (cookie == null)
throw new ArgumentNullException ("cookie");
add (cookie);
}
/// <summary>
/// Adds the specified cookies to the collection.
/// </summary>
/// <param name="cookies">
/// A <see cref="CookieCollection"/> that contains the cookies to add.
/// </param>
/// <exception cref="InvalidOperationException">
/// The collection is read-only.
/// </exception>
/// <exception cref="ArgumentNullException">
/// <paramref name="cookies"/> is <see langword="null"/>.
/// </exception>
public void Add (CookieCollection cookies)
{
if (_readOnly) {
var msg = "The collection is read-only.";
throw new InvalidOperationException (msg);
}
if (cookies == null)
throw new ArgumentNullException ("cookies");
foreach (var cookie in cookies._list)
add (cookie);
}
/// <summary>
/// Removes all cookies from the collection.
/// </summary>
/// <exception cref="InvalidOperationException">
/// The collection is read-only.
/// </exception>
public void Clear ()
{
if (_readOnly) {
var msg = "The collection is read-only.";
throw new InvalidOperationException (msg);
}
_list.Clear ();
}
/// <summary>
/// Determines whether the collection contains the specified cookie.
/// </summary>
/// <returns>
/// <c>true</c> if the cookie is found in the collection; otherwise,
/// <c>false</c>.
/// </returns>
/// <param name="cookie">
/// A <see cref="Cookie"/> to find.
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="cookie"/> is <see langword="null"/>.
/// </exception>
public bool Contains (Cookie cookie)
{
if (cookie == null)
throw new ArgumentNullException ("cookie");
return search (cookie) > -1;
}
/// <summary>
/// Copies the elements of the collection to the specified array,
/// starting at the specified index.
/// </summary>
/// <param name="array">
/// An array of <see cref="Cookie"/> that specifies the destination of
/// the elements copied from the collection.
/// </param>
/// <param name="index">
/// An <see cref="int"/> that specifies the zero-based index in
/// the array at which copying starts.
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="array"/> is <see langword="null"/>.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="index"/> is less than zero.
/// </exception>
/// <exception cref="ArgumentException">
/// The space from <paramref name="index"/> to the end of
/// <paramref name="array"/> is not enough to copy to.
/// </exception>
public void CopyTo (Cookie[] array, int index)
{
if (array == null)
throw new ArgumentNullException ("array");
if (index < 0)
throw new ArgumentOutOfRangeException ("index", "Less than zero.");
if (array.Length - index < _list.Count) {
var msg = "The available space of the array is not enough to copy to.";
throw new ArgumentException (msg);
}
_list.CopyTo (array, index);
}
/// <summary>
/// Gets the enumerator that iterates through the collection.
/// </summary>
/// <returns>
/// An <see cref="T:System.Collections.Generic.IEnumerator{Cookie}"/>
/// instance that can be used to iterate through the collection.
/// </returns>
public IEnumerator<Cookie> GetEnumerator ()
{
return _list.GetEnumerator ();
}
/// <summary>
/// Removes the specified cookie from the collection.
/// </summary>
/// <returns>
/// <para>
/// <c>true</c> if the cookie is successfully removed; otherwise,
/// <c>false</c>.
/// </para>
/// <para>
/// <c>false</c> if the cookie is not found in the collection.
/// </para>
/// </returns>
/// <param name="cookie">
/// A <see cref="Cookie"/> to remove.
/// </param>
/// <exception cref="InvalidOperationException">
/// The collection is read-only.
/// </exception>
/// <exception cref="ArgumentNullException">
/// <paramref name="cookie"/> is <see langword="null"/>.
/// </exception>
public bool Remove (Cookie cookie)
{
if (_readOnly) {
var msg = "The collection is read-only.";
throw new InvalidOperationException (msg);
}
if (cookie == null)
throw new ArgumentNullException ("cookie");
var idx = search (cookie);
if (idx == -1)
return false;
_list.RemoveAt (idx);
return true;
}
#endregion
#region Explicit Interface Implementations
/// <summary>
/// Gets the enumerator that iterates through the collection.
/// </summary>
/// <returns>
/// An <see cref="IEnumerator"/> instance that can be used to iterate
/// through the collection.
/// </returns>
IEnumerator IEnumerable.GetEnumerator ()
{
return _list.GetEnumerator ();
}
#endregion
}
}

View File

@ -0,0 +1,165 @@
#region License
/*
* CookieException.cs
*
* This code is derived from CookieException.cs (System.Net) of Mono
* (http://www.mono-project.com).
*
* The MIT License
*
* Copyright (c) 2012-2019 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
#region Authors
/*
* Authors:
* - Lawrence Pit <loz@cable.a2000.nl>
*/
#endregion
using System;
using System.Runtime.Serialization;
using System.Security.Permissions;
namespace WebSocketSharp.Net
{
/// <summary>
/// The exception that is thrown when a <see cref="Cookie"/> gets an error.
/// </summary>
[Serializable]
public class CookieException : FormatException, ISerializable
{
#region Internal Constructors
internal CookieException (string message)
: base (message)
{
}
internal CookieException (string message, Exception innerException)
: base (message, innerException)
{
}
#endregion
#region Protected Constructors
/// <summary>
/// Initializes a new instance of the <see cref="CookieException"/> class
/// with the serialized data.
/// </summary>
/// <param name="serializationInfo">
/// A <see cref="SerializationInfo"/> that holds the serialized object data.
/// </param>
/// <param name="streamingContext">
/// A <see cref="StreamingContext"/> that specifies the source for
/// the deserialization.
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="serializationInfo"/> is <see langword="null"/>.
/// </exception>
protected CookieException (
SerializationInfo serializationInfo, StreamingContext streamingContext
)
: base (serializationInfo, streamingContext)
{
}
#endregion
#region Public Constructors
/// <summary>
/// Initializes a new instance of the <see cref="CookieException"/> class.
/// </summary>
public CookieException ()
: base ()
{
}
#endregion
#region Public Methods
/// <summary>
/// Populates the specified <see cref="SerializationInfo"/> instance with
/// the data needed to serialize the current instance.
/// </summary>
/// <param name="serializationInfo">
/// A <see cref="SerializationInfo"/> that holds the serialized object data.
/// </param>
/// <param name="streamingContext">
/// A <see cref="StreamingContext"/> that specifies the destination for
/// the serialization.
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="serializationInfo"/> is <see langword="null"/>.
/// </exception>
[
SecurityPermission (
SecurityAction.LinkDemand,
Flags = SecurityPermissionFlag.SerializationFormatter
)
]
public override void GetObjectData (
SerializationInfo serializationInfo, StreamingContext streamingContext
)
{
base.GetObjectData (serializationInfo, streamingContext);
}
#endregion
#region Explicit Interface Implementation
/// <summary>
/// Populates the specified <see cref="SerializationInfo"/> instance with
/// the data needed to serialize the current instance.
/// </summary>
/// <param name="serializationInfo">
/// A <see cref="SerializationInfo"/> that holds the serialized object data.
/// </param>
/// <param name="streamingContext">
/// A <see cref="StreamingContext"/> that specifies the destination for
/// the serialization.
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="serializationInfo"/> is <see langword="null"/>.
/// </exception>
[
SecurityPermission (
SecurityAction.LinkDemand,
Flags = SecurityPermissionFlag.SerializationFormatter,
SerializationFormatter = true
)
]
void ISerializable.GetObjectData (
SerializationInfo serializationInfo, StreamingContext streamingContext
)
{
base.GetObjectData (serializationInfo, streamingContext);
}
#endregion
}
}

View File

@ -0,0 +1,515 @@
#region License
/*
* EndPointListener.cs
*
* This code is derived from EndPointListener.cs (System.Net) of Mono
* (http://www.mono-project.com).
*
* The MIT License
*
* Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
* Copyright (c) 2012-2016 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
#region Authors
/*
* Authors:
* - Gonzalo Paniagua Javier <gonzalo@novell.com>
*/
#endregion
#region Contributors
/*
* Contributors:
* - Liryna <liryna.stark@gmail.com>
* - Nicholas Devenish
*/
#endregion
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
namespace WebSocketSharp.Net
{
internal sealed class EndPointListener
{
#region Private Fields
private List<HttpListenerPrefix> _all; // host == '+'
private static readonly string _defaultCertFolderPath;
private IPEndPoint _endpoint;
private Dictionary<HttpListenerPrefix, HttpListener> _prefixes;
private bool _secure;
private Socket _socket;
private ServerSslConfiguration _sslConfig;
private List<HttpListenerPrefix> _unhandled; // host == '*'
private Dictionary<HttpConnection, HttpConnection> _unregistered;
private object _unregisteredSync;
#endregion
#region Static Constructor
static EndPointListener ()
{
_defaultCertFolderPath =
Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData);
}
#endregion
#region Internal Constructors
internal EndPointListener (
IPEndPoint endpoint,
bool secure,
string certificateFolderPath,
ServerSslConfiguration sslConfig,
bool reuseAddress
)
{
if (secure) {
var cert =
getCertificate (endpoint.Port, certificateFolderPath, sslConfig.ServerCertificate);
if (cert == null)
throw new ArgumentException ("No server certificate could be found.");
_secure = true;
_sslConfig = new ServerSslConfiguration (sslConfig);
_sslConfig.ServerCertificate = cert;
}
_endpoint = endpoint;
_prefixes = new Dictionary<HttpListenerPrefix, HttpListener> ();
_unregistered = new Dictionary<HttpConnection, HttpConnection> ();
_unregisteredSync = ((ICollection) _unregistered).SyncRoot;
_socket =
new Socket (endpoint.Address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
if (reuseAddress)
_socket.SetSocketOption (SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
_socket.Bind (endpoint);
_socket.Listen (500);
_socket.BeginAccept (onAccept, this);
}
#endregion
#region Public Properties
public IPAddress Address {
get {
return _endpoint.Address;
}
}
public bool IsSecure {
get {
return _secure;
}
}
public int Port {
get {
return _endpoint.Port;
}
}
public ServerSslConfiguration SslConfiguration {
get {
return _sslConfig;
}
}
#endregion
#region Private Methods
private static void addSpecial (List<HttpListenerPrefix> prefixes, HttpListenerPrefix prefix)
{
var path = prefix.Path;
foreach (var pref in prefixes) {
if (pref.Path == path)
throw new HttpListenerException (87, "The prefix is already in use.");
}
prefixes.Add (prefix);
}
private static RSACryptoServiceProvider createRSAFromFile (string filename)
{
byte[] pvk = null;
using (var fs = File.Open (filename, FileMode.Open, FileAccess.Read, FileShare.Read)) {
pvk = new byte[fs.Length];
fs.Read (pvk, 0, pvk.Length);
}
var rsa = new RSACryptoServiceProvider ();
rsa.ImportCspBlob (pvk);
return rsa;
}
private static X509Certificate2 getCertificate (
int port, string folderPath, X509Certificate2 defaultCertificate
)
{
if (folderPath == null || folderPath.Length == 0)
folderPath = _defaultCertFolderPath;
try {
var cer = Path.Combine (folderPath, String.Format ("{0}.cer", port));
var key = Path.Combine (folderPath, String.Format ("{0}.key", port));
if (File.Exists (cer) && File.Exists (key)) {
var cert = new X509Certificate2 (cer);
cert.PrivateKey = createRSAFromFile (key);
return cert;
}
}
catch {
}
return defaultCertificate;
}
private void leaveIfNoPrefix ()
{
if (_prefixes.Count > 0)
return;
var prefs = _unhandled;
if (prefs != null && prefs.Count > 0)
return;
prefs = _all;
if (prefs != null && prefs.Count > 0)
return;
EndPointManager.RemoveEndPoint (_endpoint);
}
private static void onAccept (IAsyncResult asyncResult)
{
var lsnr = (EndPointListener) asyncResult.AsyncState;
Socket sock = null;
try {
sock = lsnr._socket.EndAccept (asyncResult);
}
catch (SocketException) {
// TODO: Should log the error code when this class has a logging.
}
catch (ObjectDisposedException) {
return;
}
try {
lsnr._socket.BeginAccept (onAccept, lsnr);
}
catch {
if (sock != null)
sock.Close ();
return;
}
if (sock == null)
return;
processAccepted (sock, lsnr);
}
private static void processAccepted (Socket socket, EndPointListener listener)
{
HttpConnection conn = null;
try {
conn = new HttpConnection (socket, listener);
lock (listener._unregisteredSync)
listener._unregistered[conn] = conn;
conn.BeginReadRequest ();
}
catch {
if (conn != null) {
conn.Close (true);
return;
}
socket.Close ();
}
}
private static bool removeSpecial (List<HttpListenerPrefix> prefixes, HttpListenerPrefix prefix)
{
var path = prefix.Path;
var cnt = prefixes.Count;
for (var i = 0; i < cnt; i++) {
if (prefixes[i].Path == path) {
prefixes.RemoveAt (i);
return true;
}
}
return false;
}
private static HttpListener searchHttpListenerFromSpecial (
string path, List<HttpListenerPrefix> prefixes
)
{
if (prefixes == null)
return null;
HttpListener bestMatch = null;
var bestLen = -1;
foreach (var pref in prefixes) {
var prefPath = pref.Path;
var len = prefPath.Length;
if (len < bestLen)
continue;
if (path.StartsWith (prefPath)) {
bestLen = len;
bestMatch = pref.Listener;
}
}
return bestMatch;
}
#endregion
#region Internal Methods
internal static bool CertificateExists (int port, string folderPath)
{
if (folderPath == null || folderPath.Length == 0)
folderPath = _defaultCertFolderPath;
var cer = Path.Combine (folderPath, String.Format ("{0}.cer", port));
var key = Path.Combine (folderPath, String.Format ("{0}.key", port));
return File.Exists (cer) && File.Exists (key);
}
internal void RemoveConnection (HttpConnection connection)
{
lock (_unregisteredSync)
_unregistered.Remove (connection);
}
internal bool TrySearchHttpListener (Uri uri, out HttpListener listener)
{
listener = null;
if (uri == null)
return false;
var host = uri.Host;
var dns = Uri.CheckHostName (host) == UriHostNameType.Dns;
var port = uri.Port.ToString ();
var path = HttpUtility.UrlDecode (uri.AbsolutePath);
var pathSlash = path[path.Length - 1] != '/' ? path + "/" : path;
if (host != null && host.Length > 0) {
var bestLen = -1;
foreach (var pref in _prefixes.Keys) {
if (dns) {
var prefHost = pref.Host;
if (Uri.CheckHostName (prefHost) == UriHostNameType.Dns && prefHost != host)
continue;
}
if (pref.Port != port)
continue;
var prefPath = pref.Path;
var len = prefPath.Length;
if (len < bestLen)
continue;
if (path.StartsWith (prefPath) || pathSlash.StartsWith (prefPath)) {
bestLen = len;
listener = _prefixes[pref];
}
}
if (bestLen != -1)
return true;
}
var prefs = _unhandled;
listener = searchHttpListenerFromSpecial (path, prefs);
if (listener == null && pathSlash != path)
listener = searchHttpListenerFromSpecial (pathSlash, prefs);
if (listener != null)
return true;
prefs = _all;
listener = searchHttpListenerFromSpecial (path, prefs);
if (listener == null && pathSlash != path)
listener = searchHttpListenerFromSpecial (pathSlash, prefs);
return listener != null;
}
#endregion
#region Public Methods
public void AddPrefix (HttpListenerPrefix prefix, HttpListener listener)
{
List<HttpListenerPrefix> current, future;
if (prefix.Host == "*") {
do {
current = _unhandled;
future = current != null
? new List<HttpListenerPrefix> (current)
: new List<HttpListenerPrefix> ();
prefix.Listener = listener;
addSpecial (future, prefix);
}
while (Interlocked.CompareExchange (ref _unhandled, future, current) != current);
return;
}
if (prefix.Host == "+") {
do {
current = _all;
future = current != null
? new List<HttpListenerPrefix> (current)
: new List<HttpListenerPrefix> ();
prefix.Listener = listener;
addSpecial (future, prefix);
}
while (Interlocked.CompareExchange (ref _all, future, current) != current);
return;
}
Dictionary<HttpListenerPrefix, HttpListener> prefs, prefs2;
do {
prefs = _prefixes;
if (prefs.ContainsKey (prefix)) {
if (prefs[prefix] != listener) {
throw new HttpListenerException (
87, String.Format ("There's another listener for {0}.", prefix)
);
}
return;
}
prefs2 = new Dictionary<HttpListenerPrefix, HttpListener> (prefs);
prefs2[prefix] = listener;
}
while (Interlocked.CompareExchange (ref _prefixes, prefs2, prefs) != prefs);
}
public void Close ()
{
_socket.Close ();
HttpConnection[] conns = null;
lock (_unregisteredSync) {
if (_unregistered.Count == 0)
return;
var keys = _unregistered.Keys;
conns = new HttpConnection[keys.Count];
keys.CopyTo (conns, 0);
_unregistered.Clear ();
}
for (var i = conns.Length - 1; i >= 0; i--)
conns[i].Close (true);
}
public void RemovePrefix (HttpListenerPrefix prefix, HttpListener listener)
{
List<HttpListenerPrefix> current, future;
if (prefix.Host == "*") {
do {
current = _unhandled;
if (current == null)
break;
future = new List<HttpListenerPrefix> (current);
if (!removeSpecial (future, prefix))
break; // The prefix wasn't found.
}
while (Interlocked.CompareExchange (ref _unhandled, future, current) != current);
leaveIfNoPrefix ();
return;
}
if (prefix.Host == "+") {
do {
current = _all;
if (current == null)
break;
future = new List<HttpListenerPrefix> (current);
if (!removeSpecial (future, prefix))
break; // The prefix wasn't found.
}
while (Interlocked.CompareExchange (ref _all, future, current) != current);
leaveIfNoPrefix ();
return;
}
Dictionary<HttpListenerPrefix, HttpListener> prefs, prefs2;
do {
prefs = _prefixes;
if (!prefs.ContainsKey (prefix))
break;
prefs2 = new Dictionary<HttpListenerPrefix, HttpListener> (prefs);
prefs2.Remove (prefix);
}
while (Interlocked.CompareExchange (ref _prefixes, prefs2, prefs) != prefs);
leaveIfNoPrefix ();
}
#endregion
}
}

View File

@ -0,0 +1,240 @@
#region License
/*
* EndPointManager.cs
*
* This code is derived from EndPointManager.cs (System.Net) of Mono
* (http://www.mono-project.com).
*
* The MIT License
*
* Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
* Copyright (c) 2012-2016 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
#region Authors
/*
* Authors:
* - Gonzalo Paniagua Javier <gonzalo@ximian.com>
*/
#endregion
#region Contributors
/*
* Contributors:
* - Liryna <liryna.stark@gmail.com>
*/
#endregion
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
namespace WebSocketSharp.Net
{
internal sealed class EndPointManager
{
#region Private Fields
private static readonly Dictionary<IPEndPoint, EndPointListener> _endpoints;
#endregion
#region Static Constructor
static EndPointManager ()
{
_endpoints = new Dictionary<IPEndPoint, EndPointListener> ();
}
#endregion
#region Private Constructors
private EndPointManager ()
{
}
#endregion
#region Private Methods
private static void addPrefix (string uriPrefix, HttpListener listener)
{
var pref = new HttpListenerPrefix (uriPrefix);
var addr = convertToIPAddress (pref.Host);
if (addr == null)
throw new HttpListenerException (87, "Includes an invalid host.");
if (!addr.IsLocal ())
throw new HttpListenerException (87, "Includes an invalid host.");
int port;
if (!Int32.TryParse (pref.Port, out port))
throw new HttpListenerException (87, "Includes an invalid port.");
if (!port.IsPortNumber ())
throw new HttpListenerException (87, "Includes an invalid port.");
var path = pref.Path;
if (path.IndexOf ('%') != -1)
throw new HttpListenerException (87, "Includes an invalid path.");
if (path.IndexOf ("//", StringComparison.Ordinal) != -1)
throw new HttpListenerException (87, "Includes an invalid path.");
var endpoint = new IPEndPoint (addr, port);
EndPointListener lsnr;
if (_endpoints.TryGetValue (endpoint, out lsnr)) {
if (lsnr.IsSecure ^ pref.IsSecure)
throw new HttpListenerException (87, "Includes an invalid scheme.");
}
else {
lsnr =
new EndPointListener (
endpoint,
pref.IsSecure,
listener.CertificateFolderPath,
listener.SslConfiguration,
listener.ReuseAddress
);
_endpoints.Add (endpoint, lsnr);
}
lsnr.AddPrefix (pref, listener);
}
private static IPAddress convertToIPAddress (string hostname)
{
if (hostname == "*")
return IPAddress.Any;
if (hostname == "+")
return IPAddress.Any;
return hostname.ToIPAddress ();
}
private static void removePrefix (string uriPrefix, HttpListener listener)
{
var pref = new HttpListenerPrefix (uriPrefix);
var addr = convertToIPAddress (pref.Host);
if (addr == null)
return;
if (!addr.IsLocal ())
return;
int port;
if (!Int32.TryParse (pref.Port, out port))
return;
if (!port.IsPortNumber ())
return;
var path = pref.Path;
if (path.IndexOf ('%') != -1)
return;
if (path.IndexOf ("//", StringComparison.Ordinal) != -1)
return;
var endpoint = new IPEndPoint (addr, port);
EndPointListener lsnr;
if (!_endpoints.TryGetValue (endpoint, out lsnr))
return;
if (lsnr.IsSecure ^ pref.IsSecure)
return;
lsnr.RemovePrefix (pref, listener);
}
#endregion
#region Internal Methods
internal static bool RemoveEndPoint (IPEndPoint endpoint)
{
lock (((ICollection) _endpoints).SyncRoot) {
EndPointListener lsnr;
if (!_endpoints.TryGetValue (endpoint, out lsnr))
return false;
_endpoints.Remove (endpoint);
lsnr.Close ();
return true;
}
}
#endregion
#region Public Methods
public static void AddListener (HttpListener listener)
{
var added = new List<string> ();
lock (((ICollection) _endpoints).SyncRoot) {
try {
foreach (var pref in listener.Prefixes) {
addPrefix (pref, listener);
added.Add (pref);
}
}
catch {
foreach (var pref in added)
removePrefix (pref, listener);
throw;
}
}
}
public static void AddPrefix (string uriPrefix, HttpListener listener)
{
lock (((ICollection) _endpoints).SyncRoot)
addPrefix (uriPrefix, listener);
}
public static void RemoveListener (HttpListener listener)
{
lock (((ICollection) _endpoints).SyncRoot) {
foreach (var pref in listener.Prefixes)
removePrefix (pref, listener);
}
}
public static void RemovePrefix (string uriPrefix, HttpListener listener)
{
lock (((ICollection) _endpoints).SyncRoot)
removePrefix (uriPrefix, listener);
}
#endregion
}
}

View File

@ -0,0 +1,82 @@
#region License
/*
* HttpBasicIdentity.cs
*
* This code is derived from HttpListenerBasicIdentity.cs (System.Net) of
* Mono (http://www.mono-project.com).
*
* The MIT License
*
* Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
* Copyright (c) 2014-2017 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
#region Authors
/*
* Authors:
* - Gonzalo Paniagua Javier <gonzalo@novell.com>
*/
#endregion
using System;
using System.Security.Principal;
namespace WebSocketSharp.Net
{
/// <summary>
/// Holds the username and password from an HTTP Basic authentication attempt.
/// </summary>
public class HttpBasicIdentity : GenericIdentity
{
#region Private Fields
private string _password;
#endregion
#region Internal Constructors
internal HttpBasicIdentity (string username, string password)
: base (username, "Basic")
{
_password = password;
}
#endregion
#region Public Properties
/// <summary>
/// Gets the password from a basic authentication attempt.
/// </summary>
/// <value>
/// A <see cref="string"/> that represents the password.
/// </value>
public virtual string Password {
get {
return _password;
}
}
#endregion
}
}

View File

@ -0,0 +1,597 @@
#region License
/*
* HttpConnection.cs
*
* This code is derived from HttpConnection.cs (System.Net) of Mono
* (http://www.mono-project.com).
*
* The MIT License
*
* Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
* Copyright (c) 2012-2016 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
#region Authors
/*
* Authors:
* - Gonzalo Paniagua Javier <gonzalo@novell.com>
*/
#endregion
#region Contributors
/*
* Contributors:
* - Liryna <liryna.stark@gmail.com>
* - Rohan Singh <rohan-singh@hotmail.com>
*/
#endregion
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Text;
using System.Threading;
namespace WebSocketSharp.Net
{
internal sealed class HttpConnection
{
#region Private Fields
private byte[] _buffer;
private const int _bufferLength = 8192;
private HttpListenerContext _context;
private bool _contextRegistered;
private StringBuilder _currentLine;
private InputState _inputState;
private RequestStream _inputStream;
private HttpListener _lastListener;
private LineState _lineState;
private EndPointListener _listener;
private EndPoint _localEndPoint;
private ResponseStream _outputStream;
private int _position;
private EndPoint _remoteEndPoint;
private MemoryStream _requestBuffer;
private int _reuses;
private bool _secure;
private Socket _socket;
private Stream _stream;
private object _sync;
private int _timeout;
private Dictionary<int, bool> _timeoutCanceled;
private Timer _timer;
#endregion
#region Internal Constructors
internal HttpConnection (Socket socket, EndPointListener listener)
{
_socket = socket;
_listener = listener;
var netStream = new NetworkStream (socket, false);
if (listener.IsSecure) {
var sslConf = listener.SslConfiguration;
var sslStream = new SslStream (
netStream,
false,
sslConf.ClientCertificateValidationCallback
);
sslStream.AuthenticateAsServer (
sslConf.ServerCertificate,
sslConf.ClientCertificateRequired,
sslConf.EnabledSslProtocols,
sslConf.CheckCertificateRevocation
);
_secure = true;
_stream = sslStream;
}
else {
_stream = netStream;
}
_localEndPoint = socket.LocalEndPoint;
_remoteEndPoint = socket.RemoteEndPoint;
_sync = new object ();
_timeout = 90000; // 90k ms for first request, 15k ms from then on.
_timeoutCanceled = new Dictionary<int, bool> ();
_timer = new Timer (onTimeout, this, Timeout.Infinite, Timeout.Infinite);
init ();
}
#endregion
#region Public Properties
public bool IsClosed {
get {
return _socket == null;
}
}
public bool IsLocal {
get {
return ((IPEndPoint) _remoteEndPoint).Address.IsLocal ();
}
}
public bool IsSecure {
get {
return _secure;
}
}
public IPEndPoint LocalEndPoint {
get {
return (IPEndPoint) _localEndPoint;
}
}
public IPEndPoint RemoteEndPoint {
get {
return (IPEndPoint) _remoteEndPoint;
}
}
public int Reuses {
get {
return _reuses;
}
}
public Stream Stream {
get {
return _stream;
}
}
#endregion
#region Private Methods
private void close ()
{
lock (_sync) {
if (_socket == null)
return;
disposeTimer ();
disposeRequestBuffer ();
disposeStream ();
closeSocket ();
}
unregisterContext ();
removeConnection ();
}
private void closeSocket ()
{
try {
_socket.Shutdown (SocketShutdown.Both);
}
catch {
}
_socket.Close ();
_socket = null;
}
private void disposeRequestBuffer ()
{
if (_requestBuffer == null)
return;
_requestBuffer.Dispose ();
_requestBuffer = null;
}
private void disposeStream ()
{
if (_stream == null)
return;
_inputStream = null;
_outputStream = null;
_stream.Dispose ();
_stream = null;
}
private void disposeTimer ()
{
if (_timer == null)
return;
try {
_timer.Change (Timeout.Infinite, Timeout.Infinite);
}
catch {
}
_timer.Dispose ();
_timer = null;
}
private void init ()
{
_context = new HttpListenerContext (this);
_inputState = InputState.RequestLine;
_inputStream = null;
_lineState = LineState.None;
_outputStream = null;
_position = 0;
_requestBuffer = new MemoryStream ();
}
private static void onRead (IAsyncResult asyncResult)
{
var conn = (HttpConnection) asyncResult.AsyncState;
if (conn._socket == null)
return;
lock (conn._sync) {
if (conn._socket == null)
return;
var nread = -1;
var len = 0;
try {
var current = conn._reuses;
if (!conn._timeoutCanceled[current]) {
conn._timer.Change (Timeout.Infinite, Timeout.Infinite);
conn._timeoutCanceled[current] = true;
}
nread = conn._stream.EndRead (asyncResult);
conn._requestBuffer.Write (conn._buffer, 0, nread);
len = (int) conn._requestBuffer.Length;
}
catch (Exception ex) {
if (conn._requestBuffer != null && conn._requestBuffer.Length > 0) {
conn.SendError (ex.Message, 400);
return;
}
conn.close ();
return;
}
if (nread <= 0) {
conn.close ();
return;
}
if (conn.processInput (conn._requestBuffer.GetBuffer (), len)) {
if (!conn._context.HasError)
conn._context.Request.FinishInitialization ();
if (conn._context.HasError) {
conn.SendError ();
return;
}
HttpListener lsnr;
if (!conn._listener.TrySearchHttpListener (conn._context.Request.Url, out lsnr)) {
conn.SendError (null, 404);
return;
}
if (conn._lastListener != lsnr) {
conn.removeConnection ();
if (!lsnr.AddConnection (conn)) {
conn.close ();
return;
}
conn._lastListener = lsnr;
}
conn._context.Listener = lsnr;
if (!conn._context.Authenticate ())
return;
if (conn._context.Register ())
conn._contextRegistered = true;
return;
}
conn._stream.BeginRead (conn._buffer, 0, _bufferLength, onRead, conn);
}
}
private static void onTimeout (object state)
{
var conn = (HttpConnection) state;
var current = conn._reuses;
if (conn._socket == null)
return;
lock (conn._sync) {
if (conn._socket == null)
return;
if (conn._timeoutCanceled[current])
return;
conn.SendError (null, 408);
}
}
// true -> Done processing.
// false -> Need more input.
private bool processInput (byte[] data, int length)
{
if (_currentLine == null)
_currentLine = new StringBuilder (64);
var nread = 0;
try {
string line;
while ((line = readLineFrom (data, _position, length, out nread)) != null) {
_position += nread;
if (line.Length == 0) {
if (_inputState == InputState.RequestLine)
continue;
if (_position > 32768)
_context.ErrorMessage = "Headers too long";
_currentLine = null;
return true;
}
if (_inputState == InputState.RequestLine) {
_context.Request.SetRequestLine (line);
_inputState = InputState.Headers;
}
else {
_context.Request.AddHeader (line);
}
if (_context.HasError)
return true;
}
}
catch (Exception ex) {
_context.ErrorMessage = ex.Message;
return true;
}
_position += nread;
if (_position >= 32768) {
_context.ErrorMessage = "Headers too long";
return true;
}
return false;
}
private string readLineFrom (byte[] buffer, int offset, int length, out int read)
{
read = 0;
for (var i = offset; i < length && _lineState != LineState.Lf; i++) {
read++;
var b = buffer[i];
if (b == 13)
_lineState = LineState.Cr;
else if (b == 10)
_lineState = LineState.Lf;
else
_currentLine.Append ((char) b);
}
if (_lineState != LineState.Lf)
return null;
var line = _currentLine.ToString ();
_currentLine.Length = 0;
_lineState = LineState.None;
return line;
}
private void removeConnection ()
{
if (_lastListener != null)
_lastListener.RemoveConnection (this);
else
_listener.RemoveConnection (this);
}
private void unregisterContext ()
{
if (!_contextRegistered)
return;
_context.Unregister ();
_contextRegistered = false;
}
#endregion
#region Internal Methods
internal void Close (bool force)
{
if (_socket == null)
return;
lock (_sync) {
if (_socket == null)
return;
if (force) {
if (_outputStream != null)
_outputStream.Close (true);
close ();
return;
}
GetResponseStream ().Close (false);
if (_context.Response.CloseConnection) {
close ();
return;
}
if (!_context.Request.FlushInput ()) {
close ();
return;
}
disposeRequestBuffer ();
unregisterContext ();
init ();
_reuses++;
BeginReadRequest ();
}
}
#endregion
#region Public Methods
public void BeginReadRequest ()
{
if (_buffer == null)
_buffer = new byte[_bufferLength];
if (_reuses == 1)
_timeout = 15000;
try {
_timeoutCanceled.Add (_reuses, false);
_timer.Change (_timeout, Timeout.Infinite);
_stream.BeginRead (_buffer, 0, _bufferLength, onRead, this);
}
catch {
close ();
}
}
public void Close ()
{
Close (false);
}
public RequestStream GetRequestStream (long contentLength, bool chunked)
{
lock (_sync) {
if (_socket == null)
return null;
if (_inputStream != null)
return _inputStream;
var buff = _requestBuffer.GetBuffer ();
var len = (int) _requestBuffer.Length;
var cnt = len - _position;
disposeRequestBuffer ();
_inputStream = chunked
? new ChunkedRequestStream (
_stream, buff, _position, cnt, _context
)
: new RequestStream (
_stream, buff, _position, cnt, contentLength
);
return _inputStream;
}
}
public ResponseStream GetResponseStream ()
{
// TODO: Can we get this stream before reading the input?
lock (_sync) {
if (_socket == null)
return null;
if (_outputStream != null)
return _outputStream;
var lsnr = _context.Listener;
var ignore = lsnr != null ? lsnr.IgnoreWriteExceptions : true;
_outputStream = new ResponseStream (_stream, _context.Response, ignore);
return _outputStream;
}
}
public void SendError ()
{
SendError (_context.ErrorMessage, _context.ErrorStatus);
}
public void SendError (string message, int status)
{
if (_socket == null)
return;
lock (_sync) {
if (_socket == null)
return;
try {
var res = _context.Response;
res.StatusCode = status;
res.ContentType = "text/html";
var content = new StringBuilder (64);
content.AppendFormat ("<html><body><h1>{0} {1}", status, res.StatusDescription);
if (message != null && message.Length > 0)
content.AppendFormat (" ({0})</h1></body></html>", message);
else
content.Append ("</h1></body></html>");
var enc = Encoding.UTF8;
var entity = enc.GetBytes (content.ToString ());
res.ContentEncoding = enc;
res.ContentLength64 = entity.LongLength;
res.Close (entity, true);
}
catch {
Close (true);
}
}
}
#endregion
}
}

View File

@ -0,0 +1,187 @@
#region License
/*
* HttpDigestIdentity.cs
*
* The MIT License
*
* Copyright (c) 2014-2017 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
using System;
using System.Collections.Specialized;
using System.Security.Principal;
namespace WebSocketSharp.Net
{
/// <summary>
/// Holds the username and other parameters from
/// an HTTP Digest authentication attempt.
/// </summary>
public class HttpDigestIdentity : GenericIdentity
{
#region Private Fields
private NameValueCollection _parameters;
#endregion
#region Internal Constructors
internal HttpDigestIdentity (NameValueCollection parameters)
: base (parameters["username"], "Digest")
{
_parameters = parameters;
}
#endregion
#region Public Properties
/// <summary>
/// Gets the algorithm parameter from a digest authentication attempt.
/// </summary>
/// <value>
/// A <see cref="string"/> that represents the algorithm parameter.
/// </value>
public string Algorithm {
get {
return _parameters["algorithm"];
}
}
/// <summary>
/// Gets the cnonce parameter from a digest authentication attempt.
/// </summary>
/// <value>
/// A <see cref="string"/> that represents the cnonce parameter.
/// </value>
public string Cnonce {
get {
return _parameters["cnonce"];
}
}
/// <summary>
/// Gets the nc parameter from a digest authentication attempt.
/// </summary>
/// <value>
/// A <see cref="string"/> that represents the nc parameter.
/// </value>
public string Nc {
get {
return _parameters["nc"];
}
}
/// <summary>
/// Gets the nonce parameter from a digest authentication attempt.
/// </summary>
/// <value>
/// A <see cref="string"/> that represents the nonce parameter.
/// </value>
public string Nonce {
get {
return _parameters["nonce"];
}
}
/// <summary>
/// Gets the opaque parameter from a digest authentication attempt.
/// </summary>
/// <value>
/// A <see cref="string"/> that represents the opaque parameter.
/// </value>
public string Opaque {
get {
return _parameters["opaque"];
}
}
/// <summary>
/// Gets the qop parameter from a digest authentication attempt.
/// </summary>
/// <value>
/// A <see cref="string"/> that represents the qop parameter.
/// </value>
public string Qop {
get {
return _parameters["qop"];
}
}
/// <summary>
/// Gets the realm parameter from a digest authentication attempt.
/// </summary>
/// <value>
/// A <see cref="string"/> that represents the realm parameter.
/// </value>
public string Realm {
get {
return _parameters["realm"];
}
}
/// <summary>
/// Gets the response parameter from a digest authentication attempt.
/// </summary>
/// <value>
/// A <see cref="string"/> that represents the response parameter.
/// </value>
public string Response {
get {
return _parameters["response"];
}
}
/// <summary>
/// Gets the uri parameter from a digest authentication attempt.
/// </summary>
/// <value>
/// A <see cref="string"/> that represents the uri parameter.
/// </value>
public string Uri {
get {
return _parameters["uri"];
}
}
#endregion
#region Internal Methods
internal bool IsValid (
string password, string realm, string method, string entity
)
{
var copied = new NameValueCollection (_parameters);
copied["password"] = password;
copied["realm"] = realm;
copied["method"] = method;
copied["entity"] = entity;
var expected = AuthenticationResponse.CreateRequestDigest (copied);
return _parameters["response"] == expected;
}
#endregion
}
}

View File

@ -0,0 +1,128 @@
#region License
/*
* HttpHeaderInfo.cs
*
* The MIT License
*
* Copyright (c) 2013-2020 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
using System;
namespace WebSocketSharp.Net
{
internal class HttpHeaderInfo
{
#region Private Fields
private string _headerName;
private HttpHeaderType _headerType;
#endregion
#region Internal Constructors
internal HttpHeaderInfo (string headerName, HttpHeaderType headerType)
{
_headerName = headerName;
_headerType = headerType;
}
#endregion
#region Internal Properties
internal bool IsMultiValueInRequest {
get {
var headerType = _headerType & HttpHeaderType.MultiValueInRequest;
return headerType == HttpHeaderType.MultiValueInRequest;
}
}
internal bool IsMultiValueInResponse {
get {
var headerType = _headerType & HttpHeaderType.MultiValueInResponse;
return headerType == HttpHeaderType.MultiValueInResponse;
}
}
#endregion
#region Public Properties
public string HeaderName {
get {
return _headerName;
}
}
public HttpHeaderType HeaderType {
get {
return _headerType;
}
}
public bool IsRequest {
get {
var headerType = _headerType & HttpHeaderType.Request;
return headerType == HttpHeaderType.Request;
}
}
public bool IsResponse {
get {
var headerType = _headerType & HttpHeaderType.Response;
return headerType == HttpHeaderType.Response;
}
}
#endregion
#region Public Methods
public bool IsMultiValue (bool response)
{
var headerType = _headerType & HttpHeaderType.MultiValue;
if (headerType != HttpHeaderType.MultiValue)
return response ? IsMultiValueInResponse : IsMultiValueInRequest;
return response ? IsResponse : IsRequest;
}
public bool IsRestricted (bool response)
{
var headerType = _headerType & HttpHeaderType.Restricted;
if (headerType != HttpHeaderType.Restricted)
return false;
return response ? IsResponse : IsRequest;
}
#endregion
}
}

View File

@ -0,0 +1,44 @@
#region License
/*
* HttpHeaderType.cs
*
* The MIT License
*
* Copyright (c) 2013-2014 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
using System;
namespace WebSocketSharp.Net
{
[Flags]
internal enum HttpHeaderType
{
Unspecified = 0,
Request = 1,
Response = 1 << 1,
Restricted = 1 << 2,
MultiValue = 1 << 3,
MultiValueInRequest = 1 << 4,
MultiValueInResponse = 1 << 5
}
}

View File

@ -0,0 +1,836 @@
#region License
/*
* HttpListener.cs
*
* This code is derived from HttpListener.cs (System.Net) of Mono
* (http://www.mono-project.com).
*
* The MIT License
*
* Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
* Copyright (c) 2012-2016 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
#region Authors
/*
* Authors:
* - Gonzalo Paniagua Javier <gonzalo@novell.com>
*/
#endregion
#region Contributors
/*
* Contributors:
* - Liryna <liryna.stark@gmail.com>
*/
#endregion
using System;
using System.Collections;
using System.Collections.Generic;
using System.Security.Cryptography.X509Certificates;
using System.Security.Principal;
using System.Threading;
// TODO: Logging.
namespace WebSocketSharp.Net
{
/// <summary>
/// Provides a simple, programmatically controlled HTTP listener.
/// </summary>
public sealed class HttpListener : IDisposable
{
#region Private Fields
private AuthenticationSchemes _authSchemes;
private Func<HttpListenerRequest, AuthenticationSchemes> _authSchemeSelector;
private string _certFolderPath;
private Dictionary<HttpConnection, HttpConnection> _connections;
private object _connectionsSync;
private List<HttpListenerContext> _ctxQueue;
private object _ctxQueueSync;
private Dictionary<HttpListenerContext, HttpListenerContext> _ctxRegistry;
private object _ctxRegistrySync;
private static readonly string _defaultRealm;
private bool _disposed;
private bool _ignoreWriteExceptions;
private volatile bool _listening;
private Logger _logger;
private HttpListenerPrefixCollection _prefixes;
private string _realm;
private bool _reuseAddress;
private ServerSslConfiguration _sslConfig;
private Func<IIdentity, NetworkCredential> _userCredFinder;
private List<HttpListenerAsyncResult> _waitQueue;
private object _waitQueueSync;
#endregion
#region Static Constructor
static HttpListener ()
{
_defaultRealm = "SECRET AREA";
}
#endregion
#region Public Constructors
/// <summary>
/// Initializes a new instance of the <see cref="HttpListener"/> class.
/// </summary>
public HttpListener ()
{
_authSchemes = AuthenticationSchemes.Anonymous;
_connections = new Dictionary<HttpConnection, HttpConnection> ();
_connectionsSync = ((ICollection) _connections).SyncRoot;
_ctxQueue = new List<HttpListenerContext> ();
_ctxQueueSync = ((ICollection) _ctxQueue).SyncRoot;
_ctxRegistry = new Dictionary<HttpListenerContext, HttpListenerContext> ();
_ctxRegistrySync = ((ICollection) _ctxRegistry).SyncRoot;
_logger = new Logger ();
_prefixes = new HttpListenerPrefixCollection (this);
_waitQueue = new List<HttpListenerAsyncResult> ();
_waitQueueSync = ((ICollection) _waitQueue).SyncRoot;
}
#endregion
#region Internal Properties
internal bool IsDisposed {
get {
return _disposed;
}
}
internal bool ReuseAddress {
get {
return _reuseAddress;
}
set {
_reuseAddress = value;
}
}
#endregion
#region Public Properties
/// <summary>
/// Gets or sets the scheme used to authenticate the clients.
/// </summary>
/// <value>
/// One of the <see cref="WebSocketSharp.Net.AuthenticationSchemes"/> enum values,
/// represents the scheme used to authenticate the clients. The default value is
/// <see cref="WebSocketSharp.Net.AuthenticationSchemes.Anonymous"/>.
/// </value>
/// <exception cref="ObjectDisposedException">
/// This listener has been closed.
/// </exception>
public AuthenticationSchemes AuthenticationSchemes {
get {
CheckDisposed ();
return _authSchemes;
}
set {
CheckDisposed ();
_authSchemes = value;
}
}
/// <summary>
/// Gets or sets the delegate called to select the scheme used to authenticate the clients.
/// </summary>
/// <remarks>
/// If you set this property, the listener uses the authentication scheme selected by
/// the delegate for each request. Or if you don't set, the listener uses the value of
/// the <see cref="HttpListener.AuthenticationSchemes"/> property as the authentication
/// scheme for all requests.
/// </remarks>
/// <value>
/// A <c>Func&lt;<see cref="HttpListenerRequest"/>, <see cref="AuthenticationSchemes"/>&gt;</c>
/// delegate that references the method used to select an authentication scheme. The default
/// value is <see langword="null"/>.
/// </value>
/// <exception cref="ObjectDisposedException">
/// This listener has been closed.
/// </exception>
public Func<HttpListenerRequest, AuthenticationSchemes> AuthenticationSchemeSelector {
get {
CheckDisposed ();
return _authSchemeSelector;
}
set {
CheckDisposed ();
_authSchemeSelector = value;
}
}
/// <summary>
/// Gets or sets the path to the folder in which stores the certificate files used to
/// authenticate the server on the secure connection.
/// </summary>
/// <remarks>
/// <para>
/// This property represents the path to the folder in which stores the certificate files
/// associated with each port number of added URI prefixes. A set of the certificate files
/// is a pair of the <c>'port number'.cer</c> (DER) and <c>'port number'.key</c>
/// (DER, RSA Private Key).
/// </para>
/// <para>
/// If this property is <see langword="null"/> or empty, the result of
/// <c>System.Environment.GetFolderPath
/// (<see cref="Environment.SpecialFolder.ApplicationData"/>)</c> is used as the default path.
/// </para>
/// </remarks>
/// <value>
/// A <see cref="string"/> that represents the path to the folder in which stores
/// the certificate files. The default value is <see langword="null"/>.
/// </value>
/// <exception cref="ObjectDisposedException">
/// This listener has been closed.
/// </exception>
public string CertificateFolderPath {
get {
CheckDisposed ();
return _certFolderPath;
}
set {
CheckDisposed ();
_certFolderPath = value;
}
}
/// <summary>
/// Gets or sets a value indicating whether the listener returns exceptions that occur when
/// sending the response to the client.
/// </summary>
/// <value>
/// <c>true</c> if the listener shouldn't return those exceptions; otherwise, <c>false</c>.
/// The default value is <c>false</c>.
/// </value>
/// <exception cref="ObjectDisposedException">
/// This listener has been closed.
/// </exception>
public bool IgnoreWriteExceptions {
get {
CheckDisposed ();
return _ignoreWriteExceptions;
}
set {
CheckDisposed ();
_ignoreWriteExceptions = value;
}
}
/// <summary>
/// Gets a value indicating whether the listener has been started.
/// </summary>
/// <value>
/// <c>true</c> if the listener has been started; otherwise, <c>false</c>.
/// </value>
public bool IsListening {
get {
return _listening;
}
}
/// <summary>
/// Gets a value indicating whether the listener can be used with the current operating system.
/// </summary>
/// <value>
/// <c>true</c>.
/// </value>
public static bool IsSupported {
get {
return true;
}
}
/// <summary>
/// Gets the logging functions.
/// </summary>
/// <remarks>
/// The default logging level is <see cref="LogLevel.Error"/>. If you would like to change it,
/// you should set the <c>Log.Level</c> property to any of the <see cref="LogLevel"/> enum
/// values.
/// </remarks>
/// <value>
/// A <see cref="Logger"/> that provides the logging functions.
/// </value>
public Logger Log {
get {
return _logger;
}
}
/// <summary>
/// Gets the URI prefixes handled by the listener.
/// </summary>
/// <value>
/// A <see cref="HttpListenerPrefixCollection"/> that contains the URI prefixes.
/// </value>
/// <exception cref="ObjectDisposedException">
/// This listener has been closed.
/// </exception>
public HttpListenerPrefixCollection Prefixes {
get {
CheckDisposed ();
return _prefixes;
}
}
/// <summary>
/// Gets or sets the name of the realm associated with the listener.
/// </summary>
/// <remarks>
/// If this property is <see langword="null"/> or empty, <c>"SECRET AREA"</c> will be used as
/// the name of the realm.
/// </remarks>
/// <value>
/// A <see cref="string"/> that represents the name of the realm. The default value is
/// <see langword="null"/>.
/// </value>
/// <exception cref="ObjectDisposedException">
/// This listener has been closed.
/// </exception>
public string Realm {
get {
CheckDisposed ();
return _realm;
}
set {
CheckDisposed ();
_realm = value;
}
}
/// <summary>
/// Gets or sets the SSL configuration used to authenticate the server and
/// optionally the client for secure connection.
/// </summary>
/// <value>
/// A <see cref="ServerSslConfiguration"/> that represents the configuration used to
/// authenticate the server and optionally the client for secure connection.
/// </value>
/// <exception cref="ObjectDisposedException">
/// This listener has been closed.
/// </exception>
public ServerSslConfiguration SslConfiguration {
get {
CheckDisposed ();
return _sslConfig ?? (_sslConfig = new ServerSslConfiguration ());
}
set {
CheckDisposed ();
_sslConfig = value;
}
}
/// <summary>
/// Gets or sets a value indicating whether, when NTLM authentication is used,
/// the authentication information of first request is used to authenticate
/// additional requests on the same connection.
/// </summary>
/// <remarks>
/// This property isn't currently supported and always throws
/// a <see cref="NotSupportedException"/>.
/// </remarks>
/// <value>
/// <c>true</c> if the authentication information of first request is used;
/// otherwise, <c>false</c>.
/// </value>
/// <exception cref="NotSupportedException">
/// Any use of this property.
/// </exception>
public bool UnsafeConnectionNtlmAuthentication {
get {
throw new NotSupportedException ();
}
set {
throw new NotSupportedException ();
}
}
/// <summary>
/// Gets or sets the delegate called to find the credentials for an identity used to
/// authenticate a client.
/// </summary>
/// <value>
/// A <c>Func&lt;<see cref="IIdentity"/>, <see cref="NetworkCredential"/>&gt;</c> delegate
/// that references the method used to find the credentials. The default value is
/// <see langword="null"/>.
/// </value>
/// <exception cref="ObjectDisposedException">
/// This listener has been closed.
/// </exception>
public Func<IIdentity, NetworkCredential> UserCredentialsFinder {
get {
CheckDisposed ();
return _userCredFinder;
}
set {
CheckDisposed ();
_userCredFinder = value;
}
}
#endregion
#region Private Methods
private void cleanupConnections ()
{
HttpConnection[] conns = null;
lock (_connectionsSync) {
if (_connections.Count == 0)
return;
// Need to copy this since closing will call the RemoveConnection method.
var keys = _connections.Keys;
conns = new HttpConnection[keys.Count];
keys.CopyTo (conns, 0);
_connections.Clear ();
}
for (var i = conns.Length - 1; i >= 0; i--)
conns[i].Close (true);
}
private void cleanupContextQueue (bool sendServiceUnavailable)
{
HttpListenerContext[] ctxs = null;
lock (_ctxQueueSync) {
if (_ctxQueue.Count == 0)
return;
ctxs = _ctxQueue.ToArray ();
_ctxQueue.Clear ();
}
if (!sendServiceUnavailable)
return;
foreach (var ctx in ctxs) {
var res = ctx.Response;
res.StatusCode = (int) HttpStatusCode.ServiceUnavailable;
res.Close ();
}
}
private void cleanupContextRegistry ()
{
HttpListenerContext[] ctxs = null;
lock (_ctxRegistrySync) {
if (_ctxRegistry.Count == 0)
return;
// Need to copy this since closing will call the UnregisterContext method.
var keys = _ctxRegistry.Keys;
ctxs = new HttpListenerContext[keys.Count];
keys.CopyTo (ctxs, 0);
_ctxRegistry.Clear ();
}
for (var i = ctxs.Length - 1; i >= 0; i--)
ctxs[i].Connection.Close (true);
}
private void cleanupWaitQueue (Exception exception)
{
HttpListenerAsyncResult[] aress = null;
lock (_waitQueueSync) {
if (_waitQueue.Count == 0)
return;
aress = _waitQueue.ToArray ();
_waitQueue.Clear ();
}
foreach (var ares in aress)
ares.Complete (exception);
}
private void close (bool force)
{
if (_listening) {
_listening = false;
EndPointManager.RemoveListener (this);
}
lock (_ctxRegistrySync)
cleanupContextQueue (!force);
cleanupContextRegistry ();
cleanupConnections ();
cleanupWaitQueue (new ObjectDisposedException (GetType ().ToString ()));
_disposed = true;
}
private HttpListenerAsyncResult getAsyncResultFromQueue ()
{
if (_waitQueue.Count == 0)
return null;
var ares = _waitQueue[0];
_waitQueue.RemoveAt (0);
return ares;
}
private HttpListenerContext getContextFromQueue ()
{
if (_ctxQueue.Count == 0)
return null;
var ctx = _ctxQueue[0];
_ctxQueue.RemoveAt (0);
return ctx;
}
#endregion
#region Internal Methods
internal bool AddConnection (HttpConnection connection)
{
if (!_listening)
return false;
lock (_connectionsSync) {
if (!_listening)
return false;
_connections[connection] = connection;
return true;
}
}
internal HttpListenerAsyncResult BeginGetContext (HttpListenerAsyncResult asyncResult)
{
lock (_ctxRegistrySync) {
if (!_listening)
throw new HttpListenerException (995);
var ctx = getContextFromQueue ();
if (ctx == null)
_waitQueue.Add (asyncResult);
else
asyncResult.Complete (ctx, true);
return asyncResult;
}
}
internal void CheckDisposed ()
{
if (_disposed)
throw new ObjectDisposedException (GetType ().ToString ());
}
internal string GetRealm ()
{
var realm = _realm;
return realm != null && realm.Length > 0 ? realm : _defaultRealm;
}
internal Func<IIdentity, NetworkCredential> GetUserCredentialsFinder ()
{
return _userCredFinder;
}
internal bool RegisterContext (HttpListenerContext context)
{
if (!_listening)
return false;
lock (_ctxRegistrySync) {
if (!_listening)
return false;
_ctxRegistry[context] = context;
var ares = getAsyncResultFromQueue ();
if (ares == null)
_ctxQueue.Add (context);
else
ares.Complete (context);
return true;
}
}
internal void RemoveConnection (HttpConnection connection)
{
lock (_connectionsSync)
_connections.Remove (connection);
}
internal AuthenticationSchemes SelectAuthenticationScheme (HttpListenerRequest request)
{
var selector = _authSchemeSelector;
if (selector == null)
return _authSchemes;
try {
return selector (request);
}
catch {
return AuthenticationSchemes.None;
}
}
internal void UnregisterContext (HttpListenerContext context)
{
lock (_ctxRegistrySync)
_ctxRegistry.Remove (context);
}
#endregion
#region Public Methods
/// <summary>
/// Shuts down the listener immediately.
/// </summary>
public void Abort ()
{
if (_disposed)
return;
close (true);
}
/// <summary>
/// Begins getting an incoming request asynchronously.
/// </summary>
/// <remarks>
/// This asynchronous operation must be completed by calling the <c>EndGetContext</c> method.
/// Typically, the method is invoked by the <paramref name="callback"/> delegate.
/// </remarks>
/// <returns>
/// An <see cref="IAsyncResult"/> that represents the status of the asynchronous operation.
/// </returns>
/// <param name="callback">
/// An <see cref="AsyncCallback"/> delegate that references the method to invoke when
/// the asynchronous operation completes.
/// </param>
/// <param name="state">
/// An <see cref="object"/> that represents a user defined object to pass to
/// the <paramref name="callback"/> delegate.
/// </param>
/// <exception cref="InvalidOperationException">
/// <para>
/// This listener has no URI prefix on which listens.
/// </para>
/// <para>
/// -or-
/// </para>
/// <para>
/// This listener hasn't been started, or is currently stopped.
/// </para>
/// </exception>
/// <exception cref="ObjectDisposedException">
/// This listener has been closed.
/// </exception>
public IAsyncResult BeginGetContext (AsyncCallback callback, Object state)
{
CheckDisposed ();
if (_prefixes.Count == 0)
throw new InvalidOperationException ("The listener has no URI prefix on which listens.");
if (!_listening)
throw new InvalidOperationException ("The listener hasn't been started.");
return BeginGetContext (new HttpListenerAsyncResult (callback, state));
}
/// <summary>
/// Shuts down the listener.
/// </summary>
public void Close ()
{
if (_disposed)
return;
close (false);
}
/// <summary>
/// Ends an asynchronous operation to get an incoming request.
/// </summary>
/// <remarks>
/// This method completes an asynchronous operation started by calling
/// the <c>BeginGetContext</c> method.
/// </remarks>
/// <returns>
/// A <see cref="HttpListenerContext"/> that represents a request.
/// </returns>
/// <param name="asyncResult">
/// An <see cref="IAsyncResult"/> obtained by calling the <c>BeginGetContext</c> method.
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="asyncResult"/> is <see langword="null"/>.
/// </exception>
/// <exception cref="ArgumentException">
/// <paramref name="asyncResult"/> wasn't obtained by calling the <c>BeginGetContext</c> method.
/// </exception>
/// <exception cref="InvalidOperationException">
/// This method was already called for the specified <paramref name="asyncResult"/>.
/// </exception>
/// <exception cref="ObjectDisposedException">
/// This listener has been closed.
/// </exception>
public HttpListenerContext EndGetContext (IAsyncResult asyncResult)
{
CheckDisposed ();
if (asyncResult == null)
throw new ArgumentNullException ("asyncResult");
var ares = asyncResult as HttpListenerAsyncResult;
if (ares == null)
throw new ArgumentException ("A wrong IAsyncResult.", "asyncResult");
if (ares.EndCalled)
throw new InvalidOperationException ("This IAsyncResult cannot be reused.");
ares.EndCalled = true;
if (!ares.IsCompleted)
ares.AsyncWaitHandle.WaitOne ();
return ares.GetContext (); // This may throw an exception.
}
/// <summary>
/// Gets an incoming request.
/// </summary>
/// <remarks>
/// This method waits for an incoming request, and returns when a request is received.
/// </remarks>
/// <returns>
/// A <see cref="HttpListenerContext"/> that represents a request.
/// </returns>
/// <exception cref="InvalidOperationException">
/// <para>
/// This listener has no URI prefix on which listens.
/// </para>
/// <para>
/// -or-
/// </para>
/// <para>
/// This listener hasn't been started, or is currently stopped.
/// </para>
/// </exception>
/// <exception cref="ObjectDisposedException">
/// This listener has been closed.
/// </exception>
public HttpListenerContext GetContext ()
{
CheckDisposed ();
if (_prefixes.Count == 0)
throw new InvalidOperationException ("The listener has no URI prefix on which listens.");
if (!_listening)
throw new InvalidOperationException ("The listener hasn't been started.");
var ares = BeginGetContext (new HttpListenerAsyncResult (null, null));
ares.InGet = true;
return EndGetContext (ares);
}
/// <summary>
/// Starts receiving incoming requests.
/// </summary>
/// <exception cref="ObjectDisposedException">
/// This listener has been closed.
/// </exception>
public void Start ()
{
CheckDisposed ();
if (_listening)
return;
EndPointManager.AddListener (this);
_listening = true;
}
/// <summary>
/// Stops receiving incoming requests.
/// </summary>
/// <exception cref="ObjectDisposedException">
/// This listener has been closed.
/// </exception>
public void Stop ()
{
CheckDisposed ();
if (!_listening)
return;
_listening = false;
EndPointManager.RemoveListener (this);
lock (_ctxRegistrySync)
cleanupContextQueue (true);
cleanupContextRegistry ();
cleanupConnections ();
cleanupWaitQueue (new HttpListenerException (995, "The listener is stopped."));
}
#endregion
#region Explicit Interface Implementations
/// <summary>
/// Releases all resources used by the listener.
/// </summary>
void IDisposable.Dispose ()
{
if (_disposed)
return;
close (true);
}
#endregion
}
}

View File

@ -0,0 +1,198 @@
#region License
/*
* HttpListenerAsyncResult.cs
*
* This code is derived from ListenerAsyncResult.cs (System.Net) of Mono
* (http://www.mono-project.com).
*
* The MIT License
*
* Copyright (c) 2005 Ximian, Inc. (http://www.ximian.com)
* Copyright (c) 2012-2016 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
#region Authors
/*
* Authors:
* - Gonzalo Paniagua Javier <gonzalo@ximian.com>
*/
#endregion
#region Contributors
/*
* Contributors:
* - Nicholas Devenish
*/
#endregion
using System;
using System.Threading;
namespace WebSocketSharp.Net
{
internal class HttpListenerAsyncResult : IAsyncResult
{
#region Private Fields
private AsyncCallback _callback;
private bool _completed;
private HttpListenerContext _context;
private bool _endCalled;
private Exception _exception;
private bool _inGet;
private object _state;
private object _sync;
private bool _syncCompleted;
private ManualResetEvent _waitHandle;
#endregion
#region Internal Constructors
internal HttpListenerAsyncResult (AsyncCallback callback, object state)
{
_callback = callback;
_state = state;
_sync = new object ();
}
#endregion
#region Internal Properties
internal bool EndCalled {
get {
return _endCalled;
}
set {
_endCalled = value;
}
}
internal bool InGet {
get {
return _inGet;
}
set {
_inGet = value;
}
}
#endregion
#region Public Properties
public object AsyncState {
get {
return _state;
}
}
public WaitHandle AsyncWaitHandle {
get {
lock (_sync)
return _waitHandle ?? (_waitHandle = new ManualResetEvent (_completed));
}
}
public bool CompletedSynchronously {
get {
return _syncCompleted;
}
}
public bool IsCompleted {
get {
lock (_sync)
return _completed;
}
}
#endregion
#region Private Methods
private static void complete (HttpListenerAsyncResult asyncResult)
{
lock (asyncResult._sync) {
asyncResult._completed = true;
var waitHandle = asyncResult._waitHandle;
if (waitHandle != null)
waitHandle.Set ();
}
var callback = asyncResult._callback;
if (callback == null)
return;
ThreadPool.QueueUserWorkItem (
state => {
try {
callback (asyncResult);
}
catch {
}
},
null
);
}
#endregion
#region Internal Methods
internal void Complete (Exception exception)
{
_exception = _inGet && (exception is ObjectDisposedException)
? new HttpListenerException (995, "The listener is closed.")
: exception;
complete (this);
}
internal void Complete (HttpListenerContext context)
{
Complete (context, false);
}
internal void Complete (HttpListenerContext context, bool syncCompleted)
{
_context = context;
_syncCompleted = syncCompleted;
complete (this);
}
internal HttpListenerContext GetContext ()
{
if (_exception != null)
throw _exception;
return _context;
}
#endregion
}
}

View File

@ -0,0 +1,256 @@
#region License
/*
* HttpListenerContext.cs
*
* This code is derived from HttpListenerContext.cs (System.Net) of Mono
* (http://www.mono-project.com).
*
* The MIT License
*
* Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
* Copyright (c) 2012-2016 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
#region Authors
/*
* Authors:
* - Gonzalo Paniagua Javier <gonzalo@novell.com>
*/
#endregion
using System;
using System.Security.Principal;
using WebSocketSharp.Net.WebSockets;
namespace WebSocketSharp.Net
{
/// <summary>
/// Provides the access to the HTTP request and response objects used by
/// the <see cref="HttpListener"/>.
/// </summary>
/// <remarks>
/// This class cannot be inherited.
/// </remarks>
public sealed class HttpListenerContext
{
#region Private Fields
private HttpConnection _connection;
private string _error;
private int _errorStatus;
private HttpListener _listener;
private HttpListenerRequest _request;
private HttpListenerResponse _response;
private IPrincipal _user;
private HttpListenerWebSocketContext _websocketContext;
#endregion
#region Internal Constructors
internal HttpListenerContext (HttpConnection connection)
{
_connection = connection;
_errorStatus = 400;
_request = new HttpListenerRequest (this);
_response = new HttpListenerResponse (this);
}
#endregion
#region Internal Properties
internal HttpConnection Connection {
get {
return _connection;
}
}
internal string ErrorMessage {
get {
return _error;
}
set {
_error = value;
}
}
internal int ErrorStatus {
get {
return _errorStatus;
}
set {
_errorStatus = value;
}
}
internal bool HasError {
get {
return _error != null;
}
}
internal HttpListener Listener {
get {
return _listener;
}
set {
_listener = value;
}
}
#endregion
#region Public Properties
/// <summary>
/// Gets the HTTP request object that represents a client request.
/// </summary>
/// <value>
/// A <see cref="HttpListenerRequest"/> that represents the client request.
/// </value>
public HttpListenerRequest Request {
get {
return _request;
}
}
/// <summary>
/// Gets the HTTP response object used to send a response to the client.
/// </summary>
/// <value>
/// A <see cref="HttpListenerResponse"/> that represents a response to the client request.
/// </value>
public HttpListenerResponse Response {
get {
return _response;
}
}
/// <summary>
/// Gets the client information (identity, authentication, and security roles).
/// </summary>
/// <value>
/// A <see cref="IPrincipal"/> instance that represents the client information.
/// </value>
public IPrincipal User {
get {
return _user;
}
}
#endregion
#region Internal Methods
internal bool Authenticate ()
{
var schm = _listener.SelectAuthenticationScheme (_request);
if (schm == AuthenticationSchemes.Anonymous)
return true;
if (schm == AuthenticationSchemes.None) {
_response.Close (HttpStatusCode.Forbidden);
return false;
}
var realm = _listener.GetRealm ();
var user =
HttpUtility.CreateUser (
_request.Headers["Authorization"],
schm,
realm,
_request.HttpMethod,
_listener.GetUserCredentialsFinder ()
);
if (user == null || !user.Identity.IsAuthenticated) {
_response.CloseWithAuthChallenge (new AuthenticationChallenge (schm, realm).ToString ());
return false;
}
_user = user;
return true;
}
internal bool Register ()
{
return _listener.RegisterContext (this);
}
internal void Unregister ()
{
_listener.UnregisterContext (this);
}
#endregion
#region Public Methods
/// <summary>
/// Accepts a WebSocket handshake request.
/// </summary>
/// <returns>
/// A <see cref="HttpListenerWebSocketContext"/> that represents
/// the WebSocket handshake request.
/// </returns>
/// <param name="protocol">
/// A <see cref="string"/> that represents the subprotocol supported on
/// this WebSocket connection.
/// </param>
/// <exception cref="ArgumentException">
/// <para>
/// <paramref name="protocol"/> is empty.
/// </para>
/// <para>
/// -or-
/// </para>
/// <para>
/// <paramref name="protocol"/> contains an invalid character.
/// </para>
/// </exception>
/// <exception cref="InvalidOperationException">
/// This method has already been called.
/// </exception>
public HttpListenerWebSocketContext AcceptWebSocket (string protocol)
{
if (_websocketContext != null)
throw new InvalidOperationException ("The accepting is already in progress.");
if (protocol != null) {
if (protocol.Length == 0)
throw new ArgumentException ("An empty string.", "protocol");
if (!protocol.IsToken ())
throw new ArgumentException ("Contains an invalid character.", "protocol");
}
_websocketContext = new HttpListenerWebSocketContext (this, protocol);
return _websocketContext;
}
#endregion
}
}

View File

@ -0,0 +1,127 @@
#region License
/*
* HttpListenerException.cs
*
* This code is derived from System.Net.HttpListenerException.cs of Mono
* (http://www.mono-project.com).
*
* The MIT License
*
* Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
* Copyright (c) 2012-2014 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
#region Authors
/*
* Authors:
* - Gonzalo Paniagua Javier <gonzalo@novell.com>
*/
#endregion
using System;
using System.ComponentModel;
using System.Runtime.Serialization;
namespace WebSocketSharp.Net
{
/// <summary>
/// The exception that is thrown when a <see cref="HttpListener"/> gets an error
/// processing an HTTP request.
/// </summary>
[Serializable]
public class HttpListenerException : Win32Exception
{
#region Protected Constructors
/// <summary>
/// Initializes a new instance of the <see cref="HttpListenerException"/> class from
/// the specified <see cref="SerializationInfo"/> and <see cref="StreamingContext"/>.
/// </summary>
/// <param name="serializationInfo">
/// A <see cref="SerializationInfo"/> that contains the serialized object data.
/// </param>
/// <param name="streamingContext">
/// A <see cref="StreamingContext"/> that specifies the source for the deserialization.
/// </param>
protected HttpListenerException (
SerializationInfo serializationInfo, StreamingContext streamingContext)
: base (serializationInfo, streamingContext)
{
}
#endregion
#region Public Constructors
/// <summary>
/// Initializes a new instance of the <see cref="HttpListenerException"/> class.
/// </summary>
public HttpListenerException ()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="HttpListenerException"/> class
/// with the specified <paramref name="errorCode"/>.
/// </summary>
/// <param name="errorCode">
/// An <see cref="int"/> that identifies the error.
/// </param>
public HttpListenerException (int errorCode)
: base (errorCode)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="HttpListenerException"/> class
/// with the specified <paramref name="errorCode"/> and <paramref name="message"/>.
/// </summary>
/// <param name="errorCode">
/// An <see cref="int"/> that identifies the error.
/// </param>
/// <param name="message">
/// A <see cref="string"/> that describes the error.
/// </param>
public HttpListenerException (int errorCode, string message)
: base (errorCode, message)
{
}
#endregion
#region Public Properties
/// <summary>
/// Gets the error code that identifies the error that occurred.
/// </summary>
/// <value>
/// An <see cref="int"/> that identifies the error.
/// </value>
public override int ErrorCode {
get {
return NativeErrorCode;
}
}
#endregion
}
}

View File

@ -0,0 +1,228 @@
#region License
/*
* HttpListenerPrefix.cs
*
* This code is derived from ListenerPrefix.cs (System.Net) of Mono
* (http://www.mono-project.com).
*
* The MIT License
*
* Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
* Copyright (c) 2012-2016 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
#region Authors
/*
* Authors:
* - Gonzalo Paniagua Javier <gonzalo@novell.com>
* - Oleg Mihailik <mihailik@gmail.com>
*/
#endregion
using System;
using System.Net;
namespace WebSocketSharp.Net
{
internal sealed class HttpListenerPrefix
{
#region Private Fields
private string _host;
private HttpListener _listener;
private string _original;
private string _path;
private string _port;
private string _prefix;
private bool _secure;
#endregion
#region Internal Constructors
/// <summary>
/// Initializes a new instance of the <see cref="HttpListenerPrefix"/> class with
/// the specified <paramref name="uriPrefix"/>.
/// </summary>
/// <remarks>
/// This constructor must be called after calling the CheckPrefix method.
/// </remarks>
/// <param name="uriPrefix">
/// A <see cref="string"/> that represents the URI prefix.
/// </param>
internal HttpListenerPrefix (string uriPrefix)
{
_original = uriPrefix;
parse (uriPrefix);
}
#endregion
#region Public Properties
public string Host {
get {
return _host;
}
}
public bool IsSecure {
get {
return _secure;
}
}
public HttpListener Listener {
get {
return _listener;
}
set {
_listener = value;
}
}
public string Original {
get {
return _original;
}
}
public string Path {
get {
return _path;
}
}
public string Port {
get {
return _port;
}
}
#endregion
#region Private Methods
private void parse (string uriPrefix)
{
if (uriPrefix.StartsWith ("https"))
_secure = true;
var len = uriPrefix.Length;
var startHost = uriPrefix.IndexOf (':') + 3;
var root = uriPrefix.IndexOf ('/', startHost + 1, len - startHost - 1);
var colon = uriPrefix.LastIndexOf (':', root - 1, root - startHost - 1);
if (uriPrefix[root - 1] != ']' && colon > startHost) {
_host = uriPrefix.Substring (startHost, colon - startHost);
_port = uriPrefix.Substring (colon + 1, root - colon - 1);
}
else {
_host = uriPrefix.Substring (startHost, root - startHost);
_port = _secure ? "443" : "80";
}
_path = uriPrefix.Substring (root);
_prefix =
String.Format ("http{0}://{1}:{2}{3}", _secure ? "s" : "", _host, _port, _path);
}
#endregion
#region Public Methods
public static void CheckPrefix (string uriPrefix)
{
if (uriPrefix == null)
throw new ArgumentNullException ("uriPrefix");
var len = uriPrefix.Length;
if (len == 0)
throw new ArgumentException ("An empty string.", "uriPrefix");
if (!(uriPrefix.StartsWith ("http://") || uriPrefix.StartsWith ("https://")))
throw new ArgumentException ("The scheme isn't 'http' or 'https'.", "uriPrefix");
var startHost = uriPrefix.IndexOf (':') + 3;
if (startHost >= len)
throw new ArgumentException ("No host is specified.", "uriPrefix");
if (uriPrefix[startHost] == ':')
throw new ArgumentException ("No host is specified.", "uriPrefix");
var root = uriPrefix.IndexOf ('/', startHost, len - startHost);
if (root == startHost)
throw new ArgumentException ("No host is specified.", "uriPrefix");
if (root == -1 || uriPrefix[len - 1] != '/')
throw new ArgumentException ("Ends without '/'.", "uriPrefix");
if (uriPrefix[root - 1] == ':')
throw new ArgumentException ("No port is specified.", "uriPrefix");
if (root == len - 2)
throw new ArgumentException ("No path is specified.", "uriPrefix");
}
/// <summary>
/// Determines whether this instance and the specified <see cref="Object"/> have the same value.
/// </summary>
/// <remarks>
/// This method will be required to detect duplicates in any collection.
/// </remarks>
/// <param name="obj">
/// An <see cref="Object"/> to compare to this instance.
/// </param>
/// <returns>
/// <c>true</c> if <paramref name="obj"/> is a <see cref="HttpListenerPrefix"/> and
/// its value is the same as this instance; otherwise, <c>false</c>.
/// </returns>
public override bool Equals (Object obj)
{
var pref = obj as HttpListenerPrefix;
return pref != null && pref._prefix == _prefix;
}
/// <summary>
/// Gets the hash code for this instance.
/// </summary>
/// <remarks>
/// This method will be required to detect duplicates in any collection.
/// </remarks>
/// <returns>
/// An <see cref="int"/> that represents the hash code.
/// </returns>
public override int GetHashCode ()
{
return _prefix.GetHashCode ();
}
public override string ToString ()
{
return _prefix;
}
#endregion
}
}

View File

@ -0,0 +1,278 @@
#region License
/*
* HttpListenerPrefixCollection.cs
*
* This code is derived from HttpListenerPrefixCollection.cs (System.Net) of Mono
* (http://www.mono-project.com).
*
* The MIT License
*
* Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
* Copyright (c) 2012-2015 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
#region Authors
/*
* Authors:
* - Gonzalo Paniagua Javier <gonzalo@novell.com>
*/
#endregion
using System;
using System.Collections;
using System.Collections.Generic;
namespace WebSocketSharp.Net
{
/// <summary>
/// Provides the collection used to store the URI prefixes for the <see cref="HttpListener"/>.
/// </summary>
/// <remarks>
/// The <see cref="HttpListener"/> responds to the request which has a requested URI that
/// the prefixes most closely match.
/// </remarks>
public class HttpListenerPrefixCollection : ICollection<string>, IEnumerable<string>, IEnumerable
{
#region Private Fields
private HttpListener _listener;
private List<string> _prefixes;
#endregion
#region Internal Constructors
internal HttpListenerPrefixCollection (HttpListener listener)
{
_listener = listener;
_prefixes = new List<string> ();
}
#endregion
#region Public Properties
/// <summary>
/// Gets the number of prefixes in the collection.
/// </summary>
/// <value>
/// An <see cref="int"/> that represents the number of prefixes.
/// </value>
public int Count {
get {
return _prefixes.Count;
}
}
/// <summary>
/// Gets a value indicating whether the access to the collection is read-only.
/// </summary>
/// <value>
/// Always returns <c>false</c>.
/// </value>
public bool IsReadOnly {
get {
return false;
}
}
/// <summary>
/// Gets a value indicating whether the access to the collection is synchronized.
/// </summary>
/// <value>
/// Always returns <c>false</c>.
/// </value>
public bool IsSynchronized {
get {
return false;
}
}
#endregion
#region Public Methods
/// <summary>
/// Adds the specified <paramref name="uriPrefix"/> to the collection.
/// </summary>
/// <param name="uriPrefix">
/// A <see cref="string"/> that represents the URI prefix to add. The prefix must be
/// a well-formed URI prefix with http or https scheme, and must end with a <c>'/'</c>.
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="uriPrefix"/> is <see langword="null"/>.
/// </exception>
/// <exception cref="ArgumentException">
/// <paramref name="uriPrefix"/> is invalid.
/// </exception>
/// <exception cref="ObjectDisposedException">
/// The <see cref="HttpListener"/> associated with this collection is closed.
/// </exception>
public void Add (string uriPrefix)
{
_listener.CheckDisposed ();
HttpListenerPrefix.CheckPrefix (uriPrefix);
if (_prefixes.Contains (uriPrefix))
return;
_prefixes.Add (uriPrefix);
if (_listener.IsListening)
EndPointManager.AddPrefix (uriPrefix, _listener);
}
/// <summary>
/// Removes all URI prefixes from the collection.
/// </summary>
/// <exception cref="ObjectDisposedException">
/// The <see cref="HttpListener"/> associated with this collection is closed.
/// </exception>
public void Clear ()
{
_listener.CheckDisposed ();
_prefixes.Clear ();
if (_listener.IsListening)
EndPointManager.RemoveListener (_listener);
}
/// <summary>
/// Returns a value indicating whether the collection contains the specified
/// <paramref name="uriPrefix"/>.
/// </summary>
/// <returns>
/// <c>true</c> if the collection contains <paramref name="uriPrefix"/>;
/// otherwise, <c>false</c>.
/// </returns>
/// <param name="uriPrefix">
/// A <see cref="string"/> that represents the URI prefix to test.
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="uriPrefix"/> is <see langword="null"/>.
/// </exception>
/// <exception cref="ObjectDisposedException">
/// The <see cref="HttpListener"/> associated with this collection is closed.
/// </exception>
public bool Contains (string uriPrefix)
{
_listener.CheckDisposed ();
if (uriPrefix == null)
throw new ArgumentNullException ("uriPrefix");
return _prefixes.Contains (uriPrefix);
}
/// <summary>
/// Copies the contents of the collection to the specified <see cref="Array"/>.
/// </summary>
/// <param name="array">
/// An <see cref="Array"/> that receives the URI prefix strings in the collection.
/// </param>
/// <param name="offset">
/// An <see cref="int"/> that represents the zero-based index in <paramref name="array"/>
/// at which copying begins.
/// </param>
/// <exception cref="ObjectDisposedException">
/// The <see cref="HttpListener"/> associated with this collection is closed.
/// </exception>
public void CopyTo (Array array, int offset)
{
_listener.CheckDisposed ();
((ICollection) _prefixes).CopyTo (array, offset);
}
/// <summary>
/// Copies the contents of the collection to the specified array of <see cref="string"/>.
/// </summary>
/// <param name="array">
/// An array of <see cref="string"/> that receives the URI prefix strings in the collection.
/// </param>
/// <param name="offset">
/// An <see cref="int"/> that represents the zero-based index in <paramref name="array"/>
/// at which copying begins.
/// </param>
/// <exception cref="ObjectDisposedException">
/// The <see cref="HttpListener"/> associated with this collection is closed.
/// </exception>
public void CopyTo (string[] array, int offset)
{
_listener.CheckDisposed ();
_prefixes.CopyTo (array, offset);
}
/// <summary>
/// Gets the enumerator used to iterate through the <see cref="HttpListenerPrefixCollection"/>.
/// </summary>
/// <returns>
/// An <see cref="T:System.Collections.Generic.IEnumerator{string}"/> instance used to iterate
/// through the collection.
/// </returns>
public IEnumerator<string> GetEnumerator ()
{
return _prefixes.GetEnumerator ();
}
/// <summary>
/// Removes the specified <paramref name="uriPrefix"/> from the collection.
/// </summary>
/// <returns>
/// <c>true</c> if <paramref name="uriPrefix"/> is successfully found and removed;
/// otherwise, <c>false</c>.
/// </returns>
/// <param name="uriPrefix">
/// A <see cref="string"/> that represents the URI prefix to remove.
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="uriPrefix"/> is <see langword="null"/>.
/// </exception>
/// <exception cref="ObjectDisposedException">
/// The <see cref="HttpListener"/> associated with this collection is closed.
/// </exception>
public bool Remove (string uriPrefix)
{
_listener.CheckDisposed ();
if (uriPrefix == null)
throw new ArgumentNullException ("uriPrefix");
var ret = _prefixes.Remove (uriPrefix);
if (ret && _listener.IsListening)
EndPointManager.RemovePrefix (uriPrefix, _listener);
return ret;
}
#endregion
#region Explicit Interface Implementations
/// <summary>
/// Gets the enumerator used to iterate through the <see cref="HttpListenerPrefixCollection"/>.
/// </summary>
/// <returns>
/// An <see cref="IEnumerator"/> instance used to iterate through the collection.
/// </returns>
IEnumerator IEnumerable.GetEnumerator ()
{
return _prefixes.GetEnumerator ();
}
#endregion
}
}

View File

@ -0,0 +1,911 @@
#region License
/*
* HttpListenerRequest.cs
*
* This code is derived from HttpListenerRequest.cs (System.Net) of Mono
* (http://www.mono-project.com).
*
* The MIT License
*
* Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
* Copyright (c) 2012-2018 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
#region Authors
/*
* Authors:
* - Gonzalo Paniagua Javier <gonzalo@novell.com>
*/
#endregion
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.IO;
using System.Security.Cryptography.X509Certificates;
using System.Text;
namespace WebSocketSharp.Net
{
/// <summary>
/// Represents an incoming HTTP request to a <see cref="HttpListener"/>
/// instance.
/// </summary>
/// <remarks>
/// This class cannot be inherited.
/// </remarks>
public sealed class HttpListenerRequest
{
#region Private Fields
private static readonly byte[] _100continue;
private string[] _acceptTypes;
private bool _chunked;
private HttpConnection _connection;
private Encoding _contentEncoding;
private long _contentLength;
private HttpListenerContext _context;
private CookieCollection _cookies;
private WebHeaderCollection _headers;
private string _httpMethod;
private Stream _inputStream;
private Version _protocolVersion;
private NameValueCollection _queryString;
private string _rawUrl;
private Guid _requestTraceIdentifier;
private Uri _url;
private Uri _urlReferrer;
private bool _urlSet;
private string _userHostName;
private string[] _userLanguages;
#endregion
#region Static Constructor
static HttpListenerRequest ()
{
_100continue = Encoding.ASCII.GetBytes ("HTTP/1.1 100 Continue\r\n\r\n");
}
#endregion
#region Internal Constructors
internal HttpListenerRequest (HttpListenerContext context)
{
_context = context;
_connection = context.Connection;
_contentLength = -1;
_headers = new WebHeaderCollection ();
_requestTraceIdentifier = Guid.NewGuid ();
}
#endregion
#region Public Properties
/// <summary>
/// Gets the media types that are acceptable for the client.
/// </summary>
/// <value>
/// <para>
/// An array of <see cref="string"/> that contains the names of the media
/// types specified in the value of the Accept header.
/// </para>
/// <para>
/// <see langword="null"/> if the header is not present.
/// </para>
/// </value>
public string[] AcceptTypes {
get {
var val = _headers["Accept"];
if (val == null)
return null;
if (_acceptTypes == null) {
_acceptTypes = val
.SplitHeaderValue (',')
.Trim ()
.ToList ()
.ToArray ();
}
return _acceptTypes;
}
}
/// <summary>
/// Gets an error code that identifies a problem with the certificate
/// provided by the client.
/// </summary>
/// <value>
/// An <see cref="int"/> that represents an error code.
/// </value>
/// <exception cref="NotSupportedException">
/// This property is not supported.
/// </exception>
public int ClientCertificateError {
get {
throw new NotSupportedException ();
}
}
/// <summary>
/// Gets the encoding for the entity body data included in the request.
/// </summary>
/// <value>
/// <para>
/// A <see cref="Encoding"/> converted from the charset value of the
/// Content-Type header.
/// </para>
/// <para>
/// <see cref="Encoding.UTF8"/> if the charset value is not available.
/// </para>
/// </value>
public Encoding ContentEncoding {
get {
if (_contentEncoding == null)
_contentEncoding = getContentEncoding () ?? Encoding.UTF8;
return _contentEncoding;
}
}
/// <summary>
/// Gets the length in bytes of the entity body data included in the
/// request.
/// </summary>
/// <value>
/// <para>
/// A <see cref="long"/> converted from the value of the Content-Length
/// header.
/// </para>
/// <para>
/// -1 if the header is not present.
/// </para>
/// </value>
public long ContentLength64 {
get {
return _contentLength;
}
}
/// <summary>
/// Gets the media type of the entity body data included in the request.
/// </summary>
/// <value>
/// <para>
/// A <see cref="string"/> that represents the value of the Content-Type
/// header.
/// </para>
/// <para>
/// <see langword="null"/> if the header is not present.
/// </para>
/// </value>
public string ContentType {
get {
return _headers["Content-Type"];
}
}
/// <summary>
/// Gets the cookies included in the request.
/// </summary>
/// <value>
/// <para>
/// A <see cref="CookieCollection"/> that contains the cookies.
/// </para>
/// <para>
/// An empty collection if not included.
/// </para>
/// </value>
public CookieCollection Cookies {
get {
if (_cookies == null)
_cookies = _headers.GetCookies (false);
return _cookies;
}
}
/// <summary>
/// Gets a value indicating whether the request has the entity body data.
/// </summary>
/// <value>
/// <c>true</c> if the request has the entity body data; otherwise,
/// <c>false</c>.
/// </value>
public bool HasEntityBody {
get {
return _contentLength > 0 || _chunked;
}
}
/// <summary>
/// Gets the headers included in the request.
/// </summary>
/// <value>
/// A <see cref="NameValueCollection"/> that contains the headers.
/// </value>
public NameValueCollection Headers {
get {
return _headers;
}
}
/// <summary>
/// Gets the HTTP method specified by the client.
/// </summary>
/// <value>
/// A <see cref="string"/> that represents the HTTP method specified in
/// the request line.
/// </value>
public string HttpMethod {
get {
return _httpMethod;
}
}
/// <summary>
/// Gets a stream that contains the entity body data included in
/// the request.
/// </summary>
/// <value>
/// <para>
/// A <see cref="Stream"/> that contains the entity body data.
/// </para>
/// <para>
/// <see cref="Stream.Null"/> if the entity body data is not available.
/// </para>
/// </value>
public Stream InputStream {
get {
if (_inputStream == null)
_inputStream = getInputStream () ?? Stream.Null;
return _inputStream;
}
}
/// <summary>
/// Gets a value indicating whether the client is authenticated.
/// </summary>
/// <value>
/// <c>true</c> if the client is authenticated; otherwise, <c>false</c>.
/// </value>
public bool IsAuthenticated {
get {
return _context.User != null;
}
}
/// <summary>
/// Gets a value indicating whether the request is sent from the local
/// computer.
/// </summary>
/// <value>
/// <c>true</c> if the request is sent from the same computer as the server;
/// otherwise, <c>false</c>.
/// </value>
public bool IsLocal {
get {
return _connection.IsLocal;
}
}
/// <summary>
/// Gets a value indicating whether a secure connection is used to send
/// the request.
/// </summary>
/// <value>
/// <c>true</c> if the connection is secure; otherwise, <c>false</c>.
/// </value>
public bool IsSecureConnection {
get {
return _connection.IsSecure;
}
}
/// <summary>
/// Gets a value indicating whether the request is a WebSocket handshake
/// request.
/// </summary>
/// <value>
/// <c>true</c> if the request is a WebSocket handshake request; otherwise,
/// <c>false</c>.
/// </value>
public bool IsWebSocketRequest {
get {
return _httpMethod == "GET"
&& _protocolVersion > HttpVersion.Version10
&& _headers.Upgrades ("websocket");
}
}
/// <summary>
/// Gets a value indicating whether a persistent connection is requested.
/// </summary>
/// <value>
/// <c>true</c> if the request specifies that the connection is kept open;
/// otherwise, <c>false</c>.
/// </value>
public bool KeepAlive {
get {
return _headers.KeepsAlive (_protocolVersion);
}
}
/// <summary>
/// Gets the endpoint to which the request is sent.
/// </summary>
/// <value>
/// A <see cref="System.Net.IPEndPoint"/> that represents the server IP
/// address and port number.
/// </value>
public System.Net.IPEndPoint LocalEndPoint {
get {
return _connection.LocalEndPoint;
}
}
/// <summary>
/// Gets the HTTP version specified by the client.
/// </summary>
/// <value>
/// A <see cref="Version"/> that represents the HTTP version specified in
/// the request line.
/// </value>
public Version ProtocolVersion {
get {
return _protocolVersion;
}
}
/// <summary>
/// Gets the query string included in the request.
/// </summary>
/// <value>
/// <para>
/// A <see cref="NameValueCollection"/> that contains the query
/// parameters.
/// </para>
/// <para>
/// An empty collection if not included.
/// </para>
/// </value>
public NameValueCollection QueryString {
get {
if (_queryString == null) {
var url = Url;
_queryString = QueryStringCollection.Parse (
url != null ? url.Query : null,
Encoding.UTF8
);
}
return _queryString;
}
}
/// <summary>
/// Gets the raw URL specified by the client.
/// </summary>
/// <value>
/// A <see cref="string"/> that represents the request target specified in
/// the request line.
/// </value>
public string RawUrl {
get {
return _rawUrl;
}
}
/// <summary>
/// Gets the endpoint from which the request is sent.
/// </summary>
/// <value>
/// A <see cref="System.Net.IPEndPoint"/> that represents the client IP
/// address and port number.
/// </value>
public System.Net.IPEndPoint RemoteEndPoint {
get {
return _connection.RemoteEndPoint;
}
}
/// <summary>
/// Gets the trace identifier of the request.
/// </summary>
/// <value>
/// A <see cref="Guid"/> that represents the trace identifier.
/// </value>
public Guid RequestTraceIdentifier {
get {
return _requestTraceIdentifier;
}
}
/// <summary>
/// Gets the URL requested by the client.
/// </summary>
/// <value>
/// <para>
/// A <see cref="Uri"/> that represents the URL parsed from the request.
/// </para>
/// <para>
/// <see langword="null"/> if the URL cannot be parsed.
/// </para>
/// </value>
public Uri Url {
get {
if (!_urlSet) {
_url = HttpUtility.CreateRequestUrl (
_rawUrl,
_userHostName ?? UserHostAddress,
IsWebSocketRequest,
IsSecureConnection
);
_urlSet = true;
}
return _url;
}
}
/// <summary>
/// Gets the URI of the resource from which the requested URL was obtained.
/// </summary>
/// <value>
/// <para>
/// A <see cref="Uri"/> converted from the value of the Referer header.
/// </para>
/// <para>
/// <see langword="null"/> if the header value is not available.
/// </para>
/// </value>
public Uri UrlReferrer {
get {
var val = _headers["Referer"];
if (val == null)
return null;
if (_urlReferrer == null)
_urlReferrer = val.ToUri ();
return _urlReferrer;
}
}
/// <summary>
/// Gets the user agent from which the request is originated.
/// </summary>
/// <value>
/// <para>
/// A <see cref="string"/> that represents the value of the User-Agent
/// header.
/// </para>
/// <para>
/// <see langword="null"/> if the header is not present.
/// </para>
/// </value>
public string UserAgent {
get {
return _headers["User-Agent"];
}
}
/// <summary>
/// Gets the IP address and port number to which the request is sent.
/// </summary>
/// <value>
/// A <see cref="string"/> that represents the server IP address and port
/// number.
/// </value>
public string UserHostAddress {
get {
return _connection.LocalEndPoint.ToString ();
}
}
/// <summary>
/// Gets the server host name requested by the client.
/// </summary>
/// <value>
/// <para>
/// A <see cref="string"/> that represents the value of the Host header.
/// </para>
/// <para>
/// It includes the port number if provided.
/// </para>
/// <para>
/// <see langword="null"/> if the header is not present.
/// </para>
/// </value>
public string UserHostName {
get {
return _userHostName;
}
}
/// <summary>
/// Gets the natural languages that are acceptable for the client.
/// </summary>
/// <value>
/// <para>
/// An array of <see cref="string"/> that contains the names of the
/// natural languages specified in the value of the Accept-Language
/// header.
/// </para>
/// <para>
/// <see langword="null"/> if the header is not present.
/// </para>
/// </value>
public string[] UserLanguages {
get {
var val = _headers["Accept-Language"];
if (val == null)
return null;
if (_userLanguages == null)
_userLanguages = val.Split (',').Trim ().ToList ().ToArray ();
return _userLanguages;
}
}
#endregion
#region Private Methods
private void finishInitialization10 ()
{
var transferEnc = _headers["Transfer-Encoding"];
if (transferEnc != null) {
_context.ErrorMessage = "Invalid Transfer-Encoding header";
return;
}
if (_httpMethod == "POST") {
if (_contentLength == -1) {
_context.ErrorMessage = "Content-Length header required";
return;
}
if (_contentLength == 0) {
_context.ErrorMessage = "Invalid Content-Length header";
return;
}
}
}
private Encoding getContentEncoding ()
{
var val = _headers["Content-Type"];
if (val == null)
return null;
Encoding ret;
HttpUtility.TryGetEncoding (val, out ret);
return ret;
}
private RequestStream getInputStream ()
{
return _contentLength > 0 || _chunked
? _connection.GetRequestStream (_contentLength, _chunked)
: null;
}
#endregion
#region Internal Methods
internal void AddHeader (string headerField)
{
var start = headerField[0];
if (start == ' ' || start == '\t') {
_context.ErrorMessage = "Invalid header field";
return;
}
var colon = headerField.IndexOf (':');
if (colon < 1) {
_context.ErrorMessage = "Invalid header field";
return;
}
var name = headerField.Substring (0, colon).Trim ();
if (name.Length == 0 || !name.IsToken ()) {
_context.ErrorMessage = "Invalid header name";
return;
}
var val = colon < headerField.Length - 1
? headerField.Substring (colon + 1).Trim ()
: String.Empty;
_headers.InternalSet (name, val, false);
var lower = name.ToLower (CultureInfo.InvariantCulture);
if (lower == "host") {
if (_userHostName != null) {
_context.ErrorMessage = "Invalid Host header";
return;
}
if (val.Length == 0) {
_context.ErrorMessage = "Invalid Host header";
return;
}
_userHostName = val;
return;
}
if (lower == "content-length") {
if (_contentLength > -1) {
_context.ErrorMessage = "Invalid Content-Length header";
return;
}
long len;
if (!Int64.TryParse (val, out len)) {
_context.ErrorMessage = "Invalid Content-Length header";
return;
}
if (len < 0) {
_context.ErrorMessage = "Invalid Content-Length header";
return;
}
_contentLength = len;
return;
}
}
internal void FinishInitialization ()
{
if (_protocolVersion == HttpVersion.Version10) {
finishInitialization10 ();
return;
}
if (_userHostName == null) {
_context.ErrorMessage = "Host header required";
return;
}
var transferEnc = _headers["Transfer-Encoding"];
if (transferEnc != null) {
var comparison = StringComparison.OrdinalIgnoreCase;
if (!transferEnc.Equals ("chunked", comparison)) {
_context.ErrorMessage = String.Empty;
_context.ErrorStatus = 501;
return;
}
_chunked = true;
}
if (_httpMethod == "POST" || _httpMethod == "PUT") {
if (_contentLength <= 0 && !_chunked) {
_context.ErrorMessage = String.Empty;
_context.ErrorStatus = 411;
return;
}
}
var expect = _headers["Expect"];
if (expect != null) {
var comparison = StringComparison.OrdinalIgnoreCase;
if (!expect.Equals ("100-continue", comparison)) {
_context.ErrorMessage = "Invalid Expect header";
return;
}
var output = _connection.GetResponseStream ();
output.InternalWrite (_100continue, 0, _100continue.Length);
}
}
internal bool FlushInput ()
{
var input = InputStream;
if (input == Stream.Null)
return true;
var len = 2048;
if (_contentLength > 0 && _contentLength < len)
len = (int) _contentLength;
var buff = new byte[len];
while (true) {
try {
var ares = input.BeginRead (buff, 0, len, null, null);
if (!ares.IsCompleted) {
var timeout = 100;
if (!ares.AsyncWaitHandle.WaitOne (timeout))
return false;
}
if (input.EndRead (ares) <= 0)
return true;
}
catch {
return false;
}
}
}
internal bool IsUpgradeRequest (string protocol)
{
return _headers.Upgrades (protocol);
}
internal void SetRequestLine (string requestLine)
{
var parts = requestLine.Split (new[] { ' ' }, 3);
if (parts.Length < 3) {
_context.ErrorMessage = "Invalid request line (parts)";
return;
}
var method = parts[0];
if (method.Length == 0) {
_context.ErrorMessage = "Invalid request line (method)";
return;
}
var target = parts[1];
if (target.Length == 0) {
_context.ErrorMessage = "Invalid request line (target)";
return;
}
var rawVer = parts[2];
if (rawVer.Length != 8) {
_context.ErrorMessage = "Invalid request line (version)";
return;
}
if (rawVer.IndexOf ("HTTP/") != 0) {
_context.ErrorMessage = "Invalid request line (version)";
return;
}
Version ver;
if (!rawVer.Substring (5).TryCreateVersion (out ver)) {
_context.ErrorMessage = "Invalid request line (version)";
return;
}
if (ver.Major < 1) {
_context.ErrorMessage = "Invalid request line (version)";
return;
}
if (!method.IsHttpMethod (ver)) {
_context.ErrorMessage = "Invalid request line (method)";
return;
}
_httpMethod = method;
_rawUrl = target;
_protocolVersion = ver;
}
#endregion
#region Public Methods
/// <summary>
/// Begins getting the certificate provided by the client asynchronously.
/// </summary>
/// <returns>
/// An <see cref="IAsyncResult"/> instance that indicates the status of the
/// operation.
/// </returns>
/// <param name="requestCallback">
/// An <see cref="AsyncCallback"/> delegate that invokes the method called
/// when the operation is complete.
/// </param>
/// <param name="state">
/// An <see cref="object"/> that represents a user defined object to pass to
/// the callback delegate.
/// </param>
/// <exception cref="NotSupportedException">
/// This method is not supported.
/// </exception>
public IAsyncResult BeginGetClientCertificate (
AsyncCallback requestCallback, object state
)
{
throw new NotSupportedException ();
}
/// <summary>
/// Ends an asynchronous operation to get the certificate provided by the
/// client.
/// </summary>
/// <returns>
/// A <see cref="X509Certificate2"/> that represents an X.509 certificate
/// provided by the client.
/// </returns>
/// <param name="asyncResult">
/// An <see cref="IAsyncResult"/> instance returned when the operation
/// started.
/// </param>
/// <exception cref="NotSupportedException">
/// This method is not supported.
/// </exception>
public X509Certificate2 EndGetClientCertificate (IAsyncResult asyncResult)
{
throw new NotSupportedException ();
}
/// <summary>
/// Gets the certificate provided by the client.
/// </summary>
/// <returns>
/// A <see cref="X509Certificate2"/> that represents an X.509 certificate
/// provided by the client.
/// </returns>
/// <exception cref="NotSupportedException">
/// This method is not supported.
/// </exception>
public X509Certificate2 GetClientCertificate ()
{
throw new NotSupportedException ();
}
/// <summary>
/// Returns a string that represents the current instance.
/// </summary>
/// <returns>
/// A <see cref="string"/> that contains the request line and headers
/// included in the request.
/// </returns>
public override string ToString ()
{
var buff = new StringBuilder (64);
buff
.AppendFormat (
"{0} {1} HTTP/{2}\r\n", _httpMethod, _rawUrl, _protocolVersion
)
.Append (_headers.ToString ());
return buff.ToString ();
}
#endregion
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,233 @@
#region License
/*
* HttpRequestHeader.cs
*
* This code is derived from HttpRequestHeader.cs (System.Net) of Mono
* (http://www.mono-project.com).
*
* The MIT License
*
* Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
* Copyright (c) 2014-2020 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
#region Authors
/*
* Authors:
* - Gonzalo Paniagua Javier <gonzalo@novell.com>
*/
#endregion
namespace WebSocketSharp.Net
{
/// <summary>
/// Indicates the HTTP header that may be specified in a client request.
/// </summary>
/// <remarks>
/// The headers of this enumeration are defined in
/// <see href="http://tools.ietf.org/html/rfc2616#section-14">RFC 2616</see> or
/// <see href="http://tools.ietf.org/html/rfc6455#section-11.3">RFC 6455</see>.
/// </remarks>
public enum HttpRequestHeader
{
/// <summary>
/// Indicates the Cache-Control header.
/// </summary>
CacheControl,
/// <summary>
/// Indicates the Connection header.
/// </summary>
Connection,
/// <summary>
/// Indicates the Date header.
/// </summary>
Date,
/// <summary>
/// Indicates the Keep-Alive header.
/// </summary>
KeepAlive,
/// <summary>
/// Indicates the Pragma header.
/// </summary>
Pragma,
/// <summary>
/// Indicates the Trailer header.
/// </summary>
Trailer,
/// <summary>
/// Indicates the Transfer-Encoding header.
/// </summary>
TransferEncoding,
/// <summary>
/// Indicates the Upgrade header.
/// </summary>
Upgrade,
/// <summary>
/// Indicates the Via header.
/// </summary>
Via,
/// <summary>
/// Indicates the Warning header.
/// </summary>
Warning,
/// <summary>
/// Indicates the Allow header.
/// </summary>
Allow,
/// <summary>
/// Indicates the Content-Length header.
/// </summary>
ContentLength,
/// <summary>
/// Indicates the Content-Type header.
/// </summary>
ContentType,
/// <summary>
/// Indicates the Content-Encoding header.
/// </summary>
ContentEncoding,
/// <summary>
/// Indicates the Content-Language header.
/// </summary>
ContentLanguage,
/// <summary>
/// Indicates the Content-Location header.
/// </summary>
ContentLocation,
/// <summary>
/// Indicates the Content-MD5 header.
/// </summary>
ContentMd5,
/// <summary>
/// Indicates the Content-Range header.
/// </summary>
ContentRange,
/// <summary>
/// Indicates the Expires header.
/// </summary>
Expires,
/// <summary>
/// Indicates the Last-Modified header.
/// </summary>
LastModified,
/// <summary>
/// Indicates the Accept header.
/// </summary>
Accept,
/// <summary>
/// Indicates the Accept-Charset header.
/// </summary>
AcceptCharset,
/// <summary>
/// Indicates the Accept-Encoding header.
/// </summary>
AcceptEncoding,
/// <summary>
/// Indicates the Accept-Language header.
/// </summary>
AcceptLanguage,
/// <summary>
/// Indicates the Authorization header.
/// </summary>
Authorization,
/// <summary>
/// Indicates the Cookie header.
/// </summary>
Cookie,
/// <summary>
/// Indicates the Expect header.
/// </summary>
Expect,
/// <summary>
/// Indicates the From header.
/// </summary>
From,
/// <summary>
/// Indicates the Host header.
/// </summary>
Host,
/// <summary>
/// Indicates the If-Match header.
/// </summary>
IfMatch,
/// <summary>
/// Indicates the If-Modified-Since header.
/// </summary>
IfModifiedSince,
/// <summary>
/// Indicates the If-None-Match header.
/// </summary>
IfNoneMatch,
/// <summary>
/// Indicates the If-Range header.
/// </summary>
IfRange,
/// <summary>
/// Indicates the If-Unmodified-Since header.
/// </summary>
IfUnmodifiedSince,
/// <summary>
/// Indicates the Max-Forwards header.
/// </summary>
MaxForwards,
/// <summary>
/// Indicates the Proxy-Authorization header.
/// </summary>
ProxyAuthorization,
/// <summary>
/// Indicates the Referer header.
/// </summary>
Referer,
/// <summary>
/// Indicates the Range header.
/// </summary>
Range,
/// <summary>
/// Indicates the TE header.
/// </summary>
Te,
/// <summary>
/// Indicates the Translate header.
/// </summary>
Translate,
/// <summary>
/// Indicates the User-Agent header.
/// </summary>
UserAgent,
/// <summary>
/// Indicates the Sec-WebSocket-Key header.
/// </summary>
SecWebSocketKey,
/// <summary>
/// Indicates the Sec-WebSocket-Extensions header.
/// </summary>
SecWebSocketExtensions,
/// <summary>
/// Indicates the Sec-WebSocket-Protocol header.
/// </summary>
SecWebSocketProtocol,
/// <summary>
/// Indicates the Sec-WebSocket-Version header.
/// </summary>
SecWebSocketVersion
}
}

View File

@ -0,0 +1,189 @@
#region License
/*
* HttpResponseHeader.cs
*
* This code is derived from HttpResponseHeader.cs (System.Net) of Mono
* (http://www.mono-project.com).
*
* The MIT License
*
* Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
* Copyright (c) 2014-2020 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
#region Authors
/*
* Authors:
* - Gonzalo Paniagua Javier <gonzalo@novell.com>
*/
#endregion
namespace WebSocketSharp.Net
{
/// <summary>
/// Indicates the HTTP header that can be specified in a server response.
/// </summary>
/// <remarks>
/// The headers of this enumeration are defined in
/// <see href="http://tools.ietf.org/html/rfc2616#section-14">RFC 2616</see> or
/// <see href="http://tools.ietf.org/html/rfc6455#section-11.3">RFC 6455</see>.
/// </remarks>
public enum HttpResponseHeader
{
/// <summary>
/// Indicates the Cache-Control header.
/// </summary>
CacheControl,
/// <summary>
/// Indicates the Connection header.
/// </summary>
Connection,
/// <summary>
/// Indicates the Date header.
/// </summary>
Date,
/// <summary>
/// Indicates the Keep-Alive header.
/// </summary>
KeepAlive,
/// <summary>
/// Indicates the Pragma header.
/// </summary>
Pragma,
/// <summary>
/// Indicates the Trailer header.
/// </summary>
Trailer,
/// <summary>
/// Indicates the Transfer-Encoding header.
/// </summary>
TransferEncoding,
/// <summary>
/// Indicates the Upgrade header.
/// </summary>
Upgrade,
/// <summary>
/// Indicates the Via header.
/// </summary>
Via,
/// <summary>
/// Indicates the Warning header.
/// </summary>
Warning,
/// <summary>
/// Indicates the Allow header.
/// </summary>
Allow,
/// <summary>
/// Indicates the Content-Length header.
/// </summary>
ContentLength,
/// <summary>
/// Indicates the Content-Type header.
/// </summary>
ContentType,
/// <summary>
/// Indicates the Content-Encoding header.
/// </summary>
ContentEncoding,
/// <summary>
/// Indicates the Content-Language header.
/// </summary>
ContentLanguage,
/// <summary>
/// Indicates the Content-Location header.
/// </summary>
ContentLocation,
/// <summary>
/// Indicates the Content-MD5 header.
/// </summary>
ContentMd5,
/// <summary>
/// Indicates the Content-Range header.
/// </summary>
ContentRange,
/// <summary>
/// Indicates the Expires header.
/// </summary>
Expires,
/// <summary>
/// Indicates the Last-Modified header.
/// </summary>
LastModified,
/// <summary>
/// Indicates the Accept-Ranges header.
/// </summary>
AcceptRanges,
/// <summary>
/// Indicates the Age header.
/// </summary>
Age,
/// <summary>
/// Indicates the ETag header.
/// </summary>
ETag,
/// <summary>
/// Indicates the Location header.
/// </summary>
Location,
/// <summary>
/// Indicates the Proxy-Authenticate header.
/// </summary>
ProxyAuthenticate,
/// <summary>
/// Indicates the Retry-After header.
/// </summary>
RetryAfter,
/// <summary>
/// Indicates the Server header.
/// </summary>
Server,
/// <summary>
/// Indicates the Set-Cookie header.
/// </summary>
SetCookie,
/// <summary>
/// Indicates the Vary header.
/// </summary>
Vary,
/// <summary>
/// Indicates the WWW-Authenticate header.
/// </summary>
WwwAuthenticate,
/// <summary>
/// Indicates the Sec-WebSocket-Extensions header.
/// </summary>
SecWebSocketExtensions,
/// <summary>
/// Indicates the Sec-WebSocket-Accept header.
/// </summary>
SecWebSocketAccept,
/// <summary>
/// Indicates the Sec-WebSocket-Protocol header.
/// </summary>
SecWebSocketProtocol,
/// <summary>
/// Indicates the Sec-WebSocket-Version header.
/// </summary>
SecWebSocketVersion
}
}

View File

@ -0,0 +1,359 @@
#region License
/*
* HttpStatusCode.cs
*
* This code is derived from System.Net.HttpStatusCode.cs of Mono
* (http://www.mono-project.com).
*
* It was automatically generated from ECMA CLI XML Library Specification.
* Generator: libgen.xsl [1.0; (C) Sergey Chaban (serge@wildwestsoftware.com)]
* Created: Wed, 5 Sep 2001 06:32:05 UTC
* Source file: AllTypes.xml
* URL: http://msdn.microsoft.com/net/ecma/AllTypes.xml
*
* The MIT License
*
* Copyright (c) 2001 Ximian, Inc. (http://www.ximian.com)
* Copyright (c) 2012-2014 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
namespace WebSocketSharp.Net
{
/// <summary>
/// Indicates the HTTP status code that can be specified in a server response.
/// </summary>
/// <remarks>
/// The values of this enumeration are defined in
/// <see href="http://tools.ietf.org/html/rfc2616#section-10">RFC 2616</see>.
/// </remarks>
public enum HttpStatusCode
{
/// <summary>
/// Equivalent to status code 100. Indicates that the client should continue
/// with its request.
/// </summary>
Continue = 100,
/// <summary>
/// Equivalent to status code 101.
/// Indicates that the server is switching the HTTP version or protocol on the connection.
/// </summary>
SwitchingProtocols = 101,
/// <summary>
/// Equivalent to status code 200.
/// Indicates that the client's request has succeeded.
/// </summary>
OK = 200,
/// <summary>
/// Equivalent to status code 201.
/// Indicates that the client's request has been fulfilled and resulted in a new resource being
/// created.
/// </summary>
Created = 201,
/// <summary>
/// Equivalent to status code 202.
/// Indicates that the client's request has been accepted for processing, but the processing
/// hasn't been completed.
/// </summary>
Accepted = 202,
/// <summary>
/// Equivalent to status code 203.
/// Indicates that the returned metainformation is from a local or a third-party copy instead of
/// the origin server.
/// </summary>
NonAuthoritativeInformation = 203,
/// <summary>
/// Equivalent to status code 204.
/// Indicates that the server has fulfilled the client's request but doesn't need to return
/// an entity-body.
/// </summary>
NoContent = 204,
/// <summary>
/// Equivalent to status code 205.
/// Indicates that the server has fulfilled the client's request, and the user agent should
/// reset the document view which caused the request to be sent.
/// </summary>
ResetContent = 205,
/// <summary>
/// Equivalent to status code 206.
/// Indicates that the server has fulfilled the partial GET request for the resource.
/// </summary>
PartialContent = 206,
/// <summary>
/// <para>
/// Equivalent to status code 300.
/// Indicates that the requested resource corresponds to any of multiple representations.
/// </para>
/// <para>
/// MultipleChoices is a synonym for Ambiguous.
/// </para>
/// </summary>
MultipleChoices = 300,
/// <summary>
/// <para>
/// Equivalent to status code 300.
/// Indicates that the requested resource corresponds to any of multiple representations.
/// </para>
/// <para>
/// Ambiguous is a synonym for MultipleChoices.
/// </para>
/// </summary>
Ambiguous = 300,
/// <summary>
/// <para>
/// Equivalent to status code 301.
/// Indicates that the requested resource has been assigned a new permanent URI and
/// any future references to this resource should use one of the returned URIs.
/// </para>
/// <para>
/// MovedPermanently is a synonym for Moved.
/// </para>
/// </summary>
MovedPermanently = 301,
/// <summary>
/// <para>
/// Equivalent to status code 301.
/// Indicates that the requested resource has been assigned a new permanent URI and
/// any future references to this resource should use one of the returned URIs.
/// </para>
/// <para>
/// Moved is a synonym for MovedPermanently.
/// </para>
/// </summary>
Moved = 301,
/// <summary>
/// <para>
/// Equivalent to status code 302.
/// Indicates that the requested resource is located temporarily under a different URI.
/// </para>
/// <para>
/// Found is a synonym for Redirect.
/// </para>
/// </summary>
Found = 302,
/// <summary>
/// <para>
/// Equivalent to status code 302.
/// Indicates that the requested resource is located temporarily under a different URI.
/// </para>
/// <para>
/// Redirect is a synonym for Found.
/// </para>
/// </summary>
Redirect = 302,
/// <summary>
/// <para>
/// Equivalent to status code 303.
/// Indicates that the response to the request can be found under a different URI and
/// should be retrieved using a GET method on that resource.
/// </para>
/// <para>
/// SeeOther is a synonym for RedirectMethod.
/// </para>
/// </summary>
SeeOther = 303,
/// <summary>
/// <para>
/// Equivalent to status code 303.
/// Indicates that the response to the request can be found under a different URI and
/// should be retrieved using a GET method on that resource.
/// </para>
/// <para>
/// RedirectMethod is a synonym for SeeOther.
/// </para>
/// </summary>
RedirectMethod = 303,
/// <summary>
/// Equivalent to status code 304.
/// Indicates that the client has performed a conditional GET request and access is allowed,
/// but the document hasn't been modified.
/// </summary>
NotModified = 304,
/// <summary>
/// Equivalent to status code 305.
/// Indicates that the requested resource must be accessed through the proxy given by
/// the Location field.
/// </summary>
UseProxy = 305,
/// <summary>
/// Equivalent to status code 306.
/// This status code was used in a previous version of the specification, is no longer used,
/// and is reserved for future use.
/// </summary>
Unused = 306,
/// <summary>
/// <para>
/// Equivalent to status code 307.
/// Indicates that the requested resource is located temporarily under a different URI.
/// </para>
/// <para>
/// TemporaryRedirect is a synonym for RedirectKeepVerb.
/// </para>
/// </summary>
TemporaryRedirect = 307,
/// <summary>
/// <para>
/// Equivalent to status code 307.
/// Indicates that the requested resource is located temporarily under a different URI.
/// </para>
/// <para>
/// RedirectKeepVerb is a synonym for TemporaryRedirect.
/// </para>
/// </summary>
RedirectKeepVerb = 307,
/// <summary>
/// Equivalent to status code 400.
/// Indicates that the client's request couldn't be understood by the server due to
/// malformed syntax.
/// </summary>
BadRequest = 400,
/// <summary>
/// Equivalent to status code 401.
/// Indicates that the client's request requires user authentication.
/// </summary>
Unauthorized = 401,
/// <summary>
/// Equivalent to status code 402.
/// This status code is reserved for future use.
/// </summary>
PaymentRequired = 402,
/// <summary>
/// Equivalent to status code 403.
/// Indicates that the server understood the client's request but is refusing to fulfill it.
/// </summary>
Forbidden = 403,
/// <summary>
/// Equivalent to status code 404.
/// Indicates that the server hasn't found anything matching the request URI.
/// </summary>
NotFound = 404,
/// <summary>
/// Equivalent to status code 405.
/// Indicates that the method specified in the request line isn't allowed for the resource
/// identified by the request URI.
/// </summary>
MethodNotAllowed = 405,
/// <summary>
/// Equivalent to status code 406.
/// Indicates that the server doesn't have the appropriate resource to respond to the Accept
/// headers in the client's request.
/// </summary>
NotAcceptable = 406,
/// <summary>
/// Equivalent to status code 407.
/// Indicates that the client must first authenticate itself with the proxy.
/// </summary>
ProxyAuthenticationRequired = 407,
/// <summary>
/// Equivalent to status code 408.
/// Indicates that the client didn't produce a request within the time that the server was
/// prepared to wait.
/// </summary>
RequestTimeout = 408,
/// <summary>
/// Equivalent to status code 409.
/// Indicates that the client's request couldn't be completed due to a conflict on the server.
/// </summary>
Conflict = 409,
/// <summary>
/// Equivalent to status code 410.
/// Indicates that the requested resource is no longer available at the server and
/// no forwarding address is known.
/// </summary>
Gone = 410,
/// <summary>
/// Equivalent to status code 411.
/// Indicates that the server refuses to accept the client's request without a defined
/// Content-Length.
/// </summary>
LengthRequired = 411,
/// <summary>
/// Equivalent to status code 412.
/// Indicates that the precondition given in one or more of the request headers evaluated to
/// false when it was tested on the server.
/// </summary>
PreconditionFailed = 412,
/// <summary>
/// Equivalent to status code 413.
/// Indicates that the entity of the client's request is larger than the server is willing or
/// able to process.
/// </summary>
RequestEntityTooLarge = 413,
/// <summary>
/// Equivalent to status code 414.
/// Indicates that the request URI is longer than the server is willing to interpret.
/// </summary>
RequestUriTooLong = 414,
/// <summary>
/// Equivalent to status code 415.
/// Indicates that the entity of the client's request is in a format not supported by
/// the requested resource for the requested method.
/// </summary>
UnsupportedMediaType = 415,
/// <summary>
/// Equivalent to status code 416.
/// Indicates that none of the range specifier values in a Range request header overlap
/// the current extent of the selected resource.
/// </summary>
RequestedRangeNotSatisfiable = 416,
/// <summary>
/// Equivalent to status code 417.
/// Indicates that the expectation given in an Expect request header couldn't be met by
/// the server.
/// </summary>
ExpectationFailed = 417,
/// <summary>
/// Equivalent to status code 500.
/// Indicates that the server encountered an unexpected condition which prevented it from
/// fulfilling the client's request.
/// </summary>
InternalServerError = 500,
/// <summary>
/// Equivalent to status code 501.
/// Indicates that the server doesn't support the functionality required to fulfill the client's
/// request.
/// </summary>
NotImplemented = 501,
/// <summary>
/// Equivalent to status code 502.
/// Indicates that a gateway or proxy server received an invalid response from the upstream
/// server.
/// </summary>
BadGateway = 502,
/// <summary>
/// Equivalent to status code 503.
/// Indicates that the server is currently unable to handle the client's request due to
/// a temporary overloading or maintenance of the server.
/// </summary>
ServiceUnavailable = 503,
/// <summary>
/// Equivalent to status code 504.
/// Indicates that a gateway or proxy server didn't receive a timely response from the upstream
/// server or some other auxiliary server.
/// </summary>
GatewayTimeout = 504,
/// <summary>
/// Equivalent to status code 505.
/// Indicates that the server doesn't support the HTTP version used in the client's request.
/// </summary>
HttpVersionNotSupported = 505,
}
}

View File

@ -0,0 +1,184 @@
#region License
/*
* HttpStreamAsyncResult.cs
*
* This code is derived from HttpStreamAsyncResult.cs (System.Net) of Mono
* (http://www.mono-project.com).
*
* The MIT License
*
* Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
* Copyright (c) 2012-2015 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
#region Authors
/*
* Authors:
* - Gonzalo Paniagua Javier <gonzalo@novell.com>
*/
#endregion
using System;
using System.Threading;
namespace WebSocketSharp.Net
{
internal class HttpStreamAsyncResult : IAsyncResult
{
#region Private Fields
private byte[] _buffer;
private AsyncCallback _callback;
private bool _completed;
private int _count;
private Exception _exception;
private int _offset;
private object _state;
private object _sync;
private int _syncRead;
private ManualResetEvent _waitHandle;
#endregion
#region Internal Constructors
internal HttpStreamAsyncResult (AsyncCallback callback, object state)
{
_callback = callback;
_state = state;
_sync = new object ();
}
#endregion
#region Internal Properties
internal byte[] Buffer {
get {
return _buffer;
}
set {
_buffer = value;
}
}
internal int Count {
get {
return _count;
}
set {
_count = value;
}
}
internal Exception Exception {
get {
return _exception;
}
}
internal bool HasException {
get {
return _exception != null;
}
}
internal int Offset {
get {
return _offset;
}
set {
_offset = value;
}
}
internal int SyncRead {
get {
return _syncRead;
}
set {
_syncRead = value;
}
}
#endregion
#region Public Properties
public object AsyncState {
get {
return _state;
}
}
public WaitHandle AsyncWaitHandle {
get {
lock (_sync)
return _waitHandle ?? (_waitHandle = new ManualResetEvent (_completed));
}
}
public bool CompletedSynchronously {
get {
return _syncRead == _count;
}
}
public bool IsCompleted {
get {
lock (_sync)
return _completed;
}
}
#endregion
#region Internal Methods
internal void Complete ()
{
lock (_sync) {
if (_completed)
return;
_completed = true;
if (_waitHandle != null)
_waitHandle.Set ();
if (_callback != null)
_callback.BeginInvoke (this, ar => _callback.EndInvoke (ar), null);
}
}
internal void Complete (Exception exception)
{
_exception = exception;
Complete ();
}
#endregion
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,73 @@
#region License
/*
* HttpVersion.cs
*
* This code is derived from System.Net.HttpVersion.cs of Mono
* (http://www.mono-project.com).
*
* The MIT License
*
* Copyright (c) 2012-2014 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
#region Authors
/*
* Authors:
* - Lawrence Pit <loz@cable.a2000.nl>
*/
#endregion
using System;
namespace WebSocketSharp.Net
{
/// <summary>
/// Provides the HTTP version numbers.
/// </summary>
public class HttpVersion
{
#region Public Fields
/// <summary>
/// Provides a <see cref="Version"/> instance for the HTTP/1.0.
/// </summary>
public static readonly Version Version10 = new Version (1, 0);
/// <summary>
/// Provides a <see cref="Version"/> instance for the HTTP/1.1.
/// </summary>
public static readonly Version Version11 = new Version (1, 1);
#endregion
#region Public Constructors
/// <summary>
/// Initializes a new instance of the <see cref="HttpVersion"/> class.
/// </summary>
public HttpVersion ()
{
}
#endregion
}
}

Some files were not shown because too many files have changed in this diff Show More