123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306(*********************************************************************************)(* Stog *)(* *)(* Copyright (C) 2012-2015 INRIA All rights reserved. *)(* Author: Maxence Guesdon, INRIA Saclay *)(* *)(* This program is free software; you can redistribute it and/or modify *)(* it under the terms of the GNU General Public License as *)(* published by the Free Software Foundation, version 3 of the License. *)(* *)(* This program is distributed in the hope that it will be useful, *)(* but WITHOUT ANY WARRANTY; without even the implied warranty of *)(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *)(* GNU General Public License for more details. *)(* *)(* You should have received a copy of the GNU General Public *)(* License along with this program; if not, write to the Free Software *)(* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA *)(* 02111-1307 USA *)(* *)(* As a special exception, you have permission to link this program *)(* with the OCaml compiler and distribute executables, as long as you *)(* follow the requirements of the GNU GPL in regard to all of the *)(* software in the executable aside from the OCaml compiler. *)(* *)(* Contact: Maxence.Guesdon@inria.fr *)(* *)(*********************************************************************************)(** *)openStog.UrlopenConfigopenStog_server_types.TypesopenSessionopenGsmoduleS=Cohttp_lwt_unix.ServermoduleRequest=Cohttp.Requestlet(>>=)=Lwt.bindmoduleXR=Xtmpl.Rewriteletrestart_previous_sessionscfgsessions=List.iter(funsession->tryprerr_endline("restarting "^(session.session_id));Session.start_session?sshkey:cfg.ssh_priv_keysession;Gs.add_sessionsessionsessionswithe->prerr_endline(Printexc.to_stringe))(Session.load_previous_sessionscfg)letadd_loggedgsuser_tokenaccount=gs.logged:=Stog.Types.Str_map.adduser_tokenaccount!(gs.logged)letnew_token()=Session.new_id()lettoken_cookie="STOGMULTILOGINTOKEN"letaction_form_loginapp_url=leturl=List.fold_leftStog.Url.concatapp_urlPage.path_sessionsinStog.Url.to_stringurlletsha256s=Cryptokit.(leth=Hash.sha256()inlett=Hexa.encode()inString.lowercase_ascii(transform_stringt(hash_stringhs)))letrespond_pagepage=letbody=XR.to_stringpageinS.respond_string~status:`OK~body()lethandle_login_postcfggsreqbody=Cohttp_lwt__.Body.to_stringbody>>=funbody->letmoduleF=Page.Form_logininmatchlet(tmpl,form)=F.read_form(Page.param_of_bodybody)intryletaccount=List.find(funacc->acc.login=form.F.login)cfg.accountsinprerr_endline(Printf.sprintf"account found: %s\npasswd=%s"account.loginaccount.passwd);letpwd=sha256form.F.passwordinprerr_endline(Printf.sprintf"sha256(pwd)=%s"pwd);ifpwd=String.lowercase_asciiaccount.passwdthenaccountelseraiseNot_foundwith|Not_found->raise(F.Error(tmpl,["Invalid user/password"]))with|exception(F.Error(tmpl,errors))->leterror_msg=Page.error_block(`Block(List.map(funmsg->XR.node("","div")[XR.cdatamsg])errors))inletcontents=tmpl~error_msg~action:(Page.url_logincfg)()inletpage=Page.pagecfgNone~title:"Login"contentsinrespond_pagepage|account->lettoken=new_token()inadd_loggedgstokenaccount;letcookie=letpath=("/"^(String.concat"/"(Stog.Url.pathcfg.http_url.priv)))inCohttp.Cookie.Set_cookie_hdr.make~expiration:`Session~path~http_only:true(token_cookie,token)inletpage=User.pagecfggsaccountinletbody=XR.to_stringpageinletheaders=let(h,s)=Cohttp.Cookie.Set_cookie_hdr.serializecookieinCohttp.Header.init_withhsinS.respond_string~headers~status:`OK~body()lethandle_login_getcfggsopt_user=matchopt_userwithSomeuser->respond_page(User.pagecfggsuser)|None->letmoduleF=Page.Form_logininletcontents=F.form~action:(Page.url_logincfg)()inletpage=Page.pagecfgNone~title:"Login"contentsinrespond_pagepageletreq_path_from_appcfgreq=letapp_path=Stog.Url.pathcfg.http_url.privinletreq_uri=Cohttp.Request.urireqinletreq_path=Stog_base.Misc.split_string(Uri.pathreq_uri)['/']inletreciter=function|[],p->p|h1::q1,h2::q2whenh1=h2->iter(q1,q2)|_,_->letmsg=Printf.sprintf"bad query path: %S is not under %S"(Uri.to_stringreq_uri)(Stog.Url.to_stringcfg.http_url.priv)infailwithmsginiter(app_path,req_path)letget_opt_usergsreq=leth=Cohttp.Request.headersreqinletcookies=Cohttp.Cookie.Cookie_hdr.extracthintryletc=List.assoctoken_cookiecookiesinSome(Stog.Types.Str_map.findc!(gs.logged))withNot_found->Noneletrequire_usercfgopt_userf=matchopt_userwithNone->leterror=`Msg"You must be connected. Please log in"inrespond_page(Page.pagecfgNone~title:"Error"~error[])|Someuser->fuserlethandle_pathcfggs~http_url~ws_urlsockopt_userreqbody=function|[]->letcontents=Page.Form_login.form~action:(Page.url_logincfg)()inletpage=Page.pagecfgNone~title:"Login"contentsinletbody=XR.to_stringpageinS.respond_string~status:`OK~body()|["styles";s]whens=Stog_server.Preview.default_css->beginmatchcfg.css_filewith|None->Stog_server.Preview.respond_default_css|Somefile->letbody=tryStog_base.Misc.string_of_filefilewith_->""inStog_server.Preview.respond_cssbodyend|pwhenp=Page.path_login&&req.Request.meth=`GET->handle_login_getcfggsopt_user|pwhenp=Page.path_login&&req.Request.meth=`POST->handle_login_postcfggsreqbody|pwhenp=Page.path_sessions&&req.Request.meth=`GET->require_usercfgopt_user(funuser->User.handle_sessions_getcfggsuserreqbody>>=respond_page)|pwhenp=Page.path_sessions&&req.Request.meth=`POST->require_usercfgopt_user(funuser->User.handle_sessions_postcfggsuserreqbody>>=respond_page)|path->matchpathwith|"sessions"::session_id::qwhenreq.Request.meth=`GET->beginmatchStog.Types.Str_map.findsession_id!(gs.sessions)with|exceptionNot_found->letbody=Printf.sprintf"Session %S not found"session_idinS.respond_error~status:`Not_found~body()|session->matchqwith|["styles";s]whens=Stog_server.Preview.default_css->Stog_server.Preview.respond_default_css|"preview"::_->letbase_path=(Stog.Url.pathcfg.http_url.priv)@Page.path_sessions@[session_id]inStog_server.Http.handlersession.session_stog.stog_state~http_url~ws_urlbase_pathreq|"editor"::p->letbase_path=(Stog.Url.pathcfg.http_url.priv)@Page.path_sessions@[session_id]inrequire_usercfgopt_user(funuser->Ed.http_handlercfguser~http_url~ws_urlbase_pathsession_idreqbodyp)|_->S.respond_error~status:`Not_found~body:""()end|_->letbody="<html><header><title>Stog-server</title></header>"^"<body>404 Not found</body></html>"inS.respond_error~status:`Not_found~body()lethandlercfggs~http_url~ws_urlsockreqbody=letpath=req_path_from_appcfgreqinletopt_user=get_opt_usergsreqinLwt.catch(fun()->handle_pathcfggs~http_url~ws_urlsockopt_userreqbodypath)(fune->letmsg=matchewithFailuremsg|Sys_errormsg->msg|_->Printexc.to_stringeinS.respond_error~status:`Internal_server_error~body:msg())letstart_servercfggs~http_url~ws_url=lethost=Stog.Url.hosthttp_url.privinletport=Stog.Url.porthttp_url.privinLwt_io.writeLwt_io.stdout(Printf.sprintf"Listening for HTTP request on: %s:%d\n"hostport)>>=fun_->letconn_closed(_,id)=ignore(Lwt_io.writeLwt_io.stdout(Printf.sprintf"connection %s closed\n%!"(Cohttp.Connection.to_stringid)))inletconfig=S.make~callback:(handlercfggs~http_url~ws_url)~conn_closed()inConduit_lwt_unix.init~src:host()>>=functx->letctx=Cohttp_lwt_unix.Net.init~ctx()inletmode=`TCP(`Portport)inS.create~ctx~modeconfigletlaunch~http_url~ws_urlargs=letcfg=matchargswith[]->failwith"Please give a configuration file"|file::_->Config.readfileinprerr_endline(Printf.sprintf"http_url = %S\npublic_http_url = %S\nws_url = %S\npublic_ws_url = %S"(Stog.Url.to_stringcfg.http_url.priv)(Stog.Url.to_stringcfg.http_url.pub)(Stog.Url.to_stringcfg.ws_url.priv)(Stog.Url.to_stringcfg.ws_url.pub));letgs={sessions=ref(Stog.Types.Str_map.empty:sessionStog.Types.Str_map.t);logged=ref(Stog.Types.Str_map.empty:accountStog.Types.Str_map.t);}inrestart_previous_sessionscfggs.sessions;let_ws_server=Ws.run_servercfggsinstart_servercfggs~http_url:cfg.http_url~ws_url:cfg.ws_urllet()=letrun~http_url~ws_urlargs=Lwt_main.run(launch~http_url~ws_urlargs)inStog.Server_mode.set_multirun