diff --git a/README.md b/README.md index 6abb166..5933bc0 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,60 @@ -# UdpPlugWebsocket -这是一个独立的可执行Win32服务端程序,用于桥接终端APP和物联网硬件 +# UdpPlugWebSocket 简称UPWS服务端程序# + + +## 项目说明 ## + +公司要做物联网硬件产品,需要一个服务器组件桥接APP软件终端和物联网硬件。 目前比较火的MQTT物联网协议虽然技术先进,但进入门槛较高,用户也相对较为复杂。BG了一番没有找到合适的服务端开源代码,就决定使用自己比较熟悉的.Net技术从头开发一个服务器组件。于是开了这个repository,如果能帮到你,我很高兴。 有问题欢迎与我微信联系: 微信号miuser00,加好友请注明您的来意。 + +## 项目简介 ## +这是一个独立的可执行Win32服务端程序,用于桥接终端APP和物联网硬件。 APP端为Websocet接口的静态html页面,硬件端为基于Arduino开发的电路,与服务器通讯使用UDP协议通讯(注:为方便您的使用,本Respository亦提供了一个C#编写的简易UDP测试程序用来模拟硬件设备)。 APP通过Webscoket接口发送一个自定义的字符串报文给服务端,服务端根据报文中的ID把报文转发给相同ID的UDP硬件设备(或UDP测试程序)。 + +![](.\document\preview.png) + +启动方法:在本地windows环境下运行.\UdpPlugWebsocket\bin\Debug\UdpPlugWebsocket.exe即可按照config.xml中描述的端口启动服务程序。默认UDP端口为7101,Websocket端口为9000。此时外部的UDP连接和Websocket连接都会在UI中显示。其中UDP在终端设备页面中显示,Websocket连接在控制页面中显示,通讯状况在交换面板页面中显示。您可以通过配置->显示调试系信息 打开或关闭UI的通讯Log。 + + + + +## 数据包格式 (UDP与Websocket相同)## + +![](.\document\packageintro.png) + + +**Endpoint是外部连接的唯一索引。** + +**Endpoint字符串的格式为:"XXX.XXX.XXX.XXX:XXXX" 前四组三位数为IP地址,最后一组四位数为远程端口号** + +##UDP通讯测试## + + 运行.\UdpExample\client\bin\Debug\client.exe 在程序控制台中输入如下数据包 + 004832A08000000000200000000000000001234testtest05 即可在终端设备页面看到本机的连接。 +![](.\document\clienttest.png) +## Websocket通讯测试 ## + 任意支持websocket的浏览器中运行.\UPW_Browser\index.html?ID=0000000002&MM=0000000000000000与UDP测试程序建立连接(参数区分大小写) +![](.\document\webtest.png) + +## 目录结构 ## + .\UdpPlugWebsocket UPWS服务程序(主服务程序) + .\UdpPlugWebsocket\UdpExample UDP测试程序 + .\UPW_Browser Websocket测试程序 + .\bin_UPWS 编译后的Win32项目二进制文 + ... 其余目录为参考代码目录,可删除 + + +##编译环境## +Visual Studio 2015,C#,Win10 + +## 使用到的开源库 ## + Websocket-Sharp + https://github.com/sta/websocket-sharp + Coldairarrow.Util.Sockets + https://github.com/Coldairarrow/Sockets + + +## 关键词 ## + +C#,Websocket,UDP,物联网,异步编程,接口,lamda表达式,线程池,Action,Invoke,Arduino,ESP8266 + +## 版权声明 ## + +如果您愿意使用 UdpPlugWebSocket 组件,请遵循 MIT 许可所述内容. \ No newline at end of file diff --git a/UPW_Browser/WebSocketClient.html b/UPW_Browser/WebSocketClient.html new file mode 100644 index 0000000..4c9fdbf --- /dev/null +++ b/UPW_Browser/WebSocketClient.html @@ -0,0 +1,37 @@ + + + + + + +
+ + +
+ + + + + diff --git a/UPW_Browser/main/JustGage.js b/UPW_Browser/main/JustGage.js new file mode 100644 index 0000000..38825fd --- /dev/null +++ b/UPW_Browser/main/JustGage.js @@ -0,0 +1,3 @@ +(function(a){var b="0.3.4",c="hasOwnProperty",d=/[\.\/]/,e="*",f=function(){},g=function(a,b){return a-b},h,i,j={n:{}},k=function(a,b){var c=j,d=i,e=Array.prototype.slice.call(arguments,2),f=k.listeners(a),l=0,m=!1,n,o=[],p={},q=[],r=h,s=[];h=a,i=0;for(var t=0,u=f.length;tf*b.top){e=b.percents[y],p=b.percents[y-1]||0,t=t/b.top*(e-p),o=b.percents[y+1],j=b.anim[e];break}f&&d.attr(b.anim[b.percents[y]])}if(!!j){if(!k){for(var A in j)if(j[g](A))if(U[g](A)||d.paper.customAttributes[g](A)){u[A]=d.attr(A),u[A]==null&&(u[A]=T[A]),v[A]=j[A];switch(U[A]){case C:w[A]=(v[A]-u[A])/t;break;case"colour":u[A]=a.getRGB(u[A]);var B=a.getRGB(v[A]);w[A]={r:(B.r-u[A].r)/t,g:(B.g-u[A].g)/t,b:(B.b-u[A].b)/t};break;case"path":var D=bR(u[A],v[A]),E=D[1];u[A]=D[0],w[A]=[];for(y=0,z=u[A].length;yd)return d;while(cf?c=e:d=e,e=(d-c)/2+c}return e}function n(a,b){var c=o(a,b);return((l*c+k)*c+j)*c}function m(a){return((i*a+h)*a+g)*a}var g=3*b,h=3*(d-b)-g,i=1-g-h,j=3*c,k=3*(e-c)-j,l=1-j-k;return n(a,1/(200*f))}function cq(){return this.x+q+this.y+q+this.width+" � "+this.height}function cp(){return this.x+q+this.y}function cb(a,b,c,d,e,f){a!=null?(this.a=+a,this.b=+b,this.c=+c,this.d=+d,this.e=+e,this.f=+f):(this.a=1,this.b=0,this.c=0,this.d=1,this.e=0,this.f=0)}function bH(b,c,d){b=a._path2curve(b),c=a._path2curve(c);var e,f,g,h,i,j,k,l,m,n,o=d?0:[];for(var p=0,q=b.length;p=0&&y<=1&&A>=0&&A<=1&&(d?n++:n.push({x:x.x,y:x.y,t1:y,t2:A}))}}return n}function bF(a,b){return bG(a,b,1)}function bE(a,b){return bG(a,b)}function bD(a,b,c,d,e,f,g,h){if(!(x(a,c)x(e,g)||x(b,d)x(f,h))){var i=(a*d-b*c)*(e-g)-(a-c)*(e*h-f*g),j=(a*d-b*c)*(f-h)-(b-d)*(e*h-f*g),k=(a-c)*(f-h)-(b-d)*(e-g);if(!k)return;var l=i/k,m=j/k,n=+l.toFixed(2),o=+m.toFixed(2);if(n<+y(a,c).toFixed(2)||n>+x(a,c).toFixed(2)||n<+y(e,g).toFixed(2)||n>+x(e,g).toFixed(2)||o<+y(b,d).toFixed(2)||o>+x(b,d).toFixed(2)||o<+y(f,h).toFixed(2)||o>+x(f,h).toFixed(2))return;return{x:l,y:m}}}function bC(a,b,c,d,e,f,g,h,i){if(!(i<0||bB(a,b,c,d,e,f,g,h)n)k/=2,l+=(m1?1:i<0?0:i;var j=i/2,k=12,l=[-0.1252,.1252,-0.3678,.3678,-0.5873,.5873,-0.7699,.7699,-0.9041,.9041,-0.9816,.9816],m=[.2491,.2491,.2335,.2335,.2032,.2032,.1601,.1601,.1069,.1069,.0472,.0472],n=0;for(var o=0;od;d+=2){var f=[{x:+a[d-2],y:+a[d-1]},{x:+a[d],y:+a[d+1]},{x:+a[d+2],y:+a[d+3]},{x:+a[d+4],y:+a[d+5]}];b?d?e-4==d?f[3]={x:+a[0],y:+a[1]}:e-2==d&&(f[2]={x:+a[0],y:+a[1]},f[3]={x:+a[2],y:+a[3]}):f[0]={x:+a[e-2],y:+a[e-1]}:e-4==d?f[3]=f[2]:d||(f[0]={x:+a[d],y:+a[d+1]}),c.push(["C",(-f[0].x+6*f[1].x+f[2].x)/6,(-f[0].y+6*f[1].y+f[2].y)/6,(f[1].x+6*f[2].x-f[3].x)/6,(f[1].y+6*f[2].y-f[3].y)/6,f[2].x,f[2].y])}return c}function bx(){return this.hex}function bv(a,b,c){function d(){var e=Array.prototype.slice.call(arguments,0),f=e.join("?"),h=d.cache=d.cache||{},i=d.count=d.count||[];if(h[g](f)){bu(i,f);return c?c(h[f]):h[f]}i.length>=1e3&&delete h[i.shift()],i.push(f),h[f]=a[m](b,e);return c?c(h[f]):h[f]}return d}function bu(a,b){for(var c=0,d=a.length;c',bl=bk.firstChild,bl.style.behavior="url(#default#VML)";if(!bl||typeof bl.adj!="object")return a.type=p;bk=null}a.svg=!(a.vml=a.type=="VML"),a._Paper=j,a.fn=k=j.prototype=a.prototype,a._id=0,a._oid=0,a.is=function(a,b){b=v.call(b);if(b=="finite")return!M[g](+a);if(b=="array")return a instanceof Array;return b=="null"&&a===null||b==typeof a&&a!==null||b=="object"&&a===Object(a)||b=="array"&&Array.isArray&&Array.isArray(a)||H.call(a).slice(8,-1).toLowerCase()==b},a.angle=function(b,c,d,e,f,g){if(f==null){var h=b-d,i=c-e;if(!h&&!i)return 0;return(180+w.atan2(-i,-h)*180/B+360)%360}return a.angle(b,c,f,g)-a.angle(d,e,f,g)},a.rad=function(a){return a%360*B/180},a.deg=function(a){return a*180/B%360},a.snapTo=function(b,c,d){d=a.is(d,"finite")?d:10;if(a.is(b,E)){var e=b.length;while(e--)if(z(b[e]-c)<=d)return b[e]}else{b=+b;var f=c%b;if(fb-d)return c-f+b}return c};var bn=a.createUUID=function(a,b){return function(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(a,b).toUpperCase()}}(/[xy]/g,function(a){var b=w.random()*16|0,c=a=="x"?b:b&3|8;return c.toString(16)});a.setWindow=function(b){eve("raphael.setWindow",a,h.win,b),h.win=b,h.doc=h.win.document,a._engine.initWin&&a._engine.initWin(h.win)};var bo=function(b){if(a.vml){var c=/^\s+|\s+$/g,d;try{var e=new ActiveXObject("htmlfile");e.write(""),e.close(),d=e.body}catch(f){d=createPopup().document.body}var g=d.createTextRange();bo=bv(function(a){try{d.style.color=r(a).replace(c,p);var b=g.queryCommandValue("ForeColor");b=(b&255)<<16|b&65280|(b&16711680)>>>16;return"#"+("000000"+b.toString(16)).slice(-6)}catch(e){return"none"}})}else{var i=h.doc.createElement("i");i.title="Rapha�l Colour Picker",i.style.display="none",h.doc.body.appendChild(i),bo=bv(function(a){i.style.color=a;return h.doc.defaultView.getComputedStyle(i,p).getPropertyValue("color")})}return bo(b)},bp=function(){return"hsb("+[this.h,this.s,this.b]+")"},bq=function(){return"hsl("+[this.h,this.s,this.l]+")"},br=function(){return this.hex},bs=function(b,c,d){c==null&&a.is(b,"object")&&"r"in b&&"g"in b&&"b"in b&&(d=b.b,c=b.g,b=b.r);if(c==null&&a.is(b,D)){var e=a.getRGB(b);b=e.r,c=e.g,d=e.b}if(b>1||c>1||d>1)b/=255,c/=255,d/=255;return[b,c,d]},bt=function(b,c,d,e){b*=255,c*=255,d*=255;var f={r:b,g:c,b:d,hex:a.rgb(b,c,d),toString:br};a.is(e,"finite")&&(f.opacity=e);return f};a.color=function(b){var c;a.is(b,"object")&&"h"in b&&"s"in b&&"b"in b?(c=a.hsb2rgb(b),b.r=c.r,b.g=c.g,b.b=c.b,b.hex=c.hex):a.is(b,"object")&&"h"in b&&"s"in b&&"l"in b?(c=a.hsl2rgb(b),b.r=c.r,b.g=c.g,b.b=c.b,b.hex=c.hex):(a.is(b,"string")&&(b=a.getRGB(b)),a.is(b,"object")&&"r"in b&&"g"in b&&"b"in b?(c=a.rgb2hsl(b),b.h=c.h,b.s=c.s,b.l=c.l,c=a.rgb2hsb(b),b.v=c.b):(b={hex:"none"},b.r=b.g=b.b=b.h=b.s=b.v=b.l=-1)),b.toString=br;return b},a.hsb2rgb=function(a,b,c,d){this.is(a,"object")&&"h"in a&&"s"in a&&"b"in a&&(c=a.b,b=a.s,a=a.h,d=a.o),a*=360;var e,f,g,h,i;a=a%360/60,i=c*b,h=i*(1-z(a%2-1)),e=f=g=c-i,a=~~a,e+=[i,h,0,0,h,i][a],f+=[h,i,i,h,0,0][a],g+=[0,0,h,i,i,h][a];return bt(e,f,g,d)},a.hsl2rgb=function(a,b,c,d){this.is(a,"object")&&"h"in a&&"s"in a&&"l"in a&&(c=a.l,b=a.s,a=a.h);if(a>1||b>1||c>1)a/=360,b/=100,c/=100;a*=360;var e,f,g,h,i;a=a%360/60,i=2*b*(c<.5?c:1-c),h=i*(1-z(a%2-1)),e=f=g=c-i/2,a=~~a,e+=[i,h,0,0,h,i][a],f+=[h,i,i,h,0,0][a],g+=[0,0,h,i,i,h][a];return bt(e,f,g,d)},a.rgb2hsb=function(a,b,c){c=bs(a,b,c),a=c[0],b=c[1],c=c[2];var d,e,f,g;f=x(a,b,c),g=f-y(a,b,c),d=g==0?null:f==a?(b-c)/g:f==b?(c-a)/g+2:(a-b)/g+4,d=(d+360)%6*60/360,e=g==0?0:g/f;return{h:d,s:e,b:f,toString:bp}},a.rgb2hsl=function(a,b,c){c=bs(a,b,c),a=c[0],b=c[1],c=c[2];var d,e,f,g,h,i;g=x(a,b,c),h=y(a,b,c),i=g-h,d=i==0?null:g==a?(b-c)/i:g==b?(c-a)/i+2:(a-b)/i+4,d=(d+360)%6*60/360,f=(g+h)/2,e=i==0?0:f<.5?i/(2*f):i/(2-2*f);return{h:d,s:e,l:f,toString:bq}},a._path2string=function(){return this.join(",").replace(Y,"$1")};var bw=a._preload=function(a,b){var c=h.doc.createElement("img");c.style.cssText="position:absolute;left:-9999em;top:-9999em",c.onload=function(){b.call(this),this.onload=null,h.doc.body.removeChild(this)},c.onerror=function(){h.doc.body.removeChild(this)},h.doc.body.appendChild(c),c.src=a};a.getRGB=bv(function(b){if(!b||!!((b=r(b)).indexOf("-")+1))return{r:-1,g:-1,b:-1,hex:"none",error:1,toString:bx};if(b=="none")return{r:-1,g:-1,b:-1,hex:"none",toString:bx};!X[g](b.toLowerCase().substring(0,2))&&b.charAt()!="#"&&(b=bo(b));var c,d,e,f,h,i,j,k=b.match(L);if(k){k[2]&&(f=R(k[2].substring(5),16),e=R(k[2].substring(3,5),16),d=R(k[2].substring(1,3),16)),k[3]&&(f=R((i=k[3].charAt(3))+i,16),e=R((i=k[3].charAt(2))+i,16),d=R((i=k[3].charAt(1))+i,16)),k[4]&&(j=k[4][s](W),d=Q(j[0]),j[0].slice(-1)=="%"&&(d*=2.55),e=Q(j[1]),j[1].slice(-1)=="%"&&(e*=2.55),f=Q(j[2]),j[2].slice(-1)=="%"&&(f*=2.55),k[1].toLowerCase().slice(0,4)=="rgba"&&(h=Q(j[3])),j[3]&&j[3].slice(-1)=="%"&&(h/=100));if(k[5]){j=k[5][s](W),d=Q(j[0]),j[0].slice(-1)=="%"&&(d*=2.55),e=Q(j[1]),j[1].slice(-1)=="%"&&(e*=2.55),f=Q(j[2]),j[2].slice(-1)=="%"&&(f*=2.55),(j[0].slice(-3)=="deg"||j[0].slice(-1)=="�")&&(d/=360),k[1].toLowerCase().slice(0,4)=="hsba"&&(h=Q(j[3])),j[3]&&j[3].slice(-1)=="%"&&(h/=100);return a.hsb2rgb(d,e,f,h)}if(k[6]){j=k[6][s](W),d=Q(j[0]),j[0].slice(-1)=="%"&&(d*=2.55),e=Q(j[1]),j[1].slice(-1)=="%"&&(e*=2.55),f=Q(j[2]),j[2].slice(-1)=="%"&&(f*=2.55),(j[0].slice(-3)=="deg"||j[0].slice(-1)=="�")&&(d/=360),k[1].toLowerCase().slice(0,4)=="hsla"&&(h=Q(j[3])),j[3]&&j[3].slice(-1)=="%"&&(h/=100);return a.hsl2rgb(d,e,f,h)}k={r:d,g:e,b:f,toString:bx},k.hex="#"+(16777216|f|e<<8|d<<16).toString(16).slice(1),a.is(h,"finite")&&(k.opacity=h);return k}return{r:-1,g:-1,b:-1,hex:"none",error:1,toString:bx}},a),a.hsb=bv(function(b,c,d){return a.hsb2rgb(b,c,d).hex}),a.hsl=bv(function(b,c,d){return a.hsl2rgb(b,c,d).hex}),a.rgb=bv(function(a,b,c){return"#"+(16777216|c|b<<8|a<<16).toString(16).slice(1)}),a.getColor=function(a){var b=this.getColor.start=this.getColor.start||{h:0,s:1,b:a||.75},c=this.hsb2rgb(b.h,b.s,b.b);b.h+=.075,b.h>1&&(b.h=0,b.s-=.2,b.s<=0&&(this.getColor.start={h:0,s:1,b:b.b}));return c.hex},a.getColor.reset=function(){delete this.start},a.parsePathString=function(b){if(!b)return null;var c=bz(b);if(c.arr)return bJ(c.arr);var d={a:7,c:6,h:1,l:2,m:2,r:4,q:4,s:4,t:2,v:1,z:0},e=[];a.is(b,E)&&a.is(b[0],E)&&(e=bJ(b)),e.length||r(b).replace(Z,function(a,b,c){var f=[],g=b.toLowerCase();c.replace(_,function(a,b){b&&f.push(+b)}),g=="m"&&f.length>2&&(e.push([b][n](f.splice(0,2))),g="l",b=b=="m"?"l":"L");if(g=="r")e.push([b][n](f));else while(f.length>=d[g]){e.push([b][n](f.splice(0,d[g])));if(!d[g])break}}),e.toString=a._path2string,c.arr=bJ(e);return e},a.parseTransformString=bv(function(b){if(!b)return null;var c={r:3,s:4,t:2,m:6},d=[];a.is(b,E)&&a.is(b[0],E)&&(d=bJ(b)),d.length||r(b).replace($,function(a,b,c){var e=[],f=v.call(b);c.replace(_,function(a,b){b&&e.push(+b)}),d.push([b][n](e))}),d.toString=a._path2string;return d});var bz=function(a){var b=bz.ps=bz.ps||{};b[a]?b[a].sleep=100:b[a]={sleep:100},setTimeout(function(){for(var c in b)b[g](c)&&c!=a&&(b[c].sleep--,!b[c].sleep&&delete b[c])});return b[a]};a.findDotsAtSegment=function(a,b,c,d,e,f,g,h,i){var j=1-i,k=A(j,3),l=A(j,2),m=i*i,n=m*i,o=k*a+l*3*i*c+j*3*i*i*e+n*g,p=k*b+l*3*i*d+j*3*i*i*f+n*h,q=a+2*i*(c-a)+m*(e-2*c+a),r=b+2*i*(d-b)+m*(f-2*d+b),s=c+2*i*(e-c)+m*(g-2*e+c),t=d+2*i*(f-d)+m*(h-2*f+d),u=j*a+i*c,v=j*b+i*d,x=j*e+i*g,y=j*f+i*h,z=90-w.atan2(q-s,r-t)*180/B;(q>s||r=a.x&&b<=a.x2&&c>=a.y&&c<=a.y2},a.isBBoxIntersect=function(b,c){var d=a.isPointInsideBBox;return d(c,b.x,b.y)||d(c,b.x2,b.y)||d(c,b.x,b.y2)||d(c,b.x2,b.y2)||d(b,c.x,c.y)||d(b,c.x2,c.y)||d(b,c.x,c.y2)||d(b,c.x2,c.y2)||(b.xc.x||c.xb.x)&&(b.yc.y||c.yb.y)},a.pathIntersection=function(a,b){return bH(a,b)},a.pathIntersectionNumber=function(a,b){return bH(a,b,1)},a.isPointInsidePath=function(b,c,d){var e=a.pathBBox(b);return a.isPointInsideBBox(e,c,d)&&bH(b,[["M",c,d],["H",e.x2+10]],1)%2==1},a._removedFactory=function(a){return function(){eve("raphael.log",null,"Rapha�l: you are calling to method �"+a+"� of removed object",a)}};var bI=a.pathBBox=function(a){var b=bz(a);if(b.bbox)return b.bbox;if(!a)return{x:0,y:0,width:0,height:0,x2:0,y2:0};a=bR(a);var c=0,d=0,e=[],f=[],g;for(var h=0,i=a.length;h1&&(v=w.sqrt(v),c=v*c,d=v*d);var x=c*c,y=d*d,A=(f==g?-1:1)*w.sqrt(z((x*y-x*u*u-y*t*t)/(x*u*u+y*t*t))),C=A*c*u/d+(a+h)/2,D=A*-d*t/c+(b+i)/2,E=w.asin(((b-D)/d).toFixed(9)),F=w.asin(((i-D)/d).toFixed(9));E=aF&&(E=E-B*2),!g&&F>E&&(F=F-B*2)}else E=j[0],F=j[1],C=j[2],D=j[3];var G=F-E;if(z(G)>k){var H=F,I=h,J=i;F=E+k*(g&&F>E?1:-1),h=C+c*w.cos(F),i=D+d*w.sin(F),m=bO(h,i,c,d,e,0,g,I,J,[F,H,C,D])}G=F-E;var K=w.cos(E),L=w.sin(E),M=w.cos(F),N=w.sin(F),O=w.tan(G/4),P=4/3*c*O,Q=4/3*d*O,R=[a,b],S=[a+P*L,b-Q*K],T=[h+P*N,i-Q*M],U=[h,i];S[0]=2*R[0]-S[0],S[1]=2*R[1]-S[1];if(j)return[S,T,U][n](m);m=[S,T,U][n](m).join()[s](",");var V=[];for(var W=0,X=m.length;W"1e12"&&(l=.5),z(n)>"1e12"&&(n=.5),l>0&&l<1&&(q=bP(a,b,c,d,e,f,g,h,l),p.push(q.x),o.push(q.y)),n>0&&n<1&&(q=bP(a,b,c,d,e,f,g,h,n),p.push(q.x),o.push(q.y)),i=f-2*d+b-(h-2*f+d),j=2*(d-b)-2*(f-d),k=b-d,l=(-j+w.sqrt(j*j-4*i*k))/2/i,n=(-j-w.sqrt(j*j-4*i*k))/2/i,z(l)>"1e12"&&(l=.5),z(n)>"1e12"&&(n=.5),l>0&&l<1&&(q=bP(a,b,c,d,e,f,g,h,l),p.push(q.x),o.push(q.y)),n>0&&n<1&&(q=bP(a,b,c,d,e,f,g,h,n),p.push(q.x),o.push(q.y));return{min:{x:y[m](0,p),y:y[m](0,o)},max:{x:x[m](0,p),y:x[m](0,o)}}}),bR=a._path2curve=bv(function(a,b){var c=!b&&bz(a);if(!b&&c.curve)return bJ(c.curve);var d=bL(a),e=b&&bL(b),f={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},g={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},h=function(a,b){var c,d;if(!a)return["C",b.x,b.y,b.x,b.y,b.x,b.y];!(a[0]in{T:1,Q:1})&&(b.qx=b.qy=null);switch(a[0]){case"M":b.X=a[1],b.Y=a[2];break;case"A":a=["C"][n](bO[m](0,[b.x,b.y][n](a.slice(1))));break;case"S":c=b.x+(b.x-(b.bx||b.x)),d=b.y+(b.y-(b.by||b.y)),a=["C",c,d][n](a.slice(1));break;case"T":b.qx=b.x+(b.x-(b.qx||b.x)),b.qy=b.y+(b.y-(b.qy||b.y)),a=["C"][n](bN(b.x,b.y,b.qx,b.qy,a[1],a[2]));break;case"Q":b.qx=a[1],b.qy=a[2],a=["C"][n](bN(b.x,b.y,a[1],a[2],a[3],a[4]));break;case"L":a=["C"][n](bM(b.x,b.y,a[1],a[2]));break;case"H":a=["C"][n](bM(b.x,b.y,a[1],b.y));break;case"V":a=["C"][n](bM(b.x,b.y,b.x,a[1]));break;case"Z":a=["C"][n](bM(b.x,b.y,b.X,b.Y))}return a},i=function(a,b){if(a[b].length>7){a[b].shift();var c=a[b];while(c.length)a.splice(b++,0,["C"][n](c.splice(0,6)));a.splice(b,1),l=x(d.length,e&&e.length||0)}},j=function(a,b,c,f,g){a&&b&&a[g][0]=="M"&&b[g][0]!="M"&&(b.splice(g,0,["M",f.x,f.y]),c.bx=0,c.by=0,c.x=a[g][1],c.y=a[g][2],l=x(d.length,e&&e.length||0))};for(var k=0,l=x(d.length,e&&e.length||0);ke){if(c&&!l.start){m=cs(g,h,i[1],i[2],i[3],i[4],i[5],i[6],e-n),k+=["C"+m.start.x,m.start.y,m.m.x,m.m.y,m.x,m.y];if(f)return k;l.start=k,k=["M"+m.x,m.y+"C"+m.n.x,m.n.y,m.end.x,m.end.y,i[5],i[6]].join(),n+=j,g=+i[5],h=+i[6];continue}if(!b&&!c){m=cs(g,h,i[1],i[2],i[3],i[4],i[5],i[6],e-n);return{x:m.x,y:m.y,alpha:m.alpha}}}n+=j,g=+i[5],h=+i[6]}k+=i.shift()+i}l.end=k,m=b?n:c?l:a.findDotsAtSegment(g,h,i[0],i[1],i[2],i[3],i[4],i[5],1),m.alpha&&(m={x:m.x,y:m.y,alpha:m.alpha});return m}},cu=ct(1),cv=ct(),cw=ct(0,1);a.getTotalLength=cu,a.getPointAtLength=cv,a.getSubpath=function(a,b,c){if(this.getTotalLength(a)-c<1e-6)return cw(a,b).end;var d=cw(a,c,1);return b?cw(d,b).end:d},cl.getTotalLength=function(){if(this.type=="path"){if(this.node.getTotalLength)return this.node.getTotalLength();return cu(this.attrs.path)}},cl.getPointAtLength=function(a){if(this.type=="path")return cv(this.attrs.path,a)},cl.getSubpath=function(b,c){if(this.type=="path")return a.getSubpath(this.attrs.path,b,c)};var cx=a.easing_formulas={linear:function(a){return a},"<":function(a){return A(a,1.7)},">":function(a){return A(a,.48)},"<>":function(a){var b=.48-a/1.04,c=w.sqrt(.1734+b*b),d=c-b,e=A(z(d),1/3)*(d<0?-1:1),f=-c-b,g=A(z(f),1/3)*(f<0?-1:1),h=e+g+.5;return(1-h)*3*h*h+h*h*h},backIn:function(a){var b=1.70158;return a*a*((b+1)*a-b)},backOut:function(a){a=a-1;var b=1.70158;return a*a*((b+1)*a+b)+1},elastic:function(a){if(a==!!a)return a;return A(2,-10*a)*w.sin((a-.075)*2*B/.3)+1},bounce:function(a){var b=7.5625,c=2.75,d;a<1/c?d=b*a*a:a<2/c?(a-=1.5/c,d=b*a*a+.75):a<2.5/c?(a-=2.25/c,d=b*a*a+.9375):(a-=2.625/c,d=b*a*a+.984375);return d}};cx.easeIn=cx["ease-in"]=cx["<"],cx.easeOut=cx["ease-out"]=cx[">"],cx.easeInOut=cx["ease-in-out"]=cx["<>"],cx["back-in"]=cx.backIn,cx["back-out"]=cx.backOut;var cy=[],cz=window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(a){setTimeout(a,16)},cA=function(){var b=+(new Date),c=0;for(;c1&&!d.next){for(s in k)k[g](s)&&(r[s]=d.totalOrigin[s]);d.el.attr(r),cE(d.anim,d.el,d.anim.percents[0],null,d.totalOrigin,d.repeat-1)}d.next&&!d.stop&&cE(d.anim,d.el,d.next,null,d.totalOrigin,d.repeat)}}a.svg&&m&&m.paper&&m.paper.safari(),cy.length&&cz(cA)},cB=function(a){return a>255?255:a<0?0:a};cl.animateWith=function(b,c,d,e,f,g){var h=this;if(h.removed){g&&g.call(h);return h}var i=d instanceof cD?d:a.animation(d,e,f,g),j,k;cE(i,h,i.percents[0],null,h.attr());for(var l=0,m=cy.length;l.5)*2-1;i(m-.5,2)+i(n-.5,2)>.25&&(n=f.sqrt(.25-i(m-.5,2))*e+.5)&&n!=.5&&(n=n.toFixed(5)-1e-5*e)}return l}),e=e.split(/\s*\-\s*/);if(j=="linear"){var t=e.shift();t=-d(t);if(isNaN(t))return null;var u=[0,0,f.cos(a.rad(t)),f.sin(a.rad(t))],v=1/(g(h(u[2]),h(u[3]))||1);u[2]*=v,u[3]*=v,u[2]<0&&(u[0]=-u[2],u[2]=0),u[3]<0&&(u[1]=-u[3],u[3]=0)}var w=a._parseDots(e);if(!w)return null;k=k.replace(/[\(\)\s,\xb0#]/g,"_"),b.gradient&&k!=b.gradient.id&&(p.defs.removeChild(b.gradient),delete b.gradient);if(!b.gradient){s=q(j+"Gradient",{id:k}),b.gradient=s,q(s,j=="radial"?{fx:m,fy:n}:{x1:u[0],y1:u[1],x2:u[2],y2:u[3],gradientTransform:b.matrix.invert()}),p.defs.appendChild(s);for(var x=0,y=w.length;x1?G.opacity/100:G.opacity});case"stroke":G=a.getRGB(p),i.setAttribute(o,G.hex),o=="stroke"&&G[b]("opacity")&&q(i,{"stroke-opacity":G.opacity>1?G.opacity/100:G.opacity}),o=="stroke"&&d._.arrows&&("startString"in d._.arrows&&t(d,d._.arrows.startString),"endString"in d._.arrows&&t(d,d._.arrows.endString,1));break;case"gradient":(d.type=="circle"||d.type=="ellipse"||c(p).charAt()!="r")&&r(d,p);break;case"opacity":k.gradient&&!k[b]("stroke-opacity")&&q(i,{"stroke-opacity":p>1?p/100:p});case"fill-opacity":if(k.gradient){H=a._g.doc.getElementById(i.getAttribute("fill").replace(/^url\(#|\)$/g,l)),H&&(I=H.getElementsByTagName("stop"),q(I[I.length-1],{"stop-opacity":p}));break};default:o=="font-size"&&(p=e(p,10)+"px");var J=o.replace(/(\-.)/g,function(a){return a.substring(1).toUpperCase()});i.style[J]=p,d._.dirty=1,i.setAttribute(o,p)}}y(d,f),i.style.visibility=m},x=1.2,y=function(d,f){if(d.type=="text"&&!!(f[b]("text")||f[b]("font")||f[b]("font-size")||f[b]("x")||f[b]("y"))){var g=d.attrs,h=d.node,i=h.firstChild?e(a._g.doc.defaultView.getComputedStyle(h.firstChild,l).getPropertyValue("font-size"),10):10;if(f[b]("text")){g.text=f.text;while(h.firstChild)h.removeChild(h.firstChild);var j=c(f.text).split("\n"),k=[],m;for(var n=0,o=j.length;n"));var $=X.getBoundingClientRect();t.W=m.w=($.right-$.left)/Y,t.H=m.h=($.bottom-$.top)/Y,t.X=m.x,t.Y=m.y+t.H/2,("x"in i||"y"in i)&&(t.path.v=a.format("m{0},{1}l{2},{1}",f(m.x*u),f(m.y*u),f(m.x*u)+1));var _=["x","y","text","font","font-family","font-weight","font-style","font-size"];for(var ba=0,bb=_.length;ba.25&&(c=e.sqrt(.25-i(b-.5,2))*((c>.5)*2-1)+.5),m=b+n+c);return o}),f=f.split(/\s*\-\s*/);if(l=="linear"){var p=f.shift();p=-d(p);if(isNaN(p))return null}var q=a._parseDots(f);if(!q)return null;b=b.shape||b.node;if(q.length){b.removeChild(g),g.on=!0,g.method="none",g.color=q[0].color,g.color2=q[q.length-1].color;var r=[];for(var s=0,t=q.length;s')}}catch(c){F=function(a){return b.createElement("<"+a+' xmlns="urn:schemas-microsoft.com:vml" class="rvml">')}}},a._engine.initWin(a._g.win),a._engine.create=function(){var b=a._getContainer.apply(0,arguments),c=b.container,d=b.height,e,f=b.width,g=b.x,h=b.y;if(!c)throw new Error("VML container not found.");var i=new a._Paper,j=i.canvas=a._g.doc.createElement("div"),k=j.style;g=g||0,h=h||0,f=f||512,d=d||342,i.width=f,i.height=d,f==+f&&(f+="px"),d==+d&&(d+="px"),i.coordsize=u*1e3+n+u*1e3,i.coordorigin="0 0",i.span=a._g.doc.createElement("span"),i.span.style.cssText="position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;",j.appendChild(i.span),k.cssText=a.format("top:0;left:0;width:{0};height:{1};display:inline-block;position:relative;clip:rect(0 {0} {1} 0);overflow:hidden",f,d),c==1?(a._g.doc.body.appendChild(j),k.left=g+"px",k.top=h+"px",k.position="absolute"):c.firstChild?c.insertBefore(j,c.firstChild):c.appendChild(j),i.renderfix=function(){};return i},a.prototype.clear=function(){a.eve("raphael.clear",this),this.canvas.innerHTML=o,this.span=a._g.doc.createElement("span"),this.span.style.cssText="position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;display:inline;",this.canvas.appendChild(this.span),this.bottom=this.top=null},a.prototype.remove=function(){a.eve("raphael.remove",this),this.canvas.parentNode.removeChild(this.canvas);for(var b in this)this[b]=typeof this[b]=="function"?a._removedFactory(b):null;return!0};var G=a.st;for(var H in E)E[b](H)&&!G[b](H)&&(G[H]=function(a){return function(){var b=arguments;return this.forEach(function(c){c[a].apply(c,b)})}}(H))}(window.Raphael) + +JustGage=function(x){if(!x.id){alert("Missing id parameter for gauge!");return false}if(!document.getElementById(x.id)){alert('No element with id: "'+x.id+'" found!');return false}this.config={id:x.id,title:(x.title)?x.title:"Title",titleFontColor:(x.titleFontColor)?x.titleFontColor:"#999999",value:(x.value)?x.value:0,valueFontColor:(x.valueFontColor)?x.valueFontColor:"#010101",min:(x.min)?x.min:0,max:(x.max)?x.max:100,showMinMax:(x.showMinMax!=null)?x.showMinMax:true,gaugeWidthScale:(x.gaugeWidthScale)?x.gaugeWidthScale:1,gaugeColor:(x.gaugeColor)?x.gaugeColor:"#edebeb",label:(x.label)?x.label:"",showInnerShadow:(x.showInnerShadow!=null)?x.showInnerShadow:true,shadowOpacity:(x.shadowOpacity)?x.shadowOpacity:0.2,shadowSize:(x.shadowSize)?x.shadowSize:5,shadowVerticalOffset:(x.shadowVerticalOffset)?x.shadowVerticalOffset:3,levelColors:(x.levelColors)?x.levelColors:percentColors,levelColorsGradient:(x.levelColorsGradient!=null)?x.levelColorsGradient:true,labelFontColor:(x.labelFontColor)?x.labelFontColor:"#b3b3b3",startAnimationTime:(x.startAnimationTime)?x.startAnimationTime:700,startAnimationType:(x.startAnimationType)?x.startAnimationType:">",refreshAnimationTime:(x.refreshAnimationTime)?x.refreshAnimationTime:700,refreshAnimationType:(x.refreshAnimationType)?x.refreshAnimationType:">"};if(x.value>this.config.max){this.config.value=this.config.max}if(x.value1.25){c=1.25*q;l=q}else{c=d;l=d/1.25}var i=(d-c)/2;var g=(q-l)/2;var s=((l/8)>10)?(l/10):10;var n=i+c/2;var m=g+l/6.5;var k=((l/6.4)>16)?(l/6.4):16;var r=i+c/2;var p=g+l/1.4;var b=((l/16)>10)?(l/16):10;var f=i+c/2;var e=p+k/2+6;var j=((l/16)>10)?(l/16):10;var w=i+(c/10)+(c/6.666666666666667*this.config.gaugeWidthScale)/2;var v=g+l/1.126760563380282;var h=((l/16)>10)?(l/16):10;var u=i+c-(c/10)-(c/6.666666666666667*this.config.gaugeWidthScale)/2;var t=g+l/1.126760563380282;this.params={canvasW:d,canvasH:q,widgetW:c,widgetH:l,dx:i,dy:g,titleFontSize:s,titleX:n,titleY:m,valueFontSize:k,valueX:r,valueY:p,labelFontSize:b,labelX:f,labelY:e,minFontSize:j,minX:w,minY:v,maxFontSize:h,maxX:u,maxY:t};this.canvas.customAttributes.pki=function(K,L,N,E,O,F,D,y){var B=(1-(K-L)/(N-L))*Math.PI,G=E/2-E/10,J=G-E/6.666666666666667*y,C=E/2+F,A=O/1.25+D,H=E/2+F+G*Math.cos(B),P=O-(O-A)+D-G*Math.sin(B),M=E/2+F+J*Math.cos(B),z=O-(O-A)+D-J*Math.sin(B),I;I+="M"+(C-J)+","+A+" ";I+="L"+(C-G)+","+A+" ";I+="A"+G+","+G+" 0 0,1 "+H+","+P+" ";I+="L"+M+","+z+" ";I+="A"+J+","+J+" 0 0,0 "+(C-J)+","+A+" ";I+="z ";return{path:I}};this.gauge=this.canvas.path().attr({stroke:"none",fill:this.config.gaugeColor,pki:[this.config.max,this.config.min,this.config.max,this.params.widgetW,this.params.widgetH,this.params.dx,this.params.dy,this.config.gaugeWidthScale]});this.gauge.id=this.config.id+"-gauge";this.level=this.canvas.path().attr({stroke:"none",fill:getColorForPercentage((this.config.value-this.config.min)/(this.config.max-this.config.min),this.config.levelColors,this.config.levelColorsGradient),pki:[this.config.min,this.config.min,this.config.max,this.params.widgetW,this.params.widgetH,this.params.dx,this.params.dy,this.config.gaugeWidthScale]});this.level.id=this.config.id+"-level";this.txtTitle=this.canvas.text(this.params.titleX,this.params.titleY,this.config.title);this.txtTitle.attr({"font-size":this.params.titleFontSize,"font-weight":"bold","font-family":"Arial",fill:this.config.titleFontColor,"fill-opacity":"1"});this.txtTitle.id=this.config.id+"-txttitle";this.txtValue=this.canvas.text(this.params.valueX,this.params.valueY,this.originalValue);this.txtValue.attr({"font-size":this.params.valueFontSize,"font-weight":"bold","font-family":"Arial",fill:this.config.valueFontColor,"fill-opacity":"0"});this.txtValue.id=this.config.id+"-txtvalue";this.txtLabel=this.canvas.text(this.params.labelX,this.params.labelY,this.config.label);this.txtLabel.attr({"font-size":this.params.labelFontSize,"font-weight":"normal","font-family":"Arial",fill:this.config.labelFontColor,"fill-opacity":"0"});this.txtLabel.id=this.config.id+"-txtlabel";this.txtMin=this.canvas.text(this.params.minX,this.params.minY,this.config.min);this.txtMin.attr({"font-size":this.params.minFontSize,"font-weight":"normal","font-family":"Arial",fill:this.config.labelFontColor,"fill-opacity":(this.config.showMinMax==true)?"1":"0"});this.txtMin.id=this.config.id+"-txtmin";this.txtMax=this.canvas.text(this.params.maxX,this.params.maxY,this.config.max);this.txtMax.attr({"font-size":this.params.maxFontSize,"font-weight":"normal","font-family":"Arial",fill:this.config.labelFontColor,"fill-opacity":(this.config.showMinMax==true)?"1":"0"});this.txtMax.id=this.config.id+"-txtmax";var a=this.canvas.canvas.childNodes[1];var o="http://www.w3.org/2000/svg";if(ie<9){onCreateElementNsReady(function(){this.generateShadow()})}else{this.generateShadow(o,a)}this.level.animate({pki:[this.config.value,this.config.min,this.config.max,this.params.widgetW,this.params.widgetH,this.params.dx,this.params.dy,this.config.gaugeWidthScale]},this.config.startAnimationTime,this.config.startAnimationType);this.txtValue.animate({"fill-opacity":"1"},this.config.startAnimationTime,this.config.startAnimationType);this.txtLabel.animate({"fill-opacity":"1"},this.config.startAnimationTime,this.config.startAnimationType)};JustGage.prototype.refresh=function(b){originalVal=b;if(b>this.config.max){b=this.config.max}if(b",b[0]){}return a>4?a:c}()); diff --git a/UPW_Browser/main/jquery-1.8.3.min.js b/UPW_Browser/main/jquery-1.8.3.min.js new file mode 100644 index 0000000..c2a6b17 --- /dev/null +++ b/UPW_Browser/main/jquery-1.8.3.min.js @@ -0,0 +1,2 @@ +/*! jQuery v1.8.3 jquery.com | jquery.org/license */ +(function(e,t){function _(e){var t=M[e]={};return v.each(e.split(y),function(e,n){t[n]=!0}),t}function H(e,n,r){if(r===t&&e.nodeType===1){var i="data-"+n.replace(P,"-$1").toLowerCase();r=e.getAttribute(i);if(typeof r=="string"){try{r=r==="true"?!0:r==="false"?!1:r==="null"?null:+r+""===r?+r:D.test(r)?v.parseJSON(r):r}catch(s){}v.data(e,n,r)}else r=t}return r}function B(e){var t;for(t in e){if(t==="data"&&v.isEmptyObject(e[t]))continue;if(t!=="toJSON")return!1}return!0}function et(){return!1}function tt(){return!0}function ut(e){return!e||!e.parentNode||e.parentNode.nodeType===11}function at(e,t){do e=e[t];while(e&&e.nodeType!==1);return e}function ft(e,t,n){t=t||0;if(v.isFunction(t))return v.grep(e,function(e,r){var i=!!t.call(e,r,e);return i===n});if(t.nodeType)return v.grep(e,function(e,r){return e===t===n});if(typeof t=="string"){var r=v.grep(e,function(e){return e.nodeType===1});if(it.test(t))return v.filter(t,r,!n);t=v.filter(t,r)}return v.grep(e,function(e,r){return v.inArray(e,t)>=0===n})}function lt(e){var t=ct.split("|"),n=e.createDocumentFragment();if(n.createElement)while(t.length)n.createElement(t.pop());return n}function Lt(e,t){return e.getElementsByTagName(t)[0]||e.appendChild(e.ownerDocument.createElement(t))}function At(e,t){if(t.nodeType!==1||!v.hasData(e))return;var n,r,i,s=v._data(e),o=v._data(t,s),u=s.events;if(u){delete o.handle,o.events={};for(n in u)for(r=0,i=u[n].length;r").appendTo(i.body),n=t.css("display");t.remove();if(n==="none"||n===""){Pt=i.body.appendChild(Pt||v.extend(i.createElement("iframe"),{frameBorder:0,width:0,height:0}));if(!Ht||!Pt.createElement)Ht=(Pt.contentWindow||Pt.contentDocument).document,Ht.write(""),Ht.close();t=Ht.body.appendChild(Ht.createElement(e)),n=Dt(t,"display"),i.body.removeChild(Pt)}return Wt[e]=n,n}function fn(e,t,n,r){var i;if(v.isArray(t))v.each(t,function(t,i){n||sn.test(e)?r(e,i):fn(e+"["+(typeof i=="object"?t:"")+"]",i,n,r)});else if(!n&&v.type(t)==="object")for(i in t)fn(e+"["+i+"]",t[i],n,r);else r(e,t)}function Cn(e){return function(t,n){typeof t!="string"&&(n=t,t="*");var r,i,s,o=t.toLowerCase().split(y),u=0,a=o.length;if(v.isFunction(n))for(;u)[^>]*$|#([\w\-]*)$)/,E=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,S=/^[\],:{}\s]*$/,x=/(?:^|:|,)(?:\s*\[)+/g,T=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,N=/"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g,C=/^-ms-/,k=/-([\da-z])/gi,L=function(e,t){return(t+"").toUpperCase()},A=function(){i.addEventListener?(i.removeEventListener("DOMContentLoaded",A,!1),v.ready()):i.readyState==="complete"&&(i.detachEvent("onreadystatechange",A),v.ready())},O={};v.fn=v.prototype={constructor:v,init:function(e,n,r){var s,o,u,a;if(!e)return this;if(e.nodeType)return this.context=this[0]=e,this.length=1,this;if(typeof e=="string"){e.charAt(0)==="<"&&e.charAt(e.length-1)===">"&&e.length>=3?s=[null,e,null]:s=w.exec(e);if(s&&(s[1]||!n)){if(s[1])return n=n instanceof v?n[0]:n,a=n&&n.nodeType?n.ownerDocument||n:i,e=v.parseHTML(s[1],a,!0),E.test(s[1])&&v.isPlainObject(n)&&this.attr.call(e,n,!0),v.merge(this,e);o=i.getElementById(s[2]);if(o&&o.parentNode){if(o.id!==s[2])return r.find(e);this.length=1,this[0]=o}return this.context=i,this.selector=e,this}return!n||n.jquery?(n||r).find(e):this.constructor(n).find(e)}return v.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),v.makeArray(e,this))},selector:"",jquery:"1.8.3",length:0,size:function(){return this.length},toArray:function(){return l.call(this)},get:function(e){return e==null?this.toArray():e<0?this[this.length+e]:this[e]},pushStack:function(e,t,n){var r=v.merge(this.constructor(),e);return r.prevObject=this,r.context=this.context,t==="find"?r.selector=this.selector+(this.selector?" ":"")+n:t&&(r.selector=this.selector+"."+t+"("+n+")"),r},each:function(e,t){return v.each(this,e,t)},ready:function(e){return v.ready.promise().done(e),this},eq:function(e){return e=+e,e===-1?this.slice(e):this.slice(e,e+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(l.apply(this,arguments),"slice",l.call(arguments).join(","))},map:function(e){return this.pushStack(v.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:[].sort,splice:[].splice},v.fn.init.prototype=v.fn,v.extend=v.fn.extend=function(){var e,n,r,i,s,o,u=arguments[0]||{},a=1,f=arguments.length,l=!1;typeof u=="boolean"&&(l=u,u=arguments[1]||{},a=2),typeof u!="object"&&!v.isFunction(u)&&(u={}),f===a&&(u=this,--a);for(;a0)return;r.resolveWith(i,[v]),v.fn.trigger&&v(i).trigger("ready").off("ready")},isFunction:function(e){return v.type(e)==="function"},isArray:Array.isArray||function(e){return v.type(e)==="array"},isWindow:function(e){return e!=null&&e==e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return e==null?String(e):O[h.call(e)]||"object"},isPlainObject:function(e){if(!e||v.type(e)!=="object"||e.nodeType||v.isWindow(e))return!1;try{if(e.constructor&&!p.call(e,"constructor")&&!p.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(n){return!1}var r;for(r in e);return r===t||p.call(e,r)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw new Error(e)},parseHTML:function(e,t,n){var r;return!e||typeof e!="string"?null:(typeof t=="boolean"&&(n=t,t=0),t=t||i,(r=E.exec(e))?[t.createElement(r[1])]:(r=v.buildFragment([e],t,n?null:[]),v.merge([],(r.cacheable?v.clone(r.fragment):r.fragment).childNodes)))},parseJSON:function(t){if(!t||typeof t!="string")return null;t=v.trim(t);if(e.JSON&&e.JSON.parse)return e.JSON.parse(t);if(S.test(t.replace(T,"@").replace(N,"]").replace(x,"")))return(new Function("return "+t))();v.error("Invalid JSON: "+t)},parseXML:function(n){var r,i;if(!n||typeof n!="string")return null;try{e.DOMParser?(i=new DOMParser,r=i.parseFromString(n,"text/xml")):(r=new ActiveXObject("Microsoft.XMLDOM"),r.async="false",r.loadXML(n))}catch(s){r=t}return(!r||!r.documentElement||r.getElementsByTagName("parsererror").length)&&v.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&g.test(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(C,"ms-").replace(k,L)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,n,r){var i,s=0,o=e.length,u=o===t||v.isFunction(e);if(r){if(u){for(i in e)if(n.apply(e[i],r)===!1)break}else for(;s0&&e[0]&&e[a-1]||a===0||v.isArray(e));if(f)for(;u-1)a.splice(n,1),i&&(n<=o&&o--,n<=u&&u--)}),this},has:function(e){return v.inArray(e,a)>-1},empty:function(){return a=[],this},disable:function(){return a=f=n=t,this},disabled:function(){return!a},lock:function(){return f=t,n||c.disable(),this},locked:function(){return!f},fireWith:function(e,t){return t=t||[],t=[e,t.slice?t.slice():t],a&&(!r||f)&&(i?f.push(t):l(t)),this},fire:function(){return c.fireWith(this,arguments),this},fired:function(){return!!r}};return c},v.extend({Deferred:function(e){var t=[["resolve","done",v.Callbacks("once memory"),"resolved"],["reject","fail",v.Callbacks("once memory"),"rejected"],["notify","progress",v.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return v.Deferred(function(n){v.each(t,function(t,r){var s=r[0],o=e[t];i[r[1]](v.isFunction(o)?function(){var e=o.apply(this,arguments);e&&v.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[s+"With"](this===i?n:this,[e])}:n[s])}),e=null}).promise()},promise:function(e){return e!=null?v.extend(e,r):r}},i={};return r.pipe=r.then,v.each(t,function(e,s){var o=s[2],u=s[3];r[s[1]]=o.add,u&&o.add(function(){n=u},t[e^1][2].disable,t[2][2].lock),i[s[0]]=o.fire,i[s[0]+"With"]=o.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=l.call(arguments),r=n.length,i=r!==1||e&&v.isFunction(e.promise)?r:0,s=i===1?e:v.Deferred(),o=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?l.call(arguments):r,n===u?s.notifyWith(t,n):--i||s.resolveWith(t,n)}},u,a,f;if(r>1){u=new Array(r),a=new Array(r),f=new Array(r);for(;t
a",n=p.getElementsByTagName("*"),r=p.getElementsByTagName("a")[0];if(!n||!r||!n.length)return{};s=i.createElement("select"),o=s.appendChild(i.createElement("option")),u=p.getElementsByTagName("input")[0],r.style.cssText="top:1px;float:left;opacity:.5",t={leadingWhitespace:p.firstChild.nodeType===3,tbody:!p.getElementsByTagName("tbody").length,htmlSerialize:!!p.getElementsByTagName("link").length,style:/top/.test(r.getAttribute("style")),hrefNormalized:r.getAttribute("href")==="/a",opacity:/^0.5/.test(r.style.opacity),cssFloat:!!r.style.cssFloat,checkOn:u.value==="on",optSelected:o.selected,getSetAttribute:p.className!=="t",enctype:!!i.createElement("form").enctype,html5Clone:i.createElement("nav").cloneNode(!0).outerHTML!=="<:nav>",boxModel:i.compatMode==="CSS1Compat",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,boxSizingReliable:!0,pixelPosition:!1},u.checked=!0,t.noCloneChecked=u.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!o.disabled;try{delete p.test}catch(d){t.deleteExpando=!1}!p.addEventListener&&p.attachEvent&&p.fireEvent&&(p.attachEvent("onclick",h=function(){t.noCloneEvent=!1}),p.cloneNode(!0).fireEvent("onclick"),p.detachEvent("onclick",h)),u=i.createElement("input"),u.value="t",u.setAttribute("type","radio"),t.radioValue=u.value==="t",u.setAttribute("checked","checked"),u.setAttribute("name","t"),p.appendChild(u),a=i.createDocumentFragment(),a.appendChild(p.lastChild),t.checkClone=a.cloneNode(!0).cloneNode(!0).lastChild.checked,t.appendChecked=u.checked,a.removeChild(u),a.appendChild(p);if(p.attachEvent)for(l in{submit:!0,change:!0,focusin:!0})f="on"+l,c=f in p,c||(p.setAttribute(f,"return;"),c=typeof p[f]=="function"),t[l+"Bubbles"]=c;return v(function(){var n,r,s,o,u="padding:0;margin:0;border:0;display:block;overflow:hidden;",a=i.getElementsByTagName("body")[0];if(!a)return;n=i.createElement("div"),n.style.cssText="visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px",a.insertBefore(n,a.firstChild),r=i.createElement("div"),n.appendChild(r),r.innerHTML="
t
",s=r.getElementsByTagName("td"),s[0].style.cssText="padding:0;margin:0;border:0;display:none",c=s[0].offsetHeight===0,s[0].style.display="",s[1].style.display="none",t.reliableHiddenOffsets=c&&s[0].offsetHeight===0,r.innerHTML="",r.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",t.boxSizing=r.offsetWidth===4,t.doesNotIncludeMarginInBodyOffset=a.offsetTop!==1,e.getComputedStyle&&(t.pixelPosition=(e.getComputedStyle(r,null)||{}).top!=="1%",t.boxSizingReliable=(e.getComputedStyle(r,null)||{width:"4px"}).width==="4px",o=i.createElement("div"),o.style.cssText=r.style.cssText=u,o.style.marginRight=o.style.width="0",r.style.width="1px",r.appendChild(o),t.reliableMarginRight=!parseFloat((e.getComputedStyle(o,null)||{}).marginRight)),typeof r.style.zoom!="undefined"&&(r.innerHTML="",r.style.cssText=u+"width:1px;padding:1px;display:inline;zoom:1",t.inlineBlockNeedsLayout=r.offsetWidth===3,r.style.display="block",r.style.overflow="visible",r.innerHTML="
",r.firstChild.style.width="5px",t.shrinkWrapBlocks=r.offsetWidth!==3,n.style.zoom=1),a.removeChild(n),n=r=s=o=null}),a.removeChild(p),n=r=s=o=u=a=p=null,t}();var D=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,P=/([A-Z])/g;v.extend({cache:{},deletedIds:[],uuid:0,expando:"jQuery"+(v.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(e){return e=e.nodeType?v.cache[e[v.expando]]:e[v.expando],!!e&&!B(e)},data:function(e,n,r,i){if(!v.acceptData(e))return;var s,o,u=v.expando,a=typeof n=="string",f=e.nodeType,l=f?v.cache:e,c=f?e[u]:e[u]&&u;if((!c||!l[c]||!i&&!l[c].data)&&a&&r===t)return;c||(f?e[u]=c=v.deletedIds.pop()||v.guid++:c=u),l[c]||(l[c]={},f||(l[c].toJSON=v.noop));if(typeof n=="object"||typeof n=="function")i?l[c]=v.extend(l[c],n):l[c].data=v.extend(l[c].data,n);return s=l[c],i||(s.data||(s.data={}),s=s.data),r!==t&&(s[v.camelCase(n)]=r),a?(o=s[n],o==null&&(o=s[v.camelCase(n)])):o=s,o},removeData:function(e,t,n){if(!v.acceptData(e))return;var r,i,s,o=e.nodeType,u=o?v.cache:e,a=o?e[v.expando]:v.expando;if(!u[a])return;if(t){r=n?u[a]:u[a].data;if(r){v.isArray(t)||(t in r?t=[t]:(t=v.camelCase(t),t in r?t=[t]:t=t.split(" ")));for(i=0,s=t.length;i1,null,!1))},removeData:function(e){return this.each(function(){v.removeData(this,e)})}}),v.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=v._data(e,t),n&&(!r||v.isArray(n)?r=v._data(e,t,v.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=v.queue(e,t),r=n.length,i=n.shift(),s=v._queueHooks(e,t),o=function(){v.dequeue(e,t)};i==="inprogress"&&(i=n.shift(),r--),i&&(t==="fx"&&n.unshift("inprogress"),delete s.stop,i.call(e,o,s)),!r&&s&&s.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return v._data(e,n)||v._data(e,n,{empty:v.Callbacks("once memory").add(function(){v.removeData(e,t+"queue",!0),v.removeData(e,n,!0)})})}}),v.fn.extend({queue:function(e,n){var r=2;return typeof e!="string"&&(n=e,e="fx",r--),arguments.length1)},removeAttr:function(e){return this.each(function(){v.removeAttr(this,e)})},prop:function(e,t){return v.access(this,v.prop,e,t,arguments.length>1)},removeProp:function(e){return e=v.propFix[e]||e,this.each(function(){try{this[e]=t,delete this[e]}catch(n){}})},addClass:function(e){var t,n,r,i,s,o,u;if(v.isFunction(e))return this.each(function(t){v(this).addClass(e.call(this,t,this.className))});if(e&&typeof e=="string"){t=e.split(y);for(n=0,r=this.length;n=0)r=r.replace(" "+n[s]+" "," ");i.className=e?v.trim(r):""}}}return this},toggleClass:function(e,t){var n=typeof e,r=typeof t=="boolean";return v.isFunction(e)?this.each(function(n){v(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if(n==="string"){var i,s=0,o=v(this),u=t,a=e.split(y);while(i=a[s++])u=r?u:!o.hasClass(i),o[u?"addClass":"removeClass"](i)}else if(n==="undefined"||n==="boolean")this.className&&v._data(this,"__className__",this.className),this.className=this.className||e===!1?"":v._data(this,"__className__")||""})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;n=0)return!0;return!1},val:function(e){var n,r,i,s=this[0];if(!arguments.length){if(s)return n=v.valHooks[s.type]||v.valHooks[s.nodeName.toLowerCase()],n&&"get"in n&&(r=n.get(s,"value"))!==t?r:(r=s.value,typeof r=="string"?r.replace(R,""):r==null?"":r);return}return i=v.isFunction(e),this.each(function(r){var s,o=v(this);if(this.nodeType!==1)return;i?s=e.call(this,r,o.val()):s=e,s==null?s="":typeof s=="number"?s+="":v.isArray(s)&&(s=v.map(s,function(e){return e==null?"":e+""})),n=v.valHooks[this.type]||v.valHooks[this.nodeName.toLowerCase()];if(!n||!("set"in n)||n.set(this,s,"value")===t)this.value=s})}}),v.extend({valHooks:{option:{get:function(e){var t=e.attributes.value;return!t||t.specified?e.value:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,s=e.type==="select-one"||i<0,o=s?null:[],u=s?i+1:r.length,a=i<0?u:s?i:0;for(;a=0}),n.length||(e.selectedIndex=-1),n}}},attrFn:{},attr:function(e,n,r,i){var s,o,u,a=e.nodeType;if(!e||a===3||a===8||a===2)return;if(i&&v.isFunction(v.fn[n]))return v(e)[n](r);if(typeof e.getAttribute=="undefined")return v.prop(e,n,r);u=a!==1||!v.isXMLDoc(e),u&&(n=n.toLowerCase(),o=v.attrHooks[n]||(X.test(n)?F:j));if(r!==t){if(r===null){v.removeAttr(e,n);return}return o&&"set"in o&&u&&(s=o.set(e,r,n))!==t?s:(e.setAttribute(n,r+""),r)}return o&&"get"in o&&u&&(s=o.get(e,n))!==null?s:(s=e.getAttribute(n),s===null?t:s)},removeAttr:function(e,t){var n,r,i,s,o=0;if(t&&e.nodeType===1){r=t.split(y);for(;o=0}})});var $=/^(?:textarea|input|select)$/i,J=/^([^\.]*|)(?:\.(.+)|)$/,K=/(?:^|\s)hover(\.\S+|)\b/,Q=/^key/,G=/^(?:mouse|contextmenu)|click/,Y=/^(?:focusinfocus|focusoutblur)$/,Z=function(e){return v.event.special.hover?e:e.replace(K,"mouseenter$1 mouseleave$1")};v.event={add:function(e,n,r,i,s){var o,u,a,f,l,c,h,p,d,m,g;if(e.nodeType===3||e.nodeType===8||!n||!r||!(o=v._data(e)))return;r.handler&&(d=r,r=d.handler,s=d.selector),r.guid||(r.guid=v.guid++),a=o.events,a||(o.events=a={}),u=o.handle,u||(o.handle=u=function(e){return typeof v=="undefined"||!!e&&v.event.triggered===e.type?t:v.event.dispatch.apply(u.elem,arguments)},u.elem=e),n=v.trim(Z(n)).split(" ");for(f=0;f=0&&(y=y.slice(0,-1),a=!0),y.indexOf(".")>=0&&(b=y.split("."),y=b.shift(),b.sort());if((!s||v.event.customEvent[y])&&!v.event.global[y])return;n=typeof n=="object"?n[v.expando]?n:new v.Event(y,n):new v.Event(y),n.type=y,n.isTrigger=!0,n.exclusive=a,n.namespace=b.join("."),n.namespace_re=n.namespace?new RegExp("(^|\\.)"+b.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,h=y.indexOf(":")<0?"on"+y:"";if(!s){u=v.cache;for(f in u)u[f].events&&u[f].events[y]&&v.event.trigger(n,r,u[f].handle.elem,!0);return}n.result=t,n.target||(n.target=s),r=r!=null?v.makeArray(r):[],r.unshift(n),p=v.event.special[y]||{};if(p.trigger&&p.trigger.apply(s,r)===!1)return;m=[[s,p.bindType||y]];if(!o&&!p.noBubble&&!v.isWindow(s)){g=p.delegateType||y,l=Y.test(g+y)?s:s.parentNode;for(c=s;l;l=l.parentNode)m.push([l,g]),c=l;c===(s.ownerDocument||i)&&m.push([c.defaultView||c.parentWindow||e,g])}for(f=0;f=0:v.find(h,this,null,[s]).length),u[h]&&f.push(c);f.length&&w.push({elem:s,matches:f})}d.length>m&&w.push({elem:this,matches:d.slice(m)});for(r=0;r0?this.on(t,null,e,n):this.trigger(t)},Q.test(t)&&(v.event.fixHooks[t]=v.event.keyHooks),G.test(t)&&(v.event.fixHooks[t]=v.event.mouseHooks)}),function(e,t){function nt(e,t,n,r){n=n||[],t=t||g;var i,s,a,f,l=t.nodeType;if(!e||typeof e!="string")return n;if(l!==1&&l!==9)return[];a=o(t);if(!a&&!r)if(i=R.exec(e))if(f=i[1]){if(l===9){s=t.getElementById(f);if(!s||!s.parentNode)return n;if(s.id===f)return n.push(s),n}else if(t.ownerDocument&&(s=t.ownerDocument.getElementById(f))&&u(t,s)&&s.id===f)return n.push(s),n}else{if(i[2])return S.apply(n,x.call(t.getElementsByTagName(e),0)),n;if((f=i[3])&&Z&&t.getElementsByClassName)return S.apply(n,x.call(t.getElementsByClassName(f),0)),n}return vt(e.replace(j,"$1"),t,n,r,a)}function rt(e){return function(t){var n=t.nodeName.toLowerCase();return n==="input"&&t.type===e}}function it(e){return function(t){var n=t.nodeName.toLowerCase();return(n==="input"||n==="button")&&t.type===e}}function st(e){return N(function(t){return t=+t,N(function(n,r){var i,s=e([],n.length,t),o=s.length;while(o--)n[i=s[o]]&&(n[i]=!(r[i]=n[i]))})})}function ot(e,t,n){if(e===t)return n;var r=e.nextSibling;while(r){if(r===t)return-1;r=r.nextSibling}return 1}function ut(e,t){var n,r,s,o,u,a,f,l=L[d][e+" "];if(l)return t?0:l.slice(0);u=e,a=[],f=i.preFilter;while(u){if(!n||(r=F.exec(u)))r&&(u=u.slice(r[0].length)||u),a.push(s=[]);n=!1;if(r=I.exec(u))s.push(n=new m(r.shift())),u=u.slice(n.length),n.type=r[0].replace(j," ");for(o in i.filter)(r=J[o].exec(u))&&(!f[o]||(r=f[o](r)))&&(s.push(n=new m(r.shift())),u=u.slice(n.length),n.type=o,n.matches=r);if(!n)break}return t?u.length:u?nt.error(e):L(e,a).slice(0)}function at(e,t,r){var i=t.dir,s=r&&t.dir==="parentNode",o=w++;return t.first?function(t,n,r){while(t=t[i])if(s||t.nodeType===1)return e(t,n,r)}:function(t,r,u){if(!u){var a,f=b+" "+o+" ",l=f+n;while(t=t[i])if(s||t.nodeType===1){if((a=t[d])===l)return t.sizset;if(typeof a=="string"&&a.indexOf(f)===0){if(t.sizset)return t}else{t[d]=l;if(e(t,r,u))return t.sizset=!0,t;t.sizset=!1}}}else while(t=t[i])if(s||t.nodeType===1)if(e(t,r,u))return t}}function ft(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function lt(e,t,n,r,i){var s,o=[],u=0,a=e.length,f=t!=null;for(;u-1&&(s[f]=!(o[f]=c))}}else g=lt(g===o?g.splice(d,g.length):g),i?i(null,o,g,a):S.apply(o,g)})}function ht(e){var t,n,r,s=e.length,o=i.relative[e[0].type],u=o||i.relative[" "],a=o?1:0,f=at(function(e){return e===t},u,!0),l=at(function(e){return T.call(t,e)>-1},u,!0),h=[function(e,n,r){return!o&&(r||n!==c)||((t=n).nodeType?f(e,n,r):l(e,n,r))}];for(;a1&&ft(h),a>1&&e.slice(0,a-1).join("").replace(j,"$1"),n,a0,s=e.length>0,o=function(u,a,f,l,h){var p,d,v,m=[],y=0,w="0",x=u&&[],T=h!=null,N=c,C=u||s&&i.find.TAG("*",h&&a.parentNode||a),k=b+=N==null?1:Math.E;T&&(c=a!==g&&a,n=o.el);for(;(p=C[w])!=null;w++){if(s&&p){for(d=0;v=e[d];d++)if(v(p,a,f)){l.push(p);break}T&&(b=k,n=++o.el)}r&&((p=!v&&p)&&y--,u&&x.push(p))}y+=w;if(r&&w!==y){for(d=0;v=t[d];d++)v(x,m,a,f);if(u){if(y>0)while(w--)!x[w]&&!m[w]&&(m[w]=E.call(l));m=lt(m)}S.apply(l,m),T&&!u&&m.length>0&&y+t.length>1&&nt.uniqueSort(l)}return T&&(b=k,c=N),x};return o.el=0,r?N(o):o}function dt(e,t,n){var r=0,i=t.length;for(;r2&&(f=u[0]).type==="ID"&&t.nodeType===9&&!s&&i.relative[u[1].type]){t=i.find.ID(f.matches[0].replace($,""),t,s)[0];if(!t)return n;e=e.slice(u.shift().length)}for(o=J.POS.test(e)?-1:u.length-1;o>=0;o--){f=u[o];if(i.relative[l=f.type])break;if(c=i.find[l])if(r=c(f.matches[0].replace($,""),z.test(u[0].type)&&t.parentNode||t,s)){u.splice(o,1),e=r.length&&u.join("");if(!e)return S.apply(n,x.call(r,0)),n;break}}}return a(e,h)(r,t,s,n,z.test(e)),n}function mt(){}var n,r,i,s,o,u,a,f,l,c,h=!0,p="undefined",d=("sizcache"+Math.random()).replace(".",""),m=String,g=e.document,y=g.documentElement,b=0,w=0,E=[].pop,S=[].push,x=[].slice,T=[].indexOf||function(e){var t=0,n=this.length;for(;ti.cacheLength&&delete e[t.shift()],e[n+" "]=r},e)},k=C(),L=C(),A=C(),O="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+",_=M.replace("w","w#"),D="([*^$|!~]?=)",P="\\["+O+"*("+M+")"+O+"*(?:"+D+O+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+_+")|)|)"+O+"*\\]",H=":("+M+")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|([^()[\\]]*|(?:(?:"+P+")|[^:]|\\\\.)*|.*))\\)|)",B=":(even|odd|eq|gt|lt|nth|first|last)(?:\\("+O+"*((?:-\\d)?\\d*)"+O+"*\\)|)(?=[^-]|$)",j=new RegExp("^"+O+"+|((?:^|[^\\\\])(?:\\\\.)*)"+O+"+$","g"),F=new RegExp("^"+O+"*,"+O+"*"),I=new RegExp("^"+O+"*([\\x20\\t\\r\\n\\f>+~])"+O+"*"),q=new RegExp(H),R=/^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,U=/^:not/,z=/[\x20\t\r\n\f]*[+~]/,W=/:not\($/,X=/h\d/i,V=/input|select|textarea|button/i,$=/\\(?!\\)/g,J={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),NAME:new RegExp("^\\[name=['\"]?("+M+")['\"]?\\]"),TAG:new RegExp("^("+M.replace("w","w*")+")"),ATTR:new RegExp("^"+P),PSEUDO:new RegExp("^"+H),POS:new RegExp(B,"i"),CHILD:new RegExp("^:(only|nth|first|last)-child(?:\\("+O+"*(even|odd|(([+-]|)(\\d*)n|)"+O+"*(?:([+-]|)"+O+"*(\\d+)|))"+O+"*\\)|)","i"),needsContext:new RegExp("^"+O+"*[>+~]|"+B,"i")},K=function(e){var t=g.createElement("div");try{return e(t)}catch(n){return!1}finally{t=null}},Q=K(function(e){return e.appendChild(g.createComment("")),!e.getElementsByTagName("*").length}),G=K(function(e){return e.innerHTML="",e.firstChild&&typeof e.firstChild.getAttribute!==p&&e.firstChild.getAttribute("href")==="#"}),Y=K(function(e){e.innerHTML="";var t=typeof e.lastChild.getAttribute("multiple");return t!=="boolean"&&t!=="string"}),Z=K(function(e){return e.innerHTML="",!e.getElementsByClassName||!e.getElementsByClassName("e").length?!1:(e.lastChild.className="e",e.getElementsByClassName("e").length===2)}),et=K(function(e){e.id=d+0,e.innerHTML="
",y.insertBefore(e,y.firstChild);var t=g.getElementsByName&&g.getElementsByName(d).length===2+g.getElementsByName(d+0).length;return r=!g.getElementById(d),y.removeChild(e),t});try{x.call(y.childNodes,0)[0].nodeType}catch(tt){x=function(e){var t,n=[];for(;t=this[e];e++)n.push(t);return n}}nt.matches=function(e,t){return nt(e,null,null,t)},nt.matchesSelector=function(e,t){return nt(t,null,null,[e]).length>0},s=nt.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(i===1||i===9||i===11){if(typeof e.textContent=="string")return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=s(e)}else if(i===3||i===4)return e.nodeValue}else for(;t=e[r];r++)n+=s(t);return n},o=nt.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?t.nodeName!=="HTML":!1},u=nt.contains=y.contains?function(e,t){var n=e.nodeType===9?e.documentElement:e,r=t&&t.parentNode;return e===r||!!(r&&r.nodeType===1&&n.contains&&n.contains(r))}:y.compareDocumentPosition?function(e,t){return t&&!!(e.compareDocumentPosition(t)&16)}:function(e,t){while(t=t.parentNode)if(t===e)return!0;return!1},nt.attr=function(e,t){var n,r=o(e);return r||(t=t.toLowerCase()),(n=i.attrHandle[t])?n(e):r||Y?e.getAttribute(t):(n=e.getAttributeNode(t),n?typeof e[t]=="boolean"?e[t]?t:null:n.specified?n.value:null:null)},i=nt.selectors={cacheLength:50,createPseudo:N,match:J,attrHandle:G?{}:{href:function(e){return e.getAttribute("href",2)},type:function(e){return e.getAttribute("type")}},find:{ID:r?function(e,t,n){if(typeof t.getElementById!==p&&!n){var r=t.getElementById(e);return r&&r.parentNode?[r]:[]}}:function(e,n,r){if(typeof n.getElementById!==p&&!r){var i=n.getElementById(e);return i?i.id===e||typeof i.getAttributeNode!==p&&i.getAttributeNode("id").value===e?[i]:t:[]}},TAG:Q?function(e,t){if(typeof t.getElementsByTagName!==p)return t.getElementsByTagName(e)}:function(e,t){var n=t.getElementsByTagName(e);if(e==="*"){var r,i=[],s=0;for(;r=n[s];s++)r.nodeType===1&&i.push(r);return i}return n},NAME:et&&function(e,t){if(typeof t.getElementsByName!==p)return t.getElementsByName(name)},CLASS:Z&&function(e,t,n){if(typeof t.getElementsByClassName!==p&&!n)return t.getElementsByClassName(e)}},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace($,""),e[3]=(e[4]||e[5]||"").replace($,""),e[2]==="~="&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),e[1]==="nth"?(e[2]||nt.error(e[0]),e[3]=+(e[3]?e[4]+(e[5]||1):2*(e[2]==="even"||e[2]==="odd")),e[4]=+(e[6]+e[7]||e[2]==="odd")):e[2]&&nt.error(e[0]),e},PSEUDO:function(e){var t,n;if(J.CHILD.test(e[0]))return null;if(e[3])e[2]=e[3];else if(t=e[4])q.test(t)&&(n=ut(t,!0))&&(n=t.indexOf(")",t.length-n)-t.length)&&(t=t.slice(0,n),e[0]=e[0].slice(0,n)),e[2]=t;return e.slice(0,3)}},filter:{ID:r?function(e){return e=e.replace($,""),function(t){return t.getAttribute("id")===e}}:function(e){return e=e.replace($,""),function(t){var n=typeof t.getAttributeNode!==p&&t.getAttributeNode("id");return n&&n.value===e}},TAG:function(e){return e==="*"?function(){return!0}:(e=e.replace($,"").toLowerCase(),function(t){return t.nodeName&&t.nodeName.toLowerCase()===e})},CLASS:function(e){var t=k[d][e+" "];return t||(t=new RegExp("(^|"+O+")"+e+"("+O+"|$)"))&&k(e,function(e){return t.test(e.className||typeof e.getAttribute!==p&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r,i){var s=nt.attr(r,e);return s==null?t==="!=":t?(s+="",t==="="?s===n:t==="!="?s!==n:t==="^="?n&&s.indexOf(n)===0:t==="*="?n&&s.indexOf(n)>-1:t==="$="?n&&s.substr(s.length-n.length)===n:t==="~="?(" "+s+" ").indexOf(n)>-1:t==="|="?s===n||s.substr(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r){return e==="nth"?function(e){var t,i,s=e.parentNode;if(n===1&&r===0)return!0;if(s){i=0;for(t=s.firstChild;t;t=t.nextSibling)if(t.nodeType===1){i++;if(e===t)break}}return i-=r,i===n||i%n===0&&i/n>=0}:function(t){var n=t;switch(e){case"only":case"first":while(n=n.previousSibling)if(n.nodeType===1)return!1;if(e==="first")return!0;n=t;case"last":while(n=n.nextSibling)if(n.nodeType===1)return!1;return!0}}},PSEUDO:function(e,t){var n,r=i.pseudos[e]||i.setFilters[e.toLowerCase()]||nt.error("unsupported pseudo: "+e);return r[d]?r(t):r.length>1?(n=[e,e,"",t],i.setFilters.hasOwnProperty(e.toLowerCase())?N(function(e,n){var i,s=r(e,t),o=s.length;while(o--)i=T.call(e,s[o]),e[i]=!(n[i]=s[o])}):function(e){return r(e,0,n)}):r}},pseudos:{not:N(function(e){var t=[],n=[],r=a(e.replace(j,"$1"));return r[d]?N(function(e,t,n,i){var s,o=r(e,null,i,[]),u=e.length;while(u--)if(s=o[u])e[u]=!(t[u]=s)}):function(e,i,s){return t[0]=e,r(t,null,s,n),!n.pop()}}),has:N(function(e){return function(t){return nt(e,t).length>0}}),contains:N(function(e){return function(t){return(t.textContent||t.innerText||s(t)).indexOf(e)>-1}}),enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return t==="input"&&!!e.checked||t==="option"&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},parent:function(e){return!i.pseudos.empty(e)},empty:function(e){var t;e=e.firstChild;while(e){if(e.nodeName>"@"||(t=e.nodeType)===3||t===4)return!1;e=e.nextSibling}return!0},header:function(e){return X.test(e.nodeName)},text:function(e){var t,n;return e.nodeName.toLowerCase()==="input"&&(t=e.type)==="text"&&((n=e.getAttribute("type"))==null||n.toLowerCase()===t)},radio:rt("radio"),checkbox:rt("checkbox"),file:rt("file"),password:rt("password"),image:rt("image"),submit:it("submit"),reset:it("reset"),button:function(e){var t=e.nodeName.toLowerCase();return t==="input"&&e.type==="button"||t==="button"},input:function(e){return V.test(e.nodeName)},focus:function(e){var t=e.ownerDocument;return e===t.activeElement&&(!t.hasFocus||t.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},active:function(e){return e===e.ownerDocument.activeElement},first:st(function(){return[0]}),last:st(function(e,t){return[t-1]}),eq:st(function(e,t,n){return[n<0?n+t:n]}),even:st(function(e,t){for(var n=0;n=0;)e.push(r);return e}),gt:st(function(e,t,n){for(var r=n<0?n+t:n;++r",e.querySelectorAll("[selected]").length||i.push("\\["+O+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)"),e.querySelectorAll(":checked").length||i.push(":checked")}),K(function(e){e.innerHTML="

",e.querySelectorAll("[test^='']").length&&i.push("[*^$]="+O+"*(?:\"\"|'')"),e.innerHTML="",e.querySelectorAll(":enabled").length||i.push(":enabled",":disabled")}),i=new RegExp(i.join("|")),vt=function(e,r,s,o,u){if(!o&&!u&&!i.test(e)){var a,f,l=!0,c=d,h=r,p=r.nodeType===9&&e;if(r.nodeType===1&&r.nodeName.toLowerCase()!=="object"){a=ut(e),(l=r.getAttribute("id"))?c=l.replace(n,"\\$&"):r.setAttribute("id",c),c="[id='"+c+"'] ",f=a.length;while(f--)a[f]=c+a[f].join("");h=z.test(e)&&r.parentNode||r,p=a.join(",")}if(p)try{return S.apply(s,x.call(h.querySelectorAll(p),0)),s}catch(v){}finally{l||r.removeAttribute("id")}}return t(e,r,s,o,u)},u&&(K(function(t){e=u.call(t,"div");try{u.call(t,"[test!='']:sizzle"),s.push("!=",H)}catch(n){}}),s=new RegExp(s.join("|")),nt.matchesSelector=function(t,n){n=n.replace(r,"='$1']");if(!o(t)&&!s.test(n)&&!i.test(n))try{var a=u.call(t,n);if(a||e||t.document&&t.document.nodeType!==11)return a}catch(f){}return nt(n,null,null,[t]).length>0})}(),i.pseudos.nth=i.pseudos.eq,i.filters=mt.prototype=i.pseudos,i.setFilters=new mt,nt.attr=v.attr,v.find=nt,v.expr=nt.selectors,v.expr[":"]=v.expr.pseudos,v.unique=nt.uniqueSort,v.text=nt.getText,v.isXMLDoc=nt.isXML,v.contains=nt.contains}(e);var nt=/Until$/,rt=/^(?:parents|prev(?:Until|All))/,it=/^.[^:#\[\.,]*$/,st=v.expr.match.needsContext,ot={children:!0,contents:!0,next:!0,prev:!0};v.fn.extend({find:function(e){var t,n,r,i,s,o,u=this;if(typeof e!="string")return v(e).filter(function(){for(t=0,n=u.length;t0)for(i=r;i=0:v.filter(e,this).length>0:this.filter(e).length>0)},closest:function(e,t){var n,r=0,i=this.length,s=[],o=st.test(e)||typeof e!="string"?v(e,t||this.context):0;for(;r-1:v.find.matchesSelector(n,e)){s.push(n);break}n=n.parentNode}}return s=s.length>1?v.unique(s):s,this.pushStack(s,"closest",e)},index:function(e){return e?typeof e=="string"?v.inArray(this[0],v(e)):v.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.prevAll().length:-1},add:function(e,t){var n=typeof e=="string"?v(e,t):v.makeArray(e&&e.nodeType?[e]:e),r=v.merge(this.get(),n);return this.pushStack(ut(n[0])||ut(r[0])?r:v.unique(r))},addBack:function(e){return this.add(e==null?this.prevObject:this.prevObject.filter(e))}}),v.fn.andSelf=v.fn.addBack,v.each({parent:function(e){var t=e.parentNode;return t&&t.nodeType!==11?t:null},parents:function(e){return v.dir(e,"parentNode")},parentsUntil:function(e,t,n){return v.dir(e,"parentNode",n)},next:function(e){return at(e,"nextSibling")},prev:function(e){return at(e,"previousSibling")},nextAll:function(e){return v.dir(e,"nextSibling")},prevAll:function(e){return v.dir(e,"previousSibling")},nextUntil:function(e,t,n){return v.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return v.dir(e,"previousSibling",n)},siblings:function(e){return v.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return v.sibling(e.firstChild)},contents:function(e){return v.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:v.merge([],e.childNodes)}},function(e,t){v.fn[e]=function(n,r){var i=v.map(this,t,n);return nt.test(e)||(r=n),r&&typeof r=="string"&&(i=v.filter(r,i)),i=this.length>1&&!ot[e]?v.unique(i):i,this.length>1&&rt.test(e)&&(i=i.reverse()),this.pushStack(i,e,l.call(arguments).join(","))}}),v.extend({filter:function(e,t,n){return n&&(e=":not("+e+")"),t.length===1?v.find.matchesSelector(t[0],e)?[t[0]]:[]:v.find.matches(e,t)},dir:function(e,n,r){var i=[],s=e[n];while(s&&s.nodeType!==9&&(r===t||s.nodeType!==1||!v(s).is(r)))s.nodeType===1&&i.push(s),s=s[n];return i},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)e.nodeType===1&&e!==t&&n.push(e);return n}});var ct="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",ht=/ jQuery\d+="(?:null|\d+)"/g,pt=/^\s+/,dt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,vt=/<([\w:]+)/,mt=/]","i"),Et=/^(?:checkbox|radio)$/,St=/checked\s*(?:[^=]|=\s*.checked.)/i,xt=/\/(java|ecma)script/i,Tt=/^\s*\s*$/g,Nt={option:[1,""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"],area:[1,"",""],_default:[0,"",""]},Ct=lt(i),kt=Ct.appendChild(i.createElement("div"));Nt.optgroup=Nt.option,Nt.tbody=Nt.tfoot=Nt.colgroup=Nt.caption=Nt.thead,Nt.th=Nt.td,v.support.htmlSerialize||(Nt._default=[1,"X
","
"]),v.fn.extend({text:function(e){return v.access(this,function(e){return e===t?v.text(this):this.empty().append((this[0]&&this[0].ownerDocument||i).createTextNode(e))},null,e,arguments.length)},wrapAll:function(e){if(v.isFunction(e))return this.each(function(t){v(this).wrapAll(e.call(this,t))});if(this[0]){var t=v(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstChild&&e.firstChild.nodeType===1)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return v.isFunction(e)?this.each(function(t){v(this).wrapInner(e.call(this,t))}):this.each(function(){var t=v(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=v.isFunction(e);return this.each(function(n){v(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){v.nodeName(this,"body")||v(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(e){(this.nodeType===1||this.nodeType===11)&&this.appendChild(e)})},prepend:function(){return this.domManip(arguments,!0,function(e){(this.nodeType===1||this.nodeType===11)&&this.insertBefore(e,this.firstChild)})},before:function(){if(!ut(this[0]))return this.domManip(arguments,!1,function(e){this.parentNode.insertBefore(e,this)});if(arguments.length){var e=v.clean(arguments);return this.pushStack(v.merge(e,this),"before",this.selector)}},after:function(){if(!ut(this[0]))return this.domManip(arguments,!1,function(e){this.parentNode.insertBefore(e,this.nextSibling)});if(arguments.length){var e=v.clean(arguments);return this.pushStack(v.merge(this,e),"after",this.selector)}},remove:function(e,t){var n,r=0;for(;(n=this[r])!=null;r++)if(!e||v.filter(e,[n]).length)!t&&n.nodeType===1&&(v.cleanData(n.getElementsByTagName("*")),v.cleanData([n])),n.parentNode&&n.parentNode.removeChild(n);return this},empty:function(){var e,t=0;for(;(e=this[t])!=null;t++){e.nodeType===1&&v.cleanData(e.getElementsByTagName("*"));while(e.firstChild)e.removeChild(e.firstChild)}return this},clone:function(e,t){return e=e==null?!1:e,t=t==null?e:t,this.map(function(){return v.clone(this,e,t)})},html:function(e){return v.access(this,function(e){var n=this[0]||{},r=0,i=this.length;if(e===t)return n.nodeType===1?n.innerHTML.replace(ht,""):t;if(typeof e=="string"&&!yt.test(e)&&(v.support.htmlSerialize||!wt.test(e))&&(v.support.leadingWhitespace||!pt.test(e))&&!Nt[(vt.exec(e)||["",""])[1].toLowerCase()]){e=e.replace(dt,"<$1>");try{for(;r1&&typeof f=="string"&&St.test(f))return this.each(function(){v(this).domManip(e,n,r)});if(v.isFunction(f))return this.each(function(i){var s=v(this);e[0]=f.call(this,i,n?s.html():t),s.domManip(e,n,r)});if(this[0]){i=v.buildFragment(e,this,l),o=i.fragment,s=o.firstChild,o.childNodes.length===1&&(o=s);if(s){n=n&&v.nodeName(s,"tr");for(u=i.cacheable||c-1;a0?this.clone(!0):this).get(),v(o[i])[t](r),s=s.concat(r);return this.pushStack(s,e,o.selector)}}),v.extend({clone:function(e,t,n){var r,i,s,o;v.support.html5Clone||v.isXMLDoc(e)||!wt.test("<"+e.nodeName+">")?o=e.cloneNode(!0):(kt.innerHTML=e.outerHTML,kt.removeChild(o=kt.firstChild));if((!v.support.noCloneEvent||!v.support.noCloneChecked)&&(e.nodeType===1||e.nodeType===11)&&!v.isXMLDoc(e)){Ot(e,o),r=Mt(e),i=Mt(o);for(s=0;r[s];++s)i[s]&&Ot(r[s],i[s])}if(t){At(e,o);if(n){r=Mt(e),i=Mt(o);for(s=0;r[s];++s)At(r[s],i[s])}}return r=i=null,o},clean:function(e,t,n,r){var s,o,u,a,f,l,c,h,p,d,m,g,y=t===i&&Ct,b=[];if(!t||typeof t.createDocumentFragment=="undefined")t=i;for(s=0;(u=e[s])!=null;s++){typeof u=="number"&&(u+="");if(!u)continue;if(typeof u=="string")if(!gt.test(u))u=t.createTextNode(u);else{y=y||lt(t),c=t.createElement("div"),y.appendChild(c),u=u.replace(dt,"<$1>"),a=(vt.exec(u)||["",""])[1].toLowerCase(),f=Nt[a]||Nt._default,l=f[0],c.innerHTML=f[1]+u+f[2];while(l--)c=c.lastChild;if(!v.support.tbody){h=mt.test(u),p=a==="table"&&!h?c.firstChild&&c.firstChild.childNodes:f[1]===""&&!h?c.childNodes:[];for(o=p.length-1;o>=0;--o)v.nodeName(p[o],"tbody")&&!p[o].childNodes.length&&p[o].parentNode.removeChild(p[o])}!v.support.leadingWhitespace&&pt.test(u)&&c.insertBefore(t.createTextNode(pt.exec(u)[0]),c.firstChild),u=c.childNodes,c.parentNode.removeChild(c)}u.nodeType?b.push(u):v.merge(b,u)}c&&(u=c=y=null);if(!v.support.appendChecked)for(s=0;(u=b[s])!=null;s++)v.nodeName(u,"input")?_t(u):typeof u.getElementsByTagName!="undefined"&&v.grep(u.getElementsByTagName("input"),_t);if(n){m=function(e){if(!e.type||xt.test(e.type))return r?r.push(e.parentNode?e.parentNode.removeChild(e):e):n.appendChild(e)};for(s=0;(u=b[s])!=null;s++)if(!v.nodeName(u,"script")||!m(u))n.appendChild(u),typeof u.getElementsByTagName!="undefined"&&(g=v.grep(v.merge([],u.getElementsByTagName("script")),m),b.splice.apply(b,[s+1,0].concat(g)),s+=g.length)}return b},cleanData:function(e,t){var n,r,i,s,o=0,u=v.expando,a=v.cache,f=v.support.deleteExpando,l=v.event.special;for(;(i=e[o])!=null;o++)if(t||v.acceptData(i)){r=i[u],n=r&&a[r];if(n){if(n.events)for(s in n.events)l[s]?v.event.remove(i,s):v.removeEvent(i,s,n.handle);a[r]&&(delete a[r],f?delete i[u]:i.removeAttribute?i.removeAttribute(u):i[u]=null,v.deletedIds.push(r))}}}}),function(){var e,t;v.uaMatch=function(e){e=e.toLowerCase();var t=/(chrome)[ \/]([\w.]+)/.exec(e)||/(webkit)[ \/]([\w.]+)/.exec(e)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(e)||/(msie) ([\w.]+)/.exec(e)||e.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(e)||[];return{browser:t[1]||"",version:t[2]||"0"}},e=v.uaMatch(o.userAgent),t={},e.browser&&(t[e.browser]=!0,t.version=e.version),t.chrome?t.webkit=!0:t.webkit&&(t.safari=!0),v.browser=t,v.sub=function(){function e(t,n){return new e.fn.init(t,n)}v.extend(!0,e,this),e.superclass=this,e.fn=e.prototype=this(),e.fn.constructor=e,e.sub=this.sub,e.fn.init=function(r,i){return i&&i instanceof v&&!(i instanceof e)&&(i=e(i)),v.fn.init.call(this,r,i,t)},e.fn.init.prototype=e.fn;var t=e(i);return e}}();var Dt,Pt,Ht,Bt=/alpha\([^)]*\)/i,jt=/opacity=([^)]*)/,Ft=/^(top|right|bottom|left)$/,It=/^(none|table(?!-c[ea]).+)/,qt=/^margin/,Rt=new RegExp("^("+m+")(.*)$","i"),Ut=new RegExp("^("+m+")(?!px)[a-z%]+$","i"),zt=new RegExp("^([-+])=("+m+")","i"),Wt={BODY:"block"},Xt={position:"absolute",visibility:"hidden",display:"block"},Vt={letterSpacing:0,fontWeight:400},$t=["Top","Right","Bottom","Left"],Jt=["Webkit","O","Moz","ms"],Kt=v.fn.toggle;v.fn.extend({css:function(e,n){return v.access(this,function(e,n,r){return r!==t?v.style(e,n,r):v.css(e,n)},e,n,arguments.length>1)},show:function(){return Yt(this,!0)},hide:function(){return Yt(this)},toggle:function(e,t){var n=typeof e=="boolean";return v.isFunction(e)&&v.isFunction(t)?Kt.apply(this,arguments):this.each(function(){(n?e:Gt(this))?v(this).show():v(this).hide()})}}),v.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Dt(e,"opacity");return n===""?"1":n}}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":v.support.cssFloat?"cssFloat":"styleFloat"},style:function(e,n,r,i){if(!e||e.nodeType===3||e.nodeType===8||!e.style)return;var s,o,u,a=v.camelCase(n),f=e.style;n=v.cssProps[a]||(v.cssProps[a]=Qt(f,a)),u=v.cssHooks[n]||v.cssHooks[a];if(r===t)return u&&"get"in u&&(s=u.get(e,!1,i))!==t?s:f[n];o=typeof r,o==="string"&&(s=zt.exec(r))&&(r=(s[1]+1)*s[2]+parseFloat(v.css(e,n)),o="number");if(r==null||o==="number"&&isNaN(r))return;o==="number"&&!v.cssNumber[a]&&(r+="px");if(!u||!("set"in u)||(r=u.set(e,r,i))!==t)try{f[n]=r}catch(l){}},css:function(e,n,r,i){var s,o,u,a=v.camelCase(n);return n=v.cssProps[a]||(v.cssProps[a]=Qt(e.style,a)),u=v.cssHooks[n]||v.cssHooks[a],u&&"get"in u&&(s=u.get(e,!0,i)),s===t&&(s=Dt(e,n)),s==="normal"&&n in Vt&&(s=Vt[n]),r||i!==t?(o=parseFloat(s),r||v.isNumeric(o)?o||0:s):s},swap:function(e,t,n){var r,i,s={};for(i in t)s[i]=e.style[i],e.style[i]=t[i];r=n.call(e);for(i in t)e.style[i]=s[i];return r}}),e.getComputedStyle?Dt=function(t,n){var r,i,s,o,u=e.getComputedStyle(t,null),a=t.style;return u&&(r=u.getPropertyValue(n)||u[n],r===""&&!v.contains(t.ownerDocument,t)&&(r=v.style(t,n)),Ut.test(r)&&qt.test(n)&&(i=a.width,s=a.minWidth,o=a.maxWidth,a.minWidth=a.maxWidth=a.width=r,r=u.width,a.width=i,a.minWidth=s,a.maxWidth=o)),r}:i.documentElement.currentStyle&&(Dt=function(e,t){var n,r,i=e.currentStyle&&e.currentStyle[t],s=e.style;return i==null&&s&&s[t]&&(i=s[t]),Ut.test(i)&&!Ft.test(t)&&(n=s.left,r=e.runtimeStyle&&e.runtimeStyle.left,r&&(e.runtimeStyle.left=e.currentStyle.left),s.left=t==="fontSize"?"1em":i,i=s.pixelLeft+"px",s.left=n,r&&(e.runtimeStyle.left=r)),i===""?"auto":i}),v.each(["height","width"],function(e,t){v.cssHooks[t]={get:function(e,n,r){if(n)return e.offsetWidth===0&&It.test(Dt(e,"display"))?v.swap(e,Xt,function(){return tn(e,t,r)}):tn(e,t,r)},set:function(e,n,r){return Zt(e,n,r?en(e,t,r,v.support.boxSizing&&v.css(e,"boxSizing")==="border-box"):0)}}}),v.support.opacity||(v.cssHooks.opacity={get:function(e,t){return jt.test((t&&e.currentStyle?e.currentStyle.filter:e.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":t?"1":""},set:function(e,t){var n=e.style,r=e.currentStyle,i=v.isNumeric(t)?"alpha(opacity="+t*100+")":"",s=r&&r.filter||n.filter||"";n.zoom=1;if(t>=1&&v.trim(s.replace(Bt,""))===""&&n.removeAttribute){n.removeAttribute("filter");if(r&&!r.filter)return}n.filter=Bt.test(s)?s.replace(Bt,i):s+" "+i}}),v(function(){v.support.reliableMarginRight||(v.cssHooks.marginRight={get:function(e,t){return v.swap(e,{display:"inline-block"},function(){if(t)return Dt(e,"marginRight")})}}),!v.support.pixelPosition&&v.fn.position&&v.each(["top","left"],function(e,t){v.cssHooks[t]={get:function(e,n){if(n){var r=Dt(e,t);return Ut.test(r)?v(e).position()[t]+"px":r}}}})}),v.expr&&v.expr.filters&&(v.expr.filters.hidden=function(e){return e.offsetWidth===0&&e.offsetHeight===0||!v.support.reliableHiddenOffsets&&(e.style&&e.style.display||Dt(e,"display"))==="none"},v.expr.filters.visible=function(e){return!v.expr.filters.hidden(e)}),v.each({margin:"",padding:"",border:"Width"},function(e,t){v.cssHooks[e+t]={expand:function(n){var r,i=typeof n=="string"?n.split(" "):[n],s={};for(r=0;r<4;r++)s[e+$t[r]+t]=i[r]||i[r-2]||i[0];return s}},qt.test(e)||(v.cssHooks[e+t].set=Zt)});var rn=/%20/g,sn=/\[\]$/,on=/\r?\n/g,un=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,an=/^(?:select|textarea)/i;v.fn.extend({serialize:function(){return v.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?v.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||an.test(this.nodeName)||un.test(this.type))}).map(function(e,t){var n=v(this).val();return n==null?null:v.isArray(n)?v.map(n,function(e,n){return{name:t.name,value:e.replace(on,"\r\n")}}):{name:t.name,value:n.replace(on,"\r\n")}}).get()}}),v.param=function(e,n){var r,i=[],s=function(e,t){t=v.isFunction(t)?t():t==null?"":t,i[i.length]=encodeURIComponent(e)+"="+encodeURIComponent(t)};n===t&&(n=v.ajaxSettings&&v.ajaxSettings.traditional);if(v.isArray(e)||e.jquery&&!v.isPlainObject(e))v.each(e,function(){s(this.name,this.value)});else for(r in e)fn(r,e[r],n,s);return i.join("&").replace(rn,"+")};var ln,cn,hn=/#.*$/,pn=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,dn=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,vn=/^(?:GET|HEAD)$/,mn=/^\/\//,gn=/\?/,yn=/)<[^<]*)*<\/script>/gi,bn=/([?&])_=[^&]*/,wn=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,En=v.fn.load,Sn={},xn={},Tn=["*/"]+["*"];try{cn=s.href}catch(Nn){cn=i.createElement("a"),cn.href="",cn=cn.href}ln=wn.exec(cn.toLowerCase())||[],v.fn.load=function(e,n,r){if(typeof e!="string"&&En)return En.apply(this,arguments);if(!this.length)return this;var i,s,o,u=this,a=e.indexOf(" ");return a>=0&&(i=e.slice(a,e.length),e=e.slice(0,a)),v.isFunction(n)?(r=n,n=t):n&&typeof n=="object"&&(s="POST"),v.ajax({url:e,type:s,dataType:"html",data:n,complete:function(e,t){r&&u.each(r,o||[e.responseText,t,e])}}).done(function(e){o=arguments,u.html(i?v("
").append(e.replace(yn,"")).find(i):e)}),this},v.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(e,t){v.fn[t]=function(e){return this.on(t,e)}}),v.each(["get","post"],function(e,n){v[n]=function(e,r,i,s){return v.isFunction(r)&&(s=s||i,i=r,r=t),v.ajax({type:n,url:e,data:r,success:i,dataType:s})}}),v.extend({getScript:function(e,n){return v.get(e,t,n,"script")},getJSON:function(e,t,n){return v.get(e,t,n,"json")},ajaxSetup:function(e,t){return t?Ln(e,v.ajaxSettings):(t=e,e=v.ajaxSettings),Ln(e,t),e},ajaxSettings:{url:cn,isLocal:dn.test(ln[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":Tn},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":e.String,"text html":!0,"text json":v.parseJSON,"text xml":v.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:Cn(Sn),ajaxTransport:Cn(xn),ajax:function(e,n){function T(e,n,s,a){var l,y,b,w,S,T=n;if(E===2)return;E=2,u&&clearTimeout(u),o=t,i=a||"",x.readyState=e>0?4:0,s&&(w=An(c,x,s));if(e>=200&&e<300||e===304)c.ifModified&&(S=x.getResponseHeader("Last-Modified"),S&&(v.lastModified[r]=S),S=x.getResponseHeader("Etag"),S&&(v.etag[r]=S)),e===304?(T="notmodified",l=!0):(l=On(c,w),T=l.state,y=l.data,b=l.error,l=!b);else{b=T;if(!T||e)T="error",e<0&&(e=0)}x.status=e,x.statusText=(n||T)+"",l?d.resolveWith(h,[y,T,x]):d.rejectWith(h,[x,T,b]),x.statusCode(g),g=t,f&&p.trigger("ajax"+(l?"Success":"Error"),[x,c,l?y:b]),m.fireWith(h,[x,T]),f&&(p.trigger("ajaxComplete",[x,c]),--v.active||v.event.trigger("ajaxStop"))}typeof e=="object"&&(n=e,e=t),n=n||{};var r,i,s,o,u,a,f,l,c=v.ajaxSetup({},n),h=c.context||c,p=h!==c&&(h.nodeType||h instanceof v)?v(h):v.event,d=v.Deferred(),m=v.Callbacks("once memory"),g=c.statusCode||{},b={},w={},E=0,S="canceled",x={readyState:0,setRequestHeader:function(e,t){if(!E){var n=e.toLowerCase();e=w[n]=w[n]||e,b[e]=t}return this},getAllResponseHeaders:function(){return E===2?i:null},getResponseHeader:function(e){var n;if(E===2){if(!s){s={};while(n=pn.exec(i))s[n[1].toLowerCase()]=n[2]}n=s[e.toLowerCase()]}return n===t?null:n},overrideMimeType:function(e){return E||(c.mimeType=e),this},abort:function(e){return e=e||S,o&&o.abort(e),T(0,e),this}};d.promise(x),x.success=x.done,x.error=x.fail,x.complete=m.add,x.statusCode=function(e){if(e){var t;if(E<2)for(t in e)g[t]=[g[t],e[t]];else t=e[x.status],x.always(t)}return this},c.url=((e||c.url)+"").replace(hn,"").replace(mn,ln[1]+"//"),c.dataTypes=v.trim(c.dataType||"*").toLowerCase().split(y),c.crossDomain==null&&(a=wn.exec(c.url.toLowerCase()),c.crossDomain=!(!a||a[1]===ln[1]&&a[2]===ln[2]&&(a[3]||(a[1]==="http:"?80:443))==(ln[3]||(ln[1]==="http:"?80:443)))),c.data&&c.processData&&typeof c.data!="string"&&(c.data=v.param(c.data,c.traditional)),kn(Sn,c,n,x);if(E===2)return x;f=c.global,c.type=c.type.toUpperCase(),c.hasContent=!vn.test(c.type),f&&v.active++===0&&v.event.trigger("ajaxStart");if(!c.hasContent){c.data&&(c.url+=(gn.test(c.url)?"&":"?")+c.data,delete c.data),r=c.url;if(c.cache===!1){var N=v.now(),C=c.url.replace(bn,"$1_="+N);c.url=C+(C===c.url?(gn.test(c.url)?"&":"?")+"_="+N:"")}}(c.data&&c.hasContent&&c.contentType!==!1||n.contentType)&&x.setRequestHeader("Content-Type",c.contentType),c.ifModified&&(r=r||c.url,v.lastModified[r]&&x.setRequestHeader("If-Modified-Since",v.lastModified[r]),v.etag[r]&&x.setRequestHeader("If-None-Match",v.etag[r])),x.setRequestHeader("Accept",c.dataTypes[0]&&c.accepts[c.dataTypes[0]]?c.accepts[c.dataTypes[0]]+(c.dataTypes[0]!=="*"?", "+Tn+"; q=0.01":""):c.accepts["*"]);for(l in c.headers)x.setRequestHeader(l,c.headers[l]);if(!c.beforeSend||c.beforeSend.call(h,x,c)!==!1&&E!==2){S="abort";for(l in{success:1,error:1,complete:1})x[l](c[l]);o=kn(xn,c,n,x);if(!o)T(-1,"No Transport");else{x.readyState=1,f&&p.trigger("ajaxSend",[x,c]),c.async&&c.timeout>0&&(u=setTimeout(function(){x.abort("timeout")},c.timeout));try{E=1,o.send(b,T)}catch(k){if(!(E<2))throw k;T(-1,k)}}return x}return x.abort()},active:0,lastModified:{},etag:{}});var Mn=[],_n=/\?/,Dn=/(=)\?(?=&|$)|\?\?/,Pn=v.now();v.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Mn.pop()||v.expando+"_"+Pn++;return this[e]=!0,e}}),v.ajaxPrefilter("json jsonp",function(n,r,i){var s,o,u,a=n.data,f=n.url,l=n.jsonp!==!1,c=l&&Dn.test(f),h=l&&!c&&typeof a=="string"&&!(n.contentType||"").indexOf("application/x-www-form-urlencoded")&&Dn.test(a);if(n.dataTypes[0]==="jsonp"||c||h)return s=n.jsonpCallback=v.isFunction(n.jsonpCallback)?n.jsonpCallback():n.jsonpCallback,o=e[s],c?n.url=f.replace(Dn,"$1"+s):h?n.data=a.replace(Dn,"$1"+s):l&&(n.url+=(_n.test(f)?"&":"?")+n.jsonp+"="+s),n.converters["script json"]=function(){return u||v.error(s+" was not called"),u[0]},n.dataTypes[0]="json",e[s]=function(){u=arguments},i.always(function(){e[s]=o,n[s]&&(n.jsonpCallback=r.jsonpCallback,Mn.push(s)),u&&v.isFunction(o)&&o(u[0]),u=o=t}),"script"}),v.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(e){return v.globalEval(e),e}}}),v.ajaxPrefilter("script",function(e){e.cache===t&&(e.cache=!1),e.crossDomain&&(e.type="GET",e.global=!1)}),v.ajaxTransport("script",function(e){if(e.crossDomain){var n,r=i.head||i.getElementsByTagName("head")[0]||i.documentElement;return{send:function(s,o){n=i.createElement("script"),n.async="async",e.scriptCharset&&(n.charset=e.scriptCharset),n.src=e.url,n.onload=n.onreadystatechange=function(e,i){if(i||!n.readyState||/loaded|complete/.test(n.readyState))n.onload=n.onreadystatechange=null,r&&n.parentNode&&r.removeChild(n),n=t,i||o(200,"success")},r.insertBefore(n,r.firstChild)},abort:function(){n&&n.onload(0,1)}}}});var Hn,Bn=e.ActiveXObject?function(){for(var e in Hn)Hn[e](0,1)}:!1,jn=0;v.ajaxSettings.xhr=e.ActiveXObject?function(){return!this.isLocal&&Fn()||In()}:Fn,function(e){v.extend(v.support,{ajax:!!e,cors:!!e&&"withCredentials"in e})}(v.ajaxSettings.xhr()),v.support.ajax&&v.ajaxTransport(function(n){if(!n.crossDomain||v.support.cors){var r;return{send:function(i,s){var o,u,a=n.xhr();n.username?a.open(n.type,n.url,n.async,n.username,n.password):a.open(n.type,n.url,n.async);if(n.xhrFields)for(u in n.xhrFields)a[u]=n.xhrFields[u];n.mimeType&&a.overrideMimeType&&a.overrideMimeType(n.mimeType),!n.crossDomain&&!i["X-Requested-With"]&&(i["X-Requested-With"]="XMLHttpRequest");try{for(u in i)a.setRequestHeader(u,i[u])}catch(f){}a.send(n.hasContent&&n.data||null),r=function(e,i){var u,f,l,c,h;try{if(r&&(i||a.readyState===4)){r=t,o&&(a.onreadystatechange=v.noop,Bn&&delete Hn[o]);if(i)a.readyState!==4&&a.abort();else{u=a.status,l=a.getAllResponseHeaders(),c={},h=a.responseXML,h&&h.documentElement&&(c.xml=h);try{c.text=a.responseText}catch(p){}try{f=a.statusText}catch(p){f=""}!u&&n.isLocal&&!n.crossDomain?u=c.text?200:404:u===1223&&(u=204)}}}catch(d){i||s(-1,d)}c&&s(u,f,c,l)},n.async?a.readyState===4?setTimeout(r,0):(o=++jn,Bn&&(Hn||(Hn={},v(e).unload(Bn)),Hn[o]=r),a.onreadystatechange=r):r()},abort:function(){r&&r(0,1)}}}});var qn,Rn,Un=/^(?:toggle|show|hide)$/,zn=new RegExp("^(?:([-+])=|)("+m+")([a-z%]*)$","i"),Wn=/queueHooks$/,Xn=[Gn],Vn={"*":[function(e,t){var n,r,i=this.createTween(e,t),s=zn.exec(t),o=i.cur(),u=+o||0,a=1,f=20;if(s){n=+s[2],r=s[3]||(v.cssNumber[e]?"":"px");if(r!=="px"&&u){u=v.css(i.elem,e,!0)||n||1;do a=a||".5",u/=a,v.style(i.elem,e,u+r);while(a!==(a=i.cur()/o)&&a!==1&&--f)}i.unit=r,i.start=u,i.end=s[1]?u+(s[1]+1)*n:n}return i}]};v.Animation=v.extend(Kn,{tweener:function(e,t){v.isFunction(e)?(t=e,e=["*"]):e=e.split(" ");var n,r=0,i=e.length;for(;r-1,f={},l={},c,h;a?(l=i.position(),c=l.top,h=l.left):(c=parseFloat(o)||0,h=parseFloat(u)||0),v.isFunction(t)&&(t=t.call(e,n,s)),t.top!=null&&(f.top=t.top-s.top+c),t.left!=null&&(f.left=t.left-s.left+h),"using"in t?t.using.call(e,f):i.css(f)}},v.fn.extend({position:function(){if(!this[0])return;var e=this[0],t=this.offsetParent(),n=this.offset(),r=er.test(t[0].nodeName)?{top:0,left:0}:t.offset();return n.top-=parseFloat(v.css(e,"marginTop"))||0,n.left-=parseFloat(v.css(e,"marginLeft"))||0,r.top+=parseFloat(v.css(t[0],"borderTopWidth"))||0,r.left+=parseFloat(v.css(t[0],"borderLeftWidth"))||0,{top:n.top-r.top,left:n.left-r.left}},offsetParent:function(){return this.map(function(){var e=this.offsetParent||i.body;while(e&&!er.test(e.nodeName)&&v.css(e,"position")==="static")e=e.offsetParent;return e||i.body})}}),v.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(e,n){var r=/Y/.test(n);v.fn[e]=function(i){return v.access(this,function(e,i,s){var o=tr(e);if(s===t)return o?n in o?o[n]:o.document.documentElement[i]:e[i];o?o.scrollTo(r?v(o).scrollLeft():s,r?s:v(o).scrollTop()):e[i]=s},e,i,arguments.length,null)}}),v.each({Height:"height",Width:"width"},function(e,n){v.each({padding:"inner"+e,content:n,"":"outer"+e},function(r,i){v.fn[i]=function(i,s){var o=arguments.length&&(r||typeof i!="boolean"),u=r||(i===!0||s===!0?"margin":"border");return v.access(this,function(n,r,i){var s;return v.isWindow(n)?n.document.documentElement["client"+e]:n.nodeType===9?(s=n.documentElement,Math.max(n.body["scroll"+e],s["scroll"+e],n.body["offset"+e],s["offset"+e],s["client"+e])):i===t?v.css(n,r,i,u):v.style(n,r,i,u)},n,o?i:t,o,null)}})}),e.jQuery=e.$=v,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return v})})(window); \ No newline at end of file diff --git a/UPW_Browser/main/reconnecting-websocket.min.js b/UPW_Browser/main/reconnecting-websocket.min.js new file mode 100644 index 0000000..3015099 --- /dev/null +++ b/UPW_Browser/main/reconnecting-websocket.min.js @@ -0,0 +1 @@ +!function(a,b){"function"==typeof define&&define.amd?define([],b):"undefined"!=typeof module&&module.exports?module.exports=b():a.ReconnectingWebSocket=b()}(this,function(){function a(b,c,d){function l(a,b){var c=document.createEvent("CustomEvent");return c.initCustomEvent(a,!1,!1,b),c}var e={debug:!1,automaticOpen:!0,reconnectInterval:1e3,maxReconnectInterval:3e4,reconnectDecay:1.5,timeoutInterval:2e3};d||(d={});for(var f in e)this[f]="undefined"!=typeof d[f]?d[f]:e[f];this.url=b,this.reconnectAttempts=0,this.readyState=WebSocket.CONNECTING,this.protocol=null;var h,g=this,i=!1,j=!1,k=document.createElement("div");k.addEventListener("open",function(a){g.onopen(a)}),k.addEventListener("close",function(a){g.onclose(a)}),k.addEventListener("connecting",function(a){g.onconnecting(a)}),k.addEventListener("message",function(a){g.onmessage(a)}),k.addEventListener("error",function(a){g.onerror(a)}),this.addEventListener=k.addEventListener.bind(k),this.removeEventListener=k.removeEventListener.bind(k),this.dispatchEvent=k.dispatchEvent.bind(k),this.open=function(b){h=new WebSocket(g.url,c||[]),b||k.dispatchEvent(l("connecting")),(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","attempt-connect",g.url);var d=h,e=setTimeout(function(){(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","connection-timeout",g.url),j=!0,d.close(),j=!1},g.timeoutInterval);h.onopen=function(){clearTimeout(e),(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onopen",g.url),g.protocol=h.protocol,g.readyState=WebSocket.OPEN,g.reconnectAttempts=0;var d=l("open");d.isReconnect=b,b=!1,k.dispatchEvent(d)},h.onclose=function(c){if(clearTimeout(e),h=null,i)g.readyState=WebSocket.CLOSED,k.dispatchEvent(l("close"));else{g.readyState=WebSocket.CONNECTING;var d=l("connecting");d.code=c.code,d.reason=c.reason,d.wasClean=c.wasClean,k.dispatchEvent(d),b||j||((g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onclose",g.url),k.dispatchEvent(l("close")));var e=g.reconnectInterval*Math.pow(g.reconnectDecay,g.reconnectAttempts);setTimeout(function(){g.reconnectAttempts++,g.open(!0)},e>g.maxReconnectInterval?g.maxReconnectInterval:e)}},h.onmessage=function(b){(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onmessage",g.url,b.data);var c=l("message");c.data=b.data,k.dispatchEvent(c)},h.onerror=function(b){(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onerror",g.url,b),k.dispatchEvent(l("error"))}},1==this.automaticOpen&&this.open(!1),this.send=function(b){if(h)return(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","send",g.url,b),h.send(b);throw"INVALID_STATE_ERR : Pausing to reconnect websocket"},this.close=function(a,b){"undefined"==typeof a&&(a=1e3),i=!0,h&&h.close(a,b)},this.refresh=function(){h&&h.close()}}return a.prototype.onopen=function(){},a.prototype.onclose=function(){},a.prototype.onconnecting=function(){},a.prototype.onmessage=function(){},a.prototype.onerror=function(){},a.debugAll=!1,a.CONNECTING=WebSocket.CONNECTING,a.OPEN=WebSocket.OPEN,a.CLOSING=WebSocket.CLOSING,a.CLOSED=WebSocket.CLOSED,a}); diff --git a/UdpExample/UdpExample.sln b/UdpExample/UdpExample.sln new file mode 100644 index 0000000..cca4c65 --- /dev/null +++ b/UdpExample/UdpExample.sln @@ -0,0 +1,54 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "server", "server\server.csproj", "{BEC0344F-9AE7-4792-8A5D-C60A39D98732}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "client", "client\client.csproj", "{6593C9FB-053E-4292-B267-00FFD99C0278}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "udp", "udp\udp.csproj", "{F5BDA737-1EBE-4864-877C-36C5F3295F1E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|Mixed Platforms = Debug|Mixed Platforms + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|Mixed Platforms = Release|Mixed Platforms + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {BEC0344F-9AE7-4792-8A5D-C60A39D98732}.Debug|Any CPU.ActiveCfg = Debug|x86 + {BEC0344F-9AE7-4792-8A5D-C60A39D98732}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 + {BEC0344F-9AE7-4792-8A5D-C60A39D98732}.Debug|Mixed Platforms.Build.0 = Debug|x86 + {BEC0344F-9AE7-4792-8A5D-C60A39D98732}.Debug|x86.ActiveCfg = Debug|x86 + {BEC0344F-9AE7-4792-8A5D-C60A39D98732}.Debug|x86.Build.0 = Debug|x86 + {BEC0344F-9AE7-4792-8A5D-C60A39D98732}.Release|Any CPU.ActiveCfg = Release|x86 + {BEC0344F-9AE7-4792-8A5D-C60A39D98732}.Release|Mixed Platforms.ActiveCfg = Release|x86 + {BEC0344F-9AE7-4792-8A5D-C60A39D98732}.Release|Mixed Platforms.Build.0 = Release|x86 + {BEC0344F-9AE7-4792-8A5D-C60A39D98732}.Release|x86.ActiveCfg = Release|x86 + {BEC0344F-9AE7-4792-8A5D-C60A39D98732}.Release|x86.Build.0 = Release|x86 + {6593C9FB-053E-4292-B267-00FFD99C0278}.Debug|Any CPU.ActiveCfg = Debug|x86 + {6593C9FB-053E-4292-B267-00FFD99C0278}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 + {6593C9FB-053E-4292-B267-00FFD99C0278}.Debug|Mixed Platforms.Build.0 = Debug|x86 + {6593C9FB-053E-4292-B267-00FFD99C0278}.Debug|x86.ActiveCfg = Debug|x86 + {6593C9FB-053E-4292-B267-00FFD99C0278}.Debug|x86.Build.0 = Debug|x86 + {6593C9FB-053E-4292-B267-00FFD99C0278}.Release|Any CPU.ActiveCfg = Release|x86 + {6593C9FB-053E-4292-B267-00FFD99C0278}.Release|Mixed Platforms.ActiveCfg = Release|x86 + {6593C9FB-053E-4292-B267-00FFD99C0278}.Release|Mixed Platforms.Build.0 = Release|x86 + {6593C9FB-053E-4292-B267-00FFD99C0278}.Release|x86.ActiveCfg = Release|x86 + {6593C9FB-053E-4292-B267-00FFD99C0278}.Release|x86.Build.0 = Release|x86 + {F5BDA737-1EBE-4864-877C-36C5F3295F1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F5BDA737-1EBE-4864-877C-36C5F3295F1E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F5BDA737-1EBE-4864-877C-36C5F3295F1E}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {F5BDA737-1EBE-4864-877C-36C5F3295F1E}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {F5BDA737-1EBE-4864-877C-36C5F3295F1E}.Debug|x86.ActiveCfg = Debug|Any CPU + {F5BDA737-1EBE-4864-877C-36C5F3295F1E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F5BDA737-1EBE-4864-877C-36C5F3295F1E}.Release|Any CPU.Build.0 = Release|Any CPU + {F5BDA737-1EBE-4864-877C-36C5F3295F1E}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {F5BDA737-1EBE-4864-877C-36C5F3295F1E}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {F5BDA737-1EBE-4864-877C-36C5F3295F1E}.Release|x86.ActiveCfg = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/UdpExample/client/Program.cs b/UdpExample/client/Program.cs new file mode 100644 index 0000000..663a990 --- /dev/null +++ b/UdpExample/client/Program.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Net.Sockets; +using System.Net; +using udp; +namespace client +{ + class Program + { + private const string _serverIp = "127.0.0.1"; + private const int _serverPort = 7101; + + static void Main(string[] args) + { + Client client = new Client(); + client.ep = new IPEndPoint(IPAddress.Parse(_serverIp), _serverPort); + client.Listening(); + client.Received += new UdpEventHandler(client_Received); + while (true) + { + string tmp = Console.ReadLine(); + + byte[] bt = Encoding.Default.GetBytes(tmp); + System.Threading.Thread t = new System.Threading.Thread(() => + { + client.Send(bt, client.ep); + }); + t.Start(); + + } + } + + static void client_Received(object sender, UdpEventArgs e) + { + IPEndPoint ep = e.Remote as IPEndPoint; + string tmpReceived = Encoding.Default.GetString(e.Received); + Console.WriteLine(ep.Address.ToString() + ":" + ep.Port + "--> " + tmpReceived); + } + } + + public class Client : Udp + { + public EndPoint ep; + public Client() + { + + } + } +} diff --git a/UdpExample/client/Properties/AssemblyInfo.cs b/UdpExample/client/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..24e5128 --- /dev/null +++ b/UdpExample/client/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// 有关程序集的常规信息通过以下 +// 特性集控制。更改这些特性值可修改 +// 与程序集关联的信息。 +[assembly: AssemblyTitle("client")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("client")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// 将 ComVisible 设置为 false 使此程序集中的类型 +// 对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型, +// 则将该类型上的 ComVisible 特性设置为 true。 +[assembly: ComVisible(false)] + +// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID +[assembly: Guid("9550478a-9201-4126-8828-a465a0fda94d")] + +// 程序集的版本信息由下面四个值组成: +// +// 主版本 +// 次版本 +// 内部版本号 +// 修订号 +// +// 可以指定所有这些值,也可以使用“内部版本号”和“修订号”的默认值, +// 方法是按如下所示使用“*”: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/UdpExample/client/client.csproj b/UdpExample/client/client.csproj new file mode 100644 index 0000000..058d397 --- /dev/null +++ b/UdpExample/client/client.csproj @@ -0,0 +1,60 @@ + + + + Debug + x86 + 8.0.30703 + 2.0 + {6593C9FB-053E-4292-B267-00FFD99C0278} + Exe + Properties + client + client + v4.0 + Client + 512 + + + x86 + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + x86 + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + ..\udp\bin\Debug\udp.dll + + + + + + + + + \ No newline at end of file diff --git a/UdpExample/server/Program.cs b/UdpExample/server/Program.cs new file mode 100644 index 0000000..4454ba4 --- /dev/null +++ b/UdpExample/server/Program.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using System.Net.Sockets; +using System.Net; +using udp; + +namespace server +{ + class Program + { + static Server server = new Server(); + static void Main(string[] args) + { + server.Port = 8000; + server.Listening(); + if (server.IsListening) + { + server.Received += new UdpEventHandler(server_Received); + } + Console.ReadKey(); + } + + static void server_Received(object sender, UdpEventArgs e) + { + IPEndPoint ep = e.Remote as IPEndPoint; + string tmpReceived = Encoding.Default.GetString(e.Received); + Console.WriteLine(ep.Address.ToString() + ":" + ep.Port + "--> " + tmpReceived); + ///自动回复 + server.Send(Encoding.Default.GetBytes("服务器已收到数据:'" + tmpReceived + "',来自:‘" + ep.Address.ToString() + ":" + ep.Port + "’"), ep); + } + + } + public class Server : Udp + { + private EndPoint ep; + public Server() + { + } + } +} diff --git a/UdpExample/server/Properties/AssemblyInfo.cs b/UdpExample/server/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..e24a270 --- /dev/null +++ b/UdpExample/server/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// 有关程序集的常规信息通过以下 +// 特性集控制。更改这些特性值可修改 +// 与程序集关联的信息。 +[assembly: AssemblyTitle("server")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("server")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// 将 ComVisible 设置为 false 使此程序集中的类型 +// 对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型, +// 则将该类型上的 ComVisible 特性设置为 true。 +[assembly: ComVisible(false)] + +// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID +[assembly: Guid("186ec332-5da3-4abe-8970-ad8cafb2c209")] + +// 程序集的版本信息由下面四个值组成: +// +// 主版本 +// 次版本 +// 内部版本号 +// 修订号 +// +// 可以指定所有这些值,也可以使用“内部版本号”和“修订号”的默认值, +// 方法是按如下所示使用“*”: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/UdpExample/server/server.csproj b/UdpExample/server/server.csproj new file mode 100644 index 0000000..ca9492c --- /dev/null +++ b/UdpExample/server/server.csproj @@ -0,0 +1,60 @@ + + + + Debug + x86 + 8.0.30703 + 2.0 + {BEC0344F-9AE7-4792-8A5D-C60A39D98732} + Exe + Properties + server + server + v4.0 + Client + 512 + + + x86 + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + x86 + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + ..\udp\bin\Debug\udp.dll + + + + + + + + + \ No newline at end of file diff --git a/UdpExample/udp/IUdp.cs b/UdpExample/udp/IUdp.cs new file mode 100644 index 0000000..9730463 --- /dev/null +++ b/UdpExample/udp/IUdp.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Net.Sockets; +using System.Net; + +namespace udp +{ + public interface IUdp : IDisposable + { + event UdpEventHandler Received; + + void Send(byte[] bt, EndPoint ep); + + Socket UdpSocket { get; } + } +} diff --git a/UdpExample/udp/Properties/AssemblyInfo.cs b/UdpExample/udp/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..3425d63 --- /dev/null +++ b/UdpExample/udp/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// 有关程序集的常规信息通过以下 +// 特性集控制。更改这些特性值可修改 +// 与程序集关联的信息。 +[assembly: AssemblyTitle("udp")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("udp")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// 将 ComVisible 设置为 false 使此程序集中的类型 +// 对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型, +// 则将该类型上的 ComVisible 特性设置为 true。 +[assembly: ComVisible(false)] + +// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID +[assembly: Guid("1202b3ab-a590-4663-bc20-74a8c1dfebc2")] + +// 程序集的版本信息由下面四个值组成: +// +// 主版本 +// 次版本 +// 内部版本号 +// 修订号 +// +// 可以指定所有这些值,也可以使用“内部版本号”和“修订号”的默认值, +// 方法是按如下所示使用“*”: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/UdpExample/udp/Udp.cs b/UdpExample/udp/Udp.cs new file mode 100644 index 0000000..8948517 --- /dev/null +++ b/UdpExample/udp/Udp.cs @@ -0,0 +1,146 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Net; +using System.Net.Sockets; + +namespace udp +{ + public delegate void UdpEventHandler(object sender, UdpEventArgs e); + + public abstract class Udp : IUdp + { + public event UdpEventHandler Received; + + private int _port; + private string _ip; + public bool IsListening { get; private set; } + private Socket _sck; + + public Socket UdpSocket + { + get { return _sck; } + } + public string Ip + { + get { return _ip; } + set { _ip = value; } + } + public int Port + { + get { return _port; } + set + { + if (value < 0) + value = 0; + if (value > 65536) + value = 65536; + _port = value; + } + } + + public Udp() + { + _sck = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + _sck.ReceiveBufferSize = UInt16.MaxValue * 8; + //log + System.Diagnostics.Trace.Listeners.Clear(); + System.Diagnostics.Trace.AutoFlush = true; + System.Diagnostics.Trace.Listeners.Add(new System.Diagnostics.TextWriterTraceListener("log.txt")); + } + + + public void Listening() + { + + IPAddress ip = IPAddress.Any; + try + { + if (this._ip != null) + if (!IPAddress.TryParse(this._ip, out ip)) + throw new ArgumentException("IP地址错误", "Ip"); + _sck.Bind(new IPEndPoint(ip, this._port)); + + UdpState state = new UdpState(); + state.Socket = _sck; + state.Remote = new IPEndPoint(IPAddress.Any, 0); + _sck.BeginReceiveFrom(state.Buffer, 0, state.Buffer.Length, SocketFlags.None, ref state.Remote, new AsyncCallback(EndReceiveFrom), state); + + IsListening = true; + } + catch (ArgumentException ex) + { + IsListening = false; + System.Diagnostics.Trace.WriteLine(DateTime.Now.ToString() + "\t" + ex.Message); + throw ex; + } + catch (Exception ex) + { + IsListening = false; + System.Diagnostics.Trace.WriteLine(DateTime.Now.ToString() + "\t" + ex.Message); + throw ex; + } + } + private void EndReceiveFrom(IAsyncResult ir) + { + if (IsListening) + { + UdpState state = ir.AsyncState as UdpState; + try + { + if (ir.IsCompleted) + { + int length = state.Socket.EndReceiveFrom(ir, ref state.Remote); + byte[] btReceived = new byte[length]; + Buffer.BlockCopy(state.Buffer, 0, btReceived, 0, length); + OnReceived(new UdpEventArgs(btReceived, state.Remote)); + } + } + catch (Exception ex) + { + System.Diagnostics.Trace.WriteLine(DateTime.Now.ToString() + "\t" + ex.Message+ex.Source); + } + finally + { + state.Socket.BeginReceiveFrom(state.Buffer, 0, state.Buffer.Length, SocketFlags.None, ref state.Remote, new AsyncCallback(EndReceiveFrom), state); + } + } + } + + private void OnReceived(UdpEventArgs e) + { + if (this.Received != null) + { + Received(this, e); + } + } + + public void Send(byte[] bt, EndPoint ep) + { + if (_sck == null) return; + try + { + this._sck.SendTo(bt, ep); + } + catch (SocketException ex) + { + System.Diagnostics.Trace.WriteLine(DateTime.Now.ToString() + "\t" + ex.Message); + throw ex; + } + } + + public void Dispose() + { + if (_sck == null) return; + + using (_sck) ; + //this.IsListening = false; + //this._sck.Blocking = false; + //this._sck.Shutdown(SocketShutdown.Both); + //this._sck.Close(); + //this._sck = null; + } + + } +} diff --git a/UdpExample/udp/UdpEventArgs.cs b/UdpExample/udp/UdpEventArgs.cs new file mode 100644 index 0000000..5fad801 --- /dev/null +++ b/UdpExample/udp/UdpEventArgs.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Net; + +namespace udp +{ + public class UdpEventArgs : EventArgs + { + private EndPoint _remote; + + private byte[] _rec; + + public byte[] Received + { + get { return _rec; } + } + public EndPoint Remote + { + get { return _remote; } + } + + public UdpEventArgs(byte[] data, EndPoint remote) + { + this._remote = remote; + this._rec = data; + } + } +} diff --git a/UdpExample/udp/UdpState.cs b/UdpExample/udp/UdpState.cs new file mode 100644 index 0000000..85efb07 --- /dev/null +++ b/UdpExample/udp/UdpState.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Net; +using System.Net.Sockets; + +namespace udp +{ + internal class UdpState + { + public byte[] Buffer; + public EndPoint Remote; + public Socket Socket; + + public UdpState() + { + Buffer = new byte[65536]; + } + } +} diff --git a/UdpExample/udp/udp.csproj b/UdpExample/udp/udp.csproj new file mode 100644 index 0000000..bc79335 --- /dev/null +++ b/UdpExample/udp/udp.csproj @@ -0,0 +1,58 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {F5BDA737-1EBE-4864-877C-36C5F3295F1E} + Library + Properties + udp + udp + v4.0 + 512 + Client + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/UdpPlugWebsocket.sln b/UdpPlugWebsocket.sln new file mode 100644 index 0000000..0bc27f3 --- /dev/null +++ b/UdpPlugWebsocket.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UdpPlugWebsocket", "UdpPlugWebsocket\UdpPlugWebsocket.csproj", "{54CF701B-B076-4FC3-8A01-7434111A1C5B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {54CF701B-B076-4FC3-8A01-7434111A1C5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {54CF701B-B076-4FC3-8A01-7434111A1C5B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {54CF701B-B076-4FC3-8A01-7434111A1C5B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {54CF701B-B076-4FC3-8A01-7434111A1C5B}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/UdpPlugWebsocket/App.config b/UdpPlugWebsocket/App.config new file mode 100644 index 0000000..88fa402 --- /dev/null +++ b/UdpPlugWebsocket/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/UdpPlugWebsocket/Browser.Designer.cs b/UdpPlugWebsocket/Browser.Designer.cs new file mode 100644 index 0000000..c6fc046 --- /dev/null +++ b/UdpPlugWebsocket/Browser.Designer.cs @@ -0,0 +1,191 @@ +namespace UdpPlugWebsocket +{ + partial class Browser + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.dgv_browsers = new System.Windows.Forms.DataGridView(); + this.panel1 = new System.Windows.Forms.Panel(); + this.txt_Send = new System.Windows.Forms.TextBox(); + this.btn_Send = new System.Windows.Forms.Button(); + this.splitter1 = new System.Windows.Forms.Splitter(); + this.groupBox2 = new System.Windows.Forms.GroupBox(); + this.richTextBox1 = new System.Windows.Forms.RichTextBox(); + this.dataGridView1 = new System.Windows.Forms.DataGridView(); + this.groupBox1.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.dgv_browsers)).BeginInit(); + this.panel1.SuspendLayout(); + this.groupBox2.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).BeginInit(); + this.SuspendLayout(); + // + // groupBox1 + // + this.groupBox1.Controls.Add(this.dgv_browsers); + this.groupBox1.Dock = System.Windows.Forms.DockStyle.Fill; + this.groupBox1.Font = new System.Drawing.Font("微软雅黑", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); + this.groupBox1.Location = new System.Drawing.Point(0, 0); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.Size = new System.Drawing.Size(830, 600); + this.groupBox1.TabIndex = 2; + this.groupBox1.TabStop = false; + this.groupBox1.Text = "浏览客户端:"; + // + // dgv_browsers + // + this.dgv_browsers.AllowUserToAddRows = false; + this.dgv_browsers.BackgroundColor = System.Drawing.Color.WhiteSmoke; + this.dgv_browsers.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; + this.dgv_browsers.Dock = System.Windows.Forms.DockStyle.Fill; + this.dgv_browsers.GridColor = System.Drawing.Color.Snow; + this.dgv_browsers.Location = new System.Drawing.Point(3, 25); + this.dgv_browsers.MultiSelect = false; + this.dgv_browsers.Name = "dgv_browsers"; + this.dgv_browsers.ReadOnly = true; + this.dgv_browsers.RowHeadersVisible = false; + this.dgv_browsers.RowHeadersWidth = 4; + this.dgv_browsers.RowTemplate.Height = 23; + this.dgv_browsers.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect; + this.dgv_browsers.Size = new System.Drawing.Size(824, 572); + this.dgv_browsers.TabIndex = 0; + this.dgv_browsers.SelectionChanged += new System.EventHandler(this.dgv_browsers_SelectionChanged); + // + // panel1 + // + this.panel1.Controls.Add(this.txt_Send); + this.panel1.Controls.Add(this.btn_Send); + this.panel1.Dock = System.Windows.Forms.DockStyle.Bottom; + this.panel1.Location = new System.Drawing.Point(0, 600); + this.panel1.Name = "panel1"; + this.panel1.Size = new System.Drawing.Size(830, 25); + this.panel1.TabIndex = 5; + // + // txt_Send + // + this.txt_Send.Dock = System.Windows.Forms.DockStyle.Fill; + this.txt_Send.Font = new System.Drawing.Font("宋体", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); + this.txt_Send.Location = new System.Drawing.Point(0, 0); + this.txt_Send.Name = "txt_Send"; + this.txt_Send.Size = new System.Drawing.Size(744, 26); + this.txt_Send.TabIndex = 0; + // + // btn_Send + // + this.btn_Send.Dock = System.Windows.Forms.DockStyle.Right; + this.btn_Send.Location = new System.Drawing.Point(744, 0); + this.btn_Send.Name = "btn_Send"; + this.btn_Send.Size = new System.Drawing.Size(86, 25); + this.btn_Send.TabIndex = 1; + this.btn_Send.Text = "发送"; + this.btn_Send.UseVisualStyleBackColor = true; + this.btn_Send.Click += new System.EventHandler(this.btn_Send_Click); + // + // splitter1 + // + this.splitter1.Dock = System.Windows.Forms.DockStyle.Bottom; + this.splitter1.Location = new System.Drawing.Point(0, 368); + this.splitter1.Name = "splitter1"; + this.splitter1.Size = new System.Drawing.Size(830, 10); + this.splitter1.TabIndex = 9; + this.splitter1.TabStop = false; + // + // groupBox2 + // + this.groupBox2.Controls.Add(this.richTextBox1); + this.groupBox2.Controls.Add(this.dataGridView1); + this.groupBox2.Dock = System.Windows.Forms.DockStyle.Bottom; + this.groupBox2.Font = new System.Drawing.Font("微软雅黑", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); + this.groupBox2.Location = new System.Drawing.Point(0, 378); + this.groupBox2.Name = "groupBox2"; + this.groupBox2.Size = new System.Drawing.Size(830, 222); + this.groupBox2.TabIndex = 8; + this.groupBox2.TabStop = false; + this.groupBox2.Text = "接收:"; + // + // richTextBox1 + // + this.richTextBox1.Dock = System.Windows.Forms.DockStyle.Fill; + this.richTextBox1.Location = new System.Drawing.Point(3, 25); + this.richTextBox1.Name = "richTextBox1"; + this.richTextBox1.Size = new System.Drawing.Size(824, 194); + this.richTextBox1.TabIndex = 5; + this.richTextBox1.Text = ""; + // + // dataGridView1 + // + this.dataGridView1.AllowUserToAddRows = false; + this.dataGridView1.BackgroundColor = System.Drawing.Color.WhiteSmoke; + this.dataGridView1.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; + this.dataGridView1.Dock = System.Windows.Forms.DockStyle.Fill; + this.dataGridView1.GridColor = System.Drawing.Color.Snow; + this.dataGridView1.Location = new System.Drawing.Point(3, 25); + this.dataGridView1.MultiSelect = false; + this.dataGridView1.Name = "dataGridView1"; + this.dataGridView1.ReadOnly = true; + this.dataGridView1.RowHeadersVisible = false; + this.dataGridView1.RowHeadersWidth = 4; + this.dataGridView1.RowTemplate.Height = 23; + this.dataGridView1.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect; + this.dataGridView1.Size = new System.Drawing.Size(824, 194); + this.dataGridView1.TabIndex = 0; + // + // Browser + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(830, 625); + this.Controls.Add(this.splitter1); + this.Controls.Add(this.groupBox2); + this.Controls.Add(this.groupBox1); + this.Controls.Add(this.panel1); + this.Name = "Browser"; + this.Text = "Browser"; + this.Load += new System.EventHandler(this.Browser_Load); + this.groupBox1.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.dgv_browsers)).EndInit(); + this.panel1.ResumeLayout(false); + this.panel1.PerformLayout(); + this.groupBox2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).EndInit(); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.GroupBox groupBox1; + private System.Windows.Forms.DataGridView dgv_browsers; + private System.Windows.Forms.Panel panel1; + private System.Windows.Forms.TextBox txt_Send; + private System.Windows.Forms.Button btn_Send; + private System.Windows.Forms.Splitter splitter1; + private System.Windows.Forms.GroupBox groupBox2; + private System.Windows.Forms.RichTextBox richTextBox1; + private System.Windows.Forms.DataGridView dataGridView1; + } +} \ No newline at end of file diff --git a/UdpPlugWebsocket/Browser.cs b/UdpPlugWebsocket/Browser.cs new file mode 100644 index 0000000..1a492dc --- /dev/null +++ b/UdpPlugWebsocket/Browser.cs @@ -0,0 +1,222 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using WebSocketSharp.Server; +using WebSocketSharp; +using System.IO; +namespace UdpPlugWebsocket +{ + public partial class Browser : Form + { + string s_output; + //本Websocket服务对象 + public WebSocketServer server; + //人工选中的连接对象 + public static IWebSocketSession curr; + private static Browser _instance; + + public Browser() + { + InitializeComponent(); + + server = new WebSocketServer("ws://0.0.0.0:"+SetupForm.cfg.BrowserPort); + server.AddWebSocketService("/"); + server.Start(); + } + private void Browser_Load(object sender, EventArgs e) + { + + } + /// + /// 发送字节 + /// + public void Send(string EndpointString,byte[] bytes) + { + try + { + IWebSocketSession session=server.WebSocketServices["/"].Sessions.Sessions.Where(x => { return x.Context.UserEndPoint.ToString() == EndpointString; }).FirstOrDefault(); + server.WebSocketServices["/"].Sessions.SendToAsync(System.Text.Encoding.Default.GetString(bytes), session.ID, null); + } + catch(Exception ex) + { + HandleError?.Invoke(ex.StackTrace); + } + } + public void RefreshDataGrid(WebSocketSessionManager wsm) + { + DataTable dt = GetConnectionTable(wsm); + { + this.Invoke(new Action(() => + { + lock (dt.Rows.SyncRoot) + { + dgv_browsers.DataSource = dt.Copy(); + } + dgv_browsers.Columns[0].Width = 400; + dgv_browsers.Columns[1].Width = 200; + })); + } + } + private DataTable GetConnectionTable(WebSocketSessionManager wsm) + { + + DataTable dt = new DataTable(); + dt.Columns.Add("ID"); + dt.Columns.Add("EndPoint"); + dt.Columns.Add("State"); + + lock (wsm.Sessions) + { + foreach (IWebSocketSession session in wsm.Sessions.ToArray()) + { + + DataRow dr = dt.NewRow(); + dr[0] = session.ID; + dr[1] = session.Context.UserEndPoint; + dr[2] = session.State; + dt.Rows.Add(dr); + } + } + return dt; + + } + public static Browser Instance + { + get + { + if (_instance == null) + _instance = new Browser(); + return _instance; + } + private set { _instance = value; } + } + public void LogMessage(string message) + { + HandleMessage?.Invoke(message); + } + public void LogError(string message) + { + HandleError?.Invoke(message); + } + #region 消息和错误事件 + /// + /// 消息处理程序 + /// + public Action HandleMessage { get; set; } + /// + /// 异常处理程序 + /// + public Action HandleError { get; set; } + + + #endregion + private void btn_Send_Click(object sender, EventArgs e) + { + try + { + server.WebSocketServices["/"].Sessions.SendToAsync(txt_Send.Text, curr.ID, null); + }catch (Exception ex) + { + HandleError?.Invoke(ex.StackTrace); + } + } + private void dgv_browsers_SelectionChanged(object sender, EventArgs e) + { + if (dgv_browsers.SelectedRows.Count > 0) + { + string tag = dgv_browsers.SelectedRows[0].Cells["EndPoint"].Value.ToString(); + curr = server.WebSocketServices["/"].Sessions.Sessions.Where(x => + { + var Id = (string)x.Context.UserEndPoint.ToString(); + return Id == tag; + }).FirstOrDefault(); + } + else + { + curr = null; + } + } + public void SetOutput(string text) + { + //决定是否屏显 + if (SetupForm.cfg.EnableScreenLog == false) return; + + text = DateTime.Now.ToLongDateString() + " " + DateTime.Now.ToLongTimeString() + " |" + text; + this.Invoke(new Action(() => + { + s_output = s_output + text.Replace("\0", "") + "\r"; + + if ((s_output.Length) > 5000) + { + s_output = s_output.Substring(s_output.Length - 5000, 5000); + } + //滚到最后 + this.richTextBox1.Text = s_output; + this.richTextBox1.Select(richTextBox1.TextLength, 0); + //this.richTextBox1.Focus(); + this.richTextBox1.ScrollToCaret(); + + })); + } + #region 客户端连接事件 + /// + /// 当新客户端连接后执行 + /// + public Action HandleNewWebSocketClientConnected { get; set; } + + /// + /// 客户端连接接受新的消息后调用 + /// + public Action HandleWebSocketRecMsg { get; set; } + + /// + /// 客户端连接发送消息后回调 + /// + public Action HandleWebSocketSendMsg { get; set; } + + /// + /// 客户端连接关闭后回调 + /// + public Action HandleWebSocketClientClose { get; set; } + + #endregion + } + public class Laputa : WebSocketBehavior + { + + protected override void OnOpen() + { + + Browser.Instance. LogMessage( Context.UserEndPoint.ToString() + " was accepted" + " Websocket ID is " + ID); + Browser.Instance.RefreshDataGrid(Sessions); + Browser.Instance.HandleNewWebSocketClientConnected?.Invoke(Context); + base.OnOpen(); + } + protected override void OnClose(CloseEventArgs e) + { + Browser.Instance.LogMessage("Websocket "+this.ID + " was closed"); + Browser.Instance.RefreshDataGrid(Sessions); + Browser.Instance.HandleWebSocketClientClose?.Invoke(Context); + base.OnClose(e); + + } + protected override void OnMessage(MessageEventArgs e) + { + string s_data = e.Data; + Logger log = new Logger(); + //Browser.Instance.LogMessage(Context.UserEndPoint.ToString() + " is Saying: " + e.Data); + Browser.Instance.HandleWebSocketRecMsg?.Invoke(System.Text.Encoding.Default.GetBytes(e.Data), Context); + //Console.Write(e.Data); + Browser.Instance.SetOutput(Context.UserEndPoint.ToString()+" received " +e.Data); + } + + } + + +} diff --git a/UdpPlugWebsocket/Browser.resx b/UdpPlugWebsocket/Browser.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/UdpPlugWebsocket/Browser.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/UdpPlugWebsocket/ClassDiagram1.cd b/UdpPlugWebsocket/ClassDiagram1.cd new file mode 100644 index 0000000..7b89419 --- /dev/null +++ b/UdpPlugWebsocket/ClassDiagram1.cd @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/UdpPlugWebsocket/ClassDiagram2.cd b/UdpPlugWebsocket/ClassDiagram2.cd new file mode 100644 index 0000000..8a24aed --- /dev/null +++ b/UdpPlugWebsocket/ClassDiagram2.cd @@ -0,0 +1,48 @@ + + + + + + + + + + Device\SocketServer.cs + + + + + gBQAIQAAAAIEUAggQEBgABAAAkAAAQAAQAiAQAABAAg= + Device\SocketServer.cs + + + + + + + + wAQEAAAAAAAgQAAAUAAAAAAAAAAAAgAAAAoAAAAACAQ= + Device\SocketConnection.cs + + + + \ No newline at end of file diff --git a/UdpPlugWebsocket/Device/01.Coldairarrow.Util.Sockets/01.Coldairarrow.Util.Sockets.csproj b/UdpPlugWebsocket/Device/01.Coldairarrow.Util.Sockets/01.Coldairarrow.Util.Sockets.csproj new file mode 100644 index 0000000..044e5a2 --- /dev/null +++ b/UdpPlugWebsocket/Device/01.Coldairarrow.Util.Sockets/01.Coldairarrow.Util.Sockets.csproj @@ -0,0 +1,51 @@ + + + + + Debug + AnyCPU + {5DF7AFDF-6B88-42AD-B855-4EB9BF0E2495} + Library + Properties + Coldairarrow.Util.Sockets + Coldairarrow.Util.Sockets + v4.0 + 512 + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + bin\Debug\Coldairarrow.Util.Sockets.xml + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/UdpPlugWebsocket/Device/01.Coldairarrow.Util.Sockets/Properties/AssemblyInfo.cs b/UdpPlugWebsocket/Device/01.Coldairarrow.Util.Sockets/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..742eb2f --- /dev/null +++ b/UdpPlugWebsocket/Device/01.Coldairarrow.Util.Sockets/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// 有关程序集的一般信息由以下 +// 控制。更改这些特性值可修改 +// 与程序集关联的信息。 +[assembly: AssemblyTitle("01.Coldairarrow.Util.Sockets")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("01.Coldairarrow.Util.Sockets")] +[assembly: AssemblyCopyright("Copyright © 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// 将 ComVisible 设置为 false 会使此程序集中的类型 +//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型 +//请将此类型的 ComVisible 特性设置为 true。 +[assembly: ComVisible(false)] + +// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID +[assembly: Guid("5df7afdf-6b88-42ad-b855-4eb9bf0e2495")] + +// 程序集的版本信息由下列四个值组成: +// +// 主版本 +// 次版本 +// 生成号 +// 修订号 +// +// 可以指定所有值,也可以使用以下所示的 "*" 预置版本号和修订号 +//通过使用 "*",如下所示: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/UdpPlugWebsocket/Device/01.Coldairarrow.Util.Sockets/SocketClient.cs b/UdpPlugWebsocket/Device/01.Coldairarrow.Util.Sockets/SocketClient.cs new file mode 100644 index 0000000..fbf9e45 --- /dev/null +++ b/UdpPlugWebsocket/Device/01.Coldairarrow.Util.Sockets/SocketClient.cs @@ -0,0 +1,241 @@ +using System; +using System.Net; +using System.Net.Sockets; +using System.Text; + +namespace Coldairarrow.Util.Sockets +{ + /// + /// Socket客户端 + /// + public class SocketClient + { + #region 构造函数 + + /// + /// 构造函数,连接服务器IP地址默认为本机127.0.0.1 + /// + /// 监听的端口 + public SocketClient(int port) + { + _ip = "127.0.0.1"; + _port = port; + } + + /// + /// 构造函数 + /// + /// 监听的IP地址 + /// 监听的端口 + public SocketClient(string ip, int port) + { + _ip = ip; + _port = port; + } + + #endregion + + #region 内部成员 + + private Socket _socket = null; + private string _ip = ""; + private int _port = 0; + private bool _isRec=true; + private bool IsSocketConnected() + { + bool part1 = _socket.Poll(1000, SelectMode.SelectRead); + bool part2 = (_socket.Available == 0); + if (part1 && part2) + return false; + else + return true; + } + + /// + /// 开始接受客户端消息 + /// + public void StartRecMsg() + { + try + { + byte[] container = new byte[1024 * 1024 * 2]; + _socket.BeginReceive(container, 0, container.Length, SocketFlags.None, asyncResult => + { + try + { + int length = _socket.EndReceive(asyncResult); + + //马上进行下一轮接受,增加吞吐量 + if (length > 0 && _isRec && IsSocketConnected()) + StartRecMsg(); + + if (length > 0) + { + byte[] recBytes = new byte[length]; + Array.Copy(container, 0, recBytes, 0, length); + + //处理消息 + HandleRecMsg?.BeginInvoke(recBytes, this,null,null); + } + else + Close(); + } + catch (Exception ex) + { + HandleException?.BeginInvoke(ex,null,null); + Close(); + } + }, null); + } + catch (Exception ex) + { + HandleException?.BeginInvoke(ex,null,null); + Close(); + } + } + + #endregion + + #region 外部接口 + + /// + /// 开始服务,连接服务端 + /// + public void StartClient() + { + try + { + //实例化 套接字 (ip4寻址协议,流式传输,TCP协议) + _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + //创建 ip对象 + IPAddress address = IPAddress.Parse(_ip); + //创建网络节点对象 包含 ip和port + IPEndPoint endpoint = new IPEndPoint(address, _port); + //将 监听套接字 绑定到 对应的IP和端口 + _socket.BeginConnect(endpoint, asyncResult => + { + try + { + _socket.EndConnect(asyncResult); + //开始接受服务器消息 + StartRecMsg(); + + HandleClientStarted?.BeginInvoke(this,null,null); + } + catch (Exception ex) + { + HandleException?.BeginInvoke(ex,null,null); + } + }, null); + } + catch (Exception ex) + { + HandleException?.BeginInvoke(ex,null,null); + } + } + + /// + /// 发送数据 + /// + /// 数据字节 + public void Send(byte[] bytes) + { + try + { + _socket.BeginSend(bytes, 0, bytes.Length, SocketFlags.None, asyncResult => + { + try + { + int length = _socket.EndSend(asyncResult); + HandleSendMsg?.BeginInvoke(bytes, this,null,null); + } + catch (Exception ex) + { + HandleException?.BeginInvoke(ex,null,null); + } + }, null); + } + catch (Exception ex) + { + HandleException?.BeginInvoke(ex,null,null); + } + } + + /// + /// 发送字符串(默认使用UTF-8编码) + /// + /// 字符串 + public void Send(string msgStr) + { + Send(Encoding.UTF8.GetBytes(msgStr)); + } + + /// + /// 发送字符串(使用自定义编码) + /// + /// 字符串消息 + /// 使用的编码 + public void Send(string msgStr, Encoding encoding) + { + Send(encoding.GetBytes(msgStr)); + } + + /// + /// 传入自定义属性 + /// + public object Property { get; set; } + + /// + /// 关闭与服务器的连接 + /// + public void Close() + { + try + { + _isRec = false; + _socket.Disconnect(false); + HandleClientClose?.BeginInvoke(this,null,null); + } + catch (Exception ex) + { + HandleException?.BeginInvoke(ex,null,null); + } + finally + { + _socket.Dispose(); + GC.Collect(); + } + } + + #endregion + + #region 事件处理 + + /// + /// 客户端连接建立后回调 + /// + public Action HandleClientStarted { get; set; } + + /// + /// 处理接受消息的委托 + /// + public Action HandleRecMsg { get; set; } + + /// + /// 客户端连接发送消息后回调 + /// + public Action HandleSendMsg { get; set; } + + /// + /// 客户端连接关闭后回调 + /// + public Action HandleClientClose { get; set; } + + /// + /// 异常处理程序 + /// + public Action HandleException { get; set; } + + #endregion + } +} diff --git a/UdpPlugWebsocket/Device/01.Coldairarrow.Util.Sockets/SocketConnection.cs b/UdpPlugWebsocket/Device/01.Coldairarrow.Util.Sockets/SocketConnection.cs new file mode 100644 index 0000000..790b806 --- /dev/null +++ b/UdpPlugWebsocket/Device/01.Coldairarrow.Util.Sockets/SocketConnection.cs @@ -0,0 +1,195 @@ +using System; +using System.Net.Sockets; +using System.Text; + +namespace Coldairarrow.Util.Sockets +{ + /// + /// Socket连接,双向通信 + /// + public class SocketConnection + { + #region 构造函数 + + /// + /// 构造函数 + /// + /// 维护的Socket对象 + /// 维护此连接的服务对象 + public SocketConnection(Socket socket,SocketServer server) + { + _socket = socket; + _server = server; + } + + #endregion + + #region 私有成员 + + private readonly Socket _socket; + private bool _isRec=true; + private SocketServer _server = null; + private bool IsSocketConnected() + { + bool part1 = _socket.Poll(1000, SelectMode.SelectRead); + bool part2 = (_socket.Available == 0); + if (part1 && part2) + return false; + else + return true; + } + + #endregion + + #region 外部接口 + + /// + /// 开始接受客户端消息 + /// + public void StartRecMsg() + { + try + { + byte[] container = new byte[1024 * 1024 * 4]; + _socket.BeginReceive(container, 0, container.Length, SocketFlags.None, asyncResult => + { + try + { + int length = _socket.EndReceive(asyncResult); + + //马上进行下一轮接受,增加吞吐量 + if (length > 0 && _isRec && IsSocketConnected()) + StartRecMsg(); + + if (length > 0) + { + byte[] recBytes = new byte[length]; + Array.Copy(container, 0, recBytes, 0, length); + try + { + //处理消息 + HandleRecMsg?.BeginInvoke(recBytes, this, _server, null, null); + } + catch (Exception ex) + { + HandleException?.Invoke(ex); + } + } + else + Close(); + } + catch (Exception ex) + { + HandleException?.BeginInvoke(ex,null,null); + Close(); + } + }, null); + } + catch (Exception ex) + { + HandleException?.BeginInvoke(ex,null,null); + Close(); + } + } + + /// + /// 发送数据 + /// + /// 数据字节 + public void Send(byte[] bytes) + { + try + { + _socket.BeginSend(bytes, 0, bytes.Length, SocketFlags.None, asyncResult => + { + try + { + int length = _socket.EndSend(asyncResult); + HandleSendMsg?.BeginInvoke(bytes, this, _server,null,null); + } + catch (Exception ex) + { + HandleException?.BeginInvoke(ex,null,null); + } + }, null); + } + catch (Exception ex) + { + HandleException?.BeginInvoke(ex,null,null); + } + } + + /// + /// 发送字符串(默认使用UTF-8编码) + /// + /// 字符串 + public void Send(string msgStr) + { + Send(Encoding.UTF8.GetBytes(msgStr)); + } + + /// + /// 发送字符串(使用自定义编码) + /// + /// 字符串消息 + /// 使用的编码 + public void Send(string msgStr,Encoding encoding) + { + Send(encoding.GetBytes(msgStr)); + } + + /// + /// 传入自定义属性 + /// + public object Property { get; set; } + + /// + /// 关闭当前连接 + /// + public void Close() + { + try + { + _isRec = false; + _socket.Disconnect(false); + _server.RemoveConnection(this); + HandleClientClose?.BeginInvoke(this, _server,null,null); + } + catch (Exception ex) + { + HandleException?.BeginInvoke(ex,null,null); + } + finally + { + _socket.Dispose(); + GC.Collect(); + } + } + + #endregion + + #region 事件处理 + + /// + /// 客户端连接接受新的消息后调用 + /// + public Action HandleRecMsg { get; set; } + + /// + /// 客户端连接发送消息后回调 + /// + public Action HandleSendMsg { get; set; } + + /// + /// 客户端连接关闭后回调 + /// + public Action HandleClientClose { get; set; } + + /// + /// 异常处理程序 + /// + public Action HandleException { get; set; } + + #endregion + } +} diff --git a/UdpPlugWebsocket/Device/01.Coldairarrow.Util.Sockets/SocketServer.cs b/UdpPlugWebsocket/Device/01.Coldairarrow.Util.Sockets/SocketServer.cs new file mode 100644 index 0000000..b27939a --- /dev/null +++ b/UdpPlugWebsocket/Device/01.Coldairarrow.Util.Sockets/SocketServer.cs @@ -0,0 +1,275 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Threading; + +namespace Coldairarrow.Util.Sockets +{ + /// + /// Socket服务端 + /// + public class SocketServer + { + #region 构造函数 + + /// + /// 构造函数 + /// + /// 监听的IP地址 + /// 监听的端口 + public SocketServer(string ip, int port) + { + _ip = ip; + _port = port; + } + + /// + /// 构造函数,监听IP地址默认为本机0.0.0.0 + /// + /// 监听的端口 + public SocketServer(int port) + { + _ip = "0.0.0.0"; + _port = port; + } + + #endregion + + #region 内部成员 + + private Socket _socket { get; set; } = null; + private string _ip { get; set; } = ""; + private int _port { get; set; } = 0; + private bool _isListen { get; set; } = true; + private void StartListen() + { + try + { + _socket.BeginAccept(asyncResult => + { + try + { + Socket newSocket = _socket.EndAccept(asyncResult); + + //马上进行下一轮监听,增加吞吐量 + if (_isListen) + StartListen(); + + SocketConnection newConnection = new SocketConnection(newSocket, this) + { + HandleRecMsg = HandleRecMsg == null ? null : new Action(HandleRecMsg), + HandleClientClose = HandleClientClose == null ? null : new Action(HandleClientClose), + HandleSendMsg = HandleSendMsg == null ? null : new Action(HandleSendMsg), + HandleException = HandleException == null ? null : new Action(HandleException) + }; + + newConnection.StartRecMsg(); + AddConnection(newConnection); + HandleNewClientConnected?.BeginInvoke(this, newConnection, null, null); + } + catch (Exception ex) + { + HandleException?.BeginInvoke(ex, null, null); + } + }, null); + } + catch (Exception ex) + { + HandleException?.BeginInvoke(ex, null, null); + } + } + private LinkedList _clientList { get; } = new LinkedList(); + + #endregion + + #region 外部接口 + + /// + /// 开始服务,监听客户端 + /// + public void StartServer() + { + try + { + //实例化套接字(ip4寻址协议,流式传输,TCP协议) + _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + //创建ip对象 + IPAddress address = IPAddress.Parse(_ip); + //创建网络节点对象包含ip和port + IPEndPoint endpoint = new IPEndPoint(address, _port); + //将 监听套接字绑定到 对应的IP和端口 + _socket.Bind(endpoint); + //设置监听队列长度为Int32最大值(同时能够处理连接请求数量) + _socket.Listen(int.MaxValue); + //开始监听客户端 + StartListen(); + HandleServerStarted?.BeginInvoke(this, null, null); + } + catch (Exception ex) + { + HandleException?.BeginInvoke(ex, null, null); + } + } + + /// + /// 维护客户端列表的读写锁 + /// + public ReaderWriterLockSlim RWLock_ClientList { get; } = new ReaderWriterLockSlim(); + + /// + /// 关闭指定客户端连接 + /// + /// 指定的客户端连接 + public void CloseConnection(SocketConnection theConnection) + { + theConnection.Close(); + } + + /// + /// 添加客户端连接 + /// + /// 需要添加的客户端连接 + public void AddConnection(SocketConnection theConnection) + { + RWLock_ClientList.EnterWriteLock(); + try + { + _clientList.AddLast(theConnection); + } + finally + { + RWLock_ClientList.ExitWriteLock(); + } + } + + /// + /// 删除指定的客户端连接 + /// + /// 指定的客户端连接 + public void RemoveConnection(SocketConnection theConnection) + { + RWLock_ClientList.EnterWriteLock(); + try + { + _clientList.Remove(theConnection); + } + finally + { + RWLock_ClientList.ExitWriteLock(); + } + } + + /// + /// 通过条件获取客户端连接列表 + /// + /// 筛选条件 + /// + public IEnumerable GetConnectionList(Func predicate) + { + RWLock_ClientList.EnterReadLock(); + try + { + return _clientList.Where(predicate); + } + finally + { + RWLock_ClientList.ExitReadLock(); + } + } + + /// + /// 获取所有客户端连接列表 + /// + /// + public IEnumerable GetConnectionList() + { + return _clientList; + } + + /// + /// 寻找特定条件的客户端连接 + /// + /// 筛选条件 + /// + public SocketConnection GetTheConnection(Func predicate) + { + RWLock_ClientList.EnterReadLock(); + try + { + return _clientList.Where(predicate).FirstOrDefault(); + } + finally + { + RWLock_ClientList.ExitReadLock(); + } + } + + /// + /// 获取客户端连接数 + /// + /// + public int GetConnectionCount() + { + RWLock_ClientList.EnterReadLock(); + try + { + return _clientList.Count; + } + finally + { + RWLock_ClientList.ExitReadLock(); + } + } + + #endregion + + #region 公共事件 + + /// + /// 异常处理程序 + /// + public Action HandleException { get; set; } + + #endregion + + #region 服务端事件 + + /// + /// 服务启动后执行 + /// + public Action HandleServerStarted { get; set; } + + /// + /// 当新客户端连接后执行 + /// + public Action HandleNewClientConnected { get; set; } + + /// + /// 服务端关闭客户端后执行 + /// + public Action HandleCloseClient { get; set; } + + #endregion + + #region 客户端连接事件 + + /// + /// 客户端连接接受新的消息后调用 + /// + public Action HandleRecMsg { get; set; } + + /// + /// 客户端连接发送消息后回调 + /// + public Action HandleSendMsg { get; set; } + + /// + /// 客户端连接关闭后回调 + /// + public Action HandleClientClose { get; set; } + + #endregion + } +} diff --git a/UdpPlugWebsocket/Device/01.Coldairarrow.Util.Sockets/把原代码改为了UDP连接.txt b/UdpPlugWebsocket/Device/01.Coldairarrow.Util.Sockets/把原代码改为了UDP连接.txt new file mode 100644 index 0000000..6fb5fa0 --- /dev/null +++ b/UdpPlugWebsocket/Device/01.Coldairarrow.Util.Sockets/把原代码改为了UDP连接.txt @@ -0,0 +1,3 @@ +https://github.com/Coldairarrow/Sockets + +ԭΪTCP/IPЭ飬޸ΪUDPЭ \ No newline at end of file diff --git a/UdpPlugWebsocket/Device/Device.Designer.cs b/UdpPlugWebsocket/Device/Device.Designer.cs new file mode 100644 index 0000000..b731e7a --- /dev/null +++ b/UdpPlugWebsocket/Device/Device.Designer.cs @@ -0,0 +1,192 @@ +namespace UdpPlugWebsocket +{ + partial class Device + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.dgv_devices = new System.Windows.Forms.DataGridView(); + this.panel1 = new System.Windows.Forms.Panel(); + this.txt_Send = new System.Windows.Forms.TextBox(); + this.btn_Send = new System.Windows.Forms.Button(); + this.splitter1 = new System.Windows.Forms.Splitter(); + this.groupBox2 = new System.Windows.Forms.GroupBox(); + this.richTextBox1 = new System.Windows.Forms.RichTextBox(); + this.dataGridView1 = new System.Windows.Forms.DataGridView(); + this.groupBox1.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.dgv_devices)).BeginInit(); + this.panel1.SuspendLayout(); + this.groupBox2.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).BeginInit(); + this.SuspendLayout(); + // + // groupBox1 + // + this.groupBox1.Controls.Add(this.dgv_devices); + this.groupBox1.Dock = System.Windows.Forms.DockStyle.Fill; + this.groupBox1.Font = new System.Drawing.Font("微软雅黑", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); + this.groupBox1.Location = new System.Drawing.Point(0, 0); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.Size = new System.Drawing.Size(872, 614); + this.groupBox1.TabIndex = 3; + this.groupBox1.TabStop = false; + this.groupBox1.Text = "终端设备:"; + this.groupBox1.Enter += new System.EventHandler(this.groupBox1_Enter); + // + // dgv_devices + // + this.dgv_devices.AllowUserToAddRows = false; + this.dgv_devices.BackgroundColor = System.Drawing.Color.WhiteSmoke; + this.dgv_devices.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; + this.dgv_devices.Dock = System.Windows.Forms.DockStyle.Fill; + this.dgv_devices.GridColor = System.Drawing.Color.Snow; + this.dgv_devices.Location = new System.Drawing.Point(3, 25); + this.dgv_devices.MultiSelect = false; + this.dgv_devices.Name = "dgv_devices"; + this.dgv_devices.ReadOnly = true; + this.dgv_devices.RowHeadersVisible = false; + this.dgv_devices.RowHeadersWidth = 4; + this.dgv_devices.RowTemplate.Height = 23; + this.dgv_devices.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect; + this.dgv_devices.Size = new System.Drawing.Size(866, 586); + this.dgv_devices.TabIndex = 0; + this.dgv_devices.SelectionChanged += new System.EventHandler(this.dgv_devices_SelectionChanged); + // + // panel1 + // + this.panel1.Controls.Add(this.txt_Send); + this.panel1.Controls.Add(this.btn_Send); + this.panel1.Dock = System.Windows.Forms.DockStyle.Bottom; + this.panel1.Location = new System.Drawing.Point(0, 614); + this.panel1.Name = "panel1"; + this.panel1.Size = new System.Drawing.Size(872, 25); + this.panel1.TabIndex = 4; + // + // txt_Send + // + this.txt_Send.Dock = System.Windows.Forms.DockStyle.Fill; + this.txt_Send.Font = new System.Drawing.Font("宋体", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); + this.txt_Send.Location = new System.Drawing.Point(0, 0); + this.txt_Send.Name = "txt_Send"; + this.txt_Send.Size = new System.Drawing.Size(786, 26); + this.txt_Send.TabIndex = 0; + // + // btn_Send + // + this.btn_Send.Dock = System.Windows.Forms.DockStyle.Right; + this.btn_Send.Location = new System.Drawing.Point(786, 0); + this.btn_Send.Name = "btn_Send"; + this.btn_Send.Size = new System.Drawing.Size(86, 25); + this.btn_Send.TabIndex = 1; + this.btn_Send.Text = "发送"; + this.btn_Send.UseVisualStyleBackColor = true; + this.btn_Send.Click += new System.EventHandler(this.btn_Send_Click); + // + // splitter1 + // + this.splitter1.Dock = System.Windows.Forms.DockStyle.Bottom; + this.splitter1.Location = new System.Drawing.Point(0, 382); + this.splitter1.Name = "splitter1"; + this.splitter1.Size = new System.Drawing.Size(872, 10); + this.splitter1.TabIndex = 9; + this.splitter1.TabStop = false; + // + // groupBox2 + // + this.groupBox2.Controls.Add(this.richTextBox1); + this.groupBox2.Controls.Add(this.dataGridView1); + this.groupBox2.Dock = System.Windows.Forms.DockStyle.Bottom; + this.groupBox2.Font = new System.Drawing.Font("微软雅黑", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); + this.groupBox2.Location = new System.Drawing.Point(0, 392); + this.groupBox2.Name = "groupBox2"; + this.groupBox2.Size = new System.Drawing.Size(872, 222); + this.groupBox2.TabIndex = 8; + this.groupBox2.TabStop = false; + this.groupBox2.Text = "接收:"; + // + // richTextBox1 + // + this.richTextBox1.Dock = System.Windows.Forms.DockStyle.Fill; + this.richTextBox1.Location = new System.Drawing.Point(3, 25); + this.richTextBox1.Name = "richTextBox1"; + this.richTextBox1.Size = new System.Drawing.Size(866, 194); + this.richTextBox1.TabIndex = 5; + this.richTextBox1.Text = ""; + // + // dataGridView1 + // + this.dataGridView1.AllowUserToAddRows = false; + this.dataGridView1.BackgroundColor = System.Drawing.Color.WhiteSmoke; + this.dataGridView1.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; + this.dataGridView1.Dock = System.Windows.Forms.DockStyle.Fill; + this.dataGridView1.GridColor = System.Drawing.Color.Snow; + this.dataGridView1.Location = new System.Drawing.Point(3, 25); + this.dataGridView1.MultiSelect = false; + this.dataGridView1.Name = "dataGridView1"; + this.dataGridView1.ReadOnly = true; + this.dataGridView1.RowHeadersVisible = false; + this.dataGridView1.RowHeadersWidth = 4; + this.dataGridView1.RowTemplate.Height = 23; + this.dataGridView1.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect; + this.dataGridView1.Size = new System.Drawing.Size(866, 194); + this.dataGridView1.TabIndex = 0; + // + // Device + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(872, 639); + this.Controls.Add(this.splitter1); + this.Controls.Add(this.groupBox2); + this.Controls.Add(this.groupBox1); + this.Controls.Add(this.panel1); + this.Name = "Device"; + this.Text = "Device"; + this.Load += new System.EventHandler(this.Device_Load); + this.groupBox1.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.dgv_devices)).EndInit(); + this.panel1.ResumeLayout(false); + this.panel1.PerformLayout(); + this.groupBox2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).EndInit(); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.GroupBox groupBox1; + private System.Windows.Forms.DataGridView dgv_devices; + private System.Windows.Forms.Panel panel1; + private System.Windows.Forms.TextBox txt_Send; + private System.Windows.Forms.Button btn_Send; + private System.Windows.Forms.Splitter splitter1; + private System.Windows.Forms.GroupBox groupBox2; + private System.Windows.Forms.RichTextBox richTextBox1; + private System.Windows.Forms.DataGridView dataGridView1; + } +} \ No newline at end of file diff --git a/UdpPlugWebsocket/Device/Device.cs b/UdpPlugWebsocket/Device/Device.cs new file mode 100644 index 0000000..1aa6c36 --- /dev/null +++ b/UdpPlugWebsocket/Device/Device.cs @@ -0,0 +1,188 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.IO; +using System.Windows.Forms; +using Miuser.NUDP.Sockets; + + +namespace UdpPlugWebsocket +{ + public partial class Device : Form + { + string s_output; + + //本UDP服务对象 + public SocketServer server; + //人工选中的连接对象 + public SocketConnection curr; + + + public Device() + { + InitializeComponent(); + server = new SocketServer(SetupForm.cfg.UDPPort); + //处理从客户端收到的消息 + server.HandleRecMsg = new Action((bytes, client, theServer) => + { + string msg = Encoding.UTF8.GetString(bytes); + SetOutput(client.Tag+$" 收到消息:{msg}"); + }); + + //处理服务器启动后事件 + server.HandleServerStarted = new Action(theServer => + { + LogMessage("UDP Server 服务已启动************"); + }); + + //处理新的客户端连接后的事件 + server.HandleNewClientConnected = new Action((theServer, theCon) => + { + LogMessage(theCon.Tag+ $@" UDP客户端接入,当前连接数:{theServer.GetConnectionCount()}"); + RefreshDataGrid(theServer); + + }); + + //处理客户端连接关闭后的事件 + server.HandleClientClose = new Action((theCon, theServer) => + { + LogMessage(theCon.Tag + $@" UDP客户端关闭,当前连接数为:{theServer.GetConnectionCount()}"); + RefreshDataGrid(theServer); + + }); + + //处理异常 + server.HandleException = new Action(ex => + { + LogError(ex.Message+" "+ex.StackTrace); + }); + server.StartServer(); + + } + private void Device_Load(object sender, EventArgs e) + { + + } + + private void RefreshDataGrid(SocketServer theServer) + { + DataTable dt = GetConnectionTable(theServer); + lock (dt) + { + this.Invoke(new Action(() => + { + dgv_devices.DataSource = dt; + dgv_devices.Columns[0].Width = 200; + })); + } + } + private DataTable GetConnectionTable(SocketServer server) + { + DataTable dt = new DataTable(); + dt.Columns.Add("Tag"); + dt.Columns.Add("IP"); + dt.Columns.Add("UDP"); + dt.Columns.Add("Lifetime"); + + IEnumerable connections = server.GetConnectionList(); + lock (connections) + { + foreach (SocketConnection conn in server.GetConnectionList().ToArray()) + { + DataRow dr = dt.NewRow(); + dr[0] = conn.Tag; + dr[1] = conn._endpoint.Address; + dr[2] = conn._endpoint.Port; + dr[3] = conn._lifetime; + dt.Rows.Add(dr); + } + } + + return dt; + } + + private static Device _instance; + public static Device Instance + { + get + { + if (_instance == null) + _instance = new Device(); + return _instance; + } + private set { _instance = value; } + } + + + private void btn_Send_Click(object sender, EventArgs e) + { + curr?.Send(txt_Send.Text); + } + + private void dgv_devices_SelectionChanged(object sender, EventArgs e) + { + if (dgv_devices.SelectedRows.Count > 0) + { + string tag = dgv_devices.SelectedRows[0].Cells["Tag"].Value.ToString(); + curr = server.GetTheConnection(x => + { + var Id = (string)x.Tag; + return Id == tag; + }); + }else + { + curr = null; + } + } + public void LogMessage(string message) + { + HandleMessage?.Invoke(message); + } + public void LogError(string message) + { + HandleError?.Invoke(message); + } + #region 消息和错误事件 + /// + /// 消息处理程序 + /// + public Action HandleMessage { get; set; } + /// + /// 异常处理程序 + /// + public Action HandleError { get; set; } + #endregion + public void SetOutput(string text) + { + //决定是否屏显 + if (SetupForm.cfg.EnableScreenLog==false) return; + + text = DateTime.Now.ToLongDateString() + " " + DateTime.Now.ToLongTimeString() + " |" + text; + this.Invoke(new Action(() => + { + s_output = s_output + text.Replace("\0", "") + "\r"; + + if ((s_output.Length) > 5000) + { + s_output = s_output.Substring(s_output.Length - 5000, 5000); + } + //滚到最后 + this.richTextBox1.Text = s_output; + this.richTextBox1.Select(richTextBox1.TextLength, 0); + //this.richTextBox1.Focus(); + this.richTextBox1.ScrollToCaret(); + + })); + } + + private void groupBox1_Enter(object sender, EventArgs e) + { + + } + } +} diff --git a/UdpPlugWebsocket/Device/Device.resx b/UdpPlugWebsocket/Device/Device.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/UdpPlugWebsocket/Device/Device.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/UdpPlugWebsocket/Device/SocketClient.cs b/UdpPlugWebsocket/Device/SocketClient.cs new file mode 100644 index 0000000..fbf9e45 --- /dev/null +++ b/UdpPlugWebsocket/Device/SocketClient.cs @@ -0,0 +1,241 @@ +using System; +using System.Net; +using System.Net.Sockets; +using System.Text; + +namespace Coldairarrow.Util.Sockets +{ + /// + /// Socket客户端 + /// + public class SocketClient + { + #region 构造函数 + + /// + /// 构造函数,连接服务器IP地址默认为本机127.0.0.1 + /// + /// 监听的端口 + public SocketClient(int port) + { + _ip = "127.0.0.1"; + _port = port; + } + + /// + /// 构造函数 + /// + /// 监听的IP地址 + /// 监听的端口 + public SocketClient(string ip, int port) + { + _ip = ip; + _port = port; + } + + #endregion + + #region 内部成员 + + private Socket _socket = null; + private string _ip = ""; + private int _port = 0; + private bool _isRec=true; + private bool IsSocketConnected() + { + bool part1 = _socket.Poll(1000, SelectMode.SelectRead); + bool part2 = (_socket.Available == 0); + if (part1 && part2) + return false; + else + return true; + } + + /// + /// 开始接受客户端消息 + /// + public void StartRecMsg() + { + try + { + byte[] container = new byte[1024 * 1024 * 2]; + _socket.BeginReceive(container, 0, container.Length, SocketFlags.None, asyncResult => + { + try + { + int length = _socket.EndReceive(asyncResult); + + //马上进行下一轮接受,增加吞吐量 + if (length > 0 && _isRec && IsSocketConnected()) + StartRecMsg(); + + if (length > 0) + { + byte[] recBytes = new byte[length]; + Array.Copy(container, 0, recBytes, 0, length); + + //处理消息 + HandleRecMsg?.BeginInvoke(recBytes, this,null,null); + } + else + Close(); + } + catch (Exception ex) + { + HandleException?.BeginInvoke(ex,null,null); + Close(); + } + }, null); + } + catch (Exception ex) + { + HandleException?.BeginInvoke(ex,null,null); + Close(); + } + } + + #endregion + + #region 外部接口 + + /// + /// 开始服务,连接服务端 + /// + public void StartClient() + { + try + { + //实例化 套接字 (ip4寻址协议,流式传输,TCP协议) + _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + //创建 ip对象 + IPAddress address = IPAddress.Parse(_ip); + //创建网络节点对象 包含 ip和port + IPEndPoint endpoint = new IPEndPoint(address, _port); + //将 监听套接字 绑定到 对应的IP和端口 + _socket.BeginConnect(endpoint, asyncResult => + { + try + { + _socket.EndConnect(asyncResult); + //开始接受服务器消息 + StartRecMsg(); + + HandleClientStarted?.BeginInvoke(this,null,null); + } + catch (Exception ex) + { + HandleException?.BeginInvoke(ex,null,null); + } + }, null); + } + catch (Exception ex) + { + HandleException?.BeginInvoke(ex,null,null); + } + } + + /// + /// 发送数据 + /// + /// 数据字节 + public void Send(byte[] bytes) + { + try + { + _socket.BeginSend(bytes, 0, bytes.Length, SocketFlags.None, asyncResult => + { + try + { + int length = _socket.EndSend(asyncResult); + HandleSendMsg?.BeginInvoke(bytes, this,null,null); + } + catch (Exception ex) + { + HandleException?.BeginInvoke(ex,null,null); + } + }, null); + } + catch (Exception ex) + { + HandleException?.BeginInvoke(ex,null,null); + } + } + + /// + /// 发送字符串(默认使用UTF-8编码) + /// + /// 字符串 + public void Send(string msgStr) + { + Send(Encoding.UTF8.GetBytes(msgStr)); + } + + /// + /// 发送字符串(使用自定义编码) + /// + /// 字符串消息 + /// 使用的编码 + public void Send(string msgStr, Encoding encoding) + { + Send(encoding.GetBytes(msgStr)); + } + + /// + /// 传入自定义属性 + /// + public object Property { get; set; } + + /// + /// 关闭与服务器的连接 + /// + public void Close() + { + try + { + _isRec = false; + _socket.Disconnect(false); + HandleClientClose?.BeginInvoke(this,null,null); + } + catch (Exception ex) + { + HandleException?.BeginInvoke(ex,null,null); + } + finally + { + _socket.Dispose(); + GC.Collect(); + } + } + + #endregion + + #region 事件处理 + + /// + /// 客户端连接建立后回调 + /// + public Action HandleClientStarted { get; set; } + + /// + /// 处理接受消息的委托 + /// + public Action HandleRecMsg { get; set; } + + /// + /// 客户端连接发送消息后回调 + /// + public Action HandleSendMsg { get; set; } + + /// + /// 客户端连接关闭后回调 + /// + public Action HandleClientClose { get; set; } + + /// + /// 异常处理程序 + /// + public Action HandleException { get; set; } + + #endregion + } +} diff --git a/UdpPlugWebsocket/Device/SocketConnection.cs b/UdpPlugWebsocket/Device/SocketConnection.cs new file mode 100644 index 0000000..58382a7 --- /dev/null +++ b/UdpPlugWebsocket/Device/SocketConnection.cs @@ -0,0 +1,153 @@ +using System; +using System.Net.Sockets; +using System.Text; +using System.Linq; +using System.Net; +using UdpPlugWebsocket; + +namespace Miuser.NUDP.Sockets +{ + /// + /// Socket连接,双向通信 + /// + public class SocketConnection + { + /// + /// 构造函数 + /// + /// 维护的Socket对象 + /// 维护此连接的服务对象 + public SocketConnection(IPEndPoint endpoint,SocketServer server) + { + _endpoint = endpoint; + _server = server; + InitTimer(); + IsActive = true; + } + + #region 私有成员 + //UDP连接生存时间 + private static int LIFELIMIT =SetupForm.cfg.UDPTTL; + //定义Timer类 + private System.Timers.Timer timer; + #endregion + #region 内部函数 + /// + /// 初始化Timer控件 + /// + private void InitTimer() + { + //设置定时间隔(毫秒为单位) + int interval = 1000; + timer = new System.Timers.Timer(interval); + //设置执行一次(false)还是一直执行(true) + timer.AutoReset = true; + //设置是否执行System.Timers.Timer.Elapsed事件 + timer.Enabled = true; + //绑定Elapsed事件 + timer.Elapsed += new System.Timers.ElapsedEventHandler(TimerUp); + } + + /// + /// Timer类执行定时到点事件 + /// + /// + /// + private void TimerUp(object sender, System.Timers.ElapsedEventArgs e) + { + _lifetime -= (int)((System.Timers.Timer)(sender)).Interval; + if (_lifetime <= 0) + { + IsActive = false; + HandleClientClose?.Invoke(this, _server); + timer.Dispose(); + } + } + #endregion + + #region 公有成员 + //连接字符 + public readonly IPEndPoint _endpoint; + //服务端 + public SocketServer _server = null; + //剩余生存时间 + public int _lifetime = LIFELIMIT; + #endregion + + #region 外部接口 + + /// + /// 发送数据 + /// + /// 数据字节 + public void Send(byte[] bytes) + { + if (_server == null) return; + try + { + _server._socket.SendTo(bytes, _endpoint); + HandleSendMsg?.Invoke(bytes,this, _server); + } + catch (SocketException ex) + { + HandleException?.BeginInvoke(ex, null, null); + } + } + + /// + /// 发送字符串(默认使用UTF-8编码) + /// + /// 字符串 + public void Send(string msgStr) + { + Send(Encoding.UTF8.GetBytes(msgStr)); + } + + /// + /// 发送字符串(使用自定义编码) + /// + /// 字符串消息 + /// 使用的编码 + public void Send(string msgStr,Encoding encoding) + { + Send(encoding.GetBytes(msgStr)); + } + + /// + /// 传入自定义属性 + /// + public object Tag { get; set; } + + /// + /// 连接是否仍然活着 + /// + public bool IsActive { get; set; } + /// + /// 重新激活连接,连接生命加满 + /// + public void Reactive() + { + _lifetime = LIFELIMIT; + } + + #endregion + + #region 事件处理 + + /// + /// 客户端连接发送消息后回调 + /// + public Action HandleSendMsg { get; set; } + /// + /// 异常处理程序 + /// + public Action HandleException { get; set; } + + /// + /// 客户端连接关闭后回调 + /// + public Action HandleClientClose { get; set; } + + #endregion + } +} diff --git a/UdpPlugWebsocket/Device/SocketServer.cs b/UdpPlugWebsocket/Device/SocketServer.cs new file mode 100644 index 0000000..588999b --- /dev/null +++ b/UdpPlugWebsocket/Device/SocketServer.cs @@ -0,0 +1,369 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Data; +namespace Miuser.NUDP.Sockets +{ + /// + /// Socket服务端 + /// + public class SocketServer + { + + /// + /// 构造函数 + /// + /// 监听的IP地址 + /// 监听的端口 + public SocketServer(string ip, int port) + { + _ip = ip; + _port = port; + + } + /// + /// 构造函数,监听IP地址默认为本机0.0.0.0 + /// + /// 监听的端口 + public SocketServer(int port) + { + _ip = "0.0.0.0"; + _port = port; + //处理客户端连接关闭后的事件 + Instance = this; + + } + #region 内部成员 + + private string _ip { get; set; } = ""; + private int _port { get; set; } = 0; + private bool _isListen { get; set; } = true; + + private LinkedList _clientList { get; } = new LinkedList(); + + internal class UdpState + { + public byte[] Buffer; + public EndPoint Remote; + public Socket Socket; + public UdpState() + { + Buffer = new byte[65536]; + } + } + private bool IsListening { get; set; } + #endregion + #region 外部成员 + /// + /// 指向本类的静态对象,对象为最近生成的类实例 + /// + /// 监听的端口 + public static SocketServer Instance; + public Socket _socket { get; set; } = null; + #endregion + + #region 内部函数 + /// + /// 删除指定的客户端连接 + /// + /// 指定的客户端连接 + private void RemoveConnection(SocketConnection theConnection) + { + RWLock_ClientList.EnterWriteLock(); + try + { + _clientList.Remove(theConnection); + HandleClientClose?.Invoke(theConnection, this); + } + finally + { + RWLock_ClientList.ExitWriteLock(); + } + } + /// + /// 删除不活动的客户端连接 + /// + /// 指定的客户端连接 + private void RemoveInactiveConnections() + { + List conns = new List(); + foreach (SocketConnection connect in _clientList.ToArray()) + { + if (connect.IsActive == false) + { + conns.Add(connect); + } + } + foreach (SocketConnection conn in conns.ToArray()) + { + RemoveInactiveConnection(conn); + } + } + /// + /// 检测该客户端,如果不活动则删除连接 + /// + /// 指定的客户端连接 + private void RemoveInactiveConnection(SocketConnection connect) + { + if (connect.IsActive == false) + { + RemoveConnection(connect); + } + } + /// + /// 当某连接不活动超时激活该函数 + /// + private void HandleConnClientClose(SocketConnection conn, SocketServer server) + { + RemoveConnection(conn); + } + /// + /// 维护客户端列表的读写锁 + /// + private ReaderWriterLockSlim RWLock_ClientList { get; } = new ReaderWriterLockSlim(); + private void EndReceiveFrom(IAsyncResult ir) + { + if (IsListening) + { + UdpState state = ir.AsyncState as UdpState; + try + { + if (ir.IsCompleted) + { + int length = state.Socket.EndReceiveFrom(ir, ref state.Remote); + byte[] btReceived = new byte[length]; + Buffer.BlockCopy(state.Buffer, 0, btReceived, 0, length); + + //查询是否UDP连接已经存在 + SocketConnection connection = GetTheConnection(x => + { + var Id = (string)x.Tag; + return Id == state.Remote.ToString(); + }); + //如果不存在则新建一个UDP连接对象 + if (connection == null) + { + connection = new SocketConnection(state.Remote as IPEndPoint, this) + { + HandleSendMsg = HandleSendMsg == null ? null : new Action(HandleSendMsg), + HandleClientClose = new Action(HandleConnClientClose), + HandleException = HandleException == null ? null : new Action(HandleException) + }; + + //connection.HandleClientClose += HandleClientClose == null ? null : new Action(HandleClientClose); + + connection.Tag = state.Remote.ToString(); + AddConnection(connection); + + } + connection.Reactive(); + HandleRecMsg?.Invoke(btReceived, connection, this); + } + } + catch (Exception ex) + { + //System.Diagnostics.Trace.WriteLine(DateTime.Now.ToString() + "\t" + ex.Message + ex.Source); + HandleException?.Invoke(ex); + } + finally + { + state.Socket.BeginReceiveFrom(state.Buffer, 0, state.Buffer.Length, SocketFlags.None, ref state.Remote, new AsyncCallback(EndReceiveFrom), state); + } + } + } + /// + /// 关闭指定客户端连接 + /// + /// 指定的客户端连接 + #endregion + + + #region 外部接口 + /// + /// 开始服务,监听客户端 + /// + public void StartServer() + { + try + { + //实例化套接字(ip4寻址协议,流式传输,TCP协议) + _socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + //开始侦听 + Listening(); + HandleServerStarted?.BeginInvoke(this, null, null); + } + catch (Exception ex) + { + HandleException?.BeginInvoke(ex, null, null); + } + } + /// + ///按照IP地址和端口发送字符串 + /// + public void Send(string EndpointString, byte[] bytes) + { + try + { + SocketConnection conn = GetTheConnection(x => + { + var Id = (string)x.Tag; + return Id == EndpointString; + }); + conn.Send(bytes); + } + catch (Exception ex) + { + HandleException?.BeginInvoke(ex, null, null); + } + } + /// + ///开始异步监听端口 + /// + public void Listening() + { + IPAddress ip = IPAddress.Any; + try + { + if (this._ip != null) + if (!IPAddress.TryParse(this._ip, out ip)) + throw new ArgumentException("IP地址错误", "Ip"); + _socket.Bind(new IPEndPoint(ip, this._port)); + + UdpState state = new UdpState(); + state.Socket = _socket; + state.Remote = new IPEndPoint(IPAddress.Any, 0); + _socket.BeginReceiveFrom(state.Buffer, 0, state.Buffer.Length, SocketFlags.None, ref state.Remote, new AsyncCallback(EndReceiveFrom), state); + + IsListening = true; + } + catch (Exception ex) + { + IsListening = false; + HandleException?.BeginInvoke(ex, null, null); + } + + } + /// + /// 异步处理收到的数据包 + /// + + public void CloseConnection(SocketConnection theConnection) + { + RemoveConnection(theConnection); + //调用外部回调函数通知连接被关闭 + + } + /// + /// 添加客户端连接 + /// + /// 需要添加的客户端连接 + public void AddConnection(SocketConnection theConnection) + { + RWLock_ClientList.EnterWriteLock(); + try + { + _clientList.AddLast(theConnection); + + } + finally + { + RWLock_ClientList.ExitWriteLock(); + //调用外部回调函数通知新连接建立 + HandleNewClientConnected(this, theConnection); + } + } + /// + /// 通过条件获取客户端连接列表 + /// + /// 筛选条件 + /// + public IEnumerable GetConnectionList(Func predicate) + { + + RemoveInactiveConnections(); + IEnumerable ret; + lock (_clientList) + { + ret = _clientList.Where(predicate); + } + return ret; + } + /// + /// 获取所有客户端连接列表 + /// + /// + public IEnumerable GetConnectionList() + { + RemoveInactiveConnections(); + return _clientList; + } + /// + /// 寻找特定条件的客户端连接 + /// + /// 筛选条件 + /// + public SocketConnection GetTheConnection(Func predicate) + { + SocketConnection conn; + lock (_clientList) + { + conn = _clientList.Where(predicate).FirstOrDefault(); + } + if (conn == null) return null; + + RemoveInactiveConnection(conn); + + if (conn.IsActive) return conn; + return null; + } + /// + /// 获取客户端连接数 + /// + /// + public int GetConnectionCount() + { + int ret; + lock (_clientList) + { + ret = _clientList.Count; + } + return ret; + + } + #endregion + + #region 服务端事件 + /// + /// 服务启动后执行 + /// + public Action HandleServerStarted { get; set; } + #endregion + #region 客户端连接事件 + /// + /// 当新客户端连接后执行 + /// + public Action HandleNewClientConnected { get; set; } + /// + /// 客户端连接接受新的消息后调用 + /// + public Action HandleRecMsg { get; set; } + /// + /// 客户端连接发送消息后回调 + /// + public Action HandleSendMsg { get; set; } + /// + /// 客户端连接关闭后回调 + /// + public Action HandleClientClose { get; set; } + #endregion + #region 公共事件 + /// + /// 异常处理程序 + /// + public Action HandleException { get; set; } + #endregion + } +} diff --git a/UdpPlugWebsocket/Device/SocketServerEvent.cs b/UdpPlugWebsocket/Device/SocketServerEvent.cs new file mode 100644 index 0000000..acdc66e --- /dev/null +++ b/UdpPlugWebsocket/Device/SocketServerEvent.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Threading; + +namespace Miuser.NUDP.Sockets +{ + #region + /// + /// 异常处理程序 + /// + public class SocketExceptionEventArgs : EventArgs { public Exception Exception; public EndPoint Remote; } + /// + /// 服务启动后执行 + /// + public class ServerStartedEventArgs : EventArgs { public EndPoint Server; } + /// + /// 服务端关闭客户端后执行 + /// + public class ServerClosedEventArgs : EventArgs { public EndPoint Server; } + /// + /// 当新客户端连接后执行 + /// + public class NewClientEventArgs : EventArgs { public EndPoint Remote; } + + /// + /// 客户端连接关闭后回调 + /// + public class ClientClosedEventArgs : EventArgs { public EndPoint Remote; } + /// + /// 客户端连接接受新的消息后调用 + /// + public class MsgReceivedEventArgs : EventArgs { public byte[] Received; public EndPoint Remote; } + /// + /// 客户端连接发送消息后回调 + /// + public class MsgSendEventArgs : EventArgs { public byte[] Received; public EndPoint Remote; } + + #endregion + +} diff --git a/UdpPlugWebsocket/Form1.Designer.cs b/UdpPlugWebsocket/Form1.Designer.cs new file mode 100644 index 0000000..84af41f --- /dev/null +++ b/UdpPlugWebsocket/Form1.Designer.cs @@ -0,0 +1,305 @@ +namespace UdpPlugWebsocket +{ + partial class Form1 + { + /// + /// 必需的设计器变量。 + /// + private System.ComponentModel.IContainer components = null; + + /// + /// 清理所有正在使用的资源。 + /// + /// 如果应释放托管资源,为 true;否则为 false。 + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows 窗体设计器生成的代码 + + /// + /// 设计器支持所需的方法 - 不要修改 + /// 使用代码编辑器修改此方法的内容。 + /// + private void InitializeComponent() + { + this.menuStrip1 = new System.Windows.Forms.MenuStrip(); + this.配置ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.系统配置ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.配置ToolStripMenuItem1 = new System.Windows.Forms.ToolStripMenuItem(); + this.系统配置ToolStripMenuItem1 = new System.Windows.Forms.ToolStripMenuItem(); + this.exitToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.显示调试信息ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.硬件ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.pan_Left = new System.Windows.Forms.Panel(); + this.btn_Panel = new System.Windows.Forms.Button(); + this.btn_Browser = new System.Windows.Forms.Button(); + this.btn_Device = new System.Windows.Forms.Button(); + this.statusStrip1 = new System.Windows.Forms.StatusStrip(); + this.slab_bottom = new System.Windows.Forms.ToolStripStatusLabel(); + this.pan_Fill = new System.Windows.Forms.Panel(); + this.splitter1 = new System.Windows.Forms.Splitter(); + this.tabControl1 = new System.Windows.Forms.TabControl(); + this.tab_Message = new System.Windows.Forms.TabPage(); + this.tab_Error = new System.Windows.Forms.TabPage(); + this.pan_Top = new System.Windows.Forms.Panel(); + this.menuStrip1.SuspendLayout(); + this.pan_Left.SuspendLayout(); + this.statusStrip1.SuspendLayout(); + this.pan_Fill.SuspendLayout(); + this.tabControl1.SuspendLayout(); + this.SuspendLayout(); + // + // menuStrip1 + // + this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.配置ToolStripMenuItem, + this.配置ToolStripMenuItem1, + this.硬件ToolStripMenuItem}); + this.menuStrip1.Location = new System.Drawing.Point(0, 0); + this.menuStrip1.Name = "menuStrip1"; + this.menuStrip1.Size = new System.Drawing.Size(1121, 29); + this.menuStrip1.TabIndex = 23; + this.menuStrip1.Text = "mnu_Main"; + // + // 配置ToolStripMenuItem + // + this.配置ToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.系统配置ToolStripMenuItem}); + this.配置ToolStripMenuItem.Name = "配置ToolStripMenuItem"; + this.配置ToolStripMenuItem.Size = new System.Drawing.Size(12, 20); + // + // 系统配置ToolStripMenuItem + // + this.系统配置ToolStripMenuItem.Name = "系统配置ToolStripMenuItem"; + this.系统配置ToolStripMenuItem.Size = new System.Drawing.Size(124, 22); + this.系统配置ToolStripMenuItem.Text = "系统配置"; + // + // 配置ToolStripMenuItem1 + // + this.配置ToolStripMenuItem1.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.系统配置ToolStripMenuItem1, + this.exitToolStripMenuItem, + this.显示调试信息ToolStripMenuItem}); + this.配置ToolStripMenuItem1.Font = new System.Drawing.Font("新宋体", 15.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); + this.配置ToolStripMenuItem1.Name = "配置ToolStripMenuItem1"; + this.配置ToolStripMenuItem1.Size = new System.Drawing.Size(66, 25); + this.配置ToolStripMenuItem1.Text = "配置"; + // + // 系统配置ToolStripMenuItem1 + // + this.系统配置ToolStripMenuItem1.Name = "系统配置ToolStripMenuItem1"; + this.系统配置ToolStripMenuItem1.Size = new System.Drawing.Size(158, 22); + this.系统配置ToolStripMenuItem1.Text = "系统配置"; + this.系统配置ToolStripMenuItem1.Click += new System.EventHandler(this.系统配置ToolStripMenuItem1_Click); + // + // exitToolStripMenuItem + // + this.exitToolStripMenuItem.Name = "exitToolStripMenuItem"; + this.exitToolStripMenuItem.Size = new System.Drawing.Size(158, 22); + this.exitToolStripMenuItem.Text = "退出"; + this.exitToolStripMenuItem.Click += new System.EventHandler(this.exitToolStripMenuItem_Click); + // + // 显示调试信息ToolStripMenuItem + // + this.显示调试信息ToolStripMenuItem.Name = "显示调试信息ToolStripMenuItem"; + this.显示调试信息ToolStripMenuItem.Size = new System.Drawing.Size(158, 22); + this.显示调试信息ToolStripMenuItem.Text = "显示调试信息"; + this.显示调试信息ToolStripMenuItem.Click += new System.EventHandler(this.显示调试信息ToolStripMenuItem_Click); + // + // 硬件ToolStripMenuItem + // + this.硬件ToolStripMenuItem.Font = new System.Drawing.Font("新宋体", 15.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); + this.硬件ToolStripMenuItem.Name = "硬件ToolStripMenuItem"; + this.硬件ToolStripMenuItem.Size = new System.Drawing.Size(66, 25); + this.硬件ToolStripMenuItem.Text = "帮助"; + this.硬件ToolStripMenuItem.Click += new System.EventHandler(this.硬件ToolStripMenuItem_Click); + // + // pan_Left + // + this.pan_Left.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; + this.pan_Left.Controls.Add(this.btn_Panel); + this.pan_Left.Controls.Add(this.btn_Browser); + this.pan_Left.Controls.Add(this.btn_Device); + this.pan_Left.Dock = System.Windows.Forms.DockStyle.Left; + this.pan_Left.Location = new System.Drawing.Point(0, 29); + this.pan_Left.Name = "pan_Left"; + this.pan_Left.Size = new System.Drawing.Size(263, 613); + this.pan_Left.TabIndex = 24; + // + // btn_Panel + // + this.btn_Panel.BackColor = System.Drawing.Color.White; + this.btn_Panel.Dock = System.Windows.Forms.DockStyle.Top; + this.btn_Panel.Font = new System.Drawing.Font("微软雅黑", 15.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); + this.btn_Panel.Location = new System.Drawing.Point(0, 100); + this.btn_Panel.Name = "btn_Panel"; + this.btn_Panel.Size = new System.Drawing.Size(261, 50); + this.btn_Panel.TabIndex = 8; + this.btn_Panel.Text = "交换面板"; + this.btn_Panel.UseVisualStyleBackColor = false; + this.btn_Panel.Click += new System.EventHandler(this.btn_Panel_Click); + // + // btn_Browser + // + this.btn_Browser.BackColor = System.Drawing.Color.White; + this.btn_Browser.Dock = System.Windows.Forms.DockStyle.Top; + this.btn_Browser.Font = new System.Drawing.Font("微软雅黑", 15.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); + this.btn_Browser.Location = new System.Drawing.Point(0, 50); + this.btn_Browser.Name = "btn_Browser"; + this.btn_Browser.Size = new System.Drawing.Size(261, 50); + this.btn_Browser.TabIndex = 6; + this.btn_Browser.Text = "控制页面"; + this.btn_Browser.UseVisualStyleBackColor = false; + this.btn_Browser.Click += new System.EventHandler(this.btn_Browser_Click); + // + // btn_Device + // + this.btn_Device.BackColor = System.Drawing.Color.White; + this.btn_Device.Dock = System.Windows.Forms.DockStyle.Top; + this.btn_Device.Font = new System.Drawing.Font("微软雅黑", 15.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); + this.btn_Device.Location = new System.Drawing.Point(0, 0); + this.btn_Device.Name = "btn_Device"; + this.btn_Device.Size = new System.Drawing.Size(261, 50); + this.btn_Device.TabIndex = 5; + this.btn_Device.Text = "终端设备"; + this.btn_Device.UseVisualStyleBackColor = false; + this.btn_Device.Click += new System.EventHandler(this.btn_Device_Click); + // + // statusStrip1 + // + this.statusStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.slab_bottom}); + this.statusStrip1.Location = new System.Drawing.Point(0, 642); + this.statusStrip1.Name = "statusStrip1"; + this.statusStrip1.Size = new System.Drawing.Size(1121, 22); + this.statusStrip1.TabIndex = 60; + this.statusStrip1.Text = "状态:"; + // + // slab_bottom + // + this.slab_bottom.AutoSize = false; + this.slab_bottom.BackColor = System.Drawing.SystemColors.Control; + this.slab_bottom.Name = "slab_bottom"; + this.slab_bottom.Size = new System.Drawing.Size(300, 17); + this.slab_bottom.Text = "状态:"; + this.slab_bottom.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // pan_Fill + // + this.pan_Fill.BackColor = System.Drawing.Color.White; + this.pan_Fill.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; + this.pan_Fill.Controls.Add(this.splitter1); + this.pan_Fill.Controls.Add(this.tabControl1); + this.pan_Fill.Controls.Add(this.pan_Top); + this.pan_Fill.Dock = System.Windows.Forms.DockStyle.Fill; + this.pan_Fill.Location = new System.Drawing.Point(263, 29); + this.pan_Fill.Name = "pan_Fill"; + this.pan_Fill.Size = new System.Drawing.Size(858, 613); + this.pan_Fill.TabIndex = 61; + // + // splitter1 + // + this.splitter1.BackColor = System.Drawing.SystemColors.ActiveBorder; + this.splitter1.Dock = System.Windows.Forms.DockStyle.Top; + this.splitter1.Location = new System.Drawing.Point(0, 507); + this.splitter1.Name = "splitter1"; + this.splitter1.Size = new System.Drawing.Size(856, 2); + this.splitter1.TabIndex = 8; + this.splitter1.TabStop = false; + // + // tabControl1 + // + this.tabControl1.Alignment = System.Windows.Forms.TabAlignment.Bottom; + this.tabControl1.Controls.Add(this.tab_Message); + this.tabControl1.Controls.Add(this.tab_Error); + this.tabControl1.Dock = System.Windows.Forms.DockStyle.Fill; + this.tabControl1.Location = new System.Drawing.Point(0, 507); + this.tabControl1.Name = "tabControl1"; + this.tabControl1.SelectedIndex = 0; + this.tabControl1.Size = new System.Drawing.Size(856, 104); + this.tabControl1.TabIndex = 7; + // + // tab_Message + // + this.tab_Message.Location = new System.Drawing.Point(4, 4); + this.tab_Message.Name = "tab_Message"; + this.tab_Message.Padding = new System.Windows.Forms.Padding(3); + this.tab_Message.Size = new System.Drawing.Size(848, 78); + this.tab_Message.TabIndex = 1; + this.tab_Message.Text = "Message"; + this.tab_Message.UseVisualStyleBackColor = true; + // + // tab_Error + // + this.tab_Error.Location = new System.Drawing.Point(4, 4); + this.tab_Error.Name = "tab_Error"; + this.tab_Error.Size = new System.Drawing.Size(848, 80); + this.tab_Error.TabIndex = 2; + this.tab_Error.Text = "Error"; + this.tab_Error.UseVisualStyleBackColor = true; + // + // pan_Top + // + this.pan_Top.BackColor = System.Drawing.Color.White; + this.pan_Top.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; + this.pan_Top.Dock = System.Windows.Forms.DockStyle.Top; + this.pan_Top.Location = new System.Drawing.Point(0, 0); + this.pan_Top.Name = "pan_Top"; + this.pan_Top.Size = new System.Drawing.Size(856, 507); + this.pan_Top.TabIndex = 4; + // + // Form1 + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(1121, 664); + this.Controls.Add(this.pan_Fill); + this.Controls.Add(this.pan_Left); + this.Controls.Add(this.menuStrip1); + this.Controls.Add(this.statusStrip1); + this.Name = "Form1"; + this.Text = "UdpPlugWebsocket 0.1"; + this.Load += new System.EventHandler(this.Form1_Load); + this.menuStrip1.ResumeLayout(false); + this.menuStrip1.PerformLayout(); + this.pan_Left.ResumeLayout(false); + this.statusStrip1.ResumeLayout(false); + this.statusStrip1.PerformLayout(); + this.pan_Fill.ResumeLayout(false); + this.tabControl1.ResumeLayout(false); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.MenuStrip menuStrip1; + private System.Windows.Forms.ToolStripMenuItem 配置ToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem 系统配置ToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem 配置ToolStripMenuItem1; + private System.Windows.Forms.ToolStripMenuItem 系统配置ToolStripMenuItem1; + private System.Windows.Forms.ToolStripMenuItem exitToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem 硬件ToolStripMenuItem; + private System.Windows.Forms.Panel pan_Left; + private System.Windows.Forms.Button btn_Panel; + private System.Windows.Forms.Button btn_Browser; + private System.Windows.Forms.Button btn_Device; + private System.Windows.Forms.StatusStrip statusStrip1; + private System.Windows.Forms.ToolStripStatusLabel slab_bottom; + private System.Windows.Forms.Panel pan_Fill; + private System.Windows.Forms.Splitter splitter1; + private System.Windows.Forms.TabControl tabControl1; + private System.Windows.Forms.TabPage tab_Error; + private System.Windows.Forms.TabPage tab_Message; + public System.Windows.Forms.Panel pan_Top; + private System.Windows.Forms.ToolStripMenuItem 显示调试信息ToolStripMenuItem; + } +} + diff --git a/UdpPlugWebsocket/Form1.cs b/UdpPlugWebsocket/Form1.cs new file mode 100644 index 0000000..ea4d9e0 --- /dev/null +++ b/UdpPlugWebsocket/Form1.cs @@ -0,0 +1,176 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace UdpPlugWebsocket +{ + public partial class Form1 : Form + { + //所有窗体的容器 + List
forms = new List(); + + //初始化配置窗体 + SetupForm setup = new SetupForm(); + + //初始化所有窗体对象,每个窗体对应一个功能模组 + private void Init_Forms() + { + try + { + //添加终端设备窗体 + forms.Add(Device.Instance); + //添加浏览器窗体 + forms.Add(Browser.Instance); + //添加交换面板窗体 + forms.Add(Panel.Instance); + + //配置窗体为子窗体,并添加到主窗体上 + foreach (Form frm in forms) + { + frm.TopLevel = false; + frm.FormBorderStyle = FormBorderStyle.None; + frm.Dock = DockStyle.Fill; + pan_Top.Controls.Add(frm); + } + + tab_Message.Controls.Add(MessageForm.Instance); + MessageForm.Instance.Show(); + + tab_Error.Controls.Add(ErrorForm.Instance); + ErrorForm.Instance.Show(); + + //连接模块与系统终端窗口 + Device.Instance.HandleMessage += new Action(MessageForm.Log); + Device.Instance.HandleError += new Action(ErrorForm.Log); + + Browser.Instance.HandleMessage += new Action(MessageForm.Log); + Browser.Instance.HandleError += new Action(ErrorForm.Log); + //连接Pannel数据输入端 Device->Panel + Device.Instance.server.HandleRecMsg += new Action((bytes, conn, server) => + { + Panel.Instance.sw.SendFromUDP(bytes, conn.Tag.ToString()); + }); + //连接Pannel数据输入端 Browser->Panel + Browser.Instance.HandleWebSocketRecMsg += new Action((bytes, context) => + { + Panel.Instance.sw.SendFromWebSocket(bytes, context.UserEndPoint.ToString()); + }); + //连接Pannel数据输出端 Panel->Device + Panel.Instance.sw.HandleUDPSendMsg += new Action((bytes, endpointString) => + { + Device.Instance.server.Send(endpointString, bytes); + }); + //连接Panel数据输出端 Panel->Browser + Panel.Instance.sw.HandleWebSocketSendMsg += new Action((bytes, endpointString)=> + { + Browser.Instance.Send(endpointString, bytes); + }); + + //刷新菜单状态 + 显示调试信息ToolStripMenuItem.Checked = SetupForm.cfg.EnableScreenLog; + } + catch (Exception e) + { + MessageBox.Show(e.StackTrace,"系统模块初始化错误"); + + } + } + + //使所有子窗体不可见 + private void Invislble_Forms() + { + foreach (Form frm in forms) + { + frm.Visible = false; + } + } + + //根据类名获得窗体对象 + private Form Get_Form(string className) + { + foreach (Form frm in forms) + { + if (frm.GetType().Name == className) + { + return frm; + } + } + return null; + } + private void ShowModule(string moduleName) + { + try + { + if (Get_Form(moduleName) != null) Get_Form(moduleName).Visible = true; + } + catch (Exception e) + { + MessageBox.Show(e.StackTrace, "系统模块初始化错误"); + } + } + public Form1() + { + InitializeComponent(); + } + + private void Form1_Load(object sender, EventArgs e) + { + + Init_Forms(); + ShowModule("Device"); + } + + private void btn_Device_Click(object sender, EventArgs e) + { + Invislble_Forms(); + ShowModule("Device"); + + } + + private void btn_Browser_Click(object sender, EventArgs e) + { + { + Invislble_Forms(); + ShowModule("Browser"); + } + } + + private void btn_Panel_Click(object sender, EventArgs e) + { + Invislble_Forms(); + ShowModule("Panel"); + } + + private void 系统配置ToolStripMenuItem1_Click(object sender, EventArgs e) + { + + setup.ShowDialog(); + } + + private void exitToolStripMenuItem_Click(object sender, EventArgs e) + { + System.Environment.Exit(0); + } + + private void 硬件ToolStripMenuItem_Click(object sender, EventArgs e) + { + MessageBox.Show("UPW 0.1 \n Tks Song"); + } + + private void btn_LogScreen_Click(object sender, EventArgs e) + { + } + + private void 显示调试信息ToolStripMenuItem_Click(object sender, EventArgs e) + { + SetupForm.cfg.EnableScreenLog = !SetupForm.cfg.EnableScreenLog; + 显示调试信息ToolStripMenuItem.Checked = SetupForm.cfg.EnableScreenLog; + } + } +} diff --git a/UdpPlugWebsocket/Form1.resx b/UdpPlugWebsocket/Form1.resx new file mode 100644 index 0000000..46c1971 --- /dev/null +++ b/UdpPlugWebsocket/Form1.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + + 259, 17 + + + 55 + + \ No newline at end of file diff --git a/UdpPlugWebsocket/Message/ErrorForm.Designer.cs b/UdpPlugWebsocket/Message/ErrorForm.Designer.cs new file mode 100644 index 0000000..6dcf4b0 --- /dev/null +++ b/UdpPlugWebsocket/Message/ErrorForm.Designer.cs @@ -0,0 +1,60 @@ +namespace UdpPlugWebsocket +{ + partial class ErrorForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.richTextBox1 = new System.Windows.Forms.RichTextBox(); + this.SuspendLayout(); + // + // richTextBox1 + // + this.richTextBox1.Dock = System.Windows.Forms.DockStyle.Fill; + this.richTextBox1.Location = new System.Drawing.Point(0, 0); + this.richTextBox1.Name = "richTextBox1"; + this.richTextBox1.Size = new System.Drawing.Size(463, 400); + this.richTextBox1.TabIndex = 0; + this.richTextBox1.Text = ""; + // + // Message + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(463, 400); + this.Controls.Add(this.richTextBox1); + this.Name = "Message"; + this.Text = "Message"; + this.Load += new System.EventHandler(this.Message_Load); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.RichTextBox richTextBox1; + } +} \ No newline at end of file diff --git a/UdpPlugWebsocket/Message/ErrorForm.cs b/UdpPlugWebsocket/Message/ErrorForm.cs new file mode 100644 index 0000000..0c19462 --- /dev/null +++ b/UdpPlugWebsocket/Message/ErrorForm.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Windows.Forms; + +namespace UdpPlugWebsocket +{ + public partial class ErrorForm : Form + { + private ErrorForm() + { + //窗体子窗口化 + this.TopLevel = false; + this.FormBorderStyle = FormBorderStyle.None; + this.Dock = DockStyle.Fill; + InitializeComponent(); + Instance = this; + this.FormClosed += Message_FormClosed; + + + } + + private void Message_FormClosed(object sender, FormClosedEventArgs e) + { + Instance = null; + } + + private static ErrorForm _instance; + + public static ErrorForm Instance + { + get + { + if (_instance == null) + _instance = new ErrorForm(); + return _instance; + } + private set { _instance = value; } + } + + String s_output = ""; + public void SetOutput(string text) + { + text = DateTime.Now.ToLongDateString() +" "+DateTime.Now.ToLongTimeString()+ " " + text; + Action action = () => + { + s_output = s_output + text + "\r"; + + if ((s_output.Length)>5000) + { + s_output = s_output.Substring(s_output.Length - 5000, 5000); + } + //滚到最后 + this.richTextBox1.Text = s_output; + this.richTextBox1.Select(richTextBox1.TextLength, 0); + //this.richTextBox1.Focus(); + this.richTextBox1.ScrollToCaret(); + + }; + + this.richTextBox1.Invoke(action); + } + + public static void Log(string text) + { + if (_instance != null) + { + _instance.SetOutput(text); + } + } + + private void Message_Load(object sender, EventArgs e) + { + + } + } +} diff --git a/UdpPlugWebsocket/Message/ErrorForm.resx b/UdpPlugWebsocket/Message/ErrorForm.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/UdpPlugWebsocket/Message/ErrorForm.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/UdpPlugWebsocket/Message/MessageForm.Designer.cs b/UdpPlugWebsocket/Message/MessageForm.Designer.cs new file mode 100644 index 0000000..9c382fa --- /dev/null +++ b/UdpPlugWebsocket/Message/MessageForm.Designer.cs @@ -0,0 +1,60 @@ +namespace UdpPlugWebsocket +{ + partial class MessageForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.richTextBox1 = new System.Windows.Forms.RichTextBox(); + this.SuspendLayout(); + // + // richTextBox1 + // + this.richTextBox1.Dock = System.Windows.Forms.DockStyle.Fill; + this.richTextBox1.Location = new System.Drawing.Point(0, 0); + this.richTextBox1.Name = "richTextBox1"; + this.richTextBox1.Size = new System.Drawing.Size(463, 400); + this.richTextBox1.TabIndex = 0; + this.richTextBox1.Text = ""; + this.richTextBox1.TextChanged += new System.EventHandler(this.richTextBox1_TextChanged); + // + // MessageForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(this.richTextBox1); + this.Name = "MessageForm"; + this.Size = new System.Drawing.Size(463, 400); + this.Load += new System.EventHandler(this.Log_Load); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.RichTextBox richTextBox1; + } +} \ No newline at end of file diff --git a/UdpPlugWebsocket/Message/MessageForm.cs b/UdpPlugWebsocket/Message/MessageForm.cs new file mode 100644 index 0000000..a929a8d --- /dev/null +++ b/UdpPlugWebsocket/Message/MessageForm.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Windows.Forms; + +namespace UdpPlugWebsocket +{ + public partial class MessageForm : Form + { + private MessageForm() + { + //打开添加用户面板 + this.TopLevel = false; + this.FormBorderStyle = FormBorderStyle.None; + this.Dock = DockStyle.Fill; + InitializeComponent(); + Instance = this; + this.FormClosed += Log_FormClosed; + + } + private void Log_FormClosed(object sender, FormClosedEventArgs e) + { + Instance = null; + } + + private static MessageForm _instance; + + public static MessageForm Instance + { + get + { + if (_instance == null) + _instance = new MessageForm(); + return _instance; + } + private set { _instance = value; } + } + + String s_output = ""; + public void SetOutput(string text) + { + //决定是否屏显 + if (SetupForm.cfg.EnableScreenLog == false) return; + + text = DateTime.Now.ToLongDateString() +" "+DateTime.Now.ToLongTimeString()+ " " + text; + this.Invoke(new Action(() => + { + s_output = s_output + text.Replace("\0", "") + "\r"; + + if ((s_output.Length) > 5000) + { + s_output = s_output.Substring(s_output.Length - 5000, 5000); + } + //滚到最后 + this.richTextBox1.Text = s_output; + this.richTextBox1.Select(richTextBox1.TextLength, 0); + //this.richTextBox1.Focus(); + this.richTextBox1.ScrollToCaret(); + + })); + } + + public static void Log(string text) + { + if (_instance != null) + { + _instance.SetOutput(text); + } + } + + private void Log_Load(object sender, EventArgs e) + { + + } + + private void richTextBox1_TextChanged(object sender, EventArgs e) + { + + } + } +} diff --git a/UdpPlugWebsocket/Message/MessageForm.resx b/UdpPlugWebsocket/Message/MessageForm.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/UdpPlugWebsocket/Message/MessageForm.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/UdpPlugWebsocket/Panel/Node.cs b/UdpPlugWebsocket/Panel/Node.cs new file mode 100644 index 0000000..018f257 --- /dev/null +++ b/UdpPlugWebsocket/Panel/Node.cs @@ -0,0 +1,269 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace UdpPlugWebsocket +{ + public class WSConnection + { + //Websocket连接生存时间 + private static int LIFELIMIT = SetupForm.cfg.NODETTL; + public string EndpointString; + public int ttl; + //定义Timer类 + private System.Timers.Timer timer; + public WSConnection() + { + System.Timers.Timer timer = new System.Timers.Timer(); + ttl = LIFELIMIT; + InitTimer(); + IsActive = true; + } + #region 内部函数 + /// + /// 初始化Timer控件 + /// + private void InitTimer() + { + //设置定时间隔(毫秒为单位) + int interval = 1000; + timer = new System.Timers.Timer(interval); + //设置执行一次(false)还是一直执行(true) + timer.AutoReset = true; + //设置是否执行System.Timers.Timer.Elapsed事件 + timer.Enabled = true; + //绑定Elapsed事件 + timer.Elapsed += new System.Timers.ElapsedEventHandler(TimerUp); + } + /// + /// Timer类执行定时到点事件 + /// + /// + /// + private void TimerUp(object sender, System.Timers.ElapsedEventArgs e) + { + ttl -= (int)((System.Timers.Timer)(sender)).Interval; + if (ttl <= 0) + { + IsActive = false; + HandleWebsocketClosed?.Invoke(this); + timer.Dispose(); + } + } + public bool IsActive { get; set; } + + public void Reactive() + { + ttl = LIFELIMIT; + } + #endregion + #region 事件处理 + /// + /// NODE关闭后回调 + /// + public Action HandleWebsocketClosed { get; set; } + #endregion + } + public class UDPConnection + { + //UDP连接生存时间 + private static int LIFELIMIT = SetupForm.cfg.NODETTL; + public string EndpointString; + public int ttl; + //定义Timer类 + private System.Timers.Timer timer; + public UDPConnection() + { + System.Timers.Timer timer = new System.Timers.Timer(); + ttl = LIFELIMIT; + InitTimer(); + IsActive = true; + } + #region 内部函数 + /// + /// 初始化Timer控件 + /// + private void InitTimer() + { + //设置定时间隔(毫秒为单位) + int interval = 1000; + timer = new System.Timers.Timer(interval); + //设置执行一次(false)还是一直执行(true) + timer.AutoReset = true; + //设置是否执行System.Timers.Timer.Elapsed事件 + timer.Enabled = true; + //绑定Elapsed事件 + timer.Elapsed += new System.Timers.ElapsedEventHandler(TimerUp); + } + + /// + /// Timer类执行定时到点事件 + /// + /// + /// + private void TimerUp(object sender, System.Timers.ElapsedEventArgs e) + { + ttl -= (int)((System.Timers.Timer)(sender)).Interval; + if (ttl <= 0) + { + IsActive = false; + HandleUDPClosed?.Invoke(this); + timer.Dispose(); + } + } + public bool IsActive { get; set; } + + public void Reactive() + { + ttl = LIFELIMIT; + } + #endregion + #region 事件处理 + + /// + /// NODE关闭后回调 + /// + public Action HandleUDPClosed { get; set; } + #endregion + } + + //交换模块的群组 + public class NODE + { + //NODE连接生存时间 + private static int LIFELIMIT = SetupForm.cfg.NODETTL; + //剩余生存时间 + public int ttl; + //节点的UID + public string ID; + + public List WebsocketConnections; + public List UDPConnections; + + //定义Timer类 + private System.Timers.Timer timer; + + + public NODE(string ID) + { + WebsocketConnections = new List(); + UDPConnections = new List(); + System.Timers.Timer timer = new System.Timers.Timer(); + ttl = LIFELIMIT; + InitTimer(); + IsActive = true; + HandleNodeCreated?.Invoke(this); + + } + #region 内部函数 + /// + /// 初始化Timer控件 + /// + private void InitTimer() + { + //设置定时间隔(毫秒为单位) + int interval = 1000; + timer = new System.Timers.Timer(interval); + //设置执行一次(false)还是一直执行(true) + timer.AutoReset = true; + //设置是否执行System.Timers.Timer.Elapsed事件 + timer.Enabled = true; + //绑定Elapsed事件 + timer.Elapsed += new System.Timers.ElapsedEventHandler(TimerUp); + } + /// + /// Timer类执行定时到点事件 + /// + /// + /// + private void TimerUp(object sender, System.Timers.ElapsedEventArgs e) + { + ttl -= (int)((System.Timers.Timer)(sender)).Interval; + if (ttl <= 0) + { + IsActive = false; + HandleNodeClosed?.Invoke(this); + timer.Dispose(); + } + } + #endregion + + # region 外部函数 + public void AddSource(string endpoint, Switch.Source source) + { + if (source == Switch.Source.UDPPort) AddUDPConnection(endpoint); + if (source == Switch.Source.WebSocket) AddWebsocketConnection(endpoint); + } + + /// + /// 添加一个新的UDP连接 + /// + /// + public void AddUDPConnection(string endpoint) + { + UDPConnection udp = new UDPConnection(); + udp.EndpointString = endpoint; + //节点超时则自动从连接中移除 + udp.HandleUDPClosed = new Action((conn => { + UDPConnections.Remove(conn); HandleUDPRemoved?.Invoke(conn.EndpointString); + })); + UDPConnections.Add(udp); + HandleUDPAdded?.Invoke(endpoint); + } + /// + /// 添加一个新的Websocket连接 + /// + /// + public void AddWebsocketConnection(string endpoint) + { + WSConnection web = new WSConnection(); + web.EndpointString = endpoint; + //节点超时则自动从连接中移除 + web.HandleWebsocketClosed = new Action((conn => { + WebsocketConnections.Remove(conn); HandleWebSocketRemoved?.Invoke(conn.EndpointString); + })); + WebsocketConnections.Add(web); + HandleWebSocketAdded?.Invoke(endpoint); + } + public bool IsActive { get; set; } + + public void Reactive() + { + ttl = LIFELIMIT; + } + #endregion + + #region 事件处理 + /// + /// NODE关闭后回调 + /// + public Action HandleNodeClosed { get; set; } + /// + /// NODE建立后回调 + /// + public Action HandleNodeCreated { get; set; } + /// + /// 新的Websocket连接建立后回调 + /// + public Action HandleWebSocketAdded { get; set; } + /// + /// 新的Websocket连接断开后回调 + /// + public Action HandleWebSocketRemoved { get; set; } + /// + /// 新的UDP连接建立后回调 + /// + public Action HandleUDPAdded { get; set; } + /// + /// 新的UDP连接断开后回调 + /// + public Action HandleUDPRemoved { get; set; } + /// + /// 异常处理程序 + /// + public Action HandleException { get; set; } + #endregion + } +} diff --git a/UdpPlugWebsocket/Panel/Panel.Designer.cs b/UdpPlugWebsocket/Panel/Panel.Designer.cs new file mode 100644 index 0000000..c6abafb --- /dev/null +++ b/UdpPlugWebsocket/Panel/Panel.Designer.cs @@ -0,0 +1,149 @@ +namespace UdpPlugWebsocket +{ + partial class Panel + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.groupBox2 = new System.Windows.Forms.GroupBox(); + this.richTextBox1 = new System.Windows.Forms.RichTextBox(); + this.dataGridView1 = new System.Windows.Forms.DataGridView(); + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.dgv_nodes = new System.Windows.Forms.DataGridView(); + this.splitter1 = new System.Windows.Forms.Splitter(); + this.groupBox2.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).BeginInit(); + this.groupBox1.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.dgv_nodes)).BeginInit(); + this.SuspendLayout(); + // + // groupBox2 + // + this.groupBox2.Controls.Add(this.richTextBox1); + this.groupBox2.Controls.Add(this.dataGridView1); + this.groupBox2.Dock = System.Windows.Forms.DockStyle.Bottom; + this.groupBox2.Font = new System.Drawing.Font("微软雅黑", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); + this.groupBox2.Location = new System.Drawing.Point(0, 394); + this.groupBox2.Name = "groupBox2"; + this.groupBox2.Size = new System.Drawing.Size(685, 222); + this.groupBox2.TabIndex = 5; + this.groupBox2.TabStop = false; + this.groupBox2.Text = "会话:"; + // + // richTextBox1 + // + this.richTextBox1.Dock = System.Windows.Forms.DockStyle.Fill; + this.richTextBox1.Location = new System.Drawing.Point(3, 25); + this.richTextBox1.Name = "richTextBox1"; + this.richTextBox1.Size = new System.Drawing.Size(679, 194); + this.richTextBox1.TabIndex = 5; + this.richTextBox1.Text = ""; + // + // dataGridView1 + // + this.dataGridView1.AllowUserToAddRows = false; + this.dataGridView1.BackgroundColor = System.Drawing.Color.WhiteSmoke; + this.dataGridView1.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; + this.dataGridView1.Dock = System.Windows.Forms.DockStyle.Fill; + this.dataGridView1.GridColor = System.Drawing.Color.Snow; + this.dataGridView1.Location = new System.Drawing.Point(3, 25); + this.dataGridView1.MultiSelect = false; + this.dataGridView1.Name = "dataGridView1"; + this.dataGridView1.ReadOnly = true; + this.dataGridView1.RowHeadersVisible = false; + this.dataGridView1.RowHeadersWidth = 4; + this.dataGridView1.RowTemplate.Height = 23; + this.dataGridView1.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect; + this.dataGridView1.Size = new System.Drawing.Size(679, 194); + this.dataGridView1.TabIndex = 0; + // + // groupBox1 + // + this.groupBox1.Controls.Add(this.dgv_nodes); + this.groupBox1.Dock = System.Windows.Forms.DockStyle.Fill; + this.groupBox1.Font = new System.Drawing.Font("微软雅黑", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); + this.groupBox1.Location = new System.Drawing.Point(0, 0); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.Size = new System.Drawing.Size(685, 384); + this.groupBox1.TabIndex = 3; + this.groupBox1.TabStop = false; + this.groupBox1.Text = "节点:"; + // + // dgv_nodes + // + this.dgv_nodes.AllowUserToAddRows = false; + this.dgv_nodes.BackgroundColor = System.Drawing.Color.WhiteSmoke; + this.dgv_nodes.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; + this.dgv_nodes.Dock = System.Windows.Forms.DockStyle.Fill; + this.dgv_nodes.GridColor = System.Drawing.Color.Snow; + this.dgv_nodes.Location = new System.Drawing.Point(3, 25); + this.dgv_nodes.MultiSelect = false; + this.dgv_nodes.Name = "dgv_nodes"; + this.dgv_nodes.ReadOnly = true; + this.dgv_nodes.RowHeadersVisible = false; + this.dgv_nodes.RowHeadersWidth = 4; + this.dgv_nodes.RowTemplate.Height = 23; + this.dgv_nodes.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect; + this.dgv_nodes.Size = new System.Drawing.Size(679, 356); + this.dgv_nodes.TabIndex = 1; + // + // splitter1 + // + this.splitter1.Dock = System.Windows.Forms.DockStyle.Bottom; + this.splitter1.Location = new System.Drawing.Point(0, 384); + this.splitter1.Name = "splitter1"; + this.splitter1.Size = new System.Drawing.Size(685, 10); + this.splitter1.TabIndex = 7; + this.splitter1.TabStop = false; + // + // Panel + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(685, 616); + this.Controls.Add(this.groupBox1); + this.Controls.Add(this.splitter1); + this.Controls.Add(this.groupBox2); + this.Name = "Panel"; + this.Text = "Panel"; + this.Load += new System.EventHandler(this.Panel_Load); + this.groupBox2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).EndInit(); + this.groupBox1.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.dgv_nodes)).EndInit(); + this.ResumeLayout(false); + + } + + #endregion + private System.Windows.Forms.GroupBox groupBox2; + private System.Windows.Forms.RichTextBox richTextBox1; + private System.Windows.Forms.DataGridView dataGridView1; + private System.Windows.Forms.GroupBox groupBox1; + private System.Windows.Forms.Splitter splitter1; + private System.Windows.Forms.DataGridView dgv_nodes; + } +} \ No newline at end of file diff --git a/UdpPlugWebsocket/Panel/Panel.cs b/UdpPlugWebsocket/Panel/Panel.cs new file mode 100644 index 0000000..111aed5 --- /dev/null +++ b/UdpPlugWebsocket/Panel/Panel.cs @@ -0,0 +1,142 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using Miuser.NUDP.Sockets; + +namespace UdpPlugWebsocket +{ + public partial class Panel : Form + { + //交换机的实例 + public Switch sw; + public Panel() + { + InitializeComponent(); + sw = new Switch(); + //处理新的结点连接后的事件 + sw.HandleNodeCreated = new Action((theSW, theNode) => + { + //RefreshDataGrid(theSW); + }); + //处理节点关闭后的事件 + sw.HandleNodeClosed = new Action((theSW, theNode) => + { + //RefreshDataGrid(theSW); + }); + //处理节点状态变化后的事件 + sw.HandleNodeChanged = new Action((theSW, theNode) => + { + RefreshDataGrid(theSW); + }); + //节点接收UDP消息后的事件 + sw.HandleUDPRsvMsg = new Action((bytes, endpointString) => + { + SetOutput("(UDP " + endpointString + ")<=" + System.Text.Encoding.Default.GetString(bytes)); + }); + //节点收到Websocket消息后的事件 + sw.HandleWebsocketRsvMsg = new Action((bytes, endpointString) => + { + SetOutput("(WebSocket " + endpointString + ")<=" + System.Text.Encoding.Default.GetString(bytes)); + }); + + //节点发送UDP消息后的事件 + sw.HandleUDPSendMsg = new Action((bytes, endpointString) => + { + SetOutput("(UDP " + endpointString + ")=>" + System.Text.Encoding.Default.GetString(bytes)); + }); + //节点发送Websocket消息后的事件 + sw.HandleWebSocketSendMsg = new Action((bytes, endpointString) => + { + SetOutput("(WebSocket " + endpointString + ")=>" + System.Text.Encoding.Default.GetString(bytes)); + }); + + + } + private static Panel _instance; + public static Panel Instance + { + get + { + if (_instance == null) + _instance = new Panel(); + return _instance; + } + private set { _instance = value; } + } + + private void Panel_Load(object sender, EventArgs e) + { + + + + } + private void RefreshDataGrid(Switch theSw) + { + DataTable dt = GetNodeTable(theSw); + lock (dt) + { + this.Invoke(new Action(() => + { + dgv_nodes.DataSource = dt; + dgv_nodes.Columns[0].Width = 150; + dgv_nodes.Columns[1].Width = 100; + dgv_nodes.Columns[2].Width = 100; + })); + } + } + private DataTable GetNodeTable(Switch theSw) + { + DataTable dt = new DataTable(); + dt.Columns.Add("ID"); + dt.Columns.Add("UDP "); + dt.Columns.Add("Websocket"); + dt.Columns.Add("Lifetime"); + + IEnumerable nodes = theSw.GetNodeList(); + lock (nodes) + { + foreach (NODE node in theSw.GetNodeList().ToArray()) + { + DataRow dr = dt.NewRow(); + dr[0] = node.ID; + dr[1] = node.UDPConnections==null? 0:node.UDPConnections.Count; + dr[2] = node.WebsocketConnections == null ? 0 : node.WebsocketConnections.Count; + dr[3] = node.ttl; + dt.Rows.Add(dr); + } + } + + return dt; + } + String s_output = ""; + public void SetOutput(string text) + { + //决定是否屏显 + if (SetupForm.cfg.EnableScreenLog == false) return; + text = DateTime.Now.ToLongDateString() + " " + DateTime.Now.ToLongTimeString() + " |" + text; + this.Invoke(new Action(() => + { + s_output = s_output + text.Replace("\0", "") + "\r"; + + if ((s_output.Length) > 5000) + { + s_output = s_output.Substring(s_output.Length - 5000, 5000); + } + //滚到最后 + this.richTextBox1.Text = s_output; + this.richTextBox1.Select(richTextBox1.TextLength, 0); + //this.richTextBox1.Focus(); + this.richTextBox1.ScrollToCaret(); + + })); + } + + } + +} diff --git a/UdpPlugWebsocket/Panel/Panel.resx b/UdpPlugWebsocket/Panel/Panel.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/UdpPlugWebsocket/Panel/Panel.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/UdpPlugWebsocket/Panel/Switch.cs b/UdpPlugWebsocket/Panel/Switch.cs new file mode 100644 index 0000000..eade117 --- /dev/null +++ b/UdpPlugWebsocket/Panel/Switch.cs @@ -0,0 +1,389 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Threading; +using Miuser.NUDP.Sockets; + +namespace UdpPlugWebsocket +{ + public class Switch + { + //存储以ID为 + public List nodes; + + public Switch() + { + nodes = new List(); + } + /// + /// 接收来自UDP的数据并进行转发 + /// + /// + /// + public void SendFromUDP(byte[] bytes, string EndpointString) + { + HandleUDPRsvMsg?.Invoke(bytes, EndpointString); + string content = System.Text.Encoding.Default.GetString(bytes); + string ID = ""; + if (content.Length >= 19) + { + ID = content.Substring(9, 10); + NODE node = GetTheNode((x => + { + var Id = (string)x.ID; + return Id == ID; + })); + //如果不存在则新建一个NODE + if (node == null) + { + node = new NODE(ID) + { + //连接Node消息到Switch上 + HandleNodeClosed = HandleNodeClosed == null ? null : new Action((nod) => { HandleNodeClosed(this, nod); }), + HandleNodeCreated = HandleNodeCreated == null ? null : new Action((nod) => { HandleNodeCreated(this, nod); }), + HandleUDPAdded = new Action(target => + { + HandleNodeChanged?.Invoke(this, node); + }), + HandleUDPRemoved = new Action(target => + { + HandleNodeChanged?.Invoke(this, node); + }), + HandleWebSocketAdded = new Action(target => + { + HandleNodeChanged?.Invoke(this, node); + }), + HandleWebSocketRemoved= new Action(target => + { + HandleNodeChanged?.Invoke(this, node); + }), + HandleException=new Action(ex=> + { + HandleException(ex); + }) + }; + node.ID = ID; + AddNode(node); + } + + //判断连接是否为新连接,如果是则增加消息源 + if (node.UDPConnections.Where(x => { return EndpointString == x.EndpointString; }).FirstOrDefault() == null) + { + node.AddUDPConnection(EndpointString); + } + else + { + node.UDPConnections.Where(x => { return EndpointString == x.EndpointString; }).FirstOrDefault().Reactive(); + } + node.Reactive(); + //分发消息给所有在线的Websocket端口 + lock (node.WebsocketConnections) + { + foreach (WSConnection ws in node.WebsocketConnections.ToArray()) + { + HandleWebSocketSendMsg?.Invoke(bytes, ws.EndpointString); + } + } + //分发消息给所有在线的UDP端口 + lock (node.UDPConnections) + { + foreach (UDPConnection udp in node.UDPConnections.ToArray()) + { + HandleUDPSendMsg?.Invoke(bytes, udp.EndpointString); + } + } + } + + } + + public void SendFromWebSocket(byte[] bytes, string EndpointString) + { + HandleWebsocketRsvMsg?.Invoke(bytes, EndpointString); + string content = System.Text.Encoding.Default.GetString(bytes); + string ID = ""; + if (content.Length >= 19) + { + ID = content.Substring(9, 10); + NODE node = GetTheNode((x => + { + var Id = (string)x.ID; + return Id == ID; + })); + //如果不存在则新建一个NODE + if (node == null) + { + node = new NODE(ID) + { + //连接Node消息到Switch上 + HandleNodeClosed = HandleNodeClosed == null ? null : new Action((nod) => { HandleNodeClosed(this, nod); }), + HandleNodeCreated = HandleNodeCreated == null ? null : new Action((nod) => { HandleNodeCreated(this, nod); }), + HandleUDPAdded = new Action(target => + { + HandleNodeChanged?.Invoke(this, node); + }), + HandleUDPRemoved = new Action(target => + { + HandleNodeChanged?.Invoke(this, node); + }), + HandleWebSocketAdded = new Action(target => + { + HandleNodeChanged?.Invoke(this, node); + }), + HandleWebSocketRemoved= new Action(target => + { + HandleNodeChanged?.Invoke(this, node); + }), + HandleException=new Action(ex=> + { + HandleException(ex); + }) + + }; + node.ID = ID; + AddNode(node); + + } + + //判断连接是否为新连接,如果是则增加消息源 + if (node.WebsocketConnections.Where(x => { return EndpointString == x.EndpointString; }).FirstOrDefault() == null) + { + node.AddWebsocketConnection(EndpointString); + }else + { + node.WebsocketConnections.Where(x => { return EndpointString == x.EndpointString; }).FirstOrDefault().Reactive(); + } + node.Reactive(); + + //分发消息给所有在线的Websocket端口 + if (node.WebsocketConnections != null) + { + lock (node.WebsocketConnections) + { + foreach (WSConnection ws in node.WebsocketConnections.ToArray()) + { + HandleWebSocketSendMsg?.Invoke(bytes, ws.EndpointString); + } + } + } + //分发消息给所有在线的UDP端口 + if (node.UDPConnections != null) + { + lock (node.UDPConnections) + { + foreach (UDPConnection udp in node.UDPConnections.ToArray()) + { + HandleUDPSendMsg?.Invoke(bytes, udp.EndpointString); + } + } + } + } + + } + + #region 内部函数 + private void RemoveNode(NODE node) + { + RWLock_ClientList.EnterWriteLock(); + try + { + nodes.Remove(node); + } + finally + { + RWLock_ClientList.ExitWriteLock(); + } + } + /// + /// 删除不活动的客户端连接 + /// + /// 指定的客户端连接 + private void RemoveInactiveNodes() + { + List nodes = new List(); + foreach (NODE node in nodes.ToArray()) + { + if (node.IsActive == false) + { + nodes.Add(node); + } + } + foreach (NODE node in nodes.ToArray()) + { + RemoveInactiveNode(node); + } + } + /// + /// 检测该客户端,如果不活动则删除连接 + /// + /// 指定的客户端连接 + private void RemoveInactiveNode(NODE node) + { + if (node.IsActive == false) + { + RemoveNode(node); + } + } + + /// + /// 当某连接不活动超时激活该函数 + /// + private void HandleConnClientClose(NODE node) + { + RemoveNode(node); + } + + #endregion + + + /// + /// 维护客户端列表的读写锁 + /// + public ReaderWriterLockSlim RWLock_ClientList { get; } = new ReaderWriterLockSlim(); + + /// + /// 关闭指定客户端连接 + /// + /// 指定的客户端连接 + public void CloseNode(NODE node) + { + RemoveNode(node); + //调用外部回调函数通知连接被关闭 + HandleNodeClosed?.Invoke(this,node); + } + + /// + /// 添加客户端连接 + /// + /// 需要添加的客户端连接 + public enum Source {UDPPort,WebSocket } + public void AddNode(NODE node) + { + RWLock_ClientList.EnterWriteLock(); + + try + { + nodes.Add(node); + node.HandleNodeClosed = new Action((id => { nodes.Remove(id); })); + + } + finally + { + RWLock_ClientList.ExitWriteLock(); + //调用外部回调函数通知新连接建立 + HandleNodeCreated?.Invoke(this, node); + } + } + + /// + /// 通过条件获取客户端连接列表 + /// + /// 筛选条件 + /// + public IEnumerable GetNodeList(Func predicate) + { + + //RemoveInactiveNodes(); + IEnumerable ret; + lock (nodes) + { + ret = nodes.Where(predicate); + } + return ret; + } + + /// + /// 获取所有客户端连接列表 + /// + /// + public List GetNodeList() + { + //RemoveInactiveNodes(); + return nodes; + } + + /// + /// 寻找特定条件的客户端连接 + /// + /// 筛选条件 + /// + public NODE GetTheNode(Func predicate) + { + NODE node; + lock (nodes) + { + node = nodes.Where(predicate).FirstOrDefault(); + } + if (node == null) return null; + //RemoveInactiveNode(node); + + if (node.IsActive) return node; + return null; + } + + /// + /// 获取客户端连接数 + /// + /// + public int GetConnectionCount() + { + int ret; + lock (nodes) + { + ret = nodes.Count; + } + return ret; + + } + + + #region UDP客户端连接事件 + + /// + // 从UDP端口接收到消息 + /// + public Action HandleUDPRsvMsg { get; set; } + + /// + // 转发消息给UDP端口 + /// + public Action HandleUDPSendMsg { get; set; } + + #endregion + + #region Websocket客户端连接事件 + + /// + // 从Websocket端口接收到消息 + /// + public Action HandleWebsocketRsvMsg { get; set; } + + /// + /// 转发消息给Websocket端口 + /// + public Action HandleWebSocketSendMsg { get; set; } + #endregion + + #region 客户端连接事件 + /// + /// 当新NODE建立后执行 + /// + public Action HandleNodeCreated { get; set; } + /// + /// NODE关闭后回调 + /// + public Action HandleNodeClosed { get; set; } + /// + /// 异常处理程序 + /// + public Action HandleException { get; set; } + /// + /// 节点状态发生变化 + /// + public Action HandleNodeChanged { get; set; } + #endregion + + + + } +} diff --git a/UdpPlugWebsocket/Program.cs b/UdpPlugWebsocket/Program.cs new file mode 100644 index 0000000..2845fca --- /dev/null +++ b/UdpPlugWebsocket/Program.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace UdpPlugWebsocket +{ + static class Program + { + /// + /// 应用程序的主入口点。 + /// + [STAThread] + static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new Form1()); + } + } +} diff --git a/UdpPlugWebsocket/Properties/AssemblyInfo.cs b/UdpPlugWebsocket/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..85f4c59 --- /dev/null +++ b/UdpPlugWebsocket/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// 有关程序集的一般信息由以下 +// 控制。更改这些特性值可修改 +// 与程序集关联的信息。 +[assembly: AssemblyTitle("DoorControl")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("DoorControl")] +[assembly: AssemblyCopyright("Copyright © 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +//将 ComVisible 设置为 false 将使此程序集中的类型 +//对 COM 组件不可见。 如果需要从 COM 访问此程序集中的类型, +//请将此类型的 ComVisible 特性设置为 true。 +[assembly: ComVisible(false)] + +// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID +[assembly: Guid("54cf701b-b076-4fc3-8a01-7434111a1c5b")] + +// 程序集的版本信息由下列四个值组成: +// +// 主版本 +// 次版本 +// 生成号 +// 修订号 +// +//可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值, +// 方法是按如下所示使用“*”: : +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/UdpPlugWebsocket/Properties/Resources.Designer.cs b/UdpPlugWebsocket/Properties/Resources.Designer.cs new file mode 100644 index 0000000..80642f5 --- /dev/null +++ b/UdpPlugWebsocket/Properties/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// 此代码由工具生成。 +// 运行时版本:4.0.30319.42000 +// +// 对此文件的更改可能会导致不正确的行为,并且如果 +// 重新生成代码,这些更改将会丢失。 +// +//------------------------------------------------------------------------------ + +namespace UdpPlugWebsocket.Properties { + using System; + + + /// + /// 一个强类型的资源类,用于查找本地化的字符串等。 + /// + // 此类是由 StronglyTypedResourceBuilder + // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 + // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen + // (以 /str 作为命令选项),或重新生成 VS 项目。 + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// 返回此类使用的缓存的 ResourceManager 实例。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("UdpPlugWebsocket.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// 使用此强类型资源类,为所有资源查找 + /// 重写当前线程的 CurrentUICulture 属性。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/UdpPlugWebsocket/Properties/Resources.resx b/UdpPlugWebsocket/Properties/Resources.resx new file mode 100644 index 0000000..af7dbeb --- /dev/null +++ b/UdpPlugWebsocket/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/UdpPlugWebsocket/Properties/Settings.Designer.cs b/UdpPlugWebsocket/Properties/Settings.Designer.cs new file mode 100644 index 0000000..a7fe750 --- /dev/null +++ b/UdpPlugWebsocket/Properties/Settings.Designer.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// +// 此代码由工具生成。 +// 运行时版本:4.0.30319.42000 +// +// 对此文件的更改可能会导致不正确的行为,并且如果 +// 重新生成代码,这些更改将会丢失。 +// +//------------------------------------------------------------------------------ + +namespace UdpPlugWebsocket.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/UdpPlugWebsocket/Properties/Settings.settings b/UdpPlugWebsocket/Properties/Settings.settings new file mode 100644 index 0000000..3964565 --- /dev/null +++ b/UdpPlugWebsocket/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/UdpPlugWebsocket/ReadMe.md b/UdpPlugWebsocket/ReadMe.md new file mode 100644 index 0000000..af0d12b --- /dev/null +++ b/UdpPlugWebsocket/ReadMe.md @@ -0,0 +1,60 @@ +# UdpPlugWebSocket 简称UPWS服务端程序# + + +## 项目说明 ## + +公司要做物联网硬件产品,需要一个服务器组件桥接APP软件终端和物联网硬件。 目前比较火的MQTT物联网协议虽然技术先进,但进入门槛较高,用户也相对较为复杂。BG了一番没有找到合适的服务端开源代码,就决定使用自己比较熟悉的.Net技术从头开发一个服务器组件。又考虑到自己毕竟是一个中国人没必要舍近求远的去用Git,就选择了在码云开了这个repository,如果能帮到你,我很高兴。 有问题欢迎与我微信联系: 微信号miuser00,加好友请注明您的来意。 + +## 项目简介 ## +这是一个独立的可执行Win32服务端程序,用于桥接终端APP和物联网硬件。 APP端为Websocet接口的静态html页面,硬件端为基于Arduino开发的电路,与服务器通讯使用UDP协议通讯(注:为方便您的使用,本Respository亦提供了一个C#编写的简易UDP测试程序用来模拟硬件设备)。 APP通过Webscoket接口发送一个自定义的字符串报文给服务端,服务端根据报文中的ID把报文转发给相同ID的UDP硬件设备(或UDP测试程序)。 + +![](.\document\preview.png) + +启动方法:在本地windows环境下运行.\UdpPlugWebsocket\bin\Debug\UdpPlugWebsocket.exe即可按照config.xml中描述的端口启动服务程序。默认UDP端口为7101,Websocket端口为9000。此时外部的UDP连接和Websocket连接都会在UI中显示。其中UDP在终端设备页面中显示,Websocket连接在控制页面中显示,通讯状况在交换面板页面中显示。您可以通过配置->显示调试系信息 打开或关闭UI的通讯Log。 + + + + +## 数据包格式 (UDP与Websocket相同)## + +![](.\document\packageintro.png) + + +**Endpoint是外部连接的唯一索引。** + +**Endpoint字符串的格式为:"XXX.XXX.XXX.XXX:XXXX" 前四组三位数为IP地址,最后一组四位数为远程端口号** + +##UDP通讯测试## + + 运行.\UdpExample\client\bin\Debug\client.exe 在程序控制台中输入如下数据包 + 004832A08000000000200000000000000001234testtest05 即可在终端设备页面看到本机的连接。 +![](.\document\clienttest.png) +## Websocket通讯测试 ## + 任意支持websocket的浏览器中运行.\UPW_Browser\index.html?ID=0000000002&MM=0000000000000000与UDP测试程序建立连接(参数区分大小写) +![](.\document\webtest.png) + +## 目录结构 ## + .\UdpPlugWebsocket UPWS服务程序(主服务程序) + .\UdpPlugWebsocket\UdpExample UDP测试程序 + .\UPW_Browser Websocket测试程序 + .\bin_UPWS 编译后的Win32项目二进制文 + ... 其余目录为参考代码目录,可删除 + + +##编译环境## +Visual Studio 2015,C#,Win10 + +## 使用到的开源库 ## + Websocket-Sharp + https://github.com/sta/websocket-sharp + Coldairarrow.Util.Sockets + https://github.com/Coldairarrow/Sockets + + +## 关键词 ## + +C#,Websocket,UDP,物联网,异步编程,接口,lamda表达式,线程池,Action,Invoke,Arduino,ESP8266 + +## 版权声明 ## + +如果您愿意使用 UdpPlugWebSocket 组件,请遵循 MIT 许可所述内容. \ No newline at end of file diff --git a/UdpPlugWebsocket/UI/setup.Designer.cs b/UdpPlugWebsocket/UI/setup.Designer.cs new file mode 100644 index 0000000..d04e40a --- /dev/null +++ b/UdpPlugWebsocket/UI/setup.Designer.cs @@ -0,0 +1,88 @@ +namespace UserLogin +{ + partial class SetupForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.btn_save = new System.Windows.Forms.Button(); + this.prg_config = new System.Windows.Forms.PropertyGrid(); + this.button1 = new System.Windows.Forms.Button(); + this.SuspendLayout(); + // + // btn_save + // + this.btn_save.Location = new System.Drawing.Point(443, 641); + this.btn_save.Margin = new System.Windows.Forms.Padding(4); + this.btn_save.Name = "btn_save"; + this.btn_save.Size = new System.Drawing.Size(178, 30); + this.btn_save.TabIndex = 3; + this.btn_save.Text = "保存并重启"; + this.btn_save.UseVisualStyleBackColor = true; + this.btn_save.Click += new System.EventHandler(this.btn_save_Click); + // + // prg_config + // + this.prg_config.Dock = System.Windows.Forms.DockStyle.Fill; + this.prg_config.Location = new System.Drawing.Point(0, 0); + this.prg_config.Margin = new System.Windows.Forms.Padding(4); + this.prg_config.Name = "prg_config"; + this.prg_config.Size = new System.Drawing.Size(663, 684); + this.prg_config.TabIndex = 2; + // + // button1 + // + this.button1.Location = new System.Drawing.Point(106, 641); + this.button1.Name = "button1"; + this.button1.Size = new System.Drawing.Size(97, 29); + this.button1.TabIndex = 4; + this.button1.Text = "button1"; + this.button1.UseVisualStyleBackColor = true; + this.button1.Click += new System.EventHandler(this.button1_Click); + // + // SetupForm + // + this.AcceptButton = this.btn_save; + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None; + this.ClientSize = new System.Drawing.Size(663, 684); + this.Controls.Add(this.button1); + this.Controls.Add(this.btn_save); + this.Controls.Add(this.prg_config); + this.Name = "SetupForm"; + this.Text = "setup"; + this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.SetupForm_FormClosed); + this.Load += new System.EventHandler(this.SetupForm_Load); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.Button btn_save; + private System.Windows.Forms.PropertyGrid prg_config; + private System.Windows.Forms.Button button1; + } +} \ No newline at end of file diff --git a/UdpPlugWebsocket/UI/setup.cs b/UdpPlugWebsocket/UI/setup.cs new file mode 100644 index 0000000..7436245 --- /dev/null +++ b/UdpPlugWebsocket/UI/setup.cs @@ -0,0 +1,229 @@ +/// +         ///模块编号:20180730 + ///模块名:本地配置模块 +         ///作用:生成本地的存储信息 +         ///作者:Miuser +         ///编写日期:20180730 + ///版本:1.0 +/// + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +//Common reference +using System.Threading; +using System.Net.Sockets; +using System.Net; +using System.Xml.Serialization; +using System.IO; +using System.Text.RegularExpressions; +//mongo DB +using MongoDB.Driver; +using MongoDB.Bson; +using System.Security.Cryptography; + + +namespace UserLogin +{ + + public partial class SetupForm : Form + { + Config cfg; + public SetupForm(ref Config config) + { + cfg = config; + InitializeComponent(); + } + + private void SetupForm_Load(object sender, EventArgs e) + { + prg_config.SelectedObject = cfg; + } + + private void btn_save_Click(object sender, EventArgs e) + { + cfg.SavetoFile("config.xml"); + Application.Restart(); + + } + + private void SetupForm_FormClosed(object sender, FormClosedEventArgs e) + { + + + } + + private void button1_Click(object sender, EventArgs e) + { + cfg.ShopList.Add("大岛店[01]"); + cfg.ShopList.Add("洞庭路店[02]"); + } + } + public class Config + { + [CategoryAttribute("1.MongoDB设置")] + public String url { get; set; } //192.168.0.33 + [CategoryAttribute("1.MongoDB设置")] + public String port { get; set; } + [CategoryAttribute("1.MongoDB设置")] + public String user { get; set; } + + [CategoryAttribute("1.MongoDB设置")] + + [BrowsableAttribute(false)] + public String Epass { get; set; } + + [CategoryAttribute("1.MongoDB设置")] + [PasswordPropertyText(true)] + [System.Xml.Serialization.XmlIgnore] + public String pass + { + get + { + return Encryption.Decode(Epass); + } + set + { + Epass = Encryption.Encode(value); + } + } + + [CategoryAttribute("1.MongoDB设置")] + public String database { get; set; } + + [CategoryAttribute("2.球机设定")] + public int GolfPrivilegeTime { get; set; } + [CategoryAttribute("2.球机设定")] + public string ShopName { get; set; } + [CategoryAttribute("2.球机设定")] + public List ShopList { get; set; } + public int SavetoFile(String filename) + { + try + { + XmlSerializer serializer = new XmlSerializer(typeof(Config)); + TextWriter writer = new StreamWriter(filename); + serializer.Serialize(writer, this); + writer.Close(); + } + + catch (Exception ee) + { + MessageBox.Show(ee.StackTrace, ee.Message); + return 0; + } + + return 1; + } + public static Config LoadfromFile(String filename) + { + try + { + Config sptr; + + XmlSerializer serializer = new XmlSerializer(typeof(Config)); + TextReader reader = new StreamReader(filename); + sptr = (Config)(serializer.Deserialize(reader)); + reader.Close(); + return sptr; + + } + catch (Exception ee) + { + MessageBox.Show(ee.StackTrace, ee.Message); + return null; + } + + } + + } + public class Encryption + { + + /// + /// 作用:将字符串内容转化为16进制数据编码,其逆过程是Decode + /// 参数说明: + /// strEncode 需要转化的原始字符串 + /// 转换的过程是直接把字符转换成Unicode字符,比如数字"3"-->0033,汉字"我"-->U+6211 + /// 函数decode的过程是encode的逆过程. + /// + public static string Encode(string strEncode) + { + string strReturn = "";// 存储转换后的编码 + try + { + foreach (short shortx in strEncode.ToCharArray()) + { + strReturn += shortx.ToString("X4"); + } + } + catch { } + return strReturn; + } + + /// + /// 作用:将16进制数据编码转化为字符串,是Encode的逆过程 + /// + public static string Decode(string strDecode) + { + string sResult = ""; + try + { + for (int i = 0; i < strDecode.Length / 4; i++) + { + sResult += (char)short.Parse(strDecode.Substring(i * 4, 4), + global::System.Globalization.NumberStyles.HexNumber); + } + } + catch { } + return sResult; + } + + /// + /// 将数字转换成16进制字符串,后两位加入随机字符,其可逆方法为DecodeForNum + /// + public static string EncodeForNum(int id) + { + //用户加上起始位置后的 + int startUserIndex = id; + //转换成16进制 + string hexStr = Convert.ToString(startUserIndex, 16); + + //后面两位加入随机数 + string randomchars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + string tmpstr = ""; + + //整除的后得到的数可能大于被除数 + tmpstr += randomchars[(id / randomchars.Length) > randomchars.Length ? randomchars.Length - 1 : (id / randomchars.Length)]; + + //余数不可能大于被除数 + tmpstr += randomchars[(id % randomchars.Length) > randomchars.Length ? randomchars.Length - 1 : (id % randomchars.Length)]; + + //返回拼接后的字符,转成大写 + string retStr = (hexStr + tmpstr).ToUpper(); + + return retStr; + } + + /// + /// 解密16进制字符串,此方法只适合后面两位有随机字符的 + /// + public static int DecodeForNum(string strDecode) + { + if (strDecode.Length > 2) + { + strDecode = strDecode.Substring(0, strDecode.Length - 2); + return Convert.ToInt32(strDecode, 16); + } + return 0; + } + + } +} diff --git a/UdpPlugWebsocket/UI/setup.resx b/UdpPlugWebsocket/UI/setup.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/UdpPlugWebsocket/UI/setup.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/UdpPlugWebsocket/UdpPlugWebsocket.csproj b/UdpPlugWebsocket/UdpPlugWebsocket.csproj new file mode 100644 index 0000000..a7d29d9 --- /dev/null +++ b/UdpPlugWebsocket/UdpPlugWebsocket.csproj @@ -0,0 +1,157 @@ + + + + + Debug + AnyCPU + {54CF701B-B076-4FC3-8A01-7434111A1C5B} + WinExe + Properties + UdpPlugWebsocket + UdpPlugWebsocket + v4.5.2 + 512 + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + ..\packages\WebSocketSharp-netstandard.1.0.1\lib\net45\websocket-sharp.dll + + + + + Form + + + Browser.cs + + + Form + + + Device.cs + + + + + Form + + + Form1.cs + + + Form + + + ErrorForm.cs + + + Form + + + MessageForm.cs + + + + Form + + + Panel.cs + + + + + + Form + + + setup.cs + + + Browser.cs + + + Device.cs + + + Form1.cs + + + ErrorForm.cs + + + MessageForm.cs + + + Panel.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + True + + + setup.cs + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + + + + + \ No newline at end of file diff --git a/UdpPlugWebsocket/WebSocketServer/WebSocketForm.Designer.cs b/UdpPlugWebsocket/WebSocketServer/WebSocketForm.Designer.cs new file mode 100644 index 0000000..85c0c32 --- /dev/null +++ b/UdpPlugWebsocket/WebSocketServer/WebSocketForm.Designer.cs @@ -0,0 +1,170 @@ +namespace DoorControl +{ + partial class WebSocketForm + { + /// + /// 必需的设计器变量。 + /// + private System.ComponentModel.IContainer components = null; + + /// + /// 清理所有正在使用的资源。 + /// + /// 如果应释放托管资源,为 true;否则为 false。 + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows 窗体设计器生成的代码 + + /// + /// 设计器支持所需的方法 - 不要修改 + /// 使用代码编辑器修改此方法的内容。 + /// + private void InitializeComponent() + { + this.rtb_Server = new System.Windows.Forms.RichTextBox(); + this.btn_Send = new System.Windows.Forms.Button(); + this.txt_server = new System.Windows.Forms.TextBox(); + this.txt_msg_serversend = new System.Windows.Forms.TextBox(); + this.label2 = new System.Windows.Forms.Label(); + this.label3 = new System.Windows.Forms.Label(); + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.button1 = new System.Windows.Forms.Button(); + this.label1 = new System.Windows.Forms.Label(); + this.lis_sessions = new System.Windows.Forms.ListBox(); + this.groupBox1.SuspendLayout(); + this.SuspendLayout(); + // + // rtb_Server + // + this.rtb_Server.Location = new System.Drawing.Point(377, 66); + this.rtb_Server.Name = "rtb_Server"; + this.rtb_Server.Size = new System.Drawing.Size(415, 199); + this.rtb_Server.TabIndex = 0; + this.rtb_Server.Text = ""; + // + // btn_Send + // + this.btn_Send.Location = new System.Drawing.Point(706, 23); + this.btn_Send.Name = "btn_Send"; + this.btn_Send.Size = new System.Drawing.Size(70, 21); + this.btn_Send.TabIndex = 1; + this.btn_Send.Text = "发送"; + this.btn_Send.UseVisualStyleBackColor = true; + this.btn_Send.Click += new System.EventHandler(this.btn_Send_Click); + // + // txt_server + // + this.txt_server.Location = new System.Drawing.Point(101, 27); + this.txt_server.Name = "txt_server"; + this.txt_server.Size = new System.Drawing.Size(183, 21); + this.txt_server.TabIndex = 2; + this.txt_server.Text = "0.0.0.0:6666"; + // + // txt_msg_serversend + // + this.txt_msg_serversend.Location = new System.Drawing.Point(477, 23); + this.txt_msg_serversend.Name = "txt_msg_serversend"; + this.txt_msg_serversend.Size = new System.Drawing.Size(210, 21); + this.txt_msg_serversend.TabIndex = 3; + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(424, 27); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(47, 12); + this.label2.TabIndex = 5; + this.label2.Text = "Message"; + // + // label3 + // + this.label3.AutoSize = true; + this.label3.Location = new System.Drawing.Point(375, 51); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(23, 12); + this.label3.TabIndex = 6; + this.label3.Text = "Log"; + // + // groupBox1 + // + this.groupBox1.Controls.Add(this.button1); + this.groupBox1.Controls.Add(this.label1); + this.groupBox1.Controls.Add(this.lis_sessions); + this.groupBox1.Controls.Add(this.label3); + this.groupBox1.Controls.Add(this.rtb_Server); + this.groupBox1.Controls.Add(this.label2); + this.groupBox1.Controls.Add(this.btn_Send); + this.groupBox1.Controls.Add(this.txt_server); + this.groupBox1.Controls.Add(this.txt_msg_serversend); + this.groupBox1.Location = new System.Drawing.Point(46, 63); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.Size = new System.Drawing.Size(821, 367); + this.groupBox1.TabIndex = 7; + this.groupBox1.TabStop = false; + this.groupBox1.Text = "Server"; + // + // button1 + // + this.button1.Location = new System.Drawing.Point(290, 26); + this.button1.Name = "button1"; + this.button1.Size = new System.Drawing.Size(54, 22); + this.button1.TabIndex = 9; + this.button1.Text = "Start"; + this.button1.UseVisualStyleBackColor = true; + this.button1.Click += new System.EventHandler(this.button1_Click_1); + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(36, 30); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(59, 12); + this.label1.TabIndex = 8; + this.label1.Text = "Server IP"; + // + // lis_sessions + // + this.lis_sessions.FormattingEnabled = true; + this.lis_sessions.ItemHeight = 12; + this.lis_sessions.Location = new System.Drawing.Point(38, 66); + this.lis_sessions.Name = "lis_sessions"; + this.lis_sessions.Size = new System.Drawing.Size(304, 196); + this.lis_sessions.TabIndex = 7; + // + // WebSocketForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(1060, 693); + this.Controls.Add(this.groupBox1); + this.Name = "WebSocketForm"; + this.Text = "WebSocketServer"; + this.Load += new System.EventHandler(this.Form1_Load); + this.groupBox1.ResumeLayout(false); + this.groupBox1.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.RichTextBox rtb_Server; + private System.Windows.Forms.Button btn_Send; + private System.Windows.Forms.TextBox txt_server; + private System.Windows.Forms.TextBox txt_msg_serversend; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.GroupBox groupBox1; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.ListBox lis_sessions; + private System.Windows.Forms.Button button1; + } +} + diff --git a/UdpPlugWebsocket/WebSocketServer/WebSocketForm.cs b/UdpPlugWebsocket/WebSocketServer/WebSocketForm.cs new file mode 100644 index 0000000..471e282 --- /dev/null +++ b/UdpPlugWebsocket/WebSocketServer/WebSocketForm.cs @@ -0,0 +1,162 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using System.Management; +using WebSocketSharp; +using WebSocketSharp.Server; + +namespace DoorControl +{ + public partial class WebSocketForm : Form + { + WebSocketServer wssv; + + + private delegate void DelegateStringFun(string log);//代理 + + public static WebSocketForm frm; + public string text; + public string textclient; + + //自身的句柄 + public static void SendMessage(String msg) + { + frm.Log(msg); + } + + public static void ConnectionAccepted(String id) + { + frm.AddList(id); + } + + public static void ConnectionDismiss(String id) + { + frm.RemoveList(id); + } + public static String logtxt = ""; + + public WebSocketForm() + { + InitializeComponent(); + frm = this; + + + } + public string Getlog() + { + string ret = logtxt; + logtxt = ""; + return ret; + } + + public class MyEventArg : EventArgs + { + //传递主窗体的数据信息 + public string Text { get; set; } + } + + private void Form1_Load(object sender, EventArgs e) + { + wssv = new WebSocketServer("ws://" +txt_server.Text); + + } + public class Laputa : WebSocketBehavior + { + protected override void OnOpen() + { + base.OnOpen(); + SendMessage("Connect from "+Context.Host + " was accepted"); + ConnectionAccepted(ID+"|"+ Context.Host.ToString()); + + } + protected override void OnClose(CloseEventArgs e) + { + SendMessage("Connect from " + Context.Host + " was lost"); + ConnectionDismiss(ID + "|" + Context.Host.ToString()); + base.OnClose(e); + + } + protected override void OnMessage(MessageEventArgs e) + { + + string s_data = e.Data; + Logger log=new Logger(); + SendMessage("Server Received: "+e.Data); + Console.Write(e.Data); + //Loopback + //Send(e.Data); + + + } + } + + + public void Log(string log) + { + if (this.rtb_Server.InvokeRequired) + { + DelegateStringFun d = new DelegateStringFun(Log); + this.Invoke(d, new object[] { log }); + } + else + { + text= text + log.Replace("\0","") + "\n"; + rtb_Server.Text = text; + } + } + + public void AddList(string id) + { + if (this.rtb_Server.InvokeRequired) + { + DelegateStringFun d = new DelegateStringFun(AddList); + this.Invoke(d, new object[] { id }); + } + else + { + lis_sessions.Items.Add(id); + + } + } + + public void RemoveList(string id) + { + if (this.rtb_Server.InvokeRequired) + { + DelegateStringFun d = new DelegateStringFun(RemoveList); + this.Invoke(d, new object[] { id }); + } + else + { + lis_sessions.Items.Remove(id); + } + } + + + private void btn_Send_Click(object sender, EventArgs e) + { + try + { + string s_mixid = lis_sessions.SelectedItem.ToString(); + string s_id = s_mixid.Substring(0, s_mixid.IndexOf("|")); + wssv.WebSocketServices["/"].Sessions.SendToAsync(txt_msg_serversend.Text, s_id, null); + }catch + { + MessageBox.Show("请先选择session"); + } + + } + + + private void button1_Click_1(object sender, EventArgs e) + { + + } + } +} diff --git a/UdpPlugWebsocket/WebSocketServer/WebSocketForm.resx b/UdpPlugWebsocket/WebSocketServer/WebSocketForm.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/UdpPlugWebsocket/WebSocketServer/WebSocketForm.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/UdpPlugWebsocket/WebSocketServer/WebSocketServer.csproj b/UdpPlugWebsocket/WebSocketServer/WebSocketServer.csproj new file mode 100644 index 0000000..b687b50 --- /dev/null +++ b/UdpPlugWebsocket/WebSocketServer/WebSocketServer.csproj @@ -0,0 +1,97 @@ + + + + + Debug + AnyCPU + {D5A6D030-FDDE-462F-94E5-A78F312107FD} + WinExe + Properties + WebSocketServer + WebSocketServer + v4.5.2 + 512 + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + ..\packages\WebSocketSharp-netstandard.1.0.1\lib\net45\websocket-sharp.dll + True + + + + + Form + + + WebSocketForm.cs + + + + + WebSocketForm.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + + + + \ No newline at end of file diff --git a/UdpPlugWebsocket/setup.cs b/UdpPlugWebsocket/setup.cs new file mode 100644 index 0000000..01fec87 --- /dev/null +++ b/UdpPlugWebsocket/setup.cs @@ -0,0 +1,209 @@ +/// +         ///模块编号:20180730 + ///模块名:本地配置模块 +         ///作用:生成本地的存储信息 +         ///作者:Miuser +         ///编写日期:20180730 + ///版本:1.0 +/// + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +//Common reference +using System.Threading; +using System.Net.Sockets; +using System.Net; +using System.Xml.Serialization; +using System.IO; +using System.Text.RegularExpressions; +//mongo DB + +using System.Security.Cryptography; + + +namespace UdpPlugWebsocket +{ + + public partial class SetupForm : Form + { + public static Config cfg; + public SetupForm(ref Config config) + { + cfg = config; + InitializeComponent(); + } + public SetupForm() + { + cfg = Config.LoadfromFile("config.xml"); + InitializeComponent(); + } + private void SetupForm_Load(object sender, EventArgs e) + { + prg_config.SelectedObject = cfg; + } + + private void btn_save_Click(object sender, EventArgs e) + { + cfg.SavetoFile("config.xml"); + Application.Restart(); + } + + private void SetupForm_FormClosed(object sender, FormClosedEventArgs e) + { + } + + } + public class Config + { + [CategoryAttribute("1.Websocket Port")] + public String BrowserPort { get; set; } + + + [CategoryAttribute("2.UDP Port")] + public int UDPPort { get; set; } + [CategoryAttribute("2.UDP TTL (ms)")] + public int UDPTTL { get; set; } + + [CategoryAttribute("3.NODE TTL (ms)")] + public int NODETTL { get; set; } + + + //记录日志 + //[CategoryAttribute("4.Enable File Log")] + //public bool EnableFileLog { get; set; } + + //屏幕显示 + [CategoryAttribute("4.Enable Screen Log")] + public bool EnableScreenLog { get; set; } + + public int SavetoFile(String filename) + { + try + { + XmlSerializer serializer = new XmlSerializer(typeof(Config)); + TextWriter writer = new StreamWriter(filename); + serializer.Serialize(writer, this); + writer.Close(); + } + + catch (Exception ee) + { + MessageBox.Show(ee.StackTrace, ee.Message); + return 0; + } + + return 1; + } + public static Config LoadfromFile(String filename) + { + try + { + Config sptr; + + XmlSerializer serializer = new XmlSerializer(typeof(Config)); + TextReader reader = new StreamReader(filename); + sptr = (Config)(serializer.Deserialize(reader)); + reader.Close(); + return sptr; + + } + catch (Exception ee) + { + MessageBox.Show(ee.StackTrace, ee.Message); + return new Config(); + } + + } + + } + public class Encryption + { + + /// + /// 作用:将字符串内容转化为16进制数据编码,其逆过程是Decode + /// 参数说明: + /// strEncode 需要转化的原始字符串 + /// 转换的过程是直接把字符转换成Unicode字符,比如数字"3"-->0033,汉字"我"-->U+6211 + /// 函数decode的过程是encode的逆过程. + /// + public static string Encode(string strEncode) + { + string strReturn = "";// 存储转换后的编码 + try + { + foreach (short shortx in strEncode.ToCharArray()) + { + strReturn += shortx.ToString("X4"); + } + } + catch { } + return strReturn; + } + + /// + /// 作用:将16进制数据编码转化为字符串,是Encode的逆过程 + /// + public static string Decode(string strDecode) + { + string sResult = ""; + try + { + for (int i = 0; i < strDecode.Length / 4; i++) + { + sResult += (char)short.Parse(strDecode.Substring(i * 4, 4), + global::System.Globalization.NumberStyles.HexNumber); + } + } + catch { } + return sResult; + } + + /// + /// 将数字转换成16进制字符串,后两位加入随机字符,其可逆方法为DecodeForNum + /// + public static string EncodeForNum(int id) + { + //用户加上起始位置后的 + int startUserIndex = id; + //转换成16进制 + string hexStr = Convert.ToString(startUserIndex, 16); + + //后面两位加入随机数 + string randomchars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + string tmpstr = ""; + + //整除的后得到的数可能大于被除数 + tmpstr += randomchars[(id / randomchars.Length) > randomchars.Length ? randomchars.Length - 1 : (id / randomchars.Length)]; + + //余数不可能大于被除数 + tmpstr += randomchars[(id % randomchars.Length) > randomchars.Length ? randomchars.Length - 1 : (id % randomchars.Length)]; + + //返回拼接后的字符,转成大写 + string retStr = (hexStr + tmpstr).ToUpper(); + + return retStr; + } + + /// + /// 解密16进制字符串,此方法只适合后面两位有随机字符的 + /// + public static int DecodeForNum(string strDecode) + { + if (strDecode.Length > 2) + { + strDecode = strDecode.Substring(0, strDecode.Length - 2); + return Convert.ToInt32(strDecode, 16); + } + return 0; + } + + } +} diff --git a/UdpPlugWebsocket/setup.designer.cs b/UdpPlugWebsocket/setup.designer.cs new file mode 100644 index 0000000..2628a5b --- /dev/null +++ b/UdpPlugWebsocket/setup.designer.cs @@ -0,0 +1,75 @@ +namespace UdpPlugWebsocket +{ + partial class SetupForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.btn_save = new System.Windows.Forms.Button(); + this.prg_config = new System.Windows.Forms.PropertyGrid(); + this.SuspendLayout(); + // + // btn_save + // + this.btn_save.Location = new System.Drawing.Point(443, 641); + this.btn_save.Margin = new System.Windows.Forms.Padding(4); + this.btn_save.Name = "btn_save"; + this.btn_save.Size = new System.Drawing.Size(178, 30); + this.btn_save.TabIndex = 3; + this.btn_save.Text = "保存并重启"; + this.btn_save.UseVisualStyleBackColor = true; + this.btn_save.Click += new System.EventHandler(this.btn_save_Click); + // + // prg_config + // + this.prg_config.Dock = System.Windows.Forms.DockStyle.Fill; + this.prg_config.Location = new System.Drawing.Point(0, 0); + this.prg_config.Margin = new System.Windows.Forms.Padding(4); + this.prg_config.Name = "prg_config"; + this.prg_config.Size = new System.Drawing.Size(663, 684); + this.prg_config.TabIndex = 2; + // + // SetupForm + // + this.AcceptButton = this.btn_save; + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None; + this.ClientSize = new System.Drawing.Size(663, 684); + this.Controls.Add(this.btn_save); + this.Controls.Add(this.prg_config); + this.Name = "SetupForm"; + this.Text = "setup"; + this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.SetupForm_FormClosed); + this.Load += new System.EventHandler(this.SetupForm_Load); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.Button btn_save; + private System.Windows.Forms.PropertyGrid prg_config; + } +} \ No newline at end of file diff --git a/UdpPlugWebsocket/setup.resx b/UdpPlugWebsocket/setup.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/UdpPlugWebsocket/setup.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/bin_UPWS/UdpPlugWebsocket.exe b/bin_UPWS/UdpPlugWebsocket.exe new file mode 100644 index 0000000..46321d6 Binary files /dev/null and b/bin_UPWS/UdpPlugWebsocket.exe differ diff --git a/bin_UPWS/UdpPlugWebsocket.exe.config b/bin_UPWS/UdpPlugWebsocket.exe.config new file mode 100644 index 0000000..88fa402 --- /dev/null +++ b/bin_UPWS/UdpPlugWebsocket.exe.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/bin_UPWS/UdpPlugWebsocket.vshost.exe b/bin_UPWS/UdpPlugWebsocket.vshost.exe new file mode 100644 index 0000000..681ab77 Binary files /dev/null and b/bin_UPWS/UdpPlugWebsocket.vshost.exe differ diff --git a/bin_UPWS/UdpPlugWebsocket.vshost.exe.config b/bin_UPWS/UdpPlugWebsocket.vshost.exe.config new file mode 100644 index 0000000..88fa402 --- /dev/null +++ b/bin_UPWS/UdpPlugWebsocket.vshost.exe.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/bin_UPWS/UdpPlugWebsocket.vshost.exe.manifest b/bin_UPWS/UdpPlugWebsocket.vshost.exe.manifest new file mode 100644 index 0000000..061c9ca --- /dev/null +++ b/bin_UPWS/UdpPlugWebsocket.vshost.exe.manifest @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/bin_UPWS/WebSocketClient.html b/bin_UPWS/WebSocketClient.html new file mode 100644 index 0000000..4c9fdbf --- /dev/null +++ b/bin_UPWS/WebSocketClient.html @@ -0,0 +1,37 @@ + + + + + + +
+ + +
+ + + + + diff --git a/bin_UPWS/client.exe b/bin_UPWS/client.exe new file mode 100644 index 0000000..aca4936 Binary files /dev/null and b/bin_UPWS/client.exe differ diff --git a/bin_UPWS/config.xml b/bin_UPWS/config.xml new file mode 100644 index 0000000..f286a35 --- /dev/null +++ b/bin_UPWS/config.xml @@ -0,0 +1,9 @@ + + + 9000 + 7101 + 30000 + 20000 + true + true + \ No newline at end of file diff --git a/bin_UPWS/main/JustGage.js b/bin_UPWS/main/JustGage.js new file mode 100644 index 0000000..38825fd --- /dev/null +++ b/bin_UPWS/main/JustGage.js @@ -0,0 +1,3 @@ +(function(a){var b="0.3.4",c="hasOwnProperty",d=/[\.\/]/,e="*",f=function(){},g=function(a,b){return a-b},h,i,j={n:{}},k=function(a,b){var c=j,d=i,e=Array.prototype.slice.call(arguments,2),f=k.listeners(a),l=0,m=!1,n,o=[],p={},q=[],r=h,s=[];h=a,i=0;for(var t=0,u=f.length;tf*b.top){e=b.percents[y],p=b.percents[y-1]||0,t=t/b.top*(e-p),o=b.percents[y+1],j=b.anim[e];break}f&&d.attr(b.anim[b.percents[y]])}if(!!j){if(!k){for(var A in j)if(j[g](A))if(U[g](A)||d.paper.customAttributes[g](A)){u[A]=d.attr(A),u[A]==null&&(u[A]=T[A]),v[A]=j[A];switch(U[A]){case C:w[A]=(v[A]-u[A])/t;break;case"colour":u[A]=a.getRGB(u[A]);var B=a.getRGB(v[A]);w[A]={r:(B.r-u[A].r)/t,g:(B.g-u[A].g)/t,b:(B.b-u[A].b)/t};break;case"path":var D=bR(u[A],v[A]),E=D[1];u[A]=D[0],w[A]=[];for(y=0,z=u[A].length;yd)return d;while(cf?c=e:d=e,e=(d-c)/2+c}return e}function n(a,b){var c=o(a,b);return((l*c+k)*c+j)*c}function m(a){return((i*a+h)*a+g)*a}var g=3*b,h=3*(d-b)-g,i=1-g-h,j=3*c,k=3*(e-c)-j,l=1-j-k;return n(a,1/(200*f))}function cq(){return this.x+q+this.y+q+this.width+" � "+this.height}function cp(){return this.x+q+this.y}function cb(a,b,c,d,e,f){a!=null?(this.a=+a,this.b=+b,this.c=+c,this.d=+d,this.e=+e,this.f=+f):(this.a=1,this.b=0,this.c=0,this.d=1,this.e=0,this.f=0)}function bH(b,c,d){b=a._path2curve(b),c=a._path2curve(c);var e,f,g,h,i,j,k,l,m,n,o=d?0:[];for(var p=0,q=b.length;p=0&&y<=1&&A>=0&&A<=1&&(d?n++:n.push({x:x.x,y:x.y,t1:y,t2:A}))}}return n}function bF(a,b){return bG(a,b,1)}function bE(a,b){return bG(a,b)}function bD(a,b,c,d,e,f,g,h){if(!(x(a,c)x(e,g)||x(b,d)x(f,h))){var i=(a*d-b*c)*(e-g)-(a-c)*(e*h-f*g),j=(a*d-b*c)*(f-h)-(b-d)*(e*h-f*g),k=(a-c)*(f-h)-(b-d)*(e-g);if(!k)return;var l=i/k,m=j/k,n=+l.toFixed(2),o=+m.toFixed(2);if(n<+y(a,c).toFixed(2)||n>+x(a,c).toFixed(2)||n<+y(e,g).toFixed(2)||n>+x(e,g).toFixed(2)||o<+y(b,d).toFixed(2)||o>+x(b,d).toFixed(2)||o<+y(f,h).toFixed(2)||o>+x(f,h).toFixed(2))return;return{x:l,y:m}}}function bC(a,b,c,d,e,f,g,h,i){if(!(i<0||bB(a,b,c,d,e,f,g,h)n)k/=2,l+=(m1?1:i<0?0:i;var j=i/2,k=12,l=[-0.1252,.1252,-0.3678,.3678,-0.5873,.5873,-0.7699,.7699,-0.9041,.9041,-0.9816,.9816],m=[.2491,.2491,.2335,.2335,.2032,.2032,.1601,.1601,.1069,.1069,.0472,.0472],n=0;for(var o=0;od;d+=2){var f=[{x:+a[d-2],y:+a[d-1]},{x:+a[d],y:+a[d+1]},{x:+a[d+2],y:+a[d+3]},{x:+a[d+4],y:+a[d+5]}];b?d?e-4==d?f[3]={x:+a[0],y:+a[1]}:e-2==d&&(f[2]={x:+a[0],y:+a[1]},f[3]={x:+a[2],y:+a[3]}):f[0]={x:+a[e-2],y:+a[e-1]}:e-4==d?f[3]=f[2]:d||(f[0]={x:+a[d],y:+a[d+1]}),c.push(["C",(-f[0].x+6*f[1].x+f[2].x)/6,(-f[0].y+6*f[1].y+f[2].y)/6,(f[1].x+6*f[2].x-f[3].x)/6,(f[1].y+6*f[2].y-f[3].y)/6,f[2].x,f[2].y])}return c}function bx(){return this.hex}function bv(a,b,c){function d(){var e=Array.prototype.slice.call(arguments,0),f=e.join("?"),h=d.cache=d.cache||{},i=d.count=d.count||[];if(h[g](f)){bu(i,f);return c?c(h[f]):h[f]}i.length>=1e3&&delete h[i.shift()],i.push(f),h[f]=a[m](b,e);return c?c(h[f]):h[f]}return d}function bu(a,b){for(var c=0,d=a.length;c',bl=bk.firstChild,bl.style.behavior="url(#default#VML)";if(!bl||typeof bl.adj!="object")return a.type=p;bk=null}a.svg=!(a.vml=a.type=="VML"),a._Paper=j,a.fn=k=j.prototype=a.prototype,a._id=0,a._oid=0,a.is=function(a,b){b=v.call(b);if(b=="finite")return!M[g](+a);if(b=="array")return a instanceof Array;return b=="null"&&a===null||b==typeof a&&a!==null||b=="object"&&a===Object(a)||b=="array"&&Array.isArray&&Array.isArray(a)||H.call(a).slice(8,-1).toLowerCase()==b},a.angle=function(b,c,d,e,f,g){if(f==null){var h=b-d,i=c-e;if(!h&&!i)return 0;return(180+w.atan2(-i,-h)*180/B+360)%360}return a.angle(b,c,f,g)-a.angle(d,e,f,g)},a.rad=function(a){return a%360*B/180},a.deg=function(a){return a*180/B%360},a.snapTo=function(b,c,d){d=a.is(d,"finite")?d:10;if(a.is(b,E)){var e=b.length;while(e--)if(z(b[e]-c)<=d)return b[e]}else{b=+b;var f=c%b;if(fb-d)return c-f+b}return c};var bn=a.createUUID=function(a,b){return function(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(a,b).toUpperCase()}}(/[xy]/g,function(a){var b=w.random()*16|0,c=a=="x"?b:b&3|8;return c.toString(16)});a.setWindow=function(b){eve("raphael.setWindow",a,h.win,b),h.win=b,h.doc=h.win.document,a._engine.initWin&&a._engine.initWin(h.win)};var bo=function(b){if(a.vml){var c=/^\s+|\s+$/g,d;try{var e=new ActiveXObject("htmlfile");e.write(""),e.close(),d=e.body}catch(f){d=createPopup().document.body}var g=d.createTextRange();bo=bv(function(a){try{d.style.color=r(a).replace(c,p);var b=g.queryCommandValue("ForeColor");b=(b&255)<<16|b&65280|(b&16711680)>>>16;return"#"+("000000"+b.toString(16)).slice(-6)}catch(e){return"none"}})}else{var i=h.doc.createElement("i");i.title="Rapha�l Colour Picker",i.style.display="none",h.doc.body.appendChild(i),bo=bv(function(a){i.style.color=a;return h.doc.defaultView.getComputedStyle(i,p).getPropertyValue("color")})}return bo(b)},bp=function(){return"hsb("+[this.h,this.s,this.b]+")"},bq=function(){return"hsl("+[this.h,this.s,this.l]+")"},br=function(){return this.hex},bs=function(b,c,d){c==null&&a.is(b,"object")&&"r"in b&&"g"in b&&"b"in b&&(d=b.b,c=b.g,b=b.r);if(c==null&&a.is(b,D)){var e=a.getRGB(b);b=e.r,c=e.g,d=e.b}if(b>1||c>1||d>1)b/=255,c/=255,d/=255;return[b,c,d]},bt=function(b,c,d,e){b*=255,c*=255,d*=255;var f={r:b,g:c,b:d,hex:a.rgb(b,c,d),toString:br};a.is(e,"finite")&&(f.opacity=e);return f};a.color=function(b){var c;a.is(b,"object")&&"h"in b&&"s"in b&&"b"in b?(c=a.hsb2rgb(b),b.r=c.r,b.g=c.g,b.b=c.b,b.hex=c.hex):a.is(b,"object")&&"h"in b&&"s"in b&&"l"in b?(c=a.hsl2rgb(b),b.r=c.r,b.g=c.g,b.b=c.b,b.hex=c.hex):(a.is(b,"string")&&(b=a.getRGB(b)),a.is(b,"object")&&"r"in b&&"g"in b&&"b"in b?(c=a.rgb2hsl(b),b.h=c.h,b.s=c.s,b.l=c.l,c=a.rgb2hsb(b),b.v=c.b):(b={hex:"none"},b.r=b.g=b.b=b.h=b.s=b.v=b.l=-1)),b.toString=br;return b},a.hsb2rgb=function(a,b,c,d){this.is(a,"object")&&"h"in a&&"s"in a&&"b"in a&&(c=a.b,b=a.s,a=a.h,d=a.o),a*=360;var e,f,g,h,i;a=a%360/60,i=c*b,h=i*(1-z(a%2-1)),e=f=g=c-i,a=~~a,e+=[i,h,0,0,h,i][a],f+=[h,i,i,h,0,0][a],g+=[0,0,h,i,i,h][a];return bt(e,f,g,d)},a.hsl2rgb=function(a,b,c,d){this.is(a,"object")&&"h"in a&&"s"in a&&"l"in a&&(c=a.l,b=a.s,a=a.h);if(a>1||b>1||c>1)a/=360,b/=100,c/=100;a*=360;var e,f,g,h,i;a=a%360/60,i=2*b*(c<.5?c:1-c),h=i*(1-z(a%2-1)),e=f=g=c-i/2,a=~~a,e+=[i,h,0,0,h,i][a],f+=[h,i,i,h,0,0][a],g+=[0,0,h,i,i,h][a];return bt(e,f,g,d)},a.rgb2hsb=function(a,b,c){c=bs(a,b,c),a=c[0],b=c[1],c=c[2];var d,e,f,g;f=x(a,b,c),g=f-y(a,b,c),d=g==0?null:f==a?(b-c)/g:f==b?(c-a)/g+2:(a-b)/g+4,d=(d+360)%6*60/360,e=g==0?0:g/f;return{h:d,s:e,b:f,toString:bp}},a.rgb2hsl=function(a,b,c){c=bs(a,b,c),a=c[0],b=c[1],c=c[2];var d,e,f,g,h,i;g=x(a,b,c),h=y(a,b,c),i=g-h,d=i==0?null:g==a?(b-c)/i:g==b?(c-a)/i+2:(a-b)/i+4,d=(d+360)%6*60/360,f=(g+h)/2,e=i==0?0:f<.5?i/(2*f):i/(2-2*f);return{h:d,s:e,l:f,toString:bq}},a._path2string=function(){return this.join(",").replace(Y,"$1")};var bw=a._preload=function(a,b){var c=h.doc.createElement("img");c.style.cssText="position:absolute;left:-9999em;top:-9999em",c.onload=function(){b.call(this),this.onload=null,h.doc.body.removeChild(this)},c.onerror=function(){h.doc.body.removeChild(this)},h.doc.body.appendChild(c),c.src=a};a.getRGB=bv(function(b){if(!b||!!((b=r(b)).indexOf("-")+1))return{r:-1,g:-1,b:-1,hex:"none",error:1,toString:bx};if(b=="none")return{r:-1,g:-1,b:-1,hex:"none",toString:bx};!X[g](b.toLowerCase().substring(0,2))&&b.charAt()!="#"&&(b=bo(b));var c,d,e,f,h,i,j,k=b.match(L);if(k){k[2]&&(f=R(k[2].substring(5),16),e=R(k[2].substring(3,5),16),d=R(k[2].substring(1,3),16)),k[3]&&(f=R((i=k[3].charAt(3))+i,16),e=R((i=k[3].charAt(2))+i,16),d=R((i=k[3].charAt(1))+i,16)),k[4]&&(j=k[4][s](W),d=Q(j[0]),j[0].slice(-1)=="%"&&(d*=2.55),e=Q(j[1]),j[1].slice(-1)=="%"&&(e*=2.55),f=Q(j[2]),j[2].slice(-1)=="%"&&(f*=2.55),k[1].toLowerCase().slice(0,4)=="rgba"&&(h=Q(j[3])),j[3]&&j[3].slice(-1)=="%"&&(h/=100));if(k[5]){j=k[5][s](W),d=Q(j[0]),j[0].slice(-1)=="%"&&(d*=2.55),e=Q(j[1]),j[1].slice(-1)=="%"&&(e*=2.55),f=Q(j[2]),j[2].slice(-1)=="%"&&(f*=2.55),(j[0].slice(-3)=="deg"||j[0].slice(-1)=="�")&&(d/=360),k[1].toLowerCase().slice(0,4)=="hsba"&&(h=Q(j[3])),j[3]&&j[3].slice(-1)=="%"&&(h/=100);return a.hsb2rgb(d,e,f,h)}if(k[6]){j=k[6][s](W),d=Q(j[0]),j[0].slice(-1)=="%"&&(d*=2.55),e=Q(j[1]),j[1].slice(-1)=="%"&&(e*=2.55),f=Q(j[2]),j[2].slice(-1)=="%"&&(f*=2.55),(j[0].slice(-3)=="deg"||j[0].slice(-1)=="�")&&(d/=360),k[1].toLowerCase().slice(0,4)=="hsla"&&(h=Q(j[3])),j[3]&&j[3].slice(-1)=="%"&&(h/=100);return a.hsl2rgb(d,e,f,h)}k={r:d,g:e,b:f,toString:bx},k.hex="#"+(16777216|f|e<<8|d<<16).toString(16).slice(1),a.is(h,"finite")&&(k.opacity=h);return k}return{r:-1,g:-1,b:-1,hex:"none",error:1,toString:bx}},a),a.hsb=bv(function(b,c,d){return a.hsb2rgb(b,c,d).hex}),a.hsl=bv(function(b,c,d){return a.hsl2rgb(b,c,d).hex}),a.rgb=bv(function(a,b,c){return"#"+(16777216|c|b<<8|a<<16).toString(16).slice(1)}),a.getColor=function(a){var b=this.getColor.start=this.getColor.start||{h:0,s:1,b:a||.75},c=this.hsb2rgb(b.h,b.s,b.b);b.h+=.075,b.h>1&&(b.h=0,b.s-=.2,b.s<=0&&(this.getColor.start={h:0,s:1,b:b.b}));return c.hex},a.getColor.reset=function(){delete this.start},a.parsePathString=function(b){if(!b)return null;var c=bz(b);if(c.arr)return bJ(c.arr);var d={a:7,c:6,h:1,l:2,m:2,r:4,q:4,s:4,t:2,v:1,z:0},e=[];a.is(b,E)&&a.is(b[0],E)&&(e=bJ(b)),e.length||r(b).replace(Z,function(a,b,c){var f=[],g=b.toLowerCase();c.replace(_,function(a,b){b&&f.push(+b)}),g=="m"&&f.length>2&&(e.push([b][n](f.splice(0,2))),g="l",b=b=="m"?"l":"L");if(g=="r")e.push([b][n](f));else while(f.length>=d[g]){e.push([b][n](f.splice(0,d[g])));if(!d[g])break}}),e.toString=a._path2string,c.arr=bJ(e);return e},a.parseTransformString=bv(function(b){if(!b)return null;var c={r:3,s:4,t:2,m:6},d=[];a.is(b,E)&&a.is(b[0],E)&&(d=bJ(b)),d.length||r(b).replace($,function(a,b,c){var e=[],f=v.call(b);c.replace(_,function(a,b){b&&e.push(+b)}),d.push([b][n](e))}),d.toString=a._path2string;return d});var bz=function(a){var b=bz.ps=bz.ps||{};b[a]?b[a].sleep=100:b[a]={sleep:100},setTimeout(function(){for(var c in b)b[g](c)&&c!=a&&(b[c].sleep--,!b[c].sleep&&delete b[c])});return b[a]};a.findDotsAtSegment=function(a,b,c,d,e,f,g,h,i){var j=1-i,k=A(j,3),l=A(j,2),m=i*i,n=m*i,o=k*a+l*3*i*c+j*3*i*i*e+n*g,p=k*b+l*3*i*d+j*3*i*i*f+n*h,q=a+2*i*(c-a)+m*(e-2*c+a),r=b+2*i*(d-b)+m*(f-2*d+b),s=c+2*i*(e-c)+m*(g-2*e+c),t=d+2*i*(f-d)+m*(h-2*f+d),u=j*a+i*c,v=j*b+i*d,x=j*e+i*g,y=j*f+i*h,z=90-w.atan2(q-s,r-t)*180/B;(q>s||r=a.x&&b<=a.x2&&c>=a.y&&c<=a.y2},a.isBBoxIntersect=function(b,c){var d=a.isPointInsideBBox;return d(c,b.x,b.y)||d(c,b.x2,b.y)||d(c,b.x,b.y2)||d(c,b.x2,b.y2)||d(b,c.x,c.y)||d(b,c.x2,c.y)||d(b,c.x,c.y2)||d(b,c.x2,c.y2)||(b.xc.x||c.xb.x)&&(b.yc.y||c.yb.y)},a.pathIntersection=function(a,b){return bH(a,b)},a.pathIntersectionNumber=function(a,b){return bH(a,b,1)},a.isPointInsidePath=function(b,c,d){var e=a.pathBBox(b);return a.isPointInsideBBox(e,c,d)&&bH(b,[["M",c,d],["H",e.x2+10]],1)%2==1},a._removedFactory=function(a){return function(){eve("raphael.log",null,"Rapha�l: you are calling to method �"+a+"� of removed object",a)}};var bI=a.pathBBox=function(a){var b=bz(a);if(b.bbox)return b.bbox;if(!a)return{x:0,y:0,width:0,height:0,x2:0,y2:0};a=bR(a);var c=0,d=0,e=[],f=[],g;for(var h=0,i=a.length;h1&&(v=w.sqrt(v),c=v*c,d=v*d);var x=c*c,y=d*d,A=(f==g?-1:1)*w.sqrt(z((x*y-x*u*u-y*t*t)/(x*u*u+y*t*t))),C=A*c*u/d+(a+h)/2,D=A*-d*t/c+(b+i)/2,E=w.asin(((b-D)/d).toFixed(9)),F=w.asin(((i-D)/d).toFixed(9));E=aF&&(E=E-B*2),!g&&F>E&&(F=F-B*2)}else E=j[0],F=j[1],C=j[2],D=j[3];var G=F-E;if(z(G)>k){var H=F,I=h,J=i;F=E+k*(g&&F>E?1:-1),h=C+c*w.cos(F),i=D+d*w.sin(F),m=bO(h,i,c,d,e,0,g,I,J,[F,H,C,D])}G=F-E;var K=w.cos(E),L=w.sin(E),M=w.cos(F),N=w.sin(F),O=w.tan(G/4),P=4/3*c*O,Q=4/3*d*O,R=[a,b],S=[a+P*L,b-Q*K],T=[h+P*N,i-Q*M],U=[h,i];S[0]=2*R[0]-S[0],S[1]=2*R[1]-S[1];if(j)return[S,T,U][n](m);m=[S,T,U][n](m).join()[s](",");var V=[];for(var W=0,X=m.length;W"1e12"&&(l=.5),z(n)>"1e12"&&(n=.5),l>0&&l<1&&(q=bP(a,b,c,d,e,f,g,h,l),p.push(q.x),o.push(q.y)),n>0&&n<1&&(q=bP(a,b,c,d,e,f,g,h,n),p.push(q.x),o.push(q.y)),i=f-2*d+b-(h-2*f+d),j=2*(d-b)-2*(f-d),k=b-d,l=(-j+w.sqrt(j*j-4*i*k))/2/i,n=(-j-w.sqrt(j*j-4*i*k))/2/i,z(l)>"1e12"&&(l=.5),z(n)>"1e12"&&(n=.5),l>0&&l<1&&(q=bP(a,b,c,d,e,f,g,h,l),p.push(q.x),o.push(q.y)),n>0&&n<1&&(q=bP(a,b,c,d,e,f,g,h,n),p.push(q.x),o.push(q.y));return{min:{x:y[m](0,p),y:y[m](0,o)},max:{x:x[m](0,p),y:x[m](0,o)}}}),bR=a._path2curve=bv(function(a,b){var c=!b&&bz(a);if(!b&&c.curve)return bJ(c.curve);var d=bL(a),e=b&&bL(b),f={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},g={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},h=function(a,b){var c,d;if(!a)return["C",b.x,b.y,b.x,b.y,b.x,b.y];!(a[0]in{T:1,Q:1})&&(b.qx=b.qy=null);switch(a[0]){case"M":b.X=a[1],b.Y=a[2];break;case"A":a=["C"][n](bO[m](0,[b.x,b.y][n](a.slice(1))));break;case"S":c=b.x+(b.x-(b.bx||b.x)),d=b.y+(b.y-(b.by||b.y)),a=["C",c,d][n](a.slice(1));break;case"T":b.qx=b.x+(b.x-(b.qx||b.x)),b.qy=b.y+(b.y-(b.qy||b.y)),a=["C"][n](bN(b.x,b.y,b.qx,b.qy,a[1],a[2]));break;case"Q":b.qx=a[1],b.qy=a[2],a=["C"][n](bN(b.x,b.y,a[1],a[2],a[3],a[4]));break;case"L":a=["C"][n](bM(b.x,b.y,a[1],a[2]));break;case"H":a=["C"][n](bM(b.x,b.y,a[1],b.y));break;case"V":a=["C"][n](bM(b.x,b.y,b.x,a[1]));break;case"Z":a=["C"][n](bM(b.x,b.y,b.X,b.Y))}return a},i=function(a,b){if(a[b].length>7){a[b].shift();var c=a[b];while(c.length)a.splice(b++,0,["C"][n](c.splice(0,6)));a.splice(b,1),l=x(d.length,e&&e.length||0)}},j=function(a,b,c,f,g){a&&b&&a[g][0]=="M"&&b[g][0]!="M"&&(b.splice(g,0,["M",f.x,f.y]),c.bx=0,c.by=0,c.x=a[g][1],c.y=a[g][2],l=x(d.length,e&&e.length||0))};for(var k=0,l=x(d.length,e&&e.length||0);ke){if(c&&!l.start){m=cs(g,h,i[1],i[2],i[3],i[4],i[5],i[6],e-n),k+=["C"+m.start.x,m.start.y,m.m.x,m.m.y,m.x,m.y];if(f)return k;l.start=k,k=["M"+m.x,m.y+"C"+m.n.x,m.n.y,m.end.x,m.end.y,i[5],i[6]].join(),n+=j,g=+i[5],h=+i[6];continue}if(!b&&!c){m=cs(g,h,i[1],i[2],i[3],i[4],i[5],i[6],e-n);return{x:m.x,y:m.y,alpha:m.alpha}}}n+=j,g=+i[5],h=+i[6]}k+=i.shift()+i}l.end=k,m=b?n:c?l:a.findDotsAtSegment(g,h,i[0],i[1],i[2],i[3],i[4],i[5],1),m.alpha&&(m={x:m.x,y:m.y,alpha:m.alpha});return m}},cu=ct(1),cv=ct(),cw=ct(0,1);a.getTotalLength=cu,a.getPointAtLength=cv,a.getSubpath=function(a,b,c){if(this.getTotalLength(a)-c<1e-6)return cw(a,b).end;var d=cw(a,c,1);return b?cw(d,b).end:d},cl.getTotalLength=function(){if(this.type=="path"){if(this.node.getTotalLength)return this.node.getTotalLength();return cu(this.attrs.path)}},cl.getPointAtLength=function(a){if(this.type=="path")return cv(this.attrs.path,a)},cl.getSubpath=function(b,c){if(this.type=="path")return a.getSubpath(this.attrs.path,b,c)};var cx=a.easing_formulas={linear:function(a){return a},"<":function(a){return A(a,1.7)},">":function(a){return A(a,.48)},"<>":function(a){var b=.48-a/1.04,c=w.sqrt(.1734+b*b),d=c-b,e=A(z(d),1/3)*(d<0?-1:1),f=-c-b,g=A(z(f),1/3)*(f<0?-1:1),h=e+g+.5;return(1-h)*3*h*h+h*h*h},backIn:function(a){var b=1.70158;return a*a*((b+1)*a-b)},backOut:function(a){a=a-1;var b=1.70158;return a*a*((b+1)*a+b)+1},elastic:function(a){if(a==!!a)return a;return A(2,-10*a)*w.sin((a-.075)*2*B/.3)+1},bounce:function(a){var b=7.5625,c=2.75,d;a<1/c?d=b*a*a:a<2/c?(a-=1.5/c,d=b*a*a+.75):a<2.5/c?(a-=2.25/c,d=b*a*a+.9375):(a-=2.625/c,d=b*a*a+.984375);return d}};cx.easeIn=cx["ease-in"]=cx["<"],cx.easeOut=cx["ease-out"]=cx[">"],cx.easeInOut=cx["ease-in-out"]=cx["<>"],cx["back-in"]=cx.backIn,cx["back-out"]=cx.backOut;var cy=[],cz=window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(a){setTimeout(a,16)},cA=function(){var b=+(new Date),c=0;for(;c1&&!d.next){for(s in k)k[g](s)&&(r[s]=d.totalOrigin[s]);d.el.attr(r),cE(d.anim,d.el,d.anim.percents[0],null,d.totalOrigin,d.repeat-1)}d.next&&!d.stop&&cE(d.anim,d.el,d.next,null,d.totalOrigin,d.repeat)}}a.svg&&m&&m.paper&&m.paper.safari(),cy.length&&cz(cA)},cB=function(a){return a>255?255:a<0?0:a};cl.animateWith=function(b,c,d,e,f,g){var h=this;if(h.removed){g&&g.call(h);return h}var i=d instanceof cD?d:a.animation(d,e,f,g),j,k;cE(i,h,i.percents[0],null,h.attr());for(var l=0,m=cy.length;l.5)*2-1;i(m-.5,2)+i(n-.5,2)>.25&&(n=f.sqrt(.25-i(m-.5,2))*e+.5)&&n!=.5&&(n=n.toFixed(5)-1e-5*e)}return l}),e=e.split(/\s*\-\s*/);if(j=="linear"){var t=e.shift();t=-d(t);if(isNaN(t))return null;var u=[0,0,f.cos(a.rad(t)),f.sin(a.rad(t))],v=1/(g(h(u[2]),h(u[3]))||1);u[2]*=v,u[3]*=v,u[2]<0&&(u[0]=-u[2],u[2]=0),u[3]<0&&(u[1]=-u[3],u[3]=0)}var w=a._parseDots(e);if(!w)return null;k=k.replace(/[\(\)\s,\xb0#]/g,"_"),b.gradient&&k!=b.gradient.id&&(p.defs.removeChild(b.gradient),delete b.gradient);if(!b.gradient){s=q(j+"Gradient",{id:k}),b.gradient=s,q(s,j=="radial"?{fx:m,fy:n}:{x1:u[0],y1:u[1],x2:u[2],y2:u[3],gradientTransform:b.matrix.invert()}),p.defs.appendChild(s);for(var x=0,y=w.length;x1?G.opacity/100:G.opacity});case"stroke":G=a.getRGB(p),i.setAttribute(o,G.hex),o=="stroke"&&G[b]("opacity")&&q(i,{"stroke-opacity":G.opacity>1?G.opacity/100:G.opacity}),o=="stroke"&&d._.arrows&&("startString"in d._.arrows&&t(d,d._.arrows.startString),"endString"in d._.arrows&&t(d,d._.arrows.endString,1));break;case"gradient":(d.type=="circle"||d.type=="ellipse"||c(p).charAt()!="r")&&r(d,p);break;case"opacity":k.gradient&&!k[b]("stroke-opacity")&&q(i,{"stroke-opacity":p>1?p/100:p});case"fill-opacity":if(k.gradient){H=a._g.doc.getElementById(i.getAttribute("fill").replace(/^url\(#|\)$/g,l)),H&&(I=H.getElementsByTagName("stop"),q(I[I.length-1],{"stop-opacity":p}));break};default:o=="font-size"&&(p=e(p,10)+"px");var J=o.replace(/(\-.)/g,function(a){return a.substring(1).toUpperCase()});i.style[J]=p,d._.dirty=1,i.setAttribute(o,p)}}y(d,f),i.style.visibility=m},x=1.2,y=function(d,f){if(d.type=="text"&&!!(f[b]("text")||f[b]("font")||f[b]("font-size")||f[b]("x")||f[b]("y"))){var g=d.attrs,h=d.node,i=h.firstChild?e(a._g.doc.defaultView.getComputedStyle(h.firstChild,l).getPropertyValue("font-size"),10):10;if(f[b]("text")){g.text=f.text;while(h.firstChild)h.removeChild(h.firstChild);var j=c(f.text).split("\n"),k=[],m;for(var n=0,o=j.length;n"));var $=X.getBoundingClientRect();t.W=m.w=($.right-$.left)/Y,t.H=m.h=($.bottom-$.top)/Y,t.X=m.x,t.Y=m.y+t.H/2,("x"in i||"y"in i)&&(t.path.v=a.format("m{0},{1}l{2},{1}",f(m.x*u),f(m.y*u),f(m.x*u)+1));var _=["x","y","text","font","font-family","font-weight","font-style","font-size"];for(var ba=0,bb=_.length;ba.25&&(c=e.sqrt(.25-i(b-.5,2))*((c>.5)*2-1)+.5),m=b+n+c);return o}),f=f.split(/\s*\-\s*/);if(l=="linear"){var p=f.shift();p=-d(p);if(isNaN(p))return null}var q=a._parseDots(f);if(!q)return null;b=b.shape||b.node;if(q.length){b.removeChild(g),g.on=!0,g.method="none",g.color=q[0].color,g.color2=q[q.length-1].color;var r=[];for(var s=0,t=q.length;s')}}catch(c){F=function(a){return b.createElement("<"+a+' xmlns="urn:schemas-microsoft.com:vml" class="rvml">')}}},a._engine.initWin(a._g.win),a._engine.create=function(){var b=a._getContainer.apply(0,arguments),c=b.container,d=b.height,e,f=b.width,g=b.x,h=b.y;if(!c)throw new Error("VML container not found.");var i=new a._Paper,j=i.canvas=a._g.doc.createElement("div"),k=j.style;g=g||0,h=h||0,f=f||512,d=d||342,i.width=f,i.height=d,f==+f&&(f+="px"),d==+d&&(d+="px"),i.coordsize=u*1e3+n+u*1e3,i.coordorigin="0 0",i.span=a._g.doc.createElement("span"),i.span.style.cssText="position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;",j.appendChild(i.span),k.cssText=a.format("top:0;left:0;width:{0};height:{1};display:inline-block;position:relative;clip:rect(0 {0} {1} 0);overflow:hidden",f,d),c==1?(a._g.doc.body.appendChild(j),k.left=g+"px",k.top=h+"px",k.position="absolute"):c.firstChild?c.insertBefore(j,c.firstChild):c.appendChild(j),i.renderfix=function(){};return i},a.prototype.clear=function(){a.eve("raphael.clear",this),this.canvas.innerHTML=o,this.span=a._g.doc.createElement("span"),this.span.style.cssText="position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;display:inline;",this.canvas.appendChild(this.span),this.bottom=this.top=null},a.prototype.remove=function(){a.eve("raphael.remove",this),this.canvas.parentNode.removeChild(this.canvas);for(var b in this)this[b]=typeof this[b]=="function"?a._removedFactory(b):null;return!0};var G=a.st;for(var H in E)E[b](H)&&!G[b](H)&&(G[H]=function(a){return function(){var b=arguments;return this.forEach(function(c){c[a].apply(c,b)})}}(H))}(window.Raphael) + +JustGage=function(x){if(!x.id){alert("Missing id parameter for gauge!");return false}if(!document.getElementById(x.id)){alert('No element with id: "'+x.id+'" found!');return false}this.config={id:x.id,title:(x.title)?x.title:"Title",titleFontColor:(x.titleFontColor)?x.titleFontColor:"#999999",value:(x.value)?x.value:0,valueFontColor:(x.valueFontColor)?x.valueFontColor:"#010101",min:(x.min)?x.min:0,max:(x.max)?x.max:100,showMinMax:(x.showMinMax!=null)?x.showMinMax:true,gaugeWidthScale:(x.gaugeWidthScale)?x.gaugeWidthScale:1,gaugeColor:(x.gaugeColor)?x.gaugeColor:"#edebeb",label:(x.label)?x.label:"",showInnerShadow:(x.showInnerShadow!=null)?x.showInnerShadow:true,shadowOpacity:(x.shadowOpacity)?x.shadowOpacity:0.2,shadowSize:(x.shadowSize)?x.shadowSize:5,shadowVerticalOffset:(x.shadowVerticalOffset)?x.shadowVerticalOffset:3,levelColors:(x.levelColors)?x.levelColors:percentColors,levelColorsGradient:(x.levelColorsGradient!=null)?x.levelColorsGradient:true,labelFontColor:(x.labelFontColor)?x.labelFontColor:"#b3b3b3",startAnimationTime:(x.startAnimationTime)?x.startAnimationTime:700,startAnimationType:(x.startAnimationType)?x.startAnimationType:">",refreshAnimationTime:(x.refreshAnimationTime)?x.refreshAnimationTime:700,refreshAnimationType:(x.refreshAnimationType)?x.refreshAnimationType:">"};if(x.value>this.config.max){this.config.value=this.config.max}if(x.value1.25){c=1.25*q;l=q}else{c=d;l=d/1.25}var i=(d-c)/2;var g=(q-l)/2;var s=((l/8)>10)?(l/10):10;var n=i+c/2;var m=g+l/6.5;var k=((l/6.4)>16)?(l/6.4):16;var r=i+c/2;var p=g+l/1.4;var b=((l/16)>10)?(l/16):10;var f=i+c/2;var e=p+k/2+6;var j=((l/16)>10)?(l/16):10;var w=i+(c/10)+(c/6.666666666666667*this.config.gaugeWidthScale)/2;var v=g+l/1.126760563380282;var h=((l/16)>10)?(l/16):10;var u=i+c-(c/10)-(c/6.666666666666667*this.config.gaugeWidthScale)/2;var t=g+l/1.126760563380282;this.params={canvasW:d,canvasH:q,widgetW:c,widgetH:l,dx:i,dy:g,titleFontSize:s,titleX:n,titleY:m,valueFontSize:k,valueX:r,valueY:p,labelFontSize:b,labelX:f,labelY:e,minFontSize:j,minX:w,minY:v,maxFontSize:h,maxX:u,maxY:t};this.canvas.customAttributes.pki=function(K,L,N,E,O,F,D,y){var B=(1-(K-L)/(N-L))*Math.PI,G=E/2-E/10,J=G-E/6.666666666666667*y,C=E/2+F,A=O/1.25+D,H=E/2+F+G*Math.cos(B),P=O-(O-A)+D-G*Math.sin(B),M=E/2+F+J*Math.cos(B),z=O-(O-A)+D-J*Math.sin(B),I;I+="M"+(C-J)+","+A+" ";I+="L"+(C-G)+","+A+" ";I+="A"+G+","+G+" 0 0,1 "+H+","+P+" ";I+="L"+M+","+z+" ";I+="A"+J+","+J+" 0 0,0 "+(C-J)+","+A+" ";I+="z ";return{path:I}};this.gauge=this.canvas.path().attr({stroke:"none",fill:this.config.gaugeColor,pki:[this.config.max,this.config.min,this.config.max,this.params.widgetW,this.params.widgetH,this.params.dx,this.params.dy,this.config.gaugeWidthScale]});this.gauge.id=this.config.id+"-gauge";this.level=this.canvas.path().attr({stroke:"none",fill:getColorForPercentage((this.config.value-this.config.min)/(this.config.max-this.config.min),this.config.levelColors,this.config.levelColorsGradient),pki:[this.config.min,this.config.min,this.config.max,this.params.widgetW,this.params.widgetH,this.params.dx,this.params.dy,this.config.gaugeWidthScale]});this.level.id=this.config.id+"-level";this.txtTitle=this.canvas.text(this.params.titleX,this.params.titleY,this.config.title);this.txtTitle.attr({"font-size":this.params.titleFontSize,"font-weight":"bold","font-family":"Arial",fill:this.config.titleFontColor,"fill-opacity":"1"});this.txtTitle.id=this.config.id+"-txttitle";this.txtValue=this.canvas.text(this.params.valueX,this.params.valueY,this.originalValue);this.txtValue.attr({"font-size":this.params.valueFontSize,"font-weight":"bold","font-family":"Arial",fill:this.config.valueFontColor,"fill-opacity":"0"});this.txtValue.id=this.config.id+"-txtvalue";this.txtLabel=this.canvas.text(this.params.labelX,this.params.labelY,this.config.label);this.txtLabel.attr({"font-size":this.params.labelFontSize,"font-weight":"normal","font-family":"Arial",fill:this.config.labelFontColor,"fill-opacity":"0"});this.txtLabel.id=this.config.id+"-txtlabel";this.txtMin=this.canvas.text(this.params.minX,this.params.minY,this.config.min);this.txtMin.attr({"font-size":this.params.minFontSize,"font-weight":"normal","font-family":"Arial",fill:this.config.labelFontColor,"fill-opacity":(this.config.showMinMax==true)?"1":"0"});this.txtMin.id=this.config.id+"-txtmin";this.txtMax=this.canvas.text(this.params.maxX,this.params.maxY,this.config.max);this.txtMax.attr({"font-size":this.params.maxFontSize,"font-weight":"normal","font-family":"Arial",fill:this.config.labelFontColor,"fill-opacity":(this.config.showMinMax==true)?"1":"0"});this.txtMax.id=this.config.id+"-txtmax";var a=this.canvas.canvas.childNodes[1];var o="http://www.w3.org/2000/svg";if(ie<9){onCreateElementNsReady(function(){this.generateShadow()})}else{this.generateShadow(o,a)}this.level.animate({pki:[this.config.value,this.config.min,this.config.max,this.params.widgetW,this.params.widgetH,this.params.dx,this.params.dy,this.config.gaugeWidthScale]},this.config.startAnimationTime,this.config.startAnimationType);this.txtValue.animate({"fill-opacity":"1"},this.config.startAnimationTime,this.config.startAnimationType);this.txtLabel.animate({"fill-opacity":"1"},this.config.startAnimationTime,this.config.startAnimationType)};JustGage.prototype.refresh=function(b){originalVal=b;if(b>this.config.max){b=this.config.max}if(b",b[0]){}return a>4?a:c}()); diff --git a/bin_UPWS/main/jquery-1.8.3.min.js b/bin_UPWS/main/jquery-1.8.3.min.js new file mode 100644 index 0000000..c2a6b17 --- /dev/null +++ b/bin_UPWS/main/jquery-1.8.3.min.js @@ -0,0 +1,2 @@ +/*! jQuery v1.8.3 jquery.com | jquery.org/license */ +(function(e,t){function _(e){var t=M[e]={};return v.each(e.split(y),function(e,n){t[n]=!0}),t}function H(e,n,r){if(r===t&&e.nodeType===1){var i="data-"+n.replace(P,"-$1").toLowerCase();r=e.getAttribute(i);if(typeof r=="string"){try{r=r==="true"?!0:r==="false"?!1:r==="null"?null:+r+""===r?+r:D.test(r)?v.parseJSON(r):r}catch(s){}v.data(e,n,r)}else r=t}return r}function B(e){var t;for(t in e){if(t==="data"&&v.isEmptyObject(e[t]))continue;if(t!=="toJSON")return!1}return!0}function et(){return!1}function tt(){return!0}function ut(e){return!e||!e.parentNode||e.parentNode.nodeType===11}function at(e,t){do e=e[t];while(e&&e.nodeType!==1);return e}function ft(e,t,n){t=t||0;if(v.isFunction(t))return v.grep(e,function(e,r){var i=!!t.call(e,r,e);return i===n});if(t.nodeType)return v.grep(e,function(e,r){return e===t===n});if(typeof t=="string"){var r=v.grep(e,function(e){return e.nodeType===1});if(it.test(t))return v.filter(t,r,!n);t=v.filter(t,r)}return v.grep(e,function(e,r){return v.inArray(e,t)>=0===n})}function lt(e){var t=ct.split("|"),n=e.createDocumentFragment();if(n.createElement)while(t.length)n.createElement(t.pop());return n}function Lt(e,t){return e.getElementsByTagName(t)[0]||e.appendChild(e.ownerDocument.createElement(t))}function At(e,t){if(t.nodeType!==1||!v.hasData(e))return;var n,r,i,s=v._data(e),o=v._data(t,s),u=s.events;if(u){delete o.handle,o.events={};for(n in u)for(r=0,i=u[n].length;r").appendTo(i.body),n=t.css("display");t.remove();if(n==="none"||n===""){Pt=i.body.appendChild(Pt||v.extend(i.createElement("iframe"),{frameBorder:0,width:0,height:0}));if(!Ht||!Pt.createElement)Ht=(Pt.contentWindow||Pt.contentDocument).document,Ht.write(""),Ht.close();t=Ht.body.appendChild(Ht.createElement(e)),n=Dt(t,"display"),i.body.removeChild(Pt)}return Wt[e]=n,n}function fn(e,t,n,r){var i;if(v.isArray(t))v.each(t,function(t,i){n||sn.test(e)?r(e,i):fn(e+"["+(typeof i=="object"?t:"")+"]",i,n,r)});else if(!n&&v.type(t)==="object")for(i in t)fn(e+"["+i+"]",t[i],n,r);else r(e,t)}function Cn(e){return function(t,n){typeof t!="string"&&(n=t,t="*");var r,i,s,o=t.toLowerCase().split(y),u=0,a=o.length;if(v.isFunction(n))for(;u)[^>]*$|#([\w\-]*)$)/,E=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,S=/^[\],:{}\s]*$/,x=/(?:^|:|,)(?:\s*\[)+/g,T=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,N=/"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g,C=/^-ms-/,k=/-([\da-z])/gi,L=function(e,t){return(t+"").toUpperCase()},A=function(){i.addEventListener?(i.removeEventListener("DOMContentLoaded",A,!1),v.ready()):i.readyState==="complete"&&(i.detachEvent("onreadystatechange",A),v.ready())},O={};v.fn=v.prototype={constructor:v,init:function(e,n,r){var s,o,u,a;if(!e)return this;if(e.nodeType)return this.context=this[0]=e,this.length=1,this;if(typeof e=="string"){e.charAt(0)==="<"&&e.charAt(e.length-1)===">"&&e.length>=3?s=[null,e,null]:s=w.exec(e);if(s&&(s[1]||!n)){if(s[1])return n=n instanceof v?n[0]:n,a=n&&n.nodeType?n.ownerDocument||n:i,e=v.parseHTML(s[1],a,!0),E.test(s[1])&&v.isPlainObject(n)&&this.attr.call(e,n,!0),v.merge(this,e);o=i.getElementById(s[2]);if(o&&o.parentNode){if(o.id!==s[2])return r.find(e);this.length=1,this[0]=o}return this.context=i,this.selector=e,this}return!n||n.jquery?(n||r).find(e):this.constructor(n).find(e)}return v.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),v.makeArray(e,this))},selector:"",jquery:"1.8.3",length:0,size:function(){return this.length},toArray:function(){return l.call(this)},get:function(e){return e==null?this.toArray():e<0?this[this.length+e]:this[e]},pushStack:function(e,t,n){var r=v.merge(this.constructor(),e);return r.prevObject=this,r.context=this.context,t==="find"?r.selector=this.selector+(this.selector?" ":"")+n:t&&(r.selector=this.selector+"."+t+"("+n+")"),r},each:function(e,t){return v.each(this,e,t)},ready:function(e){return v.ready.promise().done(e),this},eq:function(e){return e=+e,e===-1?this.slice(e):this.slice(e,e+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(l.apply(this,arguments),"slice",l.call(arguments).join(","))},map:function(e){return this.pushStack(v.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:[].sort,splice:[].splice},v.fn.init.prototype=v.fn,v.extend=v.fn.extend=function(){var e,n,r,i,s,o,u=arguments[0]||{},a=1,f=arguments.length,l=!1;typeof u=="boolean"&&(l=u,u=arguments[1]||{},a=2),typeof u!="object"&&!v.isFunction(u)&&(u={}),f===a&&(u=this,--a);for(;a0)return;r.resolveWith(i,[v]),v.fn.trigger&&v(i).trigger("ready").off("ready")},isFunction:function(e){return v.type(e)==="function"},isArray:Array.isArray||function(e){return v.type(e)==="array"},isWindow:function(e){return e!=null&&e==e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return e==null?String(e):O[h.call(e)]||"object"},isPlainObject:function(e){if(!e||v.type(e)!=="object"||e.nodeType||v.isWindow(e))return!1;try{if(e.constructor&&!p.call(e,"constructor")&&!p.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(n){return!1}var r;for(r in e);return r===t||p.call(e,r)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw new Error(e)},parseHTML:function(e,t,n){var r;return!e||typeof e!="string"?null:(typeof t=="boolean"&&(n=t,t=0),t=t||i,(r=E.exec(e))?[t.createElement(r[1])]:(r=v.buildFragment([e],t,n?null:[]),v.merge([],(r.cacheable?v.clone(r.fragment):r.fragment).childNodes)))},parseJSON:function(t){if(!t||typeof t!="string")return null;t=v.trim(t);if(e.JSON&&e.JSON.parse)return e.JSON.parse(t);if(S.test(t.replace(T,"@").replace(N,"]").replace(x,"")))return(new Function("return "+t))();v.error("Invalid JSON: "+t)},parseXML:function(n){var r,i;if(!n||typeof n!="string")return null;try{e.DOMParser?(i=new DOMParser,r=i.parseFromString(n,"text/xml")):(r=new ActiveXObject("Microsoft.XMLDOM"),r.async="false",r.loadXML(n))}catch(s){r=t}return(!r||!r.documentElement||r.getElementsByTagName("parsererror").length)&&v.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&g.test(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(C,"ms-").replace(k,L)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,n,r){var i,s=0,o=e.length,u=o===t||v.isFunction(e);if(r){if(u){for(i in e)if(n.apply(e[i],r)===!1)break}else for(;s0&&e[0]&&e[a-1]||a===0||v.isArray(e));if(f)for(;u-1)a.splice(n,1),i&&(n<=o&&o--,n<=u&&u--)}),this},has:function(e){return v.inArray(e,a)>-1},empty:function(){return a=[],this},disable:function(){return a=f=n=t,this},disabled:function(){return!a},lock:function(){return f=t,n||c.disable(),this},locked:function(){return!f},fireWith:function(e,t){return t=t||[],t=[e,t.slice?t.slice():t],a&&(!r||f)&&(i?f.push(t):l(t)),this},fire:function(){return c.fireWith(this,arguments),this},fired:function(){return!!r}};return c},v.extend({Deferred:function(e){var t=[["resolve","done",v.Callbacks("once memory"),"resolved"],["reject","fail",v.Callbacks("once memory"),"rejected"],["notify","progress",v.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return v.Deferred(function(n){v.each(t,function(t,r){var s=r[0],o=e[t];i[r[1]](v.isFunction(o)?function(){var e=o.apply(this,arguments);e&&v.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[s+"With"](this===i?n:this,[e])}:n[s])}),e=null}).promise()},promise:function(e){return e!=null?v.extend(e,r):r}},i={};return r.pipe=r.then,v.each(t,function(e,s){var o=s[2],u=s[3];r[s[1]]=o.add,u&&o.add(function(){n=u},t[e^1][2].disable,t[2][2].lock),i[s[0]]=o.fire,i[s[0]+"With"]=o.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=l.call(arguments),r=n.length,i=r!==1||e&&v.isFunction(e.promise)?r:0,s=i===1?e:v.Deferred(),o=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?l.call(arguments):r,n===u?s.notifyWith(t,n):--i||s.resolveWith(t,n)}},u,a,f;if(r>1){u=new Array(r),a=new Array(r),f=new Array(r);for(;t
a",n=p.getElementsByTagName("*"),r=p.getElementsByTagName("a")[0];if(!n||!r||!n.length)return{};s=i.createElement("select"),o=s.appendChild(i.createElement("option")),u=p.getElementsByTagName("input")[0],r.style.cssText="top:1px;float:left;opacity:.5",t={leadingWhitespace:p.firstChild.nodeType===3,tbody:!p.getElementsByTagName("tbody").length,htmlSerialize:!!p.getElementsByTagName("link").length,style:/top/.test(r.getAttribute("style")),hrefNormalized:r.getAttribute("href")==="/a",opacity:/^0.5/.test(r.style.opacity),cssFloat:!!r.style.cssFloat,checkOn:u.value==="on",optSelected:o.selected,getSetAttribute:p.className!=="t",enctype:!!i.createElement("form").enctype,html5Clone:i.createElement("nav").cloneNode(!0).outerHTML!=="<:nav>",boxModel:i.compatMode==="CSS1Compat",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,boxSizingReliable:!0,pixelPosition:!1},u.checked=!0,t.noCloneChecked=u.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!o.disabled;try{delete p.test}catch(d){t.deleteExpando=!1}!p.addEventListener&&p.attachEvent&&p.fireEvent&&(p.attachEvent("onclick",h=function(){t.noCloneEvent=!1}),p.cloneNode(!0).fireEvent("onclick"),p.detachEvent("onclick",h)),u=i.createElement("input"),u.value="t",u.setAttribute("type","radio"),t.radioValue=u.value==="t",u.setAttribute("checked","checked"),u.setAttribute("name","t"),p.appendChild(u),a=i.createDocumentFragment(),a.appendChild(p.lastChild),t.checkClone=a.cloneNode(!0).cloneNode(!0).lastChild.checked,t.appendChecked=u.checked,a.removeChild(u),a.appendChild(p);if(p.attachEvent)for(l in{submit:!0,change:!0,focusin:!0})f="on"+l,c=f in p,c||(p.setAttribute(f,"return;"),c=typeof p[f]=="function"),t[l+"Bubbles"]=c;return v(function(){var n,r,s,o,u="padding:0;margin:0;border:0;display:block;overflow:hidden;",a=i.getElementsByTagName("body")[0];if(!a)return;n=i.createElement("div"),n.style.cssText="visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px",a.insertBefore(n,a.firstChild),r=i.createElement("div"),n.appendChild(r),r.innerHTML="
t
",s=r.getElementsByTagName("td"),s[0].style.cssText="padding:0;margin:0;border:0;display:none",c=s[0].offsetHeight===0,s[0].style.display="",s[1].style.display="none",t.reliableHiddenOffsets=c&&s[0].offsetHeight===0,r.innerHTML="",r.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",t.boxSizing=r.offsetWidth===4,t.doesNotIncludeMarginInBodyOffset=a.offsetTop!==1,e.getComputedStyle&&(t.pixelPosition=(e.getComputedStyle(r,null)||{}).top!=="1%",t.boxSizingReliable=(e.getComputedStyle(r,null)||{width:"4px"}).width==="4px",o=i.createElement("div"),o.style.cssText=r.style.cssText=u,o.style.marginRight=o.style.width="0",r.style.width="1px",r.appendChild(o),t.reliableMarginRight=!parseFloat((e.getComputedStyle(o,null)||{}).marginRight)),typeof r.style.zoom!="undefined"&&(r.innerHTML="",r.style.cssText=u+"width:1px;padding:1px;display:inline;zoom:1",t.inlineBlockNeedsLayout=r.offsetWidth===3,r.style.display="block",r.style.overflow="visible",r.innerHTML="
",r.firstChild.style.width="5px",t.shrinkWrapBlocks=r.offsetWidth!==3,n.style.zoom=1),a.removeChild(n),n=r=s=o=null}),a.removeChild(p),n=r=s=o=u=a=p=null,t}();var D=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,P=/([A-Z])/g;v.extend({cache:{},deletedIds:[],uuid:0,expando:"jQuery"+(v.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(e){return e=e.nodeType?v.cache[e[v.expando]]:e[v.expando],!!e&&!B(e)},data:function(e,n,r,i){if(!v.acceptData(e))return;var s,o,u=v.expando,a=typeof n=="string",f=e.nodeType,l=f?v.cache:e,c=f?e[u]:e[u]&&u;if((!c||!l[c]||!i&&!l[c].data)&&a&&r===t)return;c||(f?e[u]=c=v.deletedIds.pop()||v.guid++:c=u),l[c]||(l[c]={},f||(l[c].toJSON=v.noop));if(typeof n=="object"||typeof n=="function")i?l[c]=v.extend(l[c],n):l[c].data=v.extend(l[c].data,n);return s=l[c],i||(s.data||(s.data={}),s=s.data),r!==t&&(s[v.camelCase(n)]=r),a?(o=s[n],o==null&&(o=s[v.camelCase(n)])):o=s,o},removeData:function(e,t,n){if(!v.acceptData(e))return;var r,i,s,o=e.nodeType,u=o?v.cache:e,a=o?e[v.expando]:v.expando;if(!u[a])return;if(t){r=n?u[a]:u[a].data;if(r){v.isArray(t)||(t in r?t=[t]:(t=v.camelCase(t),t in r?t=[t]:t=t.split(" ")));for(i=0,s=t.length;i1,null,!1))},removeData:function(e){return this.each(function(){v.removeData(this,e)})}}),v.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=v._data(e,t),n&&(!r||v.isArray(n)?r=v._data(e,t,v.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=v.queue(e,t),r=n.length,i=n.shift(),s=v._queueHooks(e,t),o=function(){v.dequeue(e,t)};i==="inprogress"&&(i=n.shift(),r--),i&&(t==="fx"&&n.unshift("inprogress"),delete s.stop,i.call(e,o,s)),!r&&s&&s.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return v._data(e,n)||v._data(e,n,{empty:v.Callbacks("once memory").add(function(){v.removeData(e,t+"queue",!0),v.removeData(e,n,!0)})})}}),v.fn.extend({queue:function(e,n){var r=2;return typeof e!="string"&&(n=e,e="fx",r--),arguments.length1)},removeAttr:function(e){return this.each(function(){v.removeAttr(this,e)})},prop:function(e,t){return v.access(this,v.prop,e,t,arguments.length>1)},removeProp:function(e){return e=v.propFix[e]||e,this.each(function(){try{this[e]=t,delete this[e]}catch(n){}})},addClass:function(e){var t,n,r,i,s,o,u;if(v.isFunction(e))return this.each(function(t){v(this).addClass(e.call(this,t,this.className))});if(e&&typeof e=="string"){t=e.split(y);for(n=0,r=this.length;n=0)r=r.replace(" "+n[s]+" "," ");i.className=e?v.trim(r):""}}}return this},toggleClass:function(e,t){var n=typeof e,r=typeof t=="boolean";return v.isFunction(e)?this.each(function(n){v(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if(n==="string"){var i,s=0,o=v(this),u=t,a=e.split(y);while(i=a[s++])u=r?u:!o.hasClass(i),o[u?"addClass":"removeClass"](i)}else if(n==="undefined"||n==="boolean")this.className&&v._data(this,"__className__",this.className),this.className=this.className||e===!1?"":v._data(this,"__className__")||""})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;n=0)return!0;return!1},val:function(e){var n,r,i,s=this[0];if(!arguments.length){if(s)return n=v.valHooks[s.type]||v.valHooks[s.nodeName.toLowerCase()],n&&"get"in n&&(r=n.get(s,"value"))!==t?r:(r=s.value,typeof r=="string"?r.replace(R,""):r==null?"":r);return}return i=v.isFunction(e),this.each(function(r){var s,o=v(this);if(this.nodeType!==1)return;i?s=e.call(this,r,o.val()):s=e,s==null?s="":typeof s=="number"?s+="":v.isArray(s)&&(s=v.map(s,function(e){return e==null?"":e+""})),n=v.valHooks[this.type]||v.valHooks[this.nodeName.toLowerCase()];if(!n||!("set"in n)||n.set(this,s,"value")===t)this.value=s})}}),v.extend({valHooks:{option:{get:function(e){var t=e.attributes.value;return!t||t.specified?e.value:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,s=e.type==="select-one"||i<0,o=s?null:[],u=s?i+1:r.length,a=i<0?u:s?i:0;for(;a=0}),n.length||(e.selectedIndex=-1),n}}},attrFn:{},attr:function(e,n,r,i){var s,o,u,a=e.nodeType;if(!e||a===3||a===8||a===2)return;if(i&&v.isFunction(v.fn[n]))return v(e)[n](r);if(typeof e.getAttribute=="undefined")return v.prop(e,n,r);u=a!==1||!v.isXMLDoc(e),u&&(n=n.toLowerCase(),o=v.attrHooks[n]||(X.test(n)?F:j));if(r!==t){if(r===null){v.removeAttr(e,n);return}return o&&"set"in o&&u&&(s=o.set(e,r,n))!==t?s:(e.setAttribute(n,r+""),r)}return o&&"get"in o&&u&&(s=o.get(e,n))!==null?s:(s=e.getAttribute(n),s===null?t:s)},removeAttr:function(e,t){var n,r,i,s,o=0;if(t&&e.nodeType===1){r=t.split(y);for(;o=0}})});var $=/^(?:textarea|input|select)$/i,J=/^([^\.]*|)(?:\.(.+)|)$/,K=/(?:^|\s)hover(\.\S+|)\b/,Q=/^key/,G=/^(?:mouse|contextmenu)|click/,Y=/^(?:focusinfocus|focusoutblur)$/,Z=function(e){return v.event.special.hover?e:e.replace(K,"mouseenter$1 mouseleave$1")};v.event={add:function(e,n,r,i,s){var o,u,a,f,l,c,h,p,d,m,g;if(e.nodeType===3||e.nodeType===8||!n||!r||!(o=v._data(e)))return;r.handler&&(d=r,r=d.handler,s=d.selector),r.guid||(r.guid=v.guid++),a=o.events,a||(o.events=a={}),u=o.handle,u||(o.handle=u=function(e){return typeof v=="undefined"||!!e&&v.event.triggered===e.type?t:v.event.dispatch.apply(u.elem,arguments)},u.elem=e),n=v.trim(Z(n)).split(" ");for(f=0;f=0&&(y=y.slice(0,-1),a=!0),y.indexOf(".")>=0&&(b=y.split("."),y=b.shift(),b.sort());if((!s||v.event.customEvent[y])&&!v.event.global[y])return;n=typeof n=="object"?n[v.expando]?n:new v.Event(y,n):new v.Event(y),n.type=y,n.isTrigger=!0,n.exclusive=a,n.namespace=b.join("."),n.namespace_re=n.namespace?new RegExp("(^|\\.)"+b.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,h=y.indexOf(":")<0?"on"+y:"";if(!s){u=v.cache;for(f in u)u[f].events&&u[f].events[y]&&v.event.trigger(n,r,u[f].handle.elem,!0);return}n.result=t,n.target||(n.target=s),r=r!=null?v.makeArray(r):[],r.unshift(n),p=v.event.special[y]||{};if(p.trigger&&p.trigger.apply(s,r)===!1)return;m=[[s,p.bindType||y]];if(!o&&!p.noBubble&&!v.isWindow(s)){g=p.delegateType||y,l=Y.test(g+y)?s:s.parentNode;for(c=s;l;l=l.parentNode)m.push([l,g]),c=l;c===(s.ownerDocument||i)&&m.push([c.defaultView||c.parentWindow||e,g])}for(f=0;f=0:v.find(h,this,null,[s]).length),u[h]&&f.push(c);f.length&&w.push({elem:s,matches:f})}d.length>m&&w.push({elem:this,matches:d.slice(m)});for(r=0;r0?this.on(t,null,e,n):this.trigger(t)},Q.test(t)&&(v.event.fixHooks[t]=v.event.keyHooks),G.test(t)&&(v.event.fixHooks[t]=v.event.mouseHooks)}),function(e,t){function nt(e,t,n,r){n=n||[],t=t||g;var i,s,a,f,l=t.nodeType;if(!e||typeof e!="string")return n;if(l!==1&&l!==9)return[];a=o(t);if(!a&&!r)if(i=R.exec(e))if(f=i[1]){if(l===9){s=t.getElementById(f);if(!s||!s.parentNode)return n;if(s.id===f)return n.push(s),n}else if(t.ownerDocument&&(s=t.ownerDocument.getElementById(f))&&u(t,s)&&s.id===f)return n.push(s),n}else{if(i[2])return S.apply(n,x.call(t.getElementsByTagName(e),0)),n;if((f=i[3])&&Z&&t.getElementsByClassName)return S.apply(n,x.call(t.getElementsByClassName(f),0)),n}return vt(e.replace(j,"$1"),t,n,r,a)}function rt(e){return function(t){var n=t.nodeName.toLowerCase();return n==="input"&&t.type===e}}function it(e){return function(t){var n=t.nodeName.toLowerCase();return(n==="input"||n==="button")&&t.type===e}}function st(e){return N(function(t){return t=+t,N(function(n,r){var i,s=e([],n.length,t),o=s.length;while(o--)n[i=s[o]]&&(n[i]=!(r[i]=n[i]))})})}function ot(e,t,n){if(e===t)return n;var r=e.nextSibling;while(r){if(r===t)return-1;r=r.nextSibling}return 1}function ut(e,t){var n,r,s,o,u,a,f,l=L[d][e+" "];if(l)return t?0:l.slice(0);u=e,a=[],f=i.preFilter;while(u){if(!n||(r=F.exec(u)))r&&(u=u.slice(r[0].length)||u),a.push(s=[]);n=!1;if(r=I.exec(u))s.push(n=new m(r.shift())),u=u.slice(n.length),n.type=r[0].replace(j," ");for(o in i.filter)(r=J[o].exec(u))&&(!f[o]||(r=f[o](r)))&&(s.push(n=new m(r.shift())),u=u.slice(n.length),n.type=o,n.matches=r);if(!n)break}return t?u.length:u?nt.error(e):L(e,a).slice(0)}function at(e,t,r){var i=t.dir,s=r&&t.dir==="parentNode",o=w++;return t.first?function(t,n,r){while(t=t[i])if(s||t.nodeType===1)return e(t,n,r)}:function(t,r,u){if(!u){var a,f=b+" "+o+" ",l=f+n;while(t=t[i])if(s||t.nodeType===1){if((a=t[d])===l)return t.sizset;if(typeof a=="string"&&a.indexOf(f)===0){if(t.sizset)return t}else{t[d]=l;if(e(t,r,u))return t.sizset=!0,t;t.sizset=!1}}}else while(t=t[i])if(s||t.nodeType===1)if(e(t,r,u))return t}}function ft(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function lt(e,t,n,r,i){var s,o=[],u=0,a=e.length,f=t!=null;for(;u-1&&(s[f]=!(o[f]=c))}}else g=lt(g===o?g.splice(d,g.length):g),i?i(null,o,g,a):S.apply(o,g)})}function ht(e){var t,n,r,s=e.length,o=i.relative[e[0].type],u=o||i.relative[" "],a=o?1:0,f=at(function(e){return e===t},u,!0),l=at(function(e){return T.call(t,e)>-1},u,!0),h=[function(e,n,r){return!o&&(r||n!==c)||((t=n).nodeType?f(e,n,r):l(e,n,r))}];for(;a1&&ft(h),a>1&&e.slice(0,a-1).join("").replace(j,"$1"),n,a0,s=e.length>0,o=function(u,a,f,l,h){var p,d,v,m=[],y=0,w="0",x=u&&[],T=h!=null,N=c,C=u||s&&i.find.TAG("*",h&&a.parentNode||a),k=b+=N==null?1:Math.E;T&&(c=a!==g&&a,n=o.el);for(;(p=C[w])!=null;w++){if(s&&p){for(d=0;v=e[d];d++)if(v(p,a,f)){l.push(p);break}T&&(b=k,n=++o.el)}r&&((p=!v&&p)&&y--,u&&x.push(p))}y+=w;if(r&&w!==y){for(d=0;v=t[d];d++)v(x,m,a,f);if(u){if(y>0)while(w--)!x[w]&&!m[w]&&(m[w]=E.call(l));m=lt(m)}S.apply(l,m),T&&!u&&m.length>0&&y+t.length>1&&nt.uniqueSort(l)}return T&&(b=k,c=N),x};return o.el=0,r?N(o):o}function dt(e,t,n){var r=0,i=t.length;for(;r2&&(f=u[0]).type==="ID"&&t.nodeType===9&&!s&&i.relative[u[1].type]){t=i.find.ID(f.matches[0].replace($,""),t,s)[0];if(!t)return n;e=e.slice(u.shift().length)}for(o=J.POS.test(e)?-1:u.length-1;o>=0;o--){f=u[o];if(i.relative[l=f.type])break;if(c=i.find[l])if(r=c(f.matches[0].replace($,""),z.test(u[0].type)&&t.parentNode||t,s)){u.splice(o,1),e=r.length&&u.join("");if(!e)return S.apply(n,x.call(r,0)),n;break}}}return a(e,h)(r,t,s,n,z.test(e)),n}function mt(){}var n,r,i,s,o,u,a,f,l,c,h=!0,p="undefined",d=("sizcache"+Math.random()).replace(".",""),m=String,g=e.document,y=g.documentElement,b=0,w=0,E=[].pop,S=[].push,x=[].slice,T=[].indexOf||function(e){var t=0,n=this.length;for(;ti.cacheLength&&delete e[t.shift()],e[n+" "]=r},e)},k=C(),L=C(),A=C(),O="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+",_=M.replace("w","w#"),D="([*^$|!~]?=)",P="\\["+O+"*("+M+")"+O+"*(?:"+D+O+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+_+")|)|)"+O+"*\\]",H=":("+M+")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|([^()[\\]]*|(?:(?:"+P+")|[^:]|\\\\.)*|.*))\\)|)",B=":(even|odd|eq|gt|lt|nth|first|last)(?:\\("+O+"*((?:-\\d)?\\d*)"+O+"*\\)|)(?=[^-]|$)",j=new RegExp("^"+O+"+|((?:^|[^\\\\])(?:\\\\.)*)"+O+"+$","g"),F=new RegExp("^"+O+"*,"+O+"*"),I=new RegExp("^"+O+"*([\\x20\\t\\r\\n\\f>+~])"+O+"*"),q=new RegExp(H),R=/^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,U=/^:not/,z=/[\x20\t\r\n\f]*[+~]/,W=/:not\($/,X=/h\d/i,V=/input|select|textarea|button/i,$=/\\(?!\\)/g,J={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),NAME:new RegExp("^\\[name=['\"]?("+M+")['\"]?\\]"),TAG:new RegExp("^("+M.replace("w","w*")+")"),ATTR:new RegExp("^"+P),PSEUDO:new RegExp("^"+H),POS:new RegExp(B,"i"),CHILD:new RegExp("^:(only|nth|first|last)-child(?:\\("+O+"*(even|odd|(([+-]|)(\\d*)n|)"+O+"*(?:([+-]|)"+O+"*(\\d+)|))"+O+"*\\)|)","i"),needsContext:new RegExp("^"+O+"*[>+~]|"+B,"i")},K=function(e){var t=g.createElement("div");try{return e(t)}catch(n){return!1}finally{t=null}},Q=K(function(e){return e.appendChild(g.createComment("")),!e.getElementsByTagName("*").length}),G=K(function(e){return e.innerHTML="",e.firstChild&&typeof e.firstChild.getAttribute!==p&&e.firstChild.getAttribute("href")==="#"}),Y=K(function(e){e.innerHTML="";var t=typeof e.lastChild.getAttribute("multiple");return t!=="boolean"&&t!=="string"}),Z=K(function(e){return e.innerHTML="",!e.getElementsByClassName||!e.getElementsByClassName("e").length?!1:(e.lastChild.className="e",e.getElementsByClassName("e").length===2)}),et=K(function(e){e.id=d+0,e.innerHTML="
",y.insertBefore(e,y.firstChild);var t=g.getElementsByName&&g.getElementsByName(d).length===2+g.getElementsByName(d+0).length;return r=!g.getElementById(d),y.removeChild(e),t});try{x.call(y.childNodes,0)[0].nodeType}catch(tt){x=function(e){var t,n=[];for(;t=this[e];e++)n.push(t);return n}}nt.matches=function(e,t){return nt(e,null,null,t)},nt.matchesSelector=function(e,t){return nt(t,null,null,[e]).length>0},s=nt.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(i===1||i===9||i===11){if(typeof e.textContent=="string")return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=s(e)}else if(i===3||i===4)return e.nodeValue}else for(;t=e[r];r++)n+=s(t);return n},o=nt.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?t.nodeName!=="HTML":!1},u=nt.contains=y.contains?function(e,t){var n=e.nodeType===9?e.documentElement:e,r=t&&t.parentNode;return e===r||!!(r&&r.nodeType===1&&n.contains&&n.contains(r))}:y.compareDocumentPosition?function(e,t){return t&&!!(e.compareDocumentPosition(t)&16)}:function(e,t){while(t=t.parentNode)if(t===e)return!0;return!1},nt.attr=function(e,t){var n,r=o(e);return r||(t=t.toLowerCase()),(n=i.attrHandle[t])?n(e):r||Y?e.getAttribute(t):(n=e.getAttributeNode(t),n?typeof e[t]=="boolean"?e[t]?t:null:n.specified?n.value:null:null)},i=nt.selectors={cacheLength:50,createPseudo:N,match:J,attrHandle:G?{}:{href:function(e){return e.getAttribute("href",2)},type:function(e){return e.getAttribute("type")}},find:{ID:r?function(e,t,n){if(typeof t.getElementById!==p&&!n){var r=t.getElementById(e);return r&&r.parentNode?[r]:[]}}:function(e,n,r){if(typeof n.getElementById!==p&&!r){var i=n.getElementById(e);return i?i.id===e||typeof i.getAttributeNode!==p&&i.getAttributeNode("id").value===e?[i]:t:[]}},TAG:Q?function(e,t){if(typeof t.getElementsByTagName!==p)return t.getElementsByTagName(e)}:function(e,t){var n=t.getElementsByTagName(e);if(e==="*"){var r,i=[],s=0;for(;r=n[s];s++)r.nodeType===1&&i.push(r);return i}return n},NAME:et&&function(e,t){if(typeof t.getElementsByName!==p)return t.getElementsByName(name)},CLASS:Z&&function(e,t,n){if(typeof t.getElementsByClassName!==p&&!n)return t.getElementsByClassName(e)}},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace($,""),e[3]=(e[4]||e[5]||"").replace($,""),e[2]==="~="&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),e[1]==="nth"?(e[2]||nt.error(e[0]),e[3]=+(e[3]?e[4]+(e[5]||1):2*(e[2]==="even"||e[2]==="odd")),e[4]=+(e[6]+e[7]||e[2]==="odd")):e[2]&&nt.error(e[0]),e},PSEUDO:function(e){var t,n;if(J.CHILD.test(e[0]))return null;if(e[3])e[2]=e[3];else if(t=e[4])q.test(t)&&(n=ut(t,!0))&&(n=t.indexOf(")",t.length-n)-t.length)&&(t=t.slice(0,n),e[0]=e[0].slice(0,n)),e[2]=t;return e.slice(0,3)}},filter:{ID:r?function(e){return e=e.replace($,""),function(t){return t.getAttribute("id")===e}}:function(e){return e=e.replace($,""),function(t){var n=typeof t.getAttributeNode!==p&&t.getAttributeNode("id");return n&&n.value===e}},TAG:function(e){return e==="*"?function(){return!0}:(e=e.replace($,"").toLowerCase(),function(t){return t.nodeName&&t.nodeName.toLowerCase()===e})},CLASS:function(e){var t=k[d][e+" "];return t||(t=new RegExp("(^|"+O+")"+e+"("+O+"|$)"))&&k(e,function(e){return t.test(e.className||typeof e.getAttribute!==p&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r,i){var s=nt.attr(r,e);return s==null?t==="!=":t?(s+="",t==="="?s===n:t==="!="?s!==n:t==="^="?n&&s.indexOf(n)===0:t==="*="?n&&s.indexOf(n)>-1:t==="$="?n&&s.substr(s.length-n.length)===n:t==="~="?(" "+s+" ").indexOf(n)>-1:t==="|="?s===n||s.substr(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r){return e==="nth"?function(e){var t,i,s=e.parentNode;if(n===1&&r===0)return!0;if(s){i=0;for(t=s.firstChild;t;t=t.nextSibling)if(t.nodeType===1){i++;if(e===t)break}}return i-=r,i===n||i%n===0&&i/n>=0}:function(t){var n=t;switch(e){case"only":case"first":while(n=n.previousSibling)if(n.nodeType===1)return!1;if(e==="first")return!0;n=t;case"last":while(n=n.nextSibling)if(n.nodeType===1)return!1;return!0}}},PSEUDO:function(e,t){var n,r=i.pseudos[e]||i.setFilters[e.toLowerCase()]||nt.error("unsupported pseudo: "+e);return r[d]?r(t):r.length>1?(n=[e,e,"",t],i.setFilters.hasOwnProperty(e.toLowerCase())?N(function(e,n){var i,s=r(e,t),o=s.length;while(o--)i=T.call(e,s[o]),e[i]=!(n[i]=s[o])}):function(e){return r(e,0,n)}):r}},pseudos:{not:N(function(e){var t=[],n=[],r=a(e.replace(j,"$1"));return r[d]?N(function(e,t,n,i){var s,o=r(e,null,i,[]),u=e.length;while(u--)if(s=o[u])e[u]=!(t[u]=s)}):function(e,i,s){return t[0]=e,r(t,null,s,n),!n.pop()}}),has:N(function(e){return function(t){return nt(e,t).length>0}}),contains:N(function(e){return function(t){return(t.textContent||t.innerText||s(t)).indexOf(e)>-1}}),enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return t==="input"&&!!e.checked||t==="option"&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},parent:function(e){return!i.pseudos.empty(e)},empty:function(e){var t;e=e.firstChild;while(e){if(e.nodeName>"@"||(t=e.nodeType)===3||t===4)return!1;e=e.nextSibling}return!0},header:function(e){return X.test(e.nodeName)},text:function(e){var t,n;return e.nodeName.toLowerCase()==="input"&&(t=e.type)==="text"&&((n=e.getAttribute("type"))==null||n.toLowerCase()===t)},radio:rt("radio"),checkbox:rt("checkbox"),file:rt("file"),password:rt("password"),image:rt("image"),submit:it("submit"),reset:it("reset"),button:function(e){var t=e.nodeName.toLowerCase();return t==="input"&&e.type==="button"||t==="button"},input:function(e){return V.test(e.nodeName)},focus:function(e){var t=e.ownerDocument;return e===t.activeElement&&(!t.hasFocus||t.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},active:function(e){return e===e.ownerDocument.activeElement},first:st(function(){return[0]}),last:st(function(e,t){return[t-1]}),eq:st(function(e,t,n){return[n<0?n+t:n]}),even:st(function(e,t){for(var n=0;n=0;)e.push(r);return e}),gt:st(function(e,t,n){for(var r=n<0?n+t:n;++r",e.querySelectorAll("[selected]").length||i.push("\\["+O+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)"),e.querySelectorAll(":checked").length||i.push(":checked")}),K(function(e){e.innerHTML="

",e.querySelectorAll("[test^='']").length&&i.push("[*^$]="+O+"*(?:\"\"|'')"),e.innerHTML="",e.querySelectorAll(":enabled").length||i.push(":enabled",":disabled")}),i=new RegExp(i.join("|")),vt=function(e,r,s,o,u){if(!o&&!u&&!i.test(e)){var a,f,l=!0,c=d,h=r,p=r.nodeType===9&&e;if(r.nodeType===1&&r.nodeName.toLowerCase()!=="object"){a=ut(e),(l=r.getAttribute("id"))?c=l.replace(n,"\\$&"):r.setAttribute("id",c),c="[id='"+c+"'] ",f=a.length;while(f--)a[f]=c+a[f].join("");h=z.test(e)&&r.parentNode||r,p=a.join(",")}if(p)try{return S.apply(s,x.call(h.querySelectorAll(p),0)),s}catch(v){}finally{l||r.removeAttribute("id")}}return t(e,r,s,o,u)},u&&(K(function(t){e=u.call(t,"div");try{u.call(t,"[test!='']:sizzle"),s.push("!=",H)}catch(n){}}),s=new RegExp(s.join("|")),nt.matchesSelector=function(t,n){n=n.replace(r,"='$1']");if(!o(t)&&!s.test(n)&&!i.test(n))try{var a=u.call(t,n);if(a||e||t.document&&t.document.nodeType!==11)return a}catch(f){}return nt(n,null,null,[t]).length>0})}(),i.pseudos.nth=i.pseudos.eq,i.filters=mt.prototype=i.pseudos,i.setFilters=new mt,nt.attr=v.attr,v.find=nt,v.expr=nt.selectors,v.expr[":"]=v.expr.pseudos,v.unique=nt.uniqueSort,v.text=nt.getText,v.isXMLDoc=nt.isXML,v.contains=nt.contains}(e);var nt=/Until$/,rt=/^(?:parents|prev(?:Until|All))/,it=/^.[^:#\[\.,]*$/,st=v.expr.match.needsContext,ot={children:!0,contents:!0,next:!0,prev:!0};v.fn.extend({find:function(e){var t,n,r,i,s,o,u=this;if(typeof e!="string")return v(e).filter(function(){for(t=0,n=u.length;t0)for(i=r;i=0:v.filter(e,this).length>0:this.filter(e).length>0)},closest:function(e,t){var n,r=0,i=this.length,s=[],o=st.test(e)||typeof e!="string"?v(e,t||this.context):0;for(;r-1:v.find.matchesSelector(n,e)){s.push(n);break}n=n.parentNode}}return s=s.length>1?v.unique(s):s,this.pushStack(s,"closest",e)},index:function(e){return e?typeof e=="string"?v.inArray(this[0],v(e)):v.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.prevAll().length:-1},add:function(e,t){var n=typeof e=="string"?v(e,t):v.makeArray(e&&e.nodeType?[e]:e),r=v.merge(this.get(),n);return this.pushStack(ut(n[0])||ut(r[0])?r:v.unique(r))},addBack:function(e){return this.add(e==null?this.prevObject:this.prevObject.filter(e))}}),v.fn.andSelf=v.fn.addBack,v.each({parent:function(e){var t=e.parentNode;return t&&t.nodeType!==11?t:null},parents:function(e){return v.dir(e,"parentNode")},parentsUntil:function(e,t,n){return v.dir(e,"parentNode",n)},next:function(e){return at(e,"nextSibling")},prev:function(e){return at(e,"previousSibling")},nextAll:function(e){return v.dir(e,"nextSibling")},prevAll:function(e){return v.dir(e,"previousSibling")},nextUntil:function(e,t,n){return v.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return v.dir(e,"previousSibling",n)},siblings:function(e){return v.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return v.sibling(e.firstChild)},contents:function(e){return v.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:v.merge([],e.childNodes)}},function(e,t){v.fn[e]=function(n,r){var i=v.map(this,t,n);return nt.test(e)||(r=n),r&&typeof r=="string"&&(i=v.filter(r,i)),i=this.length>1&&!ot[e]?v.unique(i):i,this.length>1&&rt.test(e)&&(i=i.reverse()),this.pushStack(i,e,l.call(arguments).join(","))}}),v.extend({filter:function(e,t,n){return n&&(e=":not("+e+")"),t.length===1?v.find.matchesSelector(t[0],e)?[t[0]]:[]:v.find.matches(e,t)},dir:function(e,n,r){var i=[],s=e[n];while(s&&s.nodeType!==9&&(r===t||s.nodeType!==1||!v(s).is(r)))s.nodeType===1&&i.push(s),s=s[n];return i},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)e.nodeType===1&&e!==t&&n.push(e);return n}});var ct="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",ht=/ jQuery\d+="(?:null|\d+)"/g,pt=/^\s+/,dt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,vt=/<([\w:]+)/,mt=/]","i"),Et=/^(?:checkbox|radio)$/,St=/checked\s*(?:[^=]|=\s*.checked.)/i,xt=/\/(java|ecma)script/i,Tt=/^\s*\s*$/g,Nt={option:[1,""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"],area:[1,"",""],_default:[0,"",""]},Ct=lt(i),kt=Ct.appendChild(i.createElement("div"));Nt.optgroup=Nt.option,Nt.tbody=Nt.tfoot=Nt.colgroup=Nt.caption=Nt.thead,Nt.th=Nt.td,v.support.htmlSerialize||(Nt._default=[1,"X
","
"]),v.fn.extend({text:function(e){return v.access(this,function(e){return e===t?v.text(this):this.empty().append((this[0]&&this[0].ownerDocument||i).createTextNode(e))},null,e,arguments.length)},wrapAll:function(e){if(v.isFunction(e))return this.each(function(t){v(this).wrapAll(e.call(this,t))});if(this[0]){var t=v(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstChild&&e.firstChild.nodeType===1)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return v.isFunction(e)?this.each(function(t){v(this).wrapInner(e.call(this,t))}):this.each(function(){var t=v(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=v.isFunction(e);return this.each(function(n){v(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){v.nodeName(this,"body")||v(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(e){(this.nodeType===1||this.nodeType===11)&&this.appendChild(e)})},prepend:function(){return this.domManip(arguments,!0,function(e){(this.nodeType===1||this.nodeType===11)&&this.insertBefore(e,this.firstChild)})},before:function(){if(!ut(this[0]))return this.domManip(arguments,!1,function(e){this.parentNode.insertBefore(e,this)});if(arguments.length){var e=v.clean(arguments);return this.pushStack(v.merge(e,this),"before",this.selector)}},after:function(){if(!ut(this[0]))return this.domManip(arguments,!1,function(e){this.parentNode.insertBefore(e,this.nextSibling)});if(arguments.length){var e=v.clean(arguments);return this.pushStack(v.merge(this,e),"after",this.selector)}},remove:function(e,t){var n,r=0;for(;(n=this[r])!=null;r++)if(!e||v.filter(e,[n]).length)!t&&n.nodeType===1&&(v.cleanData(n.getElementsByTagName("*")),v.cleanData([n])),n.parentNode&&n.parentNode.removeChild(n);return this},empty:function(){var e,t=0;for(;(e=this[t])!=null;t++){e.nodeType===1&&v.cleanData(e.getElementsByTagName("*"));while(e.firstChild)e.removeChild(e.firstChild)}return this},clone:function(e,t){return e=e==null?!1:e,t=t==null?e:t,this.map(function(){return v.clone(this,e,t)})},html:function(e){return v.access(this,function(e){var n=this[0]||{},r=0,i=this.length;if(e===t)return n.nodeType===1?n.innerHTML.replace(ht,""):t;if(typeof e=="string"&&!yt.test(e)&&(v.support.htmlSerialize||!wt.test(e))&&(v.support.leadingWhitespace||!pt.test(e))&&!Nt[(vt.exec(e)||["",""])[1].toLowerCase()]){e=e.replace(dt,"<$1>");try{for(;r1&&typeof f=="string"&&St.test(f))return this.each(function(){v(this).domManip(e,n,r)});if(v.isFunction(f))return this.each(function(i){var s=v(this);e[0]=f.call(this,i,n?s.html():t),s.domManip(e,n,r)});if(this[0]){i=v.buildFragment(e,this,l),o=i.fragment,s=o.firstChild,o.childNodes.length===1&&(o=s);if(s){n=n&&v.nodeName(s,"tr");for(u=i.cacheable||c-1;a0?this.clone(!0):this).get(),v(o[i])[t](r),s=s.concat(r);return this.pushStack(s,e,o.selector)}}),v.extend({clone:function(e,t,n){var r,i,s,o;v.support.html5Clone||v.isXMLDoc(e)||!wt.test("<"+e.nodeName+">")?o=e.cloneNode(!0):(kt.innerHTML=e.outerHTML,kt.removeChild(o=kt.firstChild));if((!v.support.noCloneEvent||!v.support.noCloneChecked)&&(e.nodeType===1||e.nodeType===11)&&!v.isXMLDoc(e)){Ot(e,o),r=Mt(e),i=Mt(o);for(s=0;r[s];++s)i[s]&&Ot(r[s],i[s])}if(t){At(e,o);if(n){r=Mt(e),i=Mt(o);for(s=0;r[s];++s)At(r[s],i[s])}}return r=i=null,o},clean:function(e,t,n,r){var s,o,u,a,f,l,c,h,p,d,m,g,y=t===i&&Ct,b=[];if(!t||typeof t.createDocumentFragment=="undefined")t=i;for(s=0;(u=e[s])!=null;s++){typeof u=="number"&&(u+="");if(!u)continue;if(typeof u=="string")if(!gt.test(u))u=t.createTextNode(u);else{y=y||lt(t),c=t.createElement("div"),y.appendChild(c),u=u.replace(dt,"<$1>"),a=(vt.exec(u)||["",""])[1].toLowerCase(),f=Nt[a]||Nt._default,l=f[0],c.innerHTML=f[1]+u+f[2];while(l--)c=c.lastChild;if(!v.support.tbody){h=mt.test(u),p=a==="table"&&!h?c.firstChild&&c.firstChild.childNodes:f[1]===""&&!h?c.childNodes:[];for(o=p.length-1;o>=0;--o)v.nodeName(p[o],"tbody")&&!p[o].childNodes.length&&p[o].parentNode.removeChild(p[o])}!v.support.leadingWhitespace&&pt.test(u)&&c.insertBefore(t.createTextNode(pt.exec(u)[0]),c.firstChild),u=c.childNodes,c.parentNode.removeChild(c)}u.nodeType?b.push(u):v.merge(b,u)}c&&(u=c=y=null);if(!v.support.appendChecked)for(s=0;(u=b[s])!=null;s++)v.nodeName(u,"input")?_t(u):typeof u.getElementsByTagName!="undefined"&&v.grep(u.getElementsByTagName("input"),_t);if(n){m=function(e){if(!e.type||xt.test(e.type))return r?r.push(e.parentNode?e.parentNode.removeChild(e):e):n.appendChild(e)};for(s=0;(u=b[s])!=null;s++)if(!v.nodeName(u,"script")||!m(u))n.appendChild(u),typeof u.getElementsByTagName!="undefined"&&(g=v.grep(v.merge([],u.getElementsByTagName("script")),m),b.splice.apply(b,[s+1,0].concat(g)),s+=g.length)}return b},cleanData:function(e,t){var n,r,i,s,o=0,u=v.expando,a=v.cache,f=v.support.deleteExpando,l=v.event.special;for(;(i=e[o])!=null;o++)if(t||v.acceptData(i)){r=i[u],n=r&&a[r];if(n){if(n.events)for(s in n.events)l[s]?v.event.remove(i,s):v.removeEvent(i,s,n.handle);a[r]&&(delete a[r],f?delete i[u]:i.removeAttribute?i.removeAttribute(u):i[u]=null,v.deletedIds.push(r))}}}}),function(){var e,t;v.uaMatch=function(e){e=e.toLowerCase();var t=/(chrome)[ \/]([\w.]+)/.exec(e)||/(webkit)[ \/]([\w.]+)/.exec(e)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(e)||/(msie) ([\w.]+)/.exec(e)||e.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(e)||[];return{browser:t[1]||"",version:t[2]||"0"}},e=v.uaMatch(o.userAgent),t={},e.browser&&(t[e.browser]=!0,t.version=e.version),t.chrome?t.webkit=!0:t.webkit&&(t.safari=!0),v.browser=t,v.sub=function(){function e(t,n){return new e.fn.init(t,n)}v.extend(!0,e,this),e.superclass=this,e.fn=e.prototype=this(),e.fn.constructor=e,e.sub=this.sub,e.fn.init=function(r,i){return i&&i instanceof v&&!(i instanceof e)&&(i=e(i)),v.fn.init.call(this,r,i,t)},e.fn.init.prototype=e.fn;var t=e(i);return e}}();var Dt,Pt,Ht,Bt=/alpha\([^)]*\)/i,jt=/opacity=([^)]*)/,Ft=/^(top|right|bottom|left)$/,It=/^(none|table(?!-c[ea]).+)/,qt=/^margin/,Rt=new RegExp("^("+m+")(.*)$","i"),Ut=new RegExp("^("+m+")(?!px)[a-z%]+$","i"),zt=new RegExp("^([-+])=("+m+")","i"),Wt={BODY:"block"},Xt={position:"absolute",visibility:"hidden",display:"block"},Vt={letterSpacing:0,fontWeight:400},$t=["Top","Right","Bottom","Left"],Jt=["Webkit","O","Moz","ms"],Kt=v.fn.toggle;v.fn.extend({css:function(e,n){return v.access(this,function(e,n,r){return r!==t?v.style(e,n,r):v.css(e,n)},e,n,arguments.length>1)},show:function(){return Yt(this,!0)},hide:function(){return Yt(this)},toggle:function(e,t){var n=typeof e=="boolean";return v.isFunction(e)&&v.isFunction(t)?Kt.apply(this,arguments):this.each(function(){(n?e:Gt(this))?v(this).show():v(this).hide()})}}),v.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Dt(e,"opacity");return n===""?"1":n}}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":v.support.cssFloat?"cssFloat":"styleFloat"},style:function(e,n,r,i){if(!e||e.nodeType===3||e.nodeType===8||!e.style)return;var s,o,u,a=v.camelCase(n),f=e.style;n=v.cssProps[a]||(v.cssProps[a]=Qt(f,a)),u=v.cssHooks[n]||v.cssHooks[a];if(r===t)return u&&"get"in u&&(s=u.get(e,!1,i))!==t?s:f[n];o=typeof r,o==="string"&&(s=zt.exec(r))&&(r=(s[1]+1)*s[2]+parseFloat(v.css(e,n)),o="number");if(r==null||o==="number"&&isNaN(r))return;o==="number"&&!v.cssNumber[a]&&(r+="px");if(!u||!("set"in u)||(r=u.set(e,r,i))!==t)try{f[n]=r}catch(l){}},css:function(e,n,r,i){var s,o,u,a=v.camelCase(n);return n=v.cssProps[a]||(v.cssProps[a]=Qt(e.style,a)),u=v.cssHooks[n]||v.cssHooks[a],u&&"get"in u&&(s=u.get(e,!0,i)),s===t&&(s=Dt(e,n)),s==="normal"&&n in Vt&&(s=Vt[n]),r||i!==t?(o=parseFloat(s),r||v.isNumeric(o)?o||0:s):s},swap:function(e,t,n){var r,i,s={};for(i in t)s[i]=e.style[i],e.style[i]=t[i];r=n.call(e);for(i in t)e.style[i]=s[i];return r}}),e.getComputedStyle?Dt=function(t,n){var r,i,s,o,u=e.getComputedStyle(t,null),a=t.style;return u&&(r=u.getPropertyValue(n)||u[n],r===""&&!v.contains(t.ownerDocument,t)&&(r=v.style(t,n)),Ut.test(r)&&qt.test(n)&&(i=a.width,s=a.minWidth,o=a.maxWidth,a.minWidth=a.maxWidth=a.width=r,r=u.width,a.width=i,a.minWidth=s,a.maxWidth=o)),r}:i.documentElement.currentStyle&&(Dt=function(e,t){var n,r,i=e.currentStyle&&e.currentStyle[t],s=e.style;return i==null&&s&&s[t]&&(i=s[t]),Ut.test(i)&&!Ft.test(t)&&(n=s.left,r=e.runtimeStyle&&e.runtimeStyle.left,r&&(e.runtimeStyle.left=e.currentStyle.left),s.left=t==="fontSize"?"1em":i,i=s.pixelLeft+"px",s.left=n,r&&(e.runtimeStyle.left=r)),i===""?"auto":i}),v.each(["height","width"],function(e,t){v.cssHooks[t]={get:function(e,n,r){if(n)return e.offsetWidth===0&&It.test(Dt(e,"display"))?v.swap(e,Xt,function(){return tn(e,t,r)}):tn(e,t,r)},set:function(e,n,r){return Zt(e,n,r?en(e,t,r,v.support.boxSizing&&v.css(e,"boxSizing")==="border-box"):0)}}}),v.support.opacity||(v.cssHooks.opacity={get:function(e,t){return jt.test((t&&e.currentStyle?e.currentStyle.filter:e.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":t?"1":""},set:function(e,t){var n=e.style,r=e.currentStyle,i=v.isNumeric(t)?"alpha(opacity="+t*100+")":"",s=r&&r.filter||n.filter||"";n.zoom=1;if(t>=1&&v.trim(s.replace(Bt,""))===""&&n.removeAttribute){n.removeAttribute("filter");if(r&&!r.filter)return}n.filter=Bt.test(s)?s.replace(Bt,i):s+" "+i}}),v(function(){v.support.reliableMarginRight||(v.cssHooks.marginRight={get:function(e,t){return v.swap(e,{display:"inline-block"},function(){if(t)return Dt(e,"marginRight")})}}),!v.support.pixelPosition&&v.fn.position&&v.each(["top","left"],function(e,t){v.cssHooks[t]={get:function(e,n){if(n){var r=Dt(e,t);return Ut.test(r)?v(e).position()[t]+"px":r}}}})}),v.expr&&v.expr.filters&&(v.expr.filters.hidden=function(e){return e.offsetWidth===0&&e.offsetHeight===0||!v.support.reliableHiddenOffsets&&(e.style&&e.style.display||Dt(e,"display"))==="none"},v.expr.filters.visible=function(e){return!v.expr.filters.hidden(e)}),v.each({margin:"",padding:"",border:"Width"},function(e,t){v.cssHooks[e+t]={expand:function(n){var r,i=typeof n=="string"?n.split(" "):[n],s={};for(r=0;r<4;r++)s[e+$t[r]+t]=i[r]||i[r-2]||i[0];return s}},qt.test(e)||(v.cssHooks[e+t].set=Zt)});var rn=/%20/g,sn=/\[\]$/,on=/\r?\n/g,un=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,an=/^(?:select|textarea)/i;v.fn.extend({serialize:function(){return v.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?v.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||an.test(this.nodeName)||un.test(this.type))}).map(function(e,t){var n=v(this).val();return n==null?null:v.isArray(n)?v.map(n,function(e,n){return{name:t.name,value:e.replace(on,"\r\n")}}):{name:t.name,value:n.replace(on,"\r\n")}}).get()}}),v.param=function(e,n){var r,i=[],s=function(e,t){t=v.isFunction(t)?t():t==null?"":t,i[i.length]=encodeURIComponent(e)+"="+encodeURIComponent(t)};n===t&&(n=v.ajaxSettings&&v.ajaxSettings.traditional);if(v.isArray(e)||e.jquery&&!v.isPlainObject(e))v.each(e,function(){s(this.name,this.value)});else for(r in e)fn(r,e[r],n,s);return i.join("&").replace(rn,"+")};var ln,cn,hn=/#.*$/,pn=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,dn=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,vn=/^(?:GET|HEAD)$/,mn=/^\/\//,gn=/\?/,yn=/)<[^<]*)*<\/script>/gi,bn=/([?&])_=[^&]*/,wn=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,En=v.fn.load,Sn={},xn={},Tn=["*/"]+["*"];try{cn=s.href}catch(Nn){cn=i.createElement("a"),cn.href="",cn=cn.href}ln=wn.exec(cn.toLowerCase())||[],v.fn.load=function(e,n,r){if(typeof e!="string"&&En)return En.apply(this,arguments);if(!this.length)return this;var i,s,o,u=this,a=e.indexOf(" ");return a>=0&&(i=e.slice(a,e.length),e=e.slice(0,a)),v.isFunction(n)?(r=n,n=t):n&&typeof n=="object"&&(s="POST"),v.ajax({url:e,type:s,dataType:"html",data:n,complete:function(e,t){r&&u.each(r,o||[e.responseText,t,e])}}).done(function(e){o=arguments,u.html(i?v("
").append(e.replace(yn,"")).find(i):e)}),this},v.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(e,t){v.fn[t]=function(e){return this.on(t,e)}}),v.each(["get","post"],function(e,n){v[n]=function(e,r,i,s){return v.isFunction(r)&&(s=s||i,i=r,r=t),v.ajax({type:n,url:e,data:r,success:i,dataType:s})}}),v.extend({getScript:function(e,n){return v.get(e,t,n,"script")},getJSON:function(e,t,n){return v.get(e,t,n,"json")},ajaxSetup:function(e,t){return t?Ln(e,v.ajaxSettings):(t=e,e=v.ajaxSettings),Ln(e,t),e},ajaxSettings:{url:cn,isLocal:dn.test(ln[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":Tn},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":e.String,"text html":!0,"text json":v.parseJSON,"text xml":v.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:Cn(Sn),ajaxTransport:Cn(xn),ajax:function(e,n){function T(e,n,s,a){var l,y,b,w,S,T=n;if(E===2)return;E=2,u&&clearTimeout(u),o=t,i=a||"",x.readyState=e>0?4:0,s&&(w=An(c,x,s));if(e>=200&&e<300||e===304)c.ifModified&&(S=x.getResponseHeader("Last-Modified"),S&&(v.lastModified[r]=S),S=x.getResponseHeader("Etag"),S&&(v.etag[r]=S)),e===304?(T="notmodified",l=!0):(l=On(c,w),T=l.state,y=l.data,b=l.error,l=!b);else{b=T;if(!T||e)T="error",e<0&&(e=0)}x.status=e,x.statusText=(n||T)+"",l?d.resolveWith(h,[y,T,x]):d.rejectWith(h,[x,T,b]),x.statusCode(g),g=t,f&&p.trigger("ajax"+(l?"Success":"Error"),[x,c,l?y:b]),m.fireWith(h,[x,T]),f&&(p.trigger("ajaxComplete",[x,c]),--v.active||v.event.trigger("ajaxStop"))}typeof e=="object"&&(n=e,e=t),n=n||{};var r,i,s,o,u,a,f,l,c=v.ajaxSetup({},n),h=c.context||c,p=h!==c&&(h.nodeType||h instanceof v)?v(h):v.event,d=v.Deferred(),m=v.Callbacks("once memory"),g=c.statusCode||{},b={},w={},E=0,S="canceled",x={readyState:0,setRequestHeader:function(e,t){if(!E){var n=e.toLowerCase();e=w[n]=w[n]||e,b[e]=t}return this},getAllResponseHeaders:function(){return E===2?i:null},getResponseHeader:function(e){var n;if(E===2){if(!s){s={};while(n=pn.exec(i))s[n[1].toLowerCase()]=n[2]}n=s[e.toLowerCase()]}return n===t?null:n},overrideMimeType:function(e){return E||(c.mimeType=e),this},abort:function(e){return e=e||S,o&&o.abort(e),T(0,e),this}};d.promise(x),x.success=x.done,x.error=x.fail,x.complete=m.add,x.statusCode=function(e){if(e){var t;if(E<2)for(t in e)g[t]=[g[t],e[t]];else t=e[x.status],x.always(t)}return this},c.url=((e||c.url)+"").replace(hn,"").replace(mn,ln[1]+"//"),c.dataTypes=v.trim(c.dataType||"*").toLowerCase().split(y),c.crossDomain==null&&(a=wn.exec(c.url.toLowerCase()),c.crossDomain=!(!a||a[1]===ln[1]&&a[2]===ln[2]&&(a[3]||(a[1]==="http:"?80:443))==(ln[3]||(ln[1]==="http:"?80:443)))),c.data&&c.processData&&typeof c.data!="string"&&(c.data=v.param(c.data,c.traditional)),kn(Sn,c,n,x);if(E===2)return x;f=c.global,c.type=c.type.toUpperCase(),c.hasContent=!vn.test(c.type),f&&v.active++===0&&v.event.trigger("ajaxStart");if(!c.hasContent){c.data&&(c.url+=(gn.test(c.url)?"&":"?")+c.data,delete c.data),r=c.url;if(c.cache===!1){var N=v.now(),C=c.url.replace(bn,"$1_="+N);c.url=C+(C===c.url?(gn.test(c.url)?"&":"?")+"_="+N:"")}}(c.data&&c.hasContent&&c.contentType!==!1||n.contentType)&&x.setRequestHeader("Content-Type",c.contentType),c.ifModified&&(r=r||c.url,v.lastModified[r]&&x.setRequestHeader("If-Modified-Since",v.lastModified[r]),v.etag[r]&&x.setRequestHeader("If-None-Match",v.etag[r])),x.setRequestHeader("Accept",c.dataTypes[0]&&c.accepts[c.dataTypes[0]]?c.accepts[c.dataTypes[0]]+(c.dataTypes[0]!=="*"?", "+Tn+"; q=0.01":""):c.accepts["*"]);for(l in c.headers)x.setRequestHeader(l,c.headers[l]);if(!c.beforeSend||c.beforeSend.call(h,x,c)!==!1&&E!==2){S="abort";for(l in{success:1,error:1,complete:1})x[l](c[l]);o=kn(xn,c,n,x);if(!o)T(-1,"No Transport");else{x.readyState=1,f&&p.trigger("ajaxSend",[x,c]),c.async&&c.timeout>0&&(u=setTimeout(function(){x.abort("timeout")},c.timeout));try{E=1,o.send(b,T)}catch(k){if(!(E<2))throw k;T(-1,k)}}return x}return x.abort()},active:0,lastModified:{},etag:{}});var Mn=[],_n=/\?/,Dn=/(=)\?(?=&|$)|\?\?/,Pn=v.now();v.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Mn.pop()||v.expando+"_"+Pn++;return this[e]=!0,e}}),v.ajaxPrefilter("json jsonp",function(n,r,i){var s,o,u,a=n.data,f=n.url,l=n.jsonp!==!1,c=l&&Dn.test(f),h=l&&!c&&typeof a=="string"&&!(n.contentType||"").indexOf("application/x-www-form-urlencoded")&&Dn.test(a);if(n.dataTypes[0]==="jsonp"||c||h)return s=n.jsonpCallback=v.isFunction(n.jsonpCallback)?n.jsonpCallback():n.jsonpCallback,o=e[s],c?n.url=f.replace(Dn,"$1"+s):h?n.data=a.replace(Dn,"$1"+s):l&&(n.url+=(_n.test(f)?"&":"?")+n.jsonp+"="+s),n.converters["script json"]=function(){return u||v.error(s+" was not called"),u[0]},n.dataTypes[0]="json",e[s]=function(){u=arguments},i.always(function(){e[s]=o,n[s]&&(n.jsonpCallback=r.jsonpCallback,Mn.push(s)),u&&v.isFunction(o)&&o(u[0]),u=o=t}),"script"}),v.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(e){return v.globalEval(e),e}}}),v.ajaxPrefilter("script",function(e){e.cache===t&&(e.cache=!1),e.crossDomain&&(e.type="GET",e.global=!1)}),v.ajaxTransport("script",function(e){if(e.crossDomain){var n,r=i.head||i.getElementsByTagName("head")[0]||i.documentElement;return{send:function(s,o){n=i.createElement("script"),n.async="async",e.scriptCharset&&(n.charset=e.scriptCharset),n.src=e.url,n.onload=n.onreadystatechange=function(e,i){if(i||!n.readyState||/loaded|complete/.test(n.readyState))n.onload=n.onreadystatechange=null,r&&n.parentNode&&r.removeChild(n),n=t,i||o(200,"success")},r.insertBefore(n,r.firstChild)},abort:function(){n&&n.onload(0,1)}}}});var Hn,Bn=e.ActiveXObject?function(){for(var e in Hn)Hn[e](0,1)}:!1,jn=0;v.ajaxSettings.xhr=e.ActiveXObject?function(){return!this.isLocal&&Fn()||In()}:Fn,function(e){v.extend(v.support,{ajax:!!e,cors:!!e&&"withCredentials"in e})}(v.ajaxSettings.xhr()),v.support.ajax&&v.ajaxTransport(function(n){if(!n.crossDomain||v.support.cors){var r;return{send:function(i,s){var o,u,a=n.xhr();n.username?a.open(n.type,n.url,n.async,n.username,n.password):a.open(n.type,n.url,n.async);if(n.xhrFields)for(u in n.xhrFields)a[u]=n.xhrFields[u];n.mimeType&&a.overrideMimeType&&a.overrideMimeType(n.mimeType),!n.crossDomain&&!i["X-Requested-With"]&&(i["X-Requested-With"]="XMLHttpRequest");try{for(u in i)a.setRequestHeader(u,i[u])}catch(f){}a.send(n.hasContent&&n.data||null),r=function(e,i){var u,f,l,c,h;try{if(r&&(i||a.readyState===4)){r=t,o&&(a.onreadystatechange=v.noop,Bn&&delete Hn[o]);if(i)a.readyState!==4&&a.abort();else{u=a.status,l=a.getAllResponseHeaders(),c={},h=a.responseXML,h&&h.documentElement&&(c.xml=h);try{c.text=a.responseText}catch(p){}try{f=a.statusText}catch(p){f=""}!u&&n.isLocal&&!n.crossDomain?u=c.text?200:404:u===1223&&(u=204)}}}catch(d){i||s(-1,d)}c&&s(u,f,c,l)},n.async?a.readyState===4?setTimeout(r,0):(o=++jn,Bn&&(Hn||(Hn={},v(e).unload(Bn)),Hn[o]=r),a.onreadystatechange=r):r()},abort:function(){r&&r(0,1)}}}});var qn,Rn,Un=/^(?:toggle|show|hide)$/,zn=new RegExp("^(?:([-+])=|)("+m+")([a-z%]*)$","i"),Wn=/queueHooks$/,Xn=[Gn],Vn={"*":[function(e,t){var n,r,i=this.createTween(e,t),s=zn.exec(t),o=i.cur(),u=+o||0,a=1,f=20;if(s){n=+s[2],r=s[3]||(v.cssNumber[e]?"":"px");if(r!=="px"&&u){u=v.css(i.elem,e,!0)||n||1;do a=a||".5",u/=a,v.style(i.elem,e,u+r);while(a!==(a=i.cur()/o)&&a!==1&&--f)}i.unit=r,i.start=u,i.end=s[1]?u+(s[1]+1)*n:n}return i}]};v.Animation=v.extend(Kn,{tweener:function(e,t){v.isFunction(e)?(t=e,e=["*"]):e=e.split(" ");var n,r=0,i=e.length;for(;r-1,f={},l={},c,h;a?(l=i.position(),c=l.top,h=l.left):(c=parseFloat(o)||0,h=parseFloat(u)||0),v.isFunction(t)&&(t=t.call(e,n,s)),t.top!=null&&(f.top=t.top-s.top+c),t.left!=null&&(f.left=t.left-s.left+h),"using"in t?t.using.call(e,f):i.css(f)}},v.fn.extend({position:function(){if(!this[0])return;var e=this[0],t=this.offsetParent(),n=this.offset(),r=er.test(t[0].nodeName)?{top:0,left:0}:t.offset();return n.top-=parseFloat(v.css(e,"marginTop"))||0,n.left-=parseFloat(v.css(e,"marginLeft"))||0,r.top+=parseFloat(v.css(t[0],"borderTopWidth"))||0,r.left+=parseFloat(v.css(t[0],"borderLeftWidth"))||0,{top:n.top-r.top,left:n.left-r.left}},offsetParent:function(){return this.map(function(){var e=this.offsetParent||i.body;while(e&&!er.test(e.nodeName)&&v.css(e,"position")==="static")e=e.offsetParent;return e||i.body})}}),v.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(e,n){var r=/Y/.test(n);v.fn[e]=function(i){return v.access(this,function(e,i,s){var o=tr(e);if(s===t)return o?n in o?o[n]:o.document.documentElement[i]:e[i];o?o.scrollTo(r?v(o).scrollLeft():s,r?s:v(o).scrollTop()):e[i]=s},e,i,arguments.length,null)}}),v.each({Height:"height",Width:"width"},function(e,n){v.each({padding:"inner"+e,content:n,"":"outer"+e},function(r,i){v.fn[i]=function(i,s){var o=arguments.length&&(r||typeof i!="boolean"),u=r||(i===!0||s===!0?"margin":"border");return v.access(this,function(n,r,i){var s;return v.isWindow(n)?n.document.documentElement["client"+e]:n.nodeType===9?(s=n.documentElement,Math.max(n.body["scroll"+e],s["scroll"+e],n.body["offset"+e],s["offset"+e],s["client"+e])):i===t?v.css(n,r,i,u):v.style(n,r,i,u)},n,o?i:t,o,null)}})}),e.jQuery=e.$=v,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return v})})(window); \ No newline at end of file diff --git a/bin_UPWS/main/reconnecting-websocket.min.js b/bin_UPWS/main/reconnecting-websocket.min.js new file mode 100644 index 0000000..3015099 --- /dev/null +++ b/bin_UPWS/main/reconnecting-websocket.min.js @@ -0,0 +1 @@ +!function(a,b){"function"==typeof define&&define.amd?define([],b):"undefined"!=typeof module&&module.exports?module.exports=b():a.ReconnectingWebSocket=b()}(this,function(){function a(b,c,d){function l(a,b){var c=document.createEvent("CustomEvent");return c.initCustomEvent(a,!1,!1,b),c}var e={debug:!1,automaticOpen:!0,reconnectInterval:1e3,maxReconnectInterval:3e4,reconnectDecay:1.5,timeoutInterval:2e3};d||(d={});for(var f in e)this[f]="undefined"!=typeof d[f]?d[f]:e[f];this.url=b,this.reconnectAttempts=0,this.readyState=WebSocket.CONNECTING,this.protocol=null;var h,g=this,i=!1,j=!1,k=document.createElement("div");k.addEventListener("open",function(a){g.onopen(a)}),k.addEventListener("close",function(a){g.onclose(a)}),k.addEventListener("connecting",function(a){g.onconnecting(a)}),k.addEventListener("message",function(a){g.onmessage(a)}),k.addEventListener("error",function(a){g.onerror(a)}),this.addEventListener=k.addEventListener.bind(k),this.removeEventListener=k.removeEventListener.bind(k),this.dispatchEvent=k.dispatchEvent.bind(k),this.open=function(b){h=new WebSocket(g.url,c||[]),b||k.dispatchEvent(l("connecting")),(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","attempt-connect",g.url);var d=h,e=setTimeout(function(){(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","connection-timeout",g.url),j=!0,d.close(),j=!1},g.timeoutInterval);h.onopen=function(){clearTimeout(e),(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onopen",g.url),g.protocol=h.protocol,g.readyState=WebSocket.OPEN,g.reconnectAttempts=0;var d=l("open");d.isReconnect=b,b=!1,k.dispatchEvent(d)},h.onclose=function(c){if(clearTimeout(e),h=null,i)g.readyState=WebSocket.CLOSED,k.dispatchEvent(l("close"));else{g.readyState=WebSocket.CONNECTING;var d=l("connecting");d.code=c.code,d.reason=c.reason,d.wasClean=c.wasClean,k.dispatchEvent(d),b||j||((g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onclose",g.url),k.dispatchEvent(l("close")));var e=g.reconnectInterval*Math.pow(g.reconnectDecay,g.reconnectAttempts);setTimeout(function(){g.reconnectAttempts++,g.open(!0)},e>g.maxReconnectInterval?g.maxReconnectInterval:e)}},h.onmessage=function(b){(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onmessage",g.url,b.data);var c=l("message");c.data=b.data,k.dispatchEvent(c)},h.onerror=function(b){(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onerror",g.url,b),k.dispatchEvent(l("error"))}},1==this.automaticOpen&&this.open(!1),this.send=function(b){if(h)return(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","send",g.url,b),h.send(b);throw"INVALID_STATE_ERR : Pausing to reconnect websocket"},this.close=function(a,b){"undefined"==typeof a&&(a=1e3),i=!0,h&&h.close(a,b)},this.refresh=function(){h&&h.close()}}return a.prototype.onopen=function(){},a.prototype.onclose=function(){},a.prototype.onconnecting=function(){},a.prototype.onmessage=function(){},a.prototype.onerror=function(){},a.debugAll=!1,a.CONNECTING=WebSocket.CONNECTING,a.OPEN=WebSocket.OPEN,a.CLOSING=WebSocket.CLOSING,a.CLOSED=WebSocket.CLOSED,a}); diff --git a/bin_UPWS/udp.dll b/bin_UPWS/udp.dll new file mode 100644 index 0000000..146d45a Binary files /dev/null and b/bin_UPWS/udp.dll differ diff --git a/bin_UPWS/websocket-sharp.dll b/bin_UPWS/websocket-sharp.dll new file mode 100644 index 0000000..01614c8 Binary files /dev/null and b/bin_UPWS/websocket-sharp.dll differ diff --git a/bin_UPWS/websocket-sharp.xml b/bin_UPWS/websocket-sharp.xml new file mode 100644 index 0000000..d4ffb44 --- /dev/null +++ b/bin_UPWS/websocket-sharp.xml @@ -0,0 +1,10326 @@ + + + + websocket-sharp + + + + + Specifies the byte order. + + + + + Specifies Little-endian. + + + + + Specifies Big-endian. + + + + + Represents the event data for the event. + + + + That event occurs when the WebSocket connection has been closed. + + + If you would like to get the reason for the close, you should access + the or property. + + + + + + Gets the status code for the close. + + + A that represents the status code for the close if any. + + + + + Gets the reason for the close. + + + A that represents the reason for the close if any. + + + + + Gets a value indicating whether the connection has been closed cleanly. + + + true if the connection has been closed cleanly; otherwise, false. + + + + + Indicates the status code for the WebSocket connection close. + + + + The values of this enumeration are defined in + + Section 7.4 of RFC 6455. + + + "Reserved value" cannot be sent as a status code in + closing handshake by an endpoint. + + + + + + Equivalent to close status 1000. Indicates normal close. + + + + + Equivalent to close status 1001. Indicates that an endpoint is + going away. + + + + + Equivalent to close status 1002. Indicates that an endpoint is + terminating the connection due to a protocol error. + + + + + 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. + + + + + Equivalent to close status 1004. Still undefined. A Reserved value. + + + + + Equivalent to close status 1005. Indicates that no status code was + actually present. A Reserved value. + + + + + Equivalent to close status 1006. Indicates that the connection was + closed abnormally. A Reserved value. + + + + + 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. + + + + + Equivalent to close status 1008. Indicates that an endpoint is + terminating the connection because it has received a message that + violates its policy. + + + + + 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. + + + + + 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. + + + + + 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. + + + + + Equivalent to close status 1015. Indicates that the connection was + closed due to a failure to perform a TLS handshake. A Reserved value. + + + + + Specifies the method for compression. + + + The methods are defined in + + Compression Extensions for WebSocket. + + + + + Specifies no compression. + + + + + Specifies DEFLATE. + + + + + Represents the event data for the event. + + + + That event occurs when the gets an error. + + + If you would like to get the error message, you should access + the property. + + + And if the error is due to an exception, you can get it by accessing + the property. + + + + + + Gets the exception that caused the error. + + + An instance that represents the cause of + the error if it is due to an exception; otherwise, . + + + + + Gets the error message. + + + A that represents the error message. + + + + + Provides a set of static methods for websocket-sharp. + + + + + Determines whether the specified equals the specified , + and invokes the specified Action<int> delegate at the same time. + + + true if equals ; + otherwise, false. + + + An to compare. + + + A to compare. + + + An Action<int> delegate that references the method(s) called + at the same time as comparing. An parameter to pass to + the method(s) is . + + + + + Gets the absolute path from the specified . + + + A that represents the absolute path if it's successfully found; + otherwise, . + + + A that represents the URI to get the absolute path from. + + + + + Gets the name from the specified that contains a pair of name and + value separated by a separator character. + + + A that represents the name if any; otherwise, null. + + + A that contains a pair of name and value separated by + a separator character. + + + A that represents the separator character. + + + + + Gets the value from the specified that contains a pair of name and + value separated by a separator character. + + + A that represents the value if any; otherwise, null. + + + A that contains a pair of name and value separated by + a separator character. + + + A that represents the separator character. + + + + + Tries to create a new for WebSocket with + the specified . + + + true if the was successfully created; + otherwise, false. + + + A that represents a WebSocket URL to try. + + + When this method returns, a that + represents the WebSocket URL or + if is invalid. + + + When this method returns, a that + represents an error message or + if is valid. + + + + + Determines whether the specified contains any of characters in + the specified array of . + + + true if contains any of ; + otherwise, false. + + + A to test. + + + An array of that contains characters to find. + + + + + Determines whether the specified contains + the entry with the specified . + + + true if contains the entry with + ; otherwise, false. + + + A to test. + + + A that represents the key of the entry to find. + + + + + Determines whether the specified contains the entry with + the specified both and . + + + true if contains the entry with both + and ; otherwise, false. + + + A to test. + + + A that represents the key of the entry to find. + + + A that represents the value of the entry to find. + + + + + Emits the specified delegate if it isn't . + + + A to emit. + + + An from which emits this . + + + A that contains no event data. + + + + + Emits the specified EventHandler<TEventArgs> delegate if it isn't + . + + + An EventHandler<TEventArgs> to emit. + + + An from which emits this . + + + A TEventArgs that represents the event data. + + + The type of the event data generated by the event. + + + + + Gets the collection of the HTTP cookies from the specified HTTP . + + + A that receives a collection of the HTTP cookies. + + + A that contains a collection of the HTTP headers. + + + true if is a collection of the response headers; + otherwise, false. + + + + + Gets the description of the specified HTTP status . + + + A that represents the description of the HTTP status code. + + + One of enum values, indicates the HTTP status code. + + + + + Gets the description of the specified HTTP status . + + + A that represents the description of the HTTP status code. + + + An that represents the HTTP status code. + + + + + Determines whether the specified is in the + range of the status code for the WebSocket connection close. + + + + The ranges are the following: + + + + + 1000-2999: These numbers are reserved for definition by + the WebSocket protocol. + + + + + 3000-3999: These numbers are reserved for use by libraries, + frameworks, and applications. + + + + + 4000-4999: These numbers are reserved for private use. + + + + + + true if is in the range of + the status code for the close; otherwise, false. + + + A to test. + + + + + Determines whether the specified is + enclosed in the specified . + + + true if is enclosed in + ; otherwise, false. + + + A to test. + + + A to find. + + + + + Determines whether the specified is host (this computer + architecture) byte order. + + + true if is host byte order; otherwise, false. + + + One of the enum values, to test. + + + + + Determines whether the specified + represents a local IP address. + + + This local means NOT REMOTE for the current host. + + + true if represents a local IP address; + otherwise, false. + + + A to test. + + + + + Determines whether the specified string is or + an empty string. + + + true if the string is or an empty string; + otherwise, false. + + + A to test. + + + + + Determines whether the specified is + a predefined scheme. + + + true if is a predefined scheme; + otherwise, false. + + + A to test. + + + + + Determines whether the specified is + an HTTP Upgrade request to switch to the specified . + + + true if is an HTTP Upgrade request to switch to + ; otherwise, false. + + + A that represents the HTTP request. + + + A that represents the protocol name. + + + + is . + + + -or- + + + is . + + + + is empty. + + + + + Determines whether the specified is a URI string. + + + true if may be a URI string; + otherwise, false. + + + A to test. + + + + + Retrieves a sub-array from the specified . A sub-array starts at + the specified element position in . + + + An array of T that receives a sub-array, or an empty array of T if any problems with + the parameters. + + + An array of T from which to retrieve a sub-array. + + + An that represents the zero-based starting position of + a sub-array in . + + + An that represents the number of elements to retrieve. + + + The type of elements in . + + + + + Retrieves a sub-array from the specified . A sub-array starts at + the specified element position in . + + + An array of T that receives a sub-array, or an empty array of T if any problems with + the parameters. + + + An array of T from which to retrieve a sub-array. + + + A that represents the zero-based starting position of + a sub-array in . + + + A that represents the number of elements to retrieve. + + + The type of elements in . + + + + + Executes the specified delegate times. + + + An is the number of times to execute. + + + An delegate that references the method(s) to execute. + + + + + Executes the specified delegate times. + + + A is the number of times to execute. + + + An delegate that references the method(s) to execute. + + + + + Executes the specified delegate times. + + + A is the number of times to execute. + + + An delegate that references the method(s) to execute. + + + + + Executes the specified delegate times. + + + A is the number of times to execute. + + + An delegate that references the method(s) to execute. + + + + + Executes the specified Action<int> delegate times. + + + An is the number of times to execute. + + + An Action<int> delegate that references the method(s) to execute. + An parameter to pass to the method(s) is the zero-based count of + iteration. + + + + + Executes the specified Action<long> delegate times. + + + A is the number of times to execute. + + + An Action<long> delegate that references the method(s) to execute. + A parameter to pass to the method(s) is the zero-based count of + iteration. + + + + + Executes the specified Action<uint> delegate times. + + + A is the number of times to execute. + + + An Action<uint> delegate that references the method(s) to execute. + A parameter to pass to the method(s) is the zero-based count of + iteration. + + + + + Executes the specified Action<ulong> delegate times. + + + A is the number of times to execute. + + + An Action<ulong> delegate that references the method(s) to execute. + A parameter to pass to this method(s) is the zero-based count of + iteration. + + + + + Converts the specified array of to the specified type data. + + + A T converted from , or a default value of + T if is an empty array of or + if the type of T isn't , , , + , , , , + , , or . + + + An array of to convert. + + + One of the enum values, specifies the byte order of + . + + + The type of the return. The T must be a value type. + + + is . + + + + + Converts the specified to an array of . + + + An array of converted from . + + + A T to convert. + + + One of the enum values, specifies the byte order of the return. + + + The type of . The T must be a value type. + + + + + Converts the order of the specified array of to the host byte order. + + + An array of converted from . + + + An array of to convert. + + + One of the enum values, specifies the byte order of + . + + + is . + + + + + Converts the specified to a that + concatenates the each element of across the specified + . + + + A converted from , + or if is empty. + + + An array of T to convert. + + + A that represents the separator string. + + + The type of elements in . + + + is . + + + + + Converts the specified to a . + + + A converted from or + if the convert has failed. + + + A to convert. + + + + + URL-decodes the specified . + + + A that receives the decoded string or + if it is or empty. + + + A to decode. + + + + + URL-encodes the specified . + + + A that receives the encoded string or + if it is or empty. + + + A to encode. + + + + + Writes and sends the specified data with the specified + . + + + A that represents the HTTP response used to + send the content data. + + + An array of that represents the content data to send. + + + + is . + + + -or- + + + is . + + + + + + Indicates whether a WebSocket frame is the final frame of a message. + + + The values of this enumeration are defined in + Section 5.2 of RFC 6455. + + + + + Equivalent to numeric value 0. Indicates more frames of a message follow. + + + + + Equivalent to numeric value 1. Indicates the final frame of a message. + + + + + Represents a log data used by the class. + + + + + Gets the information of the logging method caller. + + + A that provides the information of the logging method caller. + + + + + Gets the date and time when the log data was created. + + + A that represents the date and time when the log data was created. + + + + + Gets the logging level of the log data. + + + One of the enum values, indicates the logging level of the log data. + + + + + Gets the message of the log data. + + + A that represents the message of the log data. + + + + + Returns a that represents the current . + + + A that represents the current . + + + + + Provides a set of methods and properties for logging. + + + + If you output a log with lower than the value of the property, + it cannot be outputted. + + + The default output action writes a log to the standard output stream and the log file + if the property has a valid path to it. + + + If you would like to use the custom output action, you should set + the property to any Action<LogData, string> + delegate. + + + + + + Initializes a new instance of the class. + + + This constructor initializes the current logging level with . + + + + + Initializes a new instance of the class with + the specified logging . + + + One of the enum values. + + + + + Initializes a new instance of the class with + the specified logging , path to the log , + and action. + + + One of the enum values. + + + A that represents the path to the log file. + + + An Action<LogData, string> delegate that references the method(s) used to + output a log. A parameter passed to this delegate is + . + + + + + Gets or sets the current path to the log file. + + + A that represents the current path to the log file if any. + + + + + Gets or sets the current logging level. + + + A log with lower than the value of this property cannot be outputted. + + + One of the enum values, specifies the current logging level. + + + + + Gets or sets the current output action used to output a log. + + + + An Action<LogData, string> delegate that references the method(s) used to + output a log. A parameter passed to this delegate is the value of + the property. + + + If the value to set is , the current output action is changed to + the default output action. + + + + + + Outputs as a log with . + + + If the current logging level is higher than , + this method doesn't output as a log. + + + A that represents the message to output as a log. + + + + + Outputs as a log with . + + + If the current logging level is higher than , + this method doesn't output as a log. + + + A that represents the message to output as a log. + + + + + Outputs as a log with . + + + A that represents the message to output as a log. + + + + + Outputs as a log with . + + + If the current logging level is higher than , + this method doesn't output as a log. + + + A that represents the message to output as a log. + + + + + Outputs as a log with . + + + If the current logging level is higher than , + this method doesn't output as a log. + + + A that represents the message to output as a log. + + + + + Outputs as a log with . + + + If the current logging level is higher than , + this method doesn't output as a log. + + + A that represents the message to output as a log. + + + + + Specifies the logging level. + + + + + Specifies the bottom logging level. + + + + + Specifies the 2nd logging level from the bottom. + + + + + Specifies the 3rd logging level from the bottom. + + + + + Specifies the 3rd logging level from the top. + + + + + Specifies the 2nd logging level from the top. + + + + + Specifies the top logging level. + + + + + Indicates whether the payload data of a WebSocket frame is masked. + + + The values of this enumeration are defined in + Section 5.2 of RFC 6455. + + + + + Equivalent to numeric value 0. Indicates not masked. + + + + + Equivalent to numeric value 1. Indicates masked. + + + + + Represents the event data for the event. + + + + That event occurs when the receives + a message or a ping if the + property is set to true. + + + If you would like to get the message data, you should access + the or property. + + + + + + Gets the opcode for the message. + + + , , + or . + + + + + Gets the message data as a . + + + A that represents the message data if its type is + text or ping and if decoding it to a string has successfully done; + otherwise, . + + + + + Gets a value indicating whether the message type is binary. + + + true if the message type is binary; otherwise, false. + + + + + Gets a value indicating whether the message type is ping. + + + true if the message type is ping; otherwise, false. + + + + + Gets a value indicating whether the message type is text. + + + true if the message type is text; otherwise, false. + + + + + Gets the message data as an array of . + + + An array of that represents the message data. + + + + + Specifies the scheme for authentication. + + + + + No authentication is allowed. + + + + + Specifies digest authentication. + + + + + Specifies basic authentication. + + + + + Specifies anonymous authentication. + + + + + Stores the parameters for the used by clients. + + + + + Initializes a new instance of the class. + + + + + Initializes a new instance of the class + with the specified . + + + A that represents the target host server name. + + + + + Copies the parameters from the specified to + a new instance of the class. + + + A from which to copy. + + + is . + + + + + Gets or sets a value indicating whether the certificate revocation + list is checked during authentication. + + + + true if the certificate revocation list is checked during + authentication; otherwise, false. + + + The default value is false. + + + + + + Gets or sets the certificates from which to select one to + supply to the server. + + + + A or . + + + That collection contains client certificates from which to select. + + + The default value is . + + + + + + Gets or sets the callback used to select the certificate to + supply to the server. + + + No certificate is supplied if the callback returns + . + + + + A delegate that + invokes the method called for selecting the certificate. + + + The default value is a delegate that invokes a method that + only returns . + + + + + + Gets or sets the protocols used for authentication. + + + + The enum values that represent + the protocols used for authentication. + + + The default value is . + + + + + + Gets or sets the callback used to validate the certificate + supplied by the server. + + + The certificate is valid if the callback returns true. + + + + A delegate that + invokes the method called for validating the certificate. + + + The default value is a delegate that invokes a method that + only returns true. + + + + + + Gets or sets the target host server name. + + + + A or + if not specified. + + + That string represents the name of the server that + will share a secure connection with a client. + + + + + + Provides a set of methods and properties used to manage an HTTP Cookie. + + + + The Cookie class supports the following cookie formats: + Netscape specification, + RFC 2109, and + RFC 2965 + + + The Cookie class cannot be inherited. + + + + + + Initializes a new instance of the class. + + + + + Initializes a new instance of the class with the specified + and . + + + A that represents the Name of the cookie. + + + A that represents the Value of the cookie. + + + + is or empty. + + + - or - + + + contains an invalid character. + + + - or - + + + is . + + + - or - + + + contains a string not enclosed in double quotes + that contains an invalid character. + + + + + + Initializes a new instance of the class with the specified + , , and . + + + A that represents the Name of the cookie. + + + A that represents the Value of the cookie. + + + A that represents the value of the Path attribute of the cookie. + + + + is or empty. + + + - or - + + + contains an invalid character. + + + - or - + + + is . + + + - or - + + + contains a string not enclosed in double quotes + that contains an invalid character. + + + + + + Initializes a new instance of the class with the specified + , , , and + . + + + A that represents the Name of the cookie. + + + A that represents the Value of the cookie. + + + A that represents the value of the Path attribute of the cookie. + + + A that represents the value of the Domain attribute of the cookie. + + + + is or empty. + + + - or - + + + contains an invalid character. + + + - or - + + + is . + + + - or - + + + contains a string not enclosed in double quotes + that contains an invalid character. + + + + + + Gets or sets the value of the Comment attribute of the cookie. + + + A that represents the comment to document intended use of the cookie. + + + + + Gets or sets the value of the CommentURL attribute of the cookie. + + + A that represents the URI that provides the comment to document intended + use of the cookie. + + + + + Gets or sets a value indicating whether the client discards the cookie unconditionally + when the client terminates. + + + true if the client discards the cookie unconditionally when the client terminates; + otherwise, false. The default value is false. + + + + + Gets or sets the value of the Domain attribute of the cookie. + + + A that represents the URI for which the cookie is valid. + + + + + Gets or sets a value indicating whether the cookie has expired. + + + true if the cookie has expired; otherwise, false. + The default value is false. + + + + + Gets or sets the value of the Expires attribute of the cookie. + + + A that represents the date and time at which the cookie expires. + The default value is . + + + + + Gets or sets a value indicating whether non-HTTP APIs can access the cookie. + + + true if non-HTTP APIs cannot access the cookie; otherwise, false. + The default value is false. + + + + + Gets or sets the Name of the cookie. + + + A that represents the Name of the cookie. + + + + The value specified for a set operation is or empty. + + + - or - + + + The value specified for a set operation contains an invalid character. + + + + + + Gets or sets the value of the Path attribute of the cookie. + + + A that represents the subset of URI on the origin server + to which the cookie applies. + + + + + Gets or sets the value of the Port attribute of the cookie. + + + A that represents the list of TCP ports to which the cookie applies. + + + The value specified for a set operation isn't enclosed in double quotes or + couldn't be parsed. + + + + + Gets or sets a value indicating whether the security level of the cookie is secure. + + + When this property is true, the cookie may be included in the HTTP request + only if the request is transmitted over the HTTPS. + + + true if the security level of the cookie is secure; otherwise, false. + The default value is false. + + + + + Gets the time when the cookie was issued. + + + A that represents the time when the cookie was issued. + + + + + Gets or sets the Value of the cookie. + + + A that represents the Value of the cookie. + + + + The value specified for a set operation is . + + + - or - + + + The value specified for a set operation contains a string not enclosed in double quotes + that contains an invalid character. + + + + + + Gets or sets the value of the Version attribute of the cookie. + + + An that represents the version of the HTTP state management + to which the cookie conforms. + + + The value specified for a set operation isn't 0 or 1. + + + + + Determines whether the specified is equal to the current + . + + + An to compare with the current . + + + true if is equal to the current ; + otherwise, false. + + + + + Serves as a hash function for a object. + + + An that represents the hash code for the current . + + + + + Returns a that represents the current . + + + This method returns a to use to send an HTTP Cookie to + an origin server. + + + A that represents the current . + + + + + Provides a collection container for instances of the class. + + + + + Initializes a new instance of the class. + + + + + Gets the number of cookies in the collection. + + + An that represents the number of cookies in the collection. + + + + + Gets a value indicating whether the collection is read-only. + + + true if the collection is read-only; otherwise, false. + The default value is true. + + + + + Gets a value indicating whether the access to the collection is thread safe. + + + true if the access to the collection is thread safe; otherwise, false. + The default value is false. + + + + + Gets the at the specified from + the collection. + + + A at the specified in the collection. + + + An that represents the zero-based index of the + to find. + + + is out of allowable range of indexes for the collection. + + + + + Gets the with the specified from + the collection. + + + A with the specified in the collection. + + + A that represents the name of the to find. + + + is . + + + + + Gets an object used to synchronize access to the collection. + + + An used to synchronize access to the collection. + + + + + Adds the specified to the collection. + + + A to add. + + + is . + + + + + Adds the specified to the collection. + + + A that contains the cookies to add. + + + is . + + + + + Copies the elements of the collection to the specified , starting at + the specified in the . + + + An that represents the destination of the elements copied from + the collection. + + + An that represents the zero-based index in + at which copying begins. + + + is . + + + is less than zero. + + + + is multidimensional. + + + -or- + + + The number of elements in the collection is greater than the available space from + to the end of the destination . + + + + The elements in the collection cannot be cast automatically to the type of the destination + . + + + + + Copies the elements of the collection to the specified array of , + starting at the specified in the . + + + An array of that represents the destination of the elements + copied from the collection. + + + An that represents the zero-based index in + at which copying begins. + + + is . + + + is less than zero. + + + The number of elements in the collection is greater than the available space from + to the end of the destination . + + + + + Gets the enumerator used to iterate through the collection. + + + An instance used to iterate through the collection. + + + + + The exception that is thrown when a gets an error. + + + + + Initializes a new instance of the class from + the specified and . + + + A that contains the serialized object data. + + + A that specifies the source for the deserialization. + + + + + Initializes a new instance of the class. + + + + + Populates the specified with the data needed to serialize + the current . + + + A that holds the serialized object data. + + + A that specifies the destination for the serialization. + + + + + Populates the specified with the data needed to serialize + the current . + + + A that holds the serialized object data. + + + A that specifies the destination for the serialization. + + + + + Holds the username and password from an HTTP Basic authentication attempt. + + + + + Gets the password from a basic authentication attempt. + + + A that represents the password. + + + + + Holds the username and other parameters from + an HTTP Digest authentication attempt. + + + + + Gets the algorithm parameter from a digest authentication attempt. + + + A that represents the algorithm parameter. + + + + + Gets the cnonce parameter from a digest authentication attempt. + + + A that represents the cnonce parameter. + + + + + Gets the nc parameter from a digest authentication attempt. + + + A that represents the nc parameter. + + + + + Gets the nonce parameter from a digest authentication attempt. + + + A that represents the nonce parameter. + + + + + Gets the opaque parameter from a digest authentication attempt. + + + A that represents the opaque parameter. + + + + + Gets the qop parameter from a digest authentication attempt. + + + A that represents the qop parameter. + + + + + Gets the realm parameter from a digest authentication attempt. + + + A that represents the realm parameter. + + + + + Gets the response parameter from a digest authentication attempt. + + + A that represents the response parameter. + + + + + Gets the uri parameter from a digest authentication attempt. + + + A that represents the uri parameter. + + + + + Provides a simple, programmatically controlled HTTP listener. + + + + + Initializes a new instance of the class. + + + + + Gets or sets the scheme used to authenticate the clients. + + + One of the enum values, + represents the scheme used to authenticate the clients. The default value is + . + + + This listener has been closed. + + + + + Gets or sets the delegate called to select the scheme used to authenticate the clients. + + + 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 property as the authentication + scheme for all requests. + + + A Func<, > + delegate that references the method used to select an authentication scheme. The default + value is . + + + This listener has been closed. + + + + + Gets or sets the path to the folder in which stores the certificate files used to + authenticate the server on the secure connection. + + + + 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 'port number'.cer (DER) and 'port number'.key + (DER, RSA Private Key). + + + If this property is or empty, the result of + System.Environment.GetFolderPath + () is used as the default path. + + + + A that represents the path to the folder in which stores + the certificate files. The default value is . + + + This listener has been closed. + + + + + Gets or sets a value indicating whether the listener returns exceptions that occur when + sending the response to the client. + + + true if the listener shouldn't return those exceptions; otherwise, false. + The default value is false. + + + This listener has been closed. + + + + + Gets a value indicating whether the listener has been started. + + + true if the listener has been started; otherwise, false. + + + + + Gets a value indicating whether the listener can be used with the current operating system. + + + true. + + + + + Gets the logging functions. + + + The default logging level is . If you would like to change it, + you should set the Log.Level property to any of the enum + values. + + + A that provides the logging functions. + + + + + Gets the URI prefixes handled by the listener. + + + A that contains the URI prefixes. + + + This listener has been closed. + + + + + Gets or sets the name of the realm associated with the listener. + + + If this property is or empty, "SECRET AREA" will be used as + the name of the realm. + + + A that represents the name of the realm. The default value is + . + + + This listener has been closed. + + + + + Gets or sets the SSL configuration used to authenticate the server and + optionally the client for secure connection. + + + A that represents the configuration used to + authenticate the server and optionally the client for secure connection. + + + This listener has been closed. + + + + + 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. + + + This property isn't currently supported and always throws + a . + + + true if the authentication information of first request is used; + otherwise, false. + + + Any use of this property. + + + + + Gets or sets the delegate called to find the credentials for an identity used to + authenticate a client. + + + A Func<, > delegate + that references the method used to find the credentials. The default value is + . + + + This listener has been closed. + + + + + Shuts down the listener immediately. + + + + + Begins getting an incoming request asynchronously. + + + This asynchronous operation must be completed by calling the EndGetContext method. + Typically, the method is invoked by the delegate. + + + An that represents the status of the asynchronous operation. + + + An delegate that references the method to invoke when + the asynchronous operation completes. + + + An that represents a user defined object to pass to + the delegate. + + + + This listener has no URI prefix on which listens. + + + -or- + + + This listener hasn't been started, or is currently stopped. + + + + This listener has been closed. + + + + + Shuts down the listener. + + + + + Ends an asynchronous operation to get an incoming request. + + + This method completes an asynchronous operation started by calling + the BeginGetContext method. + + + A that represents a request. + + + An obtained by calling the BeginGetContext method. + + + is . + + + wasn't obtained by calling the BeginGetContext method. + + + This method was already called for the specified . + + + This listener has been closed. + + + + + Gets an incoming request. + + + This method waits for an incoming request, and returns when a request is received. + + + A that represents a request. + + + + This listener has no URI prefix on which listens. + + + -or- + + + This listener hasn't been started, or is currently stopped. + + + + This listener has been closed. + + + + + Starts receiving incoming requests. + + + This listener has been closed. + + + + + Stops receiving incoming requests. + + + This listener has been closed. + + + + + Releases all resources used by the listener. + + + + + Provides the access to the HTTP request and response objects used by + the . + + + This class cannot be inherited. + + + + + Gets the HTTP request object that represents a client request. + + + A that represents the client request. + + + + + Gets the HTTP response object used to send a response to the client. + + + A that represents a response to the client request. + + + + + Gets the client information (identity, authentication, and security roles). + + + A instance that represents the client information. + + + + + Accepts a WebSocket handshake request. + + + A that represents + the WebSocket handshake request. + + + A that represents the subprotocol supported on + this WebSocket connection. + + + + is empty. + + + -or- + + + contains an invalid character. + + + + This method has already been called. + + + + + The exception that is thrown when a gets an error + processing an HTTP request. + + + + + Initializes a new instance of the class from + the specified and . + + + A that contains the serialized object data. + + + A that specifies the source for the deserialization. + + + + + Initializes a new instance of the class. + + + + + Initializes a new instance of the class + with the specified . + + + An that identifies the error. + + + + + Initializes a new instance of the class + with the specified and . + + + An that identifies the error. + + + A that describes the error. + + + + + Gets the error code that identifies the error that occurred. + + + An that identifies the error. + + + + + Initializes a new instance of the class with + the specified . + + + This constructor must be called after calling the CheckPrefix method. + + + A that represents the URI prefix. + + + + + Determines whether this instance and the specified have the same value. + + + This method will be required to detect duplicates in any collection. + + + An to compare to this instance. + + + true if is a and + its value is the same as this instance; otherwise, false. + + + + + Gets the hash code for this instance. + + + This method will be required to detect duplicates in any collection. + + + An that represents the hash code. + + + + + Provides the collection used to store the URI prefixes for the . + + + The responds to the request which has a requested URI that + the prefixes most closely match. + + + + + Gets the number of prefixes in the collection. + + + An that represents the number of prefixes. + + + + + Gets a value indicating whether the access to the collection is read-only. + + + Always returns false. + + + + + Gets a value indicating whether the access to the collection is synchronized. + + + Always returns false. + + + + + Adds the specified to the collection. + + + A 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 '/'. + + + is . + + + is invalid. + + + The associated with this collection is closed. + + + + + Removes all URI prefixes from the collection. + + + The associated with this collection is closed. + + + + + Returns a value indicating whether the collection contains the specified + . + + + true if the collection contains ; + otherwise, false. + + + A that represents the URI prefix to test. + + + is . + + + The associated with this collection is closed. + + + + + Copies the contents of the collection to the specified . + + + An that receives the URI prefix strings in the collection. + + + An that represents the zero-based index in + at which copying begins. + + + The associated with this collection is closed. + + + + + Copies the contents of the collection to the specified array of . + + + An array of that receives the URI prefix strings in the collection. + + + An that represents the zero-based index in + at which copying begins. + + + The associated with this collection is closed. + + + + + Gets the enumerator used to iterate through the . + + + An instance used to iterate + through the collection. + + + + + Removes the specified from the collection. + + + true if is successfully found and removed; + otherwise, false. + + + A that represents the URI prefix to remove. + + + is . + + + The associated with this collection is closed. + + + + + Gets the enumerator used to iterate through the . + + + An instance used to iterate through the collection. + + + + + Provides the access to a request to the . + + + The HttpListenerRequest class cannot be inherited. + + + + + Gets the media types which are acceptable for the response. + + + An array of that contains the media type names in + the Accept request-header, or if the request didn't include + the Accept header. + + + + + Gets an error code that identifies a problem with the client's certificate. + + + Always returns 0. + + + + + Gets the encoding for the entity body data included in the request. + + + A that represents the encoding for the entity body data, + or if the request didn't include the information about + the encoding. + + + + + Gets the number of bytes in the entity body data included in the request. + + + A that represents the value of the Content-Length entity-header, + or -1 if the value isn't known. + + + + + Gets the media type of the entity body included in the request. + + + A that represents the value of the Content-Type entity-header. + + + + + Gets the cookies included in the request. + + + A that contains the cookies included in the request. + + + + + Gets a value indicating whether the request has the entity body. + + + true if the request has the entity body; otherwise, false. + + + + + Gets the HTTP headers used in the request. + + + A that contains the HTTP headers used in the request. + + + + + Gets the HTTP method used in the request. + + + A that represents the HTTP method used in the request. + + + + + Gets a that contains the entity body data included in the request. + + + A that contains the entity body data included in the request. + + + + + Gets a value indicating whether the client that sent the request is authenticated. + + + true if the client is authenticated; otherwise, false. + + + + + Gets a value indicating whether the request is sent from the local computer. + + + true if the request is sent from the local computer; otherwise, false. + + + + + Gets a value indicating whether the HTTP connection is secured using the SSL protocol. + + + true if the HTTP connection is secured; otherwise, false. + + + + + Gets a value indicating whether the request is a WebSocket connection request. + + + true if the request is a WebSocket connection request; otherwise, false. + + + + + Gets a value indicating whether the client requests a persistent connection. + + + true if the client requests a persistent connection; otherwise, false. + + + + + Gets the server endpoint as an IP address and a port number. + + + A that represents the server endpoint. + + + + + Gets the HTTP version used in the request. + + + A that represents the HTTP version used in the request. + + + + + Gets the query string included in the request. + + + A that contains the query string parameters. + + + + + Gets the raw URL (without the scheme, host, and port) requested by the client. + + + A that represents the raw URL requested by the client. + + + + + Gets the client endpoint as an IP address and a port number. + + + A that represents the client endpoint. + + + + + Gets the request identifier of a incoming HTTP request. + + + A that represents the identifier of a request. + + + + + Gets the URL requested by the client. + + + A that represents the URL requested by the client. + + + + + Gets the URL of the resource from which the requested URL was obtained. + + + A that represents the value of the Referer request-header, + or if the request didn't include an Referer header. + + + + + Gets the information about the user agent originating the request. + + + A that represents the value of the User-Agent request-header. + + + + + Gets the server endpoint as an IP address and a port number. + + + A that represents the server endpoint. + + + + + Gets the internet host name and port number (if present) specified by the client. + + + A that represents the value of the Host request-header. + + + + + Gets the natural languages which are preferred for the response. + + + An array of that contains the natural language names in + the Accept-Language request-header, or if the request + didn't include an Accept-Language header. + + + + + Begins getting the client's X.509 v.3 certificate asynchronously. + + + This asynchronous operation must be completed by calling + the method. Typically, + that method is invoked by the delegate. + + + An that contains the status of the asynchronous operation. + + + An delegate that references the method(s) called when + the asynchronous operation completes. + + + An that contains a user defined object to pass to + the delegate. + + + This method isn't implemented. + + + + + Ends an asynchronous operation to get the client's X.509 v.3 certificate. + + + This method completes an asynchronous operation started by calling + the method. + + + A that contains the client's X.509 v.3 certificate. + + + An obtained by calling + the method. + + + This method isn't implemented. + + + + + Gets the client's X.509 v.3 certificate. + + + A that contains the client's X.509 v.3 certificate. + + + This method isn't implemented. + + + + + Returns a that represents + the current . + + + A that represents the current . + + + + + Provides the access to a response to a request received by the . + + + The HttpListenerResponse class cannot be inherited. + + + + + Gets or sets the encoding for the entity body data included in the response. + + + A that represents the encoding for the entity body data, + or if no encoding is specified. + + + This object is closed. + + + + + Gets or sets the number of bytes in the entity body data included in the response. + + + A that represents the value of the Content-Length entity-header. + + + The value specified for a set operation is less than zero. + + + The response has already been sent. + + + This object is closed. + + + + + Gets or sets the media type of the entity body included in the response. + + + A that represents the media type of the entity body, + or if no media type is specified. This value is + used for the value of the Content-Type entity-header. + + + The value specified for a set operation is empty. + + + This object is closed. + + + + + Gets or sets the cookies sent with the response. + + + A that contains the cookies sent with the response. + + + + + Gets or sets the HTTP headers sent to the client. + + + A that contains the headers sent to the client. + + + The value specified for a set operation isn't valid for a response. + + + + + Gets or sets a value indicating whether the server requests a persistent connection. + + + true if the server requests a persistent connection; otherwise, false. + The default value is true. + + + The response has already been sent. + + + This object is closed. + + + + + Gets a to use to write the entity body data. + + + A to use to write the entity body data. + + + This object is closed. + + + + + Gets or sets the HTTP version used in the response. + + + A that represents the version used in the response. + + + The value specified for a set operation is . + + + The value specified for a set operation doesn't have its Major property set to 1 or + doesn't have its Minor property set to either 0 or 1. + + + The response has already been sent. + + + This object is closed. + + + + + Gets or sets the URL to which the client is redirected to locate a requested resource. + + + A that represents the value of the Location response-header, + or if no redirect location is specified. + + + The value specified for a set operation isn't an absolute URL. + + + This object is closed. + + + + + Gets or sets a value indicating whether the response uses the chunked transfer encoding. + + + true if the response uses the chunked transfer encoding; + otherwise, false. The default value is false. + + + The response has already been sent. + + + This object is closed. + + + + + Gets or sets the HTTP status code returned to the client. + + + An that represents the status code for the response to + the request. The default value is same as . + + + The response has already been sent. + + + This object is closed. + + + The value specified for a set operation is invalid. Valid values are + between 100 and 999 inclusive. + + + + + Gets or sets the description of the HTTP status code returned to the client. + + + A that represents the description of the status code. The default + value is the RFC 2616 + description for the property value, + or if an RFC 2616 description doesn't exist. + + + The value specified for a set operation contains invalid characters. + + + The response has already been sent. + + + This object is closed. + + + + + Closes the connection to the client without returning a response. + + + + + Adds an HTTP header with the specified and + to the headers for the response. + + + A that represents the name of the header to add. + + + A that represents the value of the header to add. + + + is or empty. + + + + or contains invalid characters. + + + -or- + + + is a restricted header name. + + + + The length of is greater than 65,535 characters. + + + The header cannot be allowed to add to the current headers. + + + + + Appends the specified to the cookies sent with the response. + + + A to append. + + + is . + + + + + Appends a to the specified HTTP header sent with the response. + + + A that represents the name of the header to append + to. + + + A that represents the value to append to the header. + + + is or empty. + + + + or contains invalid characters. + + + -or- + + + is a restricted header name. + + + + The length of is greater than 65,535 characters. + + + The current headers cannot allow the header to append a value. + + + + + Returns the response to the client and releases the resources used by + this instance. + + + + + Returns the response with the specified array of to the client and + releases the resources used by this instance. + + + An array of that contains the response entity body data. + + + true if this method blocks execution while flushing the stream to the client; + otherwise, false. + + + is . + + + This object is closed. + + + + + Copies some properties from the specified to + this response. + + + A to copy. + + + is . + + + + + Configures the response to redirect the client's request to + the specified . + + + This method sets the property to + , the property to + 302, and the property to + "Found". + + + A that represents the URL to redirect the client's request to. + + + is . + + + isn't an absolute URL. + + + The response has already been sent. + + + This object is closed. + + + + + Adds or updates a in the cookies sent with the response. + + + A to set. + + + is . + + + already exists in the cookies and couldn't be replaced. + + + + + Releases all resources used by the . + + + + + Contains the HTTP headers that may be specified in a client request. + + + The HttpRequestHeader enumeration contains the HTTP request headers defined in + RFC 2616 for the HTTP/1.1 and + RFC 6455 for the WebSocket. + + + + + Indicates the Cache-Control header. + + + + + Indicates the Connection header. + + + + + Indicates the Date header. + + + + + Indicates the Keep-Alive header. + + + + + Indicates the Pragma header. + + + + + Indicates the Trailer header. + + + + + Indicates the Transfer-Encoding header. + + + + + Indicates the Upgrade header. + + + + + Indicates the Via header. + + + + + Indicates the Warning header. + + + + + Indicates the Allow header. + + + + + Indicates the Content-Length header. + + + + + Indicates the Content-Type header. + + + + + Indicates the Content-Encoding header. + + + + + Indicates the Content-Language header. + + + + + Indicates the Content-Location header. + + + + + Indicates the Content-MD5 header. + + + + + Indicates the Content-Range header. + + + + + Indicates the Expires header. + + + + + Indicates the Last-Modified header. + + + + + Indicates the Accept header. + + + + + Indicates the Accept-Charset header. + + + + + Indicates the Accept-Encoding header. + + + + + Indicates the Accept-Language header. + + + + + Indicates the Authorization header. + + + + + Indicates the Cookie header. + + + + + Indicates the Expect header. + + + + + Indicates the From header. + + + + + Indicates the Host header. + + + + + Indicates the If-Match header. + + + + + Indicates the If-Modified-Since header. + + + + + Indicates the If-None-Match header. + + + + + Indicates the If-Range header. + + + + + Indicates the If-Unmodified-Since header. + + + + + Indicates the Max-Forwards header. + + + + + Indicates the Proxy-Authorization header. + + + + + Indicates the Referer header. + + + + + Indicates the Range header. + + + + + Indicates the TE header. + + + + + Indicates the Translate header. + + + + + Indicates the User-Agent header. + + + + + Indicates the Sec-WebSocket-Key header. + + + + + Indicates the Sec-WebSocket-Extensions header. + + + + + Indicates the Sec-WebSocket-Protocol header. + + + + + Indicates the Sec-WebSocket-Version header. + + + + + Contains the HTTP headers that can be specified in a server response. + + + The HttpResponseHeader enumeration contains the HTTP response headers defined in + RFC 2616 for the HTTP/1.1 and + RFC 6455 for the WebSocket. + + + + + Indicates the Cache-Control header. + + + + + Indicates the Connection header. + + + + + Indicates the Date header. + + + + + Indicates the Keep-Alive header. + + + + + Indicates the Pragma header. + + + + + Indicates the Trailer header. + + + + + Indicates the Transfer-Encoding header. + + + + + Indicates the Upgrade header. + + + + + Indicates the Via header. + + + + + Indicates the Warning header. + + + + + Indicates the Allow header. + + + + + Indicates the Content-Length header. + + + + + Indicates the Content-Type header. + + + + + Indicates the Content-Encoding header. + + + + + Indicates the Content-Language header. + + + + + Indicates the Content-Location header. + + + + + Indicates the Content-MD5 header. + + + + + Indicates the Content-Range header. + + + + + Indicates the Expires header. + + + + + Indicates the Last-Modified header. + + + + + Indicates the Accept-Ranges header. + + + + + Indicates the Age header. + + + + + Indicates the ETag header. + + + + + Indicates the Location header. + + + + + Indicates the Proxy-Authenticate header. + + + + + Indicates the Retry-After header. + + + + + Indicates the Server header. + + + + + Indicates the Set-Cookie header. + + + + + Indicates the Vary header. + + + + + Indicates the WWW-Authenticate header. + + + + + Indicates the Sec-WebSocket-Extensions header. + + + + + Indicates the Sec-WebSocket-Accept header. + + + + + Indicates the Sec-WebSocket-Protocol header. + + + + + Indicates the Sec-WebSocket-Version header. + + + + + Contains the values of the HTTP status codes. + + + The HttpStatusCode enumeration contains the values of the HTTP status codes defined in + RFC 2616 for the HTTP/1.1. + + + + + Equivalent to status code 100. + Indicates that the client should continue with its request. + + + + + Equivalent to status code 101. + Indicates that the server is switching the HTTP version or protocol on the connection. + + + + + Equivalent to status code 200. + Indicates that the client's request has succeeded. + + + + + Equivalent to status code 201. + Indicates that the client's request has been fulfilled and resulted in a new resource being + created. + + + + + Equivalent to status code 202. + Indicates that the client's request has been accepted for processing, but the processing + hasn't been completed. + + + + + Equivalent to status code 203. + Indicates that the returned metainformation is from a local or a third-party copy instead of + the origin server. + + + + + Equivalent to status code 204. + Indicates that the server has fulfilled the client's request but doesn't need to return + an entity-body. + + + + + 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. + + + + + Equivalent to status code 206. + Indicates that the server has fulfilled the partial GET request for the resource. + + + + + + Equivalent to status code 300. + Indicates that the requested resource corresponds to any of multiple representations. + + + MultipleChoices is a synonym for Ambiguous. + + + + + + + Equivalent to status code 300. + Indicates that the requested resource corresponds to any of multiple representations. + + + Ambiguous is a synonym for MultipleChoices. + + + + + + + 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. + + + MovedPermanently is a synonym for Moved. + + + + + + + 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. + + + Moved is a synonym for MovedPermanently. + + + + + + + Equivalent to status code 302. + Indicates that the requested resource is located temporarily under a different URI. + + + Found is a synonym for Redirect. + + + + + + + Equivalent to status code 302. + Indicates that the requested resource is located temporarily under a different URI. + + + Redirect is a synonym for Found. + + + + + + + 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. + + + SeeOther is a synonym for RedirectMethod. + + + + + + + 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. + + + RedirectMethod is a synonym for SeeOther. + + + + + + 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. + + + + + Equivalent to status code 305. + Indicates that the requested resource must be accessed through the proxy given by + the Location field. + + + + + 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. + + + + + + Equivalent to status code 307. + Indicates that the requested resource is located temporarily under a different URI. + + + TemporaryRedirect is a synonym for RedirectKeepVerb. + + + + + + + Equivalent to status code 307. + Indicates that the requested resource is located temporarily under a different URI. + + + RedirectKeepVerb is a synonym for TemporaryRedirect. + + + + + + Equivalent to status code 400. + Indicates that the client's request couldn't be understood by the server due to + malformed syntax. + + + + + Equivalent to status code 401. + Indicates that the client's request requires user authentication. + + + + + Equivalent to status code 402. + This status code is reserved for future use. + + + + + Equivalent to status code 403. + Indicates that the server understood the client's request but is refusing to fulfill it. + + + + + Equivalent to status code 404. + Indicates that the server hasn't found anything matching the request URI. + + + + + 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. + + + + + 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. + + + + + Equivalent to status code 407. + Indicates that the client must first authenticate itself with the proxy. + + + + + Equivalent to status code 408. + Indicates that the client didn't produce a request within the time that the server was + prepared to wait. + + + + + Equivalent to status code 409. + Indicates that the client's request couldn't be completed due to a conflict on the server. + + + + + Equivalent to status code 410. + Indicates that the requested resource is no longer available at the server and + no forwarding address is known. + + + + + Equivalent to status code 411. + Indicates that the server refuses to accept the client's request without a defined + Content-Length. + + + + + 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. + + + + + 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. + + + + + Equivalent to status code 414. + Indicates that the request URI is longer than the server is willing to interpret. + + + + + 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. + + + + + 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. + + + + + Equivalent to status code 417. + Indicates that the expectation given in an Expect request header couldn't be met by + the server. + + + + + Equivalent to status code 500. + Indicates that the server encountered an unexpected condition which prevented it from + fulfilling the client's request. + + + + + Equivalent to status code 501. + Indicates that the server doesn't support the functionality required to fulfill the client's + request. + + + + + Equivalent to status code 502. + Indicates that a gateway or proxy server received an invalid response from the upstream + server. + + + + + 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. + + + + + 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. + + + + + Equivalent to status code 505. + Indicates that the server doesn't support the HTTP version used in the client's request. + + + + + Decodes an HTML-encoded and returns the decoded . + + + A that represents the decoded string. + + + A to decode. + + + + + Decodes an HTML-encoded and sends the decoded + to the specified . + + + A to decode. + + + A that receives the decoded string. + + + + + HTML-encodes a and returns the encoded . + + + A that represents the encoded string. + + + A to encode. + + + + + HTML-encodes a and sends the encoded + to the specified . + + + A to encode. + + + A that receives the encoded string. + + + + + Provides the HTTP version numbers. + + + + + Provides a instance for the HTTP/1.0. + + + + + Provides a instance for the HTTP/1.1. + + + + + Initializes a new instance of the class. + + + + + Provides the credentials for the password-based authentication. + + + + + Initializes a new instance of the class with + the specified and . + + + A that represents the username associated with + the credentials. + + + A that represents the password for the username + associated with the credentials. + + + is . + + + is empty. + + + + + Initializes a new instance of the class with + the specified , , + and . + + + A that represents the username associated with + the credentials. + + + A that represents the password for the username + associated with the credentials. + + + A that represents the domain associated with + the credentials. + + + An array of that represents the roles + associated with the credentials if any. + + + is . + + + is empty. + + + + + Gets the domain associated with the credentials. + + + This property returns an empty string if the domain was + initialized with . + + + A that represents the domain name + to which the username belongs. + + + + + Gets the password for the username associated with the credentials. + + + This property returns an empty string if the password was + initialized with . + + + A that represents the password. + + + + + Gets the roles associated with the credentials. + + + This property returns an empty array if the roles were + initialized with . + + + An array of that represents the role names + to which the username belongs. + + + + + Gets the username associated with the credentials. + + + A that represents the username. + + + + + Stores the parameters for the used by servers. + + + + + Initializes a new instance of the class. + + + + + Initializes a new instance of the class + with the specified . + + + A that represents the certificate used to + authenticate the server. + + + + + Copies the parameters from the specified to + a new instance of the class. + + + A from which to copy. + + + is . + + + + + Gets or sets a value indicating whether the certificate revocation + list is checked during authentication. + + + + true if the certificate revocation list is checked during + authentication; otherwise, false. + + + The default value is false. + + + + + + Gets or sets a value indicating whether the client is asked for + a certificate for authentication. + + + + true if the client is asked for a certificate for + authentication; otherwise, false. + + + The default value is false. + + + + + + Gets or sets the callback used to validate the certificate + supplied by the client. + + + The certificate is valid if the callback returns true. + + + + A delegate that + invokes the method called for validating the certificate. + + + The default value is a delegate that invokes a method that + only returns true. + + + + + + Gets or sets the protocols used for authentication. + + + + The enum values that represent + the protocols used for authentication. + + + The default value is . + + + + + + Gets or sets the certificate used to authenticate the server. + + + + A or + if not specified. + + + That instance represents an X.509 certificate. + + + + + + Provides a collection of the HTTP headers associated with a request or response. + + + + + Initializes a new instance of the class from + the specified and . + + + A that contains the serialized object data. + + + A that specifies the source for the deserialization. + + + is . + + + An element with the specified name isn't found in . + + + + + Initializes a new instance of the class. + + + + + Gets all header names in the collection. + + + An array of that contains all header names in the collection. + + + + + Gets the number of headers in the collection. + + + An that represents the number of headers in the collection. + + + + + Gets or sets the specified request in the collection. + + + A that represents the value of the request . + + + One of the enum values, represents + the request header to get or set. + + + + is a restricted header. + + + -or- + + + contains invalid characters. + + + + The length of is greater than 65,535 characters. + + + The current instance doesn't allow + the request . + + + + + Gets or sets the specified response in the collection. + + + A that represents the value of the response . + + + One of the enum values, represents + the response header to get or set. + + + + is a restricted header. + + + -or- + + + contains invalid characters. + + + + The length of is greater than 65,535 characters. + + + The current instance doesn't allow + the response . + + + + + Gets a collection of header names in the collection. + + + A that contains + all header names in the collection. + + + + + Adds a header to the collection without checking if the header is on + the restricted header list. + + + A that represents the name of the header to add. + + + A that represents the value of the header to add. + + + is or empty. + + + or contains invalid characters. + + + The length of is greater than 65,535 characters. + + + The current instance doesn't allow + the . + + + + + Adds the specified to the collection. + + + A that represents the header with the name and value separated by + a colon (':'). + + + is , empty, or the name part of + is empty. + + + + doesn't contain a colon. + + + -or- + + + is a restricted header. + + + -or- + + + The name or value part of contains invalid characters. + + + + The length of the value part of is greater than 65,535 characters. + + + The current instance doesn't allow + the . + + + + + Adds the specified request with + the specified to the collection. + + + One of the enum values, represents + the request header to add. + + + A that represents the value of the header to add. + + + + is a restricted header. + + + -or- + + + contains invalid characters. + + + + The length of is greater than 65,535 characters. + + + The current instance doesn't allow + the request . + + + + + Adds the specified response with + the specified to the collection. + + + One of the enum values, represents + the response header to add. + + + A that represents the value of the header to add. + + + + is a restricted header. + + + -or- + + + contains invalid characters. + + + + The length of is greater than 65,535 characters. + + + The current instance doesn't allow + the response . + + + + + Adds a header with the specified and + to the collection. + + + A that represents the name of the header to add. + + + A that represents the value of the header to add. + + + is or empty. + + + + or contains invalid characters. + + + -or- + + + is a restricted header name. + + + + The length of is greater than 65,535 characters. + + + The current instance doesn't allow + the header . + + + + + Removes all headers from the collection. + + + + + Get the value of the header at the specified in the collection. + + + A that receives the value of the header. + + + An that represents the zero-based index of the header to find. + + + is out of allowable range of indexes for the collection. + + + + + Get the value of the header with the specified in the collection. + + + A that receives the value of the header if found; + otherwise, . + + + A that represents the name of the header to find. + + + + + Gets the enumerator used to iterate through the collection. + + + An instance used to iterate through the collection. + + + + + Get the name of the header at the specified in the collection. + + + A that receives the header name. + + + An that represents the zero-based index of the header to find. + + + is out of allowable range of indexes for the collection. + + + + + Gets an array of header values stored in the specified position of + the collection. + + + An array of that receives the header values if found; + otherwise, . + + + An that represents the zero-based index of the header to find. + + + is out of allowable range of indexes for the collection. + + + + + Gets an array of header values stored in the specified . + + + An array of that receives the header values if found; + otherwise, . + + + A that represents the name of the header to find. + + + + + Populates the specified with the data needed to serialize + the . + + + A that holds the serialized object data. + + + A that specifies the destination for the serialization. + + + is . + + + + + Determines whether the specified header can be set for the request. + + + true if the header is restricted; otherwise, false. + + + A that represents the name of the header to test. + + + is or empty. + + + contains invalid characters. + + + + + Determines whether the specified header can be set for the request or the response. + + + true if the header is restricted; otherwise, false. + + + A that represents the name of the header to test. + + + true if does the test for the response; for the request, false. + + + is or empty. + + + contains invalid characters. + + + + + Implements the interface and raises the deserialization event + when the deserialization is complete. + + + An that represents the source of the deserialization event. + + + + + Removes the specified request from the collection. + + + One of the enum values, represents + the request header to remove. + + + is a restricted header. + + + The current instance doesn't allow + the request . + + + + + Removes the specified response from the collection. + + + One of the enum values, represents + the response header to remove. + + + is a restricted header. + + + The current instance doesn't allow + the response . + + + + + Removes the specified header from the collection. + + + A that represents the name of the header to remove. + + + is or empty. + + + + contains invalid characters. + + + -or- + + + is a restricted header name. + + + + The current instance doesn't allow + the header . + + + + + Sets the specified request to the specified value. + + + One of the enum values, represents + the request header to set. + + + A that represents the value of the request header to set. + + + + is a restricted header. + + + -or- + + + contains invalid characters. + + + + The length of is greater than 65,535 characters. + + + The current instance doesn't allow + the request . + + + + + Sets the specified response to the specified value. + + + One of the enum values, represents + the response header to set. + + + A that represents the value of the response header to set. + + + + is a restricted header. + + + -or- + + + contains invalid characters. + + + + The length of is greater than 65,535 characters. + + + The current instance doesn't allow + the response . + + + + + Sets the specified header to the specified value. + + + A that represents the name of the header to set. + + + A that represents the value of the header to set. + + + is or empty. + + + + or contains invalid characters. + + + -or- + + + is a restricted header name. + + + + The length of is greater than 65,535 characters. + + + The current instance doesn't allow + the header . + + + + + Converts the current to an array of . + + + An array of that receives the converted current + . + + + + + Returns a that represents the current + . + + + A that represents the current . + + + + + Populates the specified with the data needed to serialize + the current . + + + A that holds the serialized object data. + + + A that specifies the destination for the serialization. + + + is . + + + + + Provides the properties used to access the information in + a WebSocket handshake request received by the . + + + + + Gets the HTTP cookies included in the request. + + + A that contains the cookies. + + + + + Gets the HTTP headers included in the request. + + + A that contains the headers. + + + + + Gets the value of the Host header included in the request. + + + A that represents the value of the Host header. + + + + + Gets a value indicating whether the client is authenticated. + + + true if the client is authenticated; otherwise, false. + + + + + Gets a value indicating whether the client connected from the local computer. + + + true if the client connected from the local computer; otherwise, false. + + + + + Gets a value indicating whether the WebSocket connection is secured. + + + true if the connection is secured; otherwise, false. + + + + + Gets a value indicating whether the request is a WebSocket handshake request. + + + true if the request is a WebSocket handshake request; otherwise, false. + + + + + Gets the value of the Origin header included in the request. + + + A that represents the value of the Origin header. + + + + + Gets the query string included in the request. + + + A that contains the query string parameters. + + + + + Gets the URI requested by the client. + + + A that represents the requested URI. + + + + + Gets the value of the Sec-WebSocket-Key header included in the request. + + + This property provides a part of the information used by the server to prove that + it received a valid WebSocket handshake request. + + + A that represents the value of the Sec-WebSocket-Key header. + + + + + Gets the values of the Sec-WebSocket-Protocol header included in the request. + + + This property represents the subprotocols requested by the client. + + + An instance that provides + an enumerator which supports the iteration over the values of the Sec-WebSocket-Protocol + header. + + + + + Gets the value of the Sec-WebSocket-Version header included in the request. + + + This property represents the WebSocket protocol version. + + + A that represents the value of the Sec-WebSocket-Version header. + + + + + Gets the server endpoint as an IP address and a port number. + + + A that represents the server endpoint. + + + + + Gets the client information (identity, authentication, and security roles). + + + A instance that represents the client information. + + + + + Gets the client endpoint as an IP address and a port number. + + + A that represents the client endpoint. + + + + + Gets the instance used for + two-way communication between client and server. + + + A . + + + + + Returns a that represents + the current . + + + A that represents + the current . + + + + + Provides the properties used to access the information in + a WebSocket handshake request received by the . + + + + + Gets the HTTP cookies included in the request. + + + A that contains the cookies. + + + + + Gets the HTTP headers included in the request. + + + A that contains the headers. + + + + + Gets the value of the Host header included in the request. + + + A that represents the value of the Host header. + + + + + Gets a value indicating whether the client is authenticated. + + + true if the client is authenticated; otherwise, false. + + + + + Gets a value indicating whether the client connected from the local computer. + + + true if the client connected from the local computer; otherwise, false. + + + + + Gets a value indicating whether the WebSocket connection is secured. + + + true if the connection is secured; otherwise, false. + + + + + Gets a value indicating whether the request is a WebSocket handshake request. + + + true if the request is a WebSocket handshake request; otherwise, false. + + + + + Gets the value of the Origin header included in the request. + + + A that represents the value of the Origin header. + + + + + Gets the query string included in the request. + + + A that contains the query string parameters. + + + + + Gets the URI requested by the client. + + + A that represents the requested URI. + + + + + Gets the value of the Sec-WebSocket-Key header included in the request. + + + This property provides a part of the information used by the server to prove that + it received a valid WebSocket handshake request. + + + A that represents the value of the Sec-WebSocket-Key header. + + + + + Gets the values of the Sec-WebSocket-Protocol header included in the request. + + + This property represents the subprotocols requested by the client. + + + An instance that provides + an enumerator which supports the iteration over the values of the Sec-WebSocket-Protocol + header. + + + + + Gets the value of the Sec-WebSocket-Version header included in the request. + + + This property represents the WebSocket protocol version. + + + A that represents the value of the Sec-WebSocket-Version header. + + + + + Gets the server endpoint as an IP address and a port number. + + + A that represents the server endpoint. + + + + + Gets the client information (identity, authentication, and security roles). + + + A instance that represents the client information. + + + + + Gets the client endpoint as an IP address and a port number. + + + A that represents the client endpoint. + + + + + Gets the instance used for + two-way communication between client and server. + + + A . + + + + + Returns a that represents + the current . + + + A that represents + the current . + + + + + Exposes the properties used to access the information in a WebSocket handshake request. + + + This class is an abstract class. + + + + + Initializes a new instance of the class. + + + + + Gets the HTTP cookies included in the request. + + + A that contains the cookies. + + + + + Gets the HTTP headers included in the request. + + + A that contains the headers. + + + + + Gets the value of the Host header included in the request. + + + A that represents the value of the Host header. + + + + + Gets a value indicating whether the client is authenticated. + + + true if the client is authenticated; otherwise, false. + + + + + Gets a value indicating whether the client connected from the local computer. + + + true if the client connected from the local computer; otherwise, false. + + + + + Gets a value indicating whether the WebSocket connection is secured. + + + true if the connection is secured; otherwise, false. + + + + + Gets a value indicating whether the request is a WebSocket handshake request. + + + true if the request is a WebSocket handshake request; otherwise, false. + + + + + Gets the value of the Origin header included in the request. + + + A that represents the value of the Origin header. + + + + + Gets the query string included in the request. + + + A that contains the query string parameters. + + + + + Gets the URI requested by the client. + + + A that represents the requested URI. + + + + + Gets the value of the Sec-WebSocket-Key header included in the request. + + + This property provides a part of the information used by the server to prove that + it received a valid WebSocket handshake request. + + + A that represents the value of the Sec-WebSocket-Key header. + + + + + Gets the values of the Sec-WebSocket-Protocol header included in the request. + + + This property represents the subprotocols requested by the client. + + + An instance that provides + an enumerator which supports the iteration over the values of the Sec-WebSocket-Protocol + header. + + + + + Gets the value of the Sec-WebSocket-Version header included in the request. + + + This property represents the WebSocket protocol version. + + + A that represents the value of the Sec-WebSocket-Version header. + + + + + Gets the server endpoint as an IP address and a port number. + + + A that represents the server endpoint. + + + + + Gets the client information (identity, authentication, and security roles). + + + A instance that represents the client information. + + + + + Gets the client endpoint as an IP address and a port number. + + + A that represents the client endpoint. + + + + + Gets the instance used for + two-way communication between client and server. + + + A . + + + + + Indicates the WebSocket frame type. + + + The values of this enumeration are defined in + + Section 5.2 of RFC 6455. + + + + + Equivalent to numeric value 0. Indicates continuation frame. + + + + + Equivalent to numeric value 1. Indicates text frame. + + + + + Equivalent to numeric value 2. Indicates binary frame. + + + + + Equivalent to numeric value 8. Indicates connection close frame. + + + + + Equivalent to numeric value 9. Indicates ping frame. + + + + + Equivalent to numeric value 10. Indicates pong frame. + + + + + Represents the empty payload data. + + + + + Represents the allowable max length. + + + + A will occur if the payload data length is + greater than the value of this field. + + + If you would like to change the value, you must set it to a value between + WebSocket.FragmentLength and Int64.MaxValue inclusive. + + + + + + Indicates whether each RSV (RSV1, RSV2, and RSV3) of a WebSocket frame is non-zero. + + + The values of this enumeration are defined in + Section 5.2 of RFC 6455. + + + + + Equivalent to numeric value 0. Indicates zero. + + + + + Equivalent to numeric value 1. Indicates non-zero. + + + + + Represents the event data for the HTTP request events of + the . + + + + An HTTP request event occurs when the + receives an HTTP request. + + + You should access the property if you would + like to get the request data sent from a client. + + + And you should access the property if you would + like to get the response data to return to the client. + + + + + + Gets the request data sent from a client. + + + A that provides the methods and + properties for the request data. + + + + + Gets the response data to return to the client. + + + A that provides the methods and + properties for the response data. + + + + + Gets the information for the client. + + + + A instance or + if not authenticated. + + + That instance describes the identity, authentication scheme, + and security roles for the client. + + + + + + Reads the specified file from the document folder of + the . + + + + An array of or + if it fails. + + + That array receives the contents of the file. + + + + A that represents a virtual path to + find the file from the document folder. + + + is . + + + + is an empty string. + + + -or- + + + contains "..". + + + + + + Tries to read the specified file from the document folder of + the . + + + true if it succeeds to read; otherwise, false. + + + A that represents a virtual path to + find the file from the document folder. + + + + When this method returns, an array of or + if it fails. + + + That array receives the contents of the file. + + + + is . + + + + is an empty string. + + + -or- + + + contains "..". + + + + + + Provides a simple HTTP server that allows to accept + WebSocket handshake requests. + + + This class can provide multiple WebSocket services. + + + + + Initializes a new instance of the class. + + + The new instance listens for incoming requests on + and port 80. + + + + + Initializes a new instance of the class with + the specified . + + + + The new instance listens for incoming requests on + and . + + + It provides secure connections if is 443. + + + + An that represents the number of the port + on which to listen. + + + is less than 1 or greater than 65535. + + + + + Initializes a new instance of the class with + the specified . + + + + The new instance listens for incoming requests on the IP address of the + host of and the port of . + + + Either port 80 or 443 is used if includes + no port. Port 443 is used if the scheme of + is https; otherwise, port 80 is used. + + + The new instance provides secure connections if the scheme of + is https. + + + + A that represents the HTTP URL of the server. + + + is . + + + + is empty. + + + -or- + + + is invalid. + + + + + + Initializes a new instance of the class with + the specified and . + + + The new instance listens for incoming requests on + and . + + + An that represents the number of the port + on which to listen. + + + A : true if the new instance provides + secure connections; otherwise, false. + + + is less than 1 or greater than 65535. + + + + + Initializes a new instance of the class with + the specified and . + + + + The new instance listens for incoming requests on + and . + + + It provides secure connections if is 443. + + + + A that represents + the local IP address on which to listen. + + + An that represents the number of the port + on which to listen. + + + is . + + + is not a local IP address. + + + is less than 1 or greater than 65535. + + + + + Initializes a new instance of the class with + the specified , , + and . + + + The new instance listens for incoming requests on + and . + + + A that represents + the local IP address on which to listen. + + + An that represents the number of the port + on which to listen. + + + A : true if the new instance provides + secure connections; otherwise, false. + + + is . + + + is not a local IP address. + + + is less than 1 or greater than 65535. + + + + + Gets the IP address of the server. + + + A that represents the local + IP address on which to listen for incoming requests. + + + + + Gets or sets the scheme used to authenticate the clients. + + + The set operation does nothing if the server has already + started or it is shutting down. + + + + One of the + enum values. + + + It represents the scheme used to authenticate the clients. + + + The default value is + . + + + + + + Gets or sets the path to the document folder of the server. + + + + '/' or '\' is trimmed from the end of the value if any. + + + The set operation does nothing if the server has already + started or it is shutting down. + + + + + A that represents a path to the folder + from which to find the requested file. + + + The default value is "./Public". + + + + The value specified for a set operation is . + + + + The value specified for a set operation is an empty string. + + + -or- + + + The value specified for a set operation is an invalid path string. + + + -or- + + + The value specified for a set operation is an absolute root. + + + + + + Gets a value indicating whether the server has started. + + + true if the server has started; otherwise, false. + + + + + Gets a value indicating whether the server provides + secure connections. + + + true if the server provides secure connections; + otherwise, false. + + + + + Gets or sets a value indicating whether the server cleans up + the inactive sessions periodically. + + + The set operation does nothing if the server has already + started or it is shutting down. + + + + true if the server cleans up the inactive sessions + every 60 seconds; otherwise, false. + + + The default value is true. + + + + + + Gets the logging function for the server. + + + The default logging level is . + + + A that provides the logging function. + + + + + Gets the port of the server. + + + An that represents the number of the port + on which to listen for incoming requests. + + + + + Gets or sets the realm used for authentication. + + + + "SECRET AREA" is used as the realm if the value is + or an empty string. + + + The set operation does nothing if the server has + already started or it is shutting down. + + + + + A or by default. + + + That string represents the name of the realm. + + + + + + Gets or sets a value indicating whether the server is allowed to + be bound to an address that is already in use. + + + + You should set this property to true if you would + like to resolve to wait for socket in TIME_WAIT state. + + + The set operation does nothing if the server has already + started or it is shutting down. + + + + + true if the server is allowed to be bound to an address + that is already in use; otherwise, false. + + + The default value is false. + + + + + + Gets the configuration for secure connections. + + + This configuration will be referenced when attempts to start, + so it must be configured before the start method is called. + + + A that represents + the configuration used to provide secure connections. + + + This instance does not provide secure connections. + + + + + Gets or sets the delegate used to find the credentials + for an identity. + + + + No credentials are found if the method invoked by + the delegate returns or + the value is . + + + The set operation does nothing if the server has + already started or it is shutting down. + + + + + A Func<, + > delegate or + if not needed. + + + That delegate invokes the method called for finding + the credentials used to authenticate a client. + + + The default value is . + + + + + + Gets or sets the time to wait for the response to the WebSocket Ping or + Close. + + + The set operation does nothing if the server has already started or + it is shutting down. + + + + A to wait for the response. + + + The default value is the same as 1 second. + + + + The value specified for a set operation is zero or less. + + + + + Gets the management function for the WebSocket services + provided by the server. + + + A that manages + the WebSocket services provided by the server. + + + + + Occurs when the server receives an HTTP CONNECT request. + + + + + Occurs when the server receives an HTTP DELETE request. + + + + + Occurs when the server receives an HTTP GET request. + + + + + Occurs when the server receives an HTTP HEAD request. + + + + + Occurs when the server receives an HTTP OPTIONS request. + + + + + Occurs when the server receives an HTTP PATCH request. + + + + + Occurs when the server receives an HTTP POST request. + + + + + Occurs when the server receives an HTTP PUT request. + + + + + Occurs when the server receives an HTTP TRACE request. + + + + + Adds a WebSocket service with the specified behavior, + , and . + + + is converted to a URL-decoded string and + '/' is trimmed from the end of the converted string if any. + + + A that represents an absolute path to + the service to add. + + + + A Func<TBehavior> delegate. + + + It invokes the method called for creating + a new session instance for the service. + + + The method must create a new instance of + the specified behavior class and return it. + + + + + The type of the behavior for the service. + + + It must inherit the class. + + + + + is . + + + -or- + + + is . + + + + + is an empty string. + + + -or- + + + is not an absolute path. + + + -or- + + + includes either or both + query and fragment components. + + + -or- + + + is already in use. + + + + + + Adds a WebSocket service with the specified behavior and + . + + + is converted to a URL-decoded string and + '/' is trimmed from the end of the converted string if any. + + + A that represents an absolute path to + the service to add. + + + + The type of the behavior for the service. + + + It must inherit the class and + must have a public parameterless constructor. + + + + is . + + + + is an empty string. + + + -or- + + + is not an absolute path. + + + -or- + + + includes either or both + query and fragment components. + + + -or- + + + is already in use. + + + + + + Adds a WebSocket service with the specified behavior, + , and . + + + is converted to a URL-decoded string and + '/' is trimmed from the end of the converted string if any. + + + A that represents an absolute path to + the service to add. + + + + An Action<TBehaviorWithNew> delegate or + if not needed. + + + That delegate invokes the method called for initializing + a new session instance for the service. + + + + + The type of the behavior for the service. + + + It must inherit the class and + must have a public parameterless constructor. + + + + is . + + + + is an empty string. + + + -or- + + + is not an absolute path. + + + -or- + + + includes either or both + query and fragment components. + + + -or- + + + is already in use. + + + + + + Gets the contents of the specified file from the document + folder of the server. + + + + An array of or + if it fails. + + + That array represents the contents of the file. + + + + A that represents a virtual path to + find the file from the document folder. + + + is . + + + + is an empty string. + + + -or- + + + contains "..". + + + + + + Removes a WebSocket service with the specified . + + + + is converted to a URL-decoded string and + '/' is trimmed from the end of the converted string if any. + + + The service is stopped with close status 1001 (going away) + if it has already started. + + + + true if the service is successfully found and removed; + otherwise, false. + + + A that represents an absolute path to + the service to remove. + + + is . + + + + is an empty string. + + + -or- + + + is not an absolute path. + + + -or- + + + includes either or both + query and fragment components. + + + + + + Starts receiving incoming requests. + + + This method does nothing if the server has already started or + it is shutting down. + + + + There is no server certificate for secure connections. + + + -or- + + + The underlying has failed to start. + + + + + + Stops receiving incoming requests and closes each connection. + + + This method does nothing if the server is not started, + it is shutting down, or it has already stopped. + + + + + Stops receiving incoming requests and closes each connection. + + + This method does nothing if the server is not started, + it is shutting down, or it has already stopped. + + + + A that represents the status code + indicating the reason for the WebSocket connection close. + + + The status codes are defined in + + Section 7.4 of RFC 6455. + + + + + A that represents the reason for + the WebSocket connection close. + + + The size must be 123 bytes or less in UTF-8. + + + + + is less than 1000 or greater than 4999. + + + -or- + + + The size of is greater than 123 bytes. + + + + + is 1010 (mandatory extension). + + + -or- + + + is 1005 (no status) and + there is . + + + -or- + + + could not be UTF-8-encoded. + + + + + + Stops receiving incoming requests and closes each connection. + + + This method does nothing if the server is not started, + it is shutting down, or it has already stopped. + + + + One of the enum values. + + + It represents the status code indicating the reason for + the WebSocket connection close. + + + + + A that represents the reason for + the WebSocket connection close. + + + The size must be 123 bytes or less in UTF-8. + + + + The size of is greater than 123 bytes. + + + + is + . + + + -or- + + + is + and + there is . + + + -or- + + + could not be UTF-8-encoded. + + + + + + Exposes the properties used to access the information in a session in a WebSocket service. + + + + + Gets the information in the connection request to the WebSocket service. + + + A that provides the access to the connection request. + + + + + Gets the unique ID of the session. + + + A that represents the unique ID of the session. + + + + + Gets the WebSocket subprotocol used in the session. + + + A that represents the subprotocol if any. + + + + + Gets the time that the session has started. + + + A that represents the time that the session has started. + + + + + Gets the state of the used in the session. + + + One of the enum values, indicates the state of + the used in the session. + + + + + Exposes the methods and properties used to define the behavior of a WebSocket service + provided by the or . + + + The WebSocketBehavior class is an abstract class. + + + + + Initializes a new instance of the class. + + + + + Gets the logging functions. + + + A that provides the logging functions, + or if the WebSocket connection isn't established. + + + + + Gets the access to the sessions in the WebSocket service. + + + A that provides the access to the sessions, + or if the WebSocket connection isn't established. + + + + + Gets the information in a handshake request to the WebSocket service. + + + A instance that provides the access to the handshake request, + or if the WebSocket connection isn't established. + + + + + Gets or sets the delegate called to validate the HTTP cookies included in + a handshake request to the WebSocket service. + + + This delegate is called when the used in a session validates + the handshake request. + + + + A Func<CookieCollection, CookieCollection, bool> delegate that references + the method(s) used to validate the cookies. + + + 1st parameter passed to this delegate contains + the cookies to validate if any. + + + 2nd parameter passed to this delegate receives + the cookies to send to the client. + + + This delegate should return true if the cookies are valid. + + + The default value is , and it does nothing to validate. + + + + + + Gets or sets a value indicating whether the used in a session emits + a event when receives a Ping. + + + true if the emits a event + when receives a Ping; otherwise, false. The default value is false. + + + + + Gets the unique ID of a session. + + + A that represents the unique ID of the session, + or if the WebSocket connection isn't established. + + + + + Gets or sets a value indicating whether the WebSocket service ignores + the Sec-WebSocket-Extensions header included in a handshake request. + + + true if the WebSocket service ignores the extensions requested from + a client; otherwise, false. The default value is false. + + + + + Gets or sets the delegate called to validate the Origin header included in + a handshake request to the WebSocket service. + + + This delegate is called when the used in a session validates + the handshake request. + + + + A Func<string, bool> delegate that references the method(s) used to + validate the origin header. + + + parameter passed to this delegate represents the value of + the origin header to validate if any. + + + This delegate should return true if the origin header is valid. + + + The default value is , and it does nothing to validate. + + + + + + Gets or sets the WebSocket subprotocol used in the WebSocket service. + + + Set operation of this property is available before the WebSocket connection has + been established. + + + + A that represents the subprotocol if any. + The default value is . + + + The value to set must be a token defined in + RFC 2616. + + + + + + Gets the time that a session has started. + + + A that represents the time that the session has started, + or if the WebSocket connection isn't established. + + + + + Gets the state of the used in a session. + + + One of the enum values, indicates the state of + the . + + + + + Calls the method with the specified and + . + + + This method doesn't call the method if is + or empty. + + + A that represents the error message. + + + An instance that represents the cause of the error if any. + + + + + Called when the WebSocket connection used in a session has been closed. + + + A that represents the event data passed to + a event. + + + + + Called when the used in a session gets an error. + + + A that represents the event data passed to + a event. + + + + + Called when the used in a session receives a message. + + + A that represents the event data passed to + a event. + + + + + Called when the WebSocket connection used in a session has been established. + + + + + Sends binary to the client on a session. + + + This method is available after the WebSocket connection has been established. + + + An array of that represents the binary data to send. + + + + + Sends the specified as binary data to the client on a session. + + + This method is available after the WebSocket connection has been established. + + + A that represents the file to send. + + + + + Sends text to the client on a session. + + + This method is available after the WebSocket connection has been established. + + + A that represents the text data to send. + + + + + Sends binary asynchronously to the client on a session. + + + + This method is available after the WebSocket connection has been established. + + + This method doesn't wait for the send to be complete. + + + + An array of that represents the binary data to send. + + + An Action<bool> delegate that references the method(s) called when + the send is complete. A passed to this delegate is true + if the send is complete successfully. + + + + + Sends the specified as binary data asynchronously to + the client on a session. + + + + This method is available after the WebSocket connection has been established. + + + This method doesn't wait for the send to be complete. + + + + A that represents the file to send. + + + An Action<bool> delegate that references the method(s) called when + the send is complete. A passed to this delegate is true + if the send is complete successfully. + + + + + Sends text asynchronously to the client on a session. + + + + This method is available after the WebSocket connection has been established. + + + This method doesn't wait for the send to be complete. + + + + A that represents the text data to send. + + + An Action<bool> delegate that references the method(s) called when + the send is complete. A passed to this delegate is true + if the send is complete successfully. + + + + + Sends binary data from the specified asynchronously to + the client on a session. + + + + This method is available after the WebSocket connection has been established. + + + This method doesn't wait for the send to be complete. + + + + A from which contains the binary data to send. + + + An that represents the number of bytes to send. + + + An Action<bool> delegate that references the method(s) called when + the send is complete. A passed to this delegate is true + if the send is complete successfully. + + + + + Provides a WebSocket protocol server. + + + This class can provide multiple WebSocket services. + + + + + Initializes a new instance of the class. + + + The new instance listens for incoming handshake requests on + and port 80. + + + + + Initializes a new instance of the class + with the specified . + + + + The new instance listens for incoming handshake requests on + and . + + + It provides secure connections if is 443. + + + + An that represents the number of the port + on which to listen. + + + is less than 1 or greater than 65535. + + + + + Initializes a new instance of the class + with the specified . + + + + The new instance listens for incoming handshake requests on + the IP address of the host of and + the port of . + + + Either port 80 or 443 is used if includes + no port. Port 443 is used if the scheme of + is wss; otherwise, port 80 is used. + + + The new instance provides secure connections if the scheme of + is wss. + + + + A that represents the WebSocket URL of the server. + + + is . + + + + is an empty string. + + + -or- + + + is invalid. + + + + + + Initializes a new instance of the class + with the specified and . + + + The new instance listens for incoming handshake requests on + and . + + + An that represents the number of the port + on which to listen. + + + A : true if the new instance provides + secure connections; otherwise, false. + + + is less than 1 or greater than 65535. + + + + + Initializes a new instance of the class + with the specified and . + + + + The new instance listens for incoming handshake requests on + and . + + + It provides secure connections if is 443. + + + + A that represents the local + IP address on which to listen. + + + An that represents the number of the port + on which to listen. + + + is . + + + is not a local IP address. + + + is less than 1 or greater than 65535. + + + + + Initializes a new instance of the class + with the specified , , + and . + + + The new instance listens for incoming handshake requests on + and . + + + A that represents the local + IP address on which to listen. + + + An that represents the number of the port + on which to listen. + + + A : true if the new instance provides + secure connections; otherwise, false. + + + is . + + + is not a local IP address. + + + is less than 1 or greater than 65535. + + + + + Gets the IP address of the server. + + + A that represents the local + IP address on which to listen for incoming handshake requests. + + + + + Gets or sets a value indicating whether the server accepts every + handshake request without checking the request URI. + + + The set operation does nothing if the server has already started or + it is shutting down. + + + + true if the server accepts every handshake request without + checking the request URI; otherwise, false. + + + The default value is false. + + + + + + Gets or sets the scheme used to authenticate the clients. + + + The set operation does nothing if the server has already started or + it is shutting down. + + + + One of the + enum values. + + + It represents the scheme used to authenticate the clients. + + + The default value is + . + + + + + + Gets a value indicating whether the server has started. + + + true if the server has started; otherwise, false. + + + + + Gets a value indicating whether the server provides + secure connections. + + + true if the server provides secure connections; + otherwise, false. + + + + + Gets or sets a value indicating whether the server cleans up + the inactive sessions periodically. + + + The set operation does nothing if the server has already started or + it is shutting down. + + + + true if the server cleans up the inactive sessions every + 60 seconds; otherwise, false. + + + The default value is true. + + + + + + Gets the logging function for the server. + + + The default logging level is . + + + A that provides the logging function. + + + + + Gets the port of the server. + + + An that represents the number of the port + on which to listen for incoming handshake requests. + + + + + Gets or sets the realm used for authentication. + + + + "SECRET AREA" is used as the realm if the value is + or an empty string. + + + The set operation does nothing if the server has + already started or it is shutting down. + + + + + A or by default. + + + That string represents the name of the realm. + + + + + + Gets or sets a value indicating whether the server is allowed to + be bound to an address that is already in use. + + + + You should set this property to true if you would + like to resolve to wait for socket in TIME_WAIT state. + + + The set operation does nothing if the server has already + started or it is shutting down. + + + + + true if the server is allowed to be bound to an address + that is already in use; otherwise, false. + + + The default value is false. + + + + + + Gets the configuration for secure connections. + + + This configuration will be referenced when attempts to start, + so it must be configured before the start method is called. + + + A that represents + the configuration used to provide secure connections. + + + This instance does not provide secure connections. + + + + + Gets or sets the delegate used to find the credentials + for an identity. + + + + No credentials are found if the method invoked by + the delegate returns or + the value is . + + + The set operation does nothing if the server has + already started or it is shutting down. + + + + + A Func<, + > delegate or + if not needed. + + + That delegate invokes the method called for finding + the credentials used to authenticate a client. + + + The default value is . + + + + + + Gets or sets the time to wait for the response to the WebSocket Ping or + Close. + + + The set operation does nothing if the server has already started or + it is shutting down. + + + + A to wait for the response. + + + The default value is the same as 1 second. + + + + The value specified for a set operation is zero or less. + + + + + Gets the management function for the WebSocket services + provided by the server. + + + A that manages + the WebSocket services provided by the server. + + + + + Adds a WebSocket service with the specified behavior, + , and . + + + is converted to a URL-decoded string and + '/' is trimmed from the end of the converted string if any. + + + A that represents an absolute path to + the service to add. + + + + A Func<TBehavior> delegate. + + + It invokes the method called for creating a new session + instance for the service. + + + The method must create a new instance of the specified + behavior class and return it. + + + + + The type of the behavior for the service. + + + It must inherit the class. + + + + + is . + + + -or- + + + is . + + + + + is an empty string. + + + -or- + + + is not an absolute path. + + + -or- + + + includes either or both + query and fragment components. + + + -or- + + + is already in use. + + + + + + Adds a WebSocket service with the specified behavior and + . + + + is converted to a URL-decoded string and + '/' is trimmed from the end of the converted string if any. + + + A that represents an absolute path to + the service to add. + + + + The type of the behavior for the service. + + + It must inherit the class and + have a public parameterless constructor. + + + + is . + + + + is an empty string. + + + -or- + + + is not an absolute path. + + + -or- + + + includes either or both + query and fragment components. + + + -or- + + + is already in use. + + + + + + Adds a WebSocket service with the specified behavior, + , and . + + + is converted to a URL-decoded string and + '/' is trimmed from the end of the converted string if any. + + + A that represents an absolute path to + the service to add. + + + + An Action<TBehaviorWithNew> delegate or + if not needed. + + + That delegate invokes the method called for initializing + a new session instance for the service. + + + + + The type of the behavior for the service. + + + It must inherit the class and + have a public parameterless constructor. + + + + is . + + + + is an empty string. + + + -or- + + + is not an absolute path. + + + -or- + + + includes either or both + query and fragment components. + + + -or- + + + is already in use. + + + + + + Removes a WebSocket service with the specified . + + + + is converted to a URL-decoded string and + '/' is trimmed from the end of the converted string if any. + + + The service is stopped with close status 1001 (going away) + if it has already started. + + + + true if the service is successfully found and removed; + otherwise, false. + + + A that represents an absolute path to + the service to remove. + + + is . + + + + is an empty string. + + + -or- + + + is not an absolute path. + + + -or- + + + includes either or both + query and fragment components. + + + + + + Starts receiving incoming handshake requests. + + + This method does nothing if the server has already started or + it is shutting down. + + + + There is no server certificate for secure connections. + + + -or- + + + The underlying has failed to start. + + + + + + Stops receiving incoming handshake requests and closes + each connection. + + + This method does nothing if the server is not started, + it is shutting down, or it has already stopped. + + + The underlying has failed to stop. + + + + + Stops receiving incoming handshake requests and closes each + connection with the specified and + . + + + This method does nothing if the server is not started, + it is shutting down, or it has already stopped. + + + + A that represents the status code + indicating the reason for the close. + + + The status codes are defined in + + Section 7.4 of RFC 6455. + + + + + A that represents the reason for the close. + + + The size must be 123 bytes or less in UTF-8. + + + + + is less than 1000 or greater than 4999. + + + -or- + + + The size of is greater than 123 bytes. + + + + + is 1010 (mandatory extension). + + + -or- + + + is 1005 (no status) and + there is . + + + -or- + + + could not be UTF-8-encoded. + + + + The underlying has failed to stop. + + + + + Stops receiving incoming handshake requests and closes each + connection with the specified and + . + + + This method does nothing if the server is not started, + it is shutting down, or it has already stopped. + + + + One of the enum values. + + + It represents the status code indicating the reason for the close. + + + + + A that represents the reason for the close. + + + The size must be 123 bytes or less in UTF-8. + + + + + is + . + + + -or- + + + is + and + there is . + + + -or- + + + could not be UTF-8-encoded. + + + + The size of is greater than 123 bytes. + + + The underlying has failed to stop. + + + + + Exposes the methods and properties used to access the information in + a WebSocket service provided by the or + . + + + This class is an abstract class. + + + + + Initializes a new instance of the class + with the specified and . + + + A that represents the absolute path to the service. + + + A that represents the logging function for the service. + + + + + Gets the logging function for the service. + + + A that provides the logging function. + + + + + Gets or sets a value indicating whether the service cleans up + the inactive sessions periodically. + + + The set operation does nothing if the service has already started or + it is shutting down. + + + true if the service cleans up the inactive sessions every + 60 seconds; otherwise, false. + + + + + Gets the path to the service. + + + A that represents the absolute path to + the service. + + + + + Gets the management function for the sessions in the service. + + + A that manages the sessions in + the service. + + + + + Gets the of the behavior of the service. + + + A that represents the type of the behavior of + the service. + + + + + Gets or sets the time to wait for the response to the WebSocket Ping or + Close. + + + The set operation does nothing if the service has already started or + it is shutting down. + + + A to wait for the response. + + + The value specified for a set operation is zero or less. + + + + + Creates a new session for the service. + + + A instance that represents + the new session. + + + + + Provides the management function for the WebSocket services. + + + This class manages the WebSocket services provided by + the or . + + + + + Gets the number of the WebSocket services. + + + An that represents the number of the services. + + + + + Gets the host instances for the WebSocket services. + + + + An IEnumerable<WebSocketServiceHost> instance. + + + It provides an enumerator which supports the iteration over + the collection of the host instances. + + + + + + Gets the host instance for a WebSocket service with + the specified . + + + is converted to a URL-decoded string and + / is trimmed from the end of the converted string if any. + + + + A instance or + if not found. + + + That host instance provides the function to access + the information in the service. + + + + A that represents an absolute path to + the service to find. + + + is . + + + + is empty. + + + -or- + + + is not an absolute path. + + + -or- + + + includes either or both + query and fragment components. + + + + + + Gets or sets a value indicating whether the inactive sessions in + the WebSocket services are cleaned up periodically. + + + The set operation does nothing if the server has already started or + it is shutting down. + + + true if the inactive sessions are cleaned up every 60 seconds; + otherwise, false. + + + + + Gets the paths for the WebSocket services. + + + + An IEnumerable<string> instance. + + + It provides an enumerator which supports the iteration over + the collection of the paths. + + + + + + Gets the total number of the sessions in the WebSocket services. + + + An that represents the total number of + the sessions in the services. + + + + + Gets or sets the time to wait for the response to the WebSocket Ping or + Close. + + + The set operation does nothing if the server has already started or + it is shutting down. + + + A to wait for the response. + + + The value specified for a set operation is zero or less. + + + + + Adds a WebSocket service with the specified behavior, + , and . + + + is converted to a URL-decoded string and + / is trimmed from the end of the converted string if any. + + + A that represents an absolute path to + the service to add. + + + + An Action<TBehavior> delegate or + if not needed. + + + That delegate invokes the method called for initializing + a new session instance for the service. + + + + The type of the behavior for the service. It must inherit + the class and it must have + a public parameterless constructor. + + + is . + + + + is empty. + + + -or- + + + is not an absolute path. + + + -or- + + + includes either or both + query and fragment components. + + + -or- + + + is already in use. + + + + + + Sends to every client in the WebSocket services. + + + An array of that represents the binary data to send. + + + The current state of the manager is not Start. + + + is . + + + + + Sends to every client in the WebSocket services. + + + A that represents the text data to send. + + + The current state of the manager is not Start. + + + is . + + + could not be UTF-8-encoded. + + + + + Sends asynchronously to every client in + the WebSocket services. + + + This method does not wait for the send to be complete. + + + An array of that represents the binary data to send. + + + + An delegate or + if not needed. + + + The delegate invokes the method called when the send is complete. + + + + The current state of the manager is not Start. + + + is . + + + + + Sends asynchronously to every client in + the WebSocket services. + + + This method does not wait for the send to be complete. + + + A that represents the text data to send. + + + + An delegate or + if not needed. + + + The delegate invokes the method called when the send is complete. + + + + The current state of the manager is not Start. + + + is . + + + could not be UTF-8-encoded. + + + + + Sends the data from asynchronously to + every client in the WebSocket services. + + + + The data is sent as the binary data. + + + This method does not wait for the send to be complete. + + + + A instance from which to read the data to send. + + + An that specifies the number of bytes to send. + + + + An delegate or + if not needed. + + + The delegate invokes the method called when the send is complete. + + + + The current state of the manager is not Start. + + + is . + + + + cannot be read. + + + -or- + + + is less than 1. + + + -or- + + + No data could be read from . + + + + + + Sends a ping to every client in the WebSocket services. + + + + A Dictionary<string, Dictionary<string, bool>>. + + + It represents a collection of pairs of a service path and another + collection of pairs of a session ID and a value indicating whether + a pong has been received from the client within a time. + + + + The current state of the manager is not Start. + + + + + Sends a ping with to every client in + the WebSocket services. + + + + A Dictionary<string, Dictionary<string, bool>>. + + + It represents a collection of pairs of a service path and another + collection of pairs of a session ID and a value indicating whether + a pong has been received from the client within a time. + + + + + A that represents the message to send. + + + The size must be 125 bytes or less in UTF-8. + + + + The current state of the manager is not Start. + + + could not be UTF-8-encoded. + + + The size of is greater than 125 bytes. + + + + + Removes all WebSocket services managed by the manager. + + + A service is stopped with close status 1001 (going away) + if it has already started. + + + + + Removes a WebSocket service with the specified . + + + + is converted to a URL-decoded string and + / is trimmed from the end of the converted string if any. + + + The service is stopped with close status 1001 (going away) + if it has already started. + + + + true if the service is successfully found and removed; + otherwise, false. + + + A that represents an absolute path to + the service to remove. + + + is . + + + + is empty. + + + -or- + + + is not an absolute path. + + + -or- + + + includes either or both + query and fragment components. + + + + + + Tries to get the host instance for a WebSocket service with + the specified . + + + is converted to a URL-decoded string and + / is trimmed from the end of the converted string if any. + + + true if the service is successfully found; + otherwise, false. + + + A that represents an absolute path to + the service to find. + + + + When this method returns, a + instance or if not found. + + + That host instance provides the function to access + the information in the service. + + + + is . + + + + is empty. + + + -or- + + + is not an absolute path. + + + -or- + + + includes either or both + query and fragment components. + + + + + + Provides the management function for the sessions in a WebSocket service. + + + This class manages the sessions in a WebSocket service provided by + the or . + + + + + Gets the IDs for the active sessions in the WebSocket service. + + + + An IEnumerable<string> instance. + + + It provides an enumerator which supports the iteration over + the collection of the IDs for the active sessions. + + + + + + Gets the number of the sessions in the WebSocket service. + + + An that represents the number of the sessions. + + + + + Gets the IDs for the sessions in the WebSocket service. + + + + An IEnumerable<string> instance. + + + It provides an enumerator which supports the iteration over + the collection of the IDs for the sessions. + + + + + + Gets the IDs for the inactive sessions in the WebSocket service. + + + + An IEnumerable<string> instance. + + + It provides an enumerator which supports the iteration over + the collection of the IDs for the inactive sessions. + + + + + + Gets the session instance with . + + + + A instance or + if not found. + + + The session instance provides the function to access the information + in the session. + + + + A that represents the ID of the session to find. + + + is . + + + is an empty string. + + + + + Gets or sets a value indicating whether the inactive sessions in + the WebSocket service are cleaned up periodically. + + + The set operation does nothing if the service has already started or + it is shutting down. + + + true if the inactive sessions are cleaned up every 60 seconds; + otherwise, false. + + + + + Gets the session instances in the WebSocket service. + + + + An IEnumerable<IWebSocketSession> instance. + + + It provides an enumerator which supports the iteration over + the collection of the session instances. + + + + + + Gets or sets the time to wait for the response to the WebSocket Ping or + Close. + + + The set operation does nothing if the service has already started or + it is shutting down. + + + A to wait for the response. + + + The value specified for a set operation is zero or less. + + + + + Sends to every client in the WebSocket service. + + + An array of that represents the binary data to send. + + + The current state of the manager is not Start. + + + is . + + + + + Sends to every client in the WebSocket service. + + + A that represents the text data to send. + + + The current state of the manager is not Start. + + + is . + + + could not be UTF-8-encoded. + + + + + Sends the data from to every client in + the WebSocket service. + + + The data is sent as the binary data. + + + A instance from which to read the data to send. + + + An that specifies the number of bytes to send. + + + The current state of the manager is not Start. + + + is . + + + + cannot be read. + + + -or- + + + is less than 1. + + + -or- + + + No data could be read from . + + + + + + Sends asynchronously to every client in + the WebSocket service. + + + This method does not wait for the send to be complete. + + + An array of that represents the binary data to send. + + + + An delegate or + if not needed. + + + The delegate invokes the method called when the send is complete. + + + + The current state of the manager is not Start. + + + is . + + + + + Sends asynchronously to every client in + the WebSocket service. + + + This method does not wait for the send to be complete. + + + A that represents the text data to send. + + + + An delegate or + if not needed. + + + The delegate invokes the method called when the send is complete. + + + + The current state of the manager is not Start. + + + is . + + + could not be UTF-8-encoded. + + + + + Sends the data from asynchronously to + every client in the WebSocket service. + + + + The data is sent as the binary data. + + + This method does not wait for the send to be complete. + + + + A instance from which to read the data to send. + + + An that specifies the number of bytes to send. + + + + An delegate or + if not needed. + + + The delegate invokes the method called when the send is complete. + + + + The current state of the manager is not Start. + + + is . + + + + cannot be read. + + + -or- + + + is less than 1. + + + -or- + + + No data could be read from . + + + + + + Sends a ping to every client in the WebSocket service. + + + + A Dictionary<string, bool>. + + + It represents a collection of pairs of a session ID and + a value indicating whether a pong has been received from + the client within a time. + + + + The current state of the manager is not Start. + + + + + Sends a ping with to every client in + the WebSocket service. + + + + A Dictionary<string, bool>. + + + It represents a collection of pairs of a session ID and + a value indicating whether a pong has been received from + the client within a time. + + + + + A that represents the message to send. + + + The size must be 125 bytes or less in UTF-8. + + + + The current state of the manager is not Start. + + + could not be UTF-8-encoded. + + + The size of is greater than 125 bytes. + + + + + Closes the specified session. + + + A that represents the ID of the session to close. + + + is . + + + is an empty string. + + + The session could not be found. + + + + + Closes the specified session with and + . + + + A that represents the ID of the session to close. + + + + A that represents the status code indicating + the reason for the close. + + + The status codes are defined in + + Section 7.4 of RFC 6455. + + + + + A that represents the reason for the close. + + + The size must be 123 bytes or less in UTF-8. + + + + is . + + + + is an empty string. + + + -or- + + + is 1010 (mandatory extension). + + + -or- + + + is 1005 (no status) and there is + . + + + -or- + + + could not be UTF-8-encoded. + + + + The session could not be found. + + + + is less than 1000 or greater than 4999. + + + -or- + + + The size of is greater than 123 bytes. + + + + + + Closes the specified session with and + . + + + A that represents the ID of the session to close. + + + + One of the enum values. + + + It represents the status code indicating the reason for the close. + + + + + A that represents the reason for the close. + + + The size must be 123 bytes or less in UTF-8. + + + + is . + + + + is an empty string. + + + -or- + + + is + . + + + -or- + + + is + and there is + . + + + -or- + + + could not be UTF-8-encoded. + + + + The session could not be found. + + + The size of is greater than 123 bytes. + + + + + Sends a ping to the client using the specified session. + + + true if the send has done with no error and a pong has been + received from the client within a time; otherwise, false. + + + A that represents the ID of the session. + + + is . + + + is an empty string. + + + The session could not be found. + + + + + Sends a ping with to the client using + the specified session. + + + true if the send has done with no error and a pong has been + received from the client within a time; otherwise, false. + + + + A that represents the message to send. + + + The size must be 125 bytes or less in UTF-8. + + + + A that represents the ID of the session. + + + is . + + + + is an empty string. + + + -or- + + + could not be UTF-8-encoded. + + + + The session could not be found. + + + The size of is greater than 125 bytes. + + + + + Sends to the client using the specified session. + + + An array of that represents the binary data to send. + + + A that represents the ID of the session. + + + + is . + + + -or- + + + is . + + + + is an empty string. + + + + The session could not be found. + + + -or- + + + The current state of the WebSocket connection is not Open. + + + + + + Sends to the client using the specified session. + + + A that represents the text data to send. + + + A that represents the ID of the session. + + + + is . + + + -or- + + + is . + + + + + is an empty string. + + + -or- + + + could not be UTF-8-encoded. + + + + + The session could not be found. + + + -or- + + + The current state of the WebSocket connection is not Open. + + + + + + Sends the data from to the client using + the specified session. + + + The data is sent as the binary data. + + + A instance from which to read the data to send. + + + An that specifies the number of bytes to send. + + + A that represents the ID of the session. + + + + is . + + + -or- + + + is . + + + + + is an empty string. + + + -or- + + + cannot be read. + + + -or- + + + is less than 1. + + + -or- + + + No data could be read from . + + + + + The session could not be found. + + + -or- + + + The current state of the WebSocket connection is not Open. + + + + + + Sends asynchronously to the client using + the specified session. + + + This method does not wait for the send to be complete. + + + An array of that represents the binary data to send. + + + A that represents the ID of the session. + + + + An Action<bool> delegate or + if not needed. + + + The delegate invokes the method called when the send is complete. + + + true is passed to the method if the send has done with + no error; otherwise, false. + + + + + is . + + + -or- + + + is . + + + + is an empty string. + + + + The session could not be found. + + + -or- + + + The current state of the WebSocket connection is not Open. + + + + + + Sends asynchronously to the client using + the specified session. + + + This method does not wait for the send to be complete. + + + A that represents the text data to send. + + + A that represents the ID of the session. + + + + An Action<bool> delegate or + if not needed. + + + The delegate invokes the method called when the send is complete. + + + true is passed to the method if the send has done with + no error; otherwise, false. + + + + + is . + + + -or- + + + is . + + + + + is an empty string. + + + -or- + + + could not be UTF-8-encoded. + + + + + The session could not be found. + + + -or- + + + The current state of the WebSocket connection is not Open. + + + + + + Sends the data from asynchronously to + the client using the specified session. + + + + The data is sent as the binary data. + + + This method does not wait for the send to be complete. + + + + A instance from which to read the data to send. + + + An that specifies the number of bytes to send. + + + A that represents the ID of the session. + + + + An Action<bool> delegate or + if not needed. + + + The delegate invokes the method called when the send is complete. + + + true is passed to the method if the send has done with + no error; otherwise, false. + + + + + is . + + + -or- + + + is . + + + + + is an empty string. + + + -or- + + + cannot be read. + + + -or- + + + is less than 1. + + + -or- + + + No data could be read from . + + + + + The session could not be found. + + + -or- + + + The current state of the WebSocket connection is not Open. + + + + + + Cleans up the inactive sessions in the WebSocket service. + + + + + Tries to get the session instance with . + + + true if the session is successfully found; otherwise, + false. + + + A that represents the ID of the session to find. + + + + When this method returns, a + instance or if not found. + + + The session instance provides the function to access + the information in the session. + + + + is . + + + is an empty string. + + + + + Implements the WebSocket interface. + + + The WebSocket class provides a set of methods and properties for two-way communication using + the WebSocket protocol (RFC 6455). + + + + + Represents the empty array of used internally. + + + + + Represents the length used to determine whether the data should be fragmented in sending. + + + + The data will be fragmented if that length is greater than the value of this field. + + + If you would like to change the value, you must set it to a value between 125 and + Int32.MaxValue - 14 inclusive. + + + + + + Represents the random number generator used internally. + + + + + Initializes a new instance of the class with + and . + + + A that specifies the URL of the WebSocket + server to connect. + + + + An array of that specifies the names of + the subprotocols if necessary. + + + Each value of the array must be a token defined in + + RFC 2616. + + + + is . + + + + is an empty string. + + + -or- + + + is an invalid WebSocket URL string. + + + -or- + + + contains a value that is not a token. + + + -or- + + + contains a value twice. + + + + + + Gets or sets the compression method used to compress a message. + + + The set operation does nothing if the connection has already been + established or it is closing. + + + + One of the enum values. + + + It represents the compression method used to compress a message. + + + The default value is . + + + + The set operation cannot be used by servers. + + + + + Gets the HTTP cookies included in the WebSocket handshake request and response. + + + An + instance that provides an enumerator which supports the iteration over the collection of + the cookies. + + + + + Gets the credentials for the HTTP authentication (Basic/Digest). + + + A that represents the credentials for + the authentication. The default value is . + + + + + Gets or sets a value indicating whether the emits + a event when receives a ping. + + + true if the emits a event + when receives a ping; otherwise, false. The default value is false. + + + + + Gets or sets a value indicating whether the URL redirection for + the handshake request is allowed. + + + The set operation does nothing if the connection has already been + established or it is closing. + + + + true if the URL redirection for the handshake request is + allowed; otherwise, false. + + + The default value is false. + + + + The set operation cannot be used by servers. + + + + + Gets the WebSocket extensions selected by the server. + + + A that represents the extensions if any. + The default value is . + + + + + Gets a value indicating whether the WebSocket connection is alive. + + + true if the connection is alive; otherwise, false. + + + + + Gets a value indicating whether the WebSocket connection is secure. + + + true if the connection is secure; otherwise, false. + + + + + Gets the logging functions. + + + The default logging level is . If you would like to change it, + you should set this Log.Level property to any of the enum + values. + + + A that provides the logging functions. + + + + + Gets or sets the value of the HTTP Origin header to send with + the handshake request. + + + + The HTTP Origin header is defined in + + Section 7 of RFC 6454. + + + This instance sends the Origin header if this property has any. + + + The set operation does nothing if the connection has already been + established or it is closing. + + + + + A that represents the value of the Origin + header to send. + + + The syntax is <scheme>://<host>[:<port>]. + + + The default value is . + + + + The set operation is not available if this instance is not a client. + + + + The value specified for a set operation is not an absolute URI string. + + + -or- + + + The value specified for a set operation includes the path segments. + + + + + + Gets the WebSocket subprotocol selected by the server. + + + A that represents the subprotocol if any. + The default value is . + + + + + Gets the state of the WebSocket connection. + + + One of the enum values that indicates + the current state of the connection. The default value is + . + + + + + Gets the configuration for secure connection. + + + This configuration will be referenced when attempts to connect, + so it must be configured before any connect method is called. + + + A that represents + the configuration used to establish a secure connection. + + + + This instance is not a client. + + + This instance does not use a secure connection. + + + + + + Gets the WebSocket URL used to connect, or accepted. + + + A that represents the URL used to connect, or accepted. + + + + + Gets or sets the time to wait for the response to the ping or close. + + + The set operation does nothing if the connection has already been + established or it is closing. + + + + A to wait for the response. + + + The default value is the same as 5 seconds if the instance is + a client. + + + + The value specified for a set operation is zero or less. + + + + + Occurs when the WebSocket connection has been closed. + + + + + Occurs when the gets an error. + + + + + Occurs when the receives a message. + + + + + Occurs when the WebSocket connection has been established. + + + + + Accepts the WebSocket handshake request. + + + This method is not available in a client. + + + + + Accepts the WebSocket handshake request asynchronously. + + + + This method does not wait for the accept to be complete. + + + This method is not available in a client. + + + + + + Closes the connection. + + + This method does nothing if the current state of the connection is + Closing or Closed. + + + + + Closes the connection with the specified . + + + This method does nothing if the current state of the connection is + Closing or Closed. + + + + A that represents the status code + indicating the reason for the close. + + + The status codes are defined in + + Section 7.4 of RFC 6455. + + + + is less than 1000 or greater than 4999. + + + + is 1011 (server error). + It cannot be used by clients. + + + -or- + + + is 1010 (mandatory extension). + It cannot be used by servers. + + + + + + Closes the connection with the specified . + + + This method does nothing if the current state of the connection is + Closing or Closed. + + + + One of the enum values. + + + It represents the status code indicating the reason for the close. + + + + + is + . + It cannot be used by clients. + + + -or- + + + is + . + It cannot be used by servers. + + + + + + Closes the connection with the specified and + . + + + This method does nothing if the current state of the connection is + Closing or Closed. + + + + A that represents the status code + indicating the reason for the close. + + + The status codes are defined in + + Section 7.4 of RFC 6455. + + + + + A that represents the reason for the close. + + + The size must be 123 bytes or less in UTF-8. + + + + + is less than 1000 or greater than 4999. + + + -or- + + + The size of is greater than 123 bytes. + + + + + is 1011 (server error). + It cannot be used by clients. + + + -or- + + + is 1010 (mandatory extension). + It cannot be used by servers. + + + -or- + + + is 1005 (no status) and + there is . + + + -or- + + + could not be UTF-8-encoded. + + + + + + Closes the connection with the specified and + . + + + This method does nothing if the current state of the connection is + Closing or Closed. + + + + One of the enum values. + + + It represents the status code indicating the reason for the close. + + + + + A that represents the reason for the close. + + + The size must be 123 bytes or less in UTF-8. + + + + + is + . + It cannot be used by clients. + + + -or- + + + is + . + It cannot be used by servers. + + + -or- + + + is + and + there is . + + + -or- + + + could not be UTF-8-encoded. + + + + The size of is greater than 123 bytes. + + + + + Closes the connection asynchronously. + + + + This method does not wait for the close to be complete. + + + And this method does nothing if the current state of + the connection is Closing or Closed. + + + + + + Closes the connection asynchronously with the specified + . + + + + This method does not wait for the close to be complete. + + + And this method does nothing if the current state of + the connection is Closing or Closed. + + + + + A that represents the status code + indicating the reason for the close. + + + The status codes are defined in + + Section 7.4 of RFC 6455. + + + + is less than 1000 or greater than 4999. + + + + is 1011 (server error). + It cannot be used by clients. + + + -or- + + + is 1010 (mandatory extension). + It cannot be used by servers. + + + + + + Closes the connection asynchronously with the specified + . + + + + This method does not wait for the close to be complete. + + + And this method does nothing if the current state of + the connection is Closing or Closed. + + + + + One of the enum values. + + + It represents the status code indicating the reason for the close. + + + + + is + . + It cannot be used by clients. + + + -or- + + + is + . + It cannot be used by servers. + + + + + + Closes the connection asynchronously with the specified + and . + + + + This method does not wait for the close to be complete. + + + And this method does nothing if the current state of + the connection is Closing or Closed. + + + + + A that represents the status code + indicating the reason for the close. + + + The status codes are defined in + + Section 7.4 of RFC 6455. + + + + + A that represents the reason for the close. + + + The size must be 123 bytes or less in UTF-8. + + + + + is less than 1000 or greater than 4999. + + + -or- + + + The size of is greater than 123 bytes. + + + + + is 1011 (server error). + It cannot be used by clients. + + + -or- + + + is 1010 (mandatory extension). + It cannot be used by servers. + + + -or- + + + is 1005 (no status) and + there is . + + + -or- + + + could not be UTF-8-encoded. + + + + + + Closes the connection asynchronously with the specified + and . + + + + This method does not wait for the close to be complete. + + + And this method does nothing if the current state of + the connection is Closing or Closed. + + + + + One of the enum values. + + + It represents the status code indicating the reason for the close. + + + + + A that represents the reason for the close. + + + The size must be 123 bytes or less in UTF-8. + + + + + is + . + It cannot be used by clients. + + + -or- + + + is + . + It cannot be used by servers. + + + -or- + + + is + and + there is . + + + -or- + + + could not be UTF-8-encoded. + + + + The size of is greater than 123 bytes. + + + + + Establishes a WebSocket connection. + + + This method is not available in a server. + + + + + Establishes a WebSocket connection asynchronously. + + + + This method does not wait for the connect to be complete. + + + This method is not available in a server. + + + + + + Sends a ping using the WebSocket connection. + + + true if the send has done with no error and a pong has been + received within a time; otherwise, false. + + + + + Sends a ping with using the WebSocket + connection. + + + true if the send has done with no error and a pong has been + received within a time; otherwise, false. + + + + A that represents the message to send. + + + The size must be 125 bytes or less in UTF-8. + + + + could not be UTF-8-encoded. + + + The size of is greater than 125 bytes. + + + + + Sends using the WebSocket connection. + + + An array of that represents the binary data to send. + + + The current state of the connection is not Open. + + + is . + + + + + Sends the specified file using the WebSocket connection. + + + The file is sent as the binary data. + + + A that specifies the file to send. + + + The current state of the connection is not Open. + + + is . + + + + The file does not exist. + + + -or- + + + The file could not be opened. + + + + + + Sends using the WebSocket connection. + + + A that represents the text data to send. + + + The current state of the connection is not Open. + + + is . + + + could not be UTF-8-encoded. + + + + + Sends the data from using the WebSocket + connection. + + + The data is sent as the binary data. + + + A instance from which to read the data to send. + + + An that specifies the number of bytes to send. + + + The current state of the connection is not Open. + + + is . + + + + cannot be read. + + + -or- + + + is less than 1. + + + -or- + + + No data could be read from . + + + + + + Sends asynchronously using the WebSocket + connection. + + + This method does not wait for the send to be complete. + + + An array of that represents the binary data to send. + + + + An Action<bool> delegate or + if not needed. + + + The delegate invokes the method called when the send is complete. + + + true is passed to the method if the send has done with + no error; otherwise, false. + + + + The current state of the connection is not Open. + + + is . + + + + + Sends the specified file asynchronously using the WebSocket connection. + + + + The file is sent as the binary data. + + + This method does not wait for the send to be complete. + + + + A that specifies the file to send. + + + + An Action<bool> delegate or + if not needed. + + + The delegate invokes the method called when the send is complete. + + + true is passed to the method if the send has done with + no error; otherwise, false. + + + + The current state of the connection is not Open. + + + is . + + + + The file does not exist. + + + -or- + + + The file could not be opened. + + + + + + Sends asynchronously using the WebSocket + connection. + + + This method does not wait for the send to be complete. + + + A that represents the text data to send. + + + + An Action<bool> delegate or + if not needed. + + + The delegate invokes the method called when the send is complete. + + + true is passed to the method if the send has done with + no error; otherwise, false. + + + + The current state of the connection is not Open. + + + is . + + + could not be UTF-8-encoded. + + + + + Sends the data from asynchronously using + the WebSocket connection. + + + + The data is sent as the binary data. + + + This method does not wait for the send to be complete. + + + + A instance from which to read the data to send. + + + An that specifies the number of bytes to send. + + + + An Action<bool> delegate or + if not needed. + + + The delegate invokes the method called when the send is complete. + + + true is passed to the method if the send has done with + no error; otherwise, false. + + + + The current state of the connection is not Open. + + + is . + + + + cannot be read. + + + -or- + + + is less than 1. + + + -or- + + + No data could be read from . + + + + + + Sets an HTTP to send with + the WebSocket handshake request to the server. + + + This method is not available in a server. + + + A that represents a cookie to send. + + + + + Sets a pair of and for + the HTTP authentication (Basic/Digest). + + + This method is not available in a server. + + + + A that represents the user name used to authenticate. + + + If is or empty, + the credentials will be initialized and not be sent. + + + + A that represents the password for + used to authenticate. + + + true if the sends the credentials for + the Basic authentication with the first handshake request to the server; + otherwise, false. + + + + + Sets the HTTP proxy server URL to connect through, and if necessary, + a pair of and for + the proxy server authentication (Basic/Digest). + + + This method is not available in a server. + + + + A that represents the HTTP proxy server URL to + connect through. The syntax must be http://<host>[:<port>]. + + + If is or empty, + the url and credentials for the proxy will be initialized, + and the will not use the proxy to + connect through. + + + + + A that represents the user name used to authenticate. + + + If is or empty, + the credentials for the proxy will be initialized and not be sent. + + + + A that represents the password for + used to authenticate. + + + + + Closes the connection and releases all associated resources. + + + + This method closes the connection with close status 1001 (going away). + + + And this method does nothing if the current state of the connection is + Closing or Closed. + + + + + + The exception that is thrown when a fatal error occurs in + the WebSocket communication. + + + + + Gets the status code indicating the cause of the exception. + + + One of the enum values that represents + the status code indicating the cause of the exception. + + + + + Represents the ping frame without the payload data as an array of . + + + The value of this field is created from a non masked frame, so it can only be used to + send a ping from a server. + + + + + Indicates the state of a WebSocket connection. + + + The values of this enumeration are defined in + + The WebSocket API. + + + + + Equivalent to numeric value 0. Indicates that the connection has not + yet been established. + + + + + Equivalent to numeric value 1. Indicates that the connection has + been established, and the communication is possible. + + + + + Equivalent to numeric value 2. Indicates that the connection is + going through the closing handshake, or the close method has + been invoked. + + + + + Equivalent to numeric value 3. Indicates that the connection has + been closed or could not be established. + + + + diff --git a/document/clienttest.png b/document/clienttest.png new file mode 100644 index 0000000..9393bcb Binary files /dev/null and b/document/clienttest.png differ diff --git a/document/function.md b/document/function.md new file mode 100644 index 0000000..20dc5aa --- /dev/null +++ b/document/function.md @@ -0,0 +1,111 @@ +## 工作原理 ## +本项目仿照硬件设计思想,基于模块化原理开发,分为UDP连接池、Websocket连接池、交换面板三个大的模块。各个模块相互独立,除消息通讯外无模块间耦合。每个模块的功能部分和UI部分彼此亦基本独立。UDP和Websocket两个模块采用.net内置的Action对象与交换面板进行异步消息通讯。 + +**Endpoint是外部连接的唯一索引。** + +**Endpoint字符串的格式为:"XXX.XXX.XXX.XXX:XXXX" 前四组三位数为IP地址,最后一组四位数为远程端口号** + +UDP连接池是一个UDP连接管理模块,默认开放7101端口,供外部连接接入。每个成功连接的设备或模拟程序会被分配一个SocketConnection,SocketConnction通过Tag属性进行索引(Tag的值默认为Endpoint)并添加到一个列表里,该列表可以通过 GetConnectionList()获得,也可以使用GetTheConnection(Func predicate)函数通过LINQ语句进行指定连接的查询。 + + + +以下为UDP连接池对外暴露的函数接口 + + + /// 开始服务,监听客户端 + public void StartServer() + ///按照IP地址和端口发送字符串 + public void Send(string EndpointString, byte[] bytes) + ///开始异步监听端口 + public void Listening() + /// 关闭指定客户端连接 + public void CloseConnection(SocketConnection theConnection) + /// 添加客户端连接 + public void AddConnection(SocketConnection theConnection) + /// 通过条件获取客户端连接列表 + public IEnumerable GetConnectionList(Func predicate) + /// 获取所有客户端连接列表 + public IEnumerable GetConnectionList() + /// 寻找特定条件的客户端连接 + public SocketConnection GetTheConnection(Func predicate) + /// 获取客户端连接数 + public int GetConnectionCount() + +以下为UDP连接池对外暴露的事件接口 + + /// 服务启动后执行 + public Action HandleServerStarted { get; set; } + /// 当新客户端连接后执行 + public Action HandleNewClientConnected { get; set; } + /// 客户端连接接受新的消息后调用 + public Action HandleRecMsg { get; set; } + /// 客户端连接发送消息后回调 + public Action HandleSendMsg { get; set; } + /// 客户端连接关闭后回调 + public Action HandleClientClose { get; set; } + /// 异常处理程序 + public Action HandleException { get; set; } + +Websocket连接池是一个websocket管理模块,默认开放9000端口,供外部连接接入。每个成功联机的APP页面会分配一个唯一的session,Websocket对象维护了一个Sessions列表,该列表可以通过Where表达式,通过LINQ语句筛选。 + +以下为Websocket连接池对外暴露的函数接口 + + /// 发送字节 + public void Send(string EndpointString,byte[] bytes) + +以下为Websocket连接池对外暴露的事件接口 + + /// 当新客户端连接后执行 + public Action HandleNewWebSocketClientConnected { get; set; } + /// 客户端连接接受新的消息后调用 + public Action HandleWebSocketRecMsg { get; set; } + /// 客户端连接发送消息后回调 + public Action HandleWebSocketSendMsg { get; set; } + /// 客户端连接关闭后回调 + public Action HandleWebSocketClientClose { get; set; } + +交换面板(Switch)是一个消息转发器,他接收UDP与Websocket传入的数据包,并根据数据包中ID部分对信息进行分组转发,既收到信息后,向ID相同的全部在线端口转发收到的原始消息。MM是标识设备控制权限的,供上层应用使用,服务端不做处理。 + +对于每一个ID,Switch会生成一个结点(Node),该节点存储了含有相同ID的Websocket和UDP连接信息 1)TTL(剩余生存时间)2 Endpoint(客户连接端口),每个结点自带生命计时器,靠上层程序定期执行Reactive()函数保活。 + + private void Init_Forms() + { + try + { + ... + + //连接模块与系统终端窗口 + Device.Instance.HandleMessage += new Action(MessageForm.Log); + Device.Instance.HandleError += new Action(ErrorForm.Log); + + Browser.Instance.HandleMessage += new Action(MessageForm.Log); + Browser.Instance.HandleError += new Action(ErrorForm.Log); + //连接Pannel数据输入端 Device->Panel + Device.Instance.server.HandleRecMsg += new Action((bytes, conn, server) => + { + Panel.Instance.sw.SendFromUDP(bytes, conn.Tag.ToString()); + }); + //连接Pannel数据输入端 Browser->Panel + Browser.Instance.HandleWebSocketRecMsg += new Action((bytes, context) => + { + Panel.Instance.sw.SendFromWebSocket(bytes, context.UserEndPoint.ToString()); + }); + //连接Pannel数据输出端 Panel->Device + Panel.Instance.sw.HandleUDPSendMsg += new Action((bytes, endpointString) => + { + Device.Instance.server.Send(endpointString, bytes); + }); + //连接Panel数据输出端 Panel->Browser + Panel.Instance.sw.HandleWebSocketSendMsg += new Action((bytes, endpointString)=> + { + Browser.Instance.Send(endpointString, bytes); + }); + + ... + } + catch (Exception e) + { + MessageBox.Show(e.StackTrace,"系统模块初始化错误"); + + } + } diff --git a/document/packageintro.png b/document/packageintro.png new file mode 100644 index 0000000..93a1940 Binary files /dev/null and b/document/packageintro.png differ diff --git a/document/preview.png b/document/preview.png new file mode 100644 index 0000000..5f78a24 Binary files /dev/null and b/document/preview.png differ diff --git a/document/webtest.png b/document/webtest.png new file mode 100644 index 0000000..a28d21d Binary files /dev/null and b/document/webtest.png differ diff --git a/websocket-sharp/AssemblyInfo.cs b/websocket-sharp/AssemblyInfo.cs new file mode 100644 index 0000000..c85deaa --- /dev/null +++ b/websocket-sharp/AssemblyInfo.cs @@ -0,0 +1,26 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +// Information about this assembly is defined by the following attributes. +// Change them to the values specific to your project. + +[assembly: AssemblyTitle("websocket-sharp")] +[assembly: AssemblyDescription("A C# implementation of the WebSocket protocol client and server")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("websocket-sharp.dll")] +[assembly: AssemblyCopyright("sta.blockhead")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". +// The form "{Major}.{Minor}.*" will automatically update the build and revision, +// and "{Major}.{Minor}.{Build}.*" will update just the revision. + +[assembly: AssemblyVersion("1.0.2.*")] + +// The following attributes are used to specify the signing key for the assembly, +// if desired. See the Mono documentation for more information about signing. + +//[assembly: AssemblyDelaySign(false)] +//[assembly: AssemblyKeyFile("")] diff --git a/websocket-sharp/ByteOrder.cs b/websocket-sharp/ByteOrder.cs new file mode 100644 index 0000000..317f462 --- /dev/null +++ b/websocket-sharp/ByteOrder.cs @@ -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 +{ + /// + /// Specifies the byte order. + /// + public enum ByteOrder + { + /// + /// Specifies Little-endian. + /// + Little, + /// + /// Specifies Big-endian. + /// + Big + } +} diff --git a/websocket-sharp/CloseEventArgs.cs b/websocket-sharp/CloseEventArgs.cs new file mode 100644 index 0000000..c665ccd --- /dev/null +++ b/websocket-sharp/CloseEventArgs.cs @@ -0,0 +1,142 @@ +#region License +/* + * CloseEventArgs.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 +{ + /// + /// Represents the event data for the event. + /// + /// + /// + /// That event occurs when the WebSocket connection has been closed. + /// + /// + /// If you would like to get the reason for the close, you should access + /// the or property. + /// + /// + public class CloseEventArgs : EventArgs + { + #region Private Fields + + private bool _clean; + private PayloadData _payloadData; + + #endregion + + #region Internal Constructors + + internal CloseEventArgs () + { + _payloadData = PayloadData.Empty; + } + + internal CloseEventArgs (ushort code) + : this (code, null) + { + } + + internal CloseEventArgs (CloseStatusCode code) + : this ((ushort) code, null) + { + } + + internal CloseEventArgs (PayloadData payloadData) + { + _payloadData = payloadData; + } + + internal CloseEventArgs (ushort code, string reason) + { + _payloadData = new PayloadData (code, reason); + } + + internal CloseEventArgs (CloseStatusCode code, string reason) + : this ((ushort) code, reason) + { + } + + #endregion + + #region Internal Properties + + internal PayloadData PayloadData { + get { + return _payloadData; + } + } + + #endregion + + #region Public Properties + + /// + /// Gets the status code for the close. + /// + /// + /// A that represents the status code for the close if any. + /// + public ushort Code { + get { + return _payloadData.Code; + } + } + + /// + /// Gets the reason for the close. + /// + /// + /// A that represents the reason for the close if any. + /// + public string Reason { + get { + return _payloadData.Reason ?? String.Empty; + } + } + + /// + /// Gets a value indicating whether the connection has been closed cleanly. + /// + /// + /// true if the connection has been closed cleanly; otherwise, false. + /// + public bool WasClean { + get { + return _clean; + } + + internal set { + _clean = value; + } + } + + #endregion + } +} diff --git a/websocket-sharp/CloseStatusCode.cs b/websocket-sharp/CloseStatusCode.cs new file mode 100644 index 0000000..81f3317 --- /dev/null +++ b/websocket-sharp/CloseStatusCode.cs @@ -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 +{ + /// + /// Indicates the status code for the WebSocket connection close. + /// + /// + /// + /// The values of this enumeration are defined in + /// + /// Section 7.4 of RFC 6455. + /// + /// + /// "Reserved value" cannot be sent as a status code in + /// closing handshake by an endpoint. + /// + /// + public enum CloseStatusCode : ushort + { + /// + /// Equivalent to close status 1000. Indicates normal close. + /// + Normal = 1000, + /// + /// Equivalent to close status 1001. Indicates that an endpoint is + /// going away. + /// + Away = 1001, + /// + /// Equivalent to close status 1002. Indicates that an endpoint is + /// terminating the connection due to a protocol error. + /// + ProtocolError = 1002, + /// + /// 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. + /// + UnsupportedData = 1003, + /// + /// Equivalent to close status 1004. Still undefined. A Reserved value. + /// + Undefined = 1004, + /// + /// Equivalent to close status 1005. Indicates that no status code was + /// actually present. A Reserved value. + /// + NoStatus = 1005, + /// + /// Equivalent to close status 1006. Indicates that the connection was + /// closed abnormally. A Reserved value. + /// + Abnormal = 1006, + /// + /// 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. + /// + InvalidData = 1007, + /// + /// Equivalent to close status 1008. Indicates that an endpoint is + /// terminating the connection because it has received a message that + /// violates its policy. + /// + PolicyViolation = 1008, + /// + /// 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. + /// + TooBig = 1009, + /// + /// 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. + /// + MandatoryExtension = 1010, + /// + /// 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. + /// + ServerError = 1011, + /// + /// Equivalent to close status 1015. Indicates that the connection was + /// closed due to a failure to perform a TLS handshake. A Reserved value. + /// + TlsHandshakeFailure = 1015 + } +} diff --git a/websocket-sharp/CompressionMethod.cs b/websocket-sharp/CompressionMethod.cs new file mode 100644 index 0000000..42ab230 --- /dev/null +++ b/websocket-sharp/CompressionMethod.cs @@ -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 +{ + /// + /// Specifies the method for compression. + /// + /// + /// The methods are defined in + /// + /// Compression Extensions for WebSocket. + /// + public enum CompressionMethod : byte + { + /// + /// Specifies no compression. + /// + None, + /// + /// Specifies DEFLATE. + /// + Deflate + } +} diff --git a/websocket-sharp/ErrorEventArgs.cs b/websocket-sharp/ErrorEventArgs.cs new file mode 100644 index 0000000..41502ab --- /dev/null +++ b/websocket-sharp/ErrorEventArgs.cs @@ -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 + */ +#endregion + +using System; + +namespace WebSocketSharp +{ + /// + /// Represents the event data for the event. + /// + /// + /// + /// That event occurs when the gets an error. + /// + /// + /// If you would like to get the error message, you should access + /// the property. + /// + /// + /// And if the error is due to an exception, you can get it by accessing + /// the property. + /// + /// + 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 + + /// + /// Gets the exception that caused the error. + /// + /// + /// An instance that represents the cause of + /// the error if it is due to an exception; otherwise, . + /// + public Exception Exception { + get { + return _exception; + } + } + + /// + /// Gets the error message. + /// + /// + /// A that represents the error message. + /// + public string Message { + get { + return _message; + } + } + + #endregion + } +} diff --git a/websocket-sharp/Ext.cs b/websocket-sharp/Ext.cs new file mode 100644 index 0000000..da31527 --- /dev/null +++ b/websocket-sharp/Ext.cs @@ -0,0 +1,2032 @@ +#region License +/* + * Ext.cs + * + * Some parts of this code are derived from Mono (http://www.mono-project.com): + * - GetStatusDescription is derived from HttpListenerResponse.cs (System.Net) + * - IsPredefinedScheme is derived from Uri.cs (System) + * - MaybeUri is derived from Uri.cs (System) + * + * The MIT License + * + * Copyright (c) 2001 Garrett Rooney + * Copyright (c) 2003 Ian MacLean + * Copyright (c) 2003 Ben Maurer + * Copyright (c) 2003, 2005, 2009 Novell, Inc. (http://www.novell.com) + * Copyright (c) 2009 Stephane Delcroix + * Copyright (c) 2010-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: + * - Liryna + * - Nikola Kovacevic + * - Chris Swiedler + */ +#endregion + +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.IO; +using System.IO.Compression; +using System.Net.Sockets; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using WebSocketSharp.Net; +using WebSocketSharp.Net.WebSockets; +using WebSocketSharp.Server; + +namespace WebSocketSharp +{ + /// + /// Provides a set of static methods for websocket-sharp. + /// + public static class Ext + { + #region Private Fields + + private static readonly byte[] _last = new byte[] { 0x00 }; + private static readonly int _retry = 5; + private const string _tspecials = "()<>@,;:\\\"/[]?={} \t"; + + #endregion + + #region Private Methods + + private static byte[] compress (this byte[] data) + { + if (data.LongLength == 0) + //return new byte[] { 0x00, 0x00, 0x00, 0xff, 0xff }; + return data; + + using (var input = new MemoryStream (data)) + return input.compressToArray (); + } + + private static MemoryStream compress (this Stream stream) + { + var output = new MemoryStream (); + if (stream.Length == 0) + return output; + + stream.Position = 0; + using (var ds = new DeflateStream (output, CompressionMode.Compress, true)) { + stream.CopyTo (ds, 1024); + ds.Close (); // BFINAL set to 1. + output.Write (_last, 0, 1); + output.Position = 0; + + return output; + } + } + + private static byte[] compressToArray (this Stream stream) + { + using (var output = stream.compress ()) { + output.Close (); + return output.ToArray (); + } + } + + private static byte[] decompress (this byte[] data) + { + if (data.LongLength == 0) + return data; + + using (var input = new MemoryStream (data)) + return input.decompressToArray (); + } + + private static MemoryStream decompress (this Stream stream) + { + var output = new MemoryStream (); + if (stream.Length == 0) + return output; + + stream.Position = 0; + using (var ds = new DeflateStream (stream, CompressionMode.Decompress, true)) { + ds.CopyTo (output, 1024); + output.Position = 0; + + return output; + } + } + + private static byte[] decompressToArray (this Stream stream) + { + using (var output = stream.decompress ()) { + output.Close (); + return output.ToArray (); + } + } + + private static bool isHttpMethod (this string value) + { + return value == "GET" + || value == "HEAD" + || value == "POST" + || value == "PUT" + || value == "DELETE" + || value == "CONNECT" + || value == "OPTIONS" + || value == "TRACE"; + } + + private static bool isHttpMethod10 (this string value) + { + return value == "GET" + || value == "HEAD" + || value == "POST"; + } + + private static void times (this ulong n, Action action) + { + for (ulong i = 0; i < n; i++) + action (); + } + + #endregion + + #region Internal Methods + + internal static byte[] Append (this ushort code, string reason) + { + var ret = code.InternalToByteArray (ByteOrder.Big); + if (reason != null && reason.Length > 0) { + var buff = new List (ret); + buff.AddRange (Encoding.UTF8.GetBytes (reason)); + ret = buff.ToArray (); + } + + return ret; + } + + internal static void Close (this HttpListenerResponse response, HttpStatusCode code) + { + response.StatusCode = (int) code; + response.OutputStream.Close (); + } + + internal static void CloseWithAuthChallenge ( + this HttpListenerResponse response, string challenge) + { + response.Headers.InternalSet ("WWW-Authenticate", challenge, true); + response.Close (HttpStatusCode.Unauthorized); + } + + internal static byte[] Compress (this byte[] data, CompressionMethod method) + { + return method == CompressionMethod.Deflate + ? data.compress () + : data; + } + + internal static Stream Compress (this Stream stream, CompressionMethod method) + { + return method == CompressionMethod.Deflate + ? stream.compress () + : stream; + } + + internal static byte[] CompressToArray (this Stream stream, CompressionMethod method) + { + return method == CompressionMethod.Deflate + ? stream.compressToArray () + : stream.ToByteArray (); + } + + /// + /// Determines whether the specified string contains any of characters in + /// the specified array of . + /// + /// + /// true if contains any of characters in + /// ; otherwise, false. + /// + /// + /// A to test. + /// + /// + /// An array of that contains one or more characters to + /// seek. + /// + internal static bool Contains (this string value, params char[] anyOf) + { + return anyOf != null && anyOf.Length > 0 + ? value.IndexOfAny (anyOf) > -1 + : false; + } + + internal static bool Contains ( + this NameValueCollection collection, string name + ) + { + return collection[name] != null; + } + + internal static bool Contains ( + this NameValueCollection collection, + string name, + string value, + StringComparison comparisonTypeForValue + ) + { + var val = collection[name]; + if (val == null) + return false; + + foreach (var elm in val.Split (',')) { + if (elm.Trim ().Equals (value, comparisonTypeForValue)) + return true; + } + + return false; + } + + internal static bool Contains ( + this IEnumerable source, Func condition + ) + { + foreach (T elm in source) { + if (condition (elm)) + return true; + } + + return false; + } + + internal static bool ContainsTwice (this string[] values) + { + var len = values.Length; + var end = len - 1; + + Func seek = null; + seek = idx => { + if (idx == end) + return false; + + var val = values[idx]; + for (var i = idx + 1; i < len; i++) { + if (values[i] == val) + return true; + } + + return seek (++idx); + }; + + return seek (0); + } + + internal static T[] Copy (this T[] source, int length) + { + var dest = new T[length]; + Array.Copy (source, 0, dest, 0, length); + + return dest; + } + + internal static T[] Copy (this T[] source, long length) + { + var dest = new T[length]; + Array.Copy (source, 0, dest, 0, length); + + return dest; + } + + internal static void CopyTo (this Stream source, Stream destination, int bufferLength) + { + var buff = new byte[bufferLength]; + var nread = 0; + while ((nread = source.Read (buff, 0, bufferLength)) > 0) + destination.Write (buff, 0, nread); + } + + internal static void CopyToAsync ( + this Stream source, + Stream destination, + int bufferLength, + Action completed, + Action error) + { + var buff = new byte[bufferLength]; + + AsyncCallback callback = null; + callback = ar => { + try { + var nread = source.EndRead (ar); + if (nread <= 0) { + if (completed != null) + completed (); + + return; + } + + destination.Write (buff, 0, nread); + source.BeginRead (buff, 0, bufferLength, callback, null); + } + catch (Exception ex) { + if (error != null) + error (ex); + } + }; + + try { + source.BeginRead (buff, 0, bufferLength, callback, null); + } + catch (Exception ex) { + if (error != null) + error (ex); + } + } + + internal static byte[] Decompress (this byte[] data, CompressionMethod method) + { + return method == CompressionMethod.Deflate + ? data.decompress () + : data; + } + + internal static Stream Decompress (this Stream stream, CompressionMethod method) + { + return method == CompressionMethod.Deflate + ? stream.decompress () + : stream; + } + + internal static byte[] DecompressToArray (this Stream stream, CompressionMethod method) + { + return method == CompressionMethod.Deflate + ? stream.decompressToArray () + : stream.ToByteArray (); + } + + /// + /// Determines whether the specified equals the specified , + /// and invokes the specified Action<int> delegate at the same time. + /// + /// + /// true if equals ; + /// otherwise, false. + /// + /// + /// An to compare. + /// + /// + /// A to compare. + /// + /// + /// An Action<int> delegate that references the method(s) called + /// at the same time as comparing. An parameter to pass to + /// the method(s) is . + /// + internal static bool EqualsWith (this int value, char c, Action action) + { + action (value); + return value == c - 0; + } + + /// + /// Gets the absolute path from the specified . + /// + /// + /// A that represents the absolute path if it's successfully found; + /// otherwise, . + /// + /// + /// A that represents the URI to get the absolute path from. + /// + internal static string GetAbsolutePath (this Uri uri) + { + if (uri.IsAbsoluteUri) + return uri.AbsolutePath; + + var original = uri.OriginalString; + if (original[0] != '/') + return null; + + var idx = original.IndexOfAny (new[] { '?', '#' }); + return idx > 0 ? original.Substring (0, idx) : original; + } + + internal static CookieCollection GetCookies ( + this NameValueCollection headers, bool response + ) + { + var val = headers[response ? "Set-Cookie" : "Cookie"]; + return val != null + ? CookieCollection.Parse (val, response) + : new CookieCollection (); + } + + internal static string GetDnsSafeHost (this Uri uri, bool bracketIPv6) + { + return bracketIPv6 && uri.HostNameType == UriHostNameType.IPv6 + ? uri.Host + : uri.DnsSafeHost; + } + + internal static string GetMessage (this CloseStatusCode code) + { + return code == CloseStatusCode.ProtocolError + ? "A WebSocket protocol error has occurred." + : code == CloseStatusCode.UnsupportedData + ? "Unsupported data has been received." + : code == CloseStatusCode.Abnormal + ? "An exception has occurred." + : code == CloseStatusCode.InvalidData + ? "Invalid data has been received." + : code == CloseStatusCode.PolicyViolation + ? "A policy violation has occurred." + : code == CloseStatusCode.TooBig + ? "A too big message has been received." + : code == CloseStatusCode.MandatoryExtension + ? "WebSocket client didn't receive expected extension(s)." + : code == CloseStatusCode.ServerError + ? "WebSocket server got an internal error." + : code == CloseStatusCode.TlsHandshakeFailure + ? "An error has occurred during a TLS handshake." + : String.Empty; + } + + /// + /// Gets the name from the specified string that contains a pair of + /// name and value separated by a character. + /// + /// + /// + /// A that represents the name. + /// + /// + /// if the name is not present. + /// + /// + /// + /// A that contains a pair of name and value. + /// + /// + /// A used to separate name and value. + /// + internal static string GetName (this string nameAndValue, char separator) + { + var idx = nameAndValue.IndexOf (separator); + return idx > 0 ? nameAndValue.Substring (0, idx).Trim () : null; + } + + /// + /// Gets the value from the specified string that contains a pair of + /// name and value separated by a character. + /// + /// + /// + /// A that represents the value. + /// + /// + /// if the value is not present. + /// + /// + /// + /// A that contains a pair of name and value. + /// + /// + /// A used to separate name and value. + /// + internal static string GetValue (this string nameAndValue, char separator) + { + return nameAndValue.GetValue (separator, false); + } + + /// + /// Gets the value from the specified string that contains a pair of + /// name and value separated by a character. + /// + /// + /// + /// A that represents the value. + /// + /// + /// if the value is not present. + /// + /// + /// + /// A that contains a pair of name and value. + /// + /// + /// A used to separate name and value. + /// + /// + /// A : true if unquotes the value; otherwise, + /// false. + /// + internal static string GetValue ( + this string nameAndValue, char separator, bool unquote + ) + { + var idx = nameAndValue.IndexOf (separator); + if (idx < 0 || idx == nameAndValue.Length - 1) + return null; + + var val = nameAndValue.Substring (idx + 1).Trim (); + return unquote ? val.Unquote () : val; + } + + internal static byte[] InternalToByteArray (this ushort value, ByteOrder order) + { + var bytes = BitConverter.GetBytes (value); + if (!order.IsHostOrder ()) + Array.Reverse (bytes); + + return bytes; + } + + internal static byte[] InternalToByteArray (this ulong value, ByteOrder order) + { + var bytes = BitConverter.GetBytes (value); + if (!order.IsHostOrder ()) + Array.Reverse (bytes); + + return bytes; + } + + internal static bool IsCompressionExtension ( + this string value, CompressionMethod method + ) + { + return value.StartsWith (method.ToExtensionString ()); + } + + internal static bool IsControl (this byte opcode) + { + return opcode > 0x7 && opcode < 0x10; + } + + internal static bool IsControl (this Opcode opcode) + { + return opcode >= Opcode.Close; + } + + internal static bool IsData (this byte opcode) + { + return opcode == 0x1 || opcode == 0x2; + } + + internal static bool IsData (this Opcode opcode) + { + return opcode == Opcode.Text || opcode == Opcode.Binary; + } + + internal static bool IsHttpMethod (this string value, Version version) + { + return version == HttpVersion.Version10 + ? value.isHttpMethod10 () + : value.isHttpMethod (); + } + + internal static bool IsPortNumber (this int value) + { + return value > 0 && value < 65536; + } + + internal static bool IsReserved (this ushort code) + { + return code == 1004 + || code == 1005 + || code == 1006 + || code == 1015; + } + + internal static bool IsReserved (this CloseStatusCode code) + { + return code == CloseStatusCode.Undefined + || code == CloseStatusCode.NoStatus + || code == CloseStatusCode.Abnormal + || code == CloseStatusCode.TlsHandshakeFailure; + } + + internal static bool IsSupported (this byte opcode) + { + return Enum.IsDefined (typeof (Opcode), opcode); + } + + internal static bool IsText (this string value) + { + var len = value.Length; + + for (var i = 0; i < len; i++) { + var c = value[i]; + if (c < 0x20) { + if ("\r\n\t".IndexOf (c) == -1) + return false; + + if (c == '\n') { + i++; + if (i == len) + break; + + c = value[i]; + if (" \t".IndexOf (c) == -1) + return false; + } + + continue; + } + + if (c == 0x7f) + return false; + } + + return true; + } + + internal static bool IsToken (this string value) + { + foreach (var c in value) { + if (c < 0x20) + return false; + + if (c >= 0x7f) + return false; + + if (_tspecials.IndexOf (c) > -1) + return false; + } + + return true; + } + + internal static bool KeepsAlive ( + this NameValueCollection headers, Version version + ) + { + var comparison = StringComparison.OrdinalIgnoreCase; + return version < HttpVersion.Version11 + ? headers.Contains ("Connection", "keep-alive", comparison) + : !headers.Contains ("Connection", "close", comparison); + } + + internal static string Quote (this string value) + { + return String.Format ("\"{0}\"", value.Replace ("\"", "\\\"")); + } + + internal static byte[] ReadBytes (this Stream stream, int length) + { + var buff = new byte[length]; + var offset = 0; + try { + var nread = 0; + while (length > 0) { + nread = stream.Read (buff, offset, length); + if (nread == 0) + break; + + offset += nread; + length -= nread; + } + } + catch { + } + + return buff.SubArray (0, offset); + } + + internal static byte[] ReadBytes (this Stream stream, long length, int bufferLength) + { + using (var dest = new MemoryStream ()) { + try { + var buff = new byte[bufferLength]; + var nread = 0; + while (length > 0) { + if (length < bufferLength) + bufferLength = (int) length; + + nread = stream.Read (buff, 0, bufferLength); + if (nread == 0) + break; + + dest.Write (buff, 0, nread); + length -= nread; + } + } + catch { + } + + dest.Close (); + return dest.ToArray (); + } + } + + internal static void ReadBytesAsync ( + this Stream stream, int length, Action completed, Action error + ) + { + var buff = new byte[length]; + var offset = 0; + var retry = 0; + + AsyncCallback callback = null; + callback = + ar => { + try { + var nread = stream.EndRead (ar); + if (nread == 0 && retry < _retry) { + retry++; + stream.BeginRead (buff, offset, length, callback, null); + + return; + } + + if (nread == 0 || nread == length) { + if (completed != null) + completed (buff.SubArray (0, offset + nread)); + + return; + } + + retry = 0; + + offset += nread; + length -= nread; + + stream.BeginRead (buff, offset, length, callback, null); + } + catch (Exception ex) { + if (error != null) + error (ex); + } + }; + + try { + stream.BeginRead (buff, offset, length, callback, null); + } + catch (Exception ex) { + if (error != null) + error (ex); + } + } + + internal static void ReadBytesAsync ( + this Stream stream, + long length, + int bufferLength, + Action completed, + Action error + ) + { + var dest = new MemoryStream (); + var buff = new byte[bufferLength]; + var retry = 0; + + Action read = null; + read = + len => { + if (len < bufferLength) + bufferLength = (int) len; + + stream.BeginRead ( + buff, + 0, + bufferLength, + ar => { + try { + var nread = stream.EndRead (ar); + if (nread > 0) + dest.Write (buff, 0, nread); + + if (nread == 0 && retry < _retry) { + retry++; + read (len); + + return; + } + + if (nread == 0 || nread == len) { + if (completed != null) { + dest.Close (); + completed (dest.ToArray ()); + } + + dest.Dispose (); + return; + } + + retry = 0; + read (len - nread); + } + catch (Exception ex) { + dest.Dispose (); + if (error != null) + error (ex); + } + }, + null + ); + }; + + try { + read (length); + } + catch (Exception ex) { + dest.Dispose (); + if (error != null) + error (ex); + } + } + + internal static T[] Reverse (this T[] array) + { + var len = array.Length; + var ret = new T[len]; + + var end = len - 1; + for (var i = 0; i <= end; i++) + ret[i] = array[end - i]; + + return ret; + } + + internal static IEnumerable SplitHeaderValue ( + this string value, params char[] separators + ) + { + var len = value.Length; + + var buff = new StringBuilder (32); + var end = len - 1; + var escaped = false; + var quoted = false; + + for (var i = 0; i <= end; i++) { + var c = value[i]; + buff.Append (c); + + if (c == '"') { + if (escaped) { + escaped = false; + continue; + } + + quoted = !quoted; + continue; + } + + if (c == '\\') { + if (i == end) + break; + + if (value[i + 1] == '"') + escaped = true; + + continue; + } + + if (Array.IndexOf (separators, c) > -1) { + if (quoted) + continue; + + buff.Length -= 1; + yield return buff.ToString (); + + buff.Length = 0; + continue; + } + } + + yield return buff.ToString (); + } + + internal static byte[] ToByteArray (this Stream stream) + { + using (var output = new MemoryStream ()) { + stream.Position = 0; + stream.CopyTo (output, 1024); + output.Close (); + + return output.ToArray (); + } + } + + internal static CompressionMethod ToCompressionMethod (this string value) + { + var methods = Enum.GetValues (typeof (CompressionMethod)); + foreach (CompressionMethod method in methods) { + if (method.ToExtensionString () == value) + return method; + } + + return CompressionMethod.None; + } + + internal static string ToExtensionString ( + this CompressionMethod method, params string[] parameters + ) + { + if (method == CompressionMethod.None) + return String.Empty; + + var name = String.Format ( + "permessage-{0}", method.ToString ().ToLower () + ); + + return parameters != null && parameters.Length > 0 + ? String.Format ("{0}; {1}", name, parameters.ToString ("; ")) + : name; + } + + internal static System.Net.IPAddress ToIPAddress (this string value) + { + if (value == null || value.Length == 0) + return null; + + System.Net.IPAddress addr; + if (System.Net.IPAddress.TryParse (value, out addr)) + return addr; + + try { + var addrs = System.Net.Dns.GetHostAddresses (value); + return addrs[0]; + } + catch { + return null; + } + } + + internal static List ToList ( + this IEnumerable source + ) + { + return new List (source); + } + + internal static string ToString ( + this System.Net.IPAddress address, bool bracketIPv6 + ) + { + return bracketIPv6 + && address.AddressFamily == AddressFamily.InterNetworkV6 + ? String.Format ("[{0}]", address.ToString ()) + : address.ToString (); + } + + internal static ushort ToUInt16 (this byte[] source, ByteOrder sourceOrder) + { + return BitConverter.ToUInt16 (source.ToHostOrder (sourceOrder), 0); + } + + internal static ulong ToUInt64 (this byte[] source, ByteOrder sourceOrder) + { + return BitConverter.ToUInt64 (source.ToHostOrder (sourceOrder), 0); + } + + internal static IEnumerable Trim (this IEnumerable source) + { + foreach (var elm in source) + yield return elm.Trim (); + } + + internal static string TrimSlashFromEnd (this string value) + { + var ret = value.TrimEnd ('/'); + return ret.Length > 0 ? ret : "/"; + } + + internal static string TrimSlashOrBackslashFromEnd (this string value) + { + var ret = value.TrimEnd ('/', '\\'); + return ret.Length > 0 ? ret : value[0].ToString (); + } + + internal static bool TryCreateVersion ( + this string versionString, out Version result + ) + { + result = null; + + try { + result = new Version (versionString); + } + catch { + return false; + } + + return true; + } + + /// + /// Tries to create a new for WebSocket with + /// the specified . + /// + /// + /// true if the was successfully created; + /// otherwise, false. + /// + /// + /// A that represents a WebSocket URL to try. + /// + /// + /// When this method returns, a that + /// represents the WebSocket URL or + /// if is invalid. + /// + /// + /// When this method returns, a that + /// represents an error message or + /// if is valid. + /// + internal static bool TryCreateWebSocketUri ( + this string uriString, out Uri result, out string message + ) + { + result = null; + message = null; + + var uri = uriString.ToUri (); + if (uri == null) { + message = "An invalid URI string."; + return false; + } + + if (!uri.IsAbsoluteUri) { + message = "A relative URI."; + return false; + } + + var schm = uri.Scheme; + if (!(schm == "ws" || schm == "wss")) { + message = "The scheme part is not 'ws' or 'wss'."; + return false; + } + + var port = uri.Port; + if (port == 0) { + message = "The port part is zero."; + return false; + } + + if (uri.Fragment.Length > 0) { + message = "It includes the fragment component."; + return false; + } + + result = port != -1 + ? uri + : new Uri ( + String.Format ( + "{0}://{1}:{2}{3}", + schm, + uri.Host, + schm == "ws" ? 80 : 443, + uri.PathAndQuery + ) + ); + + return true; + } + + internal static bool TryGetUTF8DecodedString (this byte[] bytes, out string s) + { + s = null; + + try { + s = Encoding.UTF8.GetString (bytes); + } + catch { + return false; + } + + return true; + } + + internal static bool TryGetUTF8EncodedBytes (this string s, out byte[] bytes) + { + bytes = null; + + try { + bytes = Encoding.UTF8.GetBytes (s); + } + catch { + return false; + } + + return true; + } + + internal static bool TryOpenRead ( + this FileInfo fileInfo, out FileStream fileStream + ) + { + fileStream = null; + + try { + fileStream = fileInfo.OpenRead (); + } + catch { + return false; + } + + return true; + } + + internal static string Unquote (this string value) + { + var start = value.IndexOf ('"'); + if (start == -1) + return value; + + var end = value.LastIndexOf ('"'); + if (end == start) + return value; + + var len = end - start - 1; + return len > 0 + ? value.Substring (start + 1, len).Replace ("\\\"", "\"") + : String.Empty; + } + + internal static bool Upgrades ( + this NameValueCollection headers, string protocol + ) + { + var comparison = StringComparison.OrdinalIgnoreCase; + return headers.Contains ("Upgrade", protocol, comparison) + && headers.Contains ("Connection", "Upgrade", comparison); + } + + internal static string UrlDecode (this string value, Encoding encoding) + { + return HttpUtility.UrlDecode (value, encoding); + } + + internal static string UrlEncode (this string value, Encoding encoding) + { + return HttpUtility.UrlEncode (value, encoding); + } + + internal static string UTF8Decode (this byte[] bytes) + { + try { + return Encoding.UTF8.GetString (bytes); + } + catch { + return null; + } + } + + internal static byte[] UTF8Encode (this string s) + { + return Encoding.UTF8.GetBytes (s); + } + + internal static void WriteBytes (this Stream stream, byte[] bytes, int bufferLength) + { + using (var input = new MemoryStream (bytes)) + input.CopyTo (stream, bufferLength); + } + + internal static void WriteBytesAsync ( + this Stream stream, byte[] bytes, int bufferLength, Action completed, Action error) + { + var input = new MemoryStream (bytes); + input.CopyToAsync ( + stream, + bufferLength, + () => { + if (completed != null) + completed (); + + input.Dispose (); + }, + ex => { + input.Dispose (); + if (error != null) + error (ex); + }); + } + + #endregion + + #region Public Methods + + /// + /// Emits the specified delegate if it isn't . + /// + /// + /// A to emit. + /// + /// + /// An from which emits this . + /// + /// + /// A that contains no event data. + /// + public static void Emit (this EventHandler eventHandler, object sender, EventArgs e) + { + if (eventHandler != null) + eventHandler (sender, e); + } + + /// + /// Emits the specified EventHandler<TEventArgs> delegate if it isn't + /// . + /// + /// + /// An EventHandler<TEventArgs> to emit. + /// + /// + /// An from which emits this . + /// + /// + /// A TEventArgs that represents the event data. + /// + /// + /// The type of the event data generated by the event. + /// + public static void Emit ( + this EventHandler eventHandler, object sender, TEventArgs e) + where TEventArgs : EventArgs + { + if (eventHandler != null) + eventHandler (sender, e); + } + + /// + /// Gets the description of the specified HTTP status . + /// + /// + /// A that represents the description of the HTTP status code. + /// + /// + /// One of enum values, indicates the HTTP status code. + /// + public static string GetDescription (this HttpStatusCode code) + { + return ((int) code).GetStatusDescription (); + } + + /// + /// Gets the description of the specified HTTP status . + /// + /// + /// A that represents the description of the HTTP status code. + /// + /// + /// An that represents the HTTP status code. + /// + public static string GetStatusDescription (this int code) + { + switch (code) { + case 100: return "Continue"; + case 101: return "Switching Protocols"; + case 102: return "Processing"; + case 200: return "OK"; + case 201: return "Created"; + case 202: return "Accepted"; + case 203: return "Non-Authoritative Information"; + case 204: return "No Content"; + case 205: return "Reset Content"; + case 206: return "Partial Content"; + case 207: return "Multi-Status"; + case 300: return "Multiple Choices"; + case 301: return "Moved Permanently"; + case 302: return "Found"; + case 303: return "See Other"; + case 304: return "Not Modified"; + case 305: return "Use Proxy"; + case 307: return "Temporary Redirect"; + case 400: return "Bad Request"; + case 401: return "Unauthorized"; + case 402: return "Payment Required"; + case 403: return "Forbidden"; + case 404: return "Not Found"; + case 405: return "Method Not Allowed"; + case 406: return "Not Acceptable"; + case 407: return "Proxy Authentication Required"; + case 408: return "Request Timeout"; + case 409: return "Conflict"; + case 410: return "Gone"; + case 411: return "Length Required"; + case 412: return "Precondition Failed"; + case 413: return "Request Entity Too Large"; + case 414: return "Request-Uri Too Long"; + case 415: return "Unsupported Media Type"; + case 416: return "Requested Range Not Satisfiable"; + case 417: return "Expectation Failed"; + case 422: return "Unprocessable Entity"; + case 423: return "Locked"; + case 424: return "Failed Dependency"; + case 500: return "Internal Server Error"; + case 501: return "Not Implemented"; + case 502: return "Bad Gateway"; + case 503: return "Service Unavailable"; + case 504: return "Gateway Timeout"; + case 505: return "Http Version Not Supported"; + case 507: return "Insufficient Storage"; + } + + return String.Empty; + } + + /// + /// Determines whether the specified ushort is in the range of + /// the status code for the WebSocket connection close. + /// + /// + /// + /// The ranges are the following: + /// + /// + /// + /// + /// 1000-2999: These numbers are reserved for definition by + /// the WebSocket protocol. + /// + /// + /// + /// + /// 3000-3999: These numbers are reserved for use by libraries, + /// frameworks, and applications. + /// + /// + /// + /// + /// 4000-4999: These numbers are reserved for private use. + /// + /// + /// + /// + /// + /// true if is in the range of + /// the status code for the close; otherwise, false. + /// + /// + /// A to test. + /// + public static bool IsCloseStatusCode (this ushort value) + { + return value > 999 && value < 5000; + } + + /// + /// Determines whether the specified string is enclosed in + /// the specified character. + /// + /// + /// true if is enclosed in + /// ; otherwise, false. + /// + /// + /// A to test. + /// + /// + /// A to find. + /// + public static bool IsEnclosedIn (this string value, char c) + { + return value != null + && value.Length > 1 + && value[0] == c + && value[value.Length - 1] == c; + } + + /// + /// Determines whether the specified byte order is host (this computer + /// architecture) byte order. + /// + /// + /// true if is host byte order; otherwise, + /// false. + /// + /// + /// One of the enum values to test. + /// + public static bool IsHostOrder (this ByteOrder order) + { + // true: !(true ^ true) or !(false ^ false) + // false: !(true ^ false) or !(false ^ true) + return !(BitConverter.IsLittleEndian ^ (order == ByteOrder.Little)); + } + + /// + /// Determines whether the specified IP address is a local IP address. + /// + /// + /// This local means NOT REMOTE for the current host. + /// + /// + /// true if is a local IP address; + /// otherwise, false. + /// + /// + /// A to test. + /// + /// + /// is . + /// + public static bool IsLocal (this System.Net.IPAddress address) + { + if (address == null) + throw new ArgumentNullException ("address"); + + if (address.Equals (System.Net.IPAddress.Any)) + return true; + + if (address.Equals (System.Net.IPAddress.Loopback)) + return true; + + if (Socket.OSSupportsIPv6) { + if (address.Equals (System.Net.IPAddress.IPv6Any)) + return true; + + if (address.Equals (System.Net.IPAddress.IPv6Loopback)) + return true; + } + + var host = System.Net.Dns.GetHostName (); + var addrs = System.Net.Dns.GetHostAddresses (host); + foreach (var addr in addrs) { + if (address.Equals (addr)) + return true; + } + + return false; + } + + /// + /// Determines whether the specified string is or + /// an empty string. + /// + /// + /// true if is or + /// an empty string; otherwise, false. + /// + /// + /// A to test. + /// + public static bool IsNullOrEmpty (this string value) + { + return value == null || value.Length == 0; + } + + /// + /// Determines whether the specified string is a predefined scheme. + /// + /// + /// true if is a predefined scheme; + /// otherwise, false. + /// + /// + /// A to test. + /// + public static bool IsPredefinedScheme (this string value) + { + if (value == null || value.Length < 2) + return false; + + var c = value[0]; + if (c == 'h') + return value == "http" || value == "https"; + + if (c == 'w') + return value == "ws" || value == "wss"; + + if (c == 'f') + return value == "file" || value == "ftp"; + + if (c == 'g') + return value == "gopher"; + + if (c == 'm') + return value == "mailto"; + + if (c == 'n') { + c = value[1]; + return c == 'e' + ? value == "news" || value == "net.pipe" || value == "net.tcp" + : value == "nntp"; + } + + return false; + } + + /// + /// Determines whether the specified string is a URI string. + /// + /// + /// true if may be a URI string; + /// otherwise, false. + /// + /// + /// A to test. + /// + public static bool MaybeUri (this string value) + { + if (value == null || value.Length == 0) + return false; + + var idx = value.IndexOf (':'); + if (idx == -1) + return false; + + if (idx >= 10) + return false; + + var schm = value.Substring (0, idx); + return schm.IsPredefinedScheme (); + } + + /// + /// Retrieves a sub-array from the specified . A sub-array starts at + /// the specified element position in . + /// + /// + /// An array of T that receives a sub-array, or an empty array of T if any problems with + /// the parameters. + /// + /// + /// An array of T from which to retrieve a sub-array. + /// + /// + /// An that represents the zero-based starting position of + /// a sub-array in . + /// + /// + /// An that represents the number of elements to retrieve. + /// + /// + /// The type of elements in . + /// + public static T[] SubArray (this T[] array, int startIndex, int length) + { + int len; + if (array == null || (len = array.Length) == 0) + return new T[0]; + + if (startIndex < 0 || length <= 0 || startIndex + length > len) + return new T[0]; + + if (startIndex == 0 && length == len) + return array; + + var subArray = new T[length]; + Array.Copy (array, startIndex, subArray, 0, length); + + return subArray; + } + + /// + /// Retrieves a sub-array from the specified . A sub-array starts at + /// the specified element position in . + /// + /// + /// An array of T that receives a sub-array, or an empty array of T if any problems with + /// the parameters. + /// + /// + /// An array of T from which to retrieve a sub-array. + /// + /// + /// A that represents the zero-based starting position of + /// a sub-array in . + /// + /// + /// A that represents the number of elements to retrieve. + /// + /// + /// The type of elements in . + /// + public static T[] SubArray (this T[] array, long startIndex, long length) + { + long len; + if (array == null || (len = array.LongLength) == 0) + return new T[0]; + + if (startIndex < 0 || length <= 0 || startIndex + length > len) + return new T[0]; + + if (startIndex == 0 && length == len) + return array; + + var subArray = new T[length]; + Array.Copy (array, startIndex, subArray, 0, length); + + return subArray; + } + + /// + /// Executes the specified delegate times. + /// + /// + /// An is the number of times to execute. + /// + /// + /// An delegate that references the method(s) to execute. + /// + public static void Times (this int n, Action action) + { + if (n > 0 && action != null) + ((ulong) n).times (action); + } + + /// + /// Executes the specified delegate times. + /// + /// + /// A is the number of times to execute. + /// + /// + /// An delegate that references the method(s) to execute. + /// + public static void Times (this long n, Action action) + { + if (n > 0 && action != null) + ((ulong) n).times (action); + } + + /// + /// Executes the specified delegate times. + /// + /// + /// A is the number of times to execute. + /// + /// + /// An delegate that references the method(s) to execute. + /// + public static void Times (this uint n, Action action) + { + if (n > 0 && action != null) + ((ulong) n).times (action); + } + + /// + /// Executes the specified delegate times. + /// + /// + /// A is the number of times to execute. + /// + /// + /// An delegate that references the method(s) to execute. + /// + public static void Times (this ulong n, Action action) + { + if (n > 0 && action != null) + n.times (action); + } + + /// + /// Executes the specified Action<int> delegate times. + /// + /// + /// An is the number of times to execute. + /// + /// + /// An Action<int> delegate that references the method(s) to execute. + /// An parameter to pass to the method(s) is the zero-based count of + /// iteration. + /// + public static void Times (this int n, Action action) + { + if (n > 0 && action != null) + for (int i = 0; i < n; i++) + action (i); + } + + /// + /// Executes the specified Action<long> delegate times. + /// + /// + /// A is the number of times to execute. + /// + /// + /// An Action<long> delegate that references the method(s) to execute. + /// A parameter to pass to the method(s) is the zero-based count of + /// iteration. + /// + public static void Times (this long n, Action action) + { + if (n > 0 && action != null) + for (long i = 0; i < n; i++) + action (i); + } + + /// + /// Executes the specified Action<uint> delegate times. + /// + /// + /// A is the number of times to execute. + /// + /// + /// An Action<uint> delegate that references the method(s) to execute. + /// A parameter to pass to the method(s) is the zero-based count of + /// iteration. + /// + public static void Times (this uint n, Action action) + { + if (n > 0 && action != null) + for (uint i = 0; i < n; i++) + action (i); + } + + /// + /// Executes the specified Action<ulong> delegate times. + /// + /// + /// A is the number of times to execute. + /// + /// + /// An Action<ulong> delegate that references the method(s) to execute. + /// A parameter to pass to this method(s) is the zero-based count of + /// iteration. + /// + public static void Times (this ulong n, Action action) + { + if (n > 0 && action != null) + for (ulong i = 0; i < n; i++) + action (i); + } + + /// + /// Converts the specified array of to the specified type data. + /// + /// + /// A T converted from , or a default value of + /// T if is an empty array of or + /// if the type of T isn't , , , + /// , , , , + /// , , or . + /// + /// + /// An array of to convert. + /// + /// + /// One of the enum values, specifies the byte order of + /// . + /// + /// + /// The type of the return. The T must be a value type. + /// + /// + /// is . + /// + public static T To (this byte[] source, ByteOrder sourceOrder) + where T : struct + { + if (source == null) + throw new ArgumentNullException ("source"); + + if (source.Length == 0) + return default (T); + + var type = typeof (T); + var buff = source.ToHostOrder (sourceOrder); + + return type == typeof (Boolean) + ? (T)(object) BitConverter.ToBoolean (buff, 0) + : type == typeof (Char) + ? (T)(object) BitConverter.ToChar (buff, 0) + : type == typeof (Double) + ? (T)(object) BitConverter.ToDouble (buff, 0) + : type == typeof (Int16) + ? (T)(object) BitConverter.ToInt16 (buff, 0) + : type == typeof (Int32) + ? (T)(object) BitConverter.ToInt32 (buff, 0) + : type == typeof (Int64) + ? (T)(object) BitConverter.ToInt64 (buff, 0) + : type == typeof (Single) + ? (T)(object) BitConverter.ToSingle (buff, 0) + : type == typeof (UInt16) + ? (T)(object) BitConverter.ToUInt16 (buff, 0) + : type == typeof (UInt32) + ? (T)(object) BitConverter.ToUInt32 (buff, 0) + : type == typeof (UInt64) + ? (T)(object) BitConverter.ToUInt64 (buff, 0) + : default (T); + } + + /// + /// Converts the specified to an array of . + /// + /// + /// An array of converted from . + /// + /// + /// A T to convert. + /// + /// + /// One of the enum values, specifies the byte order of the return. + /// + /// + /// The type of . The T must be a value type. + /// + public static byte[] ToByteArray (this T value, ByteOrder order) + where T : struct + { + var type = typeof (T); + var bytes = type == typeof (Boolean) + ? BitConverter.GetBytes ((Boolean)(object) value) + : type == typeof (Byte) + ? new byte[] { (Byte)(object) value } + : type == typeof (Char) + ? BitConverter.GetBytes ((Char)(object) value) + : type == typeof (Double) + ? BitConverter.GetBytes ((Double)(object) value) + : type == typeof (Int16) + ? BitConverter.GetBytes ((Int16)(object) value) + : type == typeof (Int32) + ? BitConverter.GetBytes ((Int32)(object) value) + : type == typeof (Int64) + ? BitConverter.GetBytes ((Int64)(object) value) + : type == typeof (Single) + ? BitConverter.GetBytes ((Single)(object) value) + : type == typeof (UInt16) + ? BitConverter.GetBytes ((UInt16)(object) value) + : type == typeof (UInt32) + ? BitConverter.GetBytes ((UInt32)(object) value) + : type == typeof (UInt64) + ? BitConverter.GetBytes ((UInt64)(object) value) + : WebSocket.EmptyBytes; + + if (bytes.Length > 1 && !order.IsHostOrder ()) + Array.Reverse (bytes); + + return bytes; + } + + /// + /// Converts the order of elements in the specified byte array to + /// host (this computer architecture) byte order. + /// + /// + /// + /// An array of converted from + /// . + /// + /// + /// Or if the number of elements in it + /// is less than 2 or is same as + /// host byte order. + /// + /// + /// + /// An array of to convert. + /// + /// + /// + /// One of the enum values. + /// + /// + /// It specifies the order of elements in . + /// + /// + /// + /// is . + /// + public static byte[] ToHostOrder (this byte[] source, ByteOrder sourceOrder) + { + if (source == null) + throw new ArgumentNullException ("source"); + + if (source.Length < 2) + return source; + + return !sourceOrder.IsHostOrder () ? source.Reverse () : source; + } + + /// + /// Converts the specified array to a . + /// + /// + /// + /// A converted by concatenating each element of + /// across . + /// + /// + /// An empty string if is an empty array. + /// + /// + /// + /// An array of T to convert. + /// + /// + /// A used to separate each element of + /// . + /// + /// + /// The type of elements in . + /// + /// + /// is . + /// + public static string ToString (this T[] array, string separator) + { + if (array == null) + throw new ArgumentNullException ("array"); + + var len = array.Length; + if (len == 0) + return String.Empty; + + if (separator == null) + separator = String.Empty; + + var buff = new StringBuilder (64); + + for (var i = 0; i < len - 1; i++) + buff.AppendFormat ("{0}{1}", array[i], separator); + + buff.Append (array[len - 1].ToString ()); + return buff.ToString (); + } + + /// + /// Converts the specified string to a . + /// + /// + /// + /// A converted from . + /// + /// + /// if the conversion has failed. + /// + /// + /// + /// A to convert. + /// + public static Uri ToUri (this string value) + { + Uri ret; + Uri.TryCreate ( + value, value.MaybeUri () ? UriKind.Absolute : UriKind.Relative, out ret + ); + + return ret; + } + + /// + /// Writes and sends the specified data with the specified + /// . + /// + /// + /// A that represents the HTTP response used to + /// send the content data. + /// + /// + /// An array of that represents the content data to send. + /// + /// + /// + /// is . + /// + /// + /// -or- + /// + /// + /// is . + /// + /// + public static void WriteContent (this HttpListenerResponse response, byte[] content) + { + if (response == null) + throw new ArgumentNullException ("response"); + + if (content == null) + throw new ArgumentNullException ("content"); + + var len = content.LongLength; + if (len == 0) { + response.Close (); + return; + } + + response.ContentLength64 = len; + var output = response.OutputStream; + if (len <= Int32.MaxValue) + output.Write (content, 0, (int) len); + else + output.WriteBytes (content, 1024); + + output.Close (); + } + + #endregion + } +} diff --git a/websocket-sharp/Fin.cs b/websocket-sharp/Fin.cs new file mode 100644 index 0000000..8965c37 --- /dev/null +++ b/websocket-sharp/Fin.cs @@ -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 +{ + /// + /// Indicates whether a WebSocket frame is the final frame of a message. + /// + /// + /// The values of this enumeration are defined in + /// Section 5.2 of RFC 6455. + /// + internal enum Fin : byte + { + /// + /// Equivalent to numeric value 0. Indicates more frames of a message follow. + /// + More = 0x0, + /// + /// Equivalent to numeric value 1. Indicates the final frame of a message. + /// + Final = 0x1 + } +} diff --git a/websocket-sharp/HttpBase.cs b/websocket-sharp/HttpBase.cs new file mode 100644 index 0000000..a7dbd40 --- /dev/null +++ b/websocket-sharp/HttpBase.cs @@ -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 (); + var cnt = 0; + Action 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 (Stream stream, Func 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 + } +} diff --git a/websocket-sharp/HttpRequest.cs b/websocket-sharp/HttpRequest.cs new file mode 100644 index 0000000..fe74d5a --- /dev/null +++ b/websocket-sharp/HttpRequest.cs @@ -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 (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 (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 + } +} diff --git a/websocket-sharp/HttpResponse.cs b/websocket-sharp/HttpResponse.cs new file mode 100644 index 0000000..831b727 --- /dev/null +++ b/websocket-sharp/HttpResponse.cs @@ -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 (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 + } +} diff --git a/websocket-sharp/LogData.cs b/websocket-sharp/LogData.cs new file mode 100644 index 0000000..9c08430 --- /dev/null +++ b/websocket-sharp/LogData.cs @@ -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 +{ + /// + /// Represents a log data used by the class. + /// + 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 + + /// + /// Gets the information of the logging method caller. + /// + /// + /// A that provides the information of the logging method caller. + /// + public StackFrame Caller { + get { + return _caller; + } + } + + /// + /// Gets the date and time when the log data was created. + /// + /// + /// A that represents the date and time when the log data was created. + /// + public DateTime Date { + get { + return _date; + } + } + + /// + /// Gets the logging level of the log data. + /// + /// + /// One of the enum values, indicates the logging level of the log data. + /// + public LogLevel Level { + get { + return _level; + } + } + + /// + /// Gets the message of the log data. + /// + /// + /// A that represents the message of the log data. + /// + public string Message { + get { + return _message; + } + } + + #endregion + + #region Public Methods + + /// + /// Returns a that represents the current . + /// + /// + /// A that represents the current . + /// + 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 + } +} diff --git a/websocket-sharp/LogLevel.cs b/websocket-sharp/LogLevel.cs new file mode 100644 index 0000000..ef99677 --- /dev/null +++ b/websocket-sharp/LogLevel.cs @@ -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 +{ + /// + /// Specifies the logging level. + /// + public enum LogLevel + { + /// + /// Specifies the bottom logging level. + /// + Trace, + /// + /// Specifies the 2nd logging level from the bottom. + /// + Debug, + /// + /// Specifies the 3rd logging level from the bottom. + /// + Info, + /// + /// Specifies the 3rd logging level from the top. + /// + Warn, + /// + /// Specifies the 2nd logging level from the top. + /// + Error, + /// + /// Specifies the top logging level. + /// + Fatal + } +} diff --git a/websocket-sharp/Logger.cs b/websocket-sharp/Logger.cs new file mode 100644 index 0000000..17850e6 --- /dev/null +++ b/websocket-sharp/Logger.cs @@ -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 +{ + /// + /// Provides a set of methods and properties for logging. + /// + /// + /// + /// If you output a log with lower than the value of the property, + /// it cannot be outputted. + /// + /// + /// The default output action writes a log to the standard output stream and the log file + /// if the property has a valid path to it. + /// + /// + /// If you would like to use the custom output action, you should set + /// the property to any Action<LogData, string> + /// delegate. + /// + /// + public class Logger + { + #region Private Fields + + private volatile string _file; + private volatile LogLevel _level; + private Action _output; + private object _sync; + + #endregion + + #region Public Constructors + + /// + /// Initializes a new instance of the class. + /// + /// + /// This constructor initializes the current logging level with . + /// + public Logger () + : this (LogLevel.Error, null, null) + { + } + + /// + /// Initializes a new instance of the class with + /// the specified logging . + /// + /// + /// One of the enum values. + /// + public Logger (LogLevel level) + : this (level, null, null) + { + } + + /// + /// Initializes a new instance of the class with + /// the specified logging , path to the log , + /// and action. + /// + /// + /// One of the enum values. + /// + /// + /// A that represents the path to the log file. + /// + /// + /// An Action<LogData, string> delegate that references the method(s) used to + /// output a log. A parameter passed to this delegate is + /// . + /// + public Logger (LogLevel level, string file, Action output) + { + _level = level; + _file = file; + _output = output ?? defaultOutput; + _sync = new object (); + } + + #endregion + + #region Public Properties + + /// + /// Gets or sets the current path to the log file. + /// + /// + /// A that represents the current path to the log file if any. + /// + 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)); + } + } + } + + /// + /// Gets or sets the current logging level. + /// + /// + /// A log with lower than the value of this property cannot be outputted. + /// + /// + /// One of the enum values, specifies the current logging level. + /// + public LogLevel Level { + get { + return _level; + } + + set { + lock (_sync) { + _level = value; + Warn (String.Format ("The current logging level has been changed to {0}.", _level)); + } + } + } + + /// + /// Gets or sets the current output action used to output a log. + /// + /// + /// + /// An Action<LogData, string> delegate that references the method(s) used to + /// output a log. A parameter passed to this delegate is the value of + /// the property. + /// + /// + /// If the value to set is , the current output action is changed to + /// the default output action. + /// + /// + public Action 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 + + /// + /// Outputs as a log with . + /// + /// + /// If the current logging level is higher than , + /// this method doesn't output as a log. + /// + /// + /// A that represents the message to output as a log. + /// + public void Debug (string message) + { + if (_level > LogLevel.Debug) + return; + + output (message, LogLevel.Debug); + } + + /// + /// Outputs as a log with . + /// + /// + /// If the current logging level is higher than , + /// this method doesn't output as a log. + /// + /// + /// A that represents the message to output as a log. + /// + public void Error (string message) + { + if (_level > LogLevel.Error) + return; + + output (message, LogLevel.Error); + } + + /// + /// Outputs as a log with . + /// + /// + /// A that represents the message to output as a log. + /// + public void Fatal (string message) + { + output (message, LogLevel.Fatal); + } + + /// + /// Outputs as a log with . + /// + /// + /// If the current logging level is higher than , + /// this method doesn't output as a log. + /// + /// + /// A that represents the message to output as a log. + /// + public void Info (string message) + { + if (_level > LogLevel.Info) + return; + + output (message, LogLevel.Info); + } + + /// + /// Outputs as a log with . + /// + /// + /// If the current logging level is higher than , + /// this method doesn't output as a log. + /// + /// + /// A that represents the message to output as a log. + /// + public void Trace (string message) + { + if (_level > LogLevel.Trace) + return; + + output (message, LogLevel.Trace); + } + + /// + /// Outputs as a log with . + /// + /// + /// If the current logging level is higher than , + /// this method doesn't output as a log. + /// + /// + /// A that represents the message to output as a log. + /// + public void Warn (string message) + { + if (_level > LogLevel.Warn) + return; + + output (message, LogLevel.Warn); + } + + #endregion + } +} diff --git a/websocket-sharp/Mask.cs b/websocket-sharp/Mask.cs new file mode 100644 index 0000000..fcafac8 --- /dev/null +++ b/websocket-sharp/Mask.cs @@ -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 +{ + /// + /// Indicates whether the payload data of a WebSocket frame is masked. + /// + /// + /// The values of this enumeration are defined in + /// Section 5.2 of RFC 6455. + /// + internal enum Mask : byte + { + /// + /// Equivalent to numeric value 0. Indicates not masked. + /// + Off = 0x0, + /// + /// Equivalent to numeric value 1. Indicates masked. + /// + On = 0x1 + } +} diff --git a/websocket-sharp/MessageEventArgs.cs b/websocket-sharp/MessageEventArgs.cs new file mode 100644 index 0000000..adf7391 --- /dev/null +++ b/websocket-sharp/MessageEventArgs.cs @@ -0,0 +1,180 @@ +#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 +{ + /// + /// Represents the event data for the event. + /// + /// + /// + /// That event occurs when the receives + /// a message or a ping if the + /// property is set to true. + /// + /// + /// If you would like to get the message data, you should access + /// the or property. + /// + /// + 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 + + /// + /// Gets the opcode for the message. + /// + /// + /// , , + /// or . + /// + internal Opcode Opcode { + get { + return _opcode; + } + } + + #endregion + + #region Public Properties + + /// + /// Gets the message data as a . + /// + /// + /// A that represents the message data if its type is + /// text or ping and if decoding it to a string has successfully done; + /// otherwise, . + /// + public string Data { + get { + setData (); + return _data; + } + } + + /// + /// Gets a value indicating whether the message type is binary. + /// + /// + /// true if the message type is binary; otherwise, false. + /// + public bool IsBinary { + get { + return _opcode == Opcode.Binary; + } + } + + /// + /// Gets a value indicating whether the message type is ping. + /// + /// + /// true if the message type is ping; otherwise, false. + /// + public bool IsPing { + get { + return _opcode == Opcode.Ping; + } + } + + /// + /// Gets a value indicating whether the message type is text. + /// + /// + /// true if the message type is text; otherwise, false. + /// + public bool IsText { + get { + return _opcode == Opcode.Text; + } + } + + /// + /// Gets the message data as an array of . + /// + /// + /// An array of that represents the message data. + /// + public byte[] RawData { + get { + setData (); + return _rawData; + } + } + + #endregion + + #region Private Methods + + private void setData () + { + if (_dataSet) + return; + + if (_opcode == Opcode.Binary) { + _dataSet = true; + return; + } + + _data = _rawData.UTF8Decode (); + _dataSet = true; + } + + #endregion + } +} diff --git a/websocket-sharp/Net/AuthenticationBase.cs b/websocket-sharp/Net/AuthenticationBase.cs new file mode 100644 index 0000000..1077504 --- /dev/null +++ b/websocket-sharp/Net/AuthenticationBase.cs @@ -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 + } +} diff --git a/websocket-sharp/Net/AuthenticationChallenge.cs b/websocket-sharp/Net/AuthenticationChallenge.cs new file mode 100644 index 0000000..3472204 --- /dev/null +++ b/websocket-sharp/Net/AuthenticationChallenge.cs @@ -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 + } +} diff --git a/websocket-sharp/Net/AuthenticationResponse.cs b/websocket-sharp/Net/AuthenticationResponse.cs new file mode 100644 index 0000000..0257d85 --- /dev/null +++ b/websocket-sharp/Net/AuthenticationResponse.cs @@ -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 [\]:. + 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 + } +} diff --git a/websocket-sharp/Net/AuthenticationSchemes.cs b/websocket-sharp/Net/AuthenticationSchemes.cs new file mode 100644 index 0000000..ab7721a --- /dev/null +++ b/websocket-sharp/Net/AuthenticationSchemes.cs @@ -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 + */ +#endregion + +using System; + +namespace WebSocketSharp.Net +{ + /// + /// Specifies the scheme for authentication. + /// + public enum AuthenticationSchemes + { + /// + /// No authentication is allowed. + /// + None, + /// + /// Specifies digest authentication. + /// + Digest = 1, + /// + /// Specifies basic authentication. + /// + Basic = 8, + /// + /// Specifies anonymous authentication. + /// + Anonymous = 0x8000 + } +} diff --git a/websocket-sharp/Net/Chunk.cs b/websocket-sharp/Net/Chunk.cs new file mode 100644 index 0000000..7b6268b --- /dev/null +++ b/websocket-sharp/Net/Chunk.cs @@ -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 + */ +#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 + } +} diff --git a/websocket-sharp/Net/ChunkStream.cs b/websocket-sharp/Net/ChunkStream.cs new file mode 100644 index 0000000..a5271b5 --- /dev/null +++ b/websocket-sharp/Net/ChunkStream.cs @@ -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 + */ +#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 _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 (); + _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 + } +} diff --git a/websocket-sharp/Net/ChunkedRequestStream.cs b/websocket-sharp/Net/ChunkedRequestStream.cs new file mode 100644 index 0000000..913b505 --- /dev/null +++ b/websocket-sharp/Net/ChunkedRequestStream.cs @@ -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 + */ +#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 + } +} diff --git a/websocket-sharp/Net/ClientSslConfiguration.cs b/websocket-sharp/Net/ClientSslConfiguration.cs new file mode 100644 index 0000000..800bcb3 --- /dev/null +++ b/websocket-sharp/Net/ClientSslConfiguration.cs @@ -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 + */ +#endregion + +using System; +using System.Net.Security; +using System.Security.Authentication; +using System.Security.Cryptography.X509Certificates; + +namespace WebSocketSharp.Net +{ + /// + /// Stores the parameters for the used by clients. + /// + 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 + + /// + /// Initializes a new instance of the class. + /// + public ClientSslConfiguration () + { + _enabledSslProtocols = SslProtocols.Default; + } + + /// + /// Initializes a new instance of the class + /// with the specified . + /// + /// + /// A that represents the target host server name. + /// + public ClientSslConfiguration (string targetHost) + { + _targetHost = targetHost; + _enabledSslProtocols = SslProtocols.Default; + } + + /// + /// Copies the parameters from the specified to + /// a new instance of the class. + /// + /// + /// A from which to copy. + /// + /// + /// is . + /// + 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 + + /// + /// Gets or sets a value indicating whether the certificate revocation + /// list is checked during authentication. + /// + /// + /// + /// true if the certificate revocation list is checked during + /// authentication; otherwise, false. + /// + /// + /// The default value is false. + /// + /// + public bool CheckCertificateRevocation { + get { + return _checkCertRevocation; + } + + set { + _checkCertRevocation = value; + } + } + + /// + /// Gets or sets the certificates from which to select one to + /// supply to the server. + /// + /// + /// + /// A or . + /// + /// + /// That collection contains client certificates from which to select. + /// + /// + /// The default value is . + /// + /// + public X509CertificateCollection ClientCertificates { + get { + return _clientCerts; + } + + set { + _clientCerts = value; + } + } + + /// + /// Gets or sets the callback used to select the certificate to + /// supply to the server. + /// + /// + /// No certificate is supplied if the callback returns + /// . + /// + /// + /// + /// A delegate that + /// invokes the method called for selecting the certificate. + /// + /// + /// The default value is a delegate that invokes a method that + /// only returns . + /// + /// + public LocalCertificateSelectionCallback ClientCertificateSelectionCallback { + get { + if (_clientCertSelectionCallback == null) + _clientCertSelectionCallback = defaultSelectClientCertificate; + + return _clientCertSelectionCallback; + } + + set { + _clientCertSelectionCallback = value; + } + } + + /// + /// Gets or sets the protocols used for authentication. + /// + /// + /// + /// The enum values that represent + /// the protocols used for authentication. + /// + /// + /// The default value is . + /// + /// + public SslProtocols EnabledSslProtocols { + get { + return _enabledSslProtocols; + } + + set { + _enabledSslProtocols = value; + } + } + + /// + /// Gets or sets the callback used to validate the certificate + /// supplied by the server. + /// + /// + /// The certificate is valid if the callback returns true. + /// + /// + /// + /// A delegate that + /// invokes the method called for validating the certificate. + /// + /// + /// The default value is a delegate that invokes a method that + /// only returns true. + /// + /// + public RemoteCertificateValidationCallback ServerCertificateValidationCallback { + get { + if (_serverCertValidationCallback == null) + _serverCertValidationCallback = defaultValidateServerCertificate; + + return _serverCertValidationCallback; + } + + set { + _serverCertValidationCallback = value; + } + } + + /// + /// Gets or sets the target host server name. + /// + /// + /// + /// A or + /// if not specified. + /// + /// + /// That string represents the name of the server that + /// will share a secure connection with a client. + /// + /// + 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 + } +} diff --git a/websocket-sharp/Net/Cookie.cs b/websocket-sharp/Net/Cookie.cs new file mode 100644 index 0000000..d334dd2 --- /dev/null +++ b/websocket-sharp/Net/Cookie.cs @@ -0,0 +1,998 @@ +#region License +/* + * Cookie.cs + * + * This code is derived from Cookie.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 + * - Gonzalo Paniagua Javier + * - Daniel Nauck + * - Sebastien Pouliot + */ +#endregion + +using System; +using System.Globalization; +using System.Text; + +namespace WebSocketSharp.Net +{ + /// + /// Provides a set of methods and properties used to manage an HTTP cookie. + /// + /// + /// + /// This class refers to the following specifications: + /// + /// + /// + /// + /// Netscape specification + /// + /// + /// + /// + /// RFC 2109 + /// + /// + /// + /// + /// RFC 2965 + /// + /// + /// + /// + /// This class cannot be inherited. + /// + /// + [Serializable] + public sealed class Cookie + { + #region Private Fields + + private string _comment; + private Uri _commentUri; + private bool _discard; + private string _domain; + private static readonly int[] _emptyPorts; + private DateTime _expires; + private bool _httpOnly; + private string _name; + private string _path; + private string _port; + private int[] _ports; + private static readonly char[] _reservedCharsForName; + private static readonly char[] _reservedCharsForValue; + private bool _secure; + private DateTime _timeStamp; + private string _value; + private int _version; + + #endregion + + #region Static Constructor + + static Cookie () + { + _emptyPorts = new int[0]; + _reservedCharsForName = new[] { ' ', '=', ';', ',', '\n', '\r', '\t' }; + _reservedCharsForValue = new[] { ';', ',' }; + } + + #endregion + + #region Public Constructors + + /// + /// Initializes a new instance of the class. + /// + public Cookie () + { + init (String.Empty, "\"\"", String.Empty, String.Empty); + } + + /// + /// Initializes a new instance of the class with + /// the specified name and value. + /// + /// + /// A that specifies the Name of the cookie. + /// + /// + /// A that specifies the Value of the cookie. + /// + /// + /// + /// is . + /// + /// + /// - or - + /// + /// + /// is . + /// + /// + /// + /// + /// is an empty string. + /// + /// + /// - or - + /// + /// + /// starts with a dollar sign. + /// + /// + /// - or - + /// + /// + /// contains an invalid character. + /// + /// + /// - or - + /// + /// + /// is a string not enclosed in double quotes + /// that contains an invalid character. + /// + /// + public Cookie (string name, string value) + : this (name, value, String.Empty, String.Empty) + { + } + + /// + /// Initializes a new instance of the class with + /// the specified name, value, and path. + /// + /// + /// A that specifies the Name of the cookie. + /// + /// + /// A that specifies the Value of the cookie. + /// + /// + /// A that specifies the value of the Path + /// attribute of the cookie. + /// + /// + /// + /// is . + /// + /// + /// - or - + /// + /// + /// is . + /// + /// + /// + /// + /// is an empty string. + /// + /// + /// - or - + /// + /// + /// starts with a dollar sign. + /// + /// + /// - or - + /// + /// + /// contains an invalid character. + /// + /// + /// - or - + /// + /// + /// is a string not enclosed in double quotes + /// that contains an invalid character. + /// + /// + public Cookie (string name, string value, string path) + : this (name, value, path, String.Empty) + { + } + + /// + /// Initializes a new instance of the class with + /// the specified name, value, path, and domain. + /// + /// + /// A that specifies the Name of the cookie. + /// + /// + /// A that specifies the Value of the cookie. + /// + /// + /// A that specifies the value of the Path + /// attribute of the cookie. + /// + /// + /// A that specifies the value of the Domain + /// attribute of the cookie. + /// + /// + /// + /// is . + /// + /// + /// - or - + /// + /// + /// is . + /// + /// + /// + /// + /// is an empty string. + /// + /// + /// - or - + /// + /// + /// starts with a dollar sign. + /// + /// + /// - or - + /// + /// + /// contains an invalid character. + /// + /// + /// - or - + /// + /// + /// is a string not enclosed in double quotes + /// that contains an invalid character. + /// + /// + public Cookie (string name, string value, string path, string domain) + { + if (name == null) + throw new ArgumentNullException ("name"); + + if (value == null) + throw new ArgumentNullException ("value"); + + if (name.Length == 0) + throw new ArgumentException ("An empty string.", "name"); + + if (name[0] == '$') { + var msg = "It starts with a dollar sign."; + throw new ArgumentException (msg, "name"); + } + + if (name.Contains (_reservedCharsForName)) { + var msg = "It contains an invalid character."; + throw new ArgumentException (msg, "name"); + } + + if (value.Contains (_reservedCharsForValue)) { + if (!value.IsEnclosedIn ('"')) { + var msg = "A string not enclosed in double quotes."; + throw new ArgumentException (msg, "value"); + } + } + + init ( + name, + value.Length > 0 ? value : "\"\"", + path ?? String.Empty, + domain ?? String.Empty + ); + } + + #endregion + + #region Internal Properties + + internal bool ExactDomain { + get { + return _domain.Length == 0 || _domain[0] != '.'; + } + } + + internal int MaxAge { + get { + if (_expires == DateTime.MinValue) + return 0; + + var expires = _expires.Kind != DateTimeKind.Local + ? _expires.ToLocalTime () + : _expires; + + var span = expires - DateTime.Now; + return span > TimeSpan.Zero + ? (int) span.TotalSeconds + : 0; + } + } + + internal int[] Ports { + get { + return _ports ?? _emptyPorts; + } + } + + #endregion + + #region Public Properties + + /// + /// Gets or sets the value of the Comment attribute of the cookie. + /// + /// + /// + /// A that represents the comment to document + /// intended use of the cookie. + /// + /// + /// An empty string if this attribute is not needed. + /// + /// + /// The default value is an empty string. + /// + /// + public string Comment { + get { + return _comment ?? String.Empty; + } + + set { + _comment = value; + } + } + + /// + /// Gets or sets the value of the CommentURL attribute of the cookie. + /// + /// + /// + /// A that represents the URI that provides + /// the comment to document intended use of the cookie. + /// + /// + /// if this attribute is not needed. + /// + /// + /// The default value is . + /// + /// + public Uri CommentUri { + get { + return _commentUri; + } + + set { + _commentUri = value; + } + } + + /// + /// Gets or sets a value indicating whether the client discards the cookie + /// unconditionally when the client terminates. + /// + /// + /// + /// true if the client discards the cookie unconditionally + /// when the client terminates; otherwise, false. + /// + /// + /// The default value is false. + /// + /// + public bool Discard { + get { + return _discard; + } + + set { + _discard = value; + } + } + + /// + /// Gets or sets the value of the Domain attribute of the cookie. + /// + /// + /// + /// A that represents the domain name that + /// the cookie is valid for. + /// + /// + /// An empty string if this attribute is not needed. + /// + /// + public string Domain { + get { + return _domain; + } + + set { + _domain = value ?? String.Empty; + } + } + + /// + /// Gets or sets a value indicating whether the cookie has expired. + /// + /// + /// + /// true if the cookie has expired; otherwise, false. + /// + /// + /// The default value is false. + /// + /// + public bool Expired { + get { + return _expires != DateTime.MinValue && _expires <= DateTime.Now; + } + + set { + _expires = value ? DateTime.Now : DateTime.MinValue; + } + } + + /// + /// Gets or sets the value of the Expires attribute of the cookie. + /// + /// + /// + /// A that represents the date and time that + /// the cookie expires on. + /// + /// + /// if this attribute is not needed. + /// + /// + /// The default value is . + /// + /// + public DateTime Expires { + get { + return _expires; + } + + set { + _expires = value; + } + } + + /// + /// Gets or sets a value indicating whether non-HTTP APIs can access + /// the cookie. + /// + /// + /// + /// true if non-HTTP APIs cannot access the cookie; otherwise, + /// false. + /// + /// + /// The default value is false. + /// + /// + public bool HttpOnly { + get { + return _httpOnly; + } + + set { + _httpOnly = value; + } + } + + /// + /// Gets or sets the Name of the cookie. + /// + /// + /// A that represents the Name of the cookie. + /// + /// + /// The value specified for a set operation is . + /// + /// + /// + /// The value specified for a set operation is an empty string. + /// + /// + /// - or - + /// + /// + /// The value specified for a set operation starts with a dollar sign. + /// + /// + /// - or - + /// + /// + /// The value specified for a set operation contains an invalid character. + /// + /// + public string Name { + get { + return _name; + } + + set { + if (value == null) + throw new ArgumentNullException ("value"); + + if (value.Length == 0) + throw new ArgumentException ("An empty string.", "value"); + + if (value[0] == '$') { + var msg = "It starts with a dollar sign."; + throw new ArgumentException (msg, "value"); + } + + if (value.Contains (_reservedCharsForName)) { + var msg = "It contains an invalid character."; + throw new ArgumentException (msg, "value"); + } + + _name = value; + } + } + + /// + /// Gets or sets the value of the Path attribute of the cookie. + /// + /// + /// A that represents the subset of URI on + /// the origin server that the cookie applies to. + /// + public string Path { + get { + return _path; + } + + set { + _path = value ?? String.Empty; + } + } + + /// + /// Gets or sets the value of the Port attribute of the cookie. + /// + /// + /// + /// A that represents the list of TCP ports + /// that the cookie applies to. + /// + /// + /// An empty string if this attribute is not needed. + /// + /// + /// The default value is an empty string. + /// + /// + /// + /// + /// The value specified for a set operation is not enclosed in + /// double quotes. + /// + /// + /// -or- + /// + /// + /// The value specified for a set operation could not be parsed. + /// + /// + public string Port { + get { + return _port ?? String.Empty; + } + + set { + if (value.IsNullOrEmpty ()) { + _port = value; + _ports = null; + + return; + } + + if (!value.IsEnclosedIn ('"')) { + var msg = "It is not enclosed in double quotes."; + throw new ArgumentException (msg, "value"); + } + + int[] ports; + if (!tryCreatePorts (value, out ports)) { + var msg = "It could not be parsed."; + throw new ArgumentException (msg, "value"); + } + + _port = value; + _ports = ports; + } + } + + /// + /// Gets or sets a value indicating whether the security level of + /// the cookie is secure. + /// + /// + /// When this property is true, the cookie may be included in + /// the request only if the request is transmitted over HTTPS. + /// + /// + /// + /// true if the security level of the cookie is secure; + /// otherwise, false. + /// + /// + /// The default value is false. + /// + /// + public bool Secure { + get { + return _secure; + } + + set { + _secure = value; + } + } + + /// + /// Gets the time when the cookie was issued. + /// + /// + /// A that represents the time when + /// the cookie was issued. + /// + public DateTime TimeStamp { + get { + return _timeStamp; + } + } + + /// + /// Gets or sets the Value of the cookie. + /// + /// + /// A that represents the Value of the cookie. + /// + /// + /// The value specified for a set operation is . + /// + /// + /// The value specified for a set operation is a string not enclosed in + /// double quotes that contains an invalid character. + /// + public string Value { + get { + return _value; + } + + set { + if (value == null) + throw new ArgumentNullException ("value"); + + if (value.Contains (_reservedCharsForValue)) { + if (!value.IsEnclosedIn ('"')) { + var msg = "A string not enclosed in double quotes."; + throw new ArgumentException (msg, "value"); + } + } + + _value = value.Length > 0 ? value : "\"\""; + } + } + + /// + /// Gets or sets the value of the Version attribute of the cookie. + /// + /// + /// + /// An that represents the version of HTTP state + /// management that the cookie conforms to. + /// + /// + /// 0 or 1. + /// + /// + /// The default value is 0. + /// + /// + /// + /// The value specified for a set operation is not allowed. + /// + public int Version { + get { + return _version; + } + + set { + if (value < 0 || value > 1) { + var msg = "It is not allowed."; + throw new ArgumentOutOfRangeException ("value", msg); + } + + _version = value; + } + } + + #endregion + + #region Private Methods + + private static int hash (int i, int j, int k, int l, int m) + { + return i + ^ (j << 13 | j >> 19) + ^ (k << 26 | k >> 6) + ^ (l << 7 | l >> 25) + ^ (m << 20 | m >> 12); + } + + private void init (string name, string value, string path, string domain) + { + _name = name; + _value = value; + _path = path; + _domain = domain; + + _expires = DateTime.MinValue; + _timeStamp = DateTime.Now; + } + + private string toResponseStringVersion0 () + { + var buff = new StringBuilder (64); + + buff.AppendFormat ("{0}={1}", _name, _value); + + if (_expires != DateTime.MinValue) { + buff.AppendFormat ( + "; Expires={0}", + _expires.ToUniversalTime ().ToString ( + "ddd, dd'-'MMM'-'yyyy HH':'mm':'ss 'GMT'", + CultureInfo.CreateSpecificCulture ("en-US") + ) + ); + } + + if (!_path.IsNullOrEmpty ()) + buff.AppendFormat ("; Path={0}", _path); + + if (!_domain.IsNullOrEmpty ()) + buff.AppendFormat ("; Domain={0}", _domain); + + if (_secure) + buff.Append ("; Secure"); + + if (_httpOnly) + buff.Append ("; HttpOnly"); + + return buff.ToString (); + } + + private string toResponseStringVersion1 () + { + var buff = new StringBuilder (64); + + buff.AppendFormat ("{0}={1}; Version={2}", _name, _value, _version); + + if (_expires != DateTime.MinValue) + buff.AppendFormat ("; Max-Age={0}", MaxAge); + + if (!_path.IsNullOrEmpty ()) + buff.AppendFormat ("; Path={0}", _path); + + if (!_domain.IsNullOrEmpty ()) + buff.AppendFormat ("; Domain={0}", _domain); + + if (!_port.IsNullOrEmpty ()) { + if (_port != "\"\"") + buff.AppendFormat ("; Port={0}", _port); + else + buff.Append ("; Port"); + } + + if (!_comment.IsNullOrEmpty ()) + buff.AppendFormat ("; Comment={0}", HttpUtility.UrlEncode (_comment)); + + if (_commentUri != null) { + var url = _commentUri.OriginalString; + buff.AppendFormat ( + "; CommentURL={0}", !url.IsToken () ? url.Quote () : url + ); + } + + if (_discard) + buff.Append ("; Discard"); + + if (_secure) + buff.Append ("; Secure"); + + return buff.ToString (); + } + + private static bool tryCreatePorts (string value, out int[] result) + { + result = null; + + var arr = value.Trim ('"').Split (','); + var len = arr.Length; + var res = new int[len]; + + for (var i = 0; i < len; i++) { + var s = arr[i].Trim (); + if (s.Length == 0) { + res[i] = Int32.MinValue; + continue; + } + + if (!Int32.TryParse (s, out res[i])) + return false; + } + + result = res; + return true; + } + + #endregion + + #region Internal Methods + + internal bool EqualsWithoutValue (Cookie cookie) + { + var caseSensitive = StringComparison.InvariantCulture; + var caseInsensitive = StringComparison.InvariantCultureIgnoreCase; + + return _name.Equals (cookie._name, caseInsensitive) + && _path.Equals (cookie._path, caseSensitive) + && _domain.Equals (cookie._domain, caseInsensitive) + && _version == cookie._version; + } + + internal string ToRequestString (Uri uri) + { + if (_name.Length == 0) + return String.Empty; + + if (_version == 0) + return String.Format ("{0}={1}", _name, _value); + + var buff = new StringBuilder (64); + + buff.AppendFormat ("$Version={0}; {1}={2}", _version, _name, _value); + + if (!_path.IsNullOrEmpty ()) + buff.AppendFormat ("; $Path={0}", _path); + else if (uri != null) + buff.AppendFormat ("; $Path={0}", uri.GetAbsolutePath ()); + else + buff.Append ("; $Path=/"); + + if (!_domain.IsNullOrEmpty ()) { + if (uri == null || uri.Host != _domain) + buff.AppendFormat ("; $Domain={0}", _domain); + } + + if (!_port.IsNullOrEmpty ()) { + if (_port != "\"\"") + buff.AppendFormat ("; $Port={0}", _port); + else + buff.Append ("; $Port"); + } + + return buff.ToString (); + } + + /// + /// Returns a string that represents the current cookie instance. + /// + /// + /// A that is suitable for the Set-Cookie response + /// header. + /// + internal string ToResponseString () + { + return _name.Length == 0 + ? String.Empty + : _version == 0 + ? toResponseStringVersion0 () + : toResponseStringVersion1 (); + } + + #endregion + + #region Public Methods + + /// + /// Determines whether the current cookie instance is equal to + /// the specified instance. + /// + /// + /// + /// An instance to compare with + /// the current cookie instance. + /// + /// + /// An reference to a instance. + /// + /// + /// + /// true if the current cookie instance is equal to + /// ; otherwise, false. + /// + public override bool Equals (object comparand) + { + var cookie = comparand as Cookie; + if (cookie == null) + return false; + + var caseSensitive = StringComparison.InvariantCulture; + var caseInsensitive = StringComparison.InvariantCultureIgnoreCase; + + return _name.Equals (cookie._name, caseInsensitive) + && _value.Equals (cookie._value, caseSensitive) + && _path.Equals (cookie._path, caseSensitive) + && _domain.Equals (cookie._domain, caseInsensitive) + && _version == cookie._version; + } + + /// + /// Gets a hash code for the current cookie instance. + /// + /// + /// An that represents the hash code. + /// + public override int GetHashCode () + { + return hash ( + StringComparer.InvariantCultureIgnoreCase.GetHashCode (_name), + _value.GetHashCode (), + _path.GetHashCode (), + StringComparer.InvariantCultureIgnoreCase.GetHashCode (_domain), + _version + ); + } + + /// + /// Returns a string that represents the current cookie instance. + /// + /// + /// A that is suitable for the Cookie request header. + /// + public override string ToString () + { + return ToRequestString (null); + } + + #endregion + } +} diff --git a/websocket-sharp/Net/CookieCollection.cs b/websocket-sharp/Net/CookieCollection.cs new file mode 100644 index 0000000..696d44d --- /dev/null +++ b/websocket-sharp/Net/CookieCollection.cs @@ -0,0 +1,745 @@ +#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-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 + * - Gonzalo Paniagua Javier + * - Sebastien Pouliot + */ +#endregion + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.Text; + +namespace WebSocketSharp.Net +{ + /// + /// Provides a collection of instances of the class. + /// + [Serializable] + public class CookieCollection : ICollection, IEnumerable + { + #region Private Fields + + private List _list; + private bool _readOnly; + private object _sync; + + #endregion + + #region Public Constructors + + /// + /// Initializes a new instance of the class. + /// + public CookieCollection () + { + _list = new List (); + _sync = ((ICollection) _list).SyncRoot; + } + + #endregion + + #region Internal Properties + + internal IList List { + get { + return _list; + } + } + + internal IEnumerable Sorted { + get { + var list = new List (_list); + if (list.Count > 1) + list.Sort (compareForSorted); + + return list; + } + } + + #endregion + + #region Public Properties + + /// + /// Gets the number of cookies in the collection. + /// + /// + /// An that represents the number of cookies in + /// the collection. + /// + public int Count { + get { + return _list.Count; + } + } + + /// + /// Gets a value indicating whether the collection is read-only. + /// + /// + /// + /// true if the collection is read-only; otherwise, false. + /// + /// + /// The default value is false. + /// + /// + public bool IsReadOnly { + get { + return _readOnly; + } + + internal set { + _readOnly = value; + } + } + + /// + /// Gets a value indicating whether the access to the collection is + /// thread safe. + /// + /// + /// + /// true if the access to the collection is thread safe; + /// otherwise, false. + /// + /// + /// The default value is false. + /// + /// + public bool IsSynchronized { + get { + return false; + } + } + + /// + /// Gets the cookie at the specified index from the collection. + /// + /// + /// A at the specified index in the collection. + /// + /// + /// An that specifies the zero-based index of the cookie + /// to find. + /// + /// + /// is out of allowable range for the collection. + /// + public Cookie this[int index] { + get { + if (index < 0 || index >= _list.Count) + throw new ArgumentOutOfRangeException ("index"); + + return _list[index]; + } + } + + /// + /// Gets the cookie with the specified name from the collection. + /// + /// + /// + /// A with the specified name in the collection. + /// + /// + /// if not found. + /// + /// + /// + /// A that specifies the name of the cookie to find. + /// + /// + /// is . + /// + public Cookie this[string name] { + get { + if (name == null) + throw new ArgumentNullException ("name"); + + var compType = StringComparison.InvariantCultureIgnoreCase; + + foreach (var cookie in Sorted) { + if (cookie.Name.Equals (name, compType)) + return cookie; + } + + return null; + } + } + + /// + /// Gets an object used to synchronize access to the collection. + /// + /// + /// An used to synchronize access to the collection. + /// + 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 compType = StringComparison.InvariantCultureIgnoreCase; + var ver = 0; + + 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", compType)) { + 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", compType)) { + ver = val.Length > 0 ? Int32.Parse (val.Unquote ()) : 0; + continue; + } + + if (name.Equals ("$path", compType)) { + if (cookie == null) + continue; + + if (val.Length == 0) + continue; + + cookie.Path = val; + continue; + } + + if (name.Equals ("$domain", compType)) { + if (cookie == null) + continue; + + if (val.Length == 0) + continue; + + cookie.Domain = val; + continue; + } + + if (name.Equals ("$port", compType)) { + if (cookie == null) + continue; + + if (val.Length == 0) + continue; + + cookie.Port = val; + continue; + } + + if (cookie != null) + ret.Add (cookie); + + cookie = new Cookie (name, val); + + 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 compType = 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", compType)) { + cookie.Port = "\"\""; + continue; + } + + if (pair.Equals ("discard", compType)) { + cookie.Discard = true; + continue; + } + + if (pair.Equals ("secure", compType)) { + cookie.Secure = true; + continue; + } + + if (pair.Equals ("httponly", compType)) { + 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", compType)) { + if (cookie == null) + continue; + + if (val.Length == 0) + continue; + + cookie.Version = Int32.Parse (val.Unquote ()); + continue; + } + + if (name.Equals ("expires", compType)) { + 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 + ) + ) + expires = DateTime.Now; + + cookie.Expires = expires.ToLocalTime (); + continue; + } + + if (name.Equals ("max-age", compType)) { + if (cookie == null) + continue; + + if (val.Length == 0) + continue; + + var max = Int32.Parse (val.Unquote ()); + var expires = DateTime.Now.AddSeconds ((double) max); + cookie.Expires = expires; + + continue; + } + + if (name.Equals ("path", compType)) { + if (cookie == null) + continue; + + if (val.Length == 0) + continue; + + cookie.Path = val; + continue; + } + + if (name.Equals ("domain", compType)) { + if (cookie == null) + continue; + + if (val.Length == 0) + continue; + + cookie.Domain = val; + continue; + } + + if (name.Equals ("port", compType)) { + if (cookie == null) + continue; + + if (val.Length == 0) + continue; + + cookie.Port = val; + continue; + } + + if (name.Equals ("comment", compType)) { + if (cookie == null) + continue; + + if (val.Length == 0) + continue; + + cookie.Comment = urlDecode (val, Encoding.UTF8); + continue; + } + + if (name.Equals ("commenturl", compType)) { + if (cookie == null) + continue; + + if (val.Length == 0) + continue; + + cookie.CommentUri = val.Unquote ().ToUri (); + continue; + } + + if (cookie != null) + ret.Add (cookie); + + cookie = new Cookie (name, val); + } + + 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 == null) + return null; + + 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) + { + return response + ? parseResponse (value) + : parseRequest (value); + } + + 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 (Cookie cookie in cookies) + SetOrRemove (cookie); + } + + internal void Sort () + { + if (_list.Count > 1) + _list.Sort (compareForSort); + } + + #endregion + + #region Public Methods + + /// + /// Adds the specified cookie to the collection. + /// + /// + /// A to add. + /// + /// + /// is . + /// + public void Add (Cookie cookie) + { + if (cookie == null) + throw new ArgumentNullException ("cookie"); + + add (cookie); + } + + /// + /// Adds the specified cookies to the collection. + /// + /// + /// A that contains the cookies to add. + /// + /// + /// is . + /// + public void Add (CookieCollection cookies) + { + if (cookies == null) + throw new ArgumentNullException ("cookies"); + + foreach (Cookie cookie in cookies) + add (cookie); + } + + /// + /// Copies the elements of the collection to the specified array, + /// starting at the specified index. + /// + /// + /// An that specifies the destination of + /// the elements copied from the collection. + /// + /// + /// An that specifies the zero-based index in + /// the array at which copying starts. + /// + /// + /// is . + /// + /// + /// is less than zero. + /// + /// + /// + /// is multidimensional. + /// + /// + /// -or- + /// + /// + /// The space from to the end of + /// is not enough to copy to. + /// + /// + /// + /// The element type of cannot be assigned. + /// + public void CopyTo (Array array, int index) + { + if (array == null) + throw new ArgumentNullException ("array"); + + if (index < 0) + throw new ArgumentOutOfRangeException ("index", "Less than zero."); + + if (array.Rank > 1) + throw new ArgumentException ("Multidimensional.", "array"); + + if (array.Length - index < _list.Count) { + var msg = "The available space of the array is not enough to copy to."; + throw new ArgumentException (msg); + } + + var elmType = array.GetType ().GetElementType (); + if (!elmType.IsAssignableFrom (typeof (Cookie))) { + var msg = "The element type of the array cannot be assigned."; + throw new InvalidCastException (msg); + } + + ((IList) _list).CopyTo (array, index); + } + + /// + /// Copies the elements of the collection to the specified array, + /// starting at the specified index. + /// + /// + /// An array of that specifies the destination of + /// the elements copied from the collection. + /// + /// + /// An that specifies the zero-based index in + /// the array at which copying starts. + /// + /// + /// is . + /// + /// + /// is less than zero. + /// + /// + /// The space from to the end of + /// is not enough to copy to. + /// + 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); + } + + /// + /// Gets the enumerator used to iterate through the collection. + /// + /// + /// An instance used to iterate through + /// the collection. + /// + public IEnumerator GetEnumerator () + { + return _list.GetEnumerator (); + } + + #endregion + } +} diff --git a/websocket-sharp/Net/CookieException.cs b/websocket-sharp/Net/CookieException.cs new file mode 100644 index 0000000..06123ae --- /dev/null +++ b/websocket-sharp/Net/CookieException.cs @@ -0,0 +1,143 @@ +#region License +/* + * CookieException.cs + * + * This code is derived from System.Net.CookieException.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 + */ +#endregion + +using System; +using System.Runtime.Serialization; +using System.Security.Permissions; + +namespace WebSocketSharp.Net +{ + /// + /// The exception that is thrown when a gets an error. + /// + [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 + + /// + /// Initializes a new instance of the class from + /// the specified and . + /// + /// + /// A that contains the serialized object data. + /// + /// + /// A that specifies the source for the deserialization. + /// + protected CookieException ( + SerializationInfo serializationInfo, StreamingContext streamingContext) + : base (serializationInfo, streamingContext) + { + } + + #endregion + + #region Public Constructors + + /// + /// Initializes a new instance of the class. + /// + public CookieException () + : base () + { + } + + #endregion + + #region Public Methods + + /// + /// Populates the specified with the data needed to serialize + /// the current . + /// + /// + /// A that holds the serialized object data. + /// + /// + /// A that specifies the destination for the serialization. + /// + [SecurityPermission ( + SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)] + public override void GetObjectData ( + SerializationInfo serializationInfo, StreamingContext streamingContext) + { + base.GetObjectData (serializationInfo, streamingContext); + } + + #endregion + + #region Explicit Interface Implementation + + /// + /// Populates the specified with the data needed to serialize + /// the current . + /// + /// + /// A that holds the serialized object data. + /// + /// + /// A that specifies the destination for the serialization. + /// + [SecurityPermission ( + SecurityAction.LinkDemand, + Flags = SecurityPermissionFlag.SerializationFormatter, + SerializationFormatter = true)] + void ISerializable.GetObjectData ( + SerializationInfo serializationInfo, StreamingContext streamingContext) + { + base.GetObjectData (serializationInfo, streamingContext); + } + + #endregion + } +} diff --git a/websocket-sharp/Net/EndPointListener.cs b/websocket-sharp/Net/EndPointListener.cs new file mode 100644 index 0000000..67fa263 --- /dev/null +++ b/websocket-sharp/Net/EndPointListener.cs @@ -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 + */ +#endregion + +#region Contributors +/* + * Contributors: + * - Liryna + * - 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 _all; // host == '+' + private static readonly string _defaultCertFolderPath; + private IPEndPoint _endpoint; + private Dictionary _prefixes; + private bool _secure; + private Socket _socket; + private ServerSslConfiguration _sslConfig; + private List _unhandled; // host == '*' + private Dictionary _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 (); + _unregistered = new Dictionary (); + _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 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 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 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 current, future; + if (prefix.Host == "*") { + do { + current = _unhandled; + future = current != null + ? new List (current) + : new List (); + + 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 (current) + : new List (); + + prefix.Listener = listener; + addSpecial (future, prefix); + } + while (Interlocked.CompareExchange (ref _all, future, current) != current); + + return; + } + + Dictionary 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 (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 current, future; + if (prefix.Host == "*") { + do { + current = _unhandled; + if (current == null) + break; + + future = new List (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 (current); + if (!removeSpecial (future, prefix)) + break; // The prefix wasn't found. + } + while (Interlocked.CompareExchange (ref _all, future, current) != current); + + leaveIfNoPrefix (); + return; + } + + Dictionary prefs, prefs2; + do { + prefs = _prefixes; + if (!prefs.ContainsKey (prefix)) + break; + + prefs2 = new Dictionary (prefs); + prefs2.Remove (prefix); + } + while (Interlocked.CompareExchange (ref _prefixes, prefs2, prefs) != prefs); + + leaveIfNoPrefix (); + } + + #endregion + } +} diff --git a/websocket-sharp/Net/EndPointManager.cs b/websocket-sharp/Net/EndPointManager.cs new file mode 100644 index 0000000..c12349d --- /dev/null +++ b/websocket-sharp/Net/EndPointManager.cs @@ -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 + */ +#endregion + +#region Contributors +/* + * Contributors: + * - Liryna + */ +#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 _endpoints; + + #endregion + + #region Static Constructor + + static EndPointManager () + { + _endpoints = new Dictionary (); + } + + #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 (); + 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 + } +} diff --git a/websocket-sharp/Net/HttpBasicIdentity.cs b/websocket-sharp/Net/HttpBasicIdentity.cs new file mode 100644 index 0000000..d26b29f --- /dev/null +++ b/websocket-sharp/Net/HttpBasicIdentity.cs @@ -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 + */ +#endregion + +using System; +using System.Security.Principal; + +namespace WebSocketSharp.Net +{ + /// + /// Holds the username and password from an HTTP Basic authentication attempt. + /// + 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 + + /// + /// Gets the password from a basic authentication attempt. + /// + /// + /// A that represents the password. + /// + public virtual string Password { + get { + return _password; + } + } + + #endregion + } +} diff --git a/websocket-sharp/Net/HttpConnection.cs b/websocket-sharp/Net/HttpConnection.cs new file mode 100644 index 0000000..572d785 --- /dev/null +++ b/websocket-sharp/Net/HttpConnection.cs @@ -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 + */ +#endregion + +#region Contributors +/* + * Contributors: + * - Liryna + * - Rohan Singh + */ +#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 _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 (); + _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 ("

{0} {1}", status, res.StatusDescription); + if (message != null && message.Length > 0) + content.AppendFormat (" ({0})

", message); + else + content.Append (""); + + 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 + } +} diff --git a/websocket-sharp/Net/HttpDigestIdentity.cs b/websocket-sharp/Net/HttpDigestIdentity.cs new file mode 100644 index 0000000..68ec86d --- /dev/null +++ b/websocket-sharp/Net/HttpDigestIdentity.cs @@ -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 +{ + /// + /// Holds the username and other parameters from + /// an HTTP Digest authentication attempt. + /// + 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 + + /// + /// Gets the algorithm parameter from a digest authentication attempt. + /// + /// + /// A that represents the algorithm parameter. + /// + public string Algorithm { + get { + return _parameters["algorithm"]; + } + } + + /// + /// Gets the cnonce parameter from a digest authentication attempt. + /// + /// + /// A that represents the cnonce parameter. + /// + public string Cnonce { + get { + return _parameters["cnonce"]; + } + } + + /// + /// Gets the nc parameter from a digest authentication attempt. + /// + /// + /// A that represents the nc parameter. + /// + public string Nc { + get { + return _parameters["nc"]; + } + } + + /// + /// Gets the nonce parameter from a digest authentication attempt. + /// + /// + /// A that represents the nonce parameter. + /// + public string Nonce { + get { + return _parameters["nonce"]; + } + } + + /// + /// Gets the opaque parameter from a digest authentication attempt. + /// + /// + /// A that represents the opaque parameter. + /// + public string Opaque { + get { + return _parameters["opaque"]; + } + } + + /// + /// Gets the qop parameter from a digest authentication attempt. + /// + /// + /// A that represents the qop parameter. + /// + public string Qop { + get { + return _parameters["qop"]; + } + } + + /// + /// Gets the realm parameter from a digest authentication attempt. + /// + /// + /// A that represents the realm parameter. + /// + public string Realm { + get { + return _parameters["realm"]; + } + } + + /// + /// Gets the response parameter from a digest authentication attempt. + /// + /// + /// A that represents the response parameter. + /// + public string Response { + get { + return _parameters["response"]; + } + } + + /// + /// Gets the uri parameter from a digest authentication attempt. + /// + /// + /// A that represents the uri parameter. + /// + 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 + } +} diff --git a/websocket-sharp/Net/HttpHeaderInfo.cs b/websocket-sharp/Net/HttpHeaderInfo.cs new file mode 100644 index 0000000..717f8f4 --- /dev/null +++ b/websocket-sharp/Net/HttpHeaderInfo.cs @@ -0,0 +1,114 @@ +#region License +/* + * HttpHeaderInfo.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 +{ + internal class HttpHeaderInfo + { + #region Private Fields + + private string _name; + private HttpHeaderType _type; + + #endregion + + #region Internal Constructors + + internal HttpHeaderInfo (string name, HttpHeaderType type) + { + _name = name; + _type = type; + } + + #endregion + + #region Internal Properties + + internal bool IsMultiValueInRequest { + get { + return (_type & HttpHeaderType.MultiValueInRequest) == HttpHeaderType.MultiValueInRequest; + } + } + + internal bool IsMultiValueInResponse { + get { + return (_type & HttpHeaderType.MultiValueInResponse) == HttpHeaderType.MultiValueInResponse; + } + } + + #endregion + + #region Public Properties + + public bool IsRequest { + get { + return (_type & HttpHeaderType.Request) == HttpHeaderType.Request; + } + } + + public bool IsResponse { + get { + return (_type & HttpHeaderType.Response) == HttpHeaderType.Response; + } + } + + public string Name { + get { + return _name; + } + } + + public HttpHeaderType Type { + get { + return _type; + } + } + + #endregion + + #region Public Methods + + public bool IsMultiValue (bool response) + { + return (_type & HttpHeaderType.MultiValue) == HttpHeaderType.MultiValue + ? (response ? IsResponse : IsRequest) + : (response ? IsMultiValueInResponse : IsMultiValueInRequest); + } + + public bool IsRestricted (bool response) + { + return (_type & HttpHeaderType.Restricted) == HttpHeaderType.Restricted + ? (response ? IsResponse : IsRequest) + : false; + } + + #endregion + } +} diff --git a/websocket-sharp/Net/HttpHeaderType.cs b/websocket-sharp/Net/HttpHeaderType.cs new file mode 100644 index 0000000..113fb63 --- /dev/null +++ b/websocket-sharp/Net/HttpHeaderType.cs @@ -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 + } +} diff --git a/websocket-sharp/Net/HttpListener.cs b/websocket-sharp/Net/HttpListener.cs new file mode 100644 index 0000000..07970e1 --- /dev/null +++ b/websocket-sharp/Net/HttpListener.cs @@ -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 + */ +#endregion + +#region Contributors +/* + * Contributors: + * - Liryna + */ +#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 +{ + /// + /// Provides a simple, programmatically controlled HTTP listener. + /// + public sealed class HttpListener : IDisposable + { + #region Private Fields + + private AuthenticationSchemes _authSchemes; + private Func _authSchemeSelector; + private string _certFolderPath; + private Dictionary _connections; + private object _connectionsSync; + private List _ctxQueue; + private object _ctxQueueSync; + private Dictionary _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 _userCredFinder; + private List _waitQueue; + private object _waitQueueSync; + + #endregion + + #region Static Constructor + + static HttpListener () + { + _defaultRealm = "SECRET AREA"; + } + + #endregion + + #region Public Constructors + + /// + /// Initializes a new instance of the class. + /// + public HttpListener () + { + _authSchemes = AuthenticationSchemes.Anonymous; + + _connections = new Dictionary (); + _connectionsSync = ((ICollection) _connections).SyncRoot; + + _ctxQueue = new List (); + _ctxQueueSync = ((ICollection) _ctxQueue).SyncRoot; + + _ctxRegistry = new Dictionary (); + _ctxRegistrySync = ((ICollection) _ctxRegistry).SyncRoot; + + _logger = new Logger (); + + _prefixes = new HttpListenerPrefixCollection (this); + + _waitQueue = new List (); + _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 + + /// + /// Gets or sets the scheme used to authenticate the clients. + /// + /// + /// One of the enum values, + /// represents the scheme used to authenticate the clients. The default value is + /// . + /// + /// + /// This listener has been closed. + /// + public AuthenticationSchemes AuthenticationSchemes { + get { + CheckDisposed (); + return _authSchemes; + } + + set { + CheckDisposed (); + _authSchemes = value; + } + } + + /// + /// Gets or sets the delegate called to select the scheme used to authenticate the clients. + /// + /// + /// 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 property as the authentication + /// scheme for all requests. + /// + /// + /// A Func<, > + /// delegate that references the method used to select an authentication scheme. The default + /// value is . + /// + /// + /// This listener has been closed. + /// + public Func AuthenticationSchemeSelector { + get { + CheckDisposed (); + return _authSchemeSelector; + } + + set { + CheckDisposed (); + _authSchemeSelector = value; + } + } + + /// + /// Gets or sets the path to the folder in which stores the certificate files used to + /// authenticate the server on the secure connection. + /// + /// + /// + /// 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 'port number'.cer (DER) and 'port number'.key + /// (DER, RSA Private Key). + /// + /// + /// If this property is or empty, the result of + /// System.Environment.GetFolderPath + /// () is used as the default path. + /// + /// + /// + /// A that represents the path to the folder in which stores + /// the certificate files. The default value is . + /// + /// + /// This listener has been closed. + /// + public string CertificateFolderPath { + get { + CheckDisposed (); + return _certFolderPath; + } + + set { + CheckDisposed (); + _certFolderPath = value; + } + } + + /// + /// Gets or sets a value indicating whether the listener returns exceptions that occur when + /// sending the response to the client. + /// + /// + /// true if the listener shouldn't return those exceptions; otherwise, false. + /// The default value is false. + /// + /// + /// This listener has been closed. + /// + public bool IgnoreWriteExceptions { + get { + CheckDisposed (); + return _ignoreWriteExceptions; + } + + set { + CheckDisposed (); + _ignoreWriteExceptions = value; + } + } + + /// + /// Gets a value indicating whether the listener has been started. + /// + /// + /// true if the listener has been started; otherwise, false. + /// + public bool IsListening { + get { + return _listening; + } + } + + /// + /// Gets a value indicating whether the listener can be used with the current operating system. + /// + /// + /// true. + /// + public static bool IsSupported { + get { + return true; + } + } + + /// + /// Gets the logging functions. + /// + /// + /// The default logging level is . If you would like to change it, + /// you should set the Log.Level property to any of the enum + /// values. + /// + /// + /// A that provides the logging functions. + /// + public Logger Log { + get { + return _logger; + } + } + + /// + /// Gets the URI prefixes handled by the listener. + /// + /// + /// A that contains the URI prefixes. + /// + /// + /// This listener has been closed. + /// + public HttpListenerPrefixCollection Prefixes { + get { + CheckDisposed (); + return _prefixes; + } + } + + /// + /// Gets or sets the name of the realm associated with the listener. + /// + /// + /// If this property is or empty, "SECRET AREA" will be used as + /// the name of the realm. + /// + /// + /// A that represents the name of the realm. The default value is + /// . + /// + /// + /// This listener has been closed. + /// + public string Realm { + get { + CheckDisposed (); + return _realm; + } + + set { + CheckDisposed (); + _realm = value; + } + } + + /// + /// Gets or sets the SSL configuration used to authenticate the server and + /// optionally the client for secure connection. + /// + /// + /// A that represents the configuration used to + /// authenticate the server and optionally the client for secure connection. + /// + /// + /// This listener has been closed. + /// + public ServerSslConfiguration SslConfiguration { + get { + CheckDisposed (); + return _sslConfig ?? (_sslConfig = new ServerSslConfiguration ()); + } + + set { + CheckDisposed (); + _sslConfig = value; + } + } + + /// + /// 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. + /// + /// + /// This property isn't currently supported and always throws + /// a . + /// + /// + /// true if the authentication information of first request is used; + /// otherwise, false. + /// + /// + /// Any use of this property. + /// + public bool UnsafeConnectionNtlmAuthentication { + get { + throw new NotSupportedException (); + } + + set { + throw new NotSupportedException (); + } + } + + /// + /// Gets or sets the delegate called to find the credentials for an identity used to + /// authenticate a client. + /// + /// + /// A Func<, > delegate + /// that references the method used to find the credentials. The default value is + /// . + /// + /// + /// This listener has been closed. + /// + public Func 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 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 + + /// + /// Shuts down the listener immediately. + /// + public void Abort () + { + if (_disposed) + return; + + close (true); + } + + /// + /// Begins getting an incoming request asynchronously. + /// + /// + /// This asynchronous operation must be completed by calling the EndGetContext method. + /// Typically, the method is invoked by the delegate. + /// + /// + /// An that represents the status of the asynchronous operation. + /// + /// + /// An delegate that references the method to invoke when + /// the asynchronous operation completes. + /// + /// + /// An that represents a user defined object to pass to + /// the delegate. + /// + /// + /// + /// This listener has no URI prefix on which listens. + /// + /// + /// -or- + /// + /// + /// This listener hasn't been started, or is currently stopped. + /// + /// + /// + /// This listener has been closed. + /// + 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)); + } + + /// + /// Shuts down the listener. + /// + public void Close () + { + if (_disposed) + return; + + close (false); + } + + /// + /// Ends an asynchronous operation to get an incoming request. + /// + /// + /// This method completes an asynchronous operation started by calling + /// the BeginGetContext method. + /// + /// + /// A that represents a request. + /// + /// + /// An obtained by calling the BeginGetContext method. + /// + /// + /// is . + /// + /// + /// wasn't obtained by calling the BeginGetContext method. + /// + /// + /// This method was already called for the specified . + /// + /// + /// This listener has been closed. + /// + 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. + } + + /// + /// Gets an incoming request. + /// + /// + /// This method waits for an incoming request, and returns when a request is received. + /// + /// + /// A that represents a request. + /// + /// + /// + /// This listener has no URI prefix on which listens. + /// + /// + /// -or- + /// + /// + /// This listener hasn't been started, or is currently stopped. + /// + /// + /// + /// This listener has been closed. + /// + 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); + } + + /// + /// Starts receiving incoming requests. + /// + /// + /// This listener has been closed. + /// + public void Start () + { + CheckDisposed (); + if (_listening) + return; + + EndPointManager.AddListener (this); + _listening = true; + } + + /// + /// Stops receiving incoming requests. + /// + /// + /// This listener has been closed. + /// + 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 + + /// + /// Releases all resources used by the listener. + /// + void IDisposable.Dispose () + { + if (_disposed) + return; + + close (true); + } + + #endregion + } +} diff --git a/websocket-sharp/Net/HttpListenerAsyncResult.cs b/websocket-sharp/Net/HttpListenerAsyncResult.cs new file mode 100644 index 0000000..a1c7374 --- /dev/null +++ b/websocket-sharp/Net/HttpListenerAsyncResult.cs @@ -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 + */ +#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 + } +} diff --git a/websocket-sharp/Net/HttpListenerContext.cs b/websocket-sharp/Net/HttpListenerContext.cs new file mode 100644 index 0000000..638078d --- /dev/null +++ b/websocket-sharp/Net/HttpListenerContext.cs @@ -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 + */ +#endregion + +using System; +using System.Security.Principal; +using WebSocketSharp.Net.WebSockets; + +namespace WebSocketSharp.Net +{ + /// + /// Provides the access to the HTTP request and response objects used by + /// the . + /// + /// + /// This class cannot be inherited. + /// + 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 + + /// + /// Gets the HTTP request object that represents a client request. + /// + /// + /// A that represents the client request. + /// + public HttpListenerRequest Request { + get { + return _request; + } + } + + /// + /// Gets the HTTP response object used to send a response to the client. + /// + /// + /// A that represents a response to the client request. + /// + public HttpListenerResponse Response { + get { + return _response; + } + } + + /// + /// Gets the client information (identity, authentication, and security roles). + /// + /// + /// A instance that represents the client information. + /// + 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 + + /// + /// Accepts a WebSocket handshake request. + /// + /// + /// A that represents + /// the WebSocket handshake request. + /// + /// + /// A that represents the subprotocol supported on + /// this WebSocket connection. + /// + /// + /// + /// is empty. + /// + /// + /// -or- + /// + /// + /// contains an invalid character. + /// + /// + /// + /// This method has already been called. + /// + 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 + } +} diff --git a/websocket-sharp/Net/HttpListenerException.cs b/websocket-sharp/Net/HttpListenerException.cs new file mode 100644 index 0000000..a52eeec --- /dev/null +++ b/websocket-sharp/Net/HttpListenerException.cs @@ -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 + */ +#endregion + +using System; +using System.ComponentModel; +using System.Runtime.Serialization; + +namespace WebSocketSharp.Net +{ + /// + /// The exception that is thrown when a gets an error + /// processing an HTTP request. + /// + [Serializable] + public class HttpListenerException : Win32Exception + { + #region Protected Constructors + + /// + /// Initializes a new instance of the class from + /// the specified and . + /// + /// + /// A that contains the serialized object data. + /// + /// + /// A that specifies the source for the deserialization. + /// + protected HttpListenerException ( + SerializationInfo serializationInfo, StreamingContext streamingContext) + : base (serializationInfo, streamingContext) + { + } + + #endregion + + #region Public Constructors + + /// + /// Initializes a new instance of the class. + /// + public HttpListenerException () + { + } + + /// + /// Initializes a new instance of the class + /// with the specified . + /// + /// + /// An that identifies the error. + /// + public HttpListenerException (int errorCode) + : base (errorCode) + { + } + + /// + /// Initializes a new instance of the class + /// with the specified and . + /// + /// + /// An that identifies the error. + /// + /// + /// A that describes the error. + /// + public HttpListenerException (int errorCode, string message) + : base (errorCode, message) + { + } + + #endregion + + #region Public Properties + + /// + /// Gets the error code that identifies the error that occurred. + /// + /// + /// An that identifies the error. + /// + public override int ErrorCode { + get { + return NativeErrorCode; + } + } + + #endregion + } +} diff --git a/websocket-sharp/Net/HttpListenerPrefix.cs b/websocket-sharp/Net/HttpListenerPrefix.cs new file mode 100644 index 0000000..960d02e --- /dev/null +++ b/websocket-sharp/Net/HttpListenerPrefix.cs @@ -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 + * - Oleg Mihailik + */ +#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 + + /// + /// Initializes a new instance of the class with + /// the specified . + /// + /// + /// This constructor must be called after calling the CheckPrefix method. + /// + /// + /// A that represents the URI prefix. + /// + 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"); + } + + /// + /// Determines whether this instance and the specified have the same value. + /// + /// + /// This method will be required to detect duplicates in any collection. + /// + /// + /// An to compare to this instance. + /// + /// + /// true if is a and + /// its value is the same as this instance; otherwise, false. + /// + public override bool Equals (Object obj) + { + var pref = obj as HttpListenerPrefix; + return pref != null && pref._prefix == _prefix; + } + + /// + /// Gets the hash code for this instance. + /// + /// + /// This method will be required to detect duplicates in any collection. + /// + /// + /// An that represents the hash code. + /// + public override int GetHashCode () + { + return _prefix.GetHashCode (); + } + + public override string ToString () + { + return _prefix; + } + + #endregion + } +} diff --git a/websocket-sharp/Net/HttpListenerPrefixCollection.cs b/websocket-sharp/Net/HttpListenerPrefixCollection.cs new file mode 100644 index 0000000..6373b8d --- /dev/null +++ b/websocket-sharp/Net/HttpListenerPrefixCollection.cs @@ -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 + */ +#endregion + +using System; +using System.Collections; +using System.Collections.Generic; + +namespace WebSocketSharp.Net +{ + /// + /// Provides the collection used to store the URI prefixes for the . + /// + /// + /// The responds to the request which has a requested URI that + /// the prefixes most closely match. + /// + public class HttpListenerPrefixCollection : ICollection, IEnumerable, IEnumerable + { + #region Private Fields + + private HttpListener _listener; + private List _prefixes; + + #endregion + + #region Internal Constructors + + internal HttpListenerPrefixCollection (HttpListener listener) + { + _listener = listener; + _prefixes = new List (); + } + + #endregion + + #region Public Properties + + /// + /// Gets the number of prefixes in the collection. + /// + /// + /// An that represents the number of prefixes. + /// + public int Count { + get { + return _prefixes.Count; + } + } + + /// + /// Gets a value indicating whether the access to the collection is read-only. + /// + /// + /// Always returns false. + /// + public bool IsReadOnly { + get { + return false; + } + } + + /// + /// Gets a value indicating whether the access to the collection is synchronized. + /// + /// + /// Always returns false. + /// + public bool IsSynchronized { + get { + return false; + } + } + + #endregion + + #region Public Methods + + /// + /// Adds the specified to the collection. + /// + /// + /// A 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 '/'. + /// + /// + /// is . + /// + /// + /// is invalid. + /// + /// + /// The associated with this collection is closed. + /// + 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); + } + + /// + /// Removes all URI prefixes from the collection. + /// + /// + /// The associated with this collection is closed. + /// + public void Clear () + { + _listener.CheckDisposed (); + _prefixes.Clear (); + if (_listener.IsListening) + EndPointManager.RemoveListener (_listener); + } + + /// + /// Returns a value indicating whether the collection contains the specified + /// . + /// + /// + /// true if the collection contains ; + /// otherwise, false. + /// + /// + /// A that represents the URI prefix to test. + /// + /// + /// is . + /// + /// + /// The associated with this collection is closed. + /// + public bool Contains (string uriPrefix) + { + _listener.CheckDisposed (); + if (uriPrefix == null) + throw new ArgumentNullException ("uriPrefix"); + + return _prefixes.Contains (uriPrefix); + } + + /// + /// Copies the contents of the collection to the specified . + /// + /// + /// An that receives the URI prefix strings in the collection. + /// + /// + /// An that represents the zero-based index in + /// at which copying begins. + /// + /// + /// The associated with this collection is closed. + /// + public void CopyTo (Array array, int offset) + { + _listener.CheckDisposed (); + ((ICollection) _prefixes).CopyTo (array, offset); + } + + /// + /// Copies the contents of the collection to the specified array of . + /// + /// + /// An array of that receives the URI prefix strings in the collection. + /// + /// + /// An that represents the zero-based index in + /// at which copying begins. + /// + /// + /// The associated with this collection is closed. + /// + public void CopyTo (string[] array, int offset) + { + _listener.CheckDisposed (); + _prefixes.CopyTo (array, offset); + } + + /// + /// Gets the enumerator used to iterate through the . + /// + /// + /// An instance used to iterate + /// through the collection. + /// + public IEnumerator GetEnumerator () + { + return _prefixes.GetEnumerator (); + } + + /// + /// Removes the specified from the collection. + /// + /// + /// true if is successfully found and removed; + /// otherwise, false. + /// + /// + /// A that represents the URI prefix to remove. + /// + /// + /// is . + /// + /// + /// The associated with this collection is closed. + /// + 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 + + /// + /// Gets the enumerator used to iterate through the . + /// + /// + /// An instance used to iterate through the collection. + /// + IEnumerator IEnumerable.GetEnumerator () + { + return _prefixes.GetEnumerator (); + } + + #endregion + } +} diff --git a/websocket-sharp/Net/HttpListenerRequest.cs b/websocket-sharp/Net/HttpListenerRequest.cs new file mode 100644 index 0000000..953c9b9 --- /dev/null +++ b/websocket-sharp/Net/HttpListenerRequest.cs @@ -0,0 +1,910 @@ +#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 + */ +#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 +{ + /// + /// Represents an incoming request to a instance. + /// + /// + /// This class cannot be inherited. + /// + 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 + + /// + /// Gets the media types that are acceptable for the client. + /// + /// + /// + /// An array of that contains the names of the media + /// types specified in the value of the Accept header. + /// + /// + /// if the header is not present. + /// + /// + public string[] AcceptTypes { + get { + var val = _headers["Accept"]; + if (val == null) + return null; + + if (_acceptTypes == null) { + _acceptTypes = val + .SplitHeaderValue (',') + .Trim () + .ToList () + .ToArray (); + } + + return _acceptTypes; + } + } + + /// + /// Gets an error code that identifies a problem with the certificate + /// provided by the client. + /// + /// + /// An that represents an error code. + /// + /// + /// This property is not supported. + /// + public int ClientCertificateError { + get { + throw new NotSupportedException (); + } + } + + /// + /// Gets the encoding for the entity body data included in the request. + /// + /// + /// + /// A converted from the charset value of the + /// Content-Type header. + /// + /// + /// if the charset value is not available. + /// + /// + public Encoding ContentEncoding { + get { + if (_contentEncoding == null) + _contentEncoding = getContentEncoding () ?? Encoding.UTF8; + + return _contentEncoding; + } + } + + /// + /// Gets the length in bytes of the entity body data included in the + /// request. + /// + /// + /// + /// A converted from the value of the Content-Length + /// header. + /// + /// + /// -1 if the header is not present. + /// + /// + public long ContentLength64 { + get { + return _contentLength; + } + } + + /// + /// Gets the media type of the entity body data included in the request. + /// + /// + /// + /// A that represents the value of the Content-Type + /// header. + /// + /// + /// if the header is not present. + /// + /// + public string ContentType { + get { + return _headers["Content-Type"]; + } + } + + /// + /// Gets the cookies included in the request. + /// + /// + /// + /// A that contains the cookies. + /// + /// + /// An empty collection if not included. + /// + /// + public CookieCollection Cookies { + get { + if (_cookies == null) + _cookies = _headers.GetCookies (false); + + return _cookies; + } + } + + /// + /// Gets a value indicating whether the request has the entity body data. + /// + /// + /// true if the request has the entity body data; otherwise, + /// false. + /// + public bool HasEntityBody { + get { + return _contentLength > 0 || _chunked; + } + } + + /// + /// Gets the headers included in the request. + /// + /// + /// A that contains the headers. + /// + public NameValueCollection Headers { + get { + return _headers; + } + } + + /// + /// Gets the HTTP method specified by the client. + /// + /// + /// A that represents the HTTP method specified in + /// the request line. + /// + public string HttpMethod { + get { + return _httpMethod; + } + } + + /// + /// Gets a stream that contains the entity body data included in + /// the request. + /// + /// + /// + /// A that contains the entity body data. + /// + /// + /// if the entity body data is not available. + /// + /// + public Stream InputStream { + get { + if (_inputStream == null) + _inputStream = getInputStream () ?? Stream.Null; + + return _inputStream; + } + } + + /// + /// Gets a value indicating whether the client is authenticated. + /// + /// + /// true if the client is authenticated; otherwise, false. + /// + public bool IsAuthenticated { + get { + return _context.User != null; + } + } + + /// + /// Gets a value indicating whether the request is sent from the local + /// computer. + /// + /// + /// true if the request is sent from the same computer as the server; + /// otherwise, false. + /// + public bool IsLocal { + get { + return _connection.IsLocal; + } + } + + /// + /// Gets a value indicating whether a secure connection is used to send + /// the request. + /// + /// + /// true if the connection is secure; otherwise, false. + /// + public bool IsSecureConnection { + get { + return _connection.IsSecure; + } + } + + /// + /// Gets a value indicating whether the request is a WebSocket handshake + /// request. + /// + /// + /// true if the request is a WebSocket handshake request; otherwise, + /// false. + /// + public bool IsWebSocketRequest { + get { + return _httpMethod == "GET" + && _protocolVersion > HttpVersion.Version10 + && _headers.Upgrades ("websocket"); + } + } + + /// + /// Gets a value indicating whether a persistent connection is requested. + /// + /// + /// true if the request specifies that the connection is kept open; + /// otherwise, false. + /// + public bool KeepAlive { + get { + return _headers.KeepsAlive (_protocolVersion); + } + } + + /// + /// Gets the endpoint to which the request is sent. + /// + /// + /// A that represents the server IP + /// address and port number. + /// + public System.Net.IPEndPoint LocalEndPoint { + get { + return _connection.LocalEndPoint; + } + } + + /// + /// Gets the HTTP version specified by the client. + /// + /// + /// A that represents the HTTP version specified in + /// the request line. + /// + public Version ProtocolVersion { + get { + return _protocolVersion; + } + } + + /// + /// Gets the query string included in the request. + /// + /// + /// + /// A that contains the query + /// parameters. + /// + /// + /// An empty collection if not included. + /// + /// + public NameValueCollection QueryString { + get { + if (_queryString == null) { + var url = Url; + _queryString = QueryStringCollection.Parse ( + url != null ? url.Query : null, + Encoding.UTF8 + ); + } + + return _queryString; + } + } + + /// + /// Gets the raw URL specified by the client. + /// + /// + /// A that represents the request target specified in + /// the request line. + /// + public string RawUrl { + get { + return _rawUrl; + } + } + + /// + /// Gets the endpoint from which the request is sent. + /// + /// + /// A that represents the client IP + /// address and port number. + /// + public System.Net.IPEndPoint RemoteEndPoint { + get { + return _connection.RemoteEndPoint; + } + } + + /// + /// Gets the trace identifier of the request. + /// + /// + /// A that represents the trace identifier. + /// + public Guid RequestTraceIdentifier { + get { + return _requestTraceIdentifier; + } + } + + /// + /// Gets the URL requested by the client. + /// + /// + /// + /// A that represents the URL parsed from the request. + /// + /// + /// if the URL cannot be parsed. + /// + /// + public Uri Url { + get { + if (!_urlSet) { + _url = HttpUtility.CreateRequestUrl ( + _rawUrl, + _userHostName ?? UserHostAddress, + IsWebSocketRequest, + IsSecureConnection + ); + + _urlSet = true; + } + + return _url; + } + } + + /// + /// Gets the URI of the resource from which the requested URL was obtained. + /// + /// + /// + /// A converted from the value of the Referer header. + /// + /// + /// if the header value is not available. + /// + /// + public Uri UrlReferrer { + get { + var val = _headers["Referer"]; + if (val == null) + return null; + + if (_urlReferrer == null) + _urlReferrer = val.ToUri (); + + return _urlReferrer; + } + } + + /// + /// Gets the user agent from which the request is originated. + /// + /// + /// + /// A that represents the value of the User-Agent + /// header. + /// + /// + /// if the header is not present. + /// + /// + public string UserAgent { + get { + return _headers["User-Agent"]; + } + } + + /// + /// Gets the IP address and port number to which the request is sent. + /// + /// + /// A that represents the server IP address and port + /// number. + /// + public string UserHostAddress { + get { + return _connection.LocalEndPoint.ToString (); + } + } + + /// + /// Gets the server host name requested by the client. + /// + /// + /// + /// A that represents the value of the Host header. + /// + /// + /// It includes the port number if provided. + /// + /// + /// if the header is not present. + /// + /// + public string UserHostName { + get { + return _userHostName; + } + } + + /// + /// Gets the natural languages that are acceptable for the client. + /// + /// + /// + /// An array of that contains the names of the + /// natural languages specified in the value of the Accept-Language + /// header. + /// + /// + /// if the header is not present. + /// + /// + 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 + + /// + /// Begins getting the certificate provided by the client asynchronously. + /// + /// + /// An instance that indicates the status of the + /// operation. + /// + /// + /// An delegate that invokes the method called + /// when the operation is complete. + /// + /// + /// An that represents a user defined object to pass to + /// the callback delegate. + /// + /// + /// This method is not supported. + /// + public IAsyncResult BeginGetClientCertificate ( + AsyncCallback requestCallback, object state + ) + { + throw new NotSupportedException (); + } + + /// + /// Ends an asynchronous operation to get the certificate provided by the + /// client. + /// + /// + /// A that represents an X.509 certificate + /// provided by the client. + /// + /// + /// An instance returned when the operation + /// started. + /// + /// + /// This method is not supported. + /// + public X509Certificate2 EndGetClientCertificate (IAsyncResult asyncResult) + { + throw new NotSupportedException (); + } + + /// + /// Gets the certificate provided by the client. + /// + /// + /// A that represents an X.509 certificate + /// provided by the client. + /// + /// + /// This method is not supported. + /// + public X509Certificate2 GetClientCertificate () + { + throw new NotSupportedException (); + } + + /// + /// Returns a string that represents the current instance. + /// + /// + /// A that contains the request line and headers + /// included in the request. + /// + 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 + } +} diff --git a/websocket-sharp/Net/HttpListenerResponse.cs b/websocket-sharp/Net/HttpListenerResponse.cs new file mode 100644 index 0000000..92c0a79 --- /dev/null +++ b/websocket-sharp/Net/HttpListenerResponse.cs @@ -0,0 +1,846 @@ +#region License +/* + * HttpListenerResponse.cs + * + * This code is derived from HttpListenerResponse.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 + */ +#endregion + +#region Contributors +/* + * Contributors: + * - Nicholas Devenish + */ +#endregion + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Text; + +namespace WebSocketSharp.Net +{ + /// + /// Provides the access to a response to a request received by the . + /// + /// + /// The HttpListenerResponse class cannot be inherited. + /// + public sealed class HttpListenerResponse : IDisposable + { + #region Private Fields + + private bool _closeConnection; + private Encoding _contentEncoding; + private long _contentLength; + private string _contentType; + private HttpListenerContext _context; + private CookieCollection _cookies; + private bool _disposed; + private WebHeaderCollection _headers; + private bool _headersSent; + private bool _keepAlive; + private string _location; + private ResponseStream _outputStream; + private bool _sendChunked; + private int _statusCode; + private string _statusDescription; + private Version _version; + + #endregion + + #region Internal Constructors + + internal HttpListenerResponse (HttpListenerContext context) + { + _context = context; + _keepAlive = true; + _statusCode = 200; + _statusDescription = "OK"; + _version = HttpVersion.Version11; + } + + #endregion + + #region Internal Properties + + internal bool CloseConnection { + get { + return _closeConnection; + } + + set { + _closeConnection = value; + } + } + + internal bool HeadersSent { + get { + return _headersSent; + } + + set { + _headersSent = value; + } + } + + #endregion + + #region Public Properties + + /// + /// Gets or sets the encoding for the entity body data included in the response. + /// + /// + /// A that represents the encoding for the entity body data, + /// or if no encoding is specified. + /// + /// + /// This object is closed. + /// + public Encoding ContentEncoding { + get { + return _contentEncoding; + } + + set { + checkDisposed (); + _contentEncoding = value; + } + } + + /// + /// Gets or sets the number of bytes in the entity body data included in the response. + /// + /// + /// A that represents the value of the Content-Length entity-header. + /// + /// + /// The value specified for a set operation is less than zero. + /// + /// + /// The response has already been sent. + /// + /// + /// This object is closed. + /// + public long ContentLength64 { + get { + return _contentLength; + } + + set { + checkDisposedOrHeadersSent (); + if (value < 0) + throw new ArgumentOutOfRangeException ("Less than zero.", "value"); + + _contentLength = value; + } + } + + /// + /// Gets or sets the media type of the entity body included in the response. + /// + /// + /// A that represents the media type of the entity body, + /// or if no media type is specified. This value is + /// used for the value of the Content-Type entity-header. + /// + /// + /// The value specified for a set operation is empty. + /// + /// + /// This object is closed. + /// + public string ContentType { + get { + return _contentType; + } + + set { + checkDisposed (); + if (value != null && value.Length == 0) + throw new ArgumentException ("An empty string.", "value"); + + _contentType = value; + } + } + + /// + /// Gets or sets the cookies sent with the response. + /// + /// + /// A that contains the cookies sent with the response. + /// + public CookieCollection Cookies { + get { + return _cookies ?? (_cookies = new CookieCollection ()); + } + + set { + _cookies = value; + } + } + + /// + /// Gets or sets the HTTP headers sent to the client. + /// + /// + /// A that contains the headers sent to the client. + /// + /// + /// The value specified for a set operation isn't valid for a response. + /// + public WebHeaderCollection Headers { + get { + return _headers ?? (_headers = new WebHeaderCollection (HttpHeaderType.Response, false)); + } + + set { + if (value != null && value.State != HttpHeaderType.Response) + throw new InvalidOperationException ( + "The specified headers aren't valid for a response."); + + _headers = value; + } + } + + /// + /// Gets or sets a value indicating whether the server requests a persistent connection. + /// + /// + /// true if the server requests a persistent connection; otherwise, false. + /// The default value is true. + /// + /// + /// The response has already been sent. + /// + /// + /// This object is closed. + /// + public bool KeepAlive { + get { + return _keepAlive; + } + + set { + checkDisposedOrHeadersSent (); + _keepAlive = value; + } + } + + /// + /// Gets a to use to write the entity body data. + /// + /// + /// A to use to write the entity body data. + /// + /// + /// This object is closed. + /// + public Stream OutputStream { + get { + checkDisposed (); + return _outputStream ?? (_outputStream = _context.Connection.GetResponseStream ()); + } + } + + /// + /// Gets or sets the HTTP version used in the response. + /// + /// + /// A that represents the version used in the response. + /// + /// + /// The value specified for a set operation is . + /// + /// + /// The value specified for a set operation doesn't have its Major property set to 1 or + /// doesn't have its Minor property set to either 0 or 1. + /// + /// + /// The response has already been sent. + /// + /// + /// This object is closed. + /// + public Version ProtocolVersion { + get { + return _version; + } + + set { + checkDisposedOrHeadersSent (); + if (value == null) + throw new ArgumentNullException ("value"); + + if (value.Major != 1 || (value.Minor != 0 && value.Minor != 1)) + throw new ArgumentException ("Not 1.0 or 1.1.", "value"); + + _version = value; + } + } + + /// + /// Gets or sets the URL to which the client is redirected to locate a requested resource. + /// + /// + /// A that represents the value of the Location response-header, + /// or if no redirect location is specified. + /// + /// + /// The value specified for a set operation isn't an absolute URL. + /// + /// + /// This object is closed. + /// + public string RedirectLocation { + get { + return _location; + } + + set { + checkDisposed (); + if (value == null) { + _location = null; + return; + } + + Uri uri = null; + if (!value.MaybeUri () || !Uri.TryCreate (value, UriKind.Absolute, out uri)) + throw new ArgumentException ("Not an absolute URL.", "value"); + + _location = value; + } + } + + /// + /// Gets or sets a value indicating whether the response uses the chunked transfer encoding. + /// + /// + /// true if the response uses the chunked transfer encoding; + /// otherwise, false. The default value is false. + /// + /// + /// The response has already been sent. + /// + /// + /// This object is closed. + /// + public bool SendChunked { + get { + return _sendChunked; + } + + set { + checkDisposedOrHeadersSent (); + _sendChunked = value; + } + } + + /// + /// Gets or sets the HTTP status code returned to the client. + /// + /// + /// An that represents the status code for the response to + /// the request. The default value is same as . + /// + /// + /// The response has already been sent. + /// + /// + /// This object is closed. + /// + /// + /// The value specified for a set operation is invalid. Valid values are + /// between 100 and 999 inclusive. + /// + public int StatusCode { + get { + return _statusCode; + } + + set { + checkDisposedOrHeadersSent (); + if (value < 100 || value > 999) + throw new System.Net.ProtocolViolationException ( + "A value isn't between 100 and 999 inclusive."); + + _statusCode = value; + _statusDescription = value.GetStatusDescription (); + } + } + + /// + /// Gets or sets the description of the HTTP status code returned to the client. + /// + /// + /// A that represents the description of the status code. The default + /// value is the RFC 2616 + /// description for the property value, + /// or if an RFC 2616 description doesn't exist. + /// + /// + /// The value specified for a set operation contains invalid characters. + /// + /// + /// The response has already been sent. + /// + /// + /// This object is closed. + /// + public string StatusDescription { + get { + return _statusDescription; + } + + set { + checkDisposedOrHeadersSent (); + if (value == null || value.Length == 0) { + _statusDescription = _statusCode.GetStatusDescription (); + return; + } + + if (!value.IsText () || value.IndexOfAny (new[] { '\r', '\n' }) > -1) + throw new ArgumentException ("Contains invalid characters.", "value"); + + _statusDescription = value; + } + } + + #endregion + + #region Private Methods + + private bool canAddOrUpdate (Cookie cookie) + { + if (_cookies == null || _cookies.Count == 0) + return true; + + var found = findCookie (cookie).ToList (); + if (found.Count == 0) + return true; + + var ver = cookie.Version; + foreach (var c in found) + if (c.Version == ver) + return true; + + return false; + } + + private void checkDisposed () + { + if (_disposed) + throw new ObjectDisposedException (GetType ().ToString ()); + } + + private void checkDisposedOrHeadersSent () + { + if (_disposed) + throw new ObjectDisposedException (GetType ().ToString ()); + + if (_headersSent) + throw new InvalidOperationException ("Cannot be changed after the headers are sent."); + } + + private void close (bool force) + { + _disposed = true; + _context.Connection.Close (force); + } + + private IEnumerable findCookie (Cookie cookie) + { + var name = cookie.Name; + var domain = cookie.Domain; + var path = cookie.Path; + if (_cookies != null) + foreach (Cookie c in _cookies) + if (c.Name.Equals (name, StringComparison.OrdinalIgnoreCase) && + c.Domain.Equals (domain, StringComparison.OrdinalIgnoreCase) && + c.Path.Equals (path, StringComparison.Ordinal)) + yield return c; + } + + #endregion + + #region Internal Methods + + internal WebHeaderCollection WriteHeadersTo (MemoryStream destination) + { + var headers = new WebHeaderCollection (HttpHeaderType.Response, true); + if (_headers != null) + headers.Add (_headers); + + if (_contentType != null) { + var type = _contentType.IndexOf ("charset=", StringComparison.Ordinal) == -1 && + _contentEncoding != null + ? String.Format ("{0}; charset={1}", _contentType, _contentEncoding.WebName) + : _contentType; + + headers.InternalSet ("Content-Type", type, true); + } + + if (headers["Server"] == null) + headers.InternalSet ("Server", "websocket-sharp/1.0", true); + + var prov = CultureInfo.InvariantCulture; + if (headers["Date"] == null) + headers.InternalSet ("Date", DateTime.UtcNow.ToString ("r", prov), true); + + if (!_sendChunked) + headers.InternalSet ("Content-Length", _contentLength.ToString (prov), true); + else + headers.InternalSet ("Transfer-Encoding", "chunked", true); + + /* + * Apache forces closing the connection for these status codes: + * - 400 Bad Request + * - 408 Request Timeout + * - 411 Length Required + * - 413 Request Entity Too Large + * - 414 Request-Uri Too Long + * - 500 Internal Server Error + * - 503 Service Unavailable + */ + var closeConn = !_context.Request.KeepAlive || + !_keepAlive || + _statusCode == 400 || + _statusCode == 408 || + _statusCode == 411 || + _statusCode == 413 || + _statusCode == 414 || + _statusCode == 500 || + _statusCode == 503; + + var reuses = _context.Connection.Reuses; + if (closeConn || reuses >= 100) { + headers.InternalSet ("Connection", "close", true); + } + else { + headers.InternalSet ( + "Keep-Alive", String.Format ("timeout=15,max={0}", 100 - reuses), true); + + if (_context.Request.ProtocolVersion < HttpVersion.Version11) + headers.InternalSet ("Connection", "keep-alive", true); + } + + if (_location != null) + headers.InternalSet ("Location", _location, true); + + if (_cookies != null) + foreach (Cookie cookie in _cookies) + headers.InternalSet ("Set-Cookie", cookie.ToResponseString (), true); + + var enc = _contentEncoding ?? Encoding.Default; + var writer = new StreamWriter (destination, enc, 256); + writer.Write ("HTTP/{0} {1} {2}\r\n", _version, _statusCode, _statusDescription); + writer.Write (headers.ToStringMultiValue (true)); + writer.Flush (); + + // Assumes that the destination was at position 0. + destination.Position = enc.GetPreamble ().Length; + + return headers; + } + + #endregion + + #region Public Methods + + /// + /// Closes the connection to the client without returning a response. + /// + public void Abort () + { + if (_disposed) + return; + + close (true); + } + + /// + /// Adds an HTTP header with the specified and + /// to the headers for the response. + /// + /// + /// A that represents the name of the header to add. + /// + /// + /// A that represents the value of the header to add. + /// + /// + /// is or empty. + /// + /// + /// + /// or contains invalid characters. + /// + /// + /// -or- + /// + /// + /// is a restricted header name. + /// + /// + /// + /// The length of is greater than 65,535 characters. + /// + /// + /// The header cannot be allowed to add to the current headers. + /// + public void AddHeader (string name, string value) + { + Headers.Set (name, value); + } + + /// + /// Appends the specified to the cookies sent with the response. + /// + /// + /// A to append. + /// + /// + /// is . + /// + public void AppendCookie (Cookie cookie) + { + Cookies.Add (cookie); + } + + /// + /// Appends a to the specified HTTP header sent with the response. + /// + /// + /// A that represents the name of the header to append + /// to. + /// + /// + /// A that represents the value to append to the header. + /// + /// + /// is or empty. + /// + /// + /// + /// or contains invalid characters. + /// + /// + /// -or- + /// + /// + /// is a restricted header name. + /// + /// + /// + /// The length of is greater than 65,535 characters. + /// + /// + /// The current headers cannot allow the header to append a value. + /// + public void AppendHeader (string name, string value) + { + Headers.Add (name, value); + } + + /// + /// Returns the response to the client and releases the resources used by + /// this instance. + /// + public void Close () + { + if (_disposed) + return; + + close (false); + } + + /// + /// Returns the response with the specified array of to the client and + /// releases the resources used by this instance. + /// + /// + /// An array of that contains the response entity body data. + /// + /// + /// true if this method blocks execution while flushing the stream to the client; + /// otherwise, false. + /// + /// + /// is . + /// + /// + /// This object is closed. + /// + public void Close (byte[] responseEntity, bool willBlock) + { + checkDisposed (); + if (responseEntity == null) + throw new ArgumentNullException ("responseEntity"); + + var len = responseEntity.Length; + var output = OutputStream; + if (willBlock) { + output.Write (responseEntity, 0, len); + close (false); + + return; + } + + output.BeginWrite ( + responseEntity, + 0, + len, + ar => { + output.EndWrite (ar); + close (false); + }, + null); + } + + /// + /// Copies some properties from the specified to + /// this response. + /// + /// + /// A to copy. + /// + /// + /// is . + /// + public void CopyFrom (HttpListenerResponse templateResponse) + { + if (templateResponse == null) + throw new ArgumentNullException ("templateResponse"); + + if (templateResponse._headers != null) { + if (_headers != null) + _headers.Clear (); + + Headers.Add (templateResponse._headers); + } + else if (_headers != null) { + _headers = null; + } + + _contentLength = templateResponse._contentLength; + _statusCode = templateResponse._statusCode; + _statusDescription = templateResponse._statusDescription; + _keepAlive = templateResponse._keepAlive; + _version = templateResponse._version; + } + + /// + /// Configures the response to redirect the client's request to + /// the specified . + /// + /// + /// This method sets the property to + /// , the property to + /// 302, and the property to + /// "Found". + /// + /// + /// A that represents the URL to redirect the client's request to. + /// + /// + /// is . + /// + /// + /// isn't an absolute URL. + /// + /// + /// The response has already been sent. + /// + /// + /// This object is closed. + /// + public void Redirect (string url) + { + checkDisposedOrHeadersSent (); + if (url == null) + throw new ArgumentNullException ("url"); + + Uri uri = null; + if (!url.MaybeUri () || !Uri.TryCreate (url, UriKind.Absolute, out uri)) + throw new ArgumentException ("Not an absolute URL.", "url"); + + _location = url; + _statusCode = 302; + _statusDescription = "Found"; + } + + /// + /// Adds or updates a in the cookies sent with the response. + /// + /// + /// A to set. + /// + /// + /// is . + /// + /// + /// already exists in the cookies and couldn't be replaced. + /// + public void SetCookie (Cookie cookie) + { + if (cookie == null) + throw new ArgumentNullException ("cookie"); + + if (!canAddOrUpdate (cookie)) + throw new ArgumentException ("Cannot be replaced.", "cookie"); + + Cookies.Add (cookie); + } + + #endregion + + #region Explicit Interface Implementations + + /// + /// Releases all resources used by the . + /// + void IDisposable.Dispose () + { + if (_disposed) + return; + + close (true); // Same as the Abort method. + } + + #endregion + } +} diff --git a/websocket-sharp/Net/HttpRequestHeader.cs b/websocket-sharp/Net/HttpRequestHeader.cs new file mode 100644 index 0000000..08785db --- /dev/null +++ b/websocket-sharp/Net/HttpRequestHeader.cs @@ -0,0 +1,233 @@ +#region License +/* + * HttpRequestHeader.cs + * + * This code is derived from System.Net.HttpRequestHeader.cs of Mono + * (http://www.mono-project.com). + * + * The MIT License + * + * Copyright (c) 2005 Novell, Inc. (http://www.novell.com) + * 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 + +#region Authors +/* + * Authors: + * - Gonzalo Paniagua Javier + */ +#endregion + +namespace WebSocketSharp.Net +{ + /// + /// Contains the HTTP headers that may be specified in a client request. + /// + /// + /// The HttpRequestHeader enumeration contains the HTTP request headers defined in + /// RFC 2616 for the HTTP/1.1 and + /// RFC 6455 for the WebSocket. + /// + public enum HttpRequestHeader + { + /// + /// Indicates the Cache-Control header. + /// + CacheControl, + /// + /// Indicates the Connection header. + /// + Connection, + /// + /// Indicates the Date header. + /// + Date, + /// + /// Indicates the Keep-Alive header. + /// + KeepAlive, + /// + /// Indicates the Pragma header. + /// + Pragma, + /// + /// Indicates the Trailer header. + /// + Trailer, + /// + /// Indicates the Transfer-Encoding header. + /// + TransferEncoding, + /// + /// Indicates the Upgrade header. + /// + Upgrade, + /// + /// Indicates the Via header. + /// + Via, + /// + /// Indicates the Warning header. + /// + Warning, + /// + /// Indicates the Allow header. + /// + Allow, + /// + /// Indicates the Content-Length header. + /// + ContentLength, + /// + /// Indicates the Content-Type header. + /// + ContentType, + /// + /// Indicates the Content-Encoding header. + /// + ContentEncoding, + /// + /// Indicates the Content-Language header. + /// + ContentLanguage, + /// + /// Indicates the Content-Location header. + /// + ContentLocation, + /// + /// Indicates the Content-MD5 header. + /// + ContentMd5, + /// + /// Indicates the Content-Range header. + /// + ContentRange, + /// + /// Indicates the Expires header. + /// + Expires, + /// + /// Indicates the Last-Modified header. + /// + LastModified, + /// + /// Indicates the Accept header. + /// + Accept, + /// + /// Indicates the Accept-Charset header. + /// + AcceptCharset, + /// + /// Indicates the Accept-Encoding header. + /// + AcceptEncoding, + /// + /// Indicates the Accept-Language header. + /// + AcceptLanguage, + /// + /// Indicates the Authorization header. + /// + Authorization, + /// + /// Indicates the Cookie header. + /// + Cookie, + /// + /// Indicates the Expect header. + /// + Expect, + /// + /// Indicates the From header. + /// + From, + /// + /// Indicates the Host header. + /// + Host, + /// + /// Indicates the If-Match header. + /// + IfMatch, + /// + /// Indicates the If-Modified-Since header. + /// + IfModifiedSince, + /// + /// Indicates the If-None-Match header. + /// + IfNoneMatch, + /// + /// Indicates the If-Range header. + /// + IfRange, + /// + /// Indicates the If-Unmodified-Since header. + /// + IfUnmodifiedSince, + /// + /// Indicates the Max-Forwards header. + /// + MaxForwards, + /// + /// Indicates the Proxy-Authorization header. + /// + ProxyAuthorization, + /// + /// Indicates the Referer header. + /// + Referer, + /// + /// Indicates the Range header. + /// + Range, + /// + /// Indicates the TE header. + /// + Te, + /// + /// Indicates the Translate header. + /// + Translate, + /// + /// Indicates the User-Agent header. + /// + UserAgent, + /// + /// Indicates the Sec-WebSocket-Key header. + /// + SecWebSocketKey, + /// + /// Indicates the Sec-WebSocket-Extensions header. + /// + SecWebSocketExtensions, + /// + /// Indicates the Sec-WebSocket-Protocol header. + /// + SecWebSocketProtocol, + /// + /// Indicates the Sec-WebSocket-Version header. + /// + SecWebSocketVersion + } +} diff --git a/websocket-sharp/Net/HttpResponseHeader.cs b/websocket-sharp/Net/HttpResponseHeader.cs new file mode 100644 index 0000000..d8f36ed --- /dev/null +++ b/websocket-sharp/Net/HttpResponseHeader.cs @@ -0,0 +1,189 @@ +#region License +/* + * HttpResponseHeader.cs + * + * This code is derived from System.Net.HttpResponseHeader.cs of Mono + * (http://www.mono-project.com). + * + * The MIT License + * + * Copyright (c) 2005 Novell, Inc. (http://www.novell.com) + * 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 + +#region Authors +/* + * Authors: + * - Gonzalo Paniagua Javier + */ +#endregion + +namespace WebSocketSharp.Net +{ + /// + /// Contains the HTTP headers that can be specified in a server response. + /// + /// + /// The HttpResponseHeader enumeration contains the HTTP response headers defined in + /// RFC 2616 for the HTTP/1.1 and + /// RFC 6455 for the WebSocket. + /// + public enum HttpResponseHeader + { + /// + /// Indicates the Cache-Control header. + /// + CacheControl, + /// + /// Indicates the Connection header. + /// + Connection, + /// + /// Indicates the Date header. + /// + Date, + /// + /// Indicates the Keep-Alive header. + /// + KeepAlive, + /// + /// Indicates the Pragma header. + /// + Pragma, + /// + /// Indicates the Trailer header. + /// + Trailer, + /// + /// Indicates the Transfer-Encoding header. + /// + TransferEncoding, + /// + /// Indicates the Upgrade header. + /// + Upgrade, + /// + /// Indicates the Via header. + /// + Via, + /// + /// Indicates the Warning header. + /// + Warning, + /// + /// Indicates the Allow header. + /// + Allow, + /// + /// Indicates the Content-Length header. + /// + ContentLength, + /// + /// Indicates the Content-Type header. + /// + ContentType, + /// + /// Indicates the Content-Encoding header. + /// + ContentEncoding, + /// + /// Indicates the Content-Language header. + /// + ContentLanguage, + /// + /// Indicates the Content-Location header. + /// + ContentLocation, + /// + /// Indicates the Content-MD5 header. + /// + ContentMd5, + /// + /// Indicates the Content-Range header. + /// + ContentRange, + /// + /// Indicates the Expires header. + /// + Expires, + /// + /// Indicates the Last-Modified header. + /// + LastModified, + /// + /// Indicates the Accept-Ranges header. + /// + AcceptRanges, + /// + /// Indicates the Age header. + /// + Age, + /// + /// Indicates the ETag header. + /// + ETag, + /// + /// Indicates the Location header. + /// + Location, + /// + /// Indicates the Proxy-Authenticate header. + /// + ProxyAuthenticate, + /// + /// Indicates the Retry-After header. + /// + RetryAfter, + /// + /// Indicates the Server header. + /// + Server, + /// + /// Indicates the Set-Cookie header. + /// + SetCookie, + /// + /// Indicates the Vary header. + /// + Vary, + /// + /// Indicates the WWW-Authenticate header. + /// + WwwAuthenticate, + /// + /// Indicates the Sec-WebSocket-Extensions header. + /// + SecWebSocketExtensions, + /// + /// Indicates the Sec-WebSocket-Accept header. + /// + SecWebSocketAccept, + /// + /// Indicates the Sec-WebSocket-Protocol header. + /// + SecWebSocketProtocol, + /// + /// Indicates the Sec-WebSocket-Version header. + /// + SecWebSocketVersion + } +} diff --git a/websocket-sharp/Net/HttpStatusCode.cs b/websocket-sharp/Net/HttpStatusCode.cs new file mode 100644 index 0000000..123415f --- /dev/null +++ b/websocket-sharp/Net/HttpStatusCode.cs @@ -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 +{ + /// + /// Contains the values of the HTTP status codes. + /// + /// + /// The HttpStatusCode enumeration contains the values of the HTTP status codes defined in + /// RFC 2616 for the HTTP/1.1. + /// + public enum HttpStatusCode + { + /// + /// Equivalent to status code 100. + /// Indicates that the client should continue with its request. + /// + Continue = 100, + /// + /// Equivalent to status code 101. + /// Indicates that the server is switching the HTTP version or protocol on the connection. + /// + SwitchingProtocols = 101, + /// + /// Equivalent to status code 200. + /// Indicates that the client's request has succeeded. + /// + OK = 200, + /// + /// Equivalent to status code 201. + /// Indicates that the client's request has been fulfilled and resulted in a new resource being + /// created. + /// + Created = 201, + /// + /// Equivalent to status code 202. + /// Indicates that the client's request has been accepted for processing, but the processing + /// hasn't been completed. + /// + Accepted = 202, + /// + /// Equivalent to status code 203. + /// Indicates that the returned metainformation is from a local or a third-party copy instead of + /// the origin server. + /// + NonAuthoritativeInformation = 203, + /// + /// Equivalent to status code 204. + /// Indicates that the server has fulfilled the client's request but doesn't need to return + /// an entity-body. + /// + NoContent = 204, + /// + /// 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. + /// + ResetContent = 205, + /// + /// Equivalent to status code 206. + /// Indicates that the server has fulfilled the partial GET request for the resource. + /// + PartialContent = 206, + /// + /// + /// Equivalent to status code 300. + /// Indicates that the requested resource corresponds to any of multiple representations. + /// + /// + /// MultipleChoices is a synonym for Ambiguous. + /// + /// + MultipleChoices = 300, + /// + /// + /// Equivalent to status code 300. + /// Indicates that the requested resource corresponds to any of multiple representations. + /// + /// + /// Ambiguous is a synonym for MultipleChoices. + /// + /// + Ambiguous = 300, + /// + /// + /// 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. + /// + /// + /// MovedPermanently is a synonym for Moved. + /// + /// + MovedPermanently = 301, + /// + /// + /// 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. + /// + /// + /// Moved is a synonym for MovedPermanently. + /// + /// + Moved = 301, + /// + /// + /// Equivalent to status code 302. + /// Indicates that the requested resource is located temporarily under a different URI. + /// + /// + /// Found is a synonym for Redirect. + /// + /// + Found = 302, + /// + /// + /// Equivalent to status code 302. + /// Indicates that the requested resource is located temporarily under a different URI. + /// + /// + /// Redirect is a synonym for Found. + /// + /// + Redirect = 302, + /// + /// + /// 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. + /// + /// + /// SeeOther is a synonym for RedirectMethod. + /// + /// + SeeOther = 303, + /// + /// + /// 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. + /// + /// + /// RedirectMethod is a synonym for SeeOther. + /// + /// + RedirectMethod = 303, + /// + /// 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. + /// + NotModified = 304, + /// + /// Equivalent to status code 305. + /// Indicates that the requested resource must be accessed through the proxy given by + /// the Location field. + /// + UseProxy = 305, + /// + /// 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. + /// + Unused = 306, + /// + /// + /// Equivalent to status code 307. + /// Indicates that the requested resource is located temporarily under a different URI. + /// + /// + /// TemporaryRedirect is a synonym for RedirectKeepVerb. + /// + /// + TemporaryRedirect = 307, + /// + /// + /// Equivalent to status code 307. + /// Indicates that the requested resource is located temporarily under a different URI. + /// + /// + /// RedirectKeepVerb is a synonym for TemporaryRedirect. + /// + /// + RedirectKeepVerb = 307, + /// + /// Equivalent to status code 400. + /// Indicates that the client's request couldn't be understood by the server due to + /// malformed syntax. + /// + BadRequest = 400, + /// + /// Equivalent to status code 401. + /// Indicates that the client's request requires user authentication. + /// + Unauthorized = 401, + /// + /// Equivalent to status code 402. + /// This status code is reserved for future use. + /// + PaymentRequired = 402, + /// + /// Equivalent to status code 403. + /// Indicates that the server understood the client's request but is refusing to fulfill it. + /// + Forbidden = 403, + /// + /// Equivalent to status code 404. + /// Indicates that the server hasn't found anything matching the request URI. + /// + NotFound = 404, + /// + /// 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. + /// + MethodNotAllowed = 405, + /// + /// 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. + /// + NotAcceptable = 406, + /// + /// Equivalent to status code 407. + /// Indicates that the client must first authenticate itself with the proxy. + /// + ProxyAuthenticationRequired = 407, + /// + /// Equivalent to status code 408. + /// Indicates that the client didn't produce a request within the time that the server was + /// prepared to wait. + /// + RequestTimeout = 408, + /// + /// Equivalent to status code 409. + /// Indicates that the client's request couldn't be completed due to a conflict on the server. + /// + Conflict = 409, + /// + /// Equivalent to status code 410. + /// Indicates that the requested resource is no longer available at the server and + /// no forwarding address is known. + /// + Gone = 410, + /// + /// Equivalent to status code 411. + /// Indicates that the server refuses to accept the client's request without a defined + /// Content-Length. + /// + LengthRequired = 411, + /// + /// 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. + /// + PreconditionFailed = 412, + /// + /// 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. + /// + RequestEntityTooLarge = 413, + /// + /// Equivalent to status code 414. + /// Indicates that the request URI is longer than the server is willing to interpret. + /// + RequestUriTooLong = 414, + /// + /// 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. + /// + UnsupportedMediaType = 415, + /// + /// 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. + /// + RequestedRangeNotSatisfiable = 416, + /// + /// Equivalent to status code 417. + /// Indicates that the expectation given in an Expect request header couldn't be met by + /// the server. + /// + ExpectationFailed = 417, + /// + /// Equivalent to status code 500. + /// Indicates that the server encountered an unexpected condition which prevented it from + /// fulfilling the client's request. + /// + InternalServerError = 500, + /// + /// Equivalent to status code 501. + /// Indicates that the server doesn't support the functionality required to fulfill the client's + /// request. + /// + NotImplemented = 501, + /// + /// Equivalent to status code 502. + /// Indicates that a gateway or proxy server received an invalid response from the upstream + /// server. + /// + BadGateway = 502, + /// + /// 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. + /// + ServiceUnavailable = 503, + /// + /// 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. + /// + GatewayTimeout = 504, + /// + /// Equivalent to status code 505. + /// Indicates that the server doesn't support the HTTP version used in the client's request. + /// + HttpVersionNotSupported = 505, + } +} diff --git a/websocket-sharp/Net/HttpStreamAsyncResult.cs b/websocket-sharp/Net/HttpStreamAsyncResult.cs new file mode 100644 index 0000000..4418930 --- /dev/null +++ b/websocket-sharp/Net/HttpStreamAsyncResult.cs @@ -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 + */ +#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 + } +} diff --git a/websocket-sharp/Net/HttpUtility.cs b/websocket-sharp/Net/HttpUtility.cs new file mode 100644 index 0000000..47ea7ee --- /dev/null +++ b/websocket-sharp/Net/HttpUtility.cs @@ -0,0 +1,1146 @@ +#region License +/* + * HttpUtility.cs + * + * This code is derived from HttpUtility.cs (System.Net) of Mono + * (http://www.mono-project.com). + * + * The MIT License + * + * Copyright (c) 2005-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: + * - Patrik Torstensson + * - Wictor Wilén (decode/encode functions) + * - Tim Coleman + * - Gonzalo Paniagua Javier + */ +#endregion + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Globalization; +using System.IO; +using System.Security.Principal; +using System.Text; + +namespace WebSocketSharp.Net +{ + internal static class HttpUtility + { + #region Private Fields + + private static Dictionary _entities; + private static char[] _hexChars; + private static object _sync; + + #endregion + + #region Static Constructor + + static HttpUtility () + { + _hexChars = "0123456789ABCDEF".ToCharArray (); + _sync = new object (); + } + + #endregion + + #region Private Methods + + private static Dictionary getEntities () + { + lock (_sync) { + if (_entities == null) + initEntities (); + + return _entities; + } + } + + private static int getNumber (char c) + { + return c >= '0' && c <= '9' + ? c - '0' + : c >= 'A' && c <= 'F' + ? c - 'A' + 10 + : c >= 'a' && c <= 'f' + ? c - 'a' + 10 + : -1; + } + + private static int getNumber (byte[] bytes, int offset, int count) + { + var ret = 0; + + var end = offset + count - 1; + for (var i = offset; i <= end; i++) { + var num = getNumber ((char) bytes[i]); + if (num == -1) + return -1; + + ret = (ret << 4) + num; + } + + return ret; + } + + private static int getNumber (string s, int offset, int count) + { + var ret = 0; + + var end = offset + count - 1; + for (var i = offset; i <= end; i++) { + var num = getNumber (s[i]); + if (num == -1) + return -1; + + ret = (ret << 4) + num; + } + + return ret; + } + + private static string htmlDecode (string s) + { + var buff = new StringBuilder (); + + // 0: None + // 1: Right after '&' + // 2: Between '&' and ';' but no NCR + // 3: '#' found after '&' and getting numbers + // 4: 'x' found after '#' and getting numbers + var state = 0; + + var reference = new StringBuilder (); + var num = 0; + + foreach (var c in s) { + if (state == 0) { + if (c == '&') { + reference.Append ('&'); + state = 1; + + continue; + } + + buff.Append (c); + continue; + } + + if (c == '&') { + buff.Append (reference.ToString ()); + + reference.Length = 0; + reference.Append ('&'); + state = 1; + + continue; + } + + reference.Append (c); + + if (state == 1) { + if (c == ';') { + buff.Append (reference.ToString ()); + + reference.Length = 0; + state = 0; + + continue; + } + + num = 0; + state = c == '#' ? 3 : 2; + + continue; + } + + if (state == 2) { + if (c == ';') { + var entity = reference.ToString (); + var name = entity.Substring (1, entity.Length - 2); + + var entities = getEntities (); + if (entities.ContainsKey (name)) + buff.Append (entities[name]); + else + buff.Append (entity); + + reference.Length = 0; + state = 0; + + continue; + } + + continue; + } + + if (state == 3) { + if (c == ';') { + if (reference.Length > 3 && num < 65536) + buff.Append ((char) num); + else + buff.Append (reference.ToString ()); + + reference.Length = 0; + state = 0; + + continue; + } + + if (c == 'x') { + state = reference.Length == 3 ? 4 : 2; + continue; + } + + if (!isNumeric (c)) { + state = 2; + continue; + } + + num = num * 10 + (c - '0'); + continue; + } + + if (state == 4) { + if (c == ';') { + if (reference.Length > 4 && num < 65536) + buff.Append ((char) num); + else + buff.Append (reference.ToString ()); + + reference.Length = 0; + state = 0; + + continue; + } + + var n = getNumber (c); + if (n == -1) { + state = 2; + continue; + } + + num = (num << 4) + n; + } + } + + if (reference.Length > 0) + buff.Append (reference.ToString ()); + + return buff.ToString (); + } + + /// + /// Converts the specified string to an HTML-encoded string. + /// + /// + /// + /// This method starts encoding with a NCR from the character code 160 + /// but does not stop at the character code 255. + /// + /// + /// One reason is the unicode characters < and > that + /// look like < and >. + /// + /// + /// + /// A that represents an encoded string. + /// + /// + /// A to encode. + /// + /// + /// A : true if encodes without a NCR; + /// otherwise, false. + /// + private static string htmlEncode (string s, bool minimal) + { + var buff = new StringBuilder (); + + foreach (var c in s) { + buff.Append ( + c == '"' + ? """ + : c == '&' + ? "&" + : c == '<' + ? "<" + : c == '>' + ? ">" + : !minimal && c > 159 + ? String.Format ("&#{0};", (int) c) + : c.ToString () + ); + } + + return buff.ToString (); + } + + /// + /// Initializes the _entities field. + /// + /// + /// This method builds a dictionary of HTML character entity references. + /// This dictionary comes from the HTML 4.01 W3C recommendation. + /// + private static void initEntities () + { + _entities = new Dictionary (); + _entities.Add ("nbsp", '\u00A0'); + _entities.Add ("iexcl", '\u00A1'); + _entities.Add ("cent", '\u00A2'); + _entities.Add ("pound", '\u00A3'); + _entities.Add ("curren", '\u00A4'); + _entities.Add ("yen", '\u00A5'); + _entities.Add ("brvbar", '\u00A6'); + _entities.Add ("sect", '\u00A7'); + _entities.Add ("uml", '\u00A8'); + _entities.Add ("copy", '\u00A9'); + _entities.Add ("ordf", '\u00AA'); + _entities.Add ("laquo", '\u00AB'); + _entities.Add ("not", '\u00AC'); + _entities.Add ("shy", '\u00AD'); + _entities.Add ("reg", '\u00AE'); + _entities.Add ("macr", '\u00AF'); + _entities.Add ("deg", '\u00B0'); + _entities.Add ("plusmn", '\u00B1'); + _entities.Add ("sup2", '\u00B2'); + _entities.Add ("sup3", '\u00B3'); + _entities.Add ("acute", '\u00B4'); + _entities.Add ("micro", '\u00B5'); + _entities.Add ("para", '\u00B6'); + _entities.Add ("middot", '\u00B7'); + _entities.Add ("cedil", '\u00B8'); + _entities.Add ("sup1", '\u00B9'); + _entities.Add ("ordm", '\u00BA'); + _entities.Add ("raquo", '\u00BB'); + _entities.Add ("frac14", '\u00BC'); + _entities.Add ("frac12", '\u00BD'); + _entities.Add ("frac34", '\u00BE'); + _entities.Add ("iquest", '\u00BF'); + _entities.Add ("Agrave", '\u00C0'); + _entities.Add ("Aacute", '\u00C1'); + _entities.Add ("Acirc", '\u00C2'); + _entities.Add ("Atilde", '\u00C3'); + _entities.Add ("Auml", '\u00C4'); + _entities.Add ("Aring", '\u00C5'); + _entities.Add ("AElig", '\u00C6'); + _entities.Add ("Ccedil", '\u00C7'); + _entities.Add ("Egrave", '\u00C8'); + _entities.Add ("Eacute", '\u00C9'); + _entities.Add ("Ecirc", '\u00CA'); + _entities.Add ("Euml", '\u00CB'); + _entities.Add ("Igrave", '\u00CC'); + _entities.Add ("Iacute", '\u00CD'); + _entities.Add ("Icirc", '\u00CE'); + _entities.Add ("Iuml", '\u00CF'); + _entities.Add ("ETH", '\u00D0'); + _entities.Add ("Ntilde", '\u00D1'); + _entities.Add ("Ograve", '\u00D2'); + _entities.Add ("Oacute", '\u00D3'); + _entities.Add ("Ocirc", '\u00D4'); + _entities.Add ("Otilde", '\u00D5'); + _entities.Add ("Ouml", '\u00D6'); + _entities.Add ("times", '\u00D7'); + _entities.Add ("Oslash", '\u00D8'); + _entities.Add ("Ugrave", '\u00D9'); + _entities.Add ("Uacute", '\u00DA'); + _entities.Add ("Ucirc", '\u00DB'); + _entities.Add ("Uuml", '\u00DC'); + _entities.Add ("Yacute", '\u00DD'); + _entities.Add ("THORN", '\u00DE'); + _entities.Add ("szlig", '\u00DF'); + _entities.Add ("agrave", '\u00E0'); + _entities.Add ("aacute", '\u00E1'); + _entities.Add ("acirc", '\u00E2'); + _entities.Add ("atilde", '\u00E3'); + _entities.Add ("auml", '\u00E4'); + _entities.Add ("aring", '\u00E5'); + _entities.Add ("aelig", '\u00E6'); + _entities.Add ("ccedil", '\u00E7'); + _entities.Add ("egrave", '\u00E8'); + _entities.Add ("eacute", '\u00E9'); + _entities.Add ("ecirc", '\u00EA'); + _entities.Add ("euml", '\u00EB'); + _entities.Add ("igrave", '\u00EC'); + _entities.Add ("iacute", '\u00ED'); + _entities.Add ("icirc", '\u00EE'); + _entities.Add ("iuml", '\u00EF'); + _entities.Add ("eth", '\u00F0'); + _entities.Add ("ntilde", '\u00F1'); + _entities.Add ("ograve", '\u00F2'); + _entities.Add ("oacute", '\u00F3'); + _entities.Add ("ocirc", '\u00F4'); + _entities.Add ("otilde", '\u00F5'); + _entities.Add ("ouml", '\u00F6'); + _entities.Add ("divide", '\u00F7'); + _entities.Add ("oslash", '\u00F8'); + _entities.Add ("ugrave", '\u00F9'); + _entities.Add ("uacute", '\u00FA'); + _entities.Add ("ucirc", '\u00FB'); + _entities.Add ("uuml", '\u00FC'); + _entities.Add ("yacute", '\u00FD'); + _entities.Add ("thorn", '\u00FE'); + _entities.Add ("yuml", '\u00FF'); + _entities.Add ("fnof", '\u0192'); + _entities.Add ("Alpha", '\u0391'); + _entities.Add ("Beta", '\u0392'); + _entities.Add ("Gamma", '\u0393'); + _entities.Add ("Delta", '\u0394'); + _entities.Add ("Epsilon", '\u0395'); + _entities.Add ("Zeta", '\u0396'); + _entities.Add ("Eta", '\u0397'); + _entities.Add ("Theta", '\u0398'); + _entities.Add ("Iota", '\u0399'); + _entities.Add ("Kappa", '\u039A'); + _entities.Add ("Lambda", '\u039B'); + _entities.Add ("Mu", '\u039C'); + _entities.Add ("Nu", '\u039D'); + _entities.Add ("Xi", '\u039E'); + _entities.Add ("Omicron", '\u039F'); + _entities.Add ("Pi", '\u03A0'); + _entities.Add ("Rho", '\u03A1'); + _entities.Add ("Sigma", '\u03A3'); + _entities.Add ("Tau", '\u03A4'); + _entities.Add ("Upsilon", '\u03A5'); + _entities.Add ("Phi", '\u03A6'); + _entities.Add ("Chi", '\u03A7'); + _entities.Add ("Psi", '\u03A8'); + _entities.Add ("Omega", '\u03A9'); + _entities.Add ("alpha", '\u03B1'); + _entities.Add ("beta", '\u03B2'); + _entities.Add ("gamma", '\u03B3'); + _entities.Add ("delta", '\u03B4'); + _entities.Add ("epsilon", '\u03B5'); + _entities.Add ("zeta", '\u03B6'); + _entities.Add ("eta", '\u03B7'); + _entities.Add ("theta", '\u03B8'); + _entities.Add ("iota", '\u03B9'); + _entities.Add ("kappa", '\u03BA'); + _entities.Add ("lambda", '\u03BB'); + _entities.Add ("mu", '\u03BC'); + _entities.Add ("nu", '\u03BD'); + _entities.Add ("xi", '\u03BE'); + _entities.Add ("omicron", '\u03BF'); + _entities.Add ("pi", '\u03C0'); + _entities.Add ("rho", '\u03C1'); + _entities.Add ("sigmaf", '\u03C2'); + _entities.Add ("sigma", '\u03C3'); + _entities.Add ("tau", '\u03C4'); + _entities.Add ("upsilon", '\u03C5'); + _entities.Add ("phi", '\u03C6'); + _entities.Add ("chi", '\u03C7'); + _entities.Add ("psi", '\u03C8'); + _entities.Add ("omega", '\u03C9'); + _entities.Add ("thetasym", '\u03D1'); + _entities.Add ("upsih", '\u03D2'); + _entities.Add ("piv", '\u03D6'); + _entities.Add ("bull", '\u2022'); + _entities.Add ("hellip", '\u2026'); + _entities.Add ("prime", '\u2032'); + _entities.Add ("Prime", '\u2033'); + _entities.Add ("oline", '\u203E'); + _entities.Add ("frasl", '\u2044'); + _entities.Add ("weierp", '\u2118'); + _entities.Add ("image", '\u2111'); + _entities.Add ("real", '\u211C'); + _entities.Add ("trade", '\u2122'); + _entities.Add ("alefsym", '\u2135'); + _entities.Add ("larr", '\u2190'); + _entities.Add ("uarr", '\u2191'); + _entities.Add ("rarr", '\u2192'); + _entities.Add ("darr", '\u2193'); + _entities.Add ("harr", '\u2194'); + _entities.Add ("crarr", '\u21B5'); + _entities.Add ("lArr", '\u21D0'); + _entities.Add ("uArr", '\u21D1'); + _entities.Add ("rArr", '\u21D2'); + _entities.Add ("dArr", '\u21D3'); + _entities.Add ("hArr", '\u21D4'); + _entities.Add ("forall", '\u2200'); + _entities.Add ("part", '\u2202'); + _entities.Add ("exist", '\u2203'); + _entities.Add ("empty", '\u2205'); + _entities.Add ("nabla", '\u2207'); + _entities.Add ("isin", '\u2208'); + _entities.Add ("notin", '\u2209'); + _entities.Add ("ni", '\u220B'); + _entities.Add ("prod", '\u220F'); + _entities.Add ("sum", '\u2211'); + _entities.Add ("minus", '\u2212'); + _entities.Add ("lowast", '\u2217'); + _entities.Add ("radic", '\u221A'); + _entities.Add ("prop", '\u221D'); + _entities.Add ("infin", '\u221E'); + _entities.Add ("ang", '\u2220'); + _entities.Add ("and", '\u2227'); + _entities.Add ("or", '\u2228'); + _entities.Add ("cap", '\u2229'); + _entities.Add ("cup", '\u222A'); + _entities.Add ("int", '\u222B'); + _entities.Add ("there4", '\u2234'); + _entities.Add ("sim", '\u223C'); + _entities.Add ("cong", '\u2245'); + _entities.Add ("asymp", '\u2248'); + _entities.Add ("ne", '\u2260'); + _entities.Add ("equiv", '\u2261'); + _entities.Add ("le", '\u2264'); + _entities.Add ("ge", '\u2265'); + _entities.Add ("sub", '\u2282'); + _entities.Add ("sup", '\u2283'); + _entities.Add ("nsub", '\u2284'); + _entities.Add ("sube", '\u2286'); + _entities.Add ("supe", '\u2287'); + _entities.Add ("oplus", '\u2295'); + _entities.Add ("otimes", '\u2297'); + _entities.Add ("perp", '\u22A5'); + _entities.Add ("sdot", '\u22C5'); + _entities.Add ("lceil", '\u2308'); + _entities.Add ("rceil", '\u2309'); + _entities.Add ("lfloor", '\u230A'); + _entities.Add ("rfloor", '\u230B'); + _entities.Add ("lang", '\u2329'); + _entities.Add ("rang", '\u232A'); + _entities.Add ("loz", '\u25CA'); + _entities.Add ("spades", '\u2660'); + _entities.Add ("clubs", '\u2663'); + _entities.Add ("hearts", '\u2665'); + _entities.Add ("diams", '\u2666'); + _entities.Add ("quot", '\u0022'); + _entities.Add ("amp", '\u0026'); + _entities.Add ("lt", '\u003C'); + _entities.Add ("gt", '\u003E'); + _entities.Add ("OElig", '\u0152'); + _entities.Add ("oelig", '\u0153'); + _entities.Add ("Scaron", '\u0160'); + _entities.Add ("scaron", '\u0161'); + _entities.Add ("Yuml", '\u0178'); + _entities.Add ("circ", '\u02C6'); + _entities.Add ("tilde", '\u02DC'); + _entities.Add ("ensp", '\u2002'); + _entities.Add ("emsp", '\u2003'); + _entities.Add ("thinsp", '\u2009'); + _entities.Add ("zwnj", '\u200C'); + _entities.Add ("zwj", '\u200D'); + _entities.Add ("lrm", '\u200E'); + _entities.Add ("rlm", '\u200F'); + _entities.Add ("ndash", '\u2013'); + _entities.Add ("mdash", '\u2014'); + _entities.Add ("lsquo", '\u2018'); + _entities.Add ("rsquo", '\u2019'); + _entities.Add ("sbquo", '\u201A'); + _entities.Add ("ldquo", '\u201C'); + _entities.Add ("rdquo", '\u201D'); + _entities.Add ("bdquo", '\u201E'); + _entities.Add ("dagger", '\u2020'); + _entities.Add ("Dagger", '\u2021'); + _entities.Add ("permil", '\u2030'); + _entities.Add ("lsaquo", '\u2039'); + _entities.Add ("rsaquo", '\u203A'); + _entities.Add ("euro", '\u20AC'); + } + + private static bool isAlphabet (char c) + { + return (c >= 'A' && c <= 'Z') + || (c >= 'a' && c <= 'z'); + } + + private static bool isNumeric (char c) + { + return c >= '0' && c <= '9'; + } + + private static bool isUnreserved (char c) + { + return c == '*' + || c == '-' + || c == '.' + || c == '_'; + } + + private static bool isUnreservedInRfc2396 (char c) + { + return c == '!' + || c == '\'' + || c == '(' + || c == ')' + || c == '*' + || c == '-' + || c == '.' + || c == '_' + || c == '~'; + } + + private static bool isUnreservedInRfc3986 (char c) + { + return c == '-' + || c == '.' + || c == '_' + || c == '~'; + } + + private static byte[] urlDecodeToBytes (byte[] bytes, int offset, int count) + { + using (var buff = new MemoryStream ()) { + var end = offset + count - 1; + for (var i = offset; i <= end; i++) { + var b = bytes[i]; + + var c = (char) b; + if (c == '%') { + if (i > end - 2) + break; + + var num = getNumber (bytes, i + 1, 2); + if (num == -1) + break; + + buff.WriteByte ((byte) num); + i += 2; + + continue; + } + + if (c == '+') { + buff.WriteByte ((byte) ' '); + continue; + } + + buff.WriteByte (b); + } + + buff.Close (); + return buff.ToArray (); + } + } + + private static void urlEncode (byte b, Stream output) + { + if (b > 31 && b < 127) { + var c = (char) b; + if (c == ' ') { + output.WriteByte ((byte) '+'); + return; + } + + if (isNumeric (c)) { + output.WriteByte (b); + return; + } + + if (isAlphabet (c)) { + output.WriteByte (b); + return; + } + + if (isUnreserved (c)) { + output.WriteByte (b); + return; + } + } + + var i = (int) b; + var bytes = new byte[] { + (byte) '%', + (byte) _hexChars[i >> 4], + (byte) _hexChars[i & 0x0F] + }; + + output.Write (bytes, 0, 3); + } + + private static byte[] urlEncodeToBytes (byte[] bytes, int offset, int count) + { + using (var buff = new MemoryStream ()) { + var end = offset + count - 1; + for (var i = offset; i <= end; i++) + urlEncode (bytes[i], buff); + + buff.Close (); + return buff.ToArray (); + } + } + + #endregion + + #region Internal Methods + + internal static Uri CreateRequestUrl ( + string requestUri, string host, bool websocketRequest, bool secure + ) + { + if (requestUri == null || requestUri.Length == 0) + return null; + + if (host == null || host.Length == 0) + return null; + + string schm = null; + string path = null; + + if (requestUri.IndexOf ('/') == 0) { + path = requestUri; + } + else if (requestUri.MaybeUri ()) { + Uri uri; + if (!Uri.TryCreate (requestUri, UriKind.Absolute, out uri)) + return null; + + schm = uri.Scheme; + var valid = websocketRequest + ? schm == "ws" || schm == "wss" + : schm == "http" || schm == "https"; + + if (!valid) + return null; + + host = uri.Authority; + path = uri.PathAndQuery; + } + else if (requestUri == "*") { + } + else { + // As the authority form. + host = requestUri; + } + + if (schm == null) { + schm = websocketRequest + ? (secure ? "wss" : "ws") + : (secure ? "https" : "http"); + } + + if (host.IndexOf (':') == -1) + host = String.Format ("{0}:{1}", host, secure ? 443 : 80); + + var url = String.Format ("{0}://{1}{2}", schm, host, path); + + Uri ret; + return Uri.TryCreate (url, UriKind.Absolute, out ret) ? ret : null; + } + + internal static IPrincipal CreateUser ( + string response, + AuthenticationSchemes scheme, + string realm, + string method, + Func credentialsFinder + ) + { + if (response == null || response.Length == 0) + return null; + + if (scheme == AuthenticationSchemes.Digest) { + if (realm == null || realm.Length == 0) + return null; + + if (method == null || method.Length == 0) + return null; + } + else { + if (scheme != AuthenticationSchemes.Basic) + return null; + } + + if (credentialsFinder == null) + return null; + + var compType = StringComparison.OrdinalIgnoreCase; + if (response.IndexOf (scheme.ToString (), compType) != 0) + return null; + + var res = AuthenticationResponse.Parse (response); + if (res == null) + return null; + + var id = res.ToIdentity (); + if (id == null) + return null; + + NetworkCredential cred = null; + try { + cred = credentialsFinder (id); + } + catch { + } + + if (cred == null) + return null; + + if (scheme == AuthenticationSchemes.Basic) { + var basicId = (HttpBasicIdentity) id; + return basicId.Password == cred.Password + ? new GenericPrincipal (id, cred.Roles) + : null; + } + + var digestId = (HttpDigestIdentity) id; + return digestId.IsValid (cred.Password, realm, method, null) + ? new GenericPrincipal (id, cred.Roles) + : null; + } + + internal static Encoding GetEncoding (string contentType) + { + var name = "charset="; + var compType = StringComparison.OrdinalIgnoreCase; + + foreach (var elm in contentType.SplitHeaderValue (';')) { + var part = elm.Trim (); + if (part.IndexOf (name, compType) != 0) + continue; + + var val = part.GetValue ('=', true); + if (val == null || val.Length == 0) + return null; + + return Encoding.GetEncoding (val); + } + + return null; + } + + internal static bool TryGetEncoding ( + string contentType, out Encoding result + ) + { + result = null; + + try { + result = GetEncoding (contentType); + } + catch { + return false; + } + + return result != null; + } + + #endregion + + #region Public Methods + + public static string HtmlAttributeEncode (string s) + { + if (s == null) + throw new ArgumentNullException ("s"); + + return s.Length > 0 ? htmlEncode (s, true) : s; + } + + public static void HtmlAttributeEncode (string s, TextWriter output) + { + if (s == null) + throw new ArgumentNullException ("s"); + + if (output == null) + throw new ArgumentNullException ("output"); + + if (s.Length == 0) + return; + + output.Write (htmlEncode (s, true)); + } + + public static string HtmlDecode (string s) + { + if (s == null) + throw new ArgumentNullException ("s"); + + return s.Length > 0 ? htmlDecode (s) : s; + } + + public static void HtmlDecode (string s, TextWriter output) + { + if (s == null) + throw new ArgumentNullException ("s"); + + if (output == null) + throw new ArgumentNullException ("output"); + + if (s.Length == 0) + return; + + output.Write (htmlDecode (s)); + } + + public static string HtmlEncode (string s) + { + if (s == null) + throw new ArgumentNullException ("s"); + + return s.Length > 0 ? htmlEncode (s, false) : s; + } + + public static void HtmlEncode (string s, TextWriter output) + { + if (s == null) + throw new ArgumentNullException ("s"); + + if (output == null) + throw new ArgumentNullException ("output"); + + if (s.Length == 0) + return; + + output.Write (htmlEncode (s, false)); + } + + public static string UrlDecode (string s) + { + return UrlDecode (s, Encoding.UTF8); + } + + public static string UrlDecode (byte[] bytes, Encoding encoding) + { + if (bytes == null) + throw new ArgumentNullException ("bytes"); + + var len = bytes.Length; + return len > 0 + ? (encoding ?? Encoding.UTF8).GetString ( + urlDecodeToBytes (bytes, 0, len) + ) + : String.Empty; + } + + public static string UrlDecode (string s, Encoding encoding) + { + if (s == null) + throw new ArgumentNullException ("s"); + + if (s.Length == 0) + return s; + + var bytes = Encoding.ASCII.GetBytes (s); + return (encoding ?? Encoding.UTF8).GetString ( + urlDecodeToBytes (bytes, 0, bytes.Length) + ); + } + + public static string UrlDecode ( + byte[] bytes, int offset, int count, Encoding encoding + ) + { + if (bytes == null) + throw new ArgumentNullException ("bytes"); + + var len = bytes.Length; + if (len == 0) { + if (offset != 0) + throw new ArgumentOutOfRangeException ("offset"); + + if (count != 0) + throw new ArgumentOutOfRangeException ("count"); + + return String.Empty; + } + + if (offset < 0 || offset >= len) + throw new ArgumentOutOfRangeException ("offset"); + + if (count < 0 || count > len - offset) + throw new ArgumentOutOfRangeException ("count"); + + return count > 0 + ? (encoding ?? Encoding.UTF8).GetString ( + urlDecodeToBytes (bytes, offset, count) + ) + : String.Empty; + } + + public static byte[] UrlDecodeToBytes (byte[] bytes) + { + if (bytes == null) + throw new ArgumentNullException ("bytes"); + + var len = bytes.Length; + return len > 0 + ? urlDecodeToBytes (bytes, 0, len) + : bytes; + } + + public static byte[] UrlDecodeToBytes (string s) + { + if (s == null) + throw new ArgumentNullException ("s"); + + if (s.Length == 0) + return new byte[0]; + + var bytes = Encoding.ASCII.GetBytes (s); + return urlDecodeToBytes (bytes, 0, bytes.Length); + } + + public static byte[] UrlDecodeToBytes (byte[] bytes, int offset, int count) + { + if (bytes == null) + throw new ArgumentNullException ("bytes"); + + var len = bytes.Length; + if (len == 0) { + if (offset != 0) + throw new ArgumentOutOfRangeException ("offset"); + + if (count != 0) + throw new ArgumentOutOfRangeException ("count"); + + return bytes; + } + + if (offset < 0 || offset >= len) + throw new ArgumentOutOfRangeException ("offset"); + + if (count < 0 || count > len - offset) + throw new ArgumentOutOfRangeException ("count"); + + return count > 0 + ? urlDecodeToBytes (bytes, offset, count) + : new byte[0]; + } + + public static string UrlEncode (byte[] bytes) + { + if (bytes == null) + throw new ArgumentNullException ("bytes"); + + var len = bytes.Length; + return len > 0 + ? Encoding.ASCII.GetString (urlEncodeToBytes (bytes, 0, len)) + : String.Empty; + } + + public static string UrlEncode (string s) + { + return UrlEncode (s, Encoding.UTF8); + } + + public static string UrlEncode (string s, Encoding encoding) + { + if (s == null) + throw new ArgumentNullException ("s"); + + var len = s.Length; + if (len == 0) + return s; + + if (encoding == null) + encoding = Encoding.UTF8; + + var bytes = new byte[encoding.GetMaxByteCount (len)]; + var realLen = encoding.GetBytes (s, 0, len, bytes, 0); + + return Encoding.ASCII.GetString (urlEncodeToBytes (bytes, 0, realLen)); + } + + public static string UrlEncode (byte[] bytes, int offset, int count) + { + if (bytes == null) + throw new ArgumentNullException ("bytes"); + + var len = bytes.Length; + if (len == 0) { + if (offset != 0) + throw new ArgumentOutOfRangeException ("offset"); + + if (count != 0) + throw new ArgumentOutOfRangeException ("count"); + + return String.Empty; + } + + if (offset < 0 || offset >= len) + throw new ArgumentOutOfRangeException ("offset"); + + if (count < 0 || count > len - offset) + throw new ArgumentOutOfRangeException ("count"); + + return count > 0 + ? Encoding.ASCII.GetString ( + urlEncodeToBytes (bytes, offset, count) + ) + : String.Empty; + } + + public static byte[] UrlEncodeToBytes (byte[] bytes) + { + if (bytes == null) + throw new ArgumentNullException ("bytes"); + + var len = bytes.Length; + return len > 0 ? urlEncodeToBytes (bytes, 0, len) : bytes; + } + + public static byte[] UrlEncodeToBytes (string s) + { + return UrlEncodeToBytes (s, Encoding.UTF8); + } + + public static byte[] UrlEncodeToBytes (string s, Encoding encoding) + { + if (s == null) + throw new ArgumentNullException ("s"); + + if (s.Length == 0) + return new byte[0]; + + var bytes = (encoding ?? Encoding.UTF8).GetBytes (s); + return urlEncodeToBytes (bytes, 0, bytes.Length); + } + + public static byte[] UrlEncodeToBytes (byte[] bytes, int offset, int count) + { + if (bytes == null) + throw new ArgumentNullException ("bytes"); + + var len = bytes.Length; + if (len == 0) { + if (offset != 0) + throw new ArgumentOutOfRangeException ("offset"); + + if (count != 0) + throw new ArgumentOutOfRangeException ("count"); + + return bytes; + } + + if (offset < 0 || offset >= len) + throw new ArgumentOutOfRangeException ("offset"); + + if (count < 0 || count > len - offset) + throw new ArgumentOutOfRangeException ("count"); + + return count > 0 ? urlEncodeToBytes (bytes, offset, count) : new byte[0]; + } + + #endregion + } +} diff --git a/websocket-sharp/Net/HttpVersion.cs b/websocket-sharp/Net/HttpVersion.cs new file mode 100644 index 0000000..d20061e --- /dev/null +++ b/websocket-sharp/Net/HttpVersion.cs @@ -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 + */ +#endregion + +using System; + +namespace WebSocketSharp.Net +{ + /// + /// Provides the HTTP version numbers. + /// + public class HttpVersion + { + #region Public Fields + + /// + /// Provides a instance for the HTTP/1.0. + /// + public static readonly Version Version10 = new Version (1, 0); + + /// + /// Provides a instance for the HTTP/1.1. + /// + public static readonly Version Version11 = new Version (1, 1); + + #endregion + + #region Public Constructors + + /// + /// Initializes a new instance of the class. + /// + public HttpVersion () + { + } + + #endregion + } +} diff --git a/websocket-sharp/Net/InputChunkState.cs b/websocket-sharp/Net/InputChunkState.cs new file mode 100644 index 0000000..f50ad6b --- /dev/null +++ b/websocket-sharp/Net/InputChunkState.cs @@ -0,0 +1,52 @@ +#region License +/* + * InputChunkState.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 + */ +#endregion + +using System; + +namespace WebSocketSharp.Net +{ + internal enum InputChunkState + { + None, + Data, + DataEnded, + Trailer, + End + } +} diff --git a/websocket-sharp/Net/InputState.cs b/websocket-sharp/Net/InputState.cs new file mode 100644 index 0000000..9f566d2 --- /dev/null +++ b/websocket-sharp/Net/InputState.cs @@ -0,0 +1,49 @@ +#region License +/* + * InputState.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) 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 + */ +#endregion + +using System; + +namespace WebSocketSharp.Net +{ + internal enum InputState + { + RequestLine, + Headers + } +} diff --git a/websocket-sharp/Net/LineState.cs b/websocket-sharp/Net/LineState.cs new file mode 100644 index 0000000..84e271a --- /dev/null +++ b/websocket-sharp/Net/LineState.cs @@ -0,0 +1,50 @@ +#region License +/* + * LineState.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) 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 + */ +#endregion + +using System; + +namespace WebSocketSharp.Net +{ + internal enum LineState + { + None, + Cr, + Lf + } +} diff --git a/websocket-sharp/Net/NetworkCredential.cs b/websocket-sharp/Net/NetworkCredential.cs new file mode 100644 index 0000000..3ee52f4 --- /dev/null +++ b/websocket-sharp/Net/NetworkCredential.cs @@ -0,0 +1,209 @@ +#region License +/* + * NetworkCredential.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; + +namespace WebSocketSharp.Net +{ + /// + /// Provides the credentials for the password-based authentication. + /// + public class NetworkCredential + { + #region Private Fields + + private string _domain; + private static readonly string[] _noRoles; + private string _password; + private string[] _roles; + private string _username; + + #endregion + + #region Static Constructor + + static NetworkCredential () + { + _noRoles = new string[0]; + } + + #endregion + + #region Public Constructors + + /// + /// Initializes a new instance of the class with + /// the specified and . + /// + /// + /// A that represents the username associated with + /// the credentials. + /// + /// + /// A that represents the password for the username + /// associated with the credentials. + /// + /// + /// is . + /// + /// + /// is empty. + /// + public NetworkCredential (string username, string password) + : this (username, password, null, null) + { + } + + /// + /// Initializes a new instance of the class with + /// the specified , , + /// and . + /// + /// + /// A that represents the username associated with + /// the credentials. + /// + /// + /// A that represents the password for the username + /// associated with the credentials. + /// + /// + /// A that represents the domain associated with + /// the credentials. + /// + /// + /// An array of that represents the roles + /// associated with the credentials if any. + /// + /// + /// is . + /// + /// + /// is empty. + /// + public NetworkCredential ( + string username, string password, string domain, params string[] roles + ) + { + if (username == null) + throw new ArgumentNullException ("username"); + + if (username.Length == 0) + throw new ArgumentException ("An empty string.", "username"); + + _username = username; + _password = password; + _domain = domain; + _roles = roles; + } + + #endregion + + #region Public Properties + + /// + /// Gets the domain associated with the credentials. + /// + /// + /// This property returns an empty string if the domain was + /// initialized with . + /// + /// + /// A that represents the domain name + /// to which the username belongs. + /// + public string Domain { + get { + return _domain ?? String.Empty; + } + + internal set { + _domain = value; + } + } + + /// + /// Gets the password for the username associated with the credentials. + /// + /// + /// This property returns an empty string if the password was + /// initialized with . + /// + /// + /// A that represents the password. + /// + public string Password { + get { + return _password ?? String.Empty; + } + + internal set { + _password = value; + } + } + + /// + /// Gets the roles associated with the credentials. + /// + /// + /// This property returns an empty array if the roles were + /// initialized with . + /// + /// + /// An array of that represents the role names + /// to which the username belongs. + /// + public string[] Roles { + get { + return _roles ?? _noRoles; + } + + internal set { + _roles = value; + } + } + + /// + /// Gets the username associated with the credentials. + /// + /// + /// A that represents the username. + /// + public string Username { + get { + return _username; + } + + internal set { + _username = value; + } + } + + #endregion + } +} diff --git a/websocket-sharp/Net/QueryStringCollection.cs b/websocket-sharp/Net/QueryStringCollection.cs new file mode 100644 index 0000000..2e925e2 --- /dev/null +++ b/websocket-sharp/Net/QueryStringCollection.cs @@ -0,0 +1,150 @@ +#region License +/* + * QueryStringCollection.cs + * + * This code is derived from HttpUtility.cs (System.Net) of Mono + * (http://www.mono-project.com). + * + * The MIT License + * + * Copyright (c) 2005-2009 Novell, Inc. (http://www.novell.com) + * Copyright (c) 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: + * - Patrik Torstensson + * - Wictor Wilén (decode/encode functions) + * - Tim Coleman + * - Gonzalo Paniagua Javier + */ +#endregion + +using System; +using System.Collections.Specialized; +using System.Text; + +namespace WebSocketSharp.Net +{ + internal sealed class QueryStringCollection : NameValueCollection + { + #region Public Constructors + + public QueryStringCollection () + { + } + + public QueryStringCollection (int capacity) + : base (capacity) + { + } + + #endregion + + #region Private Methods + + private static string urlDecode (string s, Encoding encoding) + { + return s.IndexOfAny (new[] { '%', '+' }) > -1 + ? HttpUtility.UrlDecode (s, encoding) + : s; + } + + #endregion + + #region Public Methods + + public static QueryStringCollection Parse (string query) + { + return Parse (query, Encoding.UTF8); + } + + public static QueryStringCollection Parse (string query, Encoding encoding) + { + if (query == null) + return new QueryStringCollection (1); + + var len = query.Length; + if (len == 0) + return new QueryStringCollection (1); + + if (query == "?") + return new QueryStringCollection (1); + + if (query[0] == '?') + query = query.Substring (1); + + if (encoding == null) + encoding = Encoding.UTF8; + + var ret = new QueryStringCollection (); + + var components = query.Split ('&'); + foreach (var component in components) { + len = component.Length; + if (len == 0) + continue; + + if (component == "=") + continue; + + var i = component.IndexOf ('='); + if (i < 0) { + ret.Add (null, urlDecode (component, encoding)); + continue; + } + + if (i == 0) { + ret.Add (null, urlDecode (component.Substring (1), encoding)); + continue; + } + + var name = urlDecode (component.Substring (0, i), encoding); + + var start = i + 1; + var val = start < len + ? urlDecode (component.Substring (start), encoding) + : String.Empty; + + ret.Add (name, val); + } + + return ret; + } + + public override string ToString () + { + var buff = new StringBuilder (); + + foreach (var key in AllKeys) + buff.AppendFormat ("{0}={1}&", key, this[key]); + + if (buff.Length > 0) + buff.Length--; + + return buff.ToString (); + } + + #endregion + } +} diff --git a/websocket-sharp/Net/ReadBufferState.cs b/websocket-sharp/Net/ReadBufferState.cs new file mode 100644 index 0000000..780a69b --- /dev/null +++ b/websocket-sharp/Net/ReadBufferState.cs @@ -0,0 +1,124 @@ +#region License +/* + * ReadBufferState.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) 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 + */ +#endregion + +using System; + +namespace WebSocketSharp.Net +{ + internal class ReadBufferState + { + #region Private Fields + + private HttpStreamAsyncResult _asyncResult; + private byte[] _buffer; + private int _count; + private int _initialCount; + private int _offset; + + #endregion + + #region Public Constructors + + public ReadBufferState ( + byte[] buffer, int offset, int count, HttpStreamAsyncResult asyncResult) + { + _buffer = buffer; + _offset = offset; + _count = count; + _initialCount = count; + _asyncResult = asyncResult; + } + + #endregion + + #region Public Properties + + public HttpStreamAsyncResult AsyncResult { + get { + return _asyncResult; + } + + set { + _asyncResult = value; + } + } + + public byte[] Buffer { + get { + return _buffer; + } + + set { + _buffer = value; + } + } + + public int Count { + get { + return _count; + } + + set { + _count = value; + } + } + + public int InitialCount { + get { + return _initialCount; + } + + set { + _initialCount = value; + } + } + + public int Offset { + get { + return _offset; + } + + set { + _offset = value; + } + } + + #endregion + } +} diff --git a/websocket-sharp/Net/RequestStream.cs b/websocket-sharp/Net/RequestStream.cs new file mode 100644 index 0000000..dd40b37 --- /dev/null +++ b/websocket-sharp/Net/RequestStream.cs @@ -0,0 +1,267 @@ +#region License +/* + * RequestStream.cs + * + * This code is derived from RequestStream.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 + */ +#endregion + +using System; +using System.IO; + +namespace WebSocketSharp.Net +{ + internal class RequestStream : Stream + { + #region Private Fields + + private long _bodyLeft; + private byte[] _buffer; + private int _count; + private bool _disposed; + private int _offset; + private Stream _stream; + + #endregion + + #region Internal Constructors + + internal RequestStream (Stream stream, byte[] buffer, int offset, int count) + : this (stream, buffer, offset, count, -1) + { + } + + internal RequestStream ( + Stream stream, byte[] buffer, int offset, int count, long contentLength) + { + _stream = stream; + _buffer = buffer; + _offset = offset; + _count = count; + _bodyLeft = contentLength; + } + + #endregion + + #region Public Properties + + public override bool CanRead { + get { + return true; + } + } + + public override bool CanSeek { + get { + return false; + } + } + + public override bool CanWrite { + get { + return false; + } + } + + public override long Length { + get { + throw new NotSupportedException (); + } + } + + public override long Position { + get { + throw new NotSupportedException (); + } + + set { + throw new NotSupportedException (); + } + } + + #endregion + + #region Private Methods + + // Returns 0 if we can keep reading from the base stream, + // > 0 if we read something from the buffer, + // -1 if we had a content length set and we finished reading that many bytes. + private int fillFromBuffer (byte[] buffer, int offset, int count) + { + 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."); + + if (_bodyLeft == 0) + return -1; + + if (_count == 0 || count == 0) + return 0; + + if (count > _count) + count = _count; + + if (_bodyLeft > 0 && count > _bodyLeft) + count = (int) _bodyLeft; + + Buffer.BlockCopy (_buffer, _offset, buffer, offset, count); + _offset += count; + _count -= count; + if (_bodyLeft > 0) + _bodyLeft -= count; + + return count; + } + + #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 ()); + + var nread = fillFromBuffer (buffer, offset, count); + if (nread > 0 || nread == -1) { + var ares = new HttpStreamAsyncResult (callback, state); + ares.Buffer = buffer; + ares.Offset = offset; + ares.Count = count; + ares.SyncRead = nread > 0 ? nread : 0; + ares.Complete (); + + return ares; + } + + // Avoid reading past the end of the request to allow for HTTP pipelining. + if (_bodyLeft >= 0 && count > _bodyLeft) + count = (int) _bodyLeft; + + return _stream.BeginRead (buffer, offset, count, callback, state); + } + + public override IAsyncResult BeginWrite ( + byte[] buffer, int offset, int count, AsyncCallback callback, object state) + { + throw new NotSupportedException (); + } + + public override void Close () + { + _disposed = true; + } + + public override int EndRead (IAsyncResult asyncResult) + { + if (_disposed) + throw new ObjectDisposedException (GetType ().ToString ()); + + if (asyncResult == null) + throw new ArgumentNullException ("asyncResult"); + + if (asyncResult is HttpStreamAsyncResult) { + var ares = (HttpStreamAsyncResult) asyncResult; + if (!ares.IsCompleted) + ares.AsyncWaitHandle.WaitOne (); + + return ares.SyncRead; + } + + // Close on exception? + var nread = _stream.EndRead (asyncResult); + if (nread > 0 && _bodyLeft > 0) + _bodyLeft -= nread; + + return nread; + } + + public override void EndWrite (IAsyncResult asyncResult) + { + throw new NotSupportedException (); + } + + public override void Flush () + { + } + + public override int Read (byte[] buffer, int offset, int count) + { + if (_disposed) + throw new ObjectDisposedException (GetType ().ToString ()); + + // Call the fillFromBuffer method to check for buffer boundaries even when _bodyLeft is 0. + var nread = fillFromBuffer (buffer, offset, count); + if (nread == -1) // No more bytes available (Content-Length). + return 0; + + if (nread > 0) + return nread; + + nread = _stream.Read (buffer, offset, count); + if (nread > 0 && _bodyLeft > 0) + _bodyLeft -= nread; + + return nread; + } + + public override long Seek (long offset, SeekOrigin origin) + { + throw new NotSupportedException (); + } + + public override void SetLength (long value) + { + throw new NotSupportedException (); + } + + public override void Write (byte[] buffer, int offset, int count) + { + throw new NotSupportedException (); + } + + #endregion + } +} diff --git a/websocket-sharp/Net/ResponseStream.cs b/websocket-sharp/Net/ResponseStream.cs new file mode 100644 index 0000000..85059a4 --- /dev/null +++ b/websocket-sharp/Net/ResponseStream.cs @@ -0,0 +1,325 @@ +#region License +/* + * ResponseStream.cs + * + * This code is derived from ResponseStream.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 + */ +#endregion + +using System; +using System.IO; +using System.Text; + +namespace WebSocketSharp.Net +{ + internal class ResponseStream : Stream + { + #region Private Fields + + private MemoryStream _body; + private static readonly byte[] _crlf = new byte[] { 13, 10 }; + private bool _disposed; + private HttpListenerResponse _response; + private bool _sendChunked; + private Stream _stream; + private Action _write; + private Action _writeBody; + private Action _writeChunked; + + #endregion + + #region Internal Constructors + + internal ResponseStream ( + Stream stream, HttpListenerResponse response, bool ignoreWriteExceptions) + { + _stream = stream; + _response = response; + + if (ignoreWriteExceptions) { + _write = writeWithoutThrowingException; + _writeChunked = writeChunkedWithoutThrowingException; + } + else { + _write = stream.Write; + _writeChunked = writeChunked; + } + + _body = new MemoryStream (); + } + + #endregion + + #region Public Properties + + public override bool CanRead { + get { + return false; + } + } + + public override bool CanSeek { + get { + return false; + } + } + + public override bool CanWrite { + get { + return !_disposed; + } + } + + public override long Length { + get { + throw new NotSupportedException (); + } + } + + public override long Position { + get { + throw new NotSupportedException (); + } + + set { + throw new NotSupportedException (); + } + } + + #endregion + + #region Private Methods + + private bool flush (bool closing) + { + if (!_response.HeadersSent) { + if (!flushHeaders (closing)) { + if (closing) + _response.CloseConnection = true; + + return false; + } + + _sendChunked = _response.SendChunked; + _writeBody = _sendChunked ? _writeChunked : _write; + } + + flushBody (closing); + if (closing && _sendChunked) { + var last = getChunkSizeBytes (0, true); + _write (last, 0, last.Length); + } + + return true; + } + + private void flushBody (bool closing) + { + using (_body) { + var len = _body.Length; + if (len > Int32.MaxValue) { + _body.Position = 0; + var buffLen = 1024; + var buff = new byte[buffLen]; + var nread = 0; + while ((nread = _body.Read (buff, 0, buffLen)) > 0) + _writeBody (buff, 0, nread); + } + else if (len > 0) { + _writeBody (_body.GetBuffer (), 0, (int) len); + } + } + + _body = !closing ? new MemoryStream () : null; + } + + private bool flushHeaders (bool closing) + { + using (var buff = new MemoryStream ()) { + var headers = _response.WriteHeadersTo (buff); + var start = buff.Position; + var len = buff.Length - start; + if (len > 32768) + return false; + + if (!_response.SendChunked && _response.ContentLength64 != _body.Length) + return false; + + _write (buff.GetBuffer (), (int) start, (int) len); + _response.CloseConnection = headers["Connection"] == "close"; + _response.HeadersSent = true; + } + + return true; + } + + private static byte[] getChunkSizeBytes (int size, bool final) + { + return Encoding.ASCII.GetBytes (String.Format ("{0:x}\r\n{1}", size, final ? "\r\n" : "")); + } + + private void writeChunked (byte[] buffer, int offset, int count) + { + var size = getChunkSizeBytes (count, false); + _stream.Write (size, 0, size.Length); + _stream.Write (buffer, offset, count); + _stream.Write (_crlf, 0, 2); + } + + private void writeChunkedWithoutThrowingException (byte[] buffer, int offset, int count) + { + try { + writeChunked (buffer, offset, count); + } + catch { + } + } + + private void writeWithoutThrowingException (byte[] buffer, int offset, int count) + { + try { + _stream.Write (buffer, offset, count); + } + catch { + } + } + + #endregion + + #region Internal Methods + + internal void Close (bool force) + { + if (_disposed) + return; + + _disposed = true; + if (!force && flush (true)) { + _response.Close (); + } + else { + if (_sendChunked) { + var last = getChunkSizeBytes (0, true); + _write (last, 0, last.Length); + } + + _body.Dispose (); + _body = null; + + _response.Abort (); + } + + _response = null; + _stream = null; + } + + internal void InternalWrite (byte[] buffer, int offset, int count) + { + _write (buffer, offset, count); + } + + #endregion + + #region Public Methods + + public override IAsyncResult BeginRead ( + byte[] buffer, int offset, int count, AsyncCallback callback, object state) + { + throw new NotSupportedException (); + } + + public override IAsyncResult BeginWrite ( + byte[] buffer, int offset, int count, AsyncCallback callback, object state) + { + if (_disposed) + throw new ObjectDisposedException (GetType ().ToString ()); + + return _body.BeginWrite (buffer, offset, count, callback, state); + } + + public override void Close () + { + Close (false); + } + + protected override void Dispose (bool disposing) + { + Close (!disposing); + } + + public override int EndRead (IAsyncResult asyncResult) + { + throw new NotSupportedException (); + } + + public override void EndWrite (IAsyncResult asyncResult) + { + if (_disposed) + throw new ObjectDisposedException (GetType ().ToString ()); + + _body.EndWrite (asyncResult); + } + + public override void Flush () + { + if (!_disposed && (_sendChunked || _response.SendChunked)) + flush (false); + } + + public override int Read (byte[] buffer, int offset, int count) + { + throw new NotSupportedException (); + } + + public override long Seek (long offset, SeekOrigin origin) + { + throw new NotSupportedException (); + } + + public override void SetLength (long value) + { + throw new NotSupportedException (); + } + + public override void Write (byte[] buffer, int offset, int count) + { + if (_disposed) + throw new ObjectDisposedException (GetType ().ToString ()); + + _body.Write (buffer, offset, count); + } + + #endregion + } +} diff --git a/websocket-sharp/Net/ServerSslConfiguration.cs b/websocket-sharp/Net/ServerSslConfiguration.cs new file mode 100644 index 0000000..ad9b9e7 --- /dev/null +++ b/websocket-sharp/Net/ServerSslConfiguration.cs @@ -0,0 +1,245 @@ +#region License +/* + * ServerSslConfiguration.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 + */ +#endregion + +using System; +using System.Net.Security; +using System.Security.Authentication; +using System.Security.Cryptography.X509Certificates; + +namespace WebSocketSharp.Net +{ + /// + /// Stores the parameters for the used by servers. + /// + public class ServerSslConfiguration + { + #region Private Fields + + private bool _checkCertRevocation; + private bool _clientCertRequired; + private RemoteCertificateValidationCallback _clientCertValidationCallback; + private SslProtocols _enabledSslProtocols; + private X509Certificate2 _serverCert; + + #endregion + + #region Public Constructors + + /// + /// Initializes a new instance of the class. + /// + public ServerSslConfiguration () + { + _enabledSslProtocols = SslProtocols.Default; + } + + /// + /// Initializes a new instance of the class + /// with the specified . + /// + /// + /// A that represents the certificate used to + /// authenticate the server. + /// + public ServerSslConfiguration (X509Certificate2 serverCertificate) + { + _serverCert = serverCertificate; + _enabledSslProtocols = SslProtocols.Default; + } + + /// + /// Copies the parameters from the specified to + /// a new instance of the class. + /// + /// + /// A from which to copy. + /// + /// + /// is . + /// + public ServerSslConfiguration (ServerSslConfiguration configuration) + { + if (configuration == null) + throw new ArgumentNullException ("configuration"); + + _checkCertRevocation = configuration._checkCertRevocation; + _clientCertRequired = configuration._clientCertRequired; + _clientCertValidationCallback = configuration._clientCertValidationCallback; + _enabledSslProtocols = configuration._enabledSslProtocols; + _serverCert = configuration._serverCert; + } + + #endregion + + #region Public Properties + + /// + /// Gets or sets a value indicating whether the certificate revocation + /// list is checked during authentication. + /// + /// + /// + /// true if the certificate revocation list is checked during + /// authentication; otherwise, false. + /// + /// + /// The default value is false. + /// + /// + public bool CheckCertificateRevocation { + get { + return _checkCertRevocation; + } + + set { + _checkCertRevocation = value; + } + } + + /// + /// Gets or sets a value indicating whether the client is asked for + /// a certificate for authentication. + /// + /// + /// + /// true if the client is asked for a certificate for + /// authentication; otherwise, false. + /// + /// + /// The default value is false. + /// + /// + public bool ClientCertificateRequired { + get { + return _clientCertRequired; + } + + set { + _clientCertRequired = value; + } + } + + /// + /// Gets or sets the callback used to validate the certificate + /// supplied by the client. + /// + /// + /// The certificate is valid if the callback returns true. + /// + /// + /// + /// A delegate that + /// invokes the method called for validating the certificate. + /// + /// + /// The default value is a delegate that invokes a method that + /// only returns true. + /// + /// + public RemoteCertificateValidationCallback ClientCertificateValidationCallback { + get { + if (_clientCertValidationCallback == null) + _clientCertValidationCallback = defaultValidateClientCertificate; + + return _clientCertValidationCallback; + } + + set { + _clientCertValidationCallback = value; + } + } + + /// + /// Gets or sets the protocols used for authentication. + /// + /// + /// + /// The enum values that represent + /// the protocols used for authentication. + /// + /// + /// The default value is . + /// + /// + public SslProtocols EnabledSslProtocols { + get { + return _enabledSslProtocols; + } + + set { + _enabledSslProtocols = value; + } + } + + /// + /// Gets or sets the certificate used to authenticate the server. + /// + /// + /// + /// A or + /// if not specified. + /// + /// + /// That instance represents an X.509 certificate. + /// + /// + public X509Certificate2 ServerCertificate { + get { + return _serverCert; + } + + set { + _serverCert = value; + } + } + + #endregion + + #region Private Methods + + private static bool defaultValidateClientCertificate ( + object sender, + X509Certificate certificate, + X509Chain chain, + SslPolicyErrors sslPolicyErrors + ) + { + return true; + } + + #endregion + } +} diff --git a/websocket-sharp/Net/WebHeaderCollection.cs b/websocket-sharp/Net/WebHeaderCollection.cs new file mode 100644 index 0000000..8423d2f --- /dev/null +++ b/websocket-sharp/Net/WebHeaderCollection.cs @@ -0,0 +1,1459 @@ +#region License +/* + * WebHeaderCollection.cs + * + * This code is derived from WebHeaderCollection.cs (System.Net) of Mono + * (http://www.mono-project.com). + * + * The MIT License + * + * Copyright (c) 2003 Ximian, Inc. (http://www.ximian.com) + * Copyright (c) 2007 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: + * - Lawrence Pit + * - Gonzalo Paniagua Javier + * - Miguel de Icaza + */ +#endregion + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Runtime.InteropServices; +using System.Runtime.Serialization; +using System.Security.Permissions; +using System.Text; + +namespace WebSocketSharp.Net +{ + /// + /// Provides a collection of the HTTP headers associated with a request or response. + /// + [Serializable] + [ComVisible (true)] + public class WebHeaderCollection : NameValueCollection, ISerializable + { + #region Private Fields + + private static readonly Dictionary _headers; + private bool _internallyUsed; + private HttpHeaderType _state; + + #endregion + + #region Static Constructor + + static WebHeaderCollection () + { + _headers = + new Dictionary (StringComparer.InvariantCultureIgnoreCase) { + { + "Accept", + new HttpHeaderInfo ( + "Accept", + HttpHeaderType.Request | HttpHeaderType.Restricted | HttpHeaderType.MultiValue) + }, + { + "AcceptCharset", + new HttpHeaderInfo ( + "Accept-Charset", + HttpHeaderType.Request | HttpHeaderType.MultiValue) + }, + { + "AcceptEncoding", + new HttpHeaderInfo ( + "Accept-Encoding", + HttpHeaderType.Request | HttpHeaderType.MultiValue) + }, + { + "AcceptLanguage", + new HttpHeaderInfo ( + "Accept-Language", + HttpHeaderType.Request | HttpHeaderType.MultiValue) + }, + { + "AcceptRanges", + new HttpHeaderInfo ( + "Accept-Ranges", + HttpHeaderType.Response | HttpHeaderType.MultiValue) + }, + { + "Age", + new HttpHeaderInfo ( + "Age", + HttpHeaderType.Response) + }, + { + "Allow", + new HttpHeaderInfo ( + "Allow", + HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.MultiValue) + }, + { + "Authorization", + new HttpHeaderInfo ( + "Authorization", + HttpHeaderType.Request | HttpHeaderType.MultiValue) + }, + { + "CacheControl", + new HttpHeaderInfo ( + "Cache-Control", + HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.MultiValue) + }, + { + "Connection", + new HttpHeaderInfo ( + "Connection", + HttpHeaderType.Request | + HttpHeaderType.Response | + HttpHeaderType.Restricted | + HttpHeaderType.MultiValue) + }, + { + "ContentEncoding", + new HttpHeaderInfo ( + "Content-Encoding", + HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.MultiValue) + }, + { + "ContentLanguage", + new HttpHeaderInfo ( + "Content-Language", + HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.MultiValue) + }, + { + "ContentLength", + new HttpHeaderInfo ( + "Content-Length", + HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.Restricted) + }, + { + "ContentLocation", + new HttpHeaderInfo ( + "Content-Location", + HttpHeaderType.Request | HttpHeaderType.Response) + }, + { + "ContentMd5", + new HttpHeaderInfo ( + "Content-MD5", + HttpHeaderType.Request | HttpHeaderType.Response) + }, + { + "ContentRange", + new HttpHeaderInfo ( + "Content-Range", + HttpHeaderType.Request | HttpHeaderType.Response) + }, + { + "ContentType", + new HttpHeaderInfo ( + "Content-Type", + HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.Restricted) + }, + { + "Cookie", + new HttpHeaderInfo ( + "Cookie", + HttpHeaderType.Request) + }, + { + "Cookie2", + new HttpHeaderInfo ( + "Cookie2", + HttpHeaderType.Request) + }, + { + "Date", + new HttpHeaderInfo ( + "Date", + HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.Restricted) + }, + { + "Expect", + new HttpHeaderInfo ( + "Expect", + HttpHeaderType.Request | HttpHeaderType.Restricted | HttpHeaderType.MultiValue) + }, + { + "Expires", + new HttpHeaderInfo ( + "Expires", + HttpHeaderType.Request | HttpHeaderType.Response) + }, + { + "ETag", + new HttpHeaderInfo ( + "ETag", + HttpHeaderType.Response) + }, + { + "From", + new HttpHeaderInfo ( + "From", + HttpHeaderType.Request) + }, + { + "Host", + new HttpHeaderInfo ( + "Host", + HttpHeaderType.Request | HttpHeaderType.Restricted) + }, + { + "IfMatch", + new HttpHeaderInfo ( + "If-Match", + HttpHeaderType.Request | HttpHeaderType.MultiValue) + }, + { + "IfModifiedSince", + new HttpHeaderInfo ( + "If-Modified-Since", + HttpHeaderType.Request | HttpHeaderType.Restricted) + }, + { + "IfNoneMatch", + new HttpHeaderInfo ( + "If-None-Match", + HttpHeaderType.Request | HttpHeaderType.MultiValue) + }, + { + "IfRange", + new HttpHeaderInfo ( + "If-Range", + HttpHeaderType.Request) + }, + { + "IfUnmodifiedSince", + new HttpHeaderInfo ( + "If-Unmodified-Since", + HttpHeaderType.Request) + }, + { + "KeepAlive", + new HttpHeaderInfo ( + "Keep-Alive", + HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.MultiValue) + }, + { + "LastModified", + new HttpHeaderInfo ( + "Last-Modified", + HttpHeaderType.Request | HttpHeaderType.Response) + }, + { + "Location", + new HttpHeaderInfo ( + "Location", + HttpHeaderType.Response) + }, + { + "MaxForwards", + new HttpHeaderInfo ( + "Max-Forwards", + HttpHeaderType.Request) + }, + { + "Pragma", + new HttpHeaderInfo ( + "Pragma", + HttpHeaderType.Request | HttpHeaderType.Response) + }, + { + "ProxyAuthenticate", + new HttpHeaderInfo ( + "Proxy-Authenticate", + HttpHeaderType.Response | HttpHeaderType.MultiValue) + }, + { + "ProxyAuthorization", + new HttpHeaderInfo ( + "Proxy-Authorization", + HttpHeaderType.Request) + }, + { + "ProxyConnection", + new HttpHeaderInfo ( + "Proxy-Connection", + HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.Restricted) + }, + { + "Public", + new HttpHeaderInfo ( + "Public", + HttpHeaderType.Response | HttpHeaderType.MultiValue) + }, + { + "Range", + new HttpHeaderInfo ( + "Range", + HttpHeaderType.Request | HttpHeaderType.Restricted | HttpHeaderType.MultiValue) + }, + { + "Referer", + new HttpHeaderInfo ( + "Referer", + HttpHeaderType.Request | HttpHeaderType.Restricted) + }, + { + "RetryAfter", + new HttpHeaderInfo ( + "Retry-After", + HttpHeaderType.Response) + }, + { + "SecWebSocketAccept", + new HttpHeaderInfo ( + "Sec-WebSocket-Accept", + HttpHeaderType.Response | HttpHeaderType.Restricted) + }, + { + "SecWebSocketExtensions", + new HttpHeaderInfo ( + "Sec-WebSocket-Extensions", + HttpHeaderType.Request | + HttpHeaderType.Response | + HttpHeaderType.Restricted | + HttpHeaderType.MultiValueInRequest) + }, + { + "SecWebSocketKey", + new HttpHeaderInfo ( + "Sec-WebSocket-Key", + HttpHeaderType.Request | HttpHeaderType.Restricted) + }, + { + "SecWebSocketProtocol", + new HttpHeaderInfo ( + "Sec-WebSocket-Protocol", + HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.MultiValueInRequest) + }, + { + "SecWebSocketVersion", + new HttpHeaderInfo ( + "Sec-WebSocket-Version", + HttpHeaderType.Request | + HttpHeaderType.Response | + HttpHeaderType.Restricted | + HttpHeaderType.MultiValueInResponse) + }, + { + "Server", + new HttpHeaderInfo ( + "Server", + HttpHeaderType.Response) + }, + { + "SetCookie", + new HttpHeaderInfo ( + "Set-Cookie", + HttpHeaderType.Response | HttpHeaderType.MultiValue) + }, + { + "SetCookie2", + new HttpHeaderInfo ( + "Set-Cookie2", + HttpHeaderType.Response | HttpHeaderType.MultiValue) + }, + { + "Te", + new HttpHeaderInfo ( + "TE", + HttpHeaderType.Request) + }, + { + "Trailer", + new HttpHeaderInfo ( + "Trailer", + HttpHeaderType.Request | HttpHeaderType.Response) + }, + { + "TransferEncoding", + new HttpHeaderInfo ( + "Transfer-Encoding", + HttpHeaderType.Request | + HttpHeaderType.Response | + HttpHeaderType.Restricted | + HttpHeaderType.MultiValue) + }, + { + "Translate", + new HttpHeaderInfo ( + "Translate", + HttpHeaderType.Request) + }, + { + "Upgrade", + new HttpHeaderInfo ( + "Upgrade", + HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.MultiValue) + }, + { + "UserAgent", + new HttpHeaderInfo ( + "User-Agent", + HttpHeaderType.Request | HttpHeaderType.Restricted) + }, + { + "Vary", + new HttpHeaderInfo ( + "Vary", + HttpHeaderType.Response | HttpHeaderType.MultiValue) + }, + { + "Via", + new HttpHeaderInfo ( + "Via", + HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.MultiValue) + }, + { + "Warning", + new HttpHeaderInfo ( + "Warning", + HttpHeaderType.Request | HttpHeaderType.Response | HttpHeaderType.MultiValue) + }, + { + "WwwAuthenticate", + new HttpHeaderInfo ( + "WWW-Authenticate", + HttpHeaderType.Response | HttpHeaderType.Restricted | HttpHeaderType.MultiValue) + } + }; + } + + #endregion + + #region Internal Constructors + + internal WebHeaderCollection (HttpHeaderType state, bool internallyUsed) + { + _state = state; + _internallyUsed = internallyUsed; + } + + #endregion + + #region Protected Constructors + + /// + /// Initializes a new instance of the class from + /// the specified and . + /// + /// + /// A that contains the serialized object data. + /// + /// + /// A that specifies the source for the deserialization. + /// + /// + /// is . + /// + /// + /// An element with the specified name isn't found in . + /// + protected WebHeaderCollection ( + SerializationInfo serializationInfo, StreamingContext streamingContext) + { + if (serializationInfo == null) + throw new ArgumentNullException ("serializationInfo"); + + try { + _internallyUsed = serializationInfo.GetBoolean ("InternallyUsed"); + _state = (HttpHeaderType) serializationInfo.GetInt32 ("State"); + + var cnt = serializationInfo.GetInt32 ("Count"); + for (var i = 0; i < cnt; i++) { + base.Add ( + serializationInfo.GetString (i.ToString ()), + serializationInfo.GetString ((cnt + i).ToString ())); + } + } + catch (SerializationException ex) { + throw new ArgumentException (ex.Message, "serializationInfo", ex); + } + } + + #endregion + + #region Public Constructors + + /// + /// Initializes a new instance of the class. + /// + public WebHeaderCollection () + { + } + + #endregion + + #region Internal Properties + + internal HttpHeaderType State { + get { + return _state; + } + } + + #endregion + + #region Public Properties + + /// + /// Gets all header names in the collection. + /// + /// + /// An array of that contains all header names in the collection. + /// + public override string[] AllKeys { + get { + return base.AllKeys; + } + } + + /// + /// Gets the number of headers in the collection. + /// + /// + /// An that represents the number of headers in the collection. + /// + public override int Count { + get { + return base.Count; + } + } + + /// + /// Gets or sets the specified request in the collection. + /// + /// + /// A that represents the value of the request . + /// + /// + /// One of the enum values, represents + /// the request header to get or set. + /// + /// + /// + /// is a restricted header. + /// + /// + /// -or- + /// + /// + /// contains invalid characters. + /// + /// + /// + /// The length of is greater than 65,535 characters. + /// + /// + /// The current instance doesn't allow + /// the request . + /// + public string this[HttpRequestHeader header] { + get { + return Get (Convert (header)); + } + + set { + Add (header, value); + } + } + + /// + /// Gets or sets the specified response in the collection. + /// + /// + /// A that represents the value of the response . + /// + /// + /// One of the enum values, represents + /// the response header to get or set. + /// + /// + /// + /// is a restricted header. + /// + /// + /// -or- + /// + /// + /// contains invalid characters. + /// + /// + /// + /// The length of is greater than 65,535 characters. + /// + /// + /// The current instance doesn't allow + /// the response . + /// + public string this[HttpResponseHeader header] { + get { + return Get (Convert (header)); + } + + set { + Add (header, value); + } + } + + /// + /// Gets a collection of header names in the collection. + /// + /// + /// A that contains + /// all header names in the collection. + /// + public override NameObjectCollectionBase.KeysCollection Keys { + get { + return base.Keys; + } + } + + #endregion + + #region Private Methods + + private void add (string name, string value, bool ignoreRestricted) + { + var act = ignoreRestricted + ? (Action ) addWithoutCheckingNameAndRestricted + : addWithoutCheckingName; + + doWithCheckingState (act, checkName (name), value, true); + } + + private void addWithoutCheckingName (string name, string value) + { + doWithoutCheckingName (base.Add, name, value); + } + + private void addWithoutCheckingNameAndRestricted (string name, string value) + { + base.Add (name, checkValue (value)); + } + + private static int checkColonSeparated (string header) + { + var idx = header.IndexOf (':'); + if (idx == -1) + throw new ArgumentException ("No colon could be found.", "header"); + + return idx; + } + + private static HttpHeaderType checkHeaderType (string name) + { + var info = getHeaderInfo (name); + return info == null + ? HttpHeaderType.Unspecified + : info.IsRequest && !info.IsResponse + ? HttpHeaderType.Request + : !info.IsRequest && info.IsResponse + ? HttpHeaderType.Response + : HttpHeaderType.Unspecified; + } + + private static string checkName (string name) + { + if (name == null || name.Length == 0) + throw new ArgumentNullException ("name"); + + name = name.Trim (); + if (!IsHeaderName (name)) + throw new ArgumentException ("Contains invalid characters.", "name"); + + return name; + } + + private void checkRestricted (string name) + { + if (!_internallyUsed && isRestricted (name, true)) + throw new ArgumentException ("This header must be modified with the appropiate property."); + } + + private void checkState (bool response) + { + if (_state == HttpHeaderType.Unspecified) + return; + + if (response && _state == HttpHeaderType.Request) + throw new InvalidOperationException ( + "This collection has already been used to store the request headers."); + + if (!response && _state == HttpHeaderType.Response) + throw new InvalidOperationException ( + "This collection has already been used to store the response headers."); + } + + private static string checkValue (string value) + { + if (value == null || value.Length == 0) + return String.Empty; + + value = value.Trim (); + if (value.Length > 65535) + throw new ArgumentOutOfRangeException ("value", "Greater than 65,535 characters."); + + if (!IsHeaderValue (value)) + throw new ArgumentException ("Contains invalid characters.", "value"); + + return value; + } + + private static string convert (string key) + { + HttpHeaderInfo info; + return _headers.TryGetValue (key, out info) ? info.Name : String.Empty; + } + + private void doWithCheckingState ( + Action action, string name, string value, bool setState) + { + var type = checkHeaderType (name); + if (type == HttpHeaderType.Request) + doWithCheckingState (action, name, value, false, setState); + else if (type == HttpHeaderType.Response) + doWithCheckingState (action, name, value, true, setState); + else + action (name, value); + } + + private void doWithCheckingState ( + Action action, string name, string value, bool response, bool setState) + { + checkState (response); + action (name, value); + if (setState && _state == HttpHeaderType.Unspecified) + _state = response ? HttpHeaderType.Response : HttpHeaderType.Request; + } + + private void doWithoutCheckingName (Action action, string name, string value) + { + checkRestricted (name); + action (name, checkValue (value)); + } + + private static HttpHeaderInfo getHeaderInfo (string name) + { + foreach (var info in _headers.Values) + if (info.Name.Equals (name, StringComparison.InvariantCultureIgnoreCase)) + return info; + + return null; + } + + private static bool isRestricted (string name, bool response) + { + var info = getHeaderInfo (name); + return info != null && info.IsRestricted (response); + } + + private void removeWithoutCheckingName (string name, string unuse) + { + checkRestricted (name); + base.Remove (name); + } + + private void setWithoutCheckingName (string name, string value) + { + doWithoutCheckingName (base.Set, name, value); + } + + #endregion + + #region Internal Methods + + internal static string Convert (HttpRequestHeader header) + { + return convert (header.ToString ()); + } + + internal static string Convert (HttpResponseHeader header) + { + return convert (header.ToString ()); + } + + internal void InternalRemove (string name) + { + base.Remove (name); + } + + internal void InternalSet (string header, bool response) + { + var pos = checkColonSeparated (header); + InternalSet (header.Substring (0, pos), header.Substring (pos + 1), response); + } + + internal void InternalSet (string name, string value, bool response) + { + value = checkValue (value); + if (IsMultiValue (name, response)) + base.Add (name, value); + else + base.Set (name, value); + } + + internal static bool IsHeaderName (string name) + { + return name != null && name.Length > 0 && name.IsToken (); + } + + internal static bool IsHeaderValue (string value) + { + return value.IsText (); + } + + internal static bool IsMultiValue (string headerName, bool response) + { + if (headerName == null || headerName.Length == 0) + return false; + + var info = getHeaderInfo (headerName); + return info != null && info.IsMultiValue (response); + } + + internal string ToStringMultiValue (bool response) + { + var buff = new StringBuilder (); + Count.Times ( + i => { + var key = GetKey (i); + if (IsMultiValue (key, response)) + foreach (var val in GetValues (i)) + buff.AppendFormat ("{0}: {1}\r\n", key, val); + else + buff.AppendFormat ("{0}: {1}\r\n", key, Get (i)); + }); + + return buff.Append ("\r\n").ToString (); + } + + #endregion + + #region Protected Methods + + /// + /// Adds a header to the collection without checking if the header is on + /// the restricted header list. + /// + /// + /// A that represents the name of the header to add. + /// + /// + /// A that represents the value of the header to add. + /// + /// + /// is or empty. + /// + /// + /// or contains invalid characters. + /// + /// + /// The length of is greater than 65,535 characters. + /// + /// + /// The current instance doesn't allow + /// the . + /// + protected void AddWithoutValidate (string headerName, string headerValue) + { + add (headerName, headerValue, true); + } + + #endregion + + #region Public Methods + + /// + /// Adds the specified to the collection. + /// + /// + /// A that represents the header with the name and value separated by + /// a colon (':'). + /// + /// + /// is , empty, or the name part of + /// is empty. + /// + /// + /// + /// doesn't contain a colon. + /// + /// + /// -or- + /// + /// + /// is a restricted header. + /// + /// + /// -or- + /// + /// + /// The name or value part of contains invalid characters. + /// + /// + /// + /// The length of the value part of is greater than 65,535 characters. + /// + /// + /// The current instance doesn't allow + /// the . + /// + public void Add (string header) + { + if (header == null || header.Length == 0) + throw new ArgumentNullException ("header"); + + var pos = checkColonSeparated (header); + add (header.Substring (0, pos), header.Substring (pos + 1), false); + } + + /// + /// Adds the specified request with + /// the specified to the collection. + /// + /// + /// One of the enum values, represents + /// the request header to add. + /// + /// + /// A that represents the value of the header to add. + /// + /// + /// + /// is a restricted header. + /// + /// + /// -or- + /// + /// + /// contains invalid characters. + /// + /// + /// + /// The length of is greater than 65,535 characters. + /// + /// + /// The current instance doesn't allow + /// the request . + /// + public void Add (HttpRequestHeader header, string value) + { + doWithCheckingState (addWithoutCheckingName, Convert (header), value, false, true); + } + + /// + /// Adds the specified response with + /// the specified to the collection. + /// + /// + /// One of the enum values, represents + /// the response header to add. + /// + /// + /// A that represents the value of the header to add. + /// + /// + /// + /// is a restricted header. + /// + /// + /// -or- + /// + /// + /// contains invalid characters. + /// + /// + /// + /// The length of is greater than 65,535 characters. + /// + /// + /// The current instance doesn't allow + /// the response . + /// + public void Add (HttpResponseHeader header, string value) + { + doWithCheckingState (addWithoutCheckingName, Convert (header), value, true, true); + } + + /// + /// Adds a header with the specified and + /// to the collection. + /// + /// + /// A that represents the name of the header to add. + /// + /// + /// A that represents the value of the header to add. + /// + /// + /// is or empty. + /// + /// + /// + /// or contains invalid characters. + /// + /// + /// -or- + /// + /// + /// is a restricted header name. + /// + /// + /// + /// The length of is greater than 65,535 characters. + /// + /// + /// The current instance doesn't allow + /// the header . + /// + public override void Add (string name, string value) + { + add (name, value, false); + } + + /// + /// Removes all headers from the collection. + /// + public override void Clear () + { + base.Clear (); + _state = HttpHeaderType.Unspecified; + } + + /// + /// Get the value of the header at the specified in the collection. + /// + /// + /// A that receives the value of the header. + /// + /// + /// An that represents the zero-based index of the header to find. + /// + /// + /// is out of allowable range of indexes for the collection. + /// + public override string Get (int index) + { + return base.Get (index); + } + + /// + /// Get the value of the header with the specified in the collection. + /// + /// + /// A that receives the value of the header if found; + /// otherwise, . + /// + /// + /// A that represents the name of the header to find. + /// + public override string Get (string name) + { + return base.Get (name); + } + + /// + /// Gets the enumerator used to iterate through the collection. + /// + /// + /// An instance used to iterate through the collection. + /// + public override IEnumerator GetEnumerator () + { + return base.GetEnumerator (); + } + + /// + /// Get the name of the header at the specified in the collection. + /// + /// + /// A that receives the header name. + /// + /// + /// An that represents the zero-based index of the header to find. + /// + /// + /// is out of allowable range of indexes for the collection. + /// + public override string GetKey (int index) + { + return base.GetKey (index); + } + + /// + /// Gets an array of header values stored in the specified position of + /// the collection. + /// + /// + /// An array of that receives the header values if found; + /// otherwise, . + /// + /// + /// An that represents the zero-based index of the header to find. + /// + /// + /// is out of allowable range of indexes for the collection. + /// + public override string[] GetValues (int index) + { + var vals = base.GetValues (index); + return vals != null && vals.Length > 0 ? vals : null; + } + + /// + /// Gets an array of header values stored in the specified . + /// + /// + /// An array of that receives the header values if found; + /// otherwise, . + /// + /// + /// A that represents the name of the header to find. + /// + public override string[] GetValues (string header) + { + var vals = base.GetValues (header); + return vals != null && vals.Length > 0 ? vals : null; + } + + /// + /// Populates the specified with the data needed to serialize + /// the . + /// + /// + /// A that holds the serialized object data. + /// + /// + /// A that specifies the destination for the serialization. + /// + /// + /// is . + /// + [SecurityPermission ( + SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)] + public override void GetObjectData ( + SerializationInfo serializationInfo, StreamingContext streamingContext) + { + if (serializationInfo == null) + throw new ArgumentNullException ("serializationInfo"); + + serializationInfo.AddValue ("InternallyUsed", _internallyUsed); + serializationInfo.AddValue ("State", (int) _state); + + var cnt = Count; + serializationInfo.AddValue ("Count", cnt); + cnt.Times ( + i => { + serializationInfo.AddValue (i.ToString (), GetKey (i)); + serializationInfo.AddValue ((cnt + i).ToString (), Get (i)); + }); + } + + /// + /// Determines whether the specified header can be set for the request. + /// + /// + /// true if the header is restricted; otherwise, false. + /// + /// + /// A that represents the name of the header to test. + /// + /// + /// is or empty. + /// + /// + /// contains invalid characters. + /// + public static bool IsRestricted (string headerName) + { + return isRestricted (checkName (headerName), false); + } + + /// + /// Determines whether the specified header can be set for the request or the response. + /// + /// + /// true if the header is restricted; otherwise, false. + /// + /// + /// A that represents the name of the header to test. + /// + /// + /// true if does the test for the response; for the request, false. + /// + /// + /// is or empty. + /// + /// + /// contains invalid characters. + /// + public static bool IsRestricted (string headerName, bool response) + { + return isRestricted (checkName (headerName), response); + } + + /// + /// Implements the interface and raises the deserialization event + /// when the deserialization is complete. + /// + /// + /// An that represents the source of the deserialization event. + /// + public override void OnDeserialization (object sender) + { + } + + /// + /// Removes the specified request from the collection. + /// + /// + /// One of the enum values, represents + /// the request header to remove. + /// + /// + /// is a restricted header. + /// + /// + /// The current instance doesn't allow + /// the request . + /// + public void Remove (HttpRequestHeader header) + { + doWithCheckingState (removeWithoutCheckingName, Convert (header), null, false, false); + } + + /// + /// Removes the specified response from the collection. + /// + /// + /// One of the enum values, represents + /// the response header to remove. + /// + /// + /// is a restricted header. + /// + /// + /// The current instance doesn't allow + /// the response . + /// + public void Remove (HttpResponseHeader header) + { + doWithCheckingState (removeWithoutCheckingName, Convert (header), null, true, false); + } + + /// + /// Removes the specified header from the collection. + /// + /// + /// A that represents the name of the header to remove. + /// + /// + /// is or empty. + /// + /// + /// + /// contains invalid characters. + /// + /// + /// -or- + /// + /// + /// is a restricted header name. + /// + /// + /// + /// The current instance doesn't allow + /// the header . + /// + public override void Remove (string name) + { + doWithCheckingState (removeWithoutCheckingName, checkName (name), null, false); + } + + /// + /// Sets the specified request to the specified value. + /// + /// + /// One of the enum values, represents + /// the request header to set. + /// + /// + /// A that represents the value of the request header to set. + /// + /// + /// + /// is a restricted header. + /// + /// + /// -or- + /// + /// + /// contains invalid characters. + /// + /// + /// + /// The length of is greater than 65,535 characters. + /// + /// + /// The current instance doesn't allow + /// the request . + /// + public void Set (HttpRequestHeader header, string value) + { + doWithCheckingState (setWithoutCheckingName, Convert (header), value, false, true); + } + + /// + /// Sets the specified response to the specified value. + /// + /// + /// One of the enum values, represents + /// the response header to set. + /// + /// + /// A that represents the value of the response header to set. + /// + /// + /// + /// is a restricted header. + /// + /// + /// -or- + /// + /// + /// contains invalid characters. + /// + /// + /// + /// The length of is greater than 65,535 characters. + /// + /// + /// The current instance doesn't allow + /// the response . + /// + public void Set (HttpResponseHeader header, string value) + { + doWithCheckingState (setWithoutCheckingName, Convert (header), value, true, true); + } + + /// + /// Sets the specified header to the specified value. + /// + /// + /// A that represents the name of the header to set. + /// + /// + /// A that represents the value of the header to set. + /// + /// + /// is or empty. + /// + /// + /// + /// or contains invalid characters. + /// + /// + /// -or- + /// + /// + /// is a restricted header name. + /// + /// + /// + /// The length of is greater than 65,535 characters. + /// + /// + /// The current instance doesn't allow + /// the header . + /// + public override void Set (string name, string value) + { + doWithCheckingState (setWithoutCheckingName, checkName (name), value, true); + } + + /// + /// Converts the current to an array of . + /// + /// + /// An array of that receives the converted current + /// . + /// + public byte[] ToByteArray () + { + return Encoding.UTF8.GetBytes (ToString ()); + } + + /// + /// Returns a that represents the current + /// . + /// + /// + /// A that represents the current . + /// + public override string ToString () + { + var buff = new StringBuilder (); + Count.Times (i => buff.AppendFormat ("{0}: {1}\r\n", GetKey (i), Get (i))); + + return buff.Append ("\r\n").ToString (); + } + + #endregion + + #region Explicit Interface Implementations + + /// + /// Populates the specified with the data needed to serialize + /// the current . + /// + /// + /// A that holds the serialized object data. + /// + /// + /// A that specifies the destination for the serialization. + /// + /// + /// is . + /// + [SecurityPermission ( + SecurityAction.LinkDemand, + Flags = SecurityPermissionFlag.SerializationFormatter, + SerializationFormatter = true)] + void ISerializable.GetObjectData ( + SerializationInfo serializationInfo, StreamingContext streamingContext) + { + GetObjectData (serializationInfo, streamingContext); + } + + #endregion + } +} diff --git a/websocket-sharp/Net/WebSockets/HttpListenerWebSocketContext.cs b/websocket-sharp/Net/WebSockets/HttpListenerWebSocketContext.cs new file mode 100644 index 0000000..eed49ce --- /dev/null +++ b/websocket-sharp/Net/WebSockets/HttpListenerWebSocketContext.cs @@ -0,0 +1,394 @@ +#region License +/* + * HttpListenerWebSocketContext.cs + * + * The MIT License + * + * 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 + +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.IO; +using System.Security.Principal; + +namespace WebSocketSharp.Net.WebSockets +{ + /// + /// Provides the access to the information in a WebSocket handshake request to + /// a instance. + /// + public class HttpListenerWebSocketContext : WebSocketContext + { + #region Private Fields + + private HttpListenerContext _context; + private WebSocket _websocket; + + #endregion + + #region Internal Constructors + + internal HttpListenerWebSocketContext ( + HttpListenerContext context, string protocol + ) + { + _context = context; + _websocket = new WebSocket (this, protocol); + } + + #endregion + + #region Internal Properties + + internal Logger Log { + get { + return _context.Listener.Log; + } + } + + internal Stream Stream { + get { + return _context.Connection.Stream; + } + } + + #endregion + + #region Public Properties + + /// + /// Gets the HTTP cookies included in the handshake request. + /// + /// + /// + /// A that contains + /// the cookies. + /// + /// + /// An empty collection if not included. + /// + /// + public override CookieCollection CookieCollection { + get { + return _context.Request.Cookies; + } + } + + /// + /// Gets the HTTP headers included in the handshake request. + /// + /// + /// A that contains the headers. + /// + public override NameValueCollection Headers { + get { + return _context.Request.Headers; + } + } + + /// + /// Gets the value of the Host header included in the handshake request. + /// + /// + /// + /// A that represents the server host name requested + /// by the client. + /// + /// + /// It includes the port number if provided. + /// + /// + public override string Host { + get { + return _context.Request.UserHostName; + } + } + + /// + /// Gets a value indicating whether the client is authenticated. + /// + /// + /// true if the client is authenticated; otherwise, false. + /// + public override bool IsAuthenticated { + get { + return _context.Request.IsAuthenticated; + } + } + + /// + /// Gets a value indicating whether the handshake request is sent from + /// the local computer. + /// + /// + /// true if the handshake request is sent from the same computer + /// as the server; otherwise, false. + /// + public override bool IsLocal { + get { + return _context.Request.IsLocal; + } + } + + /// + /// Gets a value indicating whether a secure connection is used to send + /// the handshake request. + /// + /// + /// true if the connection is secure; otherwise, false. + /// + public override bool IsSecureConnection { + get { + return _context.Request.IsSecureConnection; + } + } + + /// + /// Gets a value indicating whether the request is a WebSocket handshake + /// request. + /// + /// + /// true if the request is a WebSocket handshake request; otherwise, + /// false. + /// + public override bool IsWebSocketRequest { + get { + return _context.Request.IsWebSocketRequest; + } + } + + /// + /// Gets the value of the Origin header included in the handshake request. + /// + /// + /// + /// A that represents the value of the Origin header. + /// + /// + /// if the header is not present. + /// + /// + public override string Origin { + get { + return _context.Request.Headers["Origin"]; + } + } + + /// + /// Gets the query string included in the handshake request. + /// + /// + /// + /// A that contains the query + /// parameters. + /// + /// + /// An empty collection if not included. + /// + /// + public override NameValueCollection QueryString { + get { + return _context.Request.QueryString; + } + } + + /// + /// Gets the URI requested by the client. + /// + /// + /// + /// A that represents the URI parsed from the request. + /// + /// + /// if the URI cannot be parsed. + /// + /// + public override Uri RequestUri { + get { + return _context.Request.Url; + } + } + + /// + /// Gets the value of the Sec-WebSocket-Key header included in + /// the handshake request. + /// + /// + /// + /// A that represents the value of + /// the Sec-WebSocket-Key header. + /// + /// + /// The value is used to prove that the server received + /// a valid WebSocket handshake request. + /// + /// + /// if the header is not present. + /// + /// + public override string SecWebSocketKey { + get { + return _context.Request.Headers["Sec-WebSocket-Key"]; + } + } + + /// + /// Gets the names of the subprotocols from the Sec-WebSocket-Protocol + /// header included in the handshake request. + /// + /// + /// + /// An + /// instance. + /// + /// + /// It provides an enumerator which supports the iteration over + /// the collection of the names of the subprotocols. + /// + /// + public override IEnumerable SecWebSocketProtocols { + get { + var val = _context.Request.Headers["Sec-WebSocket-Protocol"]; + if (val == null || val.Length == 0) + yield break; + + foreach (var elm in val.Split (',')) { + var protocol = elm.Trim (); + if (protocol.Length == 0) + continue; + + yield return protocol; + } + } + } + + /// + /// Gets the value of the Sec-WebSocket-Version header included in + /// the handshake request. + /// + /// + /// + /// A that represents the WebSocket protocol + /// version specified by the client. + /// + /// + /// if the header is not present. + /// + /// + public override string SecWebSocketVersion { + get { + return _context.Request.Headers["Sec-WebSocket-Version"]; + } + } + + /// + /// Gets the endpoint to which the handshake request is sent. + /// + /// + /// A that represents the server IP + /// address and port number. + /// + public override System.Net.IPEndPoint ServerEndPoint { + get { + return _context.Request.LocalEndPoint; + } + } + + /// + /// Gets the client information. + /// + /// + /// + /// A instance that represents identity, + /// authentication, and security roles for the client. + /// + /// + /// if the client is not authenticated. + /// + /// + public override IPrincipal User { + get { + return _context.User; + } + } + + /// + /// Gets the endpoint from which the handshake request is sent. + /// + /// + /// A that represents the client IP + /// address and port number. + /// + public override System.Net.IPEndPoint UserEndPoint { + get { + return _context.Request.RemoteEndPoint; + } + } + + /// + /// Gets the WebSocket instance used for two-way communication between + /// the client and server. + /// + /// + /// A . + /// + public override WebSocket WebSocket { + get { + return _websocket; + } + } + + #endregion + + #region Internal Methods + + internal void Close () + { + _context.Connection.Close (true); + } + + internal void Close (HttpStatusCode code) + { + _context.Response.Close (code); + } + + #endregion + + #region Public Methods + + /// + /// Returns a string that represents the current instance. + /// + /// + /// A that contains the request line and headers + /// included in the handshake request. + /// + public override string ToString () + { + return _context.Request.ToString (); + } + + #endregion + } +} diff --git a/websocket-sharp/Net/WebSockets/TcpListenerWebSocketContext.cs b/websocket-sharp/Net/WebSockets/TcpListenerWebSocketContext.cs new file mode 100644 index 0000000..519da78 --- /dev/null +++ b/websocket-sharp/Net/WebSockets/TcpListenerWebSocketContext.cs @@ -0,0 +1,518 @@ +#region License +/* + * TcpListenerWebSocketContext.cs + * + * The MIT License + * + * 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 Contributors +/* + * Contributors: + * - Liryna + */ +#endregion + +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.IO; +using System.Net.Security; +using System.Net.Sockets; +using System.Security.Principal; +using System.Text; + +namespace WebSocketSharp.Net.WebSockets +{ + /// + /// Provides the access to the information in a WebSocket handshake request to + /// a instance. + /// + internal class TcpListenerWebSocketContext : WebSocketContext + { + #region Private Fields + + private Logger _log; + private NameValueCollection _queryString; + private HttpRequest _request; + private Uri _requestUri; + private bool _secure; + private System.Net.EndPoint _serverEndPoint; + private Stream _stream; + private TcpClient _tcpClient; + private IPrincipal _user; + private System.Net.EndPoint _userEndPoint; + private WebSocket _websocket; + + #endregion + + #region Internal Constructors + + internal TcpListenerWebSocketContext ( + TcpClient tcpClient, + string protocol, + bool secure, + ServerSslConfiguration sslConfig, + Logger log + ) + { + _tcpClient = tcpClient; + _secure = secure; + _log = log; + + var netStream = tcpClient.GetStream (); + if (secure) { + var sslStream = new SslStream ( + netStream, + false, + sslConfig.ClientCertificateValidationCallback + ); + + sslStream.AuthenticateAsServer ( + sslConfig.ServerCertificate, + sslConfig.ClientCertificateRequired, + sslConfig.EnabledSslProtocols, + sslConfig.CheckCertificateRevocation + ); + + _stream = sslStream; + } + else { + _stream = netStream; + } + + var sock = tcpClient.Client; + _serverEndPoint = sock.LocalEndPoint; + _userEndPoint = sock.RemoteEndPoint; + + _request = HttpRequest.Read (_stream, 90000); + _websocket = new WebSocket (this, protocol); + } + + #endregion + + #region Internal Properties + + internal Logger Log { + get { + return _log; + } + } + + internal Stream Stream { + get { + return _stream; + } + } + + #endregion + + #region Public Properties + + /// + /// Gets the HTTP cookies included in the handshake request. + /// + /// + /// + /// A that contains + /// the cookies. + /// + /// + /// An empty collection if not included. + /// + /// + public override CookieCollection CookieCollection { + get { + return _request.Cookies; + } + } + + /// + /// Gets the HTTP headers included in the handshake request. + /// + /// + /// A that contains the headers. + /// + public override NameValueCollection Headers { + get { + return _request.Headers; + } + } + + /// + /// Gets the value of the Host header included in the handshake request. + /// + /// + /// + /// A that represents the server host name requested + /// by the client. + /// + /// + /// It includes the port number if provided. + /// + /// + public override string Host { + get { + return _request.Headers["Host"]; + } + } + + /// + /// Gets a value indicating whether the client is authenticated. + /// + /// + /// true if the client is authenticated; otherwise, false. + /// + public override bool IsAuthenticated { + get { + return _user != null; + } + } + + /// + /// Gets a value indicating whether the handshake request is sent from + /// the local computer. + /// + /// + /// true if the handshake request is sent from the same computer + /// as the server; otherwise, false. + /// + public override bool IsLocal { + get { + return UserEndPoint.Address.IsLocal (); + } + } + + /// + /// Gets a value indicating whether a secure connection is used to send + /// the handshake request. + /// + /// + /// true if the connection is secure; otherwise, false. + /// + public override bool IsSecureConnection { + get { + return _secure; + } + } + + /// + /// Gets a value indicating whether the request is a WebSocket handshake + /// request. + /// + /// + /// true if the request is a WebSocket handshake request; otherwise, + /// false. + /// + public override bool IsWebSocketRequest { + get { + return _request.IsWebSocketRequest; + } + } + + /// + /// Gets the value of the Origin header included in the handshake request. + /// + /// + /// + /// A that represents the value of the Origin header. + /// + /// + /// if the header is not present. + /// + /// + public override string Origin { + get { + return _request.Headers["Origin"]; + } + } + + /// + /// Gets the query string included in the handshake request. + /// + /// + /// + /// A that contains the query + /// parameters. + /// + /// + /// An empty collection if not included. + /// + /// + public override NameValueCollection QueryString { + get { + if (_queryString == null) { + var uri = RequestUri; + _queryString = QueryStringCollection.Parse ( + uri != null ? uri.Query : null, + Encoding.UTF8 + ); + } + + return _queryString; + } + } + + /// + /// Gets the URI requested by the client. + /// + /// + /// + /// A that represents the URI parsed from the request. + /// + /// + /// if the URI cannot be parsed. + /// + /// + public override Uri RequestUri { + get { + if (_requestUri == null) { + _requestUri = HttpUtility.CreateRequestUrl ( + _request.RequestUri, + _request.Headers["Host"], + _request.IsWebSocketRequest, + _secure + ); + } + + return _requestUri; + } + } + + /// + /// Gets the value of the Sec-WebSocket-Key header included in + /// the handshake request. + /// + /// + /// + /// A that represents the value of + /// the Sec-WebSocket-Key header. + /// + /// + /// The value is used to prove that the server received + /// a valid WebSocket handshake request. + /// + /// + /// if the header is not present. + /// + /// + public override string SecWebSocketKey { + get { + return _request.Headers["Sec-WebSocket-Key"]; + } + } + + /// + /// Gets the names of the subprotocols from the Sec-WebSocket-Protocol + /// header included in the handshake request. + /// + /// + /// + /// An + /// instance. + /// + /// + /// It provides an enumerator which supports the iteration over + /// the collection of the names of the subprotocols. + /// + /// + public override IEnumerable SecWebSocketProtocols { + get { + var val = _request.Headers["Sec-WebSocket-Protocol"]; + if (val == null || val.Length == 0) + yield break; + + foreach (var elm in val.Split (',')) { + var protocol = elm.Trim (); + if (protocol.Length == 0) + continue; + + yield return protocol; + } + } + } + + /// + /// Gets the value of the Sec-WebSocket-Version header included in + /// the handshake request. + /// + /// + /// + /// A that represents the WebSocket protocol + /// version specified by the client. + /// + /// + /// if the header is not present. + /// + /// + public override string SecWebSocketVersion { + get { + return _request.Headers["Sec-WebSocket-Version"]; + } + } + + /// + /// Gets the endpoint to which the handshake request is sent. + /// + /// + /// A that represents the server IP + /// address and port number. + /// + public override System.Net.IPEndPoint ServerEndPoint { + get { + return (System.Net.IPEndPoint) _serverEndPoint; + } + } + + /// + /// Gets the client information. + /// + /// + /// + /// A instance that represents identity, + /// authentication, and security roles for the client. + /// + /// + /// if the client is not authenticated. + /// + /// + public override IPrincipal User { + get { + return _user; + } + } + + /// + /// Gets the endpoint from which the handshake request is sent. + /// + /// + /// A that represents the client IP + /// address and port number. + /// + public override System.Net.IPEndPoint UserEndPoint { + get { + return (System.Net.IPEndPoint) _userEndPoint; + } + } + + /// + /// Gets the WebSocket instance used for two-way communication between + /// the client and server. + /// + /// + /// A . + /// + public override WebSocket WebSocket { + get { + return _websocket; + } + } + + #endregion + + #region Private Methods + + private HttpRequest sendAuthenticationChallenge (string challenge) + { + var res = HttpResponse.CreateUnauthorizedResponse (challenge); + var bytes = res.ToByteArray (); + _stream.Write (bytes, 0, bytes.Length); + + return HttpRequest.Read (_stream, 15000); + } + + #endregion + + #region Internal Methods + + internal bool Authenticate ( + AuthenticationSchemes scheme, + string realm, + Func credentialsFinder + ) + { + var chal = new AuthenticationChallenge (scheme, realm).ToString (); + + var retry = -1; + Func auth = null; + auth = + () => { + retry++; + if (retry > 99) + return false; + + var user = HttpUtility.CreateUser ( + _request.Headers["Authorization"], + scheme, + realm, + _request.HttpMethod, + credentialsFinder + ); + + if (user != null && user.Identity.IsAuthenticated) { + _user = user; + return true; + } + + _request = sendAuthenticationChallenge (chal); + return auth (); + }; + + return auth (); + } + + internal void Close () + { + _stream.Close (); + _tcpClient.Close (); + } + + internal void Close (HttpStatusCode code) + { + var res = HttpResponse.CreateCloseResponse (code); + var bytes = res.ToByteArray (); + _stream.Write (bytes, 0, bytes.Length); + + _stream.Close (); + _tcpClient.Close (); + } + + #endregion + + #region Public Methods + + /// + /// Returns a string that represents the current instance. + /// + /// + /// A that contains the request line and headers + /// included in the handshake request. + /// + public override string ToString () + { + return _request.ToString (); + } + + #endregion + } +} diff --git a/websocket-sharp/Net/WebSockets/WebSocketContext.cs b/websocket-sharp/Net/WebSockets/WebSocketContext.cs new file mode 100644 index 0000000..6921891 --- /dev/null +++ b/websocket-sharp/Net/WebSockets/WebSocketContext.cs @@ -0,0 +1,224 @@ +#region License +/* + * WebSocketContext.cs + * + * The MIT License + * + * 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 + +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Security.Principal; + +namespace WebSocketSharp.Net.WebSockets +{ + /// + /// Exposes the access to the information in a WebSocket handshake request. + /// + /// + /// This class is an abstract class. + /// + public abstract class WebSocketContext + { + #region Protected Constructors + + /// + /// Initializes a new instance of the class. + /// + protected WebSocketContext () + { + } + + #endregion + + #region Public Properties + + /// + /// Gets the HTTP cookies included in the handshake request. + /// + /// + /// A that contains + /// the cookies. + /// + public abstract CookieCollection CookieCollection { get; } + + /// + /// Gets the HTTP headers included in the handshake request. + /// + /// + /// A that contains the headers. + /// + public abstract NameValueCollection Headers { get; } + + /// + /// Gets the value of the Host header included in the handshake request. + /// + /// + /// A that represents the server host name requested + /// by the client. + /// + public abstract string Host { get; } + + /// + /// Gets a value indicating whether the client is authenticated. + /// + /// + /// true if the client is authenticated; otherwise, false. + /// + public abstract bool IsAuthenticated { get; } + + /// + /// Gets a value indicating whether the handshake request is sent from + /// the local computer. + /// + /// + /// true if the handshake request is sent from the same computer + /// as the server; otherwise, false. + /// + public abstract bool IsLocal { get; } + + /// + /// Gets a value indicating whether a secure connection is used to send + /// the handshake request. + /// + /// + /// true if the connection is secure; otherwise, false. + /// + public abstract bool IsSecureConnection { get; } + + /// + /// Gets a value indicating whether the request is a WebSocket handshake + /// request. + /// + /// + /// true if the request is a WebSocket handshake request; otherwise, + /// false. + /// + public abstract bool IsWebSocketRequest { get; } + + /// + /// Gets the value of the Origin header included in the handshake request. + /// + /// + /// A that represents the value of the Origin header. + /// + public abstract string Origin { get; } + + /// + /// Gets the query string included in the handshake request. + /// + /// + /// A that contains the query parameters. + /// + public abstract NameValueCollection QueryString { get; } + + /// + /// Gets the URI requested by the client. + /// + /// + /// A that represents the URI parsed from the request. + /// + public abstract Uri RequestUri { get; } + + /// + /// Gets the value of the Sec-WebSocket-Key header included in + /// the handshake request. + /// + /// + /// + /// A that represents the value of + /// the Sec-WebSocket-Key header. + /// + /// + /// The value is used to prove that the server received + /// a valid WebSocket handshake request. + /// + /// + public abstract string SecWebSocketKey { get; } + + /// + /// Gets the names of the subprotocols from the Sec-WebSocket-Protocol + /// header included in the handshake request. + /// + /// + /// + /// An + /// instance. + /// + /// + /// It provides an enumerator which supports the iteration over + /// the collection of the names of the subprotocols. + /// + /// + public abstract IEnumerable SecWebSocketProtocols { get; } + + /// + /// Gets the value of the Sec-WebSocket-Version header included in + /// the handshake request. + /// + /// + /// A that represents the WebSocket protocol + /// version specified by the client. + /// + public abstract string SecWebSocketVersion { get; } + + /// + /// Gets the endpoint to which the handshake request is sent. + /// + /// + /// A that represents the server IP + /// address and port number. + /// + public abstract System.Net.IPEndPoint ServerEndPoint { get; } + + /// + /// Gets the client information. + /// + /// + /// A instance that represents identity, + /// authentication, and security roles for the client. + /// + public abstract IPrincipal User { get; } + + /// + /// Gets the endpoint from which the handshake request is sent. + /// + /// + /// A that represents the client IP + /// address and port number. + /// + public abstract System.Net.IPEndPoint UserEndPoint { get; } + + /// + /// Gets the WebSocket instance used for two-way communication between + /// the client and server. + /// + /// + /// A . + /// + public abstract WebSocket WebSocket { get; } + + #endregion + } +} diff --git a/websocket-sharp/Opcode.cs b/websocket-sharp/Opcode.cs new file mode 100644 index 0000000..5a8c632 --- /dev/null +++ b/websocket-sharp/Opcode.cs @@ -0,0 +1,68 @@ +#region License +/* + * Opcode.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 +{ + /// + /// Indicates the WebSocket frame type. + /// + /// + /// The values of this enumeration are defined in + /// + /// Section 5.2 of RFC 6455. + /// + internal enum Opcode : byte + { + /// + /// Equivalent to numeric value 0. Indicates continuation frame. + /// + Cont = 0x0, + /// + /// Equivalent to numeric value 1. Indicates text frame. + /// + Text = 0x1, + /// + /// Equivalent to numeric value 2. Indicates binary frame. + /// + Binary = 0x2, + /// + /// Equivalent to numeric value 8. Indicates connection close frame. + /// + Close = 0x8, + /// + /// Equivalent to numeric value 9. Indicates ping frame. + /// + Ping = 0x9, + /// + /// Equivalent to numeric value 10. Indicates pong frame. + /// + Pong = 0xa + } +} diff --git a/websocket-sharp/PayloadData.cs b/websocket-sharp/PayloadData.cs new file mode 100644 index 0000000..4e629d8 --- /dev/null +++ b/websocket-sharp/PayloadData.cs @@ -0,0 +1,234 @@ +#region License +/* + * PayloadData.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; +using System.Collections; +using System.Collections.Generic; + +namespace WebSocketSharp +{ + internal class PayloadData : IEnumerable + { + #region Private Fields + + private ushort _code; + private bool _codeSet; + private byte[] _data; + private long _extDataLength; + private long _length; + private string _reason; + private bool _reasonSet; + + #endregion + + #region Public Fields + + /// + /// Represents the empty payload data. + /// + public static readonly PayloadData Empty; + + /// + /// Represents the allowable max length. + /// + /// + /// + /// A will occur if the payload data length is + /// greater than the value of this field. + /// + /// + /// If you would like to change the value, you must set it to a value between + /// WebSocket.FragmentLength and Int64.MaxValue inclusive. + /// + /// + public static readonly ulong MaxLength; + + #endregion + + #region Static Constructor + + static PayloadData () + { + Empty = new PayloadData (); + MaxLength = Int64.MaxValue; + } + + #endregion + + #region Internal Constructors + + internal PayloadData () + { + _code = 1005; + _reason = String.Empty; + + _data = WebSocket.EmptyBytes; + + _codeSet = true; + _reasonSet = true; + } + + internal PayloadData (byte[] data) + : this (data, data.LongLength) + { + } + + internal PayloadData (byte[] data, long length) + { + _data = data; + _length = length; + } + + internal PayloadData (ushort code, string reason) + { + _code = code; + _reason = reason ?? String.Empty; + + _data = code.Append (reason); + _length = _data.LongLength; + + _codeSet = true; + _reasonSet = true; + } + + #endregion + + #region Internal Properties + + internal ushort Code { + get { + if (!_codeSet) { + _code = _length > 1 + ? _data.SubArray (0, 2).ToUInt16 (ByteOrder.Big) + : (ushort) 1005; + + _codeSet = true; + } + + return _code; + } + } + + internal long ExtensionDataLength { + get { + return _extDataLength; + } + + set { + _extDataLength = value; + } + } + + internal bool HasReservedCode { + get { + return _length > 1 && Code.IsReserved (); + } + } + + internal string Reason { + get { + if (!_reasonSet) { + _reason = _length > 2 + ? _data.SubArray (2, _length - 2).UTF8Decode () + : String.Empty; + + _reasonSet = true; + } + + return _reason; + } + } + + #endregion + + #region Public Properties + + public byte[] ApplicationData { + get { + return _extDataLength > 0 + ? _data.SubArray (_extDataLength, _length - _extDataLength) + : _data; + } + } + + public byte[] ExtensionData { + get { + return _extDataLength > 0 + ? _data.SubArray (0, _extDataLength) + : WebSocket.EmptyBytes; + } + } + + public ulong Length { + get { + return (ulong) _length; + } + } + + #endregion + + #region Internal Methods + + internal void Mask (byte[] key) + { + for (long i = 0; i < _length; i++) + _data[i] = (byte) (_data[i] ^ key[i % 4]); + } + + #endregion + + #region Public Methods + + public IEnumerator GetEnumerator () + { + foreach (var b in _data) + yield return b; + } + + public byte[] ToArray () + { + return _data; + } + + public override string ToString () + { + return BitConverter.ToString (_data); + } + + #endregion + + #region Explicit Interface Implementations + + IEnumerator IEnumerable.GetEnumerator () + { + return GetEnumerator (); + } + + #endregion + } +} diff --git a/websocket-sharp/Rsv.cs b/websocket-sharp/Rsv.cs new file mode 100644 index 0000000..8a10567 --- /dev/null +++ b/websocket-sharp/Rsv.cs @@ -0,0 +1,51 @@ +#region License +/* + * Rsv.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 +{ + /// + /// Indicates whether each RSV (RSV1, RSV2, and RSV3) of a WebSocket frame is non-zero. + /// + /// + /// The values of this enumeration are defined in + /// Section 5.2 of RFC 6455. + /// + internal enum Rsv : byte + { + /// + /// Equivalent to numeric value 0. Indicates zero. + /// + Off = 0x0, + /// + /// Equivalent to numeric value 1. Indicates non-zero. + /// + On = 0x1 + } +} diff --git a/websocket-sharp/Server/HttpRequestEventArgs.cs b/websocket-sharp/Server/HttpRequestEventArgs.cs new file mode 100644 index 0000000..ee76cba --- /dev/null +++ b/websocket-sharp/Server/HttpRequestEventArgs.cs @@ -0,0 +1,255 @@ +#region License +/* + * HttpRequestEventArgs.cs + * + * The MIT License + * + * Copyright (c) 2012-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.IO; +using System.Security.Principal; +using System.Text; +using WebSocketSharp.Net; + +namespace WebSocketSharp.Server +{ + /// + /// Represents the event data for the HTTP request events of + /// the . + /// + /// + /// + /// An HTTP request event occurs when the + /// receives an HTTP request. + /// + /// + /// You should access the property if you would + /// like to get the request data sent from a client. + /// + /// + /// And you should access the property if you would + /// like to get the response data to return to the client. + /// + /// + public class HttpRequestEventArgs : EventArgs + { + #region Private Fields + + private HttpListenerContext _context; + private string _docRootPath; + + #endregion + + #region Internal Constructors + + internal HttpRequestEventArgs ( + HttpListenerContext context, string documentRootPath + ) + { + _context = context; + _docRootPath = documentRootPath; + } + + #endregion + + #region Public Properties + + /// + /// Gets the request data sent from a client. + /// + /// + /// A that provides the methods and + /// properties for the request data. + /// + public HttpListenerRequest Request { + get { + return _context.Request; + } + } + + /// + /// Gets the response data to return to the client. + /// + /// + /// A that provides the methods and + /// properties for the response data. + /// + public HttpListenerResponse Response { + get { + return _context.Response; + } + } + + /// + /// Gets the information for the client. + /// + /// + /// + /// A instance or + /// if not authenticated. + /// + /// + /// That instance describes the identity, authentication scheme, + /// and security roles for the client. + /// + /// + public IPrincipal User { + get { + return _context.User; + } + } + + #endregion + + #region Private Methods + + private string createFilePath (string childPath) + { + childPath = childPath.TrimStart ('/', '\\'); + return new StringBuilder (_docRootPath, 32) + .AppendFormat ("/{0}", childPath) + .ToString () + .Replace ('\\', '/'); + } + + private static bool tryReadFile (string path, out byte[] contents) + { + contents = null; + + if (!File.Exists (path)) + return false; + + try { + contents = File.ReadAllBytes (path); + } + catch { + return false; + } + + return true; + } + + #endregion + + #region Public Methods + + /// + /// Reads the specified file from the document folder of + /// the . + /// + /// + /// + /// An array of or + /// if it fails. + /// + /// + /// That array receives the contents of the file. + /// + /// + /// + /// A that represents a virtual path to + /// find the file from the document folder. + /// + /// + /// is . + /// + /// + /// + /// is an empty string. + /// + /// + /// -or- + /// + /// + /// contains "..". + /// + /// + public byte[] ReadFile (string path) + { + if (path == null) + throw new ArgumentNullException ("path"); + + if (path.Length == 0) + throw new ArgumentException ("An empty string.", "path"); + + if (path.IndexOf ("..") > -1) + throw new ArgumentException ("It contains '..'.", "path"); + + byte[] contents; + tryReadFile (createFilePath (path), out contents); + + return contents; + } + + /// + /// Tries to read the specified file from the document folder of + /// the . + /// + /// + /// true if it succeeds to read; otherwise, false. + /// + /// + /// A that represents a virtual path to + /// find the file from the document folder. + /// + /// + /// + /// When this method returns, an array of or + /// if it fails. + /// + /// + /// That array receives the contents of the file. + /// + /// + /// + /// is . + /// + /// + /// + /// is an empty string. + /// + /// + /// -or- + /// + /// + /// contains "..". + /// + /// + public bool TryReadFile (string path, out byte[] contents) + { + if (path == null) + throw new ArgumentNullException ("path"); + + if (path.Length == 0) + throw new ArgumentException ("An empty string.", "path"); + + if (path.IndexOf ("..") > -1) + throw new ArgumentException ("It contains '..'.", "path"); + + return tryReadFile (createFilePath (path), out contents); + } + + #endregion + } +} diff --git a/websocket-sharp/Server/HttpServer.cs b/websocket-sharp/Server/HttpServer.cs new file mode 100644 index 0000000..56925ac --- /dev/null +++ b/websocket-sharp/Server/HttpServer.cs @@ -0,0 +1,1652 @@ +#region License +/* + * HttpServer.cs + * + * A simple HTTP server that allows to accept WebSocket handshake requests. + * + * 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: + * - Juan Manuel Lallana + * - Liryna + * - Rohan Singh + */ +#endregion + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Net.Sockets; +using System.Security.Cryptography.X509Certificates; +using System.Security.Principal; +using System.Text; +using System.Threading; +using WebSocketSharp.Net; +using WebSocketSharp.Net.WebSockets; + +namespace WebSocketSharp.Server +{ + /// + /// Provides a simple HTTP server that allows to accept + /// WebSocket handshake requests. + /// + /// + /// This class can provide multiple WebSocket services. + /// + public class HttpServer + { + #region Private Fields + + private System.Net.IPAddress _address; + private string _docRootPath; + private string _hostname; + private HttpListener _listener; + private Logger _log; + private int _port; + private Thread _receiveThread; + private bool _secure; + private WebSocketServiceManager _services; + private volatile ServerState _state; + private object _sync; + + #endregion + + #region Public Constructors + + /// + /// Initializes a new instance of the class. + /// + /// + /// The new instance listens for incoming requests on + /// and port 80. + /// + public HttpServer () + { + init ("*", System.Net.IPAddress.Any, 80, false); + } + + /// + /// Initializes a new instance of the class with + /// the specified . + /// + /// + /// + /// The new instance listens for incoming requests on + /// and . + /// + /// + /// It provides secure connections if is 443. + /// + /// + /// + /// An that represents the number of the port + /// on which to listen. + /// + /// + /// is less than 1 or greater than 65535. + /// + public HttpServer (int port) + : this (port, port == 443) + { + } + + /// + /// Initializes a new instance of the class with + /// the specified . + /// + /// + /// + /// The new instance listens for incoming requests on the IP address of the + /// host of and the port of . + /// + /// + /// Either port 80 or 443 is used if includes + /// no port. Port 443 is used if the scheme of + /// is https; otherwise, port 80 is used. + /// + /// + /// The new instance provides secure connections if the scheme of + /// is https. + /// + /// + /// + /// A that represents the HTTP URL of the server. + /// + /// + /// is . + /// + /// + /// + /// is empty. + /// + /// + /// -or- + /// + /// + /// is invalid. + /// + /// + public HttpServer (string url) + { + if (url == null) + throw new ArgumentNullException ("url"); + + if (url.Length == 0) + throw new ArgumentException ("An empty string.", "url"); + + Uri uri; + string msg; + if (!tryCreateUri (url, out uri, out msg)) + throw new ArgumentException (msg, "url"); + + var host = uri.GetDnsSafeHost (true); + + var addr = host.ToIPAddress (); + if (addr == null) { + msg = "The host part could not be converted to an IP address."; + throw new ArgumentException (msg, "url"); + } + + if (!addr.IsLocal ()) { + msg = "The IP address of the host is not a local IP address."; + throw new ArgumentException (msg, "url"); + } + + init (host, addr, uri.Port, uri.Scheme == "https"); + } + + /// + /// Initializes a new instance of the class with + /// the specified and . + /// + /// + /// The new instance listens for incoming requests on + /// and . + /// + /// + /// An that represents the number of the port + /// on which to listen. + /// + /// + /// A : true if the new instance provides + /// secure connections; otherwise, false. + /// + /// + /// is less than 1 or greater than 65535. + /// + public HttpServer (int port, bool secure) + { + if (!port.IsPortNumber ()) { + var msg = "Less than 1 or greater than 65535."; + throw new ArgumentOutOfRangeException ("port", msg); + } + + init ("*", System.Net.IPAddress.Any, port, secure); + } + + /// + /// Initializes a new instance of the class with + /// the specified and . + /// + /// + /// + /// The new instance listens for incoming requests on + /// and . + /// + /// + /// It provides secure connections if is 443. + /// + /// + /// + /// A that represents + /// the local IP address on which to listen. + /// + /// + /// An that represents the number of the port + /// on which to listen. + /// + /// + /// is . + /// + /// + /// is not a local IP address. + /// + /// + /// is less than 1 or greater than 65535. + /// + public HttpServer (System.Net.IPAddress address, int port) + : this (address, port, port == 443) + { + } + + /// + /// Initializes a new instance of the class with + /// the specified , , + /// and . + /// + /// + /// The new instance listens for incoming requests on + /// and . + /// + /// + /// A that represents + /// the local IP address on which to listen. + /// + /// + /// An that represents the number of the port + /// on which to listen. + /// + /// + /// A : true if the new instance provides + /// secure connections; otherwise, false. + /// + /// + /// is . + /// + /// + /// is not a local IP address. + /// + /// + /// is less than 1 or greater than 65535. + /// + public HttpServer (System.Net.IPAddress address, int port, bool secure) + { + if (address == null) + throw new ArgumentNullException ("address"); + + if (!address.IsLocal ()) + throw new ArgumentException ("Not a local IP address.", "address"); + + if (!port.IsPortNumber ()) { + var msg = "Less than 1 or greater than 65535."; + throw new ArgumentOutOfRangeException ("port", msg); + } + + init (address.ToString (true), address, port, secure); + } + + #endregion + + #region Public Properties + + /// + /// Gets the IP address of the server. + /// + /// + /// A that represents the local + /// IP address on which to listen for incoming requests. + /// + public System.Net.IPAddress Address { + get { + return _address; + } + } + + /// + /// Gets or sets the scheme used to authenticate the clients. + /// + /// + /// The set operation does nothing if the server has already + /// started or it is shutting down. + /// + /// + /// + /// One of the + /// enum values. + /// + /// + /// It represents the scheme used to authenticate the clients. + /// + /// + /// The default value is + /// . + /// + /// + public AuthenticationSchemes AuthenticationSchemes { + get { + return _listener.AuthenticationSchemes; + } + + set { + string msg; + if (!canSet (out msg)) { + _log.Warn (msg); + return; + } + + lock (_sync) { + if (!canSet (out msg)) { + _log.Warn (msg); + return; + } + + _listener.AuthenticationSchemes = value; + } + } + } + + /// + /// Gets or sets the path to the document folder of the server. + /// + /// + /// + /// '/' or '\' is trimmed from the end of the value if any. + /// + /// + /// The set operation does nothing if the server has already + /// started or it is shutting down. + /// + /// + /// + /// + /// A that represents a path to the folder + /// from which to find the requested file. + /// + /// + /// The default value is "./Public". + /// + /// + /// + /// The value specified for a set operation is . + /// + /// + /// + /// The value specified for a set operation is an empty string. + /// + /// + /// -or- + /// + /// + /// The value specified for a set operation is an invalid path string. + /// + /// + /// -or- + /// + /// + /// The value specified for a set operation is an absolute root. + /// + /// + public string DocumentRootPath { + get { + return _docRootPath; + } + + set { + if (value == null) + throw new ArgumentNullException ("value"); + + if (value.Length == 0) + throw new ArgumentException ("An empty string.", "value"); + + value = value.TrimSlashOrBackslashFromEnd (); + + string full = null; + try { + full = Path.GetFullPath (value); + } + catch (Exception ex) { + throw new ArgumentException ("An invalid path string.", "value", ex); + } + + if (value == "/") + throw new ArgumentException ("An absolute root.", "value"); + + if (value == "\\") + throw new ArgumentException ("An absolute root.", "value"); + + if (value.Length == 2 && value[1] == ':') + throw new ArgumentException ("An absolute root.", "value"); + + if (full == "/") + throw new ArgumentException ("An absolute root.", "value"); + + full = full.TrimSlashOrBackslashFromEnd (); + if (full.Length == 2 && full[1] == ':') + throw new ArgumentException ("An absolute root.", "value"); + + string msg; + if (!canSet (out msg)) { + _log.Warn (msg); + return; + } + + lock (_sync) { + if (!canSet (out msg)) { + _log.Warn (msg); + return; + } + + _docRootPath = value; + } + } + } + + /// + /// Gets a value indicating whether the server has started. + /// + /// + /// true if the server has started; otherwise, false. + /// + public bool IsListening { + get { + return _state == ServerState.Start; + } + } + + /// + /// Gets a value indicating whether secure connections are provided. + /// + /// + /// true if this instance provides secure connections; otherwise, + /// false. + /// + public bool IsSecure { + get { + return _secure; + } + } + + /// + /// Gets or sets a value indicating whether the server cleans up + /// the inactive sessions periodically. + /// + /// + /// The set operation does nothing if the server has already + /// started or it is shutting down. + /// + /// + /// + /// true if the server cleans up the inactive sessions + /// every 60 seconds; otherwise, false. + /// + /// + /// The default value is true. + /// + /// + public bool KeepClean { + get { + return _services.KeepClean; + } + + set { + _services.KeepClean = value; + } + } + + /// + /// Gets the logging function for the server. + /// + /// + /// The default logging level is . + /// + /// + /// A that provides the logging function. + /// + public Logger Log { + get { + return _log; + } + } + + /// + /// Gets the port of the server. + /// + /// + /// An that represents the number of the port + /// on which to listen for incoming requests. + /// + public int Port { + get { + return _port; + } + } + + /// + /// Gets or sets the realm used for authentication. + /// + /// + /// + /// "SECRET AREA" is used as the realm if the value is + /// or an empty string. + /// + /// + /// The set operation does nothing if the server has + /// already started or it is shutting down. + /// + /// + /// + /// + /// A or by default. + /// + /// + /// That string represents the name of the realm. + /// + /// + public string Realm { + get { + return _listener.Realm; + } + + set { + string msg; + if (!canSet (out msg)) { + _log.Warn (msg); + return; + } + + lock (_sync) { + if (!canSet (out msg)) { + _log.Warn (msg); + return; + } + + _listener.Realm = value; + } + } + } + + /// + /// Gets or sets a value indicating whether the server is allowed to + /// be bound to an address that is already in use. + /// + /// + /// + /// You should set this property to true if you would + /// like to resolve to wait for socket in TIME_WAIT state. + /// + /// + /// The set operation does nothing if the server has already + /// started or it is shutting down. + /// + /// + /// + /// + /// true if the server is allowed to be bound to an address + /// that is already in use; otherwise, false. + /// + /// + /// The default value is false. + /// + /// + public bool ReuseAddress { + get { + return _listener.ReuseAddress; + } + + set { + string msg; + if (!canSet (out msg)) { + _log.Warn (msg); + return; + } + + lock (_sync) { + if (!canSet (out msg)) { + _log.Warn (msg); + return; + } + + _listener.ReuseAddress = value; + } + } + } + + /// + /// Gets the configuration for secure connection. + /// + /// + /// This configuration will be referenced when attempts to start, + /// so it must be configured before the start method is called. + /// + /// + /// A that represents + /// the configuration used to provide secure connections. + /// + /// + /// This instance does not provide secure connections. + /// + public ServerSslConfiguration SslConfiguration { + get { + if (!_secure) { + var msg = "This instance does not provide secure connections."; + throw new InvalidOperationException (msg); + } + + return _listener.SslConfiguration; + } + } + + /// + /// Gets or sets the delegate used to find the credentials + /// for an identity. + /// + /// + /// + /// No credentials are found if the method invoked by + /// the delegate returns or + /// the value is . + /// + /// + /// The set operation does nothing if the server has + /// already started or it is shutting down. + /// + /// + /// + /// + /// A Func<, + /// > delegate or + /// if not needed. + /// + /// + /// That delegate invokes the method called for finding + /// the credentials used to authenticate a client. + /// + /// + /// The default value is . + /// + /// + public Func UserCredentialsFinder { + get { + return _listener.UserCredentialsFinder; + } + + set { + string msg; + if (!canSet (out msg)) { + _log.Warn (msg); + return; + } + + lock (_sync) { + if (!canSet (out msg)) { + _log.Warn (msg); + return; + } + + _listener.UserCredentialsFinder = value; + } + } + } + + /// + /// Gets or sets the time to wait for the response to the WebSocket Ping or + /// Close. + /// + /// + /// The set operation does nothing if the server has already started or + /// it is shutting down. + /// + /// + /// + /// A to wait for the response. + /// + /// + /// The default value is the same as 1 second. + /// + /// + /// + /// The value specified for a set operation is zero or less. + /// + public TimeSpan WaitTime { + get { + return _services.WaitTime; + } + + set { + _services.WaitTime = value; + } + } + + /// + /// Gets the management function for the WebSocket services + /// provided by the server. + /// + /// + /// A that manages + /// the WebSocket services provided by the server. + /// + public WebSocketServiceManager WebSocketServices { + get { + return _services; + } + } + + #endregion + + #region Public Events + + /// + /// Occurs when the server receives an HTTP CONNECT request. + /// + public event EventHandler OnConnect; + + /// + /// Occurs when the server receives an HTTP DELETE request. + /// + public event EventHandler OnDelete; + + /// + /// Occurs when the server receives an HTTP GET request. + /// + public event EventHandler OnGet; + + /// + /// Occurs when the server receives an HTTP HEAD request. + /// + public event EventHandler OnHead; + + /// + /// Occurs when the server receives an HTTP OPTIONS request. + /// + public event EventHandler OnOptions; + + /// + /// Occurs when the server receives an HTTP POST request. + /// + public event EventHandler OnPost; + + /// + /// Occurs when the server receives an HTTP PUT request. + /// + public event EventHandler OnPut; + + /// + /// Occurs when the server receives an HTTP TRACE request. + /// + public event EventHandler OnTrace; + + #endregion + + #region Private Methods + + private void abort () + { + lock (_sync) { + if (_state != ServerState.Start) + return; + + _state = ServerState.ShuttingDown; + } + + try { + try { + _services.Stop (1006, String.Empty); + } + finally { + _listener.Abort (); + } + } + catch { + } + + _state = ServerState.Stop; + } + + private bool canSet (out string message) + { + message = null; + + if (_state == ServerState.Start) { + message = "The server has already started."; + return false; + } + + if (_state == ServerState.ShuttingDown) { + message = "The server is shutting down."; + return false; + } + + return true; + } + + private bool checkCertificate (out string message) + { + message = null; + + var byUser = _listener.SslConfiguration.ServerCertificate != null; + + var path = _listener.CertificateFolderPath; + var withPort = EndPointListener.CertificateExists (_port, path); + + if (!(byUser || withPort)) { + message = "There is no server certificate for secure connection."; + return false; + } + + if (byUser && withPort) + _log.Warn ("The server certificate associated with the port is used."); + + return true; + } + + private string createFilePath (string childPath) + { + childPath = childPath.TrimStart ('/', '\\'); + return new StringBuilder (_docRootPath, 32) + .AppendFormat ("/{0}", childPath) + .ToString () + .Replace ('\\', '/'); + } + + private static HttpListener createListener ( + string hostname, int port, bool secure + ) + { + var lsnr = new HttpListener (); + + var schm = secure ? "https" : "http"; + var pref = String.Format ("{0}://{1}:{2}/", schm, hostname, port); + lsnr.Prefixes.Add (pref); + + return lsnr; + } + + private void init ( + string hostname, System.Net.IPAddress address, int port, bool secure + ) + { + _hostname = hostname; + _address = address; + _port = port; + _secure = secure; + + _docRootPath = "./Public"; + _listener = createListener (_hostname, _port, _secure); + _log = _listener.Log; + _services = new WebSocketServiceManager (_log); + _sync = new object (); + } + + private void processRequest (HttpListenerContext context) + { + var method = context.Request.HttpMethod; + var evt = method == "GET" + ? OnGet + : method == "HEAD" + ? OnHead + : method == "POST" + ? OnPost + : method == "PUT" + ? OnPut + : method == "DELETE" + ? OnDelete + : method == "CONNECT" + ? OnConnect + : method == "OPTIONS" + ? OnOptions + : method == "TRACE" + ? OnTrace + : null; + + if (evt != null) + evt (this, new HttpRequestEventArgs (context, _docRootPath)); + else + context.Response.StatusCode = 501; // Not Implemented + + context.Response.Close (); + } + + private void processRequest (HttpListenerWebSocketContext context) + { + var uri = context.RequestUri; + if (uri == null) { + context.Close (HttpStatusCode.BadRequest); + return; + } + + var path = uri.AbsolutePath; + if (path.IndexOfAny (new[] { '%', '+' }) > -1) + path = HttpUtility.UrlDecode (path, Encoding.UTF8); + + WebSocketServiceHost host; + if (!_services.InternalTryGetServiceHost (path, out host)) { + context.Close (HttpStatusCode.NotImplemented); + return; + } + + host.StartSession (context); + } + + private void receiveRequest () + { + while (true) { + HttpListenerContext ctx = null; + try { + ctx = _listener.GetContext (); + ThreadPool.QueueUserWorkItem ( + state => { + try { + if (ctx.Request.IsUpgradeRequest ("websocket")) { + processRequest (ctx.AcceptWebSocket (null)); + return; + } + + processRequest (ctx); + } + catch (Exception ex) { + _log.Fatal (ex.Message); + _log.Debug (ex.ToString ()); + + ctx.Connection.Close (true); + } + } + ); + } + catch (HttpListenerException) { + _log.Info ("The underlying listener is stopped."); + break; + } + catch (InvalidOperationException) { + _log.Info ("The underlying listener is stopped."); + break; + } + catch (Exception ex) { + _log.Fatal (ex.Message); + _log.Debug (ex.ToString ()); + + if (ctx != null) + ctx.Connection.Close (true); + + break; + } + } + + if (_state != ServerState.ShuttingDown) + abort (); + } + + private void start () + { + if (_state == ServerState.Start) { + _log.Info ("The server has already started."); + return; + } + + if (_state == ServerState.ShuttingDown) { + _log.Warn ("The server is shutting down."); + return; + } + + lock (_sync) { + if (_state == ServerState.Start) { + _log.Info ("The server has already started."); + return; + } + + if (_state == ServerState.ShuttingDown) { + _log.Warn ("The server is shutting down."); + return; + } + + _services.Start (); + + try { + startReceiving (); + } + catch { + _services.Stop (1011, String.Empty); + throw; + } + + _state = ServerState.Start; + } + } + + private void startReceiving () + { + try { + _listener.Start (); + } + catch (Exception ex) { + var msg = "The underlying listener has failed to start."; + throw new InvalidOperationException (msg, ex); + } + + _receiveThread = new Thread (new ThreadStart (receiveRequest)); + _receiveThread.IsBackground = true; + _receiveThread.Start (); + } + + private void stop (ushort code, string reason) + { + if (_state == ServerState.Ready) { + _log.Info ("The server is not started."); + return; + } + + if (_state == ServerState.ShuttingDown) { + _log.Info ("The server is shutting down."); + return; + } + + if (_state == ServerState.Stop) { + _log.Info ("The server has already stopped."); + return; + } + + lock (_sync) { + if (_state == ServerState.ShuttingDown) { + _log.Info ("The server is shutting down."); + return; + } + + if (_state == ServerState.Stop) { + _log.Info ("The server has already stopped."); + return; + } + + _state = ServerState.ShuttingDown; + } + + try { + var threw = false; + try { + _services.Stop (code, reason); + } + catch { + threw = true; + throw; + } + finally { + try { + stopReceiving (5000); + } + catch { + if (!threw) + throw; + } + } + } + finally { + _state = ServerState.Stop; + } + } + + private void stopReceiving (int millisecondsTimeout) + { + _listener.Stop (); + _receiveThread.Join (millisecondsTimeout); + } + + private static bool tryCreateUri ( + string uriString, out Uri result, out string message + ) + { + result = null; + message = null; + + var uri = uriString.ToUri (); + if (uri == null) { + message = "An invalid URI string."; + return false; + } + + if (!uri.IsAbsoluteUri) { + message = "A relative URI."; + return false; + } + + var schm = uri.Scheme; + if (!(schm == "http" || schm == "https")) { + message = "The scheme part is not 'http' or 'https'."; + return false; + } + + if (uri.PathAndQuery != "/") { + message = "It includes either or both path and query components."; + return false; + } + + if (uri.Fragment.Length > 0) { + message = "It includes the fragment component."; + return false; + } + + if (uri.Port == 0) { + message = "The port part is zero."; + return false; + } + + result = uri; + return true; + } + + #endregion + + #region Public Methods + + /// + /// Adds a WebSocket service with the specified behavior, path, + /// and delegate. + /// + /// + /// + /// A that represents an absolute path to + /// the service to add. + /// + /// + /// / is trimmed from the end of the string if present. + /// + /// + /// + /// + /// A Func<TBehavior> delegate. + /// + /// + /// It invokes the method called when creating a new session + /// instance for the service. + /// + /// + /// The method must create a new instance of the specified + /// behavior class and return it. + /// + /// + /// + /// + /// The type of the behavior for the service. + /// + /// + /// It must inherit the class. + /// + /// + /// + /// + /// is . + /// + /// + /// -or- + /// + /// + /// is . + /// + /// + /// + /// + /// is an empty string. + /// + /// + /// -or- + /// + /// + /// is not an absolute path. + /// + /// + /// -or- + /// + /// + /// includes either or both + /// query and fragment components. + /// + /// + /// -or- + /// + /// + /// is already in use. + /// + /// + [Obsolete ("This method will be removed. Use added one instead.")] + public void AddWebSocketService ( + string path, Func creator + ) + where TBehavior : WebSocketBehavior + { + if (path == null) + throw new ArgumentNullException ("path"); + + if (creator == null) + throw new ArgumentNullException ("creator"); + + if (path.Length == 0) + throw new ArgumentException ("An empty string.", "path"); + + if (path[0] != '/') + throw new ArgumentException ("Not an absolute path.", "path"); + + if (path.IndexOfAny (new[] { '?', '#' }) > -1) { + var msg = "It includes either or both query and fragment components."; + throw new ArgumentException (msg, "path"); + } + + _services.Add (path, creator); + } + + /// + /// Adds a WebSocket service with the specified behavior and path. + /// + /// + /// + /// A that represents an absolute path to + /// the service to add. + /// + /// + /// / is trimmed from the end of the string if present. + /// + /// + /// + /// + /// The type of the behavior for the service. + /// + /// + /// It must inherit the class. + /// + /// + /// And also, it must have a public parameterless constructor. + /// + /// + /// + /// is . + /// + /// + /// + /// is an empty string. + /// + /// + /// -or- + /// + /// + /// is not an absolute path. + /// + /// + /// -or- + /// + /// + /// includes either or both + /// query and fragment components. + /// + /// + /// -or- + /// + /// + /// is already in use. + /// + /// + public void AddWebSocketService (string path) + where TBehaviorWithNew : WebSocketBehavior, new () + { + _services.AddService (path, null); + } + + /// + /// Adds a WebSocket service with the specified behavior, path, + /// and delegate. + /// + /// + /// + /// A that represents an absolute path to + /// the service to add. + /// + /// + /// / is trimmed from the end of the string if present. + /// + /// + /// + /// + /// An Action<TBehaviorWithNew> delegate or + /// if not needed. + /// + /// + /// The delegate invokes the method called when initializing + /// a new session instance for the service. + /// + /// + /// + /// + /// The type of the behavior for the service. + /// + /// + /// It must inherit the class. + /// + /// + /// And also, it must have a public parameterless constructor. + /// + /// + /// + /// is . + /// + /// + /// + /// is an empty string. + /// + /// + /// -or- + /// + /// + /// is not an absolute path. + /// + /// + /// -or- + /// + /// + /// includes either or both + /// query and fragment components. + /// + /// + /// -or- + /// + /// + /// is already in use. + /// + /// + public void AddWebSocketService ( + string path, Action initializer + ) + where TBehaviorWithNew : WebSocketBehavior, new () + { + _services.AddService (path, initializer); + } + + /// + /// Gets the contents of the specified file from the document + /// folder of the server. + /// + /// + /// + /// An array of or + /// if it fails. + /// + /// + /// That array represents the contents of the file. + /// + /// + /// + /// A that represents a virtual path to + /// find the file from the document folder. + /// + /// + /// is . + /// + /// + /// + /// is an empty string. + /// + /// + /// -or- + /// + /// + /// contains "..". + /// + /// + [Obsolete ("This method will be removed.")] + public byte[] GetFile (string path) + { + if (path == null) + throw new ArgumentNullException ("path"); + + if (path.Length == 0) + throw new ArgumentException ("An empty string.", "path"); + + if (path.IndexOf ("..") > -1) + throw new ArgumentException ("It contains '..'.", "path"); + + path = createFilePath (path); + return File.Exists (path) ? File.ReadAllBytes (path) : null; + } + + /// + /// Removes a WebSocket service with the specified path. + /// + /// + /// The service is stopped with close status 1001 (going away) + /// if it has already started. + /// + /// + /// true if the service is successfully found and removed; + /// otherwise, false. + /// + /// + /// + /// A that represents an absolute path to + /// the service to remove. + /// + /// + /// / is trimmed from the end of the string if present. + /// + /// + /// + /// is . + /// + /// + /// + /// is an empty string. + /// + /// + /// -or- + /// + /// + /// is not an absolute path. + /// + /// + /// -or- + /// + /// + /// includes either or both + /// query and fragment components. + /// + /// + public bool RemoveWebSocketService (string path) + { + return _services.RemoveService (path); + } + + /// + /// Starts receiving incoming requests. + /// + /// + /// This method does nothing if the server has already started or + /// it is shutting down. + /// + /// + /// + /// There is no server certificate for secure connection. + /// + /// + /// -or- + /// + /// + /// The underlying has failed to start. + /// + /// + public void Start () + { + if (_secure) { + string msg; + if (!checkCertificate (out msg)) + throw new InvalidOperationException (msg); + } + + start (); + } + + /// + /// Stops receiving incoming requests. + /// + public void Stop () + { + stop (1001, String.Empty); + } + + /// + /// Stops receiving incoming requests and closes each connection. + /// + /// + /// + /// A that represents the status code indicating + /// the reason for the WebSocket connection close. + /// + /// + /// The status codes are defined in + /// + /// Section 7.4 of RFC 6455. + /// + /// + /// + /// + /// A that represents the reason for the WebSocket + /// connection close. + /// + /// + /// The size must be 123 bytes or less in UTF-8. + /// + /// + /// + /// + /// is less than 1000 or greater than 4999. + /// + /// + /// -or- + /// + /// + /// The size of is greater than 123 bytes. + /// + /// + /// + /// + /// is 1010 (mandatory extension). + /// + /// + /// -or- + /// + /// + /// is 1005 (no status) and there is reason. + /// + /// + /// -or- + /// + /// + /// could not be UTF-8-encoded. + /// + /// + [Obsolete ("This method will be removed.")] + public void Stop (ushort code, string reason) + { + if (!code.IsCloseStatusCode ()) { + var msg = "Less than 1000 or greater than 4999."; + throw new ArgumentOutOfRangeException ("code", msg); + } + + if (code == 1010) { + var msg = "1010 cannot be used."; + throw new ArgumentException (msg, "code"); + } + + if (!reason.IsNullOrEmpty ()) { + if (code == 1005) { + var msg = "1005 cannot be used."; + throw new ArgumentException (msg, "code"); + } + + byte[] bytes; + if (!reason.TryGetUTF8EncodedBytes (out bytes)) { + var msg = "It could not be UTF-8-encoded."; + throw new ArgumentException (msg, "reason"); + } + + if (bytes.Length > 123) { + var msg = "Its size is greater than 123 bytes."; + throw new ArgumentOutOfRangeException ("reason", msg); + } + } + + stop (code, reason); + } + + /// + /// Stops receiving incoming requests and closes each connection. + /// + /// + /// + /// One of the enum values. + /// + /// + /// It represents the status code indicating the reason for the WebSocket + /// connection close. + /// + /// + /// + /// + /// A that represents the reason for the WebSocket + /// connection close. + /// + /// + /// The size must be 123 bytes or less in UTF-8. + /// + /// + /// + /// The size of is greater than 123 bytes. + /// + /// + /// + /// is + /// . + /// + /// + /// -or- + /// + /// + /// is + /// and there is reason. + /// + /// + /// -or- + /// + /// + /// could not be UTF-8-encoded. + /// + /// + [Obsolete ("This method will be removed.")] + public void Stop (CloseStatusCode code, string reason) + { + if (code == CloseStatusCode.MandatoryExtension) { + var msg = "MandatoryExtension cannot be used."; + throw new ArgumentException (msg, "code"); + } + + if (!reason.IsNullOrEmpty ()) { + if (code == CloseStatusCode.NoStatus) { + var msg = "NoStatus cannot be used."; + throw new ArgumentException (msg, "code"); + } + + byte[] bytes; + if (!reason.TryGetUTF8EncodedBytes (out bytes)) { + var msg = "It could not be UTF-8-encoded."; + throw new ArgumentException (msg, "reason"); + } + + if (bytes.Length > 123) { + var msg = "Its size is greater than 123 bytes."; + throw new ArgumentOutOfRangeException ("reason", msg); + } + } + + stop ((ushort) code, reason); + } + + #endregion + } +} diff --git a/websocket-sharp/Server/IWebSocketSession.cs b/websocket-sharp/Server/IWebSocketSession.cs new file mode 100644 index 0000000..296b5bf --- /dev/null +++ b/websocket-sharp/Server/IWebSocketSession.cs @@ -0,0 +1,91 @@ +#region License +/* + * IWebSocketSession.cs + * + * The MIT License + * + * Copyright (c) 2013-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 + +using System; +using WebSocketSharp.Net.WebSockets; + +namespace WebSocketSharp.Server +{ + /// + /// Exposes the access to the information in a WebSocket session. + /// + public interface IWebSocketSession + { + #region Properties + + /// + /// Gets the current state of the WebSocket connection for the session. + /// + /// + /// + /// One of the enum values. + /// + /// + /// It indicates the current state of the connection. + /// + /// + WebSocketState ConnectionState { get; } + + /// + /// Gets the information in the WebSocket handshake request. + /// + /// + /// A instance that provides the access to + /// the information in the handshake request. + /// + WebSocketContext Context { get; } + + /// + /// Gets the unique ID of the session. + /// + /// + /// A that represents the unique ID of the session. + /// + string ID { get; } + + /// + /// Gets the name of the WebSocket subprotocol for the session. + /// + /// + /// A that represents the name of the subprotocol + /// if present. + /// + string Protocol { get; } + + /// + /// Gets the time that the session has started. + /// + /// + /// A that represents the time that the session + /// has started. + /// + DateTime StartTime { get; } + + #endregion + } +} diff --git a/websocket-sharp/Server/ServerState.cs b/websocket-sharp/Server/ServerState.cs new file mode 100644 index 0000000..2d75829 --- /dev/null +++ b/websocket-sharp/Server/ServerState.cs @@ -0,0 +1,40 @@ +#region License +/* + * ServerState.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.Server +{ + internal enum ServerState + { + Ready, + Start, + ShuttingDown, + Stop + } +} diff --git a/websocket-sharp/Server/WebSocketBehavior.cs b/websocket-sharp/Server/WebSocketBehavior.cs new file mode 100644 index 0000000..b5e8ffe --- /dev/null +++ b/websocket-sharp/Server/WebSocketBehavior.cs @@ -0,0 +1,1204 @@ +#region License +/* + * WebSocketBehavior.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; +using System.Collections.Specialized; +using System.IO; +using WebSocketSharp.Net; +using WebSocketSharp.Net.WebSockets; + +namespace WebSocketSharp.Server +{ + /// + /// Exposes a set of methods and properties used to define the behavior of + /// a WebSocket service provided by the or + /// . + /// + /// + /// This class is an abstract class. + /// + public abstract class WebSocketBehavior : IWebSocketSession + { + #region Private Fields + + private WebSocketContext _context; + private Func _cookiesValidator; + private bool _emitOnPing; + private string _id; + private bool _ignoreExtensions; + private Func _originValidator; + private string _protocol; + private WebSocketSessionManager _sessions; + private DateTime _startTime; + private WebSocket _websocket; + + #endregion + + #region Protected Constructors + + /// + /// Initializes a new instance of the class. + /// + protected WebSocketBehavior () + { + _startTime = DateTime.MaxValue; + } + + #endregion + + #region Protected Properties + + /// + /// Gets the HTTP headers included in a WebSocket handshake request. + /// + /// + /// + /// A that contains the headers. + /// + /// + /// if the session has not started yet. + /// + /// + protected NameValueCollection Headers { + get { + return _context != null ? _context.Headers : null; + } + } + + /// + /// Gets the logging function. + /// + /// + /// + /// A that provides the logging function. + /// + /// + /// if the session has not started yet. + /// + /// + [Obsolete ("This property will be removed.")] + protected Logger Log { + get { + return _websocket != null ? _websocket.Log : null; + } + } + + /// + /// Gets the query string included in a WebSocket handshake request. + /// + /// + /// + /// A that contains the query + /// parameters. + /// + /// + /// An empty collection if not included. + /// + /// + /// if the session has not started yet. + /// + /// + protected NameValueCollection QueryString { + get { + return _context != null ? _context.QueryString : null; + } + } + + /// + /// Gets the management function for the sessions in the service. + /// + /// + /// + /// A that manages the sessions in + /// the service. + /// + /// + /// if the session has not started yet. + /// + /// + protected WebSocketSessionManager Sessions { + get { + return _sessions; + } + } + + #endregion + + #region Public Properties + + /// + /// Gets the current state of the WebSocket connection for a session. + /// + /// + /// + /// One of the enum values. + /// + /// + /// It indicates the current state of the connection. + /// + /// + /// if the session has not + /// started yet. + /// + /// + public WebSocketState ConnectionState { + get { + return _websocket != null + ? _websocket.ReadyState + : WebSocketState.Connecting; + } + } + + /// + /// Gets the information in a WebSocket handshake request to the service. + /// + /// + /// + /// A instance that provides the access to + /// the information in the handshake request. + /// + /// + /// if the session has not started yet. + /// + /// + public WebSocketContext Context { + get { + return _context; + } + } + + /// + /// Gets or sets the delegate used to validate the HTTP cookies included in + /// a WebSocket handshake request to the service. + /// + /// + /// + /// A Func<CookieCollection, CookieCollection, bool> delegate + /// or if not needed. + /// + /// + /// The delegate invokes the method called when the WebSocket instance + /// for a session validates the handshake request. + /// + /// + /// 1st parameter passed to the method + /// contains the cookies to validate if present. + /// + /// + /// 2nd parameter passed to the method + /// receives the cookies to send to the client. + /// + /// + /// The method must return true if the cookies are valid. + /// + /// + /// The default value is . + /// + /// + public Func CookiesValidator { + get { + return _cookiesValidator; + } + + set { + _cookiesValidator = value; + } + } + + /// + /// Gets or sets a value indicating whether the WebSocket instance for + /// a session emits the message event when receives a ping. + /// + /// + /// + /// true if the WebSocket instance emits the message event + /// when receives a ping; otherwise, false. + /// + /// + /// The default value is false. + /// + /// + public bool EmitOnPing { + get { + return _websocket != null ? _websocket.EmitOnPing : _emitOnPing; + } + + set { + if (_websocket != null) { + _websocket.EmitOnPing = value; + return; + } + + _emitOnPing = value; + } + } + + /// + /// Gets the unique ID of a session. + /// + /// + /// + /// A that represents the unique ID of the session. + /// + /// + /// if the session has not started yet. + /// + /// + public string ID { + get { + return _id; + } + } + + /// + /// Gets or sets a value indicating whether the service ignores + /// the Sec-WebSocket-Extensions header included in a WebSocket + /// handshake request. + /// + /// + /// + /// true if the service ignores the extensions requested + /// from a client; otherwise, false. + /// + /// + /// The default value is false. + /// + /// + public bool IgnoreExtensions { + get { + return _ignoreExtensions; + } + + set { + _ignoreExtensions = value; + } + } + + /// + /// Gets or sets the delegate used to validate the Origin header included in + /// a WebSocket handshake request to the service. + /// + /// + /// + /// A Func<string, bool> delegate or + /// if not needed. + /// + /// + /// The delegate invokes the method called when the WebSocket instance + /// for a session validates the handshake request. + /// + /// + /// The parameter passed to the method is the value + /// of the Origin header or if the header is not + /// present. + /// + /// + /// The method must return true if the header value is valid. + /// + /// + /// The default value is . + /// + /// + public Func OriginValidator { + get { + return _originValidator; + } + + set { + _originValidator = value; + } + } + + /// + /// Gets or sets the name of the WebSocket subprotocol for the service. + /// + /// + /// + /// A that represents the name of the subprotocol. + /// + /// + /// The value specified for a set must be a token defined in + /// + /// RFC 2616. + /// + /// + /// The default value is an empty string. + /// + /// + /// + /// The set operation is not available if the session has already started. + /// + /// + /// The value specified for a set operation is not a token. + /// + public string Protocol { + get { + return _websocket != null + ? _websocket.Protocol + : (_protocol ?? String.Empty); + } + + set { + if (ConnectionState != WebSocketState.Connecting) { + var msg = "The session has already started."; + throw new InvalidOperationException (msg); + } + + if (value == null || value.Length == 0) { + _protocol = null; + return; + } + + if (!value.IsToken ()) + throw new ArgumentException ("Not a token.", "value"); + + _protocol = value; + } + } + + /// + /// Gets the time that a session has started. + /// + /// + /// + /// A that represents the time that the session + /// has started. + /// + /// + /// if the session has not started yet. + /// + /// + public DateTime StartTime { + get { + return _startTime; + } + } + + #endregion + + #region Private Methods + + private string checkHandshakeRequest (WebSocketContext context) + { + if (_originValidator != null) { + if (!_originValidator (context.Origin)) + return "It includes no Origin header or an invalid one."; + } + + if (_cookiesValidator != null) { + var req = context.CookieCollection; + var res = context.WebSocket.CookieCollection; + if (!_cookiesValidator (req, res)) + return "It includes no cookie or an invalid one."; + } + + return null; + } + + private void onClose (object sender, CloseEventArgs e) + { + if (_id == null) + return; + + _sessions.Remove (_id); + OnClose (e); + } + + private void onError (object sender, ErrorEventArgs e) + { + OnError (e); + } + + private void onMessage (object sender, MessageEventArgs e) + { + OnMessage (e); + } + + private void onOpen (object sender, EventArgs e) + { + _id = _sessions.Add (this); + if (_id == null) { + _websocket.Close (CloseStatusCode.Away); + return; + } + + _startTime = DateTime.Now; + OnOpen (); + } + + #endregion + + #region Internal Methods + + internal void Start (WebSocketContext context, WebSocketSessionManager sessions) + { + if (_websocket != null) { + _websocket.Log.Error ("A session instance cannot be reused."); + context.WebSocket.Close (HttpStatusCode.ServiceUnavailable); + + return; + } + + _context = context; + _sessions = sessions; + + _websocket = context.WebSocket; + _websocket.CustomHandshakeRequestChecker = checkHandshakeRequest; + _websocket.EmitOnPing = _emitOnPing; + _websocket.IgnoreExtensions = _ignoreExtensions; + _websocket.Protocol = _protocol; + + var waitTime = sessions.WaitTime; + if (waitTime != _websocket.WaitTime) + _websocket.WaitTime = waitTime; + + _websocket.OnOpen += onOpen; + _websocket.OnMessage += onMessage; + _websocket.OnError += onError; + _websocket.OnClose += onClose; + + _websocket.InternalAccept (); + } + + #endregion + + #region Protected Methods + + /// + /// Closes the WebSocket connection for a session. + /// + /// + /// This method does nothing if the current state of the connection is + /// Closing or Closed. + /// + /// + /// The session has not started yet. + /// + protected void Close () + { + if (_websocket == null) { + var msg = "The session has not started yet."; + throw new InvalidOperationException (msg); + } + + _websocket.Close (); + } + + /// + /// Closes the WebSocket connection for a session with the specified + /// code and reason. + /// + /// + /// This method does nothing if the current state of the connection is + /// Closing or Closed. + /// + /// + /// + /// A that represents the status code indicating + /// the reason for the close. + /// + /// + /// The status codes are defined in + /// + /// Section 7.4 of RFC 6455. + /// + /// + /// + /// + /// A that represents the reason for the close. + /// + /// + /// The size must be 123 bytes or less in UTF-8. + /// + /// + /// + /// The session has not started yet. + /// + /// + /// + /// is less than 1000 or greater than 4999. + /// + /// + /// -or- + /// + /// + /// The size of is greater than 123 bytes. + /// + /// + /// + /// + /// is 1010 (mandatory extension). + /// + /// + /// -or- + /// + /// + /// is 1005 (no status) and there is reason. + /// + /// + /// -or- + /// + /// + /// could not be UTF-8-encoded. + /// + /// + protected void Close (ushort code, string reason) + { + if (_websocket == null) { + var msg = "The session has not started yet."; + throw new InvalidOperationException (msg); + } + + _websocket.Close (code, reason); + } + + /// + /// Closes the WebSocket connection for a session with the specified + /// code and reason. + /// + /// + /// This method does nothing if the current state of the connection is + /// Closing or Closed. + /// + /// + /// + /// One of the enum values. + /// + /// + /// It represents the status code indicating the reason for the close. + /// + /// + /// + /// + /// A that represents the reason for the close. + /// + /// + /// The size must be 123 bytes or less in UTF-8. + /// + /// + /// + /// The session has not started yet. + /// + /// + /// The size of is greater than 123 bytes. + /// + /// + /// + /// is + /// . + /// + /// + /// -or- + /// + /// + /// is + /// and there is reason. + /// + /// + /// -or- + /// + /// + /// could not be UTF-8-encoded. + /// + /// + protected void Close (CloseStatusCode code, string reason) + { + if (_websocket == null) { + var msg = "The session has not started yet."; + throw new InvalidOperationException (msg); + } + + _websocket.Close (code, reason); + } + + /// + /// Closes the WebSocket connection for a session asynchronously. + /// + /// + /// + /// This method does not wait for the close to be complete. + /// + /// + /// This method does nothing if the current state of the connection is + /// Closing or Closed. + /// + /// + /// + /// The session has not started yet. + /// + protected void CloseAsync () + { + if (_websocket == null) { + var msg = "The session has not started yet."; + throw new InvalidOperationException (msg); + } + + _websocket.CloseAsync (); + } + + /// + /// Closes the WebSocket connection for a session asynchronously with + /// the specified code and reason. + /// + /// + /// + /// This method does not wait for the close to be complete. + /// + /// + /// This method does nothing if the current state of the connection is + /// Closing or Closed. + /// + /// + /// + /// + /// A that represents the status code indicating + /// the reason for the close. + /// + /// + /// The status codes are defined in + /// + /// Section 7.4 of RFC 6455. + /// + /// + /// + /// + /// A that represents the reason for the close. + /// + /// + /// The size must be 123 bytes or less in UTF-8. + /// + /// + /// + /// The session has not started yet. + /// + /// + /// + /// is less than 1000 or greater than 4999. + /// + /// + /// -or- + /// + /// + /// The size of is greater than 123 bytes. + /// + /// + /// + /// + /// is 1010 (mandatory extension). + /// + /// + /// -or- + /// + /// + /// is 1005 (no status) and there is reason. + /// + /// + /// -or- + /// + /// + /// could not be UTF-8-encoded. + /// + /// + protected void CloseAsync (ushort code, string reason) + { + if (_websocket == null) { + var msg = "The session has not started yet."; + throw new InvalidOperationException (msg); + } + + _websocket.CloseAsync (code, reason); + } + + /// + /// Closes the WebSocket connection for a session asynchronously with + /// the specified code and reason. + /// + /// + /// + /// This method does not wait for the close to be complete. + /// + /// + /// This method does nothing if the current state of the connection is + /// Closing or Closed. + /// + /// + /// + /// + /// One of the enum values. + /// + /// + /// It represents the status code indicating the reason for the close. + /// + /// + /// + /// + /// A that represents the reason for the close. + /// + /// + /// The size must be 123 bytes or less in UTF-8. + /// + /// + /// + /// The session has not started yet. + /// + /// + /// + /// is + /// . + /// + /// + /// -or- + /// + /// + /// is + /// and there is reason. + /// + /// + /// -or- + /// + /// + /// could not be UTF-8-encoded. + /// + /// + /// + /// The size of is greater than 123 bytes. + /// + protected void CloseAsync (CloseStatusCode code, string reason) + { + if (_websocket == null) { + var msg = "The session has not started yet."; + throw new InvalidOperationException (msg); + } + + _websocket.CloseAsync (code, reason); + } + + /// + /// Calls the method with the specified message. + /// + /// + /// A that represents the error message. + /// + /// + /// An instance that represents the cause of + /// the error if present. + /// + /// + /// is . + /// + /// + /// is an empty string. + /// + [Obsolete ("This method will be removed.")] + protected void Error (string message, Exception exception) + { + if (message == null) + throw new ArgumentNullException ("message"); + + if (message.Length == 0) + throw new ArgumentException ("An empty string.", "message"); + + OnError (new ErrorEventArgs (message, exception)); + } + + /// + /// Called when the WebSocket connection for a session has been closed. + /// + /// + /// A that represents the event data passed + /// from a event. + /// + protected virtual void OnClose (CloseEventArgs e) + { + } + + /// + /// Called when the WebSocket instance for a session gets an error. + /// + /// + /// A that represents the event data passed + /// from a event. + /// + protected virtual void OnError (ErrorEventArgs e) + { + } + + /// + /// Called when the WebSocket instance for a session receives a message. + /// + /// + /// A that represents the event data passed + /// from a event. + /// + protected virtual void OnMessage (MessageEventArgs e) + { + } + + /// + /// Called when the WebSocket connection for a session has been established. + /// + protected virtual void OnOpen () + { + } + + /// + /// Sends the specified data to a client using the WebSocket connection. + /// + /// + /// An array of that represents the binary data to send. + /// + /// + /// The current state of the connection is not Open. + /// + /// + /// is . + /// + protected void Send (byte[] data) + { + if (_websocket == null) { + var msg = "The current state of the connection is not Open."; + throw new InvalidOperationException (msg); + } + + _websocket.Send (data); + } + + /// + /// Sends the specified file to a client using the WebSocket connection. + /// + /// + /// + /// A that specifies the file to send. + /// + /// + /// The file is sent as the binary data. + /// + /// + /// + /// The current state of the connection is not Open. + /// + /// + /// is . + /// + /// + /// + /// The file does not exist. + /// + /// + /// -or- + /// + /// + /// The file could not be opened. + /// + /// + protected void Send (FileInfo fileInfo) + { + if (_websocket == null) { + var msg = "The current state of the connection is not Open."; + throw new InvalidOperationException (msg); + } + + _websocket.Send (fileInfo); + } + + /// + /// Sends the specified data to a client using the WebSocket connection. + /// + /// + /// A that represents the text data to send. + /// + /// + /// The current state of the connection is not Open. + /// + /// + /// is . + /// + /// + /// could not be UTF-8-encoded. + /// + protected void Send (string data) + { + if (_websocket == null) { + var msg = "The current state of the connection is not Open."; + throw new InvalidOperationException (msg); + } + + _websocket.Send (data); + } + + /// + /// Sends the data from the specified stream to a client using + /// the WebSocket connection. + /// + /// + /// + /// A instance from which to read the data to send. + /// + /// + /// The data is sent as the binary data. + /// + /// + /// + /// An that specifies the number of bytes to send. + /// + /// + /// The current state of the connection is not Open. + /// + /// + /// is . + /// + /// + /// + /// cannot be read. + /// + /// + /// -or- + /// + /// + /// is less than 1. + /// + /// + /// -or- + /// + /// + /// No data could be read from . + /// + /// + protected void Send (Stream stream, int length) + { + if (_websocket == null) { + var msg = "The current state of the connection is not Open."; + throw new InvalidOperationException (msg); + } + + _websocket.Send (stream, length); + } + + /// + /// Sends the specified data to a client asynchronously using + /// the WebSocket connection. + /// + /// + /// This method does not wait for the send to be complete. + /// + /// + /// An array of that represents the binary data to send. + /// + /// + /// + /// An Action<bool> delegate or + /// if not needed. + /// + /// + /// The delegate invokes the method called when the send is complete. + /// + /// + /// true is passed to the method if the send has done with + /// no error; otherwise, false. + /// + /// + /// + /// The current state of the connection is not Open. + /// + /// + /// is . + /// + protected void SendAsync (byte[] data, Action completed) + { + if (_websocket == null) { + var msg = "The current state of the connection is not Open."; + throw new InvalidOperationException (msg); + } + + _websocket.SendAsync (data, completed); + } + + /// + /// Sends the specified file to a client asynchronously using + /// the WebSocket connection. + /// + /// + /// This method does not wait for the send to be complete. + /// + /// + /// + /// A that specifies the file to send. + /// + /// + /// The file is sent as the binary data. + /// + /// + /// + /// + /// An Action<bool> delegate or + /// if not needed. + /// + /// + /// The delegate invokes the method called when the send is complete. + /// + /// + /// true is passed to the method if the send has done with + /// no error; otherwise, false. + /// + /// + /// + /// The current state of the connection is not Open. + /// + /// + /// is . + /// + /// + /// + /// The file does not exist. + /// + /// + /// -or- + /// + /// + /// The file could not be opened. + /// + /// + protected void SendAsync (FileInfo fileInfo, Action completed) + { + if (_websocket == null) { + var msg = "The current state of the connection is not Open."; + throw new InvalidOperationException (msg); + } + + _websocket.SendAsync (fileInfo, completed); + } + + /// + /// Sends the specified data to a client asynchronously using + /// the WebSocket connection. + /// + /// + /// This method does not wait for the send to be complete. + /// + /// + /// A that represents the text data to send. + /// + /// + /// + /// An Action<bool> delegate or + /// if not needed. + /// + /// + /// The delegate invokes the method called when the send is complete. + /// + /// + /// true is passed to the method if the send has done with + /// no error; otherwise, false. + /// + /// + /// + /// The current state of the connection is not Open. + /// + /// + /// is . + /// + /// + /// could not be UTF-8-encoded. + /// + protected void SendAsync (string data, Action completed) + { + if (_websocket == null) { + var msg = "The current state of the connection is not Open."; + throw new InvalidOperationException (msg); + } + + _websocket.SendAsync (data, completed); + } + + /// + /// Sends the data from the specified stream to a client asynchronously + /// using the WebSocket connection. + /// + /// + /// This method does not wait for the send to be complete. + /// + /// + /// + /// A instance from which to read the data to send. + /// + /// + /// The data is sent as the binary data. + /// + /// + /// + /// An that specifies the number of bytes to send. + /// + /// + /// + /// An Action<bool> delegate or + /// if not needed. + /// + /// + /// The delegate invokes the method called when the send is complete. + /// + /// + /// true is passed to the method if the send has done with + /// no error; otherwise, false. + /// + /// + /// + /// The current state of the connection is not Open. + /// + /// + /// is . + /// + /// + /// + /// cannot be read. + /// + /// + /// -or- + /// + /// + /// is less than 1. + /// + /// + /// -or- + /// + /// + /// No data could be read from . + /// + /// + protected void SendAsync (Stream stream, int length, Action completed) + { + if (_websocket == null) { + var msg = "The current state of the connection is not Open."; + throw new InvalidOperationException (msg); + } + + _websocket.SendAsync (stream, length, completed); + } + + #endregion + } +} diff --git a/websocket-sharp/Server/WebSocketServer.cs b/websocket-sharp/Server/WebSocketServer.cs new file mode 100644 index 0000000..be7bca7 --- /dev/null +++ b/websocket-sharp/Server/WebSocketServer.cs @@ -0,0 +1,1518 @@ +#region License +/* + * WebSocketServer.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: + * - Juan Manuel Lallana + * - Jonas Hovgaard + * - Liryna + * - Rohan Singh + */ +#endregion + +using System; +using System.Collections.Generic; +using System.Net.Sockets; +using System.Security.Cryptography.X509Certificates; +using System.Security.Principal; +using System.Text; +using System.Threading; +using WebSocketSharp.Net; +using WebSocketSharp.Net.WebSockets; + +namespace WebSocketSharp.Server +{ + /// + /// Provides a WebSocket protocol server. + /// + /// + /// This class can provide multiple WebSocket services. + /// + public class WebSocketServer + { + #region Private Fields + + private System.Net.IPAddress _address; + private bool _allowForwardedRequest; + private AuthenticationSchemes _authSchemes; + private static readonly string _defaultRealm; + private bool _dnsStyle; + private string _hostname; + private TcpListener _listener; + private Logger _log; + private int _port; + private string _realm; + private string _realmInUse; + private Thread _receiveThread; + private bool _reuseAddress; + private bool _secure; + private WebSocketServiceManager _services; + private ServerSslConfiguration _sslConfig; + private ServerSslConfiguration _sslConfigInUse; + private volatile ServerState _state; + private object _sync; + private Func _userCredFinder; + + #endregion + + #region Static Constructor + + static WebSocketServer () + { + _defaultRealm = "SECRET AREA"; + } + + #endregion + + #region Public Constructors + + /// + /// Initializes a new instance of the class. + /// + /// + /// The new instance listens for incoming handshake requests on + /// and port 80. + /// + public WebSocketServer () + { + var addr = System.Net.IPAddress.Any; + init (addr.ToString (), addr, 80, false); + } + + /// + /// Initializes a new instance of the class + /// with the specified . + /// + /// + /// + /// The new instance listens for incoming handshake requests on + /// and . + /// + /// + /// It provides secure connections if is 443. + /// + /// + /// + /// An that represents the number of the port + /// on which to listen. + /// + /// + /// is less than 1 or greater than 65535. + /// + public WebSocketServer (int port) + : this (port, port == 443) + { + } + + /// + /// Initializes a new instance of the class + /// with the specified . + /// + /// + /// + /// The new instance listens for incoming handshake requests on + /// the IP address of the host of and + /// the port of . + /// + /// + /// Either port 80 or 443 is used if includes + /// no port. Port 443 is used if the scheme of + /// is wss; otherwise, port 80 is used. + /// + /// + /// The new instance provides secure connections if the scheme of + /// is wss. + /// + /// + /// + /// A that represents the WebSocket URL of the server. + /// + /// + /// is . + /// + /// + /// + /// is an empty string. + /// + /// + /// -or- + /// + /// + /// is invalid. + /// + /// + public WebSocketServer (string url) + { + if (url == null) + throw new ArgumentNullException ("url"); + + if (url.Length == 0) + throw new ArgumentException ("An empty string.", "url"); + + Uri uri; + string msg; + if (!tryCreateUri (url, out uri, out msg)) + throw new ArgumentException (msg, "url"); + + var host = uri.DnsSafeHost; + + var addr = host.ToIPAddress (); + if (addr == null) { + msg = "The host part could not be converted to an IP address."; + throw new ArgumentException (msg, "url"); + } + + if (!addr.IsLocal ()) { + msg = "The IP address of the host is not a local IP address."; + throw new ArgumentException (msg, "url"); + } + + init (host, addr, uri.Port, uri.Scheme == "wss"); + } + + /// + /// Initializes a new instance of the class + /// with the specified and . + /// + /// + /// The new instance listens for incoming handshake requests on + /// and . + /// + /// + /// An that represents the number of the port + /// on which to listen. + /// + /// + /// A : true if the new instance provides + /// secure connections; otherwise, false. + /// + /// + /// is less than 1 or greater than 65535. + /// + public WebSocketServer (int port, bool secure) + { + if (!port.IsPortNumber ()) { + var msg = "Less than 1 or greater than 65535."; + throw new ArgumentOutOfRangeException ("port", msg); + } + + var addr = System.Net.IPAddress.Any; + init (addr.ToString (), addr, port, secure); + } + + /// + /// Initializes a new instance of the class + /// with the specified and . + /// + /// + /// + /// The new instance listens for incoming handshake requests on + /// and . + /// + /// + /// It provides secure connections if is 443. + /// + /// + /// + /// A that represents the local + /// IP address on which to listen. + /// + /// + /// An that represents the number of the port + /// on which to listen. + /// + /// + /// is . + /// + /// + /// is not a local IP address. + /// + /// + /// is less than 1 or greater than 65535. + /// + public WebSocketServer (System.Net.IPAddress address, int port) + : this (address, port, port == 443) + { + } + + /// + /// Initializes a new instance of the class + /// with the specified , , + /// and . + /// + /// + /// The new instance listens for incoming handshake requests on + /// and . + /// + /// + /// A that represents the local + /// IP address on which to listen. + /// + /// + /// An that represents the number of the port + /// on which to listen. + /// + /// + /// A : true if the new instance provides + /// secure connections; otherwise, false. + /// + /// + /// is . + /// + /// + /// is not a local IP address. + /// + /// + /// is less than 1 or greater than 65535. + /// + public WebSocketServer (System.Net.IPAddress address, int port, bool secure) + { + if (address == null) + throw new ArgumentNullException ("address"); + + if (!address.IsLocal ()) + throw new ArgumentException ("Not a local IP address.", "address"); + + if (!port.IsPortNumber ()) { + var msg = "Less than 1 or greater than 65535."; + throw new ArgumentOutOfRangeException ("port", msg); + } + + init (address.ToString (), address, port, secure); + } + + #endregion + + #region Public Properties + + /// + /// Gets the IP address of the server. + /// + /// + /// A that represents the local + /// IP address on which to listen for incoming handshake requests. + /// + public System.Net.IPAddress Address { + get { + return _address; + } + } + + /// + /// Gets or sets a value indicating whether the server accepts every + /// handshake request without checking the request URI. + /// + /// + /// The set operation does nothing if the server has already started or + /// it is shutting down. + /// + /// + /// + /// true if the server accepts every handshake request without + /// checking the request URI; otherwise, false. + /// + /// + /// The default value is false. + /// + /// + public bool AllowForwardedRequest { + get { + return _allowForwardedRequest; + } + + set { + string msg; + if (!canSet (out msg)) { + _log.Warn (msg); + return; + } + + lock (_sync) { + if (!canSet (out msg)) { + _log.Warn (msg); + return; + } + + _allowForwardedRequest = value; + } + } + } + + /// + /// Gets or sets the scheme used to authenticate the clients. + /// + /// + /// The set operation does nothing if the server has already started or + /// it is shutting down. + /// + /// + /// + /// One of the + /// enum values. + /// + /// + /// It represents the scheme used to authenticate the clients. + /// + /// + /// The default value is + /// . + /// + /// + public AuthenticationSchemes AuthenticationSchemes { + get { + return _authSchemes; + } + + set { + string msg; + if (!canSet (out msg)) { + _log.Warn (msg); + return; + } + + lock (_sync) { + if (!canSet (out msg)) { + _log.Warn (msg); + return; + } + + _authSchemes = value; + } + } + } + + /// + /// Gets a value indicating whether the server has started. + /// + /// + /// true if the server has started; otherwise, false. + /// + public bool IsListening { + get { + return _state == ServerState.Start; + } + } + + /// + /// Gets a value indicating whether secure connections are provided. + /// + /// + /// true if this instance provides secure connections; otherwise, + /// false. + /// + public bool IsSecure { + get { + return _secure; + } + } + + /// + /// Gets or sets a value indicating whether the server cleans up + /// the inactive sessions periodically. + /// + /// + /// The set operation does nothing if the server has already started or + /// it is shutting down. + /// + /// + /// + /// true if the server cleans up the inactive sessions every + /// 60 seconds; otherwise, false. + /// + /// + /// The default value is true. + /// + /// + public bool KeepClean { + get { + return _services.KeepClean; + } + + set { + _services.KeepClean = value; + } + } + + /// + /// Gets the logging function for the server. + /// + /// + /// The default logging level is . + /// + /// + /// A that provides the logging function. + /// + public Logger Log { + get { + return _log; + } + } + + /// + /// Gets the port of the server. + /// + /// + /// An that represents the number of the port + /// on which to listen for incoming handshake requests. + /// + public int Port { + get { + return _port; + } + } + + /// + /// Gets or sets the realm used for authentication. + /// + /// + /// + /// "SECRET AREA" is used as the realm if the value is + /// or an empty string. + /// + /// + /// The set operation does nothing if the server has + /// already started or it is shutting down. + /// + /// + /// + /// + /// A or by default. + /// + /// + /// That string represents the name of the realm. + /// + /// + public string Realm { + get { + return _realm; + } + + set { + string msg; + if (!canSet (out msg)) { + _log.Warn (msg); + return; + } + + lock (_sync) { + if (!canSet (out msg)) { + _log.Warn (msg); + return; + } + + _realm = value; + } + } + } + + /// + /// Gets or sets a value indicating whether the server is allowed to + /// be bound to an address that is already in use. + /// + /// + /// + /// You should set this property to true if you would + /// like to resolve to wait for socket in TIME_WAIT state. + /// + /// + /// The set operation does nothing if the server has already + /// started or it is shutting down. + /// + /// + /// + /// + /// true if the server is allowed to be bound to an address + /// that is already in use; otherwise, false. + /// + /// + /// The default value is false. + /// + /// + public bool ReuseAddress { + get { + return _reuseAddress; + } + + set { + string msg; + if (!canSet (out msg)) { + _log.Warn (msg); + return; + } + + lock (_sync) { + if (!canSet (out msg)) { + _log.Warn (msg); + return; + } + + _reuseAddress = value; + } + } + } + + /// + /// Gets the configuration for secure connection. + /// + /// + /// This configuration will be referenced when attempts to start, + /// so it must be configured before the start method is called. + /// + /// + /// A that represents + /// the configuration used to provide secure connections. + /// + /// + /// This instance does not provide secure connections. + /// + public ServerSslConfiguration SslConfiguration { + get { + if (!_secure) { + var msg = "This instance does not provide secure connections."; + throw new InvalidOperationException (msg); + } + + return getSslConfiguration (); + } + } + + /// + /// Gets or sets the delegate used to find the credentials + /// for an identity. + /// + /// + /// + /// No credentials are found if the method invoked by + /// the delegate returns or + /// the value is . + /// + /// + /// The set operation does nothing if the server has + /// already started or it is shutting down. + /// + /// + /// + /// + /// A Func<, + /// > delegate or + /// if not needed. + /// + /// + /// That delegate invokes the method called for finding + /// the credentials used to authenticate a client. + /// + /// + /// The default value is . + /// + /// + public Func UserCredentialsFinder { + get { + return _userCredFinder; + } + + set { + string msg; + if (!canSet (out msg)) { + _log.Warn (msg); + return; + } + + lock (_sync) { + if (!canSet (out msg)) { + _log.Warn (msg); + return; + } + + _userCredFinder = value; + } + } + } + + /// + /// Gets or sets the time to wait for the response to the WebSocket Ping or + /// Close. + /// + /// + /// The set operation does nothing if the server has already started or + /// it is shutting down. + /// + /// + /// + /// A to wait for the response. + /// + /// + /// The default value is the same as 1 second. + /// + /// + /// + /// The value specified for a set operation is zero or less. + /// + public TimeSpan WaitTime { + get { + return _services.WaitTime; + } + + set { + _services.WaitTime = value; + } + } + + /// + /// Gets the management function for the WebSocket services + /// provided by the server. + /// + /// + /// A that manages + /// the WebSocket services provided by the server. + /// + public WebSocketServiceManager WebSocketServices { + get { + return _services; + } + } + + #endregion + + #region Private Methods + + private void abort () + { + lock (_sync) { + if (_state != ServerState.Start) + return; + + _state = ServerState.ShuttingDown; + } + + try { + try { + _listener.Stop (); + } + finally { + _services.Stop (1006, String.Empty); + } + } + catch { + } + + _state = ServerState.Stop; + } + + private bool authenticateClient (TcpListenerWebSocketContext context) + { + if (_authSchemes == AuthenticationSchemes.Anonymous) + return true; + + if (_authSchemes == AuthenticationSchemes.None) + return false; + + return context.Authenticate (_authSchemes, _realmInUse, _userCredFinder); + } + + private bool canSet (out string message) + { + message = null; + + if (_state == ServerState.Start) { + message = "The server has already started."; + return false; + } + + if (_state == ServerState.ShuttingDown) { + message = "The server is shutting down."; + return false; + } + + return true; + } + + private bool checkHostNameForRequest (string name) + { + return !_dnsStyle + || Uri.CheckHostName (name) != UriHostNameType.Dns + || name == _hostname; + } + + private static bool checkSslConfiguration ( + ServerSslConfiguration configuration, out string message + ) + { + message = null; + + if (configuration.ServerCertificate == null) { + message = "There is no server certificate for secure connection."; + return false; + } + + return true; + } + + private string getRealm () + { + var realm = _realm; + return realm != null && realm.Length > 0 ? realm : _defaultRealm; + } + + private ServerSslConfiguration getSslConfiguration () + { + if (_sslConfig == null) + _sslConfig = new ServerSslConfiguration (); + + return _sslConfig; + } + + private void init ( + string hostname, System.Net.IPAddress address, int port, bool secure + ) + { + _hostname = hostname; + _address = address; + _port = port; + _secure = secure; + + _authSchemes = AuthenticationSchemes.Anonymous; + _dnsStyle = Uri.CheckHostName (hostname) == UriHostNameType.Dns; + _listener = new TcpListener (address, port); + _log = new Logger (); + _services = new WebSocketServiceManager (_log); + _sync = new object (); + } + + private void processRequest (TcpListenerWebSocketContext context) + { + if (!authenticateClient (context)) { + context.Close (HttpStatusCode.Forbidden); + return; + } + + var uri = context.RequestUri; + if (uri == null) { + context.Close (HttpStatusCode.BadRequest); + return; + } + + if (!_allowForwardedRequest) { + if (uri.Port != _port) { + context.Close (HttpStatusCode.BadRequest); + return; + } + + if (!checkHostNameForRequest (uri.DnsSafeHost)) { + context.Close (HttpStatusCode.NotFound); + return; + } + } + + var path = uri.AbsolutePath; + if (path.IndexOfAny (new[] { '%', '+' }) > -1) + path = HttpUtility.UrlDecode (path, Encoding.UTF8); + + WebSocketServiceHost host; + if (!_services.InternalTryGetServiceHost (path, out host)) { + context.Close (HttpStatusCode.NotImplemented); + return; + } + + host.StartSession (context); + } + + private void receiveRequest () + { + while (true) { + TcpClient cl = null; + try { + cl = _listener.AcceptTcpClient (); + ThreadPool.QueueUserWorkItem ( + state => { + try { + var ctx = new TcpListenerWebSocketContext ( + cl, null, _secure, _sslConfigInUse, _log + ); + + processRequest (ctx); + } + catch (Exception ex) { + _log.Error (ex.Message); + _log.Debug (ex.ToString ()); + + cl.Close (); + } + } + ); + } + catch (SocketException ex) { + if (_state == ServerState.ShuttingDown) { + _log.Info ("The underlying listener is stopped."); + break; + } + + _log.Fatal (ex.Message); + _log.Debug (ex.ToString ()); + + break; + } + catch (Exception ex) { + _log.Fatal (ex.Message); + _log.Debug (ex.ToString ()); + + if (cl != null) + cl.Close (); + + break; + } + } + + if (_state != ServerState.ShuttingDown) + abort (); + } + + private void start (ServerSslConfiguration sslConfig) + { + if (_state == ServerState.Start) { + _log.Info ("The server has already started."); + return; + } + + if (_state == ServerState.ShuttingDown) { + _log.Warn ("The server is shutting down."); + return; + } + + lock (_sync) { + if (_state == ServerState.Start) { + _log.Info ("The server has already started."); + return; + } + + if (_state == ServerState.ShuttingDown) { + _log.Warn ("The server is shutting down."); + return; + } + + _sslConfigInUse = sslConfig; + _realmInUse = getRealm (); + + _services.Start (); + try { + startReceiving (); + } + catch { + _services.Stop (1011, String.Empty); + throw; + } + + _state = ServerState.Start; + } + } + + private void startReceiving () + { + if (_reuseAddress) { + _listener.Server.SetSocketOption ( + SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true + ); + } + + try { + _listener.Start (); + } + catch (Exception ex) { + var msg = "The underlying listener has failed to start."; + throw new InvalidOperationException (msg, ex); + } + + _receiveThread = new Thread (new ThreadStart (receiveRequest)); + _receiveThread.IsBackground = true; + _receiveThread.Start (); + } + + private void stop (ushort code, string reason) + { + if (_state == ServerState.Ready) { + _log.Info ("The server is not started."); + return; + } + + if (_state == ServerState.ShuttingDown) { + _log.Info ("The server is shutting down."); + return; + } + + if (_state == ServerState.Stop) { + _log.Info ("The server has already stopped."); + return; + } + + lock (_sync) { + if (_state == ServerState.ShuttingDown) { + _log.Info ("The server is shutting down."); + return; + } + + if (_state == ServerState.Stop) { + _log.Info ("The server has already stopped."); + return; + } + + _state = ServerState.ShuttingDown; + } + + try { + var threw = false; + try { + stopReceiving (5000); + } + catch { + threw = true; + throw; + } + finally { + try { + _services.Stop (code, reason); + } + catch { + if (!threw) + throw; + } + } + } + finally { + _state = ServerState.Stop; + } + } + + private void stopReceiving (int millisecondsTimeout) + { + try { + _listener.Stop (); + } + catch (Exception ex) { + var msg = "The underlying listener has failed to stop."; + throw new InvalidOperationException (msg, ex); + } + + _receiveThread.Join (millisecondsTimeout); + } + + private static bool tryCreateUri ( + string uriString, out Uri result, out string message + ) + { + if (!uriString.TryCreateWebSocketUri (out result, out message)) + return false; + + if (result.PathAndQuery != "/") { + result = null; + message = "It includes either or both path and query components."; + + return false; + } + + return true; + } + + #endregion + + #region Public Methods + + /// + /// Adds a WebSocket service with the specified behavior, path, + /// and delegate. + /// + /// + /// + /// A that represents an absolute path to + /// the service to add. + /// + /// + /// / is trimmed from the end of the string if present. + /// + /// + /// + /// + /// A Func<TBehavior> delegate. + /// + /// + /// It invokes the method called when creating a new session + /// instance for the service. + /// + /// + /// The method must create a new instance of the specified + /// behavior class and return it. + /// + /// + /// + /// + /// The type of the behavior for the service. + /// + /// + /// It must inherit the class. + /// + /// + /// + /// + /// is . + /// + /// + /// -or- + /// + /// + /// is . + /// + /// + /// + /// + /// is an empty string. + /// + /// + /// -or- + /// + /// + /// is not an absolute path. + /// + /// + /// -or- + /// + /// + /// includes either or both + /// query and fragment components. + /// + /// + /// -or- + /// + /// + /// is already in use. + /// + /// + [Obsolete ("This method will be removed. Use added one instead.")] + public void AddWebSocketService ( + string path, Func creator + ) + where TBehavior : WebSocketBehavior + { + if (path == null) + throw new ArgumentNullException ("path"); + + if (creator == null) + throw new ArgumentNullException ("creator"); + + if (path.Length == 0) + throw new ArgumentException ("An empty string.", "path"); + + if (path[0] != '/') + throw new ArgumentException ("Not an absolute path.", "path"); + + if (path.IndexOfAny (new[] { '?', '#' }) > -1) { + var msg = "It includes either or both query and fragment components."; + throw new ArgumentException (msg, "path"); + } + + _services.Add (path, creator); + } + + /// + /// Adds a WebSocket service with the specified behavior and path. + /// + /// + /// + /// A that represents an absolute path to + /// the service to add. + /// + /// + /// / is trimmed from the end of the string if present. + /// + /// + /// + /// + /// The type of the behavior for the service. + /// + /// + /// It must inherit the class. + /// + /// + /// And also, it must have a public parameterless constructor. + /// + /// + /// + /// is . + /// + /// + /// + /// is an empty string. + /// + /// + /// -or- + /// + /// + /// is not an absolute path. + /// + /// + /// -or- + /// + /// + /// includes either or both + /// query and fragment components. + /// + /// + /// -or- + /// + /// + /// is already in use. + /// + /// + public void AddWebSocketService (string path) + where TBehaviorWithNew : WebSocketBehavior, new () + { + _services.AddService (path, null); + } + + /// + /// Adds a WebSocket service with the specified behavior, path, + /// and delegate. + /// + /// + /// + /// A that represents an absolute path to + /// the service to add. + /// + /// + /// / is trimmed from the end of the string if present. + /// + /// + /// + /// + /// An Action<TBehaviorWithNew> delegate or + /// if not needed. + /// + /// + /// The delegate invokes the method called when initializing + /// a new session instance for the service. + /// + /// + /// + /// + /// The type of the behavior for the service. + /// + /// + /// It must inherit the class. + /// + /// + /// And also, it must have a public parameterless constructor. + /// + /// + /// + /// is . + /// + /// + /// + /// is an empty string. + /// + /// + /// -or- + /// + /// + /// is not an absolute path. + /// + /// + /// -or- + /// + /// + /// includes either or both + /// query and fragment components. + /// + /// + /// -or- + /// + /// + /// is already in use. + /// + /// + public void AddWebSocketService ( + string path, Action initializer + ) + where TBehaviorWithNew : WebSocketBehavior, new () + { + _services.AddService (path, initializer); + } + + /// + /// Removes a WebSocket service with the specified path. + /// + /// + /// The service is stopped with close status 1001 (going away) + /// if it has already started. + /// + /// + /// true if the service is successfully found and removed; + /// otherwise, false. + /// + /// + /// + /// A that represents an absolute path to + /// the service to remove. + /// + /// + /// / is trimmed from the end of the string if present. + /// + /// + /// + /// is . + /// + /// + /// + /// is an empty string. + /// + /// + /// -or- + /// + /// + /// is not an absolute path. + /// + /// + /// -or- + /// + /// + /// includes either or both + /// query and fragment components. + /// + /// + public bool RemoveWebSocketService (string path) + { + return _services.RemoveService (path); + } + + /// + /// Starts receiving incoming handshake requests. + /// + /// + /// This method does nothing if the server has already started or + /// it is shutting down. + /// + /// + /// + /// There is no server certificate for secure connection. + /// + /// + /// -or- + /// + /// + /// The underlying has failed to start. + /// + /// + public void Start () + { + ServerSslConfiguration sslConfig = null; + + if (_secure) { + sslConfig = new ServerSslConfiguration (getSslConfiguration ()); + + string msg; + if (!checkSslConfiguration (sslConfig, out msg)) + throw new InvalidOperationException (msg); + } + + start (sslConfig); + } + + /// + /// Stops receiving incoming handshake requests. + /// + /// + /// The underlying has failed to stop. + /// + public void Stop () + { + stop (1001, String.Empty); + } + + /// + /// Stops receiving incoming handshake requests and closes each connection + /// with the specified code and reason. + /// + /// + /// + /// A that represents the status code indicating + /// the reason for the close. + /// + /// + /// The status codes are defined in + /// + /// Section 7.4 of RFC 6455. + /// + /// + /// + /// + /// A that represents the reason for the close. + /// + /// + /// The size must be 123 bytes or less in UTF-8. + /// + /// + /// + /// + /// is less than 1000 or greater than 4999. + /// + /// + /// -or- + /// + /// + /// The size of is greater than 123 bytes. + /// + /// + /// + /// + /// is 1010 (mandatory extension). + /// + /// + /// -or- + /// + /// + /// is 1005 (no status) and there is reason. + /// + /// + /// -or- + /// + /// + /// could not be UTF-8-encoded. + /// + /// + /// + /// The underlying has failed to stop. + /// + [Obsolete ("This method will be removed.")] + public void Stop (ushort code, string reason) + { + if (!code.IsCloseStatusCode ()) { + var msg = "Less than 1000 or greater than 4999."; + throw new ArgumentOutOfRangeException ("code", msg); + } + + if (code == 1010) { + var msg = "1010 cannot be used."; + throw new ArgumentException (msg, "code"); + } + + if (!reason.IsNullOrEmpty ()) { + if (code == 1005) { + var msg = "1005 cannot be used."; + throw new ArgumentException (msg, "code"); + } + + byte[] bytes; + if (!reason.TryGetUTF8EncodedBytes (out bytes)) { + var msg = "It could not be UTF-8-encoded."; + throw new ArgumentException (msg, "reason"); + } + + if (bytes.Length > 123) { + var msg = "Its size is greater than 123 bytes."; + throw new ArgumentOutOfRangeException ("reason", msg); + } + } + + stop (code, reason); + } + + /// + /// Stops receiving incoming handshake requests and closes each connection + /// with the specified code and reason. + /// + /// + /// + /// One of the enum values. + /// + /// + /// It represents the status code indicating the reason for the close. + /// + /// + /// + /// + /// A that represents the reason for the close. + /// + /// + /// The size must be 123 bytes or less in UTF-8. + /// + /// + /// + /// + /// is + /// . + /// + /// + /// -or- + /// + /// + /// is + /// and there is reason. + /// + /// + /// -or- + /// + /// + /// could not be UTF-8-encoded. + /// + /// + /// + /// The size of is greater than 123 bytes. + /// + /// + /// The underlying has failed to stop. + /// + [Obsolete ("This method will be removed.")] + public void Stop (CloseStatusCode code, string reason) + { + if (code == CloseStatusCode.MandatoryExtension) { + var msg = "MandatoryExtension cannot be used."; + throw new ArgumentException (msg, "code"); + } + + if (!reason.IsNullOrEmpty ()) { + if (code == CloseStatusCode.NoStatus) { + var msg = "NoStatus cannot be used."; + throw new ArgumentException (msg, "code"); + } + + byte[] bytes; + if (!reason.TryGetUTF8EncodedBytes (out bytes)) { + var msg = "It could not be UTF-8-encoded."; + throw new ArgumentException (msg, "reason"); + } + + if (bytes.Length > 123) { + var msg = "Its size is greater than 123 bytes."; + throw new ArgumentOutOfRangeException ("reason", msg); + } + } + + stop ((ushort) code, reason); + } + + #endregion + } +} diff --git a/websocket-sharp/Server/WebSocketServiceHost.cs b/websocket-sharp/Server/WebSocketServiceHost.cs new file mode 100644 index 0000000..1da7642 --- /dev/null +++ b/websocket-sharp/Server/WebSocketServiceHost.cs @@ -0,0 +1,224 @@ +#region License +/* + * WebSocketServiceHost.cs + * + * The MIT License + * + * Copyright (c) 2012-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 Contributors +/* + * Contributors: + * - Juan Manuel Lallana + */ +#endregion + +using System; +using WebSocketSharp.Net.WebSockets; + +namespace WebSocketSharp.Server +{ + /// + /// Exposes the methods and properties used to access the information in + /// a WebSocket service provided by the or + /// . + /// + /// + /// This class is an abstract class. + /// + public abstract class WebSocketServiceHost + { + #region Private Fields + + private Logger _log; + private string _path; + private WebSocketSessionManager _sessions; + + #endregion + + #region Protected Constructors + + /// + /// Initializes a new instance of the class + /// with the specified and . + /// + /// + /// A that represents the absolute path to the service. + /// + /// + /// A that represents the logging function for the service. + /// + protected WebSocketServiceHost (string path, Logger log) + { + _path = path; + _log = log; + + _sessions = new WebSocketSessionManager (log); + } + + #endregion + + #region Internal Properties + + internal ServerState State { + get { + return _sessions.State; + } + } + + #endregion + + #region Protected Properties + + /// + /// Gets the logging function for the service. + /// + /// + /// A that provides the logging function. + /// + protected Logger Log { + get { + return _log; + } + } + + #endregion + + #region Public Properties + + /// + /// Gets or sets a value indicating whether the service cleans up + /// the inactive sessions periodically. + /// + /// + /// The set operation does nothing if the service has already started or + /// it is shutting down. + /// + /// + /// true if the service cleans up the inactive sessions every + /// 60 seconds; otherwise, false. + /// + public bool KeepClean { + get { + return _sessions.KeepClean; + } + + set { + _sessions.KeepClean = value; + } + } + + /// + /// Gets the path to the service. + /// + /// + /// A that represents the absolute path to + /// the service. + /// + public string Path { + get { + return _path; + } + } + + /// + /// Gets the management function for the sessions in the service. + /// + /// + /// A that manages the sessions in + /// the service. + /// + public WebSocketSessionManager Sessions { + get { + return _sessions; + } + } + + /// + /// Gets the of the behavior of the service. + /// + /// + /// A that represents the type of the behavior of + /// the service. + /// + public abstract Type BehaviorType { get; } + + /// + /// Gets or sets the time to wait for the response to the WebSocket Ping or + /// Close. + /// + /// + /// The set operation does nothing if the service has already started or + /// it is shutting down. + /// + /// + /// A to wait for the response. + /// + /// + /// The value specified for a set operation is zero or less. + /// + public TimeSpan WaitTime { + get { + return _sessions.WaitTime; + } + + set { + _sessions.WaitTime = value; + } + } + + #endregion + + #region Internal Methods + + internal void Start () + { + _sessions.Start (); + } + + internal void StartSession (WebSocketContext context) + { + CreateSession ().Start (context, _sessions); + } + + internal void Stop (ushort code, string reason) + { + _sessions.Stop (code, reason); + } + + #endregion + + #region Protected Methods + + /// + /// Creates a new session for the service. + /// + /// + /// A instance that represents + /// the new session. + /// + protected abstract WebSocketBehavior CreateSession (); + + #endregion + } +} diff --git a/websocket-sharp/Server/WebSocketServiceHost`1.cs b/websocket-sharp/Server/WebSocketServiceHost`1.cs new file mode 100644 index 0000000..d4ca6a2 --- /dev/null +++ b/websocket-sharp/Server/WebSocketServiceHost`1.cs @@ -0,0 +1,102 @@ +#region License +/* + * WebSocketServiceHost`1.cs + * + * The MIT License + * + * Copyright (c) 2015-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.Server +{ + internal class WebSocketServiceHost : WebSocketServiceHost + where TBehavior : WebSocketBehavior + { + #region Private Fields + + private Func _creator; + + #endregion + + #region Internal Constructors + + internal WebSocketServiceHost ( + string path, Func creator, Logger log + ) + : this (path, creator, null, log) + { + } + + internal WebSocketServiceHost ( + string path, + Func creator, + Action initializer, + Logger log + ) + : base (path, log) + { + _creator = createCreator (creator, initializer); + } + + #endregion + + #region Public Properties + + public override Type BehaviorType { + get { + return typeof (TBehavior); + } + } + + #endregion + + #region Private Methods + + private Func createCreator ( + Func creator, Action initializer + ) + { + if (initializer == null) + return creator; + + return () => { + var ret = creator (); + initializer (ret); + + return ret; + }; + } + + #endregion + + #region Protected Methods + + protected override WebSocketBehavior CreateSession () + { + return _creator (); + } + + #endregion + } +} diff --git a/websocket-sharp/Server/WebSocketServiceManager.cs b/websocket-sharp/Server/WebSocketServiceManager.cs new file mode 100644 index 0000000..ee1256f --- /dev/null +++ b/websocket-sharp/Server/WebSocketServiceManager.cs @@ -0,0 +1,1078 @@ +#region License +/* + * WebSocketServiceManager.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; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading; +using WebSocketSharp.Net; + +namespace WebSocketSharp.Server +{ + /// + /// Provides the management function for the WebSocket services. + /// + /// + /// This class manages the WebSocket services provided by + /// the or . + /// + public class WebSocketServiceManager + { + #region Private Fields + + private volatile bool _clean; + private Dictionary _hosts; + private Logger _log; + private volatile ServerState _state; + private object _sync; + private TimeSpan _waitTime; + + #endregion + + #region Internal Constructors + + internal WebSocketServiceManager (Logger log) + { + _log = log; + + _clean = true; + _hosts = new Dictionary (); + _state = ServerState.Ready; + _sync = ((ICollection) _hosts).SyncRoot; + _waitTime = TimeSpan.FromSeconds (1); + } + + #endregion + + #region Public Properties + + /// + /// Gets the number of the WebSocket services. + /// + /// + /// An that represents the number of the services. + /// + public int Count { + get { + lock (_sync) + return _hosts.Count; + } + } + + /// + /// Gets the host instances for the WebSocket services. + /// + /// + /// + /// An IEnumerable<WebSocketServiceHost> instance. + /// + /// + /// It provides an enumerator which supports the iteration over + /// the collection of the host instances. + /// + /// + public IEnumerable Hosts { + get { + lock (_sync) + return _hosts.Values.ToList (); + } + } + + /// + /// Gets the host instance for a WebSocket service with the specified path. + /// + /// + /// + /// A instance or + /// if not found. + /// + /// + /// The host instance provides the function to access + /// the information in the service. + /// + /// + /// + /// + /// A that represents an absolute path to + /// the service to find. + /// + /// + /// / is trimmed from the end of the string if present. + /// + /// + /// + /// is . + /// + /// + /// + /// is empty. + /// + /// + /// -or- + /// + /// + /// is not an absolute path. + /// + /// + /// -or- + /// + /// + /// includes either or both + /// query and fragment components. + /// + /// + public WebSocketServiceHost this[string path] { + get { + if (path == null) + throw new ArgumentNullException ("path"); + + if (path.Length == 0) + throw new ArgumentException ("An empty string.", "path"); + + if (path[0] != '/') + throw new ArgumentException ("Not an absolute path.", "path"); + + if (path.IndexOfAny (new[] { '?', '#' }) > -1) { + var msg = "It includes either or both query and fragment components."; + throw new ArgumentException (msg, "path"); + } + + WebSocketServiceHost host; + InternalTryGetServiceHost (path, out host); + + return host; + } + } + + /// + /// Gets or sets a value indicating whether the inactive sessions in + /// the WebSocket services are cleaned up periodically. + /// + /// + /// The set operation does nothing if the server has already started or + /// it is shutting down. + /// + /// + /// true if the inactive sessions are cleaned up every 60 seconds; + /// otherwise, false. + /// + public bool KeepClean { + get { + return _clean; + } + + set { + string msg; + if (!canSet (out msg)) { + _log.Warn (msg); + return; + } + + lock (_sync) { + if (!canSet (out msg)) { + _log.Warn (msg); + return; + } + + foreach (var host in _hosts.Values) + host.KeepClean = value; + + _clean = value; + } + } + } + + /// + /// Gets the paths for the WebSocket services. + /// + /// + /// + /// An IEnumerable<string> instance. + /// + /// + /// It provides an enumerator which supports the iteration over + /// the collection of the paths. + /// + /// + public IEnumerable Paths { + get { + lock (_sync) + return _hosts.Keys.ToList (); + } + } + + /// + /// Gets the total number of the sessions in the WebSocket services. + /// + /// + /// An that represents the total number of + /// the sessions in the services. + /// + [Obsolete ("This property will be removed.")] + public int SessionCount { + get { + var cnt = 0; + foreach (var host in Hosts) { + if (_state != ServerState.Start) + break; + + cnt += host.Sessions.Count; + } + + return cnt; + } + } + + /// + /// Gets or sets the time to wait for the response to the WebSocket Ping or + /// Close. + /// + /// + /// The set operation does nothing if the server has already started or + /// it is shutting down. + /// + /// + /// A to wait for the response. + /// + /// + /// The value specified for a set operation is zero or less. + /// + public TimeSpan WaitTime { + get { + return _waitTime; + } + + set { + if (value <= TimeSpan.Zero) + throw new ArgumentOutOfRangeException ("value", "Zero or less."); + + string msg; + if (!canSet (out msg)) { + _log.Warn (msg); + return; + } + + lock (_sync) { + if (!canSet (out msg)) { + _log.Warn (msg); + return; + } + + foreach (var host in _hosts.Values) + host.WaitTime = value; + + _waitTime = value; + } + } + } + + #endregion + + #region Private Methods + + private void broadcast (Opcode opcode, byte[] data, Action completed) + { + var cache = new Dictionary (); + + try { + foreach (var host in Hosts) { + if (_state != ServerState.Start) { + _log.Error ("The server is shutting down."); + break; + } + + host.Sessions.Broadcast (opcode, data, cache); + } + + if (completed != null) + completed (); + } + catch (Exception ex) { + _log.Error (ex.Message); + _log.Debug (ex.ToString ()); + } + finally { + cache.Clear (); + } + } + + private void broadcast (Opcode opcode, Stream stream, Action completed) + { + var cache = new Dictionary (); + + try { + foreach (var host in Hosts) { + if (_state != ServerState.Start) { + _log.Error ("The server is shutting down."); + break; + } + + host.Sessions.Broadcast (opcode, stream, cache); + } + + if (completed != null) + completed (); + } + catch (Exception ex) { + _log.Error (ex.Message); + _log.Debug (ex.ToString ()); + } + finally { + foreach (var cached in cache.Values) + cached.Dispose (); + + cache.Clear (); + } + } + + private void broadcastAsync (Opcode opcode, byte[] data, Action completed) + { + ThreadPool.QueueUserWorkItem ( + state => broadcast (opcode, data, completed) + ); + } + + private void broadcastAsync (Opcode opcode, Stream stream, Action completed) + { + ThreadPool.QueueUserWorkItem ( + state => broadcast (opcode, stream, completed) + ); + } + + private Dictionary> broadping ( + byte[] frameAsBytes, TimeSpan timeout + ) + { + var ret = new Dictionary> (); + + foreach (var host in Hosts) { + if (_state != ServerState.Start) { + _log.Error ("The server is shutting down."); + break; + } + + var res = host.Sessions.Broadping (frameAsBytes, timeout); + ret.Add (host.Path, res); + } + + return ret; + } + + private bool canSet (out string message) + { + message = null; + + if (_state == ServerState.Start) { + message = "The server has already started."; + return false; + } + + if (_state == ServerState.ShuttingDown) { + message = "The server is shutting down."; + return false; + } + + return true; + } + + #endregion + + #region Internal Methods + + internal void Add (string path, Func creator) + where TBehavior : WebSocketBehavior + { + path = path.TrimSlashFromEnd (); + + lock (_sync) { + WebSocketServiceHost host; + if (_hosts.TryGetValue (path, out host)) + throw new ArgumentException ("Already in use.", "path"); + + host = new WebSocketServiceHost ( + path, creator, null, _log + ); + + if (!_clean) + host.KeepClean = false; + + if (_waitTime != host.WaitTime) + host.WaitTime = _waitTime; + + if (_state == ServerState.Start) + host.Start (); + + _hosts.Add (path, host); + } + } + + internal bool InternalTryGetServiceHost ( + string path, out WebSocketServiceHost host + ) + { + path = path.TrimSlashFromEnd (); + + lock (_sync) + return _hosts.TryGetValue (path, out host); + } + + internal void Start () + { + lock (_sync) { + foreach (var host in _hosts.Values) + host.Start (); + + _state = ServerState.Start; + } + } + + internal void Stop (ushort code, string reason) + { + lock (_sync) { + _state = ServerState.ShuttingDown; + + foreach (var host in _hosts.Values) + host.Stop (code, reason); + + _state = ServerState.Stop; + } + } + + #endregion + + #region Public Methods + + /// + /// Adds a WebSocket service with the specified behavior, path, + /// and delegate. + /// + /// + /// + /// A that represents an absolute path to + /// the service to add. + /// + /// + /// / is trimmed from the end of the string if present. + /// + /// + /// + /// + /// An Action<TBehavior> delegate or + /// if not needed. + /// + /// + /// The delegate invokes the method called when initializing + /// a new session instance for the service. + /// + /// + /// + /// + /// The type of the behavior for the service. + /// + /// + /// It must inherit the class. + /// + /// + /// And also, it must have a public parameterless constructor. + /// + /// + /// + /// is . + /// + /// + /// + /// is empty. + /// + /// + /// -or- + /// + /// + /// is not an absolute path. + /// + /// + /// -or- + /// + /// + /// includes either or both + /// query and fragment components. + /// + /// + /// -or- + /// + /// + /// is already in use. + /// + /// + public void AddService ( + string path, Action initializer + ) + where TBehavior : WebSocketBehavior, new () + { + if (path == null) + throw new ArgumentNullException ("path"); + + if (path.Length == 0) + throw new ArgumentException ("An empty string.", "path"); + + if (path[0] != '/') + throw new ArgumentException ("Not an absolute path.", "path"); + + if (path.IndexOfAny (new[] { '?', '#' }) > -1) { + var msg = "It includes either or both query and fragment components."; + throw new ArgumentException (msg, "path"); + } + + path = path.TrimSlashFromEnd (); + + lock (_sync) { + WebSocketServiceHost host; + if (_hosts.TryGetValue (path, out host)) + throw new ArgumentException ("Already in use.", "path"); + + host = new WebSocketServiceHost ( + path, () => new TBehavior (), initializer, _log + ); + + if (!_clean) + host.KeepClean = false; + + if (_waitTime != host.WaitTime) + host.WaitTime = _waitTime; + + if (_state == ServerState.Start) + host.Start (); + + _hosts.Add (path, host); + } + } + + /// + /// Sends to every client in the WebSocket services. + /// + /// + /// An array of that represents the binary data to send. + /// + /// + /// The current state of the manager is not Start. + /// + /// + /// is . + /// + [Obsolete ("This method will be removed.")] + public void Broadcast (byte[] data) + { + if (_state != ServerState.Start) { + var msg = "The current state of the manager is not Start."; + throw new InvalidOperationException (msg); + } + + if (data == null) + throw new ArgumentNullException ("data"); + + if (data.LongLength <= WebSocket.FragmentLength) + broadcast (Opcode.Binary, data, null); + else + broadcast (Opcode.Binary, new MemoryStream (data), null); + } + + /// + /// Sends to every client in the WebSocket services. + /// + /// + /// A that represents the text data to send. + /// + /// + /// The current state of the manager is not Start. + /// + /// + /// is . + /// + /// + /// could not be UTF-8-encoded. + /// + [Obsolete ("This method will be removed.")] + public void Broadcast (string data) + { + if (_state != ServerState.Start) { + var msg = "The current state of the manager is not Start."; + throw new InvalidOperationException (msg); + } + + if (data == null) + throw new ArgumentNullException ("data"); + + byte[] bytes; + if (!data.TryGetUTF8EncodedBytes (out bytes)) { + var msg = "It could not be UTF-8-encoded."; + throw new ArgumentException (msg, "data"); + } + + if (bytes.LongLength <= WebSocket.FragmentLength) + broadcast (Opcode.Text, bytes, null); + else + broadcast (Opcode.Text, new MemoryStream (bytes), null); + } + + /// + /// Sends asynchronously to every client in + /// the WebSocket services. + /// + /// + /// This method does not wait for the send to be complete. + /// + /// + /// An array of that represents the binary data to send. + /// + /// + /// + /// An delegate or + /// if not needed. + /// + /// + /// The delegate invokes the method called when the send is complete. + /// + /// + /// + /// The current state of the manager is not Start. + /// + /// + /// is . + /// + [Obsolete ("This method will be removed.")] + public void BroadcastAsync (byte[] data, Action completed) + { + if (_state != ServerState.Start) { + var msg = "The current state of the manager is not Start."; + throw new InvalidOperationException (msg); + } + + if (data == null) + throw new ArgumentNullException ("data"); + + if (data.LongLength <= WebSocket.FragmentLength) + broadcastAsync (Opcode.Binary, data, completed); + else + broadcastAsync (Opcode.Binary, new MemoryStream (data), completed); + } + + /// + /// Sends asynchronously to every client in + /// the WebSocket services. + /// + /// + /// This method does not wait for the send to be complete. + /// + /// + /// A that represents the text data to send. + /// + /// + /// + /// An delegate or + /// if not needed. + /// + /// + /// The delegate invokes the method called when the send is complete. + /// + /// + /// + /// The current state of the manager is not Start. + /// + /// + /// is . + /// + /// + /// could not be UTF-8-encoded. + /// + [Obsolete ("This method will be removed.")] + public void BroadcastAsync (string data, Action completed) + { + if (_state != ServerState.Start) { + var msg = "The current state of the manager is not Start."; + throw new InvalidOperationException (msg); + } + + if (data == null) + throw new ArgumentNullException ("data"); + + byte[] bytes; + if (!data.TryGetUTF8EncodedBytes (out bytes)) { + var msg = "It could not be UTF-8-encoded."; + throw new ArgumentException (msg, "data"); + } + + if (bytes.LongLength <= WebSocket.FragmentLength) + broadcastAsync (Opcode.Text, bytes, completed); + else + broadcastAsync (Opcode.Text, new MemoryStream (bytes), completed); + } + + /// + /// Sends the data from asynchronously to + /// every client in the WebSocket services. + /// + /// + /// + /// The data is sent as the binary data. + /// + /// + /// This method does not wait for the send to be complete. + /// + /// + /// + /// A instance from which to read the data to send. + /// + /// + /// An that specifies the number of bytes to send. + /// + /// + /// + /// An delegate or + /// if not needed. + /// + /// + /// The delegate invokes the method called when the send is complete. + /// + /// + /// + /// The current state of the manager is not Start. + /// + /// + /// is . + /// + /// + /// + /// cannot be read. + /// + /// + /// -or- + /// + /// + /// is less than 1. + /// + /// + /// -or- + /// + /// + /// No data could be read from . + /// + /// + [Obsolete ("This method will be removed.")] + public void BroadcastAsync (Stream stream, int length, Action completed) + { + if (_state != ServerState.Start) { + var msg = "The current state of the manager is not Start."; + throw new InvalidOperationException (msg); + } + + if (stream == null) + throw new ArgumentNullException ("stream"); + + if (!stream.CanRead) { + var msg = "It cannot be read."; + throw new ArgumentException (msg, "stream"); + } + + if (length < 1) { + var msg = "Less than 1."; + throw new ArgumentException (msg, "length"); + } + + var bytes = stream.ReadBytes (length); + + var len = bytes.Length; + if (len == 0) { + var msg = "No data could be read from it."; + throw new ArgumentException (msg, "stream"); + } + + if (len < length) { + _log.Warn ( + String.Format ( + "Only {0} byte(s) of data could be read from the stream.", + len + ) + ); + } + + if (len <= WebSocket.FragmentLength) + broadcastAsync (Opcode.Binary, bytes, completed); + else + broadcastAsync (Opcode.Binary, new MemoryStream (bytes), completed); + } + + /// + /// Sends a ping to every client in the WebSocket services. + /// + /// + /// + /// A Dictionary<string, Dictionary<string, bool>>. + /// + /// + /// It represents a collection of pairs of a service path and another + /// collection of pairs of a session ID and a value indicating whether + /// a pong has been received from the client within a time. + /// + /// + /// + /// The current state of the manager is not Start. + /// + [Obsolete ("This method will be removed.")] + public Dictionary> Broadping () + { + if (_state != ServerState.Start) { + var msg = "The current state of the manager is not Start."; + throw new InvalidOperationException (msg); + } + + return broadping (WebSocketFrame.EmptyPingBytes, _waitTime); + } + + /// + /// Sends a ping with to every client in + /// the WebSocket services. + /// + /// + /// + /// A Dictionary<string, Dictionary<string, bool>>. + /// + /// + /// It represents a collection of pairs of a service path and another + /// collection of pairs of a session ID and a value indicating whether + /// a pong has been received from the client within a time. + /// + /// + /// + /// + /// A that represents the message to send. + /// + /// + /// The size must be 125 bytes or less in UTF-8. + /// + /// + /// + /// The current state of the manager is not Start. + /// + /// + /// could not be UTF-8-encoded. + /// + /// + /// The size of is greater than 125 bytes. + /// + [Obsolete ("This method will be removed.")] + public Dictionary> Broadping (string message) + { + if (_state != ServerState.Start) { + var msg = "The current state of the manager is not Start."; + throw new InvalidOperationException (msg); + } + + if (message.IsNullOrEmpty ()) + return broadping (WebSocketFrame.EmptyPingBytes, _waitTime); + + byte[] bytes; + if (!message.TryGetUTF8EncodedBytes (out bytes)) { + var msg = "It could not be UTF-8-encoded."; + throw new ArgumentException (msg, "message"); + } + + if (bytes.Length > 125) { + var msg = "Its size is greater than 125 bytes."; + throw new ArgumentOutOfRangeException ("message", msg); + } + + var frame = WebSocketFrame.CreatePingFrame (bytes, false); + return broadping (frame.ToArray (), _waitTime); + } + + /// + /// Removes all WebSocket services managed by the manager. + /// + /// + /// A service is stopped with close status 1001 (going away) + /// if it has already started. + /// + public void Clear () + { + List hosts = null; + + lock (_sync) { + hosts = _hosts.Values.ToList (); + _hosts.Clear (); + } + + foreach (var host in hosts) { + if (host.State == ServerState.Start) + host.Stop (1001, String.Empty); + } + } + + /// + /// Removes a WebSocket service with the specified path. + /// + /// + /// The service is stopped with close status 1001 (going away) + /// if it has already started. + /// + /// + /// true if the service is successfully found and removed; + /// otherwise, false. + /// + /// + /// + /// A that represents an absolute path to + /// the service to remove. + /// + /// + /// / is trimmed from the end of the string if present. + /// + /// + /// + /// is . + /// + /// + /// + /// is empty. + /// + /// + /// -or- + /// + /// + /// is not an absolute path. + /// + /// + /// -or- + /// + /// + /// includes either or both + /// query and fragment components. + /// + /// + public bool RemoveService (string path) + { + if (path == null) + throw new ArgumentNullException ("path"); + + if (path.Length == 0) + throw new ArgumentException ("An empty string.", "path"); + + if (path[0] != '/') + throw new ArgumentException ("Not an absolute path.", "path"); + + if (path.IndexOfAny (new[] { '?', '#' }) > -1) { + var msg = "It includes either or both query and fragment components."; + throw new ArgumentException (msg, "path"); + } + + path = path.TrimSlashFromEnd (); + + WebSocketServiceHost host; + lock (_sync) { + if (!_hosts.TryGetValue (path, out host)) + return false; + + _hosts.Remove (path); + } + + if (host.State == ServerState.Start) + host.Stop (1001, String.Empty); + + return true; + } + + /// + /// Tries to get the host instance for a WebSocket service with + /// the specified path. + /// + /// + /// true if the service is successfully found; otherwise, + /// false. + /// + /// + /// + /// A that represents an absolute path to + /// the service to find. + /// + /// + /// / is trimmed from the end of the string if present. + /// + /// + /// + /// + /// When this method returns, a + /// instance or if not found. + /// + /// + /// The host instance provides the function to access + /// the information in the service. + /// + /// + /// + /// is . + /// + /// + /// + /// is empty. + /// + /// + /// -or- + /// + /// + /// is not an absolute path. + /// + /// + /// -or- + /// + /// + /// includes either or both + /// query and fragment components. + /// + /// + public bool TryGetServiceHost (string path, out WebSocketServiceHost host) + { + if (path == null) + throw new ArgumentNullException ("path"); + + if (path.Length == 0) + throw new ArgumentException ("An empty string.", "path"); + + if (path[0] != '/') + throw new ArgumentException ("Not an absolute path.", "path"); + + if (path.IndexOfAny (new[] { '?', '#' }) > -1) { + var msg = "It includes either or both query and fragment components."; + throw new ArgumentException (msg, "path"); + } + + return InternalTryGetServiceHost (path, out host); + } + + #endregion + } +} diff --git a/websocket-sharp/Server/WebSocketSessionManager.cs b/websocket-sharp/Server/WebSocketSessionManager.cs new file mode 100644 index 0000000..f7144b0 --- /dev/null +++ b/websocket-sharp/Server/WebSocketSessionManager.cs @@ -0,0 +1,1695 @@ +#region License +/* + * WebSocketSessionManager.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; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Timers; + +namespace WebSocketSharp.Server +{ + /// + /// Provides the management function for the sessions in a WebSocket service. + /// + /// + /// This class manages the sessions in a WebSocket service provided by + /// the or . + /// + public class WebSocketSessionManager + { + #region Private Fields + + private volatile bool _clean; + private object _forSweep; + private Logger _log; + private Dictionary _sessions; + private volatile ServerState _state; + private volatile bool _sweeping; + private System.Timers.Timer _sweepTimer; + private object _sync; + private TimeSpan _waitTime; + + #endregion + + #region Internal Constructors + + internal WebSocketSessionManager (Logger log) + { + _log = log; + + _clean = true; + _forSweep = new object (); + _sessions = new Dictionary (); + _state = ServerState.Ready; + _sync = ((ICollection) _sessions).SyncRoot; + _waitTime = TimeSpan.FromSeconds (1); + + setSweepTimer (60000); + } + + #endregion + + #region Internal Properties + + internal ServerState State { + get { + return _state; + } + } + + #endregion + + #region Public Properties + + /// + /// Gets the IDs for the active sessions in the WebSocket service. + /// + /// + /// + /// An IEnumerable<string> instance. + /// + /// + /// It provides an enumerator which supports the iteration over + /// the collection of the IDs for the active sessions. + /// + /// + public IEnumerable ActiveIDs { + get { + foreach (var res in broadping (WebSocketFrame.EmptyPingBytes)) { + if (res.Value) + yield return res.Key; + } + } + } + + /// + /// Gets the number of the sessions in the WebSocket service. + /// + /// + /// An that represents the number of the sessions. + /// + public int Count { + get { + lock (_sync) + return _sessions.Count; + } + } + + /// + /// Gets the IDs for the sessions in the WebSocket service. + /// + /// + /// + /// An IEnumerable<string> instance. + /// + /// + /// It provides an enumerator which supports the iteration over + /// the collection of the IDs for the sessions. + /// + /// + public IEnumerable IDs { + get { + if (_state != ServerState.Start) + return Enumerable.Empty (); + + lock (_sync) { + if (_state != ServerState.Start) + return Enumerable.Empty (); + + return _sessions.Keys.ToList (); + } + } + } + + /// + /// Gets the IDs for the inactive sessions in the WebSocket service. + /// + /// + /// + /// An IEnumerable<string> instance. + /// + /// + /// It provides an enumerator which supports the iteration over + /// the collection of the IDs for the inactive sessions. + /// + /// + public IEnumerable InactiveIDs { + get { + foreach (var res in broadping (WebSocketFrame.EmptyPingBytes)) { + if (!res.Value) + yield return res.Key; + } + } + } + + /// + /// Gets the session instance with . + /// + /// + /// + /// A instance or + /// if not found. + /// + /// + /// The session instance provides the function to access the information + /// in the session. + /// + /// + /// + /// A that represents the ID of the session to find. + /// + /// + /// is . + /// + /// + /// is an empty string. + /// + public IWebSocketSession this[string id] { + get { + if (id == null) + throw new ArgumentNullException ("id"); + + if (id.Length == 0) + throw new ArgumentException ("An empty string.", "id"); + + IWebSocketSession session; + tryGetSession (id, out session); + + return session; + } + } + + /// + /// Gets or sets a value indicating whether the inactive sessions in + /// the WebSocket service are cleaned up periodically. + /// + /// + /// The set operation does nothing if the service has already started or + /// it is shutting down. + /// + /// + /// true if the inactive sessions are cleaned up every 60 seconds; + /// otherwise, false. + /// + public bool KeepClean { + get { + return _clean; + } + + set { + string msg; + if (!canSet (out msg)) { + _log.Warn (msg); + return; + } + + lock (_sync) { + if (!canSet (out msg)) { + _log.Warn (msg); + return; + } + + _clean = value; + } + } + } + + /// + /// Gets the session instances in the WebSocket service. + /// + /// + /// + /// An IEnumerable<IWebSocketSession> instance. + /// + /// + /// It provides an enumerator which supports the iteration over + /// the collection of the session instances. + /// + /// + public IEnumerable Sessions { + get { + if (_state != ServerState.Start) + return Enumerable.Empty (); + + lock (_sync) { + if (_state != ServerState.Start) + return Enumerable.Empty (); + + return _sessions.Values.ToList (); + } + } + } + + /// + /// Gets or sets the time to wait for the response to the WebSocket Ping or + /// Close. + /// + /// + /// The set operation does nothing if the service has already started or + /// it is shutting down. + /// + /// + /// A to wait for the response. + /// + /// + /// The value specified for a set operation is zero or less. + /// + public TimeSpan WaitTime { + get { + return _waitTime; + } + + set { + if (value <= TimeSpan.Zero) + throw new ArgumentOutOfRangeException ("value", "Zero or less."); + + string msg; + if (!canSet (out msg)) { + _log.Warn (msg); + return; + } + + lock (_sync) { + if (!canSet (out msg)) { + _log.Warn (msg); + return; + } + + _waitTime = value; + } + } + } + + #endregion + + #region Private Methods + + private void broadcast (Opcode opcode, byte[] data, Action completed) + { + var cache = new Dictionary (); + + try { + foreach (var session in Sessions) { + if (_state != ServerState.Start) { + _log.Error ("The service is shutting down."); + break; + } + + session.Context.WebSocket.Send (opcode, data, cache); + } + + if (completed != null) + completed (); + } + catch (Exception ex) { + _log.Error (ex.Message); + _log.Debug (ex.ToString ()); + } + finally { + cache.Clear (); + } + } + + private void broadcast (Opcode opcode, Stream stream, Action completed) + { + var cache = new Dictionary (); + + try { + foreach (var session in Sessions) { + if (_state != ServerState.Start) { + _log.Error ("The service is shutting down."); + break; + } + + session.Context.WebSocket.Send (opcode, stream, cache); + } + + if (completed != null) + completed (); + } + catch (Exception ex) { + _log.Error (ex.Message); + _log.Debug (ex.ToString ()); + } + finally { + foreach (var cached in cache.Values) + cached.Dispose (); + + cache.Clear (); + } + } + + private void broadcastAsync (Opcode opcode, byte[] data, Action completed) + { + ThreadPool.QueueUserWorkItem ( + state => broadcast (opcode, data, completed) + ); + } + + private void broadcastAsync (Opcode opcode, Stream stream, Action completed) + { + ThreadPool.QueueUserWorkItem ( + state => broadcast (opcode, stream, completed) + ); + } + + private Dictionary broadping (byte[] frameAsBytes) + { + var ret = new Dictionary (); + + foreach (var session in Sessions) { + if (_state != ServerState.Start) { + _log.Error ("The service is shutting down."); + break; + } + + var res = session.Context.WebSocket.Ping (frameAsBytes, _waitTime); + ret.Add (session.ID, res); + } + + return ret; + } + + private bool canSet (out string message) + { + message = null; + + if (_state == ServerState.Start) { + message = "The service has already started."; + return false; + } + + if (_state == ServerState.ShuttingDown) { + message = "The service is shutting down."; + return false; + } + + return true; + } + + private static string createID () + { + return Guid.NewGuid ().ToString ("N"); + } + + private void setSweepTimer (double interval) + { + _sweepTimer = new System.Timers.Timer (interval); + _sweepTimer.Elapsed += (sender, e) => Sweep (); + } + + private void stop (PayloadData payloadData, bool send) + { + var bytes = send + ? WebSocketFrame.CreateCloseFrame (payloadData, false).ToArray () + : null; + + lock (_sync) { + _state = ServerState.ShuttingDown; + + _sweepTimer.Enabled = false; + foreach (var session in _sessions.Values.ToList ()) + session.Context.WebSocket.Close (payloadData, bytes); + + _state = ServerState.Stop; + } + } + + private bool tryGetSession (string id, out IWebSocketSession session) + { + session = null; + + if (_state != ServerState.Start) + return false; + + lock (_sync) { + if (_state != ServerState.Start) + return false; + + return _sessions.TryGetValue (id, out session); + } + } + + #endregion + + #region Internal Methods + + internal string Add (IWebSocketSession session) + { + lock (_sync) { + if (_state != ServerState.Start) + return null; + + var id = createID (); + _sessions.Add (id, session); + + return id; + } + } + + internal void Broadcast ( + Opcode opcode, byte[] data, Dictionary cache + ) + { + foreach (var session in Sessions) { + if (_state != ServerState.Start) { + _log.Error ("The service is shutting down."); + break; + } + + session.Context.WebSocket.Send (opcode, data, cache); + } + } + + internal void Broadcast ( + Opcode opcode, Stream stream, Dictionary cache + ) + { + foreach (var session in Sessions) { + if (_state != ServerState.Start) { + _log.Error ("The service is shutting down."); + break; + } + + session.Context.WebSocket.Send (opcode, stream, cache); + } + } + + internal Dictionary Broadping ( + byte[] frameAsBytes, TimeSpan timeout + ) + { + var ret = new Dictionary (); + + foreach (var session in Sessions) { + if (_state != ServerState.Start) { + _log.Error ("The service is shutting down."); + break; + } + + var res = session.Context.WebSocket.Ping (frameAsBytes, timeout); + ret.Add (session.ID, res); + } + + return ret; + } + + internal bool Remove (string id) + { + lock (_sync) + return _sessions.Remove (id); + } + + internal void Start () + { + lock (_sync) { + _sweepTimer.Enabled = _clean; + _state = ServerState.Start; + } + } + + internal void Stop (ushort code, string reason) + { + if (code == 1005) { // == no status + stop (PayloadData.Empty, true); + return; + } + + stop (new PayloadData (code, reason), !code.IsReserved ()); + } + + #endregion + + #region Public Methods + + /// + /// Sends to every client in the WebSocket service. + /// + /// + /// An array of that represents the binary data to send. + /// + /// + /// The current state of the manager is not Start. + /// + /// + /// is . + /// + public void Broadcast (byte[] data) + { + if (_state != ServerState.Start) { + var msg = "The current state of the manager is not Start."; + throw new InvalidOperationException (msg); + } + + if (data == null) + throw new ArgumentNullException ("data"); + + if (data.LongLength <= WebSocket.FragmentLength) + broadcast (Opcode.Binary, data, null); + else + broadcast (Opcode.Binary, new MemoryStream (data), null); + } + + /// + /// Sends to every client in the WebSocket service. + /// + /// + /// A that represents the text data to send. + /// + /// + /// The current state of the manager is not Start. + /// + /// + /// is . + /// + /// + /// could not be UTF-8-encoded. + /// + public void Broadcast (string data) + { + if (_state != ServerState.Start) { + var msg = "The current state of the manager is not Start."; + throw new InvalidOperationException (msg); + } + + if (data == null) + throw new ArgumentNullException ("data"); + + byte[] bytes; + if (!data.TryGetUTF8EncodedBytes (out bytes)) { + var msg = "It could not be UTF-8-encoded."; + throw new ArgumentException (msg, "data"); + } + + if (bytes.LongLength <= WebSocket.FragmentLength) + broadcast (Opcode.Text, bytes, null); + else + broadcast (Opcode.Text, new MemoryStream (bytes), null); + } + + /// + /// Sends the data from to every client in + /// the WebSocket service. + /// + /// + /// The data is sent as the binary data. + /// + /// + /// A instance from which to read the data to send. + /// + /// + /// An that specifies the number of bytes to send. + /// + /// + /// The current state of the manager is not Start. + /// + /// + /// is . + /// + /// + /// + /// cannot be read. + /// + /// + /// -or- + /// + /// + /// is less than 1. + /// + /// + /// -or- + /// + /// + /// No data could be read from . + /// + /// + public void Broadcast (Stream stream, int length) + { + if (_state != ServerState.Start) { + var msg = "The current state of the manager is not Start."; + throw new InvalidOperationException (msg); + } + + if (stream == null) + throw new ArgumentNullException ("stream"); + + if (!stream.CanRead) { + var msg = "It cannot be read."; + throw new ArgumentException (msg, "stream"); + } + + if (length < 1) { + var msg = "Less than 1."; + throw new ArgumentException (msg, "length"); + } + + var bytes = stream.ReadBytes (length); + + var len = bytes.Length; + if (len == 0) { + var msg = "No data could be read from it."; + throw new ArgumentException (msg, "stream"); + } + + if (len < length) { + _log.Warn ( + String.Format ( + "Only {0} byte(s) of data could be read from the stream.", + len + ) + ); + } + + if (len <= WebSocket.FragmentLength) + broadcast (Opcode.Binary, bytes, null); + else + broadcast (Opcode.Binary, new MemoryStream (bytes), null); + } + + /// + /// Sends asynchronously to every client in + /// the WebSocket service. + /// + /// + /// This method does not wait for the send to be complete. + /// + /// + /// An array of that represents the binary data to send. + /// + /// + /// + /// An delegate or + /// if not needed. + /// + /// + /// The delegate invokes the method called when the send is complete. + /// + /// + /// + /// The current state of the manager is not Start. + /// + /// + /// is . + /// + public void BroadcastAsync (byte[] data, Action completed) + { + if (_state != ServerState.Start) { + var msg = "The current state of the manager is not Start."; + throw new InvalidOperationException (msg); + } + + if (data == null) + throw new ArgumentNullException ("data"); + + if (data.LongLength <= WebSocket.FragmentLength) + broadcastAsync (Opcode.Binary, data, completed); + else + broadcastAsync (Opcode.Binary, new MemoryStream (data), completed); + } + + /// + /// Sends asynchronously to every client in + /// the WebSocket service. + /// + /// + /// This method does not wait for the send to be complete. + /// + /// + /// A that represents the text data to send. + /// + /// + /// + /// An delegate or + /// if not needed. + /// + /// + /// The delegate invokes the method called when the send is complete. + /// + /// + /// + /// The current state of the manager is not Start. + /// + /// + /// is . + /// + /// + /// could not be UTF-8-encoded. + /// + public void BroadcastAsync (string data, Action completed) + { + if (_state != ServerState.Start) { + var msg = "The current state of the manager is not Start."; + throw new InvalidOperationException (msg); + } + + if (data == null) + throw new ArgumentNullException ("data"); + + byte[] bytes; + if (!data.TryGetUTF8EncodedBytes (out bytes)) { + var msg = "It could not be UTF-8-encoded."; + throw new ArgumentException (msg, "data"); + } + + if (bytes.LongLength <= WebSocket.FragmentLength) + broadcastAsync (Opcode.Text, bytes, completed); + else + broadcastAsync (Opcode.Text, new MemoryStream (bytes), completed); + } + + /// + /// Sends the data from asynchronously to + /// every client in the WebSocket service. + /// + /// + /// + /// The data is sent as the binary data. + /// + /// + /// This method does not wait for the send to be complete. + /// + /// + /// + /// A instance from which to read the data to send. + /// + /// + /// An that specifies the number of bytes to send. + /// + /// + /// + /// An delegate or + /// if not needed. + /// + /// + /// The delegate invokes the method called when the send is complete. + /// + /// + /// + /// The current state of the manager is not Start. + /// + /// + /// is . + /// + /// + /// + /// cannot be read. + /// + /// + /// -or- + /// + /// + /// is less than 1. + /// + /// + /// -or- + /// + /// + /// No data could be read from . + /// + /// + public void BroadcastAsync (Stream stream, int length, Action completed) + { + if (_state != ServerState.Start) { + var msg = "The current state of the manager is not Start."; + throw new InvalidOperationException (msg); + } + + if (stream == null) + throw new ArgumentNullException ("stream"); + + if (!stream.CanRead) { + var msg = "It cannot be read."; + throw new ArgumentException (msg, "stream"); + } + + if (length < 1) { + var msg = "Less than 1."; + throw new ArgumentException (msg, "length"); + } + + var bytes = stream.ReadBytes (length); + + var len = bytes.Length; + if (len == 0) { + var msg = "No data could be read from it."; + throw new ArgumentException (msg, "stream"); + } + + if (len < length) { + _log.Warn ( + String.Format ( + "Only {0} byte(s) of data could be read from the stream.", + len + ) + ); + } + + if (len <= WebSocket.FragmentLength) + broadcastAsync (Opcode.Binary, bytes, completed); + else + broadcastAsync (Opcode.Binary, new MemoryStream (bytes), completed); + } + + /// + /// Sends a ping to every client in the WebSocket service. + /// + /// + /// + /// A Dictionary<string, bool>. + /// + /// + /// It represents a collection of pairs of a session ID and + /// a value indicating whether a pong has been received from + /// the client within a time. + /// + /// + /// + /// The current state of the manager is not Start. + /// + [Obsolete ("This method will be removed.")] + public Dictionary Broadping () + { + if (_state != ServerState.Start) { + var msg = "The current state of the manager is not Start."; + throw new InvalidOperationException (msg); + } + + return Broadping (WebSocketFrame.EmptyPingBytes, _waitTime); + } + + /// + /// Sends a ping with to every client in + /// the WebSocket service. + /// + /// + /// + /// A Dictionary<string, bool>. + /// + /// + /// It represents a collection of pairs of a session ID and + /// a value indicating whether a pong has been received from + /// the client within a time. + /// + /// + /// + /// + /// A that represents the message to send. + /// + /// + /// The size must be 125 bytes or less in UTF-8. + /// + /// + /// + /// The current state of the manager is not Start. + /// + /// + /// could not be UTF-8-encoded. + /// + /// + /// The size of is greater than 125 bytes. + /// + [Obsolete ("This method will be removed.")] + public Dictionary Broadping (string message) + { + if (_state != ServerState.Start) { + var msg = "The current state of the manager is not Start."; + throw new InvalidOperationException (msg); + } + + if (message.IsNullOrEmpty ()) + return Broadping (WebSocketFrame.EmptyPingBytes, _waitTime); + + byte[] bytes; + if (!message.TryGetUTF8EncodedBytes (out bytes)) { + var msg = "It could not be UTF-8-encoded."; + throw new ArgumentException (msg, "message"); + } + + if (bytes.Length > 125) { + var msg = "Its size is greater than 125 bytes."; + throw new ArgumentOutOfRangeException ("message", msg); + } + + var frame = WebSocketFrame.CreatePingFrame (bytes, false); + return Broadping (frame.ToArray (), _waitTime); + } + + /// + /// Closes the specified session. + /// + /// + /// A that represents the ID of the session to close. + /// + /// + /// is . + /// + /// + /// is an empty string. + /// + /// + /// The session could not be found. + /// + public void CloseSession (string id) + { + IWebSocketSession session; + if (!TryGetSession (id, out session)) { + var msg = "The session could not be found."; + throw new InvalidOperationException (msg); + } + + session.Context.WebSocket.Close (); + } + + /// + /// Closes the specified session with and + /// . + /// + /// + /// A that represents the ID of the session to close. + /// + /// + /// + /// A that represents the status code indicating + /// the reason for the close. + /// + /// + /// The status codes are defined in + /// + /// Section 7.4 of RFC 6455. + /// + /// + /// + /// + /// A that represents the reason for the close. + /// + /// + /// The size must be 123 bytes or less in UTF-8. + /// + /// + /// + /// is . + /// + /// + /// + /// is an empty string. + /// + /// + /// -or- + /// + /// + /// is 1010 (mandatory extension). + /// + /// + /// -or- + /// + /// + /// is 1005 (no status) and there is + /// . + /// + /// + /// -or- + /// + /// + /// could not be UTF-8-encoded. + /// + /// + /// + /// The session could not be found. + /// + /// + /// + /// is less than 1000 or greater than 4999. + /// + /// + /// -or- + /// + /// + /// The size of is greater than 123 bytes. + /// + /// + public void CloseSession (string id, ushort code, string reason) + { + IWebSocketSession session; + if (!TryGetSession (id, out session)) { + var msg = "The session could not be found."; + throw new InvalidOperationException (msg); + } + + session.Context.WebSocket.Close (code, reason); + } + + /// + /// Closes the specified session with and + /// . + /// + /// + /// A that represents the ID of the session to close. + /// + /// + /// + /// One of the enum values. + /// + /// + /// It represents the status code indicating the reason for the close. + /// + /// + /// + /// + /// A that represents the reason for the close. + /// + /// + /// The size must be 123 bytes or less in UTF-8. + /// + /// + /// + /// is . + /// + /// + /// + /// is an empty string. + /// + /// + /// -or- + /// + /// + /// is + /// . + /// + /// + /// -or- + /// + /// + /// is + /// and there is + /// . + /// + /// + /// -or- + /// + /// + /// could not be UTF-8-encoded. + /// + /// + /// + /// The session could not be found. + /// + /// + /// The size of is greater than 123 bytes. + /// + public void CloseSession (string id, CloseStatusCode code, string reason) + { + IWebSocketSession session; + if (!TryGetSession (id, out session)) { + var msg = "The session could not be found."; + throw new InvalidOperationException (msg); + } + + session.Context.WebSocket.Close (code, reason); + } + + /// + /// Sends a ping to the client using the specified session. + /// + /// + /// true if the send has done with no error and a pong has been + /// received from the client within a time; otherwise, false. + /// + /// + /// A that represents the ID of the session. + /// + /// + /// is . + /// + /// + /// is an empty string. + /// + /// + /// The session could not be found. + /// + public bool PingTo (string id) + { + IWebSocketSession session; + if (!TryGetSession (id, out session)) { + var msg = "The session could not be found."; + throw new InvalidOperationException (msg); + } + + return session.Context.WebSocket.Ping (); + } + + /// + /// Sends a ping with to the client using + /// the specified session. + /// + /// + /// true if the send has done with no error and a pong has been + /// received from the client within a time; otherwise, false. + /// + /// + /// + /// A that represents the message to send. + /// + /// + /// The size must be 125 bytes or less in UTF-8. + /// + /// + /// + /// A that represents the ID of the session. + /// + /// + /// is . + /// + /// + /// + /// is an empty string. + /// + /// + /// -or- + /// + /// + /// could not be UTF-8-encoded. + /// + /// + /// + /// The session could not be found. + /// + /// + /// The size of is greater than 125 bytes. + /// + public bool PingTo (string message, string id) + { + IWebSocketSession session; + if (!TryGetSession (id, out session)) { + var msg = "The session could not be found."; + throw new InvalidOperationException (msg); + } + + return session.Context.WebSocket.Ping (message); + } + + /// + /// Sends to the client using the specified session. + /// + /// + /// An array of that represents the binary data to send. + /// + /// + /// A that represents the ID of the session. + /// + /// + /// + /// is . + /// + /// + /// -or- + /// + /// + /// is . + /// + /// + /// + /// is an empty string. + /// + /// + /// + /// The session could not be found. + /// + /// + /// -or- + /// + /// + /// The current state of the WebSocket connection is not Open. + /// + /// + public void SendTo (byte[] data, string id) + { + IWebSocketSession session; + if (!TryGetSession (id, out session)) { + var msg = "The session could not be found."; + throw new InvalidOperationException (msg); + } + + session.Context.WebSocket.Send (data); + } + + /// + /// Sends to the client using the specified session. + /// + /// + /// A that represents the text data to send. + /// + /// + /// A that represents the ID of the session. + /// + /// + /// + /// is . + /// + /// + /// -or- + /// + /// + /// is . + /// + /// + /// + /// + /// is an empty string. + /// + /// + /// -or- + /// + /// + /// could not be UTF-8-encoded. + /// + /// + /// + /// + /// The session could not be found. + /// + /// + /// -or- + /// + /// + /// The current state of the WebSocket connection is not Open. + /// + /// + public void SendTo (string data, string id) + { + IWebSocketSession session; + if (!TryGetSession (id, out session)) { + var msg = "The session could not be found."; + throw new InvalidOperationException (msg); + } + + session.Context.WebSocket.Send (data); + } + + /// + /// Sends the data from to the client using + /// the specified session. + /// + /// + /// The data is sent as the binary data. + /// + /// + /// A instance from which to read the data to send. + /// + /// + /// An that specifies the number of bytes to send. + /// + /// + /// A that represents the ID of the session. + /// + /// + /// + /// is . + /// + /// + /// -or- + /// + /// + /// is . + /// + /// + /// + /// + /// is an empty string. + /// + /// + /// -or- + /// + /// + /// cannot be read. + /// + /// + /// -or- + /// + /// + /// is less than 1. + /// + /// + /// -or- + /// + /// + /// No data could be read from . + /// + /// + /// + /// + /// The session could not be found. + /// + /// + /// -or- + /// + /// + /// The current state of the WebSocket connection is not Open. + /// + /// + public void SendTo (Stream stream, int length, string id) + { + IWebSocketSession session; + if (!TryGetSession (id, out session)) { + var msg = "The session could not be found."; + throw new InvalidOperationException (msg); + } + + session.Context.WebSocket.Send (stream, length); + } + + /// + /// Sends asynchronously to the client using + /// the specified session. + /// + /// + /// This method does not wait for the send to be complete. + /// + /// + /// An array of that represents the binary data to send. + /// + /// + /// A that represents the ID of the session. + /// + /// + /// + /// An Action<bool> delegate or + /// if not needed. + /// + /// + /// The delegate invokes the method called when the send is complete. + /// + /// + /// true is passed to the method if the send has done with + /// no error; otherwise, false. + /// + /// + /// + /// + /// is . + /// + /// + /// -or- + /// + /// + /// is . + /// + /// + /// + /// is an empty string. + /// + /// + /// + /// The session could not be found. + /// + /// + /// -or- + /// + /// + /// The current state of the WebSocket connection is not Open. + /// + /// + public void SendToAsync (byte[] data, string id, Action completed) + { + IWebSocketSession session; + if (!TryGetSession (id, out session)) { + var msg = "The session could not be found."; + throw new InvalidOperationException (msg); + } + + session.Context.WebSocket.SendAsync (data, completed); + } + + /// + /// Sends asynchronously to the client using + /// the specified session. + /// + /// + /// This method does not wait for the send to be complete. + /// + /// + /// A that represents the text data to send. + /// + /// + /// A that represents the ID of the session. + /// + /// + /// + /// An Action<bool> delegate or + /// if not needed. + /// + /// + /// The delegate invokes the method called when the send is complete. + /// + /// + /// true is passed to the method if the send has done with + /// no error; otherwise, false. + /// + /// + /// + /// + /// is . + /// + /// + /// -or- + /// + /// + /// is . + /// + /// + /// + /// + /// is an empty string. + /// + /// + /// -or- + /// + /// + /// could not be UTF-8-encoded. + /// + /// + /// + /// + /// The session could not be found. + /// + /// + /// -or- + /// + /// + /// The current state of the WebSocket connection is not Open. + /// + /// + public void SendToAsync (string data, string id, Action completed) + { + IWebSocketSession session; + if (!TryGetSession (id, out session)) { + var msg = "The session could not be found."; + throw new InvalidOperationException (msg); + } + + session.Context.WebSocket.SendAsync (data, completed); + } + + /// + /// Sends the data from asynchronously to + /// the client using the specified session. + /// + /// + /// + /// The data is sent as the binary data. + /// + /// + /// This method does not wait for the send to be complete. + /// + /// + /// + /// A instance from which to read the data to send. + /// + /// + /// An that specifies the number of bytes to send. + /// + /// + /// A that represents the ID of the session. + /// + /// + /// + /// An Action<bool> delegate or + /// if not needed. + /// + /// + /// The delegate invokes the method called when the send is complete. + /// + /// + /// true is passed to the method if the send has done with + /// no error; otherwise, false. + /// + /// + /// + /// + /// is . + /// + /// + /// -or- + /// + /// + /// is . + /// + /// + /// + /// + /// is an empty string. + /// + /// + /// -or- + /// + /// + /// cannot be read. + /// + /// + /// -or- + /// + /// + /// is less than 1. + /// + /// + /// -or- + /// + /// + /// No data could be read from . + /// + /// + /// + /// + /// The session could not be found. + /// + /// + /// -or- + /// + /// + /// The current state of the WebSocket connection is not Open. + /// + /// + public void SendToAsync ( + Stream stream, int length, string id, Action completed + ) + { + IWebSocketSession session; + if (!TryGetSession (id, out session)) { + var msg = "The session could not be found."; + throw new InvalidOperationException (msg); + } + + session.Context.WebSocket.SendAsync (stream, length, completed); + } + + /// + /// Cleans up the inactive sessions in the WebSocket service. + /// + public void Sweep () + { + if (_sweeping) { + _log.Info ("The sweeping is already in progress."); + return; + } + + lock (_forSweep) { + if (_sweeping) { + _log.Info ("The sweeping is already in progress."); + return; + } + + _sweeping = true; + } + + foreach (var id in InactiveIDs) { + if (_state != ServerState.Start) + break; + + lock (_sync) { + if (_state != ServerState.Start) + break; + + IWebSocketSession session; + if (_sessions.TryGetValue (id, out session)) { + var state = session.ConnectionState; + if (state == WebSocketState.Open) + session.Context.WebSocket.Close (CloseStatusCode.Abnormal); + else if (state == WebSocketState.Closing) + continue; + else + _sessions.Remove (id); + } + } + } + + _sweeping = false; + } + + /// + /// Tries to get the session instance with . + /// + /// + /// true if the session is successfully found; otherwise, + /// false. + /// + /// + /// A that represents the ID of the session to find. + /// + /// + /// + /// When this method returns, a + /// instance or if not found. + /// + /// + /// The session instance provides the function to access + /// the information in the session. + /// + /// + /// + /// is . + /// + /// + /// is an empty string. + /// + public bool TryGetSession (string id, out IWebSocketSession session) + { + if (id == null) + throw new ArgumentNullException ("id"); + + if (id.Length == 0) + throw new ArgumentException ("An empty string.", "id"); + + return tryGetSession (id, out session); + } + + #endregion + } +} diff --git a/websocket-sharp/WebSocket.cs b/websocket-sharp/WebSocket.cs new file mode 100644 index 0000000..93ed5bf --- /dev/null +++ b/websocket-sharp/WebSocket.cs @@ -0,0 +1,4094 @@ +#region License +/* + * WebSocket.cs + * + * This code is derived from WebSocket.java + * (http://github.com/adamac/Java-WebSocket-client). + * + * The MIT License + * + * Copyright (c) 2009 Adam MacBeth + * Copyright (c) 2010-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 + * - David Wood + * - Liryna + */ +#endregion + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Diagnostics; +using System.IO; +using System.Net.Security; +using System.Net.Sockets; +using System.Security.Cryptography; +using System.Text; +using System.Threading; +using WebSocketSharp.Net; +using WebSocketSharp.Net.WebSockets; + +namespace WebSocketSharp +{ + /// + /// Implements the WebSocket interface. + /// + /// + /// + /// This class provides a set of methods and properties for two-way + /// communication using the WebSocket protocol. + /// + /// + /// The WebSocket protocol is defined in + /// RFC 6455. + /// + /// + public class WebSocket : IDisposable + { + #region Private Fields + + private AuthenticationChallenge _authChallenge; + private string _base64Key; + private bool _client; + private Action _closeContext; + private CompressionMethod _compression; + private WebSocketContext _context; + private CookieCollection _cookies; + private NetworkCredential _credentials; + private bool _emitOnPing; + private bool _enableRedirection; + private string _extensions; + private bool _extensionsRequested; + private object _forMessageEventQueue; + private object _forPing; + private object _forSend; + private object _forState; + private MemoryStream _fragmentsBuffer; + private bool _fragmentsCompressed; + private Opcode _fragmentsOpcode; + private const string _guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + private Func _handshakeRequestChecker; + private bool _ignoreExtensions; + private bool _inContinuation; + private volatile bool _inMessage; + private volatile Logger _logger; + private static readonly int _maxRetryCountForConnect; + private Action _message; + private Queue _messageEventQueue; + private uint _nonceCount; + private string _origin; + private ManualResetEvent _pongReceived; + private bool _preAuth; + private string _protocol; + private string[] _protocols; + private bool _protocolsRequested; + private NetworkCredential _proxyCredentials; + private Uri _proxyUri; + private volatile WebSocketState _readyState; + private ManualResetEvent _receivingExited; + private int _retryCountForConnect; + private bool _secure; + private ClientSslConfiguration _sslConfig; + private Stream _stream; + private TcpClient _tcpClient; + private Uri _uri; + private const string _version = "13"; + private TimeSpan _waitTime; + + #endregion + + #region Internal Fields + + /// + /// Represents the empty array of used internally. + /// + internal static readonly byte[] EmptyBytes; + + /// + /// Represents the length used to determine whether the data should be fragmented in sending. + /// + /// + /// + /// The data will be fragmented if that length is greater than the value of this field. + /// + /// + /// If you would like to change the value, you must set it to a value between 125 and + /// Int32.MaxValue - 14 inclusive. + /// + /// + internal static readonly int FragmentLength; + + /// + /// Represents the random number generator used internally. + /// + internal static readonly RandomNumberGenerator RandomNumber; + + #endregion + + #region Static Constructor + + static WebSocket () + { + _maxRetryCountForConnect = 10; + EmptyBytes = new byte[0]; + FragmentLength = 1016; + RandomNumber = new RNGCryptoServiceProvider (); + } + + #endregion + + #region Internal Constructors + + // As server + internal WebSocket (HttpListenerWebSocketContext context, string protocol) + { + _context = context; + _protocol = protocol; + + _closeContext = context.Close; + _logger = context.Log; + _message = messages; + _secure = context.IsSecureConnection; + _stream = context.Stream; + _waitTime = TimeSpan.FromSeconds (1); + + init (); + } + + // As server + internal WebSocket (TcpListenerWebSocketContext context, string protocol) + { + _context = context; + _protocol = protocol; + + _closeContext = context.Close; + _logger = context.Log; + _message = messages; + _secure = context.IsSecureConnection; + _stream = context.Stream; + _waitTime = TimeSpan.FromSeconds (1); + + init (); + } + + #endregion + + #region Public Constructors + + /// + /// Initializes a new instance of the class with + /// and optionally . + /// + /// + /// + /// A that specifies the URL to which to connect. + /// + /// + /// The scheme of the URL must be ws or wss. + /// + /// + /// The new instance uses a secure connection if the scheme is wss. + /// + /// + /// + /// + /// An array of that specifies the names of + /// the subprotocols if necessary. + /// + /// + /// Each value of the array must be a token defined in + /// + /// RFC 2616. + /// + /// + /// + /// is . + /// + /// + /// + /// is an empty string. + /// + /// + /// -or- + /// + /// + /// is an invalid WebSocket URL string. + /// + /// + /// -or- + /// + /// + /// contains a value that is not a token. + /// + /// + /// -or- + /// + /// + /// contains a value twice. + /// + /// + public WebSocket (string url, params string[] protocols) + { + if (url == null) + throw new ArgumentNullException ("url"); + + if (url.Length == 0) + throw new ArgumentException ("An empty string.", "url"); + + string msg; + if (!url.TryCreateWebSocketUri (out _uri, out msg)) + throw new ArgumentException (msg, "url"); + + if (protocols != null && protocols.Length > 0) { + if (!checkProtocols (protocols, out msg)) + throw new ArgumentException (msg, "protocols"); + + _protocols = protocols; + } + + _base64Key = CreateBase64Key (); + _client = true; + _logger = new Logger (); + _message = messagec; + _secure = _uri.Scheme == "wss"; + _waitTime = TimeSpan.FromSeconds (5); + + init (); + } + + #endregion + + #region Internal Properties + + internal CookieCollection CookieCollection { + get { + return _cookies; + } + } + + // As server + internal Func CustomHandshakeRequestChecker { + get { + return _handshakeRequestChecker; + } + + set { + _handshakeRequestChecker = value; + } + } + + internal bool HasMessage { + get { + lock (_forMessageEventQueue) + return _messageEventQueue.Count > 0; + } + } + + // As server + internal bool IgnoreExtensions { + get { + return _ignoreExtensions; + } + + set { + _ignoreExtensions = value; + } + } + + internal bool IsConnected { + get { + return _readyState == WebSocketState.Open || _readyState == WebSocketState.Closing; + } + } + + #endregion + + #region Public Properties + + /// + /// Gets or sets the compression method used to compress a message. + /// + /// + /// The set operation does nothing if the connection has already been + /// established or it is closing. + /// + /// + /// + /// One of the enum values. + /// + /// + /// It specifies the compression method used to compress a message. + /// + /// + /// The default value is . + /// + /// + /// + /// The set operation is not available if this instance is not a client. + /// + public CompressionMethod Compression { + get { + return _compression; + } + + set { + string msg = null; + + if (!_client) { + msg = "This instance is not a client."; + throw new InvalidOperationException (msg); + } + + if (!canSet (out msg)) { + _logger.Warn (msg); + return; + } + + lock (_forState) { + if (!canSet (out msg)) { + _logger.Warn (msg); + return; + } + + _compression = value; + } + } + } + + /// + /// Gets the HTTP cookies included in the handshake request/response. + /// + /// + /// + /// An + /// instance. + /// + /// + /// It provides an enumerator which supports the iteration over + /// the collection of the cookies. + /// + /// + public IEnumerable Cookies { + get { + lock (_cookies.SyncRoot) { + foreach (Cookie cookie in _cookies) + yield return cookie; + } + } + } + + /// + /// Gets the credentials for the HTTP authentication (Basic/Digest). + /// + /// + /// + /// A that represents the credentials + /// used to authenticate the client. + /// + /// + /// The default value is . + /// + /// + public NetworkCredential Credentials { + get { + return _credentials; + } + } + + /// + /// Gets or sets a value indicating whether a event + /// is emitted when a ping is received. + /// + /// + /// + /// true if this instance emits a event + /// when receives a ping; otherwise, false. + /// + /// + /// The default value is false. + /// + /// + public bool EmitOnPing { + get { + return _emitOnPing; + } + + set { + _emitOnPing = value; + } + } + + /// + /// Gets or sets a value indicating whether the URL redirection for + /// the handshake request is allowed. + /// + /// + /// The set operation does nothing if the connection has already been + /// established or it is closing. + /// + /// + /// + /// true if this instance allows the URL redirection for + /// the handshake request; otherwise, false. + /// + /// + /// The default value is false. + /// + /// + /// + /// The set operation is not available if this instance is not a client. + /// + public bool EnableRedirection { + get { + return _enableRedirection; + } + + set { + string msg = null; + + if (!_client) { + msg = "This instance is not a client."; + throw new InvalidOperationException (msg); + } + + if (!canSet (out msg)) { + _logger.Warn (msg); + return; + } + + lock (_forState) { + if (!canSet (out msg)) { + _logger.Warn (msg); + return; + } + + _enableRedirection = value; + } + } + } + + /// + /// Gets the extensions selected by server. + /// + /// + /// A that will be a list of the extensions + /// negotiated between client and server, or an empty string if + /// not specified or selected. + /// + public string Extensions { + get { + return _extensions ?? String.Empty; + } + } + + /// + /// Gets a value indicating whether the connection is alive. + /// + /// + /// The get operation returns the value by using a ping/pong + /// if the current state of the connection is Open. + /// + /// + /// true if the connection is alive; otherwise, false. + /// + public bool IsAlive { + get { + return ping (EmptyBytes); + } + } + + /// + /// Gets a value indicating whether a secure connection is used. + /// + /// + /// true if this instance uses a secure connection; otherwise, + /// false. + /// + public bool IsSecure { + get { + return _secure; + } + } + + /// + /// Gets the logging function. + /// + /// + /// The default logging level is . + /// + /// + /// A that provides the logging function. + /// + public Logger Log { + get { + return _logger; + } + + internal set { + _logger = value; + } + } + + /// + /// Gets or sets the value of the HTTP Origin header to send with + /// the handshake request. + /// + /// + /// + /// The HTTP Origin header is defined in + /// + /// Section 7 of RFC 6454. + /// + /// + /// This instance sends the Origin header if this property has any. + /// + /// + /// The set operation does nothing if the connection has already been + /// established or it is closing. + /// + /// + /// + /// + /// A that represents the value of the Origin + /// header to send. + /// + /// + /// The syntax is <scheme>://<host>[:<port>]. + /// + /// + /// The default value is . + /// + /// + /// + /// The set operation is not available if this instance is not a client. + /// + /// + /// + /// The value specified for a set operation is not an absolute URI string. + /// + /// + /// -or- + /// + /// + /// The value specified for a set operation includes the path segments. + /// + /// + public string Origin { + get { + return _origin; + } + + set { + string msg = null; + + if (!_client) { + msg = "This instance is not a client."; + throw new InvalidOperationException (msg); + } + + if (!value.IsNullOrEmpty ()) { + Uri uri; + if (!Uri.TryCreate (value, UriKind.Absolute, out uri)) { + msg = "Not an absolute URI string."; + throw new ArgumentException (msg, "value"); + } + + if (uri.Segments.Length > 1) { + msg = "It includes the path segments."; + throw new ArgumentException (msg, "value"); + } + } + + if (!canSet (out msg)) { + _logger.Warn (msg); + return; + } + + lock (_forState) { + if (!canSet (out msg)) { + _logger.Warn (msg); + return; + } + + _origin = !value.IsNullOrEmpty () ? value.TrimEnd ('/') : value; + } + } + } + + /// + /// Gets the name of subprotocol selected by the server. + /// + /// + /// + /// A that will be one of the names of + /// subprotocols specified by client. + /// + /// + /// An empty string if not specified or selected. + /// + /// + public string Protocol { + get { + return _protocol ?? String.Empty; + } + + internal set { + _protocol = value; + } + } + + /// + /// Gets the current state of the connection. + /// + /// + /// + /// One of the enum values. + /// + /// + /// It indicates the current state of the connection. + /// + /// + /// The default value is . + /// + /// + public WebSocketState ReadyState { + get { + return _readyState; + } + } + + /// + /// Gets the configuration for secure connection. + /// + /// + /// This configuration will be referenced when attempts to connect, + /// so it must be configured before any connect method is called. + /// + /// + /// A that represents + /// the configuration used to establish a secure connection. + /// + /// + /// + /// This instance is not a client. + /// + /// + /// This instance does not use a secure connection. + /// + /// + public ClientSslConfiguration SslConfiguration { + get { + if (!_client) { + var msg = "This instance is not a client."; + throw new InvalidOperationException (msg); + } + + if (!_secure) { + var msg = "This instance does not use a secure connection."; + throw new InvalidOperationException (msg); + } + + return getSslConfiguration (); + } + } + + /// + /// Gets the URL to which to connect. + /// + /// + /// A that represents the URL to which to connect. + /// + public Uri Url { + get { + return _client ? _uri : _context.RequestUri; + } + } + + /// + /// Gets or sets the time to wait for the response to the ping or close. + /// + /// + /// The set operation does nothing if the connection has already been + /// established or it is closing. + /// + /// + /// + /// A to wait for the response. + /// + /// + /// The default value is the same as 5 seconds if this instance is + /// a client. + /// + /// + /// + /// The value specified for a set operation is zero or less. + /// + public TimeSpan WaitTime { + get { + return _waitTime; + } + + set { + if (value <= TimeSpan.Zero) + throw new ArgumentOutOfRangeException ("value", "Zero or less."); + + string msg; + if (!canSet (out msg)) { + _logger.Warn (msg); + return; + } + + lock (_forState) { + if (!canSet (out msg)) { + _logger.Warn (msg); + return; + } + + _waitTime = value; + } + } + } + + #endregion + + #region Public Events + + /// + /// Occurs when the WebSocket connection has been closed. + /// + public event EventHandler OnClose; + + /// + /// Occurs when the gets an error. + /// + public event EventHandler OnError; + + /// + /// Occurs when the receives a message. + /// + public event EventHandler OnMessage; + + /// + /// Occurs when the WebSocket connection has been established. + /// + public event EventHandler OnOpen; + + #endregion + + #region Private Methods + + // As server + private bool accept () + { + if (_readyState == WebSocketState.Open) { + var msg = "The handshake request has already been accepted."; + _logger.Warn (msg); + + return false; + } + + lock (_forState) { + if (_readyState == WebSocketState.Open) { + var msg = "The handshake request has already been accepted."; + _logger.Warn (msg); + + return false; + } + + if (_readyState == WebSocketState.Closing) { + var msg = "The close process has set in."; + _logger.Error (msg); + + msg = "An interruption has occurred while attempting to accept."; + error (msg, null); + + return false; + } + + if (_readyState == WebSocketState.Closed) { + var msg = "The connection has been closed."; + _logger.Error (msg); + + msg = "An interruption has occurred while attempting to accept."; + error (msg, null); + + return false; + } + + try { + if (!acceptHandshake ()) + return false; + } + catch (Exception ex) { + _logger.Fatal (ex.Message); + _logger.Debug (ex.ToString ()); + + var msg = "An exception has occurred while attempting to accept."; + fatal (msg, ex); + + return false; + } + + _readyState = WebSocketState.Open; + return true; + } + } + + // As server + private bool acceptHandshake () + { + _logger.Debug ( + String.Format ( + "A handshake request from {0}:\n{1}", _context.UserEndPoint, _context + ) + ); + + string msg; + if (!checkHandshakeRequest (_context, out msg)) { + _logger.Error (msg); + + refuseHandshake ( + CloseStatusCode.ProtocolError, + "A handshake error has occurred while attempting to accept." + ); + + return false; + } + + if (!customCheckHandshakeRequest (_context, out msg)) { + _logger.Error (msg); + + refuseHandshake ( + CloseStatusCode.PolicyViolation, + "A handshake error has occurred while attempting to accept." + ); + + return false; + } + + _base64Key = _context.Headers["Sec-WebSocket-Key"]; + + if (_protocol != null) { + var vals = _context.SecWebSocketProtocols; + processSecWebSocketProtocolClientHeader (vals); + } + + if (!_ignoreExtensions) { + var val = _context.Headers["Sec-WebSocket-Extensions"]; + processSecWebSocketExtensionsClientHeader (val); + } + + return sendHttpResponse (createHandshakeResponse ()); + } + + private bool canSet (out string message) + { + message = null; + + if (_readyState == WebSocketState.Open) { + message = "The connection has already been established."; + return false; + } + + if (_readyState == WebSocketState.Closing) { + message = "The connection is closing."; + return false; + } + + return true; + } + + // As server + private bool checkHandshakeRequest ( + WebSocketContext context, out string message + ) + { + message = null; + + if (!context.IsWebSocketRequest) { + message = "Not a handshake request."; + return false; + } + + if (context.RequestUri == null) { + message = "It specifies an invalid Request-URI."; + return false; + } + + var headers = context.Headers; + + var key = headers["Sec-WebSocket-Key"]; + if (key == null) { + message = "It includes no Sec-WebSocket-Key header."; + return false; + } + + if (key.Length == 0) { + message = "It includes an invalid Sec-WebSocket-Key header."; + return false; + } + + var version = headers["Sec-WebSocket-Version"]; + if (version == null) { + message = "It includes no Sec-WebSocket-Version header."; + return false; + } + + if (version != _version) { + message = "It includes an invalid Sec-WebSocket-Version header."; + return false; + } + + var protocol = headers["Sec-WebSocket-Protocol"]; + if (protocol != null && protocol.Length == 0) { + message = "It includes an invalid Sec-WebSocket-Protocol header."; + return false; + } + + if (!_ignoreExtensions) { + var extensions = headers["Sec-WebSocket-Extensions"]; + if (extensions != null && extensions.Length == 0) { + message = "It includes an invalid Sec-WebSocket-Extensions header."; + return false; + } + } + + return true; + } + + // As client + private bool checkHandshakeResponse (HttpResponse response, out string message) + { + message = null; + + if (response.IsRedirect) { + message = "Indicates the redirection."; + return false; + } + + if (response.IsUnauthorized) { + message = "Requires the authentication."; + return false; + } + + if (!response.IsWebSocketResponse) { + message = "Not a WebSocket handshake response."; + return false; + } + + var headers = response.Headers; + if (!validateSecWebSocketAcceptHeader (headers["Sec-WebSocket-Accept"])) { + message = "Includes no Sec-WebSocket-Accept header, or it has an invalid value."; + return false; + } + + if (!validateSecWebSocketProtocolServerHeader (headers["Sec-WebSocket-Protocol"])) { + message = "Includes no Sec-WebSocket-Protocol header, or it has an invalid value."; + return false; + } + + if (!validateSecWebSocketExtensionsServerHeader (headers["Sec-WebSocket-Extensions"])) { + message = "Includes an invalid Sec-WebSocket-Extensions header."; + return false; + } + + if (!validateSecWebSocketVersionServerHeader (headers["Sec-WebSocket-Version"])) { + message = "Includes an invalid Sec-WebSocket-Version header."; + return false; + } + + return true; + } + + private static bool checkProtocols (string[] protocols, out string message) + { + message = null; + + Func cond = protocol => protocol.IsNullOrEmpty () + || !protocol.IsToken (); + + if (protocols.Contains (cond)) { + message = "It contains a value that is not a token."; + return false; + } + + if (protocols.ContainsTwice ()) { + message = "It contains a value twice."; + return false; + } + + return true; + } + + private bool checkReceivedFrame (WebSocketFrame frame, out string message) + { + message = null; + + var masked = frame.IsMasked; + if (_client && masked) { + message = "A frame from the server is masked."; + return false; + } + + if (!_client && !masked) { + message = "A frame from a client is not masked."; + return false; + } + + if (_inContinuation && frame.IsData) { + message = "A data frame has been received while receiving continuation frames."; + return false; + } + + if (frame.IsCompressed && _compression == CompressionMethod.None) { + message = "A compressed frame has been received without any agreement for it."; + return false; + } + + if (frame.Rsv2 == Rsv.On) { + message = "The RSV2 of a frame is non-zero without any negotiation for it."; + return false; + } + + if (frame.Rsv3 == Rsv.On) { + message = "The RSV3 of a frame is non-zero without any negotiation for it."; + return false; + } + + return true; + } + + private void close (ushort code, string reason) + { + if (_readyState == WebSocketState.Closing) { + _logger.Info ("The closing is already in progress."); + return; + } + + if (_readyState == WebSocketState.Closed) { + _logger.Info ("The connection has already been closed."); + return; + } + + if (code == 1005) { // == no status + close (PayloadData.Empty, true, true, false); + return; + } + + var send = !code.IsReserved (); + close (new PayloadData (code, reason), send, send, false); + } + + private void close ( + PayloadData payloadData, bool send, bool receive, bool received + ) + { + lock (_forState) { + if (_readyState == WebSocketState.Closing) { + _logger.Info ("The closing is already in progress."); + return; + } + + if (_readyState == WebSocketState.Closed) { + _logger.Info ("The connection has already been closed."); + return; + } + + send = send && _readyState == WebSocketState.Open; + receive = send && receive; + + _readyState = WebSocketState.Closing; + } + + _logger.Trace ("Begin closing the connection."); + + var res = closeHandshake (payloadData, send, receive, received); + releaseResources (); + + _logger.Trace ("End closing the connection."); + + _readyState = WebSocketState.Closed; + + var e = new CloseEventArgs (payloadData); + e.WasClean = res; + + try { + OnClose.Emit (this, e); + } + catch (Exception ex) { + _logger.Error (ex.ToString ()); + error ("An error has occurred during the OnClose event.", ex); + } + } + + private void closeAsync (ushort code, string reason) + { + if (_readyState == WebSocketState.Closing) { + _logger.Info ("The closing is already in progress."); + return; + } + + if (_readyState == WebSocketState.Closed) { + _logger.Info ("The connection has already been closed."); + return; + } + + if (code == 1005) { // == no status + closeAsync (PayloadData.Empty, true, true, false); + return; + } + + var send = !code.IsReserved (); + closeAsync (new PayloadData (code, reason), send, send, false); + } + + private void closeAsync ( + PayloadData payloadData, bool send, bool receive, bool received + ) + { + Action closer = close; + closer.BeginInvoke ( + payloadData, send, receive, received, ar => closer.EndInvoke (ar), null + ); + } + + private bool closeHandshake (byte[] frameAsBytes, bool receive, bool received) + { + var sent = frameAsBytes != null && sendBytes (frameAsBytes); + + var wait = !received && sent && receive && _receivingExited != null; + if (wait) + received = _receivingExited.WaitOne (_waitTime); + + var ret = sent && received; + + _logger.Debug ( + String.Format ( + "Was clean?: {0}\n sent: {1}\n received: {2}", ret, sent, received + ) + ); + + return ret; + } + + private bool closeHandshake ( + PayloadData payloadData, bool send, bool receive, bool received + ) + { + var sent = false; + if (send) { + var frame = WebSocketFrame.CreateCloseFrame (payloadData, _client); + sent = sendBytes (frame.ToArray ()); + + if (_client) + frame.Unmask (); + } + + var wait = !received && sent && receive && _receivingExited != null; + if (wait) + received = _receivingExited.WaitOne (_waitTime); + + var ret = sent && received; + + _logger.Debug ( + String.Format ( + "Was clean?: {0}\n sent: {1}\n received: {2}", ret, sent, received + ) + ); + + return ret; + } + + // As client + private bool connect () + { + if (_readyState == WebSocketState.Open) { + var msg = "The connection has already been established."; + _logger.Warn (msg); + + return false; + } + + lock (_forState) { + if (_readyState == WebSocketState.Open) { + var msg = "The connection has already been established."; + _logger.Warn (msg); + + return false; + } + + if (_readyState == WebSocketState.Closing) { + var msg = "The close process has set in."; + _logger.Error (msg); + + msg = "An interruption has occurred while attempting to connect."; + error (msg, null); + + return false; + } + + if (_retryCountForConnect > _maxRetryCountForConnect) { + var msg = "An opportunity for reconnecting has been lost."; + _logger.Error (msg); + + msg = "An interruption has occurred while attempting to connect."; + error (msg, null); + + return false; + } + + _readyState = WebSocketState.Connecting; + + try { + doHandshake (); + } + catch (Exception ex) { + _retryCountForConnect++; + + _logger.Fatal (ex.Message); + _logger.Debug (ex.ToString ()); + + var msg = "An exception has occurred while attempting to connect."; + fatal (msg, ex); + + return false; + } + + _retryCountForConnect = 1; + _readyState = WebSocketState.Open; + + return true; + } + } + + // As client + private string createExtensions () + { + var buff = new StringBuilder (80); + + if (_compression != CompressionMethod.None) { + var str = _compression.ToExtensionString ( + "server_no_context_takeover", "client_no_context_takeover"); + + buff.AppendFormat ("{0}, ", str); + } + + var len = buff.Length; + if (len > 2) { + buff.Length = len - 2; + return buff.ToString (); + } + + return null; + } + + // As server + private HttpResponse createHandshakeFailureResponse (HttpStatusCode code) + { + var ret = HttpResponse.CreateCloseResponse (code); + ret.Headers["Sec-WebSocket-Version"] = _version; + + return ret; + } + + // As client + private HttpRequest createHandshakeRequest () + { + var ret = HttpRequest.CreateWebSocketRequest (_uri); + + var headers = ret.Headers; + if (!_origin.IsNullOrEmpty ()) + headers["Origin"] = _origin; + + headers["Sec-WebSocket-Key"] = _base64Key; + + _protocolsRequested = _protocols != null; + if (_protocolsRequested) + headers["Sec-WebSocket-Protocol"] = _protocols.ToString (", "); + + _extensionsRequested = _compression != CompressionMethod.None; + if (_extensionsRequested) + headers["Sec-WebSocket-Extensions"] = createExtensions (); + + headers["Sec-WebSocket-Version"] = _version; + + AuthenticationResponse authRes = null; + if (_authChallenge != null && _credentials != null) { + authRes = new AuthenticationResponse (_authChallenge, _credentials, _nonceCount); + _nonceCount = authRes.NonceCount; + } + else if (_preAuth) { + authRes = new AuthenticationResponse (_credentials); + } + + if (authRes != null) + headers["Authorization"] = authRes.ToString (); + + if (_cookies.Count > 0) + ret.SetCookies (_cookies); + + return ret; + } + + // As server + private HttpResponse createHandshakeResponse () + { + var ret = HttpResponse.CreateWebSocketResponse (); + + var headers = ret.Headers; + headers["Sec-WebSocket-Accept"] = CreateResponseKey (_base64Key); + + if (_protocol != null) + headers["Sec-WebSocket-Protocol"] = _protocol; + + if (_extensions != null) + headers["Sec-WebSocket-Extensions"] = _extensions; + + if (_cookies.Count > 0) + ret.SetCookies (_cookies); + + return ret; + } + + // As server + private bool customCheckHandshakeRequest ( + WebSocketContext context, out string message + ) + { + message = null; + + if (_handshakeRequestChecker == null) + return true; + + message = _handshakeRequestChecker (context); + return message == null; + } + + private MessageEventArgs dequeueFromMessageEventQueue () + { + lock (_forMessageEventQueue) + return _messageEventQueue.Count > 0 ? _messageEventQueue.Dequeue () : null; + } + + // As client + private void doHandshake () + { + setClientStream (); + var res = sendHandshakeRequest (); + + string msg; + if (!checkHandshakeResponse (res, out msg)) + throw new WebSocketException (CloseStatusCode.ProtocolError, msg); + + if (_protocolsRequested) + _protocol = res.Headers["Sec-WebSocket-Protocol"]; + + if (_extensionsRequested) + processSecWebSocketExtensionsServerHeader (res.Headers["Sec-WebSocket-Extensions"]); + + processCookies (res.Cookies); + } + + private void enqueueToMessageEventQueue (MessageEventArgs e) + { + lock (_forMessageEventQueue) + _messageEventQueue.Enqueue (e); + } + + private void error (string message, Exception exception) + { + try { + OnError.Emit (this, new ErrorEventArgs (message, exception)); + } + catch (Exception ex) { + _logger.Error (ex.Message); + _logger.Debug (ex.ToString ()); + } + } + + private void fatal (string message, Exception exception) + { + var code = exception is WebSocketException + ? ((WebSocketException) exception).Code + : CloseStatusCode.Abnormal; + + fatal (message, (ushort) code); + } + + private void fatal (string message, ushort code) + { + var payload = new PayloadData (code, message); + close (payload, !code.IsReserved (), false, false); + } + + private void fatal (string message, CloseStatusCode code) + { + fatal (message, (ushort) code); + } + + private ClientSslConfiguration getSslConfiguration () + { + if (_sslConfig == null) + _sslConfig = new ClientSslConfiguration (_uri.DnsSafeHost); + + return _sslConfig; + } + + private void init () + { + _compression = CompressionMethod.None; + _cookies = new CookieCollection (); + _forPing = new object (); + _forSend = new object (); + _forState = new object (); + _messageEventQueue = new Queue (); + _forMessageEventQueue = ((ICollection) _messageEventQueue).SyncRoot; + _readyState = WebSocketState.Connecting; + } + + private void message () + { + MessageEventArgs e = null; + lock (_forMessageEventQueue) { + if (_inMessage || _messageEventQueue.Count == 0 || _readyState != WebSocketState.Open) + return; + + _inMessage = true; + e = _messageEventQueue.Dequeue (); + } + + _message (e); + } + + private void messagec (MessageEventArgs e) + { + do { + try { + OnMessage.Emit (this, e); + } + catch (Exception ex) { + _logger.Error (ex.ToString ()); + error ("An error has occurred during an OnMessage event.", ex); + } + + lock (_forMessageEventQueue) { + if (_messageEventQueue.Count == 0 || _readyState != WebSocketState.Open) { + _inMessage = false; + break; + } + + e = _messageEventQueue.Dequeue (); + } + } + while (true); + } + + private void messages (MessageEventArgs e) + { + try { + OnMessage.Emit (this, e); + } + catch (Exception ex) { + _logger.Error (ex.ToString ()); + error ("An error has occurred during an OnMessage event.", ex); + } + + lock (_forMessageEventQueue) { + if (_messageEventQueue.Count == 0 || _readyState != WebSocketState.Open) { + _inMessage = false; + return; + } + + e = _messageEventQueue.Dequeue (); + } + + ThreadPool.QueueUserWorkItem (state => messages (e)); + } + + private void open () + { + _inMessage = true; + startReceiving (); + try { + OnOpen.Emit (this, EventArgs.Empty); + } + catch (Exception ex) { + _logger.Error (ex.ToString ()); + error ("An error has occurred during the OnOpen event.", ex); + } + + MessageEventArgs e = null; + lock (_forMessageEventQueue) { + if (_messageEventQueue.Count == 0 || _readyState != WebSocketState.Open) { + _inMessage = false; + return; + } + + e = _messageEventQueue.Dequeue (); + } + + _message.BeginInvoke (e, ar => _message.EndInvoke (ar), null); + } + + private bool ping (byte[] data) + { + if (_readyState != WebSocketState.Open) + return false; + + var pongReceived = _pongReceived; + if (pongReceived == null) + return false; + + lock (_forPing) { + try { + pongReceived.Reset (); + if (!send (Fin.Final, Opcode.Ping, data, false)) + return false; + + return pongReceived.WaitOne (_waitTime); + } + catch (ObjectDisposedException) { + return false; + } + } + } + + private bool processCloseFrame (WebSocketFrame frame) + { + var payload = frame.PayloadData; + close (payload, !payload.HasReservedCode, false, true); + + return false; + } + + // As client + private void processCookies (CookieCollection cookies) + { + if (cookies.Count == 0) + return; + + _cookies.SetOrRemove (cookies); + } + + private bool processDataFrame (WebSocketFrame frame) + { + enqueueToMessageEventQueue ( + frame.IsCompressed + ? new MessageEventArgs ( + frame.Opcode, frame.PayloadData.ApplicationData.Decompress (_compression)) + : new MessageEventArgs (frame)); + + return true; + } + + private bool processFragmentFrame (WebSocketFrame frame) + { + if (!_inContinuation) { + // Must process first fragment. + if (frame.IsContinuation) + return true; + + _fragmentsOpcode = frame.Opcode; + _fragmentsCompressed = frame.IsCompressed; + _fragmentsBuffer = new MemoryStream (); + _inContinuation = true; + } + + _fragmentsBuffer.WriteBytes (frame.PayloadData.ApplicationData, 1024); + if (frame.IsFinal) { + using (_fragmentsBuffer) { + var data = _fragmentsCompressed + ? _fragmentsBuffer.DecompressToArray (_compression) + : _fragmentsBuffer.ToArray (); + + enqueueToMessageEventQueue (new MessageEventArgs (_fragmentsOpcode, data)); + } + + _fragmentsBuffer = null; + _inContinuation = false; + } + + return true; + } + + private bool processPingFrame (WebSocketFrame frame) + { + _logger.Trace ("A ping was received."); + + var pong = WebSocketFrame.CreatePongFrame (frame.PayloadData, _client); + + lock (_forState) { + if (_readyState != WebSocketState.Open) { + _logger.Error ("The connection is closing."); + return true; + } + + if (!sendBytes (pong.ToArray ())) + return false; + } + + _logger.Trace ("A pong to this ping has been sent."); + + if (_emitOnPing) { + if (_client) + pong.Unmask (); + + enqueueToMessageEventQueue (new MessageEventArgs (frame)); + } + + return true; + } + + private bool processPongFrame (WebSocketFrame frame) + { + _logger.Trace ("A pong was received."); + + try { + _pongReceived.Set (); + } + catch (NullReferenceException ex) { + _logger.Error (ex.Message); + _logger.Debug (ex.ToString ()); + + return false; + } + catch (ObjectDisposedException ex) { + _logger.Error (ex.Message); + _logger.Debug (ex.ToString ()); + + return false; + } + + _logger.Trace ("It has been signaled."); + + return true; + } + + private bool processReceivedFrame (WebSocketFrame frame) + { + string msg; + if (!checkReceivedFrame (frame, out msg)) + throw new WebSocketException (CloseStatusCode.ProtocolError, msg); + + frame.Unmask (); + return frame.IsFragment + ? processFragmentFrame (frame) + : frame.IsData + ? processDataFrame (frame) + : frame.IsPing + ? processPingFrame (frame) + : frame.IsPong + ? processPongFrame (frame) + : frame.IsClose + ? processCloseFrame (frame) + : processUnsupportedFrame (frame); + } + + // As server + private void processSecWebSocketExtensionsClientHeader (string value) + { + if (value == null) + return; + + var buff = new StringBuilder (80); + var comp = false; + + foreach (var elm in value.SplitHeaderValue (',')) { + var extension = elm.Trim (); + if (extension.Length == 0) + continue; + + if (!comp) { + if (extension.IsCompressionExtension (CompressionMethod.Deflate)) { + _compression = CompressionMethod.Deflate; + + buff.AppendFormat ( + "{0}, ", + _compression.ToExtensionString ( + "client_no_context_takeover", "server_no_context_takeover" + ) + ); + + comp = true; + } + } + } + + var len = buff.Length; + if (len <= 2) + return; + + buff.Length = len - 2; + _extensions = buff.ToString (); + } + + // As client + private void processSecWebSocketExtensionsServerHeader (string value) + { + if (value == null) { + _compression = CompressionMethod.None; + return; + } + + _extensions = value; + } + + // As server + private void processSecWebSocketProtocolClientHeader ( + IEnumerable values + ) + { + if (values.Contains (val => val == _protocol)) + return; + + _protocol = null; + } + + private bool processUnsupportedFrame (WebSocketFrame frame) + { + _logger.Fatal ("An unsupported frame:" + frame.PrintToString (false)); + fatal ("There is no way to handle it.", CloseStatusCode.PolicyViolation); + + return false; + } + + // As server + private void refuseHandshake (CloseStatusCode code, string reason) + { + _readyState = WebSocketState.Closing; + + var res = createHandshakeFailureResponse (HttpStatusCode.BadRequest); + sendHttpResponse (res); + + releaseServerResources (); + + _readyState = WebSocketState.Closed; + + var e = new CloseEventArgs (code, reason); + + try { + OnClose.Emit (this, e); + } + catch (Exception ex) { + _logger.Error (ex.Message); + _logger.Debug (ex.ToString ()); + } + } + + // As client + private void releaseClientResources () + { + if (_stream != null) { + _stream.Dispose (); + _stream = null; + } + + if (_tcpClient != null) { + _tcpClient.Close (); + _tcpClient = null; + } + } + + private void releaseCommonResources () + { + if (_fragmentsBuffer != null) { + _fragmentsBuffer.Dispose (); + _fragmentsBuffer = null; + _inContinuation = false; + } + + if (_pongReceived != null) { + _pongReceived.Close (); + _pongReceived = null; + } + + if (_receivingExited != null) { + _receivingExited.Close (); + _receivingExited = null; + } + } + + private void releaseResources () + { + if (_client) + releaseClientResources (); + else + releaseServerResources (); + + releaseCommonResources (); + } + + // As server + private void releaseServerResources () + { + if (_closeContext == null) + return; + + _closeContext (); + _closeContext = null; + _stream = null; + _context = null; + } + + private bool send (Opcode opcode, Stream stream) + { + lock (_forSend) { + var src = stream; + var compressed = false; + var sent = false; + try { + if (_compression != CompressionMethod.None) { + stream = stream.Compress (_compression); + compressed = true; + } + + sent = send (opcode, stream, compressed); + if (!sent) + error ("A send has been interrupted.", null); + } + catch (Exception ex) { + _logger.Error (ex.ToString ()); + error ("An error has occurred during a send.", ex); + } + finally { + if (compressed) + stream.Dispose (); + + src.Dispose (); + } + + return sent; + } + } + + private bool send (Opcode opcode, Stream stream, bool compressed) + { + var len = stream.Length; + if (len == 0) + return send (Fin.Final, opcode, EmptyBytes, false); + + var quo = len / FragmentLength; + var rem = (int) (len % FragmentLength); + + byte[] buff = null; + if (quo == 0) { + buff = new byte[rem]; + return stream.Read (buff, 0, rem) == rem + && send (Fin.Final, opcode, buff, compressed); + } + + if (quo == 1 && rem == 0) { + buff = new byte[FragmentLength]; + return stream.Read (buff, 0, FragmentLength) == FragmentLength + && send (Fin.Final, opcode, buff, compressed); + } + + /* Send fragments */ + + // Begin + buff = new byte[FragmentLength]; + var sent = stream.Read (buff, 0, FragmentLength) == FragmentLength + && send (Fin.More, opcode, buff, compressed); + + if (!sent) + return false; + + var n = rem == 0 ? quo - 2 : quo - 1; + for (long i = 0; i < n; i++) { + sent = stream.Read (buff, 0, FragmentLength) == FragmentLength + && send (Fin.More, Opcode.Cont, buff, false); + + if (!sent) + return false; + } + + // End + if (rem == 0) + rem = FragmentLength; + else + buff = new byte[rem]; + + return stream.Read (buff, 0, rem) == rem + && send (Fin.Final, Opcode.Cont, buff, false); + } + + private bool send (Fin fin, Opcode opcode, byte[] data, bool compressed) + { + lock (_forState) { + if (_readyState != WebSocketState.Open) { + _logger.Error ("The connection is closing."); + return false; + } + + var frame = new WebSocketFrame (fin, opcode, data, compressed, _client); + return sendBytes (frame.ToArray ()); + } + } + + private void sendAsync (Opcode opcode, Stream stream, Action completed) + { + Func sender = send; + sender.BeginInvoke ( + opcode, + stream, + ar => { + try { + var sent = sender.EndInvoke (ar); + if (completed != null) + completed (sent); + } + catch (Exception ex) { + _logger.Error (ex.ToString ()); + error ( + "An error has occurred during the callback for an async send.", + ex + ); + } + }, + null + ); + } + + private bool sendBytes (byte[] bytes) + { + try { + _stream.Write (bytes, 0, bytes.Length); + } + catch (Exception ex) { + _logger.Error (ex.Message); + _logger.Debug (ex.ToString ()); + + return false; + } + + return true; + } + + // As client + private HttpResponse sendHandshakeRequest () + { + var req = createHandshakeRequest (); + var res = sendHttpRequest (req, 90000); + if (res.IsUnauthorized) { + var chal = res.Headers["WWW-Authenticate"]; + _logger.Warn (String.Format ("Received an authentication requirement for '{0}'.", chal)); + if (chal.IsNullOrEmpty ()) { + _logger.Error ("No authentication challenge is specified."); + return res; + } + + _authChallenge = AuthenticationChallenge.Parse (chal); + if (_authChallenge == null) { + _logger.Error ("An invalid authentication challenge is specified."); + return res; + } + + if (_credentials != null && + (!_preAuth || _authChallenge.Scheme == AuthenticationSchemes.Digest)) { + if (res.HasConnectionClose) { + releaseClientResources (); + setClientStream (); + } + + var authRes = new AuthenticationResponse (_authChallenge, _credentials, _nonceCount); + _nonceCount = authRes.NonceCount; + req.Headers["Authorization"] = authRes.ToString (); + res = sendHttpRequest (req, 15000); + } + } + + if (res.IsRedirect) { + var url = res.Headers["Location"]; + _logger.Warn (String.Format ("Received a redirection to '{0}'.", url)); + if (_enableRedirection) { + if (url.IsNullOrEmpty ()) { + _logger.Error ("No url to redirect is located."); + return res; + } + + Uri uri; + string msg; + if (!url.TryCreateWebSocketUri (out uri, out msg)) { + _logger.Error ("An invalid url to redirect is located: " + msg); + return res; + } + + releaseClientResources (); + + _uri = uri; + _secure = uri.Scheme == "wss"; + + setClientStream (); + return sendHandshakeRequest (); + } + } + + return res; + } + + // As client + private HttpResponse sendHttpRequest (HttpRequest request, int millisecondsTimeout) + { + _logger.Debug ("A request to the server:\n" + request.ToString ()); + var res = request.GetResponse (_stream, millisecondsTimeout); + _logger.Debug ("A response to this request:\n" + res.ToString ()); + + return res; + } + + // As server + private bool sendHttpResponse (HttpResponse response) + { + _logger.Debug ( + String.Format ( + "A response to {0}:\n{1}", _context.UserEndPoint, response + ) + ); + + return sendBytes (response.ToByteArray ()); + } + + // As client + private void sendProxyConnectRequest () + { + var req = HttpRequest.CreateConnectRequest (_uri); + var res = sendHttpRequest (req, 90000); + if (res.IsProxyAuthenticationRequired) { + var chal = res.Headers["Proxy-Authenticate"]; + _logger.Warn ( + String.Format ("Received a proxy authentication requirement for '{0}'.", chal)); + + if (chal.IsNullOrEmpty ()) + throw new WebSocketException ("No proxy authentication challenge is specified."); + + var authChal = AuthenticationChallenge.Parse (chal); + if (authChal == null) + throw new WebSocketException ("An invalid proxy authentication challenge is specified."); + + if (_proxyCredentials != null) { + if (res.HasConnectionClose) { + releaseClientResources (); + _tcpClient = new TcpClient (_proxyUri.DnsSafeHost, _proxyUri.Port); + _stream = _tcpClient.GetStream (); + } + + var authRes = new AuthenticationResponse (authChal, _proxyCredentials, 0); + req.Headers["Proxy-Authorization"] = authRes.ToString (); + res = sendHttpRequest (req, 15000); + } + + if (res.IsProxyAuthenticationRequired) + throw new WebSocketException ("A proxy authentication is required."); + } + + if (res.StatusCode[0] != '2') + throw new WebSocketException ( + "The proxy has failed a connection to the requested host and port."); + } + + // As client + private void setClientStream () + { + if (_proxyUri != null) { + _tcpClient = new TcpClient (_proxyUri.DnsSafeHost, _proxyUri.Port); + _stream = _tcpClient.GetStream (); + sendProxyConnectRequest (); + } + else { + _tcpClient = new TcpClient (_uri.DnsSafeHost, _uri.Port); + _stream = _tcpClient.GetStream (); + } + + if (_secure) { + var conf = getSslConfiguration (); + var host = conf.TargetHost; + if (host != _uri.DnsSafeHost) + throw new WebSocketException ( + CloseStatusCode.TlsHandshakeFailure, "An invalid host name is specified."); + + try { + var sslStream = new SslStream ( + _stream, + false, + conf.ServerCertificateValidationCallback, + conf.ClientCertificateSelectionCallback); + + sslStream.AuthenticateAsClient ( + host, + conf.ClientCertificates, + conf.EnabledSslProtocols, + conf.CheckCertificateRevocation); + + _stream = sslStream; + } + catch (Exception ex) { + throw new WebSocketException (CloseStatusCode.TlsHandshakeFailure, ex); + } + } + } + + private void startReceiving () + { + if (_messageEventQueue.Count > 0) + _messageEventQueue.Clear (); + + _pongReceived = new ManualResetEvent (false); + _receivingExited = new ManualResetEvent (false); + + Action receive = null; + receive = + () => + WebSocketFrame.ReadFrameAsync ( + _stream, + false, + frame => { + if (!processReceivedFrame (frame) || _readyState == WebSocketState.Closed) { + var exited = _receivingExited; + if (exited != null) + exited.Set (); + + return; + } + + // Receive next asap because the Ping or Close needs a response to it. + receive (); + + if (_inMessage || !HasMessage || _readyState != WebSocketState.Open) + return; + + message (); + }, + ex => { + _logger.Fatal (ex.ToString ()); + fatal ("An exception has occurred while receiving.", ex); + } + ); + + receive (); + } + + // As client + private bool validateSecWebSocketAcceptHeader (string value) + { + return value != null && value == CreateResponseKey (_base64Key); + } + + // As client + private bool validateSecWebSocketExtensionsServerHeader (string value) + { + if (value == null) + return true; + + if (value.Length == 0) + return false; + + if (!_extensionsRequested) + return false; + + var comp = _compression != CompressionMethod.None; + foreach (var e in value.SplitHeaderValue (',')) { + var ext = e.Trim (); + if (comp && ext.IsCompressionExtension (_compression)) { + if (!ext.Contains ("server_no_context_takeover")) { + _logger.Error ("The server hasn't sent back 'server_no_context_takeover'."); + return false; + } + + if (!ext.Contains ("client_no_context_takeover")) + _logger.Warn ("The server hasn't sent back 'client_no_context_takeover'."); + + var method = _compression.ToExtensionString (); + var invalid = + ext.SplitHeaderValue (';').Contains ( + t => { + t = t.Trim (); + return t != method + && t != "server_no_context_takeover" + && t != "client_no_context_takeover"; + } + ); + + if (invalid) + return false; + } + else { + return false; + } + } + + return true; + } + + // As client + private bool validateSecWebSocketProtocolServerHeader (string value) + { + if (value == null) + return !_protocolsRequested; + + if (value.Length == 0) + return false; + + return _protocolsRequested && _protocols.Contains (p => p == value); + } + + // As client + private bool validateSecWebSocketVersionServerHeader (string value) + { + return value == null || value == _version; + } + + #endregion + + #region Internal Methods + + // As server + internal void Close (HttpResponse response) + { + _readyState = WebSocketState.Closing; + + sendHttpResponse (response); + releaseServerResources (); + + _readyState = WebSocketState.Closed; + } + + // As server + internal void Close (HttpStatusCode code) + { + Close (createHandshakeFailureResponse (code)); + } + + // As server + internal void Close (PayloadData payloadData, byte[] frameAsBytes) + { + lock (_forState) { + if (_readyState == WebSocketState.Closing) { + _logger.Info ("The closing is already in progress."); + return; + } + + if (_readyState == WebSocketState.Closed) { + _logger.Info ("The connection has already been closed."); + return; + } + + _readyState = WebSocketState.Closing; + } + + _logger.Trace ("Begin closing the connection."); + + var sent = frameAsBytes != null && sendBytes (frameAsBytes); + var received = sent && _receivingExited != null + ? _receivingExited.WaitOne (_waitTime) + : false; + + var res = sent && received; + + _logger.Debug ( + String.Format ( + "Was clean?: {0}\n sent: {1}\n received: {2}", res, sent, received + ) + ); + + releaseServerResources (); + releaseCommonResources (); + + _logger.Trace ("End closing the connection."); + + _readyState = WebSocketState.Closed; + + var e = new CloseEventArgs (payloadData); + e.WasClean = res; + + try { + OnClose.Emit (this, e); + } + catch (Exception ex) { + _logger.Error (ex.ToString ()); + } + } + + // As client + internal static string CreateBase64Key () + { + var src = new byte[16]; + RandomNumber.GetBytes (src); + + return Convert.ToBase64String (src); + } + + internal static string CreateResponseKey (string base64Key) + { + var buff = new StringBuilder (base64Key, 64); + buff.Append (_guid); + SHA1 sha1 = new SHA1CryptoServiceProvider (); + var src = sha1.ComputeHash (buff.ToString ().UTF8Encode ()); + + return Convert.ToBase64String (src); + } + + // As server + internal void InternalAccept () + { + try { + if (!acceptHandshake ()) + return; + } + catch (Exception ex) { + _logger.Fatal (ex.Message); + _logger.Debug (ex.ToString ()); + + var msg = "An exception has occurred while attempting to accept."; + fatal (msg, ex); + + return; + } + + _readyState = WebSocketState.Open; + + open (); + } + + // As server + internal bool Ping (byte[] frameAsBytes, TimeSpan timeout) + { + if (_readyState != WebSocketState.Open) + return false; + + var pongReceived = _pongReceived; + if (pongReceived == null) + return false; + + lock (_forPing) { + try { + pongReceived.Reset (); + + lock (_forState) { + if (_readyState != WebSocketState.Open) + return false; + + if (!sendBytes (frameAsBytes)) + return false; + } + + return pongReceived.WaitOne (timeout); + } + catch (ObjectDisposedException) { + return false; + } + } + } + + // As server + internal void Send ( + Opcode opcode, byte[] data, Dictionary cache + ) + { + lock (_forSend) { + lock (_forState) { + if (_readyState != WebSocketState.Open) { + _logger.Error ("The connection is closing."); + return; + } + + byte[] found; + if (!cache.TryGetValue (_compression, out found)) { + found = new WebSocketFrame ( + Fin.Final, + opcode, + data.Compress (_compression), + _compression != CompressionMethod.None, + false + ) + .ToArray (); + + cache.Add (_compression, found); + } + + sendBytes (found); + } + } + } + + // As server + internal void Send ( + Opcode opcode, Stream stream, Dictionary cache + ) + { + lock (_forSend) { + Stream found; + if (!cache.TryGetValue (_compression, out found)) { + found = stream.Compress (_compression); + cache.Add (_compression, found); + } + else { + found.Position = 0; + } + + send (opcode, found, _compression != CompressionMethod.None); + } + } + + #endregion + + #region Public Methods + + /// + /// Accepts the handshake request. + /// + /// + /// This method does nothing if the handshake request has already been + /// accepted. + /// + /// + /// + /// This instance is a client. + /// + /// + /// -or- + /// + /// + /// The close process is in progress. + /// + /// + /// -or- + /// + /// + /// The connection has already been closed. + /// + /// + public void Accept () + { + if (_client) { + var msg = "This instance is a client."; + throw new InvalidOperationException (msg); + } + + if (_readyState == WebSocketState.Closing) { + var msg = "The close process is in progress."; + throw new InvalidOperationException (msg); + } + + if (_readyState == WebSocketState.Closed) { + var msg = "The connection has already been closed."; + throw new InvalidOperationException (msg); + } + + if (accept ()) + open (); + } + + /// + /// Accepts the handshake request asynchronously. + /// + /// + /// + /// This method does not wait for the accept process to be complete. + /// + /// + /// This method does nothing if the handshake request has already been + /// accepted. + /// + /// + /// + /// + /// This instance is a client. + /// + /// + /// -or- + /// + /// + /// The close process is in progress. + /// + /// + /// -or- + /// + /// + /// The connection has already been closed. + /// + /// + public void AcceptAsync () + { + if (_client) { + var msg = "This instance is a client."; + throw new InvalidOperationException (msg); + } + + if (_readyState == WebSocketState.Closing) { + var msg = "The close process is in progress."; + throw new InvalidOperationException (msg); + } + + if (_readyState == WebSocketState.Closed) { + var msg = "The connection has already been closed."; + throw new InvalidOperationException (msg); + } + + Func acceptor = accept; + acceptor.BeginInvoke ( + ar => { + if (acceptor.EndInvoke (ar)) + open (); + }, + null + ); + } + + /// + /// Closes the connection. + /// + /// + /// This method does nothing if the current state of the connection is + /// Closing or Closed. + /// + public void Close () + { + close (1005, String.Empty); + } + + /// + /// Closes the connection with the specified code. + /// + /// + /// This method does nothing if the current state of the connection is + /// Closing or Closed. + /// + /// + /// + /// A that represents the status code indicating + /// the reason for the close. + /// + /// + /// The status codes are defined in + /// + /// Section 7.4 of RFC 6455. + /// + /// + /// + /// is less than 1000 or greater than 4999. + /// + /// + /// + /// is 1011 (server error). + /// It cannot be used by clients. + /// + /// + /// -or- + /// + /// + /// is 1010 (mandatory extension). + /// It cannot be used by servers. + /// + /// + public void Close (ushort code) + { + if (!code.IsCloseStatusCode ()) { + var msg = "Less than 1000 or greater than 4999."; + throw new ArgumentOutOfRangeException ("code", msg); + } + + if (_client && code == 1011) { + var msg = "1011 cannot be used."; + throw new ArgumentException (msg, "code"); + } + + if (!_client && code == 1010) { + var msg = "1010 cannot be used."; + throw new ArgumentException (msg, "code"); + } + + close (code, String.Empty); + } + + /// + /// Closes the connection with the specified code. + /// + /// + /// This method does nothing if the current state of the connection is + /// Closing or Closed. + /// + /// + /// + /// One of the enum values. + /// + /// + /// It represents the status code indicating the reason for the close. + /// + /// + /// + /// + /// is + /// . + /// It cannot be used by clients. + /// + /// + /// -or- + /// + /// + /// is + /// . + /// It cannot be used by servers. + /// + /// + public void Close (CloseStatusCode code) + { + if (_client && code == CloseStatusCode.ServerError) { + var msg = "ServerError cannot be used."; + throw new ArgumentException (msg, "code"); + } + + if (!_client && code == CloseStatusCode.MandatoryExtension) { + var msg = "MandatoryExtension cannot be used."; + throw new ArgumentException (msg, "code"); + } + + close ((ushort) code, String.Empty); + } + + /// + /// Closes the connection with the specified code and reason. + /// + /// + /// This method does nothing if the current state of the connection is + /// Closing or Closed. + /// + /// + /// + /// A that represents the status code indicating + /// the reason for the close. + /// + /// + /// The status codes are defined in + /// + /// Section 7.4 of RFC 6455. + /// + /// + /// + /// + /// A that represents the reason for the close. + /// + /// + /// The size must be 123 bytes or less in UTF-8. + /// + /// + /// + /// + /// is less than 1000 or greater than 4999. + /// + /// + /// -or- + /// + /// + /// The size of is greater than 123 bytes. + /// + /// + /// + /// + /// is 1011 (server error). + /// It cannot be used by clients. + /// + /// + /// -or- + /// + /// + /// is 1010 (mandatory extension). + /// It cannot be used by servers. + /// + /// + /// -or- + /// + /// + /// is 1005 (no status) and there is reason. + /// + /// + /// -or- + /// + /// + /// could not be UTF-8-encoded. + /// + /// + public void Close (ushort code, string reason) + { + if (!code.IsCloseStatusCode ()) { + var msg = "Less than 1000 or greater than 4999."; + throw new ArgumentOutOfRangeException ("code", msg); + } + + if (_client && code == 1011) { + var msg = "1011 cannot be used."; + throw new ArgumentException (msg, "code"); + } + + if (!_client && code == 1010) { + var msg = "1010 cannot be used."; + throw new ArgumentException (msg, "code"); + } + + if (reason.IsNullOrEmpty ()) { + close (code, String.Empty); + return; + } + + if (code == 1005) { + var msg = "1005 cannot be used."; + throw new ArgumentException (msg, "code"); + } + + byte[] bytes; + if (!reason.TryGetUTF8EncodedBytes (out bytes)) { + var msg = "It could not be UTF-8-encoded."; + throw new ArgumentException (msg, "reason"); + } + + if (bytes.Length > 123) { + var msg = "Its size is greater than 123 bytes."; + throw new ArgumentOutOfRangeException ("reason", msg); + } + + close (code, reason); + } + + /// + /// Closes the connection with the specified code and reason. + /// + /// + /// This method does nothing if the current state of the connection is + /// Closing or Closed. + /// + /// + /// + /// One of the enum values. + /// + /// + /// It represents the status code indicating the reason for the close. + /// + /// + /// + /// + /// A that represents the reason for the close. + /// + /// + /// The size must be 123 bytes or less in UTF-8. + /// + /// + /// + /// + /// is + /// . + /// It cannot be used by clients. + /// + /// + /// -or- + /// + /// + /// is + /// . + /// It cannot be used by servers. + /// + /// + /// -or- + /// + /// + /// is + /// and there is reason. + /// + /// + /// -or- + /// + /// + /// could not be UTF-8-encoded. + /// + /// + /// + /// The size of is greater than 123 bytes. + /// + public void Close (CloseStatusCode code, string reason) + { + if (_client && code == CloseStatusCode.ServerError) { + var msg = "ServerError cannot be used."; + throw new ArgumentException (msg, "code"); + } + + if (!_client && code == CloseStatusCode.MandatoryExtension) { + var msg = "MandatoryExtension cannot be used."; + throw new ArgumentException (msg, "code"); + } + + if (reason.IsNullOrEmpty ()) { + close ((ushort) code, String.Empty); + return; + } + + if (code == CloseStatusCode.NoStatus) { + var msg = "NoStatus cannot be used."; + throw new ArgumentException (msg, "code"); + } + + byte[] bytes; + if (!reason.TryGetUTF8EncodedBytes (out bytes)) { + var msg = "It could not be UTF-8-encoded."; + throw new ArgumentException (msg, "reason"); + } + + if (bytes.Length > 123) { + var msg = "Its size is greater than 123 bytes."; + throw new ArgumentOutOfRangeException ("reason", msg); + } + + close ((ushort) code, reason); + } + + /// + /// Closes the connection asynchronously. + /// + /// + /// + /// This method does not wait for the close to be complete. + /// + /// + /// This method does nothing if the current state of the connection is + /// Closing or Closed. + /// + /// + public void CloseAsync () + { + closeAsync (1005, String.Empty); + } + + /// + /// Closes the connection asynchronously with the specified code. + /// + /// + /// + /// This method does not wait for the close to be complete. + /// + /// + /// This method does nothing if the current state of the connection is + /// Closing or Closed. + /// + /// + /// + /// + /// A that represents the status code indicating + /// the reason for the close. + /// + /// + /// The status codes are defined in + /// + /// Section 7.4 of RFC 6455. + /// + /// + /// + /// is less than 1000 or greater than 4999. + /// + /// + /// + /// is 1011 (server error). + /// It cannot be used by clients. + /// + /// + /// -or- + /// + /// + /// is 1010 (mandatory extension). + /// It cannot be used by servers. + /// + /// + public void CloseAsync (ushort code) + { + if (!code.IsCloseStatusCode ()) { + var msg = "Less than 1000 or greater than 4999."; + throw new ArgumentOutOfRangeException ("code", msg); + } + + if (_client && code == 1011) { + var msg = "1011 cannot be used."; + throw new ArgumentException (msg, "code"); + } + + if (!_client && code == 1010) { + var msg = "1010 cannot be used."; + throw new ArgumentException (msg, "code"); + } + + closeAsync (code, String.Empty); + } + + /// + /// Closes the connection asynchronously with the specified code. + /// + /// + /// + /// This method does not wait for the close to be complete. + /// + /// + /// This method does nothing if the current state of the connection is + /// Closing or Closed. + /// + /// + /// + /// + /// One of the enum values. + /// + /// + /// It represents the status code indicating the reason for the close. + /// + /// + /// + /// + /// is + /// . + /// It cannot be used by clients. + /// + /// + /// -or- + /// + /// + /// is + /// . + /// It cannot be used by servers. + /// + /// + public void CloseAsync (CloseStatusCode code) + { + if (_client && code == CloseStatusCode.ServerError) { + var msg = "ServerError cannot be used."; + throw new ArgumentException (msg, "code"); + } + + if (!_client && code == CloseStatusCode.MandatoryExtension) { + var msg = "MandatoryExtension cannot be used."; + throw new ArgumentException (msg, "code"); + } + + closeAsync ((ushort) code, String.Empty); + } + + /// + /// Closes the connection asynchronously with the specified code and reason. + /// + /// + /// + /// This method does not wait for the close to be complete. + /// + /// + /// This method does nothing if the current state of the connection is + /// Closing or Closed. + /// + /// + /// + /// + /// A that represents the status code indicating + /// the reason for the close. + /// + /// + /// The status codes are defined in + /// + /// Section 7.4 of RFC 6455. + /// + /// + /// + /// + /// A that represents the reason for the close. + /// + /// + /// The size must be 123 bytes or less in UTF-8. + /// + /// + /// + /// + /// is less than 1000 or greater than 4999. + /// + /// + /// -or- + /// + /// + /// The size of is greater than 123 bytes. + /// + /// + /// + /// + /// is 1011 (server error). + /// It cannot be used by clients. + /// + /// + /// -or- + /// + /// + /// is 1010 (mandatory extension). + /// It cannot be used by servers. + /// + /// + /// -or- + /// + /// + /// is 1005 (no status) and there is reason. + /// + /// + /// -or- + /// + /// + /// could not be UTF-8-encoded. + /// + /// + public void CloseAsync (ushort code, string reason) + { + if (!code.IsCloseStatusCode ()) { + var msg = "Less than 1000 or greater than 4999."; + throw new ArgumentOutOfRangeException ("code", msg); + } + + if (_client && code == 1011) { + var msg = "1011 cannot be used."; + throw new ArgumentException (msg, "code"); + } + + if (!_client && code == 1010) { + var msg = "1010 cannot be used."; + throw new ArgumentException (msg, "code"); + } + + if (reason.IsNullOrEmpty ()) { + closeAsync (code, String.Empty); + return; + } + + if (code == 1005) { + var msg = "1005 cannot be used."; + throw new ArgumentException (msg, "code"); + } + + byte[] bytes; + if (!reason.TryGetUTF8EncodedBytes (out bytes)) { + var msg = "It could not be UTF-8-encoded."; + throw new ArgumentException (msg, "reason"); + } + + if (bytes.Length > 123) { + var msg = "Its size is greater than 123 bytes."; + throw new ArgumentOutOfRangeException ("reason", msg); + } + + closeAsync (code, reason); + } + + /// + /// Closes the connection asynchronously with the specified code and reason. + /// + /// + /// + /// This method does not wait for the close to be complete. + /// + /// + /// This method does nothing if the current state of the connection is + /// Closing or Closed. + /// + /// + /// + /// + /// One of the enum values. + /// + /// + /// It represents the status code indicating the reason for the close. + /// + /// + /// + /// + /// A that represents the reason for the close. + /// + /// + /// The size must be 123 bytes or less in UTF-8. + /// + /// + /// + /// + /// is + /// . + /// It cannot be used by clients. + /// + /// + /// -or- + /// + /// + /// is + /// . + /// It cannot be used by servers. + /// + /// + /// -or- + /// + /// + /// is + /// and there is reason. + /// + /// + /// -or- + /// + /// + /// could not be UTF-8-encoded. + /// + /// + /// + /// The size of is greater than 123 bytes. + /// + public void CloseAsync (CloseStatusCode code, string reason) + { + if (_client && code == CloseStatusCode.ServerError) { + var msg = "ServerError cannot be used."; + throw new ArgumentException (msg, "code"); + } + + if (!_client && code == CloseStatusCode.MandatoryExtension) { + var msg = "MandatoryExtension cannot be used."; + throw new ArgumentException (msg, "code"); + } + + if (reason.IsNullOrEmpty ()) { + closeAsync ((ushort) code, String.Empty); + return; + } + + if (code == CloseStatusCode.NoStatus) { + var msg = "NoStatus cannot be used."; + throw new ArgumentException (msg, "code"); + } + + byte[] bytes; + if (!reason.TryGetUTF8EncodedBytes (out bytes)) { + var msg = "It could not be UTF-8-encoded."; + throw new ArgumentException (msg, "reason"); + } + + if (bytes.Length > 123) { + var msg = "Its size is greater than 123 bytes."; + throw new ArgumentOutOfRangeException ("reason", msg); + } + + closeAsync ((ushort) code, reason); + } + + /// + /// Establishes a connection. + /// + /// + /// This method does nothing if the connection has already been established. + /// + /// + /// + /// This instance is not a client. + /// + /// + /// -or- + /// + /// + /// The close process is in progress. + /// + /// + /// -or- + /// + /// + /// A series of reconnecting has failed. + /// + /// + public void Connect () + { + if (!_client) { + var msg = "This instance is not a client."; + throw new InvalidOperationException (msg); + } + + if (_readyState == WebSocketState.Closing) { + var msg = "The close process is in progress."; + throw new InvalidOperationException (msg); + } + + if (_retryCountForConnect > _maxRetryCountForConnect) { + var msg = "A series of reconnecting has failed."; + throw new InvalidOperationException (msg); + } + + if (connect ()) + open (); + } + + /// + /// Establishes a connection asynchronously. + /// + /// + /// + /// This method does not wait for the connect process to be complete. + /// + /// + /// This method does nothing if the connection has already been + /// established. + /// + /// + /// + /// + /// This instance is not a client. + /// + /// + /// -or- + /// + /// + /// The close process is in progress. + /// + /// + /// -or- + /// + /// + /// A series of reconnecting has failed. + /// + /// + public void ConnectAsync () + { + if (!_client) { + var msg = "This instance is not a client."; + throw new InvalidOperationException (msg); + } + + if (_readyState == WebSocketState.Closing) { + var msg = "The close process is in progress."; + throw new InvalidOperationException (msg); + } + + if (_retryCountForConnect > _maxRetryCountForConnect) { + var msg = "A series of reconnecting has failed."; + throw new InvalidOperationException (msg); + } + + Func connector = connect; + connector.BeginInvoke ( + ar => { + if (connector.EndInvoke (ar)) + open (); + }, + null + ); + } + + /// + /// Sends a ping using the WebSocket connection. + /// + /// + /// true if the send has done with no error and a pong has been + /// received within a time; otherwise, false. + /// + public bool Ping () + { + return ping (EmptyBytes); + } + + /// + /// Sends a ping with using the WebSocket + /// connection. + /// + /// + /// true if the send has done with no error and a pong has been + /// received within a time; otherwise, false. + /// + /// + /// + /// A that represents the message to send. + /// + /// + /// The size must be 125 bytes or less in UTF-8. + /// + /// + /// + /// could not be UTF-8-encoded. + /// + /// + /// The size of is greater than 125 bytes. + /// + public bool Ping (string message) + { + if (message.IsNullOrEmpty ()) + return ping (EmptyBytes); + + byte[] bytes; + if (!message.TryGetUTF8EncodedBytes (out bytes)) { + var msg = "It could not be UTF-8-encoded."; + throw new ArgumentException (msg, "message"); + } + + if (bytes.Length > 125) { + var msg = "Its size is greater than 125 bytes."; + throw new ArgumentOutOfRangeException ("message", msg); + } + + return ping (bytes); + } + + /// + /// Sends the specified data using the WebSocket connection. + /// + /// + /// An array of that represents the binary data to send. + /// + /// + /// The current state of the connection is not Open. + /// + /// + /// is . + /// + public void Send (byte[] data) + { + if (_readyState != WebSocketState.Open) { + var msg = "The current state of the connection is not Open."; + throw new InvalidOperationException (msg); + } + + if (data == null) + throw new ArgumentNullException ("data"); + + send (Opcode.Binary, new MemoryStream (data)); + } + + /// + /// Sends the specified file using the WebSocket connection. + /// + /// + /// + /// A that specifies the file to send. + /// + /// + /// The file is sent as the binary data. + /// + /// + /// + /// The current state of the connection is not Open. + /// + /// + /// is . + /// + /// + /// + /// The file does not exist. + /// + /// + /// -or- + /// + /// + /// The file could not be opened. + /// + /// + public void Send (FileInfo fileInfo) + { + if (_readyState != WebSocketState.Open) { + var msg = "The current state of the connection is not Open."; + throw new InvalidOperationException (msg); + } + + if (fileInfo == null) + throw new ArgumentNullException ("fileInfo"); + + if (!fileInfo.Exists) { + var msg = "The file does not exist."; + throw new ArgumentException (msg, "fileInfo"); + } + + FileStream stream; + if (!fileInfo.TryOpenRead (out stream)) { + var msg = "The file could not be opened."; + throw new ArgumentException (msg, "fileInfo"); + } + + send (Opcode.Binary, stream); + } + + /// + /// Sends the specified data using the WebSocket connection. + /// + /// + /// A that represents the text data to send. + /// + /// + /// The current state of the connection is not Open. + /// + /// + /// is . + /// + /// + /// could not be UTF-8-encoded. + /// + public void Send (string data) + { + if (_readyState != WebSocketState.Open) { + var msg = "The current state of the connection is not Open."; + throw new InvalidOperationException (msg); + } + + if (data == null) + throw new ArgumentNullException ("data"); + + byte[] bytes; + if (!data.TryGetUTF8EncodedBytes (out bytes)) { + var msg = "It could not be UTF-8-encoded."; + throw new ArgumentException (msg, "data"); + } + + send (Opcode.Text, new MemoryStream (bytes)); + } + + /// + /// Sends the data from the specified stream using the WebSocket connection. + /// + /// + /// + /// A instance from which to read the data to send. + /// + /// + /// The data is sent as the binary data. + /// + /// + /// + /// An that specifies the number of bytes to send. + /// + /// + /// The current state of the connection is not Open. + /// + /// + /// is . + /// + /// + /// + /// cannot be read. + /// + /// + /// -or- + /// + /// + /// is less than 1. + /// + /// + /// -or- + /// + /// + /// No data could be read from . + /// + /// + public void Send (Stream stream, int length) + { + if (_readyState != WebSocketState.Open) { + var msg = "The current state of the connection is not Open."; + throw new InvalidOperationException (msg); + } + + if (stream == null) + throw new ArgumentNullException ("stream"); + + if (!stream.CanRead) { + var msg = "It cannot be read."; + throw new ArgumentException (msg, "stream"); + } + + if (length < 1) { + var msg = "Less than 1."; + throw new ArgumentException (msg, "length"); + } + + var bytes = stream.ReadBytes (length); + + var len = bytes.Length; + if (len == 0) { + var msg = "No data could be read from it."; + throw new ArgumentException (msg, "stream"); + } + + if (len < length) { + _logger.Warn ( + String.Format ( + "Only {0} byte(s) of data could be read from the stream.", + len + ) + ); + } + + send (Opcode.Binary, new MemoryStream (bytes)); + } + + /// + /// Sends the specified data asynchronously using the WebSocket connection. + /// + /// + /// This method does not wait for the send to be complete. + /// + /// + /// An array of that represents the binary data to send. + /// + /// + /// + /// An Action<bool> delegate or + /// if not needed. + /// + /// + /// The delegate invokes the method called when the send is complete. + /// + /// + /// true is passed to the method if the send has done with + /// no error; otherwise, false. + /// + /// + /// + /// The current state of the connection is not Open. + /// + /// + /// is . + /// + public void SendAsync (byte[] data, Action completed) + { + if (_readyState != WebSocketState.Open) { + var msg = "The current state of the connection is not Open."; + throw new InvalidOperationException (msg); + } + + if (data == null) + throw new ArgumentNullException ("data"); + + sendAsync (Opcode.Binary, new MemoryStream (data), completed); + } + + /// + /// Sends the specified file asynchronously using the WebSocket connection. + /// + /// + /// This method does not wait for the send to be complete. + /// + /// + /// + /// A that specifies the file to send. + /// + /// + /// The file is sent as the binary data. + /// + /// + /// + /// + /// An Action<bool> delegate or + /// if not needed. + /// + /// + /// The delegate invokes the method called when the send is complete. + /// + /// + /// true is passed to the method if the send has done with + /// no error; otherwise, false. + /// + /// + /// + /// The current state of the connection is not Open. + /// + /// + /// is . + /// + /// + /// + /// The file does not exist. + /// + /// + /// -or- + /// + /// + /// The file could not be opened. + /// + /// + public void SendAsync (FileInfo fileInfo, Action completed) + { + if (_readyState != WebSocketState.Open) { + var msg = "The current state of the connection is not Open."; + throw new InvalidOperationException (msg); + } + + if (fileInfo == null) + throw new ArgumentNullException ("fileInfo"); + + if (!fileInfo.Exists) { + var msg = "The file does not exist."; + throw new ArgumentException (msg, "fileInfo"); + } + + FileStream stream; + if (!fileInfo.TryOpenRead (out stream)) { + var msg = "The file could not be opened."; + throw new ArgumentException (msg, "fileInfo"); + } + + sendAsync (Opcode.Binary, stream, completed); + } + + /// + /// Sends the specified data asynchronously using the WebSocket connection. + /// + /// + /// This method does not wait for the send to be complete. + /// + /// + /// A that represents the text data to send. + /// + /// + /// + /// An Action<bool> delegate or + /// if not needed. + /// + /// + /// The delegate invokes the method called when the send is complete. + /// + /// + /// true is passed to the method if the send has done with + /// no error; otherwise, false. + /// + /// + /// + /// The current state of the connection is not Open. + /// + /// + /// is . + /// + /// + /// could not be UTF-8-encoded. + /// + public void SendAsync (string data, Action completed) + { + if (_readyState != WebSocketState.Open) { + var msg = "The current state of the connection is not Open."; + throw new InvalidOperationException (msg); + } + + if (data == null) + throw new ArgumentNullException ("data"); + + byte[] bytes; + if (!data.TryGetUTF8EncodedBytes (out bytes)) { + var msg = "It could not be UTF-8-encoded."; + throw new ArgumentException (msg, "data"); + } + + sendAsync (Opcode.Text, new MemoryStream (bytes), completed); + } + + /// + /// Sends the data from the specified stream asynchronously using + /// the WebSocket connection. + /// + /// + /// This method does not wait for the send to be complete. + /// + /// + /// + /// A instance from which to read the data to send. + /// + /// + /// The data is sent as the binary data. + /// + /// + /// + /// An that specifies the number of bytes to send. + /// + /// + /// + /// An Action<bool> delegate or + /// if not needed. + /// + /// + /// The delegate invokes the method called when the send is complete. + /// + /// + /// true is passed to the method if the send has done with + /// no error; otherwise, false. + /// + /// + /// + /// The current state of the connection is not Open. + /// + /// + /// is . + /// + /// + /// + /// cannot be read. + /// + /// + /// -or- + /// + /// + /// is less than 1. + /// + /// + /// -or- + /// + /// + /// No data could be read from . + /// + /// + public void SendAsync (Stream stream, int length, Action completed) + { + if (_readyState != WebSocketState.Open) { + var msg = "The current state of the connection is not Open."; + throw new InvalidOperationException (msg); + } + + if (stream == null) + throw new ArgumentNullException ("stream"); + + if (!stream.CanRead) { + var msg = "It cannot be read."; + throw new ArgumentException (msg, "stream"); + } + + if (length < 1) { + var msg = "Less than 1."; + throw new ArgumentException (msg, "length"); + } + + var bytes = stream.ReadBytes (length); + + var len = bytes.Length; + if (len == 0) { + var msg = "No data could be read from it."; + throw new ArgumentException (msg, "stream"); + } + + if (len < length) { + _logger.Warn ( + String.Format ( + "Only {0} byte(s) of data could be read from the stream.", + len + ) + ); + } + + sendAsync (Opcode.Binary, new MemoryStream (bytes), completed); + } + + /// + /// Sets an HTTP cookie to send with the handshake request. + /// + /// + /// This method does nothing if the connection has already been + /// established or it is closing. + /// + /// + /// A that represents the cookie to send. + /// + /// + /// This instance is not a client. + /// + /// + /// is . + /// + public void SetCookie (Cookie cookie) + { + string msg = null; + + if (!_client) { + msg = "This instance is not a client."; + throw new InvalidOperationException (msg); + } + + if (cookie == null) + throw new ArgumentNullException ("cookie"); + + if (!canSet (out msg)) { + _logger.Warn (msg); + return; + } + + lock (_forState) { + if (!canSet (out msg)) { + _logger.Warn (msg); + return; + } + + lock (_cookies.SyncRoot) + _cookies.SetOrRemove (cookie); + } + } + + /// + /// Sets the credentials for the HTTP authentication (Basic/Digest). + /// + /// + /// This method does nothing if the connection has already been + /// established or it is closing. + /// + /// + /// + /// A that represents the username associated with + /// the credentials. + /// + /// + /// or an empty string if initializes + /// the credentials. + /// + /// + /// + /// + /// A that represents the password for the username + /// associated with the credentials. + /// + /// + /// or an empty string if not necessary. + /// + /// + /// + /// true if sends the credentials for the Basic authentication in + /// advance with the first handshake request; otherwise, false. + /// + /// + /// This instance is not a client. + /// + /// + /// + /// contains an invalid character. + /// + /// + /// -or- + /// + /// + /// contains an invalid character. + /// + /// + public void SetCredentials (string username, string password, bool preAuth) + { + string msg = null; + + if (!_client) { + msg = "This instance is not a client."; + throw new InvalidOperationException (msg); + } + + if (!username.IsNullOrEmpty ()) { + if (username.Contains (':') || !username.IsText ()) { + msg = "It contains an invalid character."; + throw new ArgumentException (msg, "username"); + } + } + + if (!password.IsNullOrEmpty ()) { + if (!password.IsText ()) { + msg = "It contains an invalid character."; + throw new ArgumentException (msg, "password"); + } + } + + if (!canSet (out msg)) { + _logger.Warn (msg); + return; + } + + lock (_forState) { + if (!canSet (out msg)) { + _logger.Warn (msg); + return; + } + + if (username.IsNullOrEmpty ()) { + _credentials = null; + _preAuth = false; + + return; + } + + _credentials = new NetworkCredential ( + username, password, _uri.PathAndQuery + ); + + _preAuth = preAuth; + } + } + + /// + /// Sets the URL of the HTTP proxy server through which to connect and + /// the credentials for the HTTP proxy authentication (Basic/Digest). + /// + /// + /// This method does nothing if the connection has already been + /// established or it is closing. + /// + /// + /// + /// A that represents the URL of the proxy server + /// through which to connect. + /// + /// + /// The syntax is http://<host>[:<port>]. + /// + /// + /// or an empty string if initializes the URL and + /// the credentials. + /// + /// + /// + /// + /// A that represents the username associated with + /// the credentials. + /// + /// + /// or an empty string if the credentials are not + /// necessary. + /// + /// + /// + /// + /// A that represents the password for the username + /// associated with the credentials. + /// + /// + /// or an empty string if not necessary. + /// + /// + /// + /// This instance is not a client. + /// + /// + /// + /// is not an absolute URI string. + /// + /// + /// -or- + /// + /// + /// The scheme of is not http. + /// + /// + /// -or- + /// + /// + /// includes the path segments. + /// + /// + /// -or- + /// + /// + /// contains an invalid character. + /// + /// + /// -or- + /// + /// + /// contains an invalid character. + /// + /// + public void SetProxy (string url, string username, string password) + { + string msg = null; + + if (!_client) { + msg = "This instance is not a client."; + throw new InvalidOperationException (msg); + } + + Uri uri = null; + + if (!url.IsNullOrEmpty ()) { + if (!Uri.TryCreate (url, UriKind.Absolute, out uri)) { + msg = "Not an absolute URI string."; + throw new ArgumentException (msg, "url"); + } + + if (uri.Scheme != "http") { + msg = "The scheme part is not http."; + throw new ArgumentException (msg, "url"); + } + + if (uri.Segments.Length > 1) { + msg = "It includes the path segments."; + throw new ArgumentException (msg, "url"); + } + } + + if (!username.IsNullOrEmpty ()) { + if (username.Contains (':') || !username.IsText ()) { + msg = "It contains an invalid character."; + throw new ArgumentException (msg, "username"); + } + } + + if (!password.IsNullOrEmpty ()) { + if (!password.IsText ()) { + msg = "It contains an invalid character."; + throw new ArgumentException (msg, "password"); + } + } + + if (!canSet (out msg)) { + _logger.Warn (msg); + return; + } + + lock (_forState) { + if (!canSet (out msg)) { + _logger.Warn (msg); + return; + } + + if (url.IsNullOrEmpty ()) { + _proxyUri = null; + _proxyCredentials = null; + + return; + } + + _proxyUri = uri; + _proxyCredentials = !username.IsNullOrEmpty () + ? new NetworkCredential ( + username, + password, + String.Format ( + "{0}:{1}", _uri.DnsSafeHost, _uri.Port + ) + ) + : null; + } + } + + #endregion + + #region Explicit Interface Implementations + + /// + /// Closes the connection and releases all associated resources. + /// + /// + /// + /// This method closes the connection with close status 1001 (going away). + /// + /// + /// And this method does nothing if the current state of the connection is + /// Closing or Closed. + /// + /// + void IDisposable.Dispose () + { + close (1001, String.Empty); + } + + #endregion + } +} diff --git a/websocket-sharp/WebSocketException.cs b/websocket-sharp/WebSocketException.cs new file mode 100644 index 0000000..81d7c80 --- /dev/null +++ b/websocket-sharp/WebSocketException.cs @@ -0,0 +1,109 @@ +#region License +/* + * WebSocketException.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 +{ + /// + /// The exception that is thrown when a fatal error occurs in + /// the WebSocket communication. + /// + public class WebSocketException : Exception + { + #region Private Fields + + private CloseStatusCode _code; + + #endregion + + #region Internal Constructors + + internal WebSocketException () + : this (CloseStatusCode.Abnormal, null, null) + { + } + + internal WebSocketException (Exception innerException) + : this (CloseStatusCode.Abnormal, null, innerException) + { + } + + internal WebSocketException (string message) + : this (CloseStatusCode.Abnormal, message, null) + { + } + + internal WebSocketException (CloseStatusCode code) + : this (code, null, null) + { + } + + internal WebSocketException (string message, Exception innerException) + : this (CloseStatusCode.Abnormal, message, innerException) + { + } + + internal WebSocketException (CloseStatusCode code, Exception innerException) + : this (code, null, innerException) + { + } + + internal WebSocketException (CloseStatusCode code, string message) + : this (code, message, null) + { + } + + internal WebSocketException ( + CloseStatusCode code, string message, Exception innerException + ) + : base (message ?? code.GetMessage (), innerException) + { + _code = code; + } + + #endregion + + #region Public Properties + + /// + /// Gets the status code indicating the cause of the exception. + /// + /// + /// One of the enum values that represents + /// the status code indicating the cause of the exception. + /// + public CloseStatusCode Code { + get { + return _code; + } + } + + #endregion + } +} diff --git a/websocket-sharp/WebSocketFrame.cs b/websocket-sharp/WebSocketFrame.cs new file mode 100644 index 0000000..3be6300 --- /dev/null +++ b/websocket-sharp/WebSocketFrame.cs @@ -0,0 +1,797 @@ +#region License +/* + * WebSocketFrame.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: + * - Chris Swiedler + */ +#endregion + +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace WebSocketSharp +{ + internal class WebSocketFrame : IEnumerable + { + #region Private Fields + + private byte[] _extPayloadLength; + private Fin _fin; + private Mask _mask; + private byte[] _maskingKey; + private Opcode _opcode; + private PayloadData _payloadData; + private byte _payloadLength; + private Rsv _rsv1; + private Rsv _rsv2; + private Rsv _rsv3; + + #endregion + + #region Internal Fields + + /// + /// Represents the ping frame without the payload data as an array of . + /// + /// + /// The value of this field is created from a non masked frame, so it can only be used to + /// send a ping from a server. + /// + internal static readonly byte[] EmptyPingBytes; + + #endregion + + #region Static Constructor + + static WebSocketFrame () + { + EmptyPingBytes = CreatePingFrame (false).ToArray (); + } + + #endregion + + #region Private Constructors + + private WebSocketFrame () + { + } + + #endregion + + #region Internal Constructors + + internal WebSocketFrame (Opcode opcode, PayloadData payloadData, bool mask) + : this (Fin.Final, opcode, payloadData, false, mask) + { + } + + internal WebSocketFrame (Fin fin, Opcode opcode, byte[] data, bool compressed, bool mask) + : this (fin, opcode, new PayloadData (data), compressed, mask) + { + } + + internal WebSocketFrame ( + Fin fin, Opcode opcode, PayloadData payloadData, bool compressed, bool mask) + { + _fin = fin; + _rsv1 = opcode.IsData () && compressed ? Rsv.On : Rsv.Off; + _rsv2 = Rsv.Off; + _rsv3 = Rsv.Off; + _opcode = opcode; + + var len = payloadData.Length; + if (len < 126) { + _payloadLength = (byte) len; + _extPayloadLength = WebSocket.EmptyBytes; + } + else if (len < 0x010000) { + _payloadLength = (byte) 126; + _extPayloadLength = ((ushort) len).InternalToByteArray (ByteOrder.Big); + } + else { + _payloadLength = (byte) 127; + _extPayloadLength = len.InternalToByteArray (ByteOrder.Big); + } + + if (mask) { + _mask = Mask.On; + _maskingKey = createMaskingKey (); + payloadData.Mask (_maskingKey); + } + else { + _mask = Mask.Off; + _maskingKey = WebSocket.EmptyBytes; + } + + _payloadData = payloadData; + } + + #endregion + + #region Internal Properties + + internal int ExtendedPayloadLengthCount { + get { + return _payloadLength < 126 ? 0 : (_payloadLength == 126 ? 2 : 8); + } + } + + internal ulong FullPayloadLength { + get { + return _payloadLength < 126 + ? _payloadLength + : _payloadLength == 126 + ? _extPayloadLength.ToUInt16 (ByteOrder.Big) + : _extPayloadLength.ToUInt64 (ByteOrder.Big); + } + } + + #endregion + + #region Public Properties + + public byte[] ExtendedPayloadLength { + get { + return _extPayloadLength; + } + } + + public Fin Fin { + get { + return _fin; + } + } + + public bool IsBinary { + get { + return _opcode == Opcode.Binary; + } + } + + public bool IsClose { + get { + return _opcode == Opcode.Close; + } + } + + public bool IsCompressed { + get { + return _rsv1 == Rsv.On; + } + } + + public bool IsContinuation { + get { + return _opcode == Opcode.Cont; + } + } + + public bool IsControl { + get { + return _opcode >= Opcode.Close; + } + } + + public bool IsData { + get { + return _opcode == Opcode.Text || _opcode == Opcode.Binary; + } + } + + public bool IsFinal { + get { + return _fin == Fin.Final; + } + } + + public bool IsFragment { + get { + return _fin == Fin.More || _opcode == Opcode.Cont; + } + } + + public bool IsMasked { + get { + return _mask == Mask.On; + } + } + + public bool IsPing { + get { + return _opcode == Opcode.Ping; + } + } + + public bool IsPong { + get { + return _opcode == Opcode.Pong; + } + } + + public bool IsText { + get { + return _opcode == Opcode.Text; + } + } + + public ulong Length { + get { + return 2 + (ulong) (_extPayloadLength.Length + _maskingKey.Length) + _payloadData.Length; + } + } + + public Mask Mask { + get { + return _mask; + } + } + + public byte[] MaskingKey { + get { + return _maskingKey; + } + } + + public Opcode Opcode { + get { + return _opcode; + } + } + + public PayloadData PayloadData { + get { + return _payloadData; + } + } + + public byte PayloadLength { + get { + return _payloadLength; + } + } + + public Rsv Rsv1 { + get { + return _rsv1; + } + } + + public Rsv Rsv2 { + get { + return _rsv2; + } + } + + public Rsv Rsv3 { + get { + return _rsv3; + } + } + + #endregion + + #region Private Methods + + private static byte[] createMaskingKey () + { + var key = new byte[4]; + WebSocket.RandomNumber.GetBytes (key); + + return key; + } + + private static string dump (WebSocketFrame frame) + { + var len = frame.Length; + var cnt = (long) (len / 4); + var rem = (int) (len % 4); + + int cntDigit; + string cntFmt; + if (cnt < 10000) { + cntDigit = 4; + cntFmt = "{0,4}"; + } + else if (cnt < 0x010000) { + cntDigit = 4; + cntFmt = "{0,4:X}"; + } + else if (cnt < 0x0100000000) { + cntDigit = 8; + cntFmt = "{0,8:X}"; + } + else { + cntDigit = 16; + cntFmt = "{0,16:X}"; + } + + var spFmt = String.Format ("{{0,{0}}}", cntDigit); + var headerFmt = String.Format (@" +{0} 01234567 89ABCDEF 01234567 89ABCDEF +{0}+--------+--------+--------+--------+\n", spFmt); + var lineFmt = String.Format ("{0}|{{1,8}} {{2,8}} {{3,8}} {{4,8}}|\n", cntFmt); + var footerFmt = String.Format ("{0}+--------+--------+--------+--------+", spFmt); + + var output = new StringBuilder (64); + Func> linePrinter = () => { + long lineCnt = 0; + return (arg1, arg2, arg3, arg4) => + output.AppendFormat (lineFmt, ++lineCnt, arg1, arg2, arg3, arg4); + }; + var printLine = linePrinter (); + + output.AppendFormat (headerFmt, String.Empty); + + var bytes = frame.ToArray (); + for (long i = 0; i <= cnt; i++) { + var j = i * 4; + if (i < cnt) { + printLine ( + Convert.ToString (bytes[j], 2).PadLeft (8, '0'), + Convert.ToString (bytes[j + 1], 2).PadLeft (8, '0'), + Convert.ToString (bytes[j + 2], 2).PadLeft (8, '0'), + Convert.ToString (bytes[j + 3], 2).PadLeft (8, '0')); + + continue; + } + + if (rem > 0) + printLine ( + Convert.ToString (bytes[j], 2).PadLeft (8, '0'), + rem >= 2 ? Convert.ToString (bytes[j + 1], 2).PadLeft (8, '0') : String.Empty, + rem == 3 ? Convert.ToString (bytes[j + 2], 2).PadLeft (8, '0') : String.Empty, + String.Empty); + } + + output.AppendFormat (footerFmt, String.Empty); + return output.ToString (); + } + + private static string print (WebSocketFrame frame) + { + // Payload Length + var payloadLen = frame._payloadLength; + + // Extended Payload Length + var extPayloadLen = payloadLen > 125 ? frame.FullPayloadLength.ToString () : String.Empty; + + // Masking Key + var maskingKey = BitConverter.ToString (frame._maskingKey); + + // Payload Data + var payload = payloadLen == 0 + ? String.Empty + : payloadLen > 125 + ? "---" + : frame.IsText && !(frame.IsFragment || frame.IsMasked || frame.IsCompressed) + ? frame._payloadData.ApplicationData.UTF8Decode () + : frame._payloadData.ToString (); + + var fmt = @" + FIN: {0} + RSV1: {1} + RSV2: {2} + RSV3: {3} + Opcode: {4} + MASK: {5} + Payload Length: {6} +Extended Payload Length: {7} + Masking Key: {8} + Payload Data: {9}"; + + return String.Format ( + fmt, + frame._fin, + frame._rsv1, + frame._rsv2, + frame._rsv3, + frame._opcode, + frame._mask, + payloadLen, + extPayloadLen, + maskingKey, + payload); + } + + private static WebSocketFrame processHeader (byte[] header) + { + if (header.Length != 2) + throw new WebSocketException ("The header of a frame cannot be read from the stream."); + + // FIN + var fin = (header[0] & 0x80) == 0x80 ? Fin.Final : Fin.More; + + // RSV1 + var rsv1 = (header[0] & 0x40) == 0x40 ? Rsv.On : Rsv.Off; + + // RSV2 + var rsv2 = (header[0] & 0x20) == 0x20 ? Rsv.On : Rsv.Off; + + // RSV3 + var rsv3 = (header[0] & 0x10) == 0x10 ? Rsv.On : Rsv.Off; + + // Opcode + var opcode = (byte) (header[0] & 0x0f); + + // MASK + var mask = (header[1] & 0x80) == 0x80 ? Mask.On : Mask.Off; + + // Payload Length + var payloadLen = (byte) (header[1] & 0x7f); + + var err = !opcode.IsSupported () + ? "An unsupported opcode." + : !opcode.IsData () && rsv1 == Rsv.On + ? "A non data frame is compressed." + : opcode.IsControl () && fin == Fin.More + ? "A control frame is fragmented." + : opcode.IsControl () && payloadLen > 125 + ? "A control frame has a long payload length." + : null; + + if (err != null) + throw new WebSocketException (CloseStatusCode.ProtocolError, err); + + var frame = new WebSocketFrame (); + frame._fin = fin; + frame._rsv1 = rsv1; + frame._rsv2 = rsv2; + frame._rsv3 = rsv3; + frame._opcode = (Opcode) opcode; + frame._mask = mask; + frame._payloadLength = payloadLen; + + return frame; + } + + private static WebSocketFrame readExtendedPayloadLength (Stream stream, WebSocketFrame frame) + { + var len = frame.ExtendedPayloadLengthCount; + if (len == 0) { + frame._extPayloadLength = WebSocket.EmptyBytes; + return frame; + } + + var bytes = stream.ReadBytes (len); + if (bytes.Length != len) + throw new WebSocketException ( + "The extended payload length of a frame cannot be read from the stream."); + + frame._extPayloadLength = bytes; + return frame; + } + + private static void readExtendedPayloadLengthAsync ( + Stream stream, + WebSocketFrame frame, + Action completed, + Action error) + { + var len = frame.ExtendedPayloadLengthCount; + if (len == 0) { + frame._extPayloadLength = WebSocket.EmptyBytes; + completed (frame); + + return; + } + + stream.ReadBytesAsync ( + len, + bytes => { + if (bytes.Length != len) + throw new WebSocketException ( + "The extended payload length of a frame cannot be read from the stream."); + + frame._extPayloadLength = bytes; + completed (frame); + }, + error); + } + + private static WebSocketFrame readHeader (Stream stream) + { + return processHeader (stream.ReadBytes (2)); + } + + private static void readHeaderAsync ( + Stream stream, Action completed, Action error) + { + stream.ReadBytesAsync (2, bytes => completed (processHeader (bytes)), error); + } + + private static WebSocketFrame readMaskingKey (Stream stream, WebSocketFrame frame) + { + var len = frame.IsMasked ? 4 : 0; + if (len == 0) { + frame._maskingKey = WebSocket.EmptyBytes; + return frame; + } + + var bytes = stream.ReadBytes (len); + if (bytes.Length != len) + throw new WebSocketException ("The masking key of a frame cannot be read from the stream."); + + frame._maskingKey = bytes; + return frame; + } + + private static void readMaskingKeyAsync ( + Stream stream, + WebSocketFrame frame, + Action completed, + Action error) + { + var len = frame.IsMasked ? 4 : 0; + if (len == 0) { + frame._maskingKey = WebSocket.EmptyBytes; + completed (frame); + + return; + } + + stream.ReadBytesAsync ( + len, + bytes => { + if (bytes.Length != len) + throw new WebSocketException ( + "The masking key of a frame cannot be read from the stream."); + + frame._maskingKey = bytes; + completed (frame); + }, + error); + } + + private static WebSocketFrame readPayloadData (Stream stream, WebSocketFrame frame) + { + var len = frame.FullPayloadLength; + if (len == 0) { + frame._payloadData = PayloadData.Empty; + return frame; + } + + if (len > PayloadData.MaxLength) + throw new WebSocketException (CloseStatusCode.TooBig, "A frame has a long payload length."); + + var llen = (long) len; + var bytes = frame._payloadLength < 127 + ? stream.ReadBytes ((int) len) + : stream.ReadBytes (llen, 1024); + + if (bytes.LongLength != llen) + throw new WebSocketException ( + "The payload data of a frame cannot be read from the stream."); + + frame._payloadData = new PayloadData (bytes, llen); + return frame; + } + + private static void readPayloadDataAsync ( + Stream stream, + WebSocketFrame frame, + Action completed, + Action error) + { + var len = frame.FullPayloadLength; + if (len == 0) { + frame._payloadData = PayloadData.Empty; + completed (frame); + + return; + } + + if (len > PayloadData.MaxLength) + throw new WebSocketException (CloseStatusCode.TooBig, "A frame has a long payload length."); + + var llen = (long) len; + Action compl = bytes => { + if (bytes.LongLength != llen) + throw new WebSocketException ( + "The payload data of a frame cannot be read from the stream."); + + frame._payloadData = new PayloadData (bytes, llen); + completed (frame); + }; + + if (frame._payloadLength < 127) { + stream.ReadBytesAsync ((int) len, compl, error); + return; + } + + stream.ReadBytesAsync (llen, 1024, compl, error); + } + + #endregion + + #region Internal Methods + + internal static WebSocketFrame CreateCloseFrame ( + PayloadData payloadData, bool mask + ) + { + return new WebSocketFrame ( + Fin.Final, Opcode.Close, payloadData, false, mask + ); + } + + internal static WebSocketFrame CreatePingFrame (bool mask) + { + return new WebSocketFrame ( + Fin.Final, Opcode.Ping, PayloadData.Empty, false, mask + ); + } + + internal static WebSocketFrame CreatePingFrame (byte[] data, bool mask) + { + return new WebSocketFrame ( + Fin.Final, Opcode.Ping, new PayloadData (data), false, mask + ); + } + + internal static WebSocketFrame CreatePongFrame ( + PayloadData payloadData, bool mask + ) + { + return new WebSocketFrame ( + Fin.Final, Opcode.Pong, payloadData, false, mask + ); + } + + internal static WebSocketFrame ReadFrame (Stream stream, bool unmask) + { + var frame = readHeader (stream); + readExtendedPayloadLength (stream, frame); + readMaskingKey (stream, frame); + readPayloadData (stream, frame); + + if (unmask) + frame.Unmask (); + + return frame; + } + + internal static void ReadFrameAsync ( + Stream stream, + bool unmask, + Action completed, + Action error + ) + { + readHeaderAsync ( + stream, + frame => + readExtendedPayloadLengthAsync ( + stream, + frame, + frame1 => + readMaskingKeyAsync ( + stream, + frame1, + frame2 => + readPayloadDataAsync ( + stream, + frame2, + frame3 => { + if (unmask) + frame3.Unmask (); + + completed (frame3); + }, + error + ), + error + ), + error + ), + error + ); + } + + internal void Unmask () + { + if (_mask == Mask.Off) + return; + + _mask = Mask.Off; + _payloadData.Mask (_maskingKey); + _maskingKey = WebSocket.EmptyBytes; + } + + #endregion + + #region Public Methods + + public IEnumerator GetEnumerator () + { + foreach (var b in ToArray ()) + yield return b; + } + + public void Print (bool dumped) + { + Console.WriteLine (dumped ? dump (this) : print (this)); + } + + public string PrintToString (bool dumped) + { + return dumped ? dump (this) : print (this); + } + + public byte[] ToArray () + { + using (var buff = new MemoryStream ()) { + var header = (int) _fin; + header = (header << 1) + (int) _rsv1; + header = (header << 1) + (int) _rsv2; + header = (header << 1) + (int) _rsv3; + header = (header << 4) + (int) _opcode; + header = (header << 1) + (int) _mask; + header = (header << 7) + (int) _payloadLength; + buff.Write (((ushort) header).InternalToByteArray (ByteOrder.Big), 0, 2); + + if (_payloadLength > 125) + buff.Write (_extPayloadLength, 0, _payloadLength == 126 ? 2 : 8); + + if (_mask == Mask.On) + buff.Write (_maskingKey, 0, 4); + + if (_payloadLength > 0) { + var bytes = _payloadData.ToArray (); + if (_payloadLength < 127) + buff.Write (bytes, 0, bytes.Length); + else + buff.WriteBytes (bytes, 1024); + } + + buff.Close (); + return buff.ToArray (); + } + } + + public override string ToString () + { + return BitConverter.ToString (ToArray ()); + } + + #endregion + + #region Explicit Interface Implementations + + IEnumerator IEnumerable.GetEnumerator () + { + return GetEnumerator (); + } + + #endregion + } +} diff --git a/websocket-sharp/WebSocketState.cs b/websocket-sharp/WebSocketState.cs new file mode 100644 index 0000000..2cbcd68 --- /dev/null +++ b/websocket-sharp/WebSocketState.cs @@ -0,0 +1,65 @@ +#region License +/* + * WebSocketState.cs + * + * The MIT License + * + * Copyright (c) 2010-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 +{ + /// + /// Indicates the state of a WebSocket connection. + /// + /// + /// The values of this enumeration are defined in + /// + /// The WebSocket API. + /// + public enum WebSocketState : ushort + { + /// + /// Equivalent to numeric value 0. Indicates that the connection has not + /// yet been established. + /// + Connecting = 0, + /// + /// Equivalent to numeric value 1. Indicates that the connection has + /// been established, and the communication is possible. + /// + Open = 1, + /// + /// Equivalent to numeric value 2. Indicates that the connection is + /// going through the closing handshake, or the close method has + /// been invoked. + /// + Closing = 2, + /// + /// Equivalent to numeric value 3. Indicates that the connection has + /// been closed or could not be established. + /// + Closed = 3 + } +} diff --git a/websocket-sharp/doc/.gitignore b/websocket-sharp/doc/.gitignore new file mode 100644 index 0000000..7b744c3 --- /dev/null +++ b/websocket-sharp/doc/.gitignore @@ -0,0 +1,4 @@ +## Ignore MonoDevelop build results. + +html +mdoc diff --git a/websocket-sharp/doc/doc.sh b/websocket-sharp/doc/doc.sh new file mode 100644 index 0000000..e4f3fa6 --- /dev/null +++ b/websocket-sharp/doc/doc.sh @@ -0,0 +1,31 @@ +#!/bin/sh +# +# @(#) doc.sh ver.0.0.2 2013.01.24 +# +# Usage: +# doc.sh +# +# Description: +# Creating documentation for websocket-sharp. +# +########################################################################### + +SRC_DIR="../bin/Release_Ubuntu" +XML="${SRC_DIR}/websocket-sharp.xml" +DLL="${SRC_DIR}/websocket-sharp.dll" + +DOC_DIR="." +MDOC_DIR="${DOC_DIR}/mdoc" +HTML_DIR="${DOC_DIR}/html" + +createDir() { + if [ ! -d $1 ]; then + mkdir -p $1 + fi +} + +set -e +createDir ${MDOC_DIR} +createDir ${HTML_DIR} +mdoc update --delete -fno-assembly-versions -i ${XML} -o ${MDOC_DIR}/ ${DLL} +mdoc export-html -o ${HTML_DIR}/ ${MDOC_DIR}/ diff --git a/websocket-sharp/websocket-sharp.csproj b/websocket-sharp/websocket-sharp.csproj new file mode 100644 index 0000000..0860c03 --- /dev/null +++ b/websocket-sharp/websocket-sharp.csproj @@ -0,0 +1,149 @@ + + + + Debug + AnyCPU + 9.0.21022 + 2.0 + {B357BAC7-529E-4D81-A0D2-71041B19C8DE} + Library + WebSocketSharp + websocket-sharp + v3.5 + true + websocket-sharp.snk + + + true + full + false + bin\Debug + DEBUG + prompt + 4 + false + + + none + false + bin\Release + prompt + 4 + false + + + true + full + false + bin\Debug_Ubuntu + DEBUG + prompt + 4 + false + + + none + false + bin\Release_Ubuntu + prompt + 4 + false + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/websocket-sharp/websocket-sharp.snk b/websocket-sharp/websocket-sharp.snk new file mode 100644 index 0000000..a2546f3 Binary files /dev/null and b/websocket-sharp/websocket-sharp.snk differ