From bcd33dacd75e767203b46228a18a3b3267868638 Mon Sep 17 00:00:00 2001 From: whaffman Date: Wed, 1 Oct 2025 17:50:10 +0200 Subject: [PATCH] feat: serve static pages --- .iwyu.imp | 26 +-- config/default.conf | 5 +- static_site/img/nginy.jpg | Bin 0 -> 54861 bytes static_site/index.html | 192 ++++++++++++++++++ webserv/client/Client.cpp | 2 +- webserv/config/AConfig.cpp | 5 +- webserv/config/AConfig.hpp | 13 +- webserv/config/ConfigManager.cpp | 7 +- webserv/config/GlobalConfig.cpp | 5 +- webserv/config/ServerConfig.cpp | 4 +- webserv/config/directive/BoolDirective.cpp | 5 +- webserv/config/directive/DirectiveFactory.cpp | 5 +- .../config/directive/IntStringDirective.cpp | 5 +- webserv/config/directive/SizeDirective.cpp | 5 +- webserv/handler/ErrorHandler.hpp | 3 +- webserv/handler/Errorhandler.cpp | 23 ++- webserv/handler/FileHandler.cpp | 125 ++++++++++++ webserv/handler/FileHandler.hpp | 24 ++- webserv/handler/MIMETypes.cpp | 37 ++++ webserv/handler/MIMETypes.hpp | 24 +++ webserv/handler/URIParser.cpp | 94 +++++++++ webserv/handler/URIParser.hpp | 29 +++ webserv/http/HttpHeaders.cpp | 5 +- webserv/http/HttpRequest.cpp | 7 +- webserv/main.cpp | 4 +- webserv/router/Router.cpp | 64 ++---- webserv/router/Router.hpp | 5 +- webserv/server/Server.cpp | 5 +- webserv/utils/FileUtils.cpp | 61 ++++++ webserv/utils/FileUtils.hpp | 11 + webserv/{config => utils}/utils.cpp | 4 +- webserv/{config => utils}/utils.hpp | 2 +- 32 files changed, 683 insertions(+), 123 deletions(-) create mode 100644 static_site/img/nginy.jpg create mode 100644 static_site/index.html create mode 100644 webserv/handler/FileHandler.cpp create mode 100644 webserv/handler/MIMETypes.cpp create mode 100644 webserv/handler/MIMETypes.hpp create mode 100644 webserv/handler/URIParser.cpp create mode 100644 webserv/handler/URIParser.hpp create mode 100644 webserv/utils/FileUtils.cpp create mode 100644 webserv/utils/FileUtils.hpp rename webserv/{config => utils}/utils.cpp (97%) rename webserv/{config => utils}/utils.hpp (94%) diff --git a/.iwyu.imp b/.iwyu.imp index 8949600..ec004d4 100644 --- a/.iwyu.imp +++ b/.iwyu.imp @@ -14,7 +14,7 @@ { include: ["", "private", "", "public"] }, { include: ["", "private", "", "public"] }, { include: ["", "private", "", "public"] }, - + # Standard library strict mappings (preference 5C - very strict) { include: ["", "public", "", "public"] }, { symbol: ["std::cout", "private", "", "public"] }, @@ -29,7 +29,7 @@ { symbol: ["std::shared_ptr", "private", "", "public"] }, { symbol: ["std::make_unique", "private", "", "public"] }, { symbol: ["std::make_shared", "private", "", "public"] }, - + # System headers for socket programming { include: ["", "public", "", "public"] }, { include: ["", "public", "", "public"] }, @@ -37,46 +37,46 @@ { include: ["", "public", "", "public"] }, { include: ["", "public", "", "public"] }, { include: ["", "public", "", "public"] }, - + # Error handling (preference 6A - stdexcept) { symbol: ["std::runtime_error", "private", "", "public"] }, { symbol: ["std::logic_error", "private", "", "public"] }, { symbol: ["std::invalid_argument", "private", "", "public"] }, { symbol: ["std::out_of_range", "private", "", "public"] }, - + # ============================================================================ # PROJECT HEADER MAPPINGS - Standardized on <> angle brackets # ============================================================================ - + # Convert quoted includes to angle brackets for consistency { include: ["\"webserv/log/Log.hpp\"", "public", "", "public"] }, { include: ["\"webserv/log/Channel.hpp\"", "public", "", "public"] }, { include: ["\"webserv/log/StdoutChannel.hpp\"", "public", "", "public"] }, { include: ["\"webserv/log/FileChannel.hpp\"", "public", "", "public"] }, - + { include: ["\"webserv/http/HttpRequest.hpp\"", "public", "", "public"] }, { include: ["\"webserv/http/HttpResponse.hpp\"", "public", "", "public"] }, { include: ["\"webserv/http/HttpHeaders.hpp\"", "public", "", "public"] }, { include: ["\"webserv/http/HttpConstants.hpp\"", "public", "", "public"] }, - + { include: ["\"webserv/config/ConfigManager.hpp\"", "public", "", "public"] }, { include: ["\"webserv/config/ServerConfig.hpp\"", "public", "", "public"] }, { include: ["\"webserv/config/LocationConfig.hpp\"", "public", "", "public"] }, - { include: ["\"webserv/config/utils.hpp\"", "public", "", "public"] }, - + { include: ["\i"webserv/utils/utls.hpp\"", "public", "", "public"] }, + { include: ["\"webserv/server/Server.hpp\"", "public", "", "public"] }, { include: ["\"webserv/client/Client.hpp\"", "public", "", "public"] }, { include: ["\"webserv/socket/Socket.hpp\"", "public", "", "public"] }, { include: ["\"webserv/router/Router.hpp\"", "public", "", "public"] }, - + { include: ["\"webserv/handler/CgiHandler.hpp\"", "public", "", "public"] }, { include: ["\"webserv/handler/FileHandler.hpp\"", "public", "", "public"] }, { include: ["\"webserv/handler/ErrorHandler.hpp\"", "public", "", "public"] }, - + # ============================================================================ - # FORWARD DECLARATION PREFERENCES + # FORWARD DECLARATION PREFERENCES # ============================================================================ - + # Suggest forward declarations instead of full includes in headers where possible { symbol: ["Client", "private", "", "public"] }, { symbol: ["Server", "private", "", "public"] }, diff --git a/config/default.conf b/config/default.conf index 16b5c86..30ca84e 100644 --- a/config/default.conf +++ b/config/default.conf @@ -26,7 +26,8 @@ server { location / { autoindex off; - index index.html; + root ./static_site/; + index index2.html index.html; allowed_methods GET POST DELETE; } @@ -37,7 +38,7 @@ server { } location /images { - root /var/www/images; + root ./static_site/img; autoindex off; index index.jpg; allowed_methods GET; diff --git a/static_site/img/nginy.jpg b/static_site/img/nginy.jpg new file mode 100644 index 0000000000000000000000000000000000000000..43136e05ab8e6e718c08d2204141baa2e3461e92 GIT binary patch literal 54861 zcmeFZWk4NE(f~TR2M+`f5Zv7%!QCN2g9n#`YZ5dNAV9ES!5xA-1PPiT!9BRU`x_$n zX1{Om?%nU-dvBXFOr4(UnyTum>gt|8b2E1H4Zx6*kd^?Tpr8OL@DI3|!)6nAH8%wS zX=yqD5dgpg3kn8+0-@W301EE=*#?9Of5OfnO!E^58iZM)paC@S>=zS(w;aKq@S3oP4Y-d@QVFtlWGY+%REb}VeHEKm_YU?}h&!26%|tSl^0@Bh3f^!p$8`~WtB`3cY63I!G<;2Na$ zOG*Ih2Yg!r^%DkF0ziXOg7kr>U_H!Fi9tR9_}^gMpYSan6!Z@|rXW7_Pq-BmyZ{!& z?|(PH_ra|SK+Ky-KomfPhev>iLqtG8Kte)9M#VuxML|KmhlP!fLrg$QLQFtJL`KO( zO-9Z@K}1BuP0PT-%E7@wO3f?C!zRGQ&cSv|1PTcW2^9qu9}Nwk?LN_cw*R=?d;~BN z0g!tbC~^QA6AA_s>ZT1K1^I*nE#}r}eil&euyF7Qh)BpNAVLMkt)^h0VPW9lU_oU= zdEIskEG8V*eb$HY*vbY7Kx&OV7y6%FfBnD=jOpsI024scmU(`_$gi+134ZaA>2&Ip51*n)lFu`=Cc}o1re>{S^7dp^!2Xi#*i5gyhPf>Z_)4 zuf3m6#%4tMKEpHJF}g5mYCtpPG<@DEGx@@c3?;s~`R!Hn;ZYGx8wu;M=c7+%QQcL2 z7#%IwLKO*_s}libya6ra%TD*K9QAXH%tvgcl1V5yk|Z~GH&(7iIf+$HH0mr9HUi&i z2tM8we7;GSX85o)#`?c?Ai#!{_hxB+4<6Wr&qWO zV1I&O^`O3@v|=j=BnvO}T*_O07Z$?yDMzdRL4aCv;Ht%l|9qip>sQlWt2V_isEN5Z z0P+lOqGQCC!GZKw?>>yC&*vn;%a^nZD2h0bHi)dzKFgq%B#ZRfc?7S~=W+}WY^&xH zM5y@I8i?blGSH+21OM)z|J=xY1LS;dnu$7Nbhmbk{d5By5olg~^?6W`?}a&Ky(8QkQ1ogi^Qmuqv{ zaRcbp52_C`IeJNG(`V2#n31$OQ%p2)&yimGa-yGa3Lg?Ir|+eI>LFhUR*oFjsoB%w zv_)-M^z5;#7D^=8%R3GaZrZ1pyaDu4E<;@#_-+9F1D})dNI}9myi5NI()0PIb6gjp z`+LdJb6t@rRnAH6J4~#R2)RYSBba!q-T)C@O&c@98#0;Z%Bf$7d%JO$M4|3=cw848 zGtz66jTETtcPmpWp82kNtsl8E`(DaI=5!xF_-Jn?Brw)zb!ecs0lPOfnbBYEIxR{M z(~Mh0h>`w;mD{U6mP{~!`HXj{7yedTgVGlQd>M=jA#6)vDqOeRw z-nx=Gfz`<u>`ETq>H(TS}XiYbw%K)(R`HsTT;?r>*l1d@Wpt z2+iloToy>`khaw5`vq$%47)(zIa(JCwQocQduYA&x{h?wQn*lFBH`Nlvv-}cMAWre z?jfAga773s)~BMKEX=k$2>GNU{O`(~AZSVDv3OVv_7ZjRal3G?!XW~(PV)`&pAqMK zfiF1T!F(l!dp!OULbgWPc)^cdU2Kg#;9tyd(YMKyKapv9+^)roXWmh$Qg!P8e#&Ai zJ0w5?y~M=c&*-RF=u`u0Pu92ps>7Ylyr#l z{3THqV@l=WOU)t8hdSu3;Yjl7BInWTTea2l`S-42H~MPk>(*COX}jM!;l_^!L`$ox z>w(t|xB-?TAz1H376C%renM7l#teU8(}SR&7?U_!}KO^G?V#F_ey z8Lz0ul)+|^bONIo@;Og9WMkvWES}FIb~b46kQ?CryZs)*wZo0EqOHV?0j?$TB8Xis zBpW*7t{HBy>0A`q4!Am7V?-a%m&2 zPv_$7(GKZRp}wE~{@o#;jh8pTs(SRE?6oA-4N&ZQ0~8vB+yKsJ@0!l=Bu9_T2BvKcD_7b+E@PURr;uy$FT&T= z%=^XsrxD)$cS#wr>>V*rcACi!pD2F7X=h z{%zOMse2D?JA&pdBjt1byb=b9Y%U>J^wAZ`+DR4@=F=GwS#o7WQJuJ3joiEE8;yi^ zL1`#^M0(g9;Wl=`dpHtJBU|e85Mx~wR`=A~a6v8Fdm%%;2 z-ZZum43H{})>J`iPYX*R%-hZ&pHqL-V+3=3H!G83aZLlYV_2K|sOUmvr1-E8mRMtX zWNMm@9-qY-^$qK2|LD6@bkQ5YFYptZn*bf-QwV#G4z1O0^&vRc9;O8O>{HB!8{Yu^ z{(22FwDX`6BTwDK1-RkRW-HiXS2GAev_6;|dE zLKa7C;lr~^ERC&;R15kBNo6KWc7snnJ`yy1>PFu}+MIP0I5V=H#>H#3MZh0+YLH;y z_vt9N9`gZ>L}trV7c{?sVsIFY^jW2tZTdlv9{!P>1MQM@PA(ozd6<`^t?u5i*dW9%f&Iru1G|rs1v+Jp<>grDEO>vy4c5xIA zFB-O8Q>A9|LS&X7*GrDj)xLT>y1U)vG&ilxwAHK16T?0*T75p+xaOW&RBS-Jl<{K0 zKDi!D5H^-;D@<}{%}eo0>yxL$Rk5S{Mu{zQhwb`TKQ5!AZ+GblM{Ee=Zj>v|({Q3mj`Q(TGmxA)NRVrHK zthf(Ci{0({Y=4hHQyY!QUj;zeR<>f~-!#a=bHDhgsaU#EWhEAe1XRwN-F*bOK*vYX@Z&bdR zc4|Mj>crU?b}u^mq`=#(6MYbjG`gld9Tr-2_Z3)9)r0(LRGY|4I`lj_cCWTJM^zZR z1&VZIcmX9v9aJ^p-Mo6<4YwP>C-79$R$R1I*}XcQ+VB1J6yB7{Gd;k|WYr@q_kn(?|(+@S&0%~a#UwEF{DUqgaR$HbL_aYX<-AZMF# zzOFKI#t|JSPkHE&>Z!2Q?%{r)5%#MGgtAf>9|YmR!Qbh<_IigBrp!)# zC%MaK3rI4&aC}i|aNpQqsZPEqvw=*PLm})0*Vf05OnYy-(@GmZ0F>@Rf~>~*5cTZjImfftJCN|Lc{vj>SZ&K_>GIL z@Q^b_Uv_q_(mgX@TW>OBfLq=EvVp$mQhK-1(r%RCzWEym>#@t$RgY1I)rvd;R1I}L z?;}RddACnGgO2&rT8r9all8ssTSVx&Te{hSLihCa=Dm!ZK>xmfGUjovnY-pL9u{iB zSitxdzeC5#XkF3$zIcy@0v=ti>P&vxr2>7cFlu2z38VnGCLymyXx__9-W}CGLkf#S zT^t`u0?1a@Rel*Z9#21b@9j%0==X6816Dg9#Z269*FuBe;a;%tj8M7j`CF z2Qy|?US3{i7B*%!HbxMG5#na!XyD3d1EKguLCgeV+Xl`pmc1zK~(ALROkQ}`K z55}zR=)KQaKUzeApcY5Z*L7s z$R}oN4Z?Cf0ZJgnS2JiMGgF*HmZAfRE~lKQ?}OHO8N#3um~G5~wo+}gm*gxSK*#Oyos ze4-8}29CB4YPPmkg5-ZJ^4{9n4;4$9L%_>!KP^|)#QrB_Wlr|pZukt0Zmm#|9Ae;X zVod(C(&(9ijhPAPPhg+^Kr{a*DmLKdVdLd7;b!FEG&Ex5;Nk$gh?UKN(b&M)h?~{G z$dHAb=U01(t*N7nfrH6IGqArwPQl*(z8pqYDZgL)&Aoo1gFbr;{t*I%$o^aYtAYP&;J+I9uLl0B zf&XgY|9=|za}r@<17MbGbO$-NJDoi~}ZY#voh(!VC@|gCIP1TW|OS-n@m)zQea0 z3IH4jH5E}%w%eTqWM;p?hQGl^&m62l91akN(%9Mtq!0c02W)%`d)~sK0KD+!p|de+K|mW*}{yzmWTOf5;zt z`i0}M3u?FTHd;0F%zc9Q@cG6DiTA}TT}Dhe_R3fdhk4759#cTi9;?qXnK zXZsZvD+~ z82=v!RbIWyx=*)=^nVhQ>|kLfha-RDKs&I+xh}!Sz4_Q(M&$oM3L_{;?AwgM^t~^+ zhNljw2Fa=$!?VOF-ey|^@xj?-{|Aaew={EBI?ShkL z`0TZv@Ez0Wm0?yzAwhQ7A5EcX*}Jd!Cd<4>lQtbU5;OibLC{s@7#~|c({s|)*%-A5 z=jpnQXtza1N0Fj$zI&`p#e#XuA5CBb^hQRe zr^;-IH>k55;=4uuxbklR=%Cjq`cWCYM>);O8|D{q?2trjl4DNwc=|4 zij`t&YPx~Yd17j(^WW3I#ekAC==9W2dXkty&?NK)01FO__>mRB~K0teM!a>hIkZ4m0q+ zy*K1pm>qzmI9xJ3?UQEAyRH5<*#5x_%YC=1XwlQ9B9IgnwWsY2*YZ|?QZ&_@m0`!_ zB#F1+_Qj@^rjhBC47I=6L&!#V9?oAb4-kC=5WDx*nnsht(=~aqfNcCBEvGBYR2c;f zP(B6TBWBIj@qj*whww#1bC6lKkb6%7XjSjNbns^BLvP4o#j>!$6a!Wa>7j}UAO8Oey=}Vgfh&@sP`GLZ~2=?m?digZc804S)KGGkCsfzD_j<>=Uv;qJUn#JcNSz^y=gHpo<9M-iawMrBozx>XIB*=zMpM(f!STRacjiUybcDG7qE*?p`SY%kZOR=+qBTs=W z^6Bv8>LvSay_q_~^wB(1#Gs1PjnT-twGtiBPRPy{Li2lChZ8T@PtN7SV|eB!>Mx4C zM|Z4i%fQHrNKQd8Jhk?tDW#ALvsgJd;x~P=`=3r!>Pr=1C7{l$l36@yA3^D ztjh^Q)01~uB7j>-Yc!qXYoqwC!;6cOhF9U@ z)~^_-ly&)P3?;_9U!yYYNieOKoJ3(=XQdGS1dY0_(A@pXa*fy4<3W}6(S^es0Honr zUo%pr+zmF*Ziv?7Bv6 zM92M#r+4Xt7I%tw7ia?C3r;(c@29gbX}li+DyoBxfsLY$0ZU^k08u=uLp#<@4Mj-c zG!B5`Ss5nQPdBL(ya$Z%B?>vOD18P!&UXCJeX{J(JxWZHXC#kc)L)M*zK3zetjb2A z&%34*fTzw*?Z#j5rVKq3IPo>_!?quC^rU#~~2XiZ*Io(b1I_FDX zkR&HbJ2Wl|U0-@gpB)Jau>_a(nVwjAp%_$nm3E6@l9&qgXk^V1c@sUvT%DNMq)+MJ z7P@}_L;Bx*3me<*X!U5+Xtko05&(P=MwSi~=>m6q)Z#>-3)t7as|HG^lV3l#Bvv5h zhvb-&0S~x0&jqc=&jqD@W7i!$u_Hc3U`ftS&Sn;#ygu;Ar2PB>o;1C0NlOT>{lZhF_@I$n={bHppQI7CFh&$0{&nS<*+UK?Ay!)!u&uJ*&!D=}NvJPOl0 zVSiCuNo9l2)N!I2p zU*flIGM+u=Hw*9U3#Szp*xOj|inKj+A6fCqa{xT&IrZrS`zkCP-R3Z%rMlhuX5GiV zm?{H{hmYXAi}A&pBaCXp@Vx44-=@MN|zQmL_JPCrZ3n8Rj%iQiyN+cbElBAiSv?LM!*YT=M`epS`rSCzN;&f&t*xsLEWXt}c`j+~KmnE1U#vDB(LIIeCA zil)6QPp5@^w+|2Ho-??g*GJ2pftw$+bt89b8b zJFNH4PWP6mS7zkB9!An%V~qUA&BhlBV$Xj#%s-Et5tsz@ zN$M2_Oa0TbJ|o760@ZF1MW$$fH@o|{1Jj=)LN-9-B}w1O)J`WpT|lr z+w;jbp>LHz5fVW2+Hr@ZAuVqhznz+)a_MDXgPhIhO)b;lZa7&u!0 zRHmvxZZ^o|TRv5pqFr+s|HILkUf|Dj6?=E|SIT-b-^Q9+Sc65u1nr~{=X1P#;fELi z=6oxfWY?VP(u?r)*;$*Tixjt>r7*szsMgcDWwy{SmnSaSVD=PFODY7qnKbBT-wi37 zQlMn1XIMt#e%Hd+cbOGdT#DhbVa0QHWYp@6eddLt~i7>vm_8NusO(6m65)z=loVz`7h!gKFBhDQ_MVK|%Y*tAn0+ z3pHD+%KFm$$-(Tclln=YOy$652Q^x;B1SqjDTtgjv7UtojJaE)nSn_{tbrclQQl6)2NSsWd@2X4R5p}L)-B#*4< zO*i_k^^A6{tn^(XS7Lr%xZIP04Prx{e-M3<+P_vO-naJJAyXk@Zg;52VSKvidSmPH zrMI^ZI24~mX)8xjPOlyn_O0UdDB5pK(3Pl!3z*R~ZXebda&7 zYTzf99&U8^V$UUy3*sQ<%FpA>lcUb#o0aHsc4vbsaN_aY>P?M%{%2lFbw@9Ez)nw+ zL@ItACHjJIc5UD_#LFSI$TfPIr-z{`e{7vlee~Y&*Ch*ZIH%lFxC#W5nffH<)P?$) zr^S<#h1>cfO`F@64J3+Os?R|4hl1^dr5R;`gB@pO_pTdLF6peC-FE#285 zuIpHt>nKl2IpeIj$0olWuBFrX;^pdrTUxrrsp}~~AE5|b(ED@p z@Oy&uBN$a!7$YgP9+w^lYAoq2!jhueE%&N3!|fFx3E$@M9k)6BUq|dNBv4f+{9sal zU=YZpZq5@(9SXZRp*zVnyy5I-82boxvKW-xAs=5aoIPf(T*h-ZF`a+Yh0kWd=@Vx; zuSTp$b`JnU`{+LJqkD>q#ltZdqZ4LJD=W97{-l#**Q5ptwaDYGMqyzRfsD(GuHqa} zQ-M-iMV^I9>1Uirrl|X?+UkAJjyzJNANCFhdl$`Viv1am&f3gpIDI5W^3>eix{#U+ zxU8T%EGW?$@K5g9$LIc?V3a(Z`vzK&+xw^wV*5BR9{TMQ&r9C$2=UdT%nrtrznB#8 zx@U_wyAin|tzG<3PZkBi?%-0l_1)RRyw`fkb=q}Lhl!9kZr$ziIYF0gIuWmFwsfnT zr&~Mq=xBbC=4Q67%i{yrF*D(4F%h{o3F^$=PiL3MbY+LD8&0#Pj^_(YD}lscz1^&b z2mQ8sZWHU@l#~m%W9z)OQxSB)OZzH*QbShgVq1$oKF{=ybAjrS3v%s!L1Mdx&6yM} zlLLv|#oh7;TB*DH`;oA{RTWda*B!1U)7>4J6z9Ipcj*e}3q` zQF5p?j&+}aK0$SGqJj;0J9NB8;(zdT%L$y+;roOIPH(*Ru$KC#v!-7P!WI;UG_H7D zNyFkJa7TT?=$QJG!6`Un{yX2{?w)Wa1^)SR{4mC{Jct-7%9RMVW0vFk> znwFb8?#gOEHS!lpGe#)xm)x~2t$09AMsH+C`N;N+yKxmd?uAkHN}cDuTUh=XWXa6A#uVqc?bG)<-C5g9fJPM&gUq zDezO|&R@=Y^m3m&$^EGZ1?(#RWBD7vsPgpk(se)TV27&mJ#41u%bJWs{-}a?IT~aLRb3#MWn9B_mPX zIM07r#$P0a-^cP>_W^LJsSY;}PgG9q`o5Zi^ZooI9)Z2yw10@ciAHgBZ(rj@d_wBU z-gcSVNgaHHC}bbb1tfGKh*m?BTIvl#K!>ZHoO_w=mK`{Xt?{rTiv>3L9) zWwIH?(#it7+0drbgUYR=#8;%k0zB(c2OBm;L6goXOgBKOwg5OTxk;X_MCm`q67(RM zX18rHoou)VU_cu5lJZ@nhveiAof=O{`2>X&vitXXeYnWU@_D;l&UKkBYiF*UDB-VT+f z^!(5oZmus>v9=o8@U(QNj<~FEC6IJb$irrE5G(%ZaCXEaU~J>W;_Y)p-uVip;Y5P< zr^(?}GY5hVy+WCu=K=yi;~}3Qw^iLD$pt4>Gm{hx&r^pp0NpG${OMB64bV5(|D)e; zSKb8l6DKGV0cb;8p$3w=O|QFIVnOX3ntZw06?tL+5UH0CWja?^tD*HO{)gNLy=kq# zC;tPOS=GZ=gpYFuNxU0EduwZEi>@!o0Ggn3*Hecrg$RRzdcWmV`WUs zz1>9_{XWkb5<~#Nvbxg&$t=`V%;<#nJ4iB2DCF^khYWB{R`#wrWAx*mXJvgGSwQt7 znYpO#vYgTl`@a4IOE4JQu|ikKByd?YS$Ab%+u#8?T&(rK)7-Zl1^UKXo%$@<_+*iY zC#vhWbVRx!^7u|&#eO5+A9DZi`JvNwh|Z$aYa?+;-a8Mi3=sg@6t|KOJKJeIL9ljN z1hBYLKo@}IWc|KkkVUbASy(fbLK;?S(lpd{>_P`>NCv+z5S`KG%aYq=MbSmwB!Wlw zq4ArbV1wdAUgsoh^Ip(<;&%@BnZ4dEi{P|9xvY1}rVepDIR+!gxqx`I}c3 zVzz9I`3ak(!OT)QjK{ku+P77oYi72=-Q$scsICA?Qs<$&S&6%DLPt?sFIy)RJgSAO zv8(HbJ~QLJl8V=I7wG+>20zXFw`IS$gr;oCVt!KbJcqZzh8&W7nK@1te&9ny+1Iv? zJBBwvl)Ft9ofL*eg93u^0nOpk^WT;SM1E@T*XiXS6%WY(@L}V!kt8RnQEtf3Z1}hF z23B!=XE@0=%{Y0oj7{M5Y~P2#_i5Neun|`Gg`h(Errvgqm0TyzJM4Jnp^1vew=2QF z)}@&f@Y?ja=<1CZ(gd+O$pRg{8{G@n`j;$tt!4$K#rp?9GVVEVQ{YUI!LlD|O^^dL+G(D7+gwT9nS6!Yt0h za=Ni=f%hOuC{77C^kPNGwicJftkLQh*1v1{f6&B4zeA#>C{vbqxSx$GXPv^Yj0Z zAc&40!(;iyMkBO8XYBH%Q zTj(fuG&WQ9|3VAg4-@q2Z$It(KQR0M!X~8P?hG~za2pNydIng8+ZQ?93i18*4FEg_ zCK)FJ_<{^9Ts#qMocnBC51-oIrBZqWZrVTuH*-M2L4!ZEe3Tb~vuxWE)3n5vf_3)- zPEH&-)u)YedjxZi*?zRMZ1?V+aso~9-DUziEwCqUdA~fc{C(GlbH%Pg^3?YW72%HfbLTE z_C3)J@NqE{GuV>bt&l2QCpmM<;Vr+IJB)(wRqu5s<>_*j&# zRVlciLLuB4b+vULpWj@cX%KA*@texN3c8{*+%m&c#Y3qnzNFFi|NKBy*V;rjohZ6D7x3)ClN3rT-uERQfPx?pPyG4oCCQB_|Y;}pfFkP=Aso#Gq zXKcO3+1!R2qH=wPmR!_n&Hr9M{76(~p5j4q;Uf`do(wVUXO0d9xz1g@lofvPc!>vu?}67R?|{FfHC+_hF?hd@%T`@CKataIp7Fv>hCwVia|I_NuDdc)QIO)W zI9>{28M`=6q0xZv%x_~^wK!g;(SYvEFMk<3Ee9S=k%aP7^&V!l#LVZ6qyXL}1>*#c ztewfaf-^ljd%EGA_j}a$=iAz$ee-Ks-$fi)Ex|;m3|zIeb8K$MjdsA8Z3SdjG1^Uj zbR4Orv`ak1>hC=x&DS>=@Y$RSBHm<;ek(y+kg-r>LKA?6mhX(HEC-KfL4-}YIDc>_ z7Y~_Ru#k0mua?<{VL}!wsEcbwr zNoPNzS}ztWr@-jdC%7RS54L%MK&nR%@kb6~RdYGpAUH&ML&ps>)GS_h^)_(2g6AqS z#4y@;FP?Yhs3R4s%e-RZZ*Wn#W5ApDrAD3adg!Na6~xNzAH;Qc(BhovuaAy7>R)h^ zBY2?N4u5)5;65BBy!WbTMM=fCBQO|IsLfrk%*^@VttgVbrNY^uAi{--0-dx$YZ>|% ze+Hj`w~;M%myAy0_qNPRZ&27`ah$OHW<{Ew~yC;qxEsozIAyiKu$1lG*T;luvQ^Q#Da=^G3roB34Gh%0VZq`fT z!py7p?(#W-L^1CX#jE_lNsQq$^xct-tlpYH7NT9u?xKxXF18%wT&G;J6QQy;T60y; z*U}bZ_Mc=#({-(K>b*hgNKpK}cWk>jMf!4K=Sr6x!a~XnP+dK04fq+P0`M!W(nrxE z=n&Upo<8Baqr`58FV^X%6GAQKISF%BDtKk4!?&|GYqe9;GBqnIKfu5ET8GJn-eT_f zlMEFy)<)H3(nwf^na;;J^*Q_3!4uZ}YQ^13J(FlZnMx08_>^g%^QjrM=R1U1YxFU* z92B9yTYf_mWh>v4mqDEE{WYEqkMMC&ZNB)if}Qq=SACQRo4KUq8|v75f*ozUyeL8f zJ0*Pq^yAKxQ@fqUY_GD$6%>dU8a71g5ogR>E#2jNW{T<2VXXo@UL{ZGpm`NmD+6%O z`BbXtmy9e0?|w>Orj1~cOCI|bCWyqh>AF+!XxCyxWS+eBb04s=zxp3N$)Zdnr{y3Q zN^n8QHAmmx96J7Cp>=xj$gayxM_*2hi@igd3JF^~X36ozsbfm`;LFkM2U*8>-Aqb7 zP^it`q#3m{Ne}cA)0OTvlf{4>7u|Ah#O-eDp`RC4ts3yJFz8?^EK-5roaMwB%1!P@%*@h9@mg+**>?@Z*9 zzx~D`OZXcN)+vrjx0rLA4a*fQtRRE)ieqBD>o2##n{aYprn@wsdGB-Hc7?aym}Abk zBqoz#CT6s`rfCaqw26-3GsE-!;4w`nceH5~UX6x&v==>}?^C~!OV{`=8y$>)DBw|&G!)S@0GY+98)$Dok2qo62B?Nr!L#1R>YoON=|JL*g56Gh~ zc0j<>ya07MOtFpR5#N1h9(#0`j=;&gFY>#Wl_Sm#Yu`ucemRwUyY@0Fnem}LOGx%T zzL5RrE8D+9;y|0;f_Mt=Qn(1@Ynq8#tPXlq?Qf$D=xYTZz_%?BH;a8-s!3HuzaN_J zfP!li(F;pY*In&Vt3H6c9N{{ls;6;Ew;ImzRw!|=2gN+R?@R33i|cWc8vw*|nN@8Z zpM}KST|VPxc}vgDq~_X4O!xT({_$Km#)*5+9a5{up?WUN&CEJ%E%u#nIUcKq+^6hP z3<|Fz0EhTiTc|-vOpj15lTIf;j;?kJx?a6|ZR{c1PtpD~lUse-CkD!xB>J*<)VF3* zJ47GVgrmF2a1Sv(7N+!i4f|Dc?7t##Dx-2J>k&`U45p;%F0?K5qvBq0-C z&)Cx+QNHmu%3mbjPsGf~Y__B2YvgjOI3U-5Z`39@O9C>BrmOiRT`u)=$sR$9+(&EF zBoykqE*OZ17?1B#SN_iO{W!Ej`$!XFZX#N<$J#kb^<2Mli`$sbrN2Q_9V^+)B z53B@+Xy|T2V2!lRZ{1_Abt|}N?OTYc5q|5F1q9go$+%;rj`AcnadSw~%i=>|k+!qw z&G^ZF9*=2LhwQ{}QNO(?8j8zRvS5btj6@NG%SP>CH)W*@)7i-fa{3##O_@GLd@l_{%8mn#vYd& ze78enotbVg7_fYuyK6NjpHouk>O7jxzm5C&GZ@vB5-M6{?RU}KD$>$bUm0l*3^AC$ zrsB$Z-J$>sd+*}m_Z)JUn8O~gdQpqL8DkYKg*u4;M;`O^1}HHyHNCr?jfmNzYHuOd zG|*a#-Z#(b-kg-)o0#@IZlsM&l(pZdQZDTYDvz`BS@+{cjtp!Z{nfIahpi@M`V!Xqd`(9*hPA$q*-F4SYPSKe?kp?{8I=gZs#nlY>)#X}E01Pd6j@eTiV) zP~}n)p(~nz_%<)GMQ4&)_w#)o-!ca#Pm?HFr0+Rz{=SR{F_C zE~P1AsRouTb7x!AhDMNl^0|{M%bJnjrw})qEk{3?#kn>}aJUxg?%3YIXQY?JQmha+ z#6iwtT_G)zPB)-Od|&yPP{MNQ%~QvZ-?lqmJKD`{K2tMk?4>GQTvCZCyJr*|Sn{5= zM1Fptpe#4Rt$ZAJuG`Sq)p>|ch!wH&+>bpsLAO3vZB7+J6NozI+3*S66B>YC*iis0 zdfzxMZ@W)KIsDj2WiqDPdWvHY^&3--oL(-;I)OP-)_Jv`LktBdRp7pa9J&gN{0z0d z-B5S23?#P@dP(QL>fU9cIZbTn#F@lgWhflMS%9-F;RNhb??QAAcpzI~gS z`p_5$ThNI!SCB-Wou!atdAV|uXHE2^C-FE(i4_rk{Azw+m^t;R+P~qAr*SO#r`{O5 z1cyGo85sN)191N0Q@mc(^Tf>NuDHa?A~=}TFN@Hc(eoS=o8u1K>Ote#D3_Fb|ZS8k8Vgb zKV53AaszBIgKG<{m;e+E6vFr4to-=>$L%jcVBs(@!FO7+W0SFvvpvKWQO0>{U`O%D z{^!yH_)8ICs2gA*_Noqi%au>Y)VFDwyb2gc!}G+kdE5uNLj!BYcdo++tg?!N@7LxP zN9tmZYQJb!)ojGhds%x=BE+fb(0Gsy^OgGDR6b5TJd5pVY)XdXJX!gV-;RhyDe^3(Tgx3R%U(a4J;yUq)+Z7L@)(h*2XZ2JFsq63 z-Ze^jbtgCSzTvq6L^exQK6fU(Qg=>u?H*c*P0daUXBeW>RtlFLVz0DrdQbGNIV4&) zORyxDjxq{z9HG{Aw>xqW9poH+oOG6dN;lR?(9>=+&l=%NTE~{}2elF3IkltBIExn@ zC!Yr^G4t}u51wHs>)F%$)L8a7t7$}-M$%+ky$heI&2A`JU(z(6OUH@ElHL|}$8o~y zC{c?^kZKX@ctsd=wG@V&))3*LE}lxcT6ld)r~Pp6S#JJ9v{Be>-O@XriU;Zwri!wv zDZ!+t)^27M%39*@1jUuQDz7GWb-(u0Qb)@7`M;^Tpc>zQYjyU>OQ3Lyfn~P0GvrwQ zef%WsI?}u34t5_@PUb+R9Lw4X{*x!;8vCU1-I1>b*VQ-*Up@Vx;9q<~m%v(doNM67 z^szYQ0V8XUH}bpCw;OP8aW%!p+k;(SGj(gsAY)&vaa|2L(^b7WCAV<{)CL^Wi38Tx z_3AiOBvu=!JN655e4ivevmMvQgl6{-d>I&x*5pJST(~F*zF;aaj_PEB!}ax+0{*k6 za1<9c0gB4u`Q83~KduWw?*-hd2}9xiuumHM$>59lM0Kc49yVKYdUB@wzrVa4oewuxks3>Q zrBGo6C;Cj1{lk{$rs)!!sqo|iCAXsm1yX6rg)>(ruIphlXwfiwH~D>jzB6}ak2rny*j6Zl(IdI)bVDH z<$=4gh;NR5pT`uzRCiIHWD}|eWeuA-g>!JQcfI>@?;bWQ-IK(HS~lVR+VIOF@D+sg zIX6J}I{39nSCc6{R=vA7w|U~d{b=nV_(DvPu-xL#g0u6_!^Ae_^hC3wn(EtAS#rm? z4>d>nPlf#%3jM32HJ|Z`u1RRx$QbW)tmfn&B$nPU%1MMSF5)d^HB?wD<$VX55Zr_y zG;!%*eKm`zLqR#wf-jeZr0|q4Xt=oktrU%8o9edrcaG_A%!O224Rj9#anM`RlqArT)7g&S<<<74vb$ zsZ}`nOG{)n^@FcvZh#;<N+^x$9kK-CPmy&y>a=IhG1!WO~Wg|p6UMH zGoQ3&s7-^P>0*BTA01P{+h^r)*CHoR?DNNyk=^u$W67q{Ebq6jx%%-Fr!LiWwC7k< ziLJPo;8uEbb)A379UCC zvfbw-Jer0H$m=WY_kLzuiReSN%7~6!38Z&%JouHt(8@N>0VJgjYWD1*u4*6 zaRqcGpW?`OPVwiFW3$8YquO@cW+tRE(ebiZu{2zN&2vLLmN=0vQ5Og}XHp8ZHc?y~ zPm5Ijf5`d@ur{9OZ=@88dvPhng1fsF*Wm8%QoP08-CcsaLvf0`JH@rQ_f5b4`@cze z?(*cgogMki?A+`wOC5eqvz2#dzf&4vURC}f4~yb6!}e_(QX-72!HmZYJ)pCGC$`LI z^xcwz!p^ZHG)m(?7TLJSpHqIcz2LlBbLt5_0HZdAlbPZAzqScxwNVbOsm}<16GQHh z)sj|^;`$Tih6AT%1$ zc)zC-ewh>Pd20xil`iB=3?8PA7%dFwZJmDN346d55_!tKy-&9 z?|W=BFJVDE6NF$DIsFZA#4xa&vZswcs16oW8gt&_KOFQ3>v|}nVSsW5t~hiIrbpoc zuFLJ8?y1~`@%1d7(r;8ILrcE^@+d_p-UsWK0P31(VmjNvOpiTptyCm1E+*(}eKe1V ze&rJ_XMVLC}qp#5Ntyw?Y6qX0;I`%1Qjz5?sT$wEX8@(6bbfJoR*so%?yp8RWo?ZMqb z;~1Vc*#VeJIGG_TK;IHO3t-H(+b+$$7RWOA+wPk@(}S)+u<~?%|JDI~4>$g9f7bRDNb@GnGG#R%<}z!u zI|WoGIVjFBH}arYmVDU-q}=rZ$q&D%bR|BP`qJDey{a7*K3C02KL}UK59fK!@UfcU zP-fuSE2|7GWWc`|m)+EsF}n@BXi>8Rieom!hEcTuN23)(vpSKzV$w3Ej(*rZt5EQB zcefg=I@5hi!KFa)#UI1#qu8K03VwMdBN6;BTHfCmLF*!$Is5NA8YlXnK=4@+X+^xB zmYt>)SgtFVuOl)^;8#J>Au6cASNDa8CQp}$}OsM@5Eq5u9hJ9 zMxyw69GHug=VH`SlcT}3qdNoi6+Kj2z<3cF2b12gZ#}38yrAiZu~Fo-HBg?kGdO{y z?1-Co>eC(9QsmD86K(*WPdJmNj^)!6?HT>Ca9#0%rDq+XT@>@x7X^)!2<>cBL{>fd zI1WQhnC@ntv;Ue4kM(lTOzaR^msJ{JYdzJY?Kp-*zhd@9)QVatF$@Zoh}Ef_(~?jR zgOf-QjM|KIcFKpQ)(P;05DCD7Z$M1uL+AIqH7$rF*1eeH1AvDuq-`15k(i&Sw4Cx< zlL;c91{5?x_UUhq8{7S6l?D*84{s6C9BP8z&llHA@Ks0P{! z#g?9r2PH4X1QW~C2Y$2qWyvs zNE>d8xlVGn2oqViO^=ELdY;ilF4OEe1879H=%`mVj6Kin`v6By81kPFdemN-Ojk1>*T6}4sY_KqU_v`t(GWAjq4ZeJ(u&_MCBn{z|frzMZd}={$cja zXFy|BRo(&&IQJDc0e)2Amc#muVf#sKcF2L2lyTm0-DQ9e_VG+L_n%>H@3U_u} z{ns5>Kf8&MMt}e{EUGUJj4sQiOR=e*|AahXST%u#J1s9jZxSOzAWXPh;(uxc+Pf@f zEkGw!)N7S-=NCN}vX=9hxQg!qK(_Va_$42hrQjF0JpXcCd<(yQL2wfRs~~y_wM=DS zQ5}6|V^vq}_;#C%jykC=F+dVAxG^erhDRsCJ|qp|>~nvi2)nPO`b*7!bbQcuL3tKO zC$A_&W_+f~<^S-wnMORCqoO40m~z7%eyW(3EBR8Dn2LZ;NKwT_y@{|9JmW>IpV25U zw-`;l@wby|LF(5aI{pLnS1wB})>vklLbQrq%Fa*fnky?o2TgC!3KG7TovlAgambzc zU@F1|L3-Vxfs0E|)*$`vpOpWDk>L8QL?N|F9}qP@jRO&lTI65q%5St}c7eQ)2=sn0 zvuDZIDBo2JLSy|*ZFJa1ccIdnxfpn(0smMKeiX+5`qWwaAx?z4FRu}&j6c2P`Flt7 zZ6a{Uu9k1$(7RB@bHbX5Tg?PMEmnUfEET&i=~E4me?UwFZRDfJFhb3@t|Z`_=iqZcrFexWWn8ZcA>;8R|X@ zmvD-!1kWu=e>x2FgGBIhWZ{&>@h<&Sa(lngX)v%D#&q87ny1&w;okhW&$CcAyLq!0 z+cDX52-hvxOQ7%eOr2{z_6-D5>p8{7J8GI@Yl%`QKj4;`v-5#>bs-~_=^5Nr|7^YN zcxOh1$WsAuB+Q^?T4Oal$<>&Bp|Elrt}m6dJ{2WSz#yZa0tJOrmagNAreZo-^-U5;mXRu_U4NReXKH=vGt(+nM>AD$zyKY_q05zgq z9{_K>GYdg0y7#xoINvfE6fUeY^00~fmRl5swz<4NumoGmKV}p~rGW^Gg_TVpffa=B zCM&#@nd%K-NW5-Np5D=uj`XHyyKY|ieHscreFKHPi8@nXYKY!MEnl1B0*}uW+s35s zgUwkHpGKG~U$^CfZ7d`vjp<3tO9`aIbj(z!+kxlQ!nxjHW1+w+)FA)i`k4&)pwhSqA&uZ& ze$~2N)n$ziI?-*1%W2hCsL~1w$unVbo@G_zhg70n;PMyqe%i{6Z7){tQPh_O9iiEq zH}`r($38E2GN~uGRr9hB@};rhVGx1am7mZQd!G1-+Of%Vu?lz?;Gn+K6~w7W-3^+^ zb@nOtIn9cVhXIR?@68lG3mj}>%ymn z-oU4g8|(gft5dmg=iV=gZ-RpbGDm;Q3jhho`qq zTq=!5g>%F3=398aLbe$!5rrxm^-HbWTZdBd zjfk=jWnh0nuy*~fKKY#?f&$l{y#2jG$nWx#KQcsUsOTh2%%qGk7%W1VSRVux!5Jc9 z1qY=V$3Lo1p#Dk`WgXqVCeV+xdi{cU(6zdg$$I()@#z=D?4joDA;C>;ZLM*orFHHK z*k^MK&OVGJR<#~|c|K^3a^#Tx?0c;Uko*O4S*g=ydcavEe^QCOF?pU^A)nA?%wwR# ztkn0t7hK3YsYS`l;(SQs7n9@l4ON z6&4WYJB*C{GIsTr^3x%^kX_gMn�)AVFYhJS^QW2s@`<<^8dx)>cBFW8?3aMeSU6 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 CuhlRB literal 0 HcmV?d00001 diff --git a/static_site/index.html b/static_site/index.html new file mode 100644 index 0000000..a181e9f --- /dev/null +++ b/static_site/index.html @@ -0,0 +1,192 @@ + + + + + + + WebServ - HTTP Server + + + + +

+
+

WebServ

+

High-Performance HTTP Server

+ +
+ +
+
+

⚡ Fast & Efficient

+

Built with performance in mind, handling multiple concurrent connections with minimal resource usage. +

+
+ +
+

🛡️ Secure

+

Implements robust security measures and follows best practices for web server development.

+
+ +
+

🔧 Configurable

+

Highly customizable with flexible configuration options to suit your specific needs.

+
+ +
+

📊 HTTP/1.1 Compliant

+

Full support for HTTP/1.1 protocol with proper request/response handling.

+
+
+ +
+

Server Status

+

Server is running and ready to serve requests

+

Version: 1.0.0

+

Started:

+
+ Nginx +
+

© 2024 WebServ Project. Built with passion for web technologies.

+
+
+ + + + + + \ No newline at end of file diff --git a/webserv/client/Client.cpp b/webserv/client/Client.cpp index d56842d..15af01e 100644 --- a/webserv/client/Client.cpp +++ b/webserv/client/Client.cpp @@ -115,7 +115,7 @@ std::vector Client::getResponse() const const Router &router = server_.getRouter(); auto response = router.handleRequest(*httpRequest_); - return response.toBytes(); + return response->toBytes(); } // void Client::setError(int statusCode) diff --git a/webserv/config/AConfig.cpp b/webserv/config/AConfig.cpp index dbe4ae0..6336aca 100644 --- a/webserv/config/AConfig.cpp +++ b/webserv/config/AConfig.cpp @@ -1,9 +1,8 @@ #include // for AConfig - #include // for ADirective #include // for DirectiveFactory -#include // for trim #include // for Log, LOCATION +#include // for trim #include // for basic_stringstream, stringstream #include // for move, pair @@ -71,7 +70,7 @@ void AConfig::parseDirectives(const std::string &declarations) std::string AConfig::getErrorPage(int statusCode) const { - //TODO + // TODO const ADirective *directive = getDirective("error_page"); for (const auto &directive : directives_) { diff --git a/webserv/config/AConfig.hpp b/webserv/config/AConfig.hpp index 8e669f3..d4ac8d2 100644 --- a/webserv/config/AConfig.hpp +++ b/webserv/config/AConfig.hpp @@ -3,8 +3,8 @@ #include // for ADirective #include -#include // for map #include // for unique_ptr +#include #include // for string #include // for vector @@ -21,17 +21,17 @@ class AConfig virtual ~AConfig() = default; void addDirective(const std::string &line); - [[nodiscard]] const ADirective *getDirective(const std::string &name) const; [[nodiscard]] std::string getErrorPage(int statusCode) const; [[nodiscard]] bool hasDirective(const std::string &name) const; - template T getDirectiveValue(const std::string &name, const T &defaultValue = T{}) const + template + std::optional get(const std::string &name) const { const auto *directive = getDirective(name); if (!directive) { - return defaultValue; + return std::nullopt; } auto value = directive->getValue(); @@ -39,10 +39,11 @@ class AConfig { return value.get(); } - return defaultValue; + return std::nullopt; } - protected: + protected: + [[nodiscard]] const ADirective *getDirective(const std::string &name) const; virtual void parseBlock(const std::string &block) = 0; void parseDirectives(const std::string &declarations); std::vector> diff --git a/webserv/config/ConfigManager.cpp b/webserv/config/ConfigManager.cpp index 7b98c74..94cd091 100644 --- a/webserv/config/ConfigManager.cpp +++ b/webserv/config/ConfigManager.cpp @@ -1,8 +1,7 @@ #include - #include // for GlobalConfig -#include // for removeComments #include // for Log +#include // for removeComments #include // for basic_ifstream, basic_filebuf, basic_ostream::operator<<, ifstream, stringstream #include // for basic_stringstream @@ -86,8 +85,8 @@ ServerConfig *ConfigManager::getMatchingServerConfig(const std::string &host, in std::vector serverConfigs = globalConfig_->getServerConfigs(); for (ServerConfig *serverConfig : serverConfigs) { - auto serverName = serverConfig->getDirectiveValue("server_name", ""); - auto listenPorts = serverConfig->getDirectiveValue("listen", 80); + auto serverName = serverConfig->get("server_name").value_or(""); + auto listenPorts = serverConfig->get("listen").value_or(80); Log::debug("Checking server config: " + serverName + " on port " + std::to_string(listenPorts)); if ((serverName == host) && (listenPorts == port)) { diff --git a/webserv/config/GlobalConfig.cpp b/webserv/config/GlobalConfig.cpp index ec1a0bc..2fa4ae7 100644 --- a/webserv/config/GlobalConfig.cpp +++ b/webserv/config/GlobalConfig.cpp @@ -1,7 +1,6 @@ #include - -#include // for findCorrespondingClosingBrace -#include // for Log +#include // for Log +#include // for findCorrespondingClosingBrace #include // for runtime_error #include // for vector diff --git a/webserv/config/ServerConfig.cpp b/webserv/config/ServerConfig.cpp index 8eb9ad5..075b6be 100644 --- a/webserv/config/ServerConfig.cpp +++ b/webserv/config/ServerConfig.cpp @@ -1,8 +1,8 @@ #include // for AConfig #include // for LocationConfig #include -#include // for findCorrespondingClosingBrace, trim -#include // for Log, LOCATION +#include // for Log, LOCATION +#include // for findCorrespondingClosingBrace, trim #include // for runtime_error #include // for pair diff --git a/webserv/config/directive/BoolDirective.cpp b/webserv/config/directive/BoolDirective.cpp index d8d675a..b0f8276 100644 --- a/webserv/config/directive/BoolDirective.cpp +++ b/webserv/config/directive/BoolDirective.cpp @@ -1,8 +1,7 @@ -#include // for IntDirective - #include // for ADirective +#include // for IntDirective #include // for DirectiveValueType -#include // for trim +#include // for trim #include // for __transform_fn, transform #include // for tolower diff --git a/webserv/config/directive/DirectiveFactory.cpp b/webserv/config/directive/DirectiveFactory.cpp index d92a84c..23c0893 100644 --- a/webserv/config/directive/DirectiveFactory.cpp +++ b/webserv/config/directive/DirectiveFactory.cpp @@ -1,13 +1,12 @@ -#include // for DirectiveFactory - #include // for BoolDirective +#include // for DirectiveFactory #include // for IntDirective #include // for IntStringDirective #include // for SizeDirective #include // for StringDirective #include // for VectorDirective -#include // for trim, trimSemi #include // for LOCATION, Log +#include // for trim, trimSemi #include // for basic_stringstream, stringstream #include // for invalid_argument diff --git a/webserv/config/directive/IntStringDirective.cpp b/webserv/config/directive/IntStringDirective.cpp index 41c9dbc..c2f928f 100644 --- a/webserv/config/directive/IntStringDirective.cpp +++ b/webserv/config/directive/IntStringDirective.cpp @@ -1,8 +1,7 @@ -#include // for IntDirective - #include // for ADirective #include // for DirectiveValueType -#include // for trim +#include // for IntDirective +#include // for trim #include // for basic_istringstream, basic_istream::operator>>, istringstream diff --git a/webserv/config/directive/SizeDirective.cpp b/webserv/config/directive/SizeDirective.cpp index 3c7cefe..895ef97 100644 --- a/webserv/config/directive/SizeDirective.cpp +++ b/webserv/config/directive/SizeDirective.cpp @@ -1,8 +1,7 @@ -#include // for SizeDirective - #include // for ADirective #include // for DirectiveValueType -#include // for trim +#include // for SizeDirective +#include // for trim #include // for __transform_fn, transform #include // for tolower diff --git a/webserv/handler/ErrorHandler.hpp b/webserv/handler/ErrorHandler.hpp index 7c38474..dec6285 100644 --- a/webserv/handler/ErrorHandler.hpp +++ b/webserv/handler/ErrorHandler.hpp @@ -3,11 +3,12 @@ #include "webserv/http/HttpResponse.hpp" #include +#include class ErrorHandler { public: - static HttpResponse getErrorResponse(int statusCode, AConfig *config = nullptr); + static std::unique_ptr getErrorResponse(int statusCode, AConfig *config = nullptr); private: static std::string generateErrorPage(int statusCode, AConfig *config = nullptr); diff --git a/webserv/handler/Errorhandler.cpp b/webserv/handler/Errorhandler.cpp index 91ceb59..bb1ae17 100644 --- a/webserv/handler/Errorhandler.cpp +++ b/webserv/handler/Errorhandler.cpp @@ -8,18 +8,21 @@ #include // for Log #include // for basic_ifstream, basic_filebuf, basic_ostream::operator<<, ifstream, stringstream +#include #include // for basic_stringstream #include // for basic_string, operator+, allocator, char_traits, string, to_string -HttpResponse ErrorHandler::getErrorResponse(int statusCode, AConfig *config) +std::unique_ptr ErrorHandler::getErrorResponse(int statusCode, AConfig *config) { - HttpResponse response; + std::string statusMessage = Http::getStatusCodeReason(statusCode); + Log::warning("Generating error response: " + std::to_string(statusCode) + " " + statusMessage); + auto response = std::make_unique(); std::string body = generateErrorPage(statusCode, config); - response.appendBody(body); - response.setStatus(statusCode); - response.addHeader("Content-Type", "text/html"); - response.addHeader("Connection", "close"); + response->appendBody(body); + response->setStatus(statusCode); + response->addHeader("Content-Type", "text/html"); + response->addHeader("Connection", "close"); return response; } @@ -33,20 +36,20 @@ std::string ErrorHandler::generateErrorPage(int statusCode, AConfig *config) } if (config != nullptr) { - Log::info("Checking for custom error page for status code: " + std::to_string(statusCode)); + Log::info("Checking for custom error page"); std::string customPage = config->getErrorPage(statusCode); if (!customPage.empty()) { return getErrorPageFile(customPage); } - Log::warning("No custom error page found in config for status code: " + std::to_string(statusCode)); + Log::warning("No custom error page found"); } return generateDefaultErrorPage(statusCode); } std::string ErrorHandler::generateDefaultErrorPage(int statusCode) { - Log::info("Generating default error page for status code: " + std::to_string(statusCode)); + Log::info("Generating default error page"); std::string statusMessage = Http::getStatusCodeReason(statusCode); std::string html = "" + std::to_string(statusCode) + " " + statusMessage + "

" + std::to_string(statusCode) + " " + statusMessage + @@ -56,7 +59,7 @@ std::string ErrorHandler::generateDefaultErrorPage(int statusCode) std::string ErrorHandler::getErrorPageFile(const std::string &path) { - Log::info("Loading custom error page from: " + path); + Log::debug("Loading custom error page from: " + path); std::ifstream file(path.c_str()); if (!file.is_open()) { diff --git a/webserv/handler/FileHandler.cpp b/webserv/handler/FileHandler.cpp new file mode 100644 index 0000000..9794a35 --- /dev/null +++ b/webserv/handler/FileHandler.cpp @@ -0,0 +1,125 @@ +#include "webserv/handler/ErrorHandler.hpp" +#include "webserv/http/HttpResponse.hpp" + +#include +#include +#include +#include +#include +#include + +#include // for errno +#include // for strerror, strlen +#include +#include +#include // for views +#include +#include +// for FILE, fopen, fclose, fread, SEEK_END, SEEK +#include // for stat, S_ISREG, S_ISDIR + +FileHandler::FileHandler(const LocationConfig *location, const URIParser &uriParser) + : location_(location), uriParser_(uriParser) +{ + Log::trace(LOCATION); +} + +std::vector FileHandler::readBinaryFile(const std::string &filepath) +{ + Log::trace(LOCATION); + + std::ifstream file(filepath, std::ios::binary | std::ios::ate); + if (!file.is_open()) + { + Log::error("Failed to open file: " + filepath); + return {}; + } + + // Get file size + std::streamsize size = file.tellg(); + file.seekg(0, std::ios::beg); + + // Read entire file + std::vector buffer(size); + if (!file.read(buffer.data(), size)) + { + Log::error("Failed to read file: " + filepath); + return {}; + } + + return buffer; +} + +std::string FileHandler::readFileAsString(const std::string &filepath) +{ + Log::trace(LOCATION); + + std::ifstream file(filepath, std::ios::binary); + if (!file.is_open()) + { + return ""; + } + + return std::string(std::istreambuf_iterator(file), std::istreambuf_iterator()); +} + +std::unique_ptr FileHandler::getResponse() const +{ + Log::trace(LOCATION); + auto response = std::make_unique(); + response->setStatus(200); + std::string filepath = uriParser_.getFilePath(); + if (uriParser_.isFile()) + { + std::string extension = uriParser_.getExtension(); + std::string mimeType = MIMETypes().getType(extension); + response->addHeader("Content-Type", mimeType); + + std::vector fileData = readBinaryFile(filepath); + Log::debug("Serving file: " + filepath + " with MIME type: " + mimeType); + if (fileData.empty()) + { + return ErrorHandler::getErrorResponse(404); + } + response->setBody(std::string(fileData.begin(), fileData.end())); + } + else if (uriParser_.isDirectory() && location_->get>("index").has_value()) + { + auto possible_indexes = location_->get>("index").value(); + std::string indexPath; + for (auto &index : possible_indexes) + { + indexPath = FileUtils::joinPath(filepath, index); + Log::debug("Checking for index file: " + indexPath); + if (FileUtils::isFile(indexPath)) + { + Log::debug("Found index file: " + indexPath); + break; + } + indexPath.clear(); + } + + std::vector fileData = readBinaryFile(indexPath); + Log::debug("Serving index file: " + indexPath); + if (fileData.empty()) + { + return ErrorHandler::getErrorResponse(404); + } + + std::string extension = FileUtils::getExtension(indexPath); + std::string mimeType = MIMETypes().getType(extension); + response->addHeader("Content-Type", mimeType); + response->setBody(std::string(fileData.begin(), fileData.end())); + } + + else if (uriParser_.isDirectory() && location_->get("autoindex").value_or(false)) + { + Log::debug("Requested path is a directory: " + filepath); + return ErrorHandler::getErrorResponse(403); + } + else + { + return ErrorHandler::getErrorResponse(404); + } + return response; +} \ No newline at end of file diff --git a/webserv/handler/FileHandler.hpp b/webserv/handler/FileHandler.hpp index 7b9637e..f8aa1bb 100644 --- a/webserv/handler/FileHandler.hpp +++ b/webserv/handler/FileHandler.hpp @@ -1 +1,23 @@ -#pragma once \ No newline at end of file +#pragma once + +#include "webserv/config/LocationConfig.hpp" +#include "webserv/handler/URIParser.hpp" +#include "webserv/http/HttpResponse.hpp" + +#include +#include + +class FileHandler +{ + public: + FileHandler(const LocationConfig *location, const URIParser &uriParser); + + [[nodiscard]] std::unique_ptr getResponse() const; + static std::vector readBinaryFile(const std ::string &filepath); + static std::string readFileAsString(const std::string &filepath); + + private: + // std::unique_ptr response_; + const LocationConfig *location_; + const URIParser &uriParser_; +}; \ No newline at end of file diff --git a/webserv/handler/MIMETypes.cpp b/webserv/handler/MIMETypes.cpp new file mode 100644 index 0000000..7a55684 --- /dev/null +++ b/webserv/handler/MIMETypes.cpp @@ -0,0 +1,37 @@ + +#include + + + +MIMETypes::MIMETypes() { + initializeDefaults(); +} + +std::string MIMETypes::getType(const std::string& extension) const { + auto it = mimeMap.find(extension); + if (it != mimeMap.end()) { + return it->second; + } + return "application/octet-stream"; // Default MIME type +} + +void MIMETypes::setType(const std::string& extension, const std::string& mimeType) { + mimeMap[extension] = mimeType; +} + +void MIMETypes::initializeDefaults() { + mimeMap["html"] = "text/html"; + mimeMap["htm"] = "text/html"; + mimeMap["css"] = "text/css"; + mimeMap["js"] = "application/javascript"; + mimeMap["json"] = "application/json"; + mimeMap["png"] = "image/png"; + mimeMap["jpg"] = "image/jpeg"; + mimeMap["jpeg"] = "image/jpeg"; + mimeMap["gif"] = "image/gif"; + mimeMap["svg"] = "image/svg+xml"; + mimeMap["txt"] = "text/plain"; + mimeMap["xml"] = "application/xml"; + mimeMap["pdf"] = "application/pdf"; + // Add more default MIME types as needed +} \ No newline at end of file diff --git a/webserv/handler/MIMETypes.hpp b/webserv/handler/MIMETypes.hpp new file mode 100644 index 0000000..95f653c --- /dev/null +++ b/webserv/handler/MIMETypes.hpp @@ -0,0 +1,24 @@ +#ifndef MIMETYPES_HPP +#define MIMETYPES_HPP + +#include +#include + +class MIMETypes { +public: + MIMETypes(); + + + // Returns the MIME type for a given file extension (e.g., "html" -> "text/html") + [[nodiscard]] std::string getType(const std::string& extension) const; + + // Adds or updates a MIME type mapping + void setType(const std::string& extension, const std::string& mimeType); + +private: + std::map mimeMap; + + void initializeDefaults(); +}; + +#endif // MIMETYPES_HPP \ No newline at end of file diff --git a/webserv/handler/URIParser.cpp b/webserv/handler/URIParser.cpp new file mode 100644 index 0000000..19a8df2 --- /dev/null +++ b/webserv/handler/URIParser.cpp @@ -0,0 +1,94 @@ +#include +#include +#include + +#include // for stat, S_ISREG, S_ISDIR + +URIParser::URIParser(const std::string &uri, const ServerConfig &serverConfig) : _locationConfig(nullptr) +{ + const auto &locations = serverConfig.getLocationPaths(); + size_t maxMatchLength = 0; + + for (const auto &locationPath : locations) + { + if (uri.starts_with((locationPath == "/") ? locationPath : locationPath + "/")) + { // TODO HMHMMz why does it need to end on a /? + if (locationPath.length() > maxMatchLength) + { + maxMatchLength = locationPath.length(); + _locationConfig = serverConfig.getLocation(locationPath); + } + } + } + + root_ = _locationConfig != nullptr ? _locationConfig->get("root").value_or("") : ""; + if (!root_.empty() && root_.back() == '/' ) + { + root_.pop_back(); // Remove trailing slash to avoid double slashes in path + } + + relativePath_ = uri.substr(maxMatchLength); + if (relativePath_.empty() || relativePath_[0] != '/') + { + relativePath_ = "/" + relativePath_; + } +} + +std::string URIParser::getFilePath() const +{ + return root_ + relativePath_; +} + +std::string URIParser::getFilename() const +{ + size_t lastSlash = relativePath_.find_last_of('/'); + if (lastSlash == std::string::npos) + { + return relativePath_; // No slashes, return the whole path + } + return relativePath_.substr(lastSlash + 1); +} + +std::string URIParser::getExtension() const +{ + std::string filename = getFilename(); + size_t lastDot = filename.find_last_of('.'); + if (lastDot == std::string::npos || lastDot == 0 || lastDot == filename.length() - 1) + { + return ""; // No extension found or dot is at start/end + } + return filename.substr(lastDot + 1); +} + + +LocationConfig const *URIParser::getLocation() const +{ + return _locationConfig; +} + +bool URIParser::isFile() const +{ + struct stat pathStat{}; + if (stat(getFilePath().c_str(), &pathStat) != 0) + { + return false; + } + return S_ISREG(pathStat.st_mode); +} + +bool URIParser::isDirectory() const +{ + struct stat pathStat{}; + if (stat(getFilePath().c_str(), &pathStat) != 0) + { + return false; + } + return S_ISDIR(pathStat.st_mode); +} + +bool URIParser::isValid() const +{ + struct stat pathStat{}; + return stat(getFilePath().c_str(), &pathStat) == 0; +} + diff --git a/webserv/handler/URIParser.hpp b/webserv/handler/URIParser.hpp new file mode 100644 index 0000000..bf6aa1c --- /dev/null +++ b/webserv/handler/URIParser.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include "webserv/config/LocationConfig.hpp" +#include "webserv/config/ServerConfig.hpp" +#include "webserv/server/Server.hpp" + +class URIParser +{ + public: + URIParser(const std::string &uri, const ServerConfig &serverConfig); + + + [[nodiscard]] std::string getFilePath() const; + [[nodiscard]] std::string getFilename() const; + [[nodiscard]] std::string getExtension() const; + + + [[nodiscard]] const LocationConfig *getLocation() const ; + + [[nodiscard]] bool isFile() const; + + [[nodiscard]] bool isDirectory() const; + [[nodiscard]] bool isValid() const; + + private: + const LocationConfig *_locationConfig; + std::string relativePath_; + std::string root_; +}; \ No newline at end of file diff --git a/webserv/http/HttpHeaders.cpp b/webserv/http/HttpHeaders.cpp index 02cdfbb..681dccc 100644 --- a/webserv/http/HttpHeaders.cpp +++ b/webserv/http/HttpHeaders.cpp @@ -1,8 +1,7 @@ -#include // for HttpHeaders - -#include // for trim #include // for CRLF +#include // for HttpHeaders #include +#include // for trim #include // for __transform_fn, transform #include // for tolower diff --git a/webserv/http/HttpRequest.cpp b/webserv/http/HttpRequest.cpp index b801f01..2a868d5 100644 --- a/webserv/http/HttpRequest.cpp +++ b/webserv/http/HttpRequest.cpp @@ -1,9 +1,8 @@ -#include - #include // for Client -#include // for stoul #include // for CRLF, DOUBLE_CRLF, BAD_REQUEST -#include // for Log, LOCATION +#include +#include // for Log, LOCATION +#include // for stoul #include // for map #include // for optional diff --git a/webserv/main.cpp b/webserv/main.cpp index b7dcd5c..5b674f4 100644 --- a/webserv/main.cpp +++ b/webserv/main.cpp @@ -15,8 +15,8 @@ int main(int argc, char **argv) std::cerr << "Usage: " << argv[0] << " \n"; // NOLINT return 1; } - Log::setFileChannel("webserv.log", std::ios_base::app, Log::Level::Info); - Log::setStdoutChannel(Log::Level::Info); + Log::setFileChannel("webserv.log", std::ios_base::trunc, Log::Level::Info); + Log::setStdoutChannel(Log::Level::Trace); Log::info("\n======================\nStarting webserv...\n======================\n"); ConfigManager::getInstance().init(argv[1]); // NOLINT ConfigManager &configManager = ConfigManager::getInstance(); diff --git a/webserv/router/Router.cpp b/webserv/router/Router.cpp index f8ab561..3d8055f 100644 --- a/webserv/router/Router.cpp +++ b/webserv/router/Router.cpp @@ -2,80 +2,46 @@ #include "webserv/config/ConfigManager.hpp" #include "webserv/config/ServerConfig.hpp" #include "webserv/handler/ErrorHandler.hpp" +#include "webserv/handler/FileHandler.hpp" +#include "webserv/handler/URIParser.hpp" #include "webserv/http/HttpResponse.hpp" #include "webserv/log/Log.hpp" #include +#include #include Router::Router() {} -HttpResponse Router::handleRequest(const HttpRequest &request) const +std::unique_ptr Router::handleRequest(const HttpRequest &request) const { Log::trace(LOCATION); ServerConfig *config = ConfigManager::getInstance().getMatchingServerConfig(request.getHeaders().getHost().value_or("")); - HttpResponse response; if (config == nullptr) { - Log::warning("No matching server config found"); - handleError(400, response); - return response; + return ErrorHandler::getErrorResponse(400); } + URIParser uriParser{request.getTarget(), *config}; const std::string &target = request.getTarget(); const std::string &method = request.getMethod(); - const LocationConfig *location = getLocation(target, *config); + const LocationConfig *location = uriParser.getLocation(); if (location == nullptr) { - Log::warning("No matching location found for target: " + target); - handleError(404, response, config); - return response; + return ErrorHandler::getErrorResponse(404); } - response.setStatus(200); - response.addHeader("Content-Type", "text/plain"); - response.setBody("Hello, World! You requested " + target + " on " + request.getHeaders().getHost().value_or("") + - ". Current resource is " + location->getPath()); - response.setComplete(); - return response; + FileHandler fileHandler(location, uriParser); + return fileHandler.getResponse(); } -void Router::handleError(int statusCode, HttpResponse &response, AConfig *config) const -{ - response = ErrorHandler::getErrorResponse(statusCode, config); - response.setComplete(); -} - -const LocationConfig *Router::getLocation(const std::string &path, const ServerConfig &serverConfig) const -{ - const LocationConfig *bestMatch = nullptr; - size_t maxSize = 0; - - std::vector locations = serverConfig.getLocationPaths(); - - for (const auto &locPath : locations) - { - if (!path.starts_with(locPath)) - { - continue; - } - if (locPath.length() > maxSize && - (path.length() == locPath.length() || locPath.back() == '/' || path[locPath.length()] == '/')) - { - maxSize = locPath.length(); - bestMatch = serverConfig.getLocation(locPath); - Log::debug("Found new best match: " + locPath); - } - } - - if (bestMatch == nullptr) - { - Log::warning("No location matched for path: " + path); - } - return bestMatch; -} \ No newline at end of file +// void Router::handleError(int statusCode, HttpResponse &response, AConfig *config) const +// { +// response = ErrorHandler::getErrorResponse(statusCode, config); +// response.setComplete(); +// } diff --git a/webserv/router/Router.hpp b/webserv/router/Router.hpp index eb9ea3c..f9cb958 100644 --- a/webserv/router/Router.hpp +++ b/webserv/router/Router.hpp @@ -5,6 +5,7 @@ #include #include +#include #include class Router @@ -12,9 +13,9 @@ class Router public: Router(); - [[nodiscard]] HttpResponse handleRequest(const HttpRequest &request) const; + [[nodiscard]] std::unique_ptr handleRequest(const HttpRequest &request) const; private: - void handleError(int statusCode, HttpResponse &response, AConfig *config = nullptr) const; + // void handleError(int statusCode, HttpResponse &response, AConfig *config = nullptr) const; [[nodiscard]] const LocationConfig *getLocation(const std::string &path, const ServerConfig &serverConfig) const; }; \ No newline at end of file diff --git a/webserv/server/Server.cpp b/webserv/server/Server.cpp index 47e1b55..1323dcf 100644 --- a/webserv/server/Server.cpp +++ b/webserv/server/Server.cpp @@ -102,8 +102,9 @@ void Server::setupServerSocket(const ServerConfig &config) Log::trace(LOCATION); try { - auto host = config.getDirectiveValue("host"); - auto port = config.getDirectiveValue("listen"); + auto host = config.get("host").value_or(std::string()); // TODO should not be a default host + + auto port = config.get("listen").value_or(0); // TODO should not be a default port std::unique_ptr serverSocket = std::make_unique(); serverSocket->bind(host, port); serverSocket->listen(SOMAXCONN); diff --git a/webserv/utils/FileUtils.cpp b/webserv/utils/FileUtils.cpp new file mode 100644 index 0000000..0bc0f9c --- /dev/null +++ b/webserv/utils/FileUtils.cpp @@ -0,0 +1,61 @@ +#include + +#include // for strlen +#include // for string + +#include // for stat, S_ISREG, S_ISDIR + +namespace FileUtils +{ +bool isDirectory(const std::string &path) +{ + struct stat pathStat{}; + if (stat(path.c_str(), &pathStat) != 0) + { + return false; // Could not access path + } + return S_ISDIR(pathStat.st_mode); +} + +bool isFile(const std::string &path) +{ + struct stat pathStat{}; + if (stat(path.c_str(), &pathStat) != 0) + { + return false; // Could not access path + } + return S_ISREG(pathStat.st_mode); +} + +std::string getExtension(const std::string &filename) +{ + size_t dotPos = filename.find_last_of('.'); + if (dotPos == std::string::npos || dotPos == filename.length() - 1) + { + return ""; // No extension found + } + return filename.substr(dotPos + 1); +} + +std::string joinPath(const std::string &base, const std::string &addition) // NOLINT +{ + std::string result = base; + if (result.empty()) + { + result = addition; + } + else if (result.back() == '/' && addition.front() == '/') + { + result += addition.substr(1); + } + else if (result.back() != '/' && addition.front() != '/') + { + result += '/' + addition; + } + else + { + result += addition; + } + return result; +} +} // namespace FileUtils \ No newline at end of file diff --git a/webserv/utils/FileUtils.hpp b/webserv/utils/FileUtils.hpp new file mode 100644 index 0000000..9c94096 --- /dev/null +++ b/webserv/utils/FileUtils.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include + +namespace FileUtils +{ +bool isDirectory(const std::string &path); +bool isFile(const std::string &path); +std::string getExtension(const std::string &filename); +std::string joinPath(const std::string &base, const std::string &addition); +} // namespace FileUtils \ No newline at end of file diff --git a/webserv/config/utils.cpp b/webserv/utils/utils.cpp similarity index 97% rename from webserv/config/utils.cpp rename to webserv/utils/utils.cpp index 94f927e..a902758 100644 --- a/webserv/config/utils.cpp +++ b/webserv/utils/utils.cpp @@ -1,5 +1,5 @@ -#include +#include #include #include @@ -104,4 +104,4 @@ void removeComments(std::string &str) } removeEmptyLines(str); } -} // namespace utils +} // namespace utils \ No newline at end of file diff --git a/webserv/config/utils.hpp b/webserv/utils/utils.hpp similarity index 94% rename from webserv/config/utils.hpp rename to webserv/utils/utils.hpp index 70cb6c0..002b5e3 100644 --- a/webserv/config/utils.hpp +++ b/webserv/utils/utils.hpp @@ -11,4 +11,4 @@ std::string trim(const std::string &str); size_t findCorrespondingClosingBrace(const std::string &str, size_t openPos); void removeEmptyLines(std::string &str); void removeComments(std::string &str); -} // namespace utils +} // namespace utils \ No newline at end of file