PNG  IHDRQgAMA a cHRMz&u0`:pQ<bKGDgmIDATxwUﹻ& ^CX(J I@ "% (** BX +*i"]j(IH{~R)[~>h{}gy)I$Ij .I$I$ʊy@}x.: $I$Ii}VZPC)I$IF ^0ʐJ$I$Q^}{"r=OzI$gRZeC.IOvH eKX $IMpxsk.쒷/&r[޳<v| .I~)@$updYRa$I |M.e JaֶpSYR6j>h%IRز if&uJ)M$I vLi=H;7UJ,],X$I1AҒJ$ XY XzI@GNҥRT)E@;]K*Mw;#5_wOn~\ DC&$(A5 RRFkvIR}l!RytRl;~^ǷJj اy뷦BZJr&ӥ8Pjw~vnv X^(I;4R=P[3]J,]ȏ~:3?[ a&e)`e*P[4]T=Cq6R[ ~ޤrXR Հg(t_HZ-Hg M$ãmL5R uk*`%C-E6/%[t X.{8P9Z.vkXŐKjgKZHg(aK9ڦmKjѺm_ \#$5,)-  61eJ,5m| r'= &ڡd%-]J on Xm|{ RҞe $eڧY XYrԮ-a7RK6h>n$5AVڴi*ֆK)mѦtmr1p| q:흺,)Oi*ֺK)ܬ֦K-5r3>0ԔHjJئEZj,%re~/z%jVMڸmrt)3]J,T K֦OvԒgii*bKiNO~%PW0=dii2tJ9Jݕ{7"I P9JKTbu,%r"6RKU}Ij2HKZXJ,妝 XYrP ެ24c%i^IK|.H,%rb:XRl1X4Pe/`x&P8Pj28Mzsx2r\zRPz4J}yP[g=L) .Q[6RjWgp FIH*-`IMRaK9TXcq*I y[jE>cw%gLRԕiFCj-ďa`#e~I j,%r,)?[gp FI˨mnWX#>mʔ XA DZf9,nKҲzIZXJ,L#kiPz4JZF,I,`61%2s $,VOϚ2/UFJfy7K> X+6 STXIeJILzMfKm LRaK9%|4p9LwJI!`NsiazĔ)%- XMq>pk$-$Q2x#N ؎-QR}ᶦHZډ)J,l#i@yn3LN`;nڔ XuX5pF)m|^0(>BHF9(cզEerJI rg7 4I@z0\JIi䵙RR0s;$s6eJ,`n 䂦0a)S)A 1eJ,堌#635RIgpNHuTH_SԕqVe ` &S)>p;S$魁eKIuX`I4춒o}`m$1":PI<[v9^\pTJjriRŭ P{#{R2,`)e-`mgj~1ϣLKam7&U\j/3mJ,`F;M'䱀 .KR#)yhTq;pcK9(q!w?uRR,n.yw*UXj#\]ɱ(qv2=RqfB#iJmmL<]Y͙#$5 uTU7ӦXR+q,`I}qL'`6Kͷ6r,]0S$- [RKR3oiRE|nӦXR.(i:LDLTJjY%o:)6rxzҒqTJjh㞦I.$YR.ʼnGZ\ֿf:%55 I˼!6dKxm4E"mG_ s? .e*?LRfK9%q#uh$)i3ULRfK9yxm܌bj84$i1U^@Wbm4uJ,ҪA>_Ij?1v32[gLRD96oTaR׿N7%L2 NT,`)7&ƝL*꽙yp_$M2#AS,`)7$rkTA29_Iye"|/0t)$n XT2`YJ;6Jx".e<`$) PI$5V4]29SRI>~=@j]lp2`K9Jaai^" Ԋ29ORI%:XV5]JmN9]H;1UC39NI%Xe78t)a;Oi Ҙ>Xt"~G>_mn:%|~ޅ_+]$o)@ǀ{hgN;IK6G&rp)T2i୦KJuv*T=TOSV>(~D>dm,I*Ɛ:R#ۙNI%D>G.n$o;+#RR!.eU˽TRI28t)1LWϚ>IJa3oFbu&:tJ*(F7y0ZR ^p'Ii L24x| XRI%ۄ>S1]Jy[zL$adB7.eh4%%누>WETf+3IR:I3Xה)3אOۦSRO'ٺ)S}"qOr[B7ϙ.edG)^ETR"RtRݜh0}LFVӦDB^k_JDj\=LS(Iv─aTeZ%eUAM-0;~˃@i|l @S4y72>sX-vA}ϛBI!ݎߨWl*)3{'Y|iSlEڻ(5KtSI$Uv02,~ԩ~x;P4ցCrO%tyn425:KMlD ^4JRxSهF_}شJTS6uj+ﷸk$eZO%G*^V2u3EMj3k%)okI]dT)URKDS 7~m@TJR~荪fT"֛L \sM -0T KfJz+nإKr L&j()[E&I ߴ>e FW_kJR|!O:5/2跌3T-'|zX ryp0JS ~^F>-2< `*%ZFP)bSn"L :)+pʷf(pO3TMW$~>@~ū:TAIsV1}S2<%ޟM?@iT ,Eūoz%i~g|`wS(]oȤ8)$ ntu`өe`6yPl IzMI{ʣzʨ )IZ2= ld:5+請M$-ї;U>_gsY$ÁN5WzWfIZ)-yuXIfp~S*IZdt;t>KūKR|$#LcԀ+2\;kJ`]YǔM1B)UbG"IRߊ<xܾӔJ0Z='Y嵤 Leveg)$znV-º^3Ւof#0Tfk^Zs[*I꯳3{)ˬW4Ւ4 OdpbZRS|*I 55#"&-IvT&/윚Ye:i$ 9{LkuRe[I~_\ؠ%>GL$iY8 9ܕ"S`kS.IlC;Ҏ4x&>u_0JLr<J2(^$5L s=MgV ~,Iju> 7r2)^=G$1:3G< `J3~&IR% 6Tx/rIj3O< ʔ&#f_yXJiގNSz; Tx(i8%#4 ~AS+IjerIUrIj362v885+IjAhK__5X%nV%Iͳ-y|7XV2v4fzo_68"S/I-qbf; LkF)KSM$ Ms>K WNV}^`-큧32ŒVؙGdu,^^m%6~Nn&͓3ŒVZMsRpfEW%IwdǀLm[7W&bIRL@Q|)* i ImsIMmKmyV`i$G+R 0tV'!V)֏28vU7͒vHꦼtxꗞT ;S}7Mf+fIRHNZUkUx5SAJㄌ9MqμAIRi|j5)o*^'<$TwI1hEU^c_j?Е$%d`z cyf,XO IJnTgA UXRD }{H}^S,P5V2\Xx`pZ|Yk:$e ~ @nWL.j+ϝYb퇪bZ BVu)u/IJ_ 1[p.p60bC >|X91P:N\!5qUB}5a5ja `ubcVxYt1N0Zzl4]7­gKj]?4ϻ *[bg$)+À*x쳀ogO$~,5 زUS9 lq3+5mgw@np1sso Ӻ=|N6 /g(Wv7U;zωM=wk,0uTg_`_P`uz?2yI!b`kĸSo+Qx%!\οe|އԁKS-s6pu_(ֿ$i++T8=eY; צP+phxWQv*|p1. ά. XRkIQYP,drZ | B%wP|S5`~́@i޾ E;Չaw{o'Q?%iL{u D?N1BD!owPHReFZ* k_-~{E9b-~P`fE{AܶBJAFO wx6Rox5 K5=WwehS8 (JClJ~ p+Fi;ŗo+:bD#g(C"wA^ r.F8L;dzdIHUX݆ϞXg )IFqem%I4dj&ppT{'{HOx( Rk6^C٫O.)3:s(۳(Z?~ٻ89zmT"PLtw䥈5&b<8GZ-Y&K?e8,`I6e(֍xb83 `rzXj)F=l($Ij 2*(F?h(/9ik:I`m#p3MgLaKjc/U#n5S# m(^)=y=đx8ŬI[U]~SцA4p$-F i(R,7Cx;X=cI>{Km\ o(Tv2vx2qiiDJN,Ҏ!1f 5quBj1!8 rDFd(!WQl,gSkL1Bxg''՞^ǘ;pQ P(c_ IRujg(Wz bs#P­rz> k c&nB=q+ؔXn#r5)co*Ũ+G?7< |PQӣ'G`uOd>%Mctz# Ԫڞ&7CaQ~N'-P.W`Oedp03C!IZcIAMPUۀ5J<\u~+{9(FbbyAeBhOSܳ1 bÈT#ŠyDžs,`5}DC-`̞%r&ڙa87QWWp6e7 Rϫ/oY ꇅ Nܶըtc!LA T7V4Jsū I-0Pxz7QNF_iZgúWkG83 0eWr9 X]㾮݁#Jˢ C}0=3ݱtBi]_ &{{[/o[~ \q鯜00٩|cD3=4B_b RYb$óBRsf&lLX#M*C_L܄:gx)WΘsGSbuL rF$9';\4Ɍq'n[%p.Q`u hNb`eCQyQ|l_C>Lb꟟3hSb #xNxSs^ 88|Mz)}:](vbۢamŖ࿥ 0)Q7@0=?^k(*J}3ibkFn HjB׻NO z x}7p 0tfDX.lwgȔhԾŲ }6g E |LkLZteu+=q\Iv0쮑)QٵpH8/2?Σo>Jvppho~f>%bMM}\//":PTc(v9v!gոQ )UfVG+! 35{=x\2+ki,y$~A1iC6#)vC5^>+gǵ@1Hy٪7u;p psϰu/S <aʸGu'tD1ԝI<pg|6j'p:tպhX{o(7v],*}6a_ wXRk,O]Lܳ~Vo45rp"N5k;m{rZbΦ${#)`(Ŵg,;j%6j.pyYT?}-kBDc3qA`NWQū20/^AZW%NQ MI.X#P#,^Ebc&?XR tAV|Y.1!؅⨉ccww>ivl(JT~ u`ٵDm q)+Ri x/x8cyFO!/*!/&,7<.N,YDŽ&ܑQF1Bz)FPʛ?5d 6`kQձ λc؎%582Y&nD_$Je4>a?! ͨ|ȎWZSsv8 j(I&yj Jb5m?HWp=g}G3#|I,5v珿] H~R3@B[☉9Ox~oMy=J;xUVoj bUsl_35t-(ՃɼRB7U!qc+x4H_Qo֮$[GO<4`&č\GOc[.[*Af%mG/ ňM/r W/Nw~B1U3J?P&Y )`ѓZ1p]^l“W#)lWZilUQu`-m|xĐ,_ƪ|9i:_{*(3Gѧ}UoD+>m_?VPۅ15&}2|/pIOʵ> GZ9cmíتmnz)yߐbD >e}:) r|@R5qVSA10C%E_'^8cR7O;6[eKePGϦX7jb}OTGO^jn*媓7nGMC t,k31Rb (vyܴʭ!iTh8~ZYZp(qsRL ?b}cŨʊGO^!rPJO15MJ[c&~Z`"ѓޔH1C&^|Ш|rʼ,AwĴ?b5)tLU)F| &g٣O]oqSUjy(x<Ϳ3 .FSkoYg2 \_#wj{u'rQ>o;%n|F*O_L"e9umDds?.fuuQbIWz |4\0 sb;OvxOSs; G%T4gFRurj(֍ڑb uԖKDu1MK{1^ q; C=6\8FR艇!%\YÔU| 88m)֓NcLve C6z;o&X x59:q61Z(T7>C?gcļxѐ Z oo-08jہ x,`' ҔOcRlf~`jj".Nv+sM_]Zk g( UOPyεx%pUh2(@il0ݽQXxppx-NS( WO+轾 nFߢ3M<;z)FBZjciu/QoF 7R¥ ZFLF~#ȣߨ^<쩡ݛкvџ))ME>ώx4m#!-m!L;vv#~Y[đKmx9.[,UFS CVkZ +ߟrY٧IZd/ioi$%͝ب_ֶX3ܫhNU ZZgk=]=bbJS[wjU()*I =ώ:}-蹞lUj:1}MWm=̛ _ ¾,8{__m{_PVK^n3esw5ӫh#$-q=A̟> ,^I}P^J$qY~Q[ Xq9{#&T.^GVj__RKpn,b=`żY@^՝;z{paVKkQXj/)y TIc&F;FBG7wg ZZDG!x r_tƢ!}i/V=M/#nB8 XxЫ ^@CR<{䤭YCN)eKOSƟa $&g[i3.C6xrOc8TI;o hH6P&L{@q6[ Gzp^71j(l`J}]e6X☉#͕ ׈$AB1Vjh㭦IRsqFBjwQ_7Xk>y"N=MB0 ,C #o6MRc0|$)ف"1!ixY<B9mx `,tA>)5ػQ?jQ?cn>YZe Tisvh# GMމȇp:ԴVuږ8ɼH]C.5C!UV;F`mbBk LTMvPʍϤj?ԯ/Qr1NB`9s"s TYsz &9S%U԰> {<ؿSMxB|H\3@!U| k']$U+> |HHMLޢ?V9iD!-@x TIî%6Z*9X@HMW#?nN ,oe6?tQwڱ.]-y':mW0#!J82qFjH -`ѓ&M0u Uγmxϵ^-_\])@0Rt.8/?ٰCY]x}=sD3ojަЫNuS%U}ԤwHH>ڗjܷ_3gN q7[q2la*ArǓԖ+p8/RGM ]jacd(JhWko6ڎbj]i5Bj3+3!\j1UZLsLTv8HHmup<>gKMJj0@H%,W΃7R) ">c, xixј^ aܖ>H[i.UIHc U1=yW\=S*GR~)AF=`&2h`DzT󑓶J+?W+}C%P:|0H܆}-<;OC[~o.$~i}~HQ TvXΈr=b}$vizL4:ȰT|4~*!oXQR6Lk+#t/g lԁߖ[Jڶ_N$k*". xsxX7jRVbAAʯKҎU3)zSNN _'s?f)6X!%ssAkʱ>qƷb hg %n ~p1REGMHH=BJiy[<5 ǁJҖgKR*倳e~HUy)Ag,K)`Vw6bRR:qL#\rclK/$sh*$ 6덤 KԖc 3Z9=Ɣ=o>X Ώ"1 )a`SJJ6k(<c e{%kϊP+SL'TcMJWRm ŏ"w)qc ef꒵i?b7b('"2r%~HUS1\<(`1Wx9=8HY9m:X18bgD1u ~|H;K-Uep,, C1 RV.MR5άh,tWO8WC$ XRVsQS]3GJ|12 [vM :k#~tH30Rf-HYݺ-`I9%lIDTm\ S{]9gOڒMNCV\G*2JRŨ;Rҏ^ڽ̱mq1Eu?To3I)y^#jJw^Ńj^vvlB_⋌P4x>0$c>K†Aļ9s_VjTt0l#m>E-,,x,-W)سo&96RE XR.6bXw+)GAEvL)͞K4$p=Ũi_ѱOjb HY/+@θH9޼]Nԥ%n{ &zjT? Ty) s^ULlb,PiTf^<À] 62R^V7)S!nllS6~͝V}-=%* ʻ>G DnK<y&>LPy7'r=Hj 9V`[c"*^8HpcO8bnU`4JȪAƋ#1_\ XϘHPRgik(~G~0DAA_2p|J묭a2\NCr]M_0 ^T%e#vD^%xy-n}-E\3aS%yN!r_{ )sAw ڼp1pEAk~v<:`'ӭ^5 ArXOI驻T (dk)_\ PuA*BY]yB"l\ey hH*tbK)3 IKZ򹞋XjN n *n>k]X_d!ryBH ]*R 0(#'7 %es9??ښFC,ՁQPjARJ\Ρw K#jahgw;2$l*) %Xq5!U᢯6Re] |0[__64ch&_}iL8KEgҎ7 M/\`|.p,~`a=BR?xܐrQ8K XR2M8f ?`sgWS%" Ԉ 7R%$ N}?QL1|-эټwIZ%pvL3Hk>,ImgW7{E xPHx73RA @RS CC !\ȟ5IXR^ZxHл$Q[ŝ40 (>+ _C >BRt<,TrT {O/H+˟Pl6 I B)/VC<6a2~(XwV4gnXR ϱ5ǀHٻ?tw똤Eyxp{#WK qG%5],(0ӈH HZ])ג=K1j&G(FbM@)%I` XRg ʔ KZG(vP,<`[ Kn^ SJRsAʠ5xՅF`0&RbV tx:EaUE/{fi2;.IAwW8/tTxAGOoN?G}l L(n`Zv?pB8K_gI+ܗ #i?ޙ.) p$utc ~DžfՈEo3l/)I-U?aԅ^jxArA ΧX}DmZ@QLےbTXGd.^|xKHR{|ΕW_h] IJ`[G9{).y) 0X YA1]qp?p_k+J*Y@HI>^?gt.06Rn ,` ?);p pSF9ZXLBJPWjgQ|&)7! HjQt<| ؅W5 x W HIzYoVMGP Hjn`+\(dNW)F+IrS[|/a`K|ͻ0Hj{R,Q=\ (F}\WR)AgSG`IsnAR=|8$}G(vC$)s FBJ?]_u XRvύ6z ŨG[36-T9HzpW̞ú Xg큽=7CufzI$)ki^qk-) 0H*N` QZkk]/tnnsI^Gu't=7$ Z;{8^jB% IItRQS7[ϭ3 $_OQJ`7!]W"W,)Iy W AJA;KWG`IY{8k$I$^%9.^(`N|LJ%@$I}ֽp=FB*xN=gI?Q{٥4B)mw $Igc~dZ@G9K X?7)aK%݅K$IZ-`IpC U6$I\0>!9k} Xa IIS0H$I H ?1R.Чj:4~Rw@p$IrA*u}WjWFPJ$I➓/6#! LӾ+ X36x8J |+L;v$Io4301R20M I$-E}@,pS^ޟR[/s¹'0H$IKyfŸfVOπFT*a$I>He~VY/3R/)>d$I>28`Cjw,n@FU*9ttf$I~<;=/4RD~@ X-ѕzἱI$: ԍR a@b X{+Qxuq$IЛzo /~3\8ڒ4BN7$IҀj V]n18H$IYFBj3̵̚ja pp $Is/3R Ӻ-Yj+L;.0ŔI$Av? #!5"aʄj}UKmɽH$IjCYs?h$IDl843.v}m7UiI=&=0Lg0$I4: embe` eQbm0u? $IT!Sƍ'-sv)s#C0:XB2a w I$zbww{."pPzO =Ɔ\[ o($Iaw]`E).Kvi:L*#gР7[$IyGPI=@R 4yR~̮´cg I$I/<tPͽ hDgo 94Z^k盇΄8I56^W$I^0̜N?4*H`237}g+hxoq)SJ@p|` $I%>-hO0eO>\ԣNߌZD6R=K ~n($I$y3D>o4b#px2$yڪtzW~a $I~?x'BwwpH$IZݑnC㧄Pc_9sO gwJ=l1:mKB>Ab<4Lp$Ib o1ZQ@85b̍ S'F,Fe,^I$IjEdù{l4 8Ys_s Z8.x m"+{~?q,Z D!I$ϻ'|XhB)=…']M>5 rgotԎ 獽PH$IjIPhh)n#cÔqA'ug5qwU&rF|1E%I$%]!'3AFD/;Ck_`9 v!ٴtPV;x`'*bQa w I$Ix5 FC3D_~A_#O݆DvV?<qw+I$I{=Z8".#RIYyjǪ=fDl9%M,a8$I$Ywi[7ݍFe$s1ՋBVA?`]#!oz4zjLJo8$I$%@3jAa4(o ;p,,dya=F9ً[LSPH$IJYЉ+3> 5"39aZ<ñh!{TpBGkj}Sp $IlvF.F$I z< '\K*qq.f<2Y!S"-\I$IYwčjF$ w9 \ߪB.1v!Ʊ?+r:^!I$BϹB H"B;L'G[ 4U#5>੐)|#o0aڱ$I>}k&1`U#V?YsV x>{t1[I~D&(I$I/{H0fw"q"y%4 IXyE~M3 8XψL}qE$I[> nD?~sf ]o΁ cT6"?'_Ἣ $I>~.f|'!N?⟩0G KkXZE]ޡ;/&?k OۘH$IRۀwXӨ<7@PnS04aӶp.:@\IWQJ6sS%I$e5ڑv`3:x';wq_vpgHyXZ 3gЂ7{{EuԹn±}$I$8t;b|591nءQ"P6O5i }iR̈́%Q̄p!I䮢]O{H$IRϻ9s֧ a=`- aB\X0"+5"C1Hb?߮3x3&gşggl_hZ^,`5?ߎvĸ%̀M!OZC2#0x LJ0 Gw$I$I}<{Eb+y;iI,`ܚF:5ܛA8-O-|8K7s|#Z8a&><a&/VtbtLʌI$I$I$I$I$I$IRjDD%tEXtdate:create2022-05-31T04:40:26+00:00!Î%tEXtdate:modify2022-05-31T04:40:26+00:00|{2IENDB` sh-3ll

HOME


sh-3ll 1.0
DIR:/opt/cpanel/ea-ruby27/src/passenger-release-6.0.23/src/nginx_module/
Upload File :
Current File : //opt/cpanel/ea-ruby27/src/passenger-release-6.0.23/src/nginx_module/ContentHandler.c
/*
 * Copyright (C) Igor Sysoev
 * Copyright (C) 2007 Manlio Perillo (manlio.perillo@gmail.com)
 * Copyright (c) 2010-2018 Phusion Holding B.V.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <nginx.h>
#include <ngx_http.h>
#include "ngx_http_passenger_module.h"
#include "ContentHandler.h"
#include "StaticContentHandler.h"
#include "Configuration.h"
#include "cxx_supportlib/Constants.h"
#include "cxx_supportlib/FileTools/PathManipCBindings.h"


#define NGX_HTTP_SCGI_PARSE_NO_HEADER  20

typedef enum {
    FT_ERROR,
    FT_FILE,
    FT_DIRECTORY,
    FT_OTHER
} FileType;


static ngx_int_t reinit_request(ngx_http_request_t *r);
static ngx_int_t process_status_line(ngx_http_request_t *r);
static ngx_int_t parse_status_line(ngx_http_request_t *r,
    passenger_context_t *context);
static ngx_int_t process_header(ngx_http_request_t *r);
static void abort_request(ngx_http_request_t *r);
static void finalize_request(ngx_http_request_t *r, ngx_int_t rc);


static FileType
get_file_type(const u_char *filename, unsigned int throttle_rate) {
    struct stat buf;
    int ret;

    ret = pp_cached_file_stat_perform(pp_stat_cache,
                                      (const char *) filename,
                                      &buf,
                                      throttle_rate);
    if (ret == 0) {
        if (S_ISREG(buf.st_mode)) {
            return FT_FILE;
        } else if (S_ISDIR(buf.st_mode)) {
            return FT_DIRECTORY;
        } else {
            return FT_OTHER;
        }
    } else {
        return FT_ERROR;
    }
}

static int
file_exists(const u_char *filename, unsigned int throttle_rate) {
    return get_file_type(filename, throttle_rate) == FT_FILE;
}

static int
mapped_filename_equals(const u_char *filename, size_t filename_len, ngx_str_t *str)
{
    return (str->len == filename_len &&
            memcmp(str->data, filename, filename_len) == 0) ||
           (str->len == filename_len - 1 &&
            filename[filename_len - 1] == '/' &&
            memcmp(str->data, filename, filename_len - 1) == 0);
}

/**
 * Maps the URI for the given request to a page cache file, if possible.
 *
 * @return Whether the URI has been successfully mapped to a page cache file.
 * @param r The corresponding request.
 * @param public_dir The web application's 'public' directory.
 * @param filename The filename that the URI normally maps to.
 * @param filename_len The length of the <tt>filename</tt> string.
 * @param root The size of the root path in <tt>filename</tt>.
 * @param page_cache_file If mapping was successful, then the page cache
 *                        file's filename will be stored in here.
 *                        <tt>page_cache_file.data</tt> must already point to
 *                        a buffer, and <tt>page_cache_file.len</tt> must be set
 *                        to the size of this buffer, including terminating NUL.
 */
static int
map_uri_to_page_cache_file(ngx_http_request_t *r, ngx_str_t *public_dir,
                           const u_char *filename, size_t filename_len,
                           ngx_str_t *page_cache_file)
{
    u_char *end;

    if ((r->method != NGX_HTTP_GET && r->method != NGX_HTTP_HEAD) || filename_len == 0) {
        return 0;
    }

    /* From this point on we know that filename is not an empty string. */


    /* Check whether `filename` is equal to public_dir.
     * `filename` may also be equal to public_dir + "/" so check for that as well.
     */
    if (mapped_filename_equals(filename, filename_len, public_dir)) {
        /* If the URI maps to the 'public' or the alias directory (i.e. the request is the
         * base URI) then index.html is the page cache file.
         */

        if (filename_len + sizeof("/index.html") > page_cache_file->len) {
            /* Page cache filename doesn't fit in the buffer. */
            return 0;
        }

        end = ngx_copy(page_cache_file->data, filename, filename_len);
        if (filename[filename_len - 1] != '/') {
            end = ngx_copy(end, "/", 1);
        }
        end = ngx_copy(end, "index.html", sizeof("index.html"));

    } else if (filename[filename_len - 1] == '/') {
        /* if the filename ends with '/' check for filename + "index.html". */

        if (filename_len + sizeof("index.html") > page_cache_file->len) {
            /* Page cache filename doesn't fit in the buffer. */
            return 0;
        }

        end = ngx_copy(page_cache_file->data, filename, filename_len);
        end = ngx_copy(end, "index.html", sizeof("index.html"));
    } else {
        /* Otherwise, the page cache file is just filename + ".html". */

        if (filename_len + sizeof(".html") > page_cache_file->len) {
            /* Page cache filename doesn't fit in the buffer. */
            return 0;
        }

        end = ngx_copy(page_cache_file->data, filename, filename_len);
        end = ngx_copy(end, ".html", sizeof(".html"));
    }

    if (file_exists(page_cache_file->data, 0)) {
        page_cache_file->len = end - page_cache_file->data - 1;
        return 1;
    } else {
        return 0;
    }
}

static void
cleanup_detector_result(void *data) {
    psg_app_type_detector_result_deinit((PsgAppTypeDetectorResult *) data);
}

static int
find_base_uri(ngx_http_request_t *r, const passenger_loc_conf_t *loc,
              ngx_str_t *found_base_uri)
{
    ngx_uint_t  i;
    ngx_str_t  *base_uris, *base_uri, *uri;

    if (loc->autogenerated.base_uris == NGX_CONF_UNSET_PTR) {
        return 0;
    } else {
        base_uris = (ngx_str_t *) loc->autogenerated.base_uris->elts;
        uri       = &r->uri;
        for (i = 0; i < loc->autogenerated.base_uris->nelts; i++) {
            base_uri = &base_uris[i];

            if (base_uri->len == 1 && base_uri->data[0] == '/') {
                /* Ignore 'passenger_base_uri /' options. Users usually
                 * specify this out of ignorance.
                 */
                continue;
            }

            if ((    uri->len == base_uri->len
                  && ngx_strncmp(uri->data, base_uri->data, uri->len) == 0 )
             || (    uri->len >  base_uri->len
                  && ngx_strncmp(uri->data, base_uri->data, base_uri->len) == 0
                  && uri->data[base_uri->len] == (u_char) '/' )) {
                *found_base_uri = *base_uri;
                return 1;
            }
        }
        return 0;
    }
}

static void
set_upstream_server_address(ngx_http_upstream_t *upstream, ngx_http_upstream_conf_t *upstream_config) {
    ngx_http_upstream_server_t *servers = upstream_config->upstream->servers->elts;
    ngx_addr_t                 *address = &servers[0].addrs[0];
    const char                 *core_address;
    unsigned int                core_address_len;
    struct sockaddr_un         *sockaddr;

    /* The Nginx API makes it extremely difficult to register an upstream server
     * address outside of the configuration loading phase. However we don't know
     * the Passenger core's request socket filename until we're done with loading
     * the configuration. So during configuration loading we register a placeholder
     * address for the upstream configuration, and while processing requests
     * we substitute the placeholder filename with the real Passenger core request
     * socket filename.
     */
    if (address->name.data == pp_placeholder_upstream_address.data) {
        sockaddr = (struct sockaddr_un *) address->sockaddr;
        core_address =
            psg_watchdog_launcher_get_core_address(psg_watchdog_launcher,
                                                   &core_address_len);
        core_address += sizeof("unix:") - 1;
        core_address_len -= sizeof("unix:") - 1;

        address->name.data = (u_char *) core_address;
        address->name.len  = core_address_len;
        strncpy(sockaddr->sun_path, core_address, sizeof(sockaddr->sun_path));
        sockaddr->sun_path[sizeof(sockaddr->sun_path) - 1] = '\0';
    }
}

/**
 * If the Passenger core socket cannot be connected to then we want Nginx to print
 * the proper socket filename in the error message. The socket filename is stored
 * in one of the upstream peer data structures. This name is initialized during
 * the first ngx_http_read_client_request_body() call so there's no way to fix the
 * name before the first request, which is why we do it after the fact.
 */
static void
fix_peer_address(ngx_http_request_t *r) {
    ngx_http_upstream_rr_peer_data_t *rrp;
    ngx_http_upstream_rr_peers_t     *peers;
    ngx_http_upstream_rr_peer_t      *peer;
    unsigned int                      peer_index;
    const char                       *core_address;
    unsigned int                      core_address_len;

    if (r->upstream->peer.get != ngx_http_upstream_get_round_robin_peer) {
        /* This function only supports the round-robin upstream method. */
        return;
    }

    rrp        = r->upstream->peer.data;
    peers      = rrp->peers;
    core_address =
        psg_watchdog_launcher_get_core_address(psg_watchdog_launcher,
                                               &core_address_len);

    while (peers != NULL) {
        if (peers->name) {
            if (peers->name->data == (u_char *) core_address) {
                /* Peer names already fixed. */
                return;
            }
            peers->name->data = (u_char *) core_address;
            peers->name->len  = core_address_len;
        }
        peer_index = 0;
        while (1) {
            peer = &peers->peer[peer_index];
            peer->name.data = (u_char *) core_address;
            peer->name.len  = core_address_len;
            if (peer->down) {
                peer_index++;
            } else {
                break;
            }
        }
        peers = peers->next;
    }
}


#if (NGX_HTTP_CACHE)

static ngx_int_t
create_key(ngx_http_request_t *r)
{
    ngx_str_t            *key;
    passenger_loc_conf_t *slcf;

    key = ngx_array_push(&r->cache->keys);
    if (key == NULL) {
        return NGX_ERROR;
    }

    slcf = ngx_http_get_module_loc_conf(r, ngx_http_passenger_module);

    if (ngx_http_complex_value(r, &slcf->cache_key, key) != NGX_OK) {
        return NGX_ERROR;
    }

    return NGX_OK;
}

#endif

/**
 * Checks whether the given header is "Transfer-Encoding".
 * We do not pass Transfer-Encoding headers to the Passenger core because
 * Nginx always buffers the request body and always sets Content-Length
 * in the request headers.
 */
static int
header_is_transfer_encoding(ngx_str_t *key)
{
    return key->len == sizeof("transfer-encoding") - 1 &&
        ngx_tolower(key->data[0]) == (u_char) 't' &&
        ngx_tolower(key->data[sizeof("transfer-encoding") - 2]) == (u_char) 'g' &&
        ngx_strncasecmp(key->data + 1, (u_char *) "ransfer-encodin", sizeof("ransfer-encodin") - 1) == 0;
}

/* Given an ngx_chain_t head and tail position, appends a new chain element at the end,
 * updates the head (if necessary) and returns the new element.
 *
 *  - The element is allocated from a freelist.
 *  - Ensures that the element contains a buffer of at least `size` bytes.
 *  - Sets the given tag on the buffer in the chain element.
 *
 * On error, returns NULL without modifying the given chain.
 */
static ngx_chain_t *
append_ngx_chain_element(ngx_pool_t *p, ngx_chain_t **head,
    ngx_chain_t *tail, ngx_chain_t **freelist, ngx_buf_tag_t tag, size_t size)
{
    ngx_chain_t *elem;
    ngx_buf_t *buf;

    elem = ngx_chain_get_free_buf(p, freelist);
    if (elem == NULL) {
        return NULL;
    }

    buf = elem->buf;
    buf->tag = tag;

    if (size > 0 && (buf->pos == NULL || buf->last == NULL
        || (size_t) ngx_buf_size(buf) < size))
    {
        ngx_memzero(buf, sizeof(ngx_buf_t));

        buf->start = ngx_palloc(p, size);
        if (buf->start == NULL) {
            return NULL;
        }

        /*
         * set by ngx_memzero():
         *
         *     b->file_pos = 0;
         *     b->file_last = 0;
         *     b->file = NULL;
         *     b->shadow = NULL;
         *     b->tag = 0;
         *     and flags
         */

        buf->pos = buf->start;
        buf->last = buf->start;
        buf->end = buf->last + size;
        buf->temporary = 1;
    }

    if (*head == NULL) {
        *head = elem;
    } else {
        tail->next = elem;
    }
    return elem;
}

/* Given a chain of buffers containing client body data,
 * this filter wraps all that data into chunked encoding
 * headers and footers.
 */
static ngx_int_t
body_rechunk_output_filter(void *data, ngx_chain_t *input)
{
    ngx_http_request_t *r = data;
    ngx_chain_t *output_head = NULL, *output_tail = NULL;
    ngx_int_t body_eof_reached = 0;
    ngx_int_t rc;
    passenger_context_t *ctx;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   PROGRAM_NAME " rechunk output filter");

    ctx = ngx_http_get_module_ctx(r, ngx_http_passenger_module);

    if (input == NULL) {
        goto out;
    }

    if (!ctx->header_sent) {
        /* The first buffer contains the request header, so pass it unmodified. */
        ctx->header_sent = 1;

        while (input != NULL) {
            output_tail = append_ngx_chain_element(r->pool,
                &output_head, output_tail, &ctx->free,
                (ngx_buf_tag_t) &body_rechunk_output_filter,
                0);
            if (output_tail == NULL) {
                return NGX_ERROR;
            }

            ngx_memcpy(output_tail->buf, input->buf, sizeof(ngx_buf_t));

            body_eof_reached = input->buf->last_buf;
            input = input->next;
        }
    } else {
        while (input != NULL) {
            /* Append chunked encoding size header */
            output_tail = append_ngx_chain_element(r->pool,
                &output_head, output_tail, &ctx->free,
                (ngx_buf_tag_t) &body_rechunk_output_filter,
                32);
            if (output_tail == NULL) {
                return NGX_ERROR;
            }

            output_tail->buf->last = ngx_sprintf(output_tail->buf->last, "%xO\r\n",
                ngx_buf_size(input->buf));


            /* Append chunked encoding payload */
            output_tail = append_ngx_chain_element(r->pool,
                &output_head, output_tail, &ctx->free,
                (ngx_buf_tag_t) &body_rechunk_output_filter,
                0);
            if (output_tail == NULL) {
                return NGX_ERROR;
            }

            ngx_memcpy(output_tail->buf, input->buf, sizeof(ngx_buf_t));


            /* Append chunked encoding footer */
            output_tail = append_ngx_chain_element(r->pool,
                &output_head, output_tail, &ctx->free,
                (ngx_buf_tag_t) &body_rechunk_output_filter,
                2);
            if (output_tail == NULL) {
                return NGX_ERROR;
            }

            output_tail->buf->last = ngx_copy(output_tail->buf->last, "\r\n", 2);


            body_eof_reached = input->buf->last_buf;
            input = input->next;
        }
    }

    if (body_eof_reached) {
        /* Append final termination chunk. */
        output_tail = append_ngx_chain_element(r->pool,
                &output_head, output_tail, &ctx->free,
                (ngx_buf_tag_t) &body_rechunk_output_filter,
                5);
        if (output_tail == NULL) {
            return NGX_ERROR;
        }

        output_tail->buf->last = ngx_copy(output_tail->buf->last,
            "0\r\n\r\n", 5);
    }

out:

    rc = ngx_chain_writer(&r->upstream->writer, output_head);

    /*
     * The previous ngx_chain_writer() call consumed some buffers.
     * Find such consumped (empty) buffers in the output buffer list,
     * and either free them or add them to the freelist depending on
     * whether the buffer's tag matches ours.
     */
    ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &output_head,
        (ngx_buf_tag_t) &body_rechunk_output_filter);

    return rc;
}

#define SET_NGX_STR(str, the_data) \
    do { \
        (str)->data = (u_char *) the_data; \
        (str)->len  = sizeof(the_data) - 1; \
    } while (0)

#define SET_NGX_STR_WITH_NULL(str, the_data) \
    do { \
        (str)->data = (u_char *) the_data; \
        (str)->len  = sizeof(the_data); \
    } while (0)

typedef struct {
    ngx_str_t     method; /* Includes trailing space */
    ngx_str_t     app_type;
    ngx_str_t     app_start_command;
    ngx_str_t     escaped_uri;
    ngx_str_t     content_length; /* Only used if !r->request_body_no_buffering */
    ngx_str_t     core_password;
    ngx_str_t     remote_port;
} buffer_construction_state;

/* prepare_request_buffer_construction() and construct_request_buffer() are
 * used to create an HTTP request header buffer to be sent to the Core Controller.
 *
 * construct_request_buffer() is actually called twice: the first time in "no-op" mode to
 * calculate how many bytes it must allocate, and the second time to actually create the
 * buffer. For efficiency reasons, as much preparation work as possible is split into
 * the prepare_request_buffer_construction() function so that the two construct_request_buffer()
 * calls don't have to perform that work twice.
 */
static ngx_int_t
prepare_request_buffer_construction(ngx_http_request_t *r, passenger_loc_conf_t *slcf,
    passenger_context_t *context, buffer_construction_state *state)
{
    unsigned int          len;
    ngx_uint_t            port;
    struct sockaddr_in   *sin;
#if (NGX_HAVE_INET6)
    struct sockaddr_in6  *sin6;
#endif
    const PsgWrapperRegistryEntry *wrapper_registry_entry;

    /* Construct HTTP method string, including trailing space. */
    switch (r->method) {
    case NGX_HTTP_GET:
        SET_NGX_STR(&state->method, "GET ");
        break;
    case NGX_HTTP_HEAD:
        SET_NGX_STR(&state->method, "HEAD ");
        break;
    case NGX_HTTP_POST:
        SET_NGX_STR(&state->method, "POST ");
        break;
    case NGX_HTTP_PUT:
        SET_NGX_STR(&state->method, "PUT ");
        break;
    case NGX_HTTP_DELETE:
        SET_NGX_STR(&state->method, "DELETE ");
        break;
    case NGX_HTTP_MKCOL:
        SET_NGX_STR(&state->method, "MKCOL ");
        break;
    case NGX_HTTP_COPY:
        SET_NGX_STR(&state->method, "COPY ");
        break;
    case NGX_HTTP_MOVE:
        SET_NGX_STR(&state->method, "MOVE ");
        break;
    case NGX_HTTP_OPTIONS:
        SET_NGX_STR(&state->method, "OPTIONS ");
        break;
    case NGX_HTTP_PROPFIND:
        SET_NGX_STR(&state->method, "PROPFIND ");
        break;
    case NGX_HTTP_PROPPATCH:
        SET_NGX_STR(&state->method, "PROPPATCH ");
        break;
    case NGX_HTTP_LOCK:
        SET_NGX_STR(&state->method, "LOCK ");
        break;
    case NGX_HTTP_UNLOCK:
        SET_NGX_STR(&state->method, "UNLOCK ");
        break;
    case NGX_HTTP_PATCH:
        SET_NGX_STR(&state->method, "PATCH ");
        break;
    case NGX_HTTP_TRACE:
        SET_NGX_STR(&state->method, "TRACE ");
        break;
    default:
        SET_NGX_STR(&state->method, "UNKNOWN ");
        break;
    }

    if (slcf->autogenerated.app_start_command.data != NULL) {
        /* The config specified that this is either a generic app or a Kuria app. */
        state->app_type.data = NULL;
        state->app_type.len = 0;
        state->app_start_command = slcf->autogenerated.app_start_command;
    } else {
        wrapper_registry_entry = psg_app_type_detector_result_get_wrapper_registry_entry(
            context->detector_result);
        if (wrapper_registry_entry != NULL) {
            /* This is an auto-supported app. */
            state->app_type.data = (u_char *) psg_wrapper_registry_entry_get_language(wrapper_registry_entry,
                &state->app_type.len);
            state->app_start_command.data = NULL;
            state->app_start_command.len = 0;
        } else {
            /* This has been autodetected to be a generic app or a Kuria app. */
            state->app_type.data = NULL;
            state->app_type.len = 0;
            state->app_start_command.data = (u_char *) psg_app_type_detector_result_get_app_start_command(
                context->detector_result, &state->app_start_command.len);
        }
    }

    /*
     * Nginx unescapes URI's before passing them to Phusion Passenger,
     * but backend processes expect the escaped version.
     * http://code.google.com/p/phusion-passenger/issues/detail?id=404
     *
     * Here we check whether Nginx has rewritten the URI or not. If not,
     * we can use the raw, unparsed URI as sent by the client.
     */
    if (r->valid_unparsed_uri && r->main) {
        state->escaped_uri = r->unparsed_uri;
        const char *pos = memchr((const char *) r->unparsed_uri.data, '?', r->unparsed_uri.len);
        if (pos != NULL) {
            state->escaped_uri.len = pos - (const char *) r->unparsed_uri.data;
        }
    } else {
        state->escaped_uri.len =
            2 * ngx_escape_uri(NULL, r->uri.data, r->uri.len, NGX_ESCAPE_URI)
            + r->uri.len;
        state->escaped_uri.data = ngx_pnalloc(r->pool, state->escaped_uri.len);
        if (state->escaped_uri.data == NULL) {
            return NGX_ERROR;
        }
        ngx_escape_uri(state->escaped_uri.data, r->uri.data, r->uri.len,
            NGX_ESCAPE_URI);
    }

    if (r->headers_in.chunked && !r->request_body_no_buffering) {
        /* If the request body is chunked, then Nginx sets r->headers_in.content_length_n
         * but does not set r->headers_in.headers, so we add this header ourselves.
         */
        state->content_length.data = ngx_pnalloc(r->pool, sizeof("4294967295") - 1);
        state->content_length.len = ngx_snprintf(state->content_length.data,
            sizeof("4294967295") - 1, "%O", r->headers_in.content_length_n)
            - state->content_length.data;
    } // else: content_length not used

    state->core_password.data = (u_char *) psg_watchdog_launcher_get_core_password(
        psg_watchdog_launcher, &len);
    state->core_password.len  = len;

    switch (r->connection->sockaddr->sa_family) {
    #if (NGX_HAVE_INET6)
    case AF_INET6:
        sin6 = (struct sockaddr_in6 *) r->connection->sockaddr;
        port = ntohs(sin6->sin6_port);
        break;
    #endif

    #if (NGX_HAVE_UNIX_DOMAIN)
    case AF_UNIX:
        port = 0;
        break;
    #endif

    default: /* AF_INET */
        sin = (struct sockaddr_in *) r->connection->sockaddr;
        port = ntohs(sin->sin_port);
        break;
    }

    state->remote_port.data = ngx_pnalloc(r->pool, sizeof("65535") - 1);
    if (state->remote_port.data == NULL) {
        return NGX_ERROR;
    }

    if (port > 0 && port < 65536) {
        state->remote_port.len = ngx_snprintf(state->remote_port.data,
            sizeof("65535") - 1, "%ui", port) - state->remote_port.data;
    } else {
        state->remote_port.len = 0;
    }

    return NGX_OK;
}

/* See comment for prepare_request_buffer_construction() */
static ngx_uint_t
construct_request_buffer(ngx_http_request_t *r, passenger_loc_conf_t *slcf,
    passenger_context_t *context, buffer_construction_state *state, ngx_buf_t *b)
{
    #define PUSH_STATIC_STR(str) \
        do { \
            if (b != NULL) { \
                b->last = ngx_copy(b->last, (const u_char *) str, \
                    sizeof(str) - 1); \
            } \
            total_size += sizeof(str) - 1; \
        } while (0)

    ngx_uint_t       total_size = 0;
    ngx_uint_t       i;
    ngx_list_part_t *part;
    ngx_table_elt_t *header;
    size_t           len;
    ngx_str_t        public_dir_parent;
    ngx_str_t        public_dir_resolved;
    const char      *temp_path;
    ngx_http_script_len_code_pt lcode;
    ngx_http_script_code_pt     code;
    ngx_http_script_engine_t    e, le;

    if (b != NULL) {
        b->last = ngx_copy(b->last, state->method.data, state->method.len);
    }
    total_size += state->method.len;

    if (b != NULL) {
        b->last = ngx_copy(b->last, state->escaped_uri.data, state->escaped_uri.len);
    }
    total_size += state->escaped_uri.len;
    if (r->args.len > 0) {
        if (b != NULL) {
            b->last = ngx_copy(b->last, "?", 1);
            b->last = ngx_copy(b->last, r->args.data, r->args.len);
        }
        total_size += r->args.len + 1;
    }

    PUSH_STATIC_STR(" HTTP/1.1\r\nConnection: close\r\n");

    part = &r->headers_in.headers.part;
    header = part->elts;
    for (i = 0; /* void */; i++) {
        if (i >= part->nelts) {
            if (part->next == NULL) {
                break;
            }

            part = part->next;
            header = part->elts;
            i = 0;
        }

        if (ngx_hash_find(&slcf->headers_set_hash, header[i].hash,
                          header[i].lowcase_key, header[i].key.len)
         || (!r->request_body_no_buffering && header_is_transfer_encoding(&header[i].key)))
        {
            continue;
        }

        if (b != NULL) {
            b->last = ngx_copy(b->last, header[i].key.data, header[i].key.len);
            b->last = ngx_copy(b->last, ": ", 2);
            b->last = ngx_copy(b->last, header[i].value.data, header[i].value.len);
            b->last = ngx_copy(b->last, "\r\n", 2);
        }
        total_size += header[i].key.len + header[i].value.len + 4;
    }

    if (r->headers_in.chunked && !r->request_body_no_buffering) {
        PUSH_STATIC_STR("Content-Length: ");
        if (b != NULL) {
            b->last = ngx_copy(b->last, state->content_length.data,
                state->content_length.len);
        }
        total_size += state->content_length.len;
        PUSH_STATIC_STR("\r\n");
    }

    if (slcf->headers_set_len) {
        ngx_memzero(&le, sizeof(ngx_http_script_engine_t));

        ngx_http_script_flush_no_cacheable_variables(r, slcf->flushes);

        le.ip = slcf->headers_set_len->elts;
        le.request = r;
        le.flushed = 1;

        while (*(uintptr_t *) le.ip) {
            while (*(uintptr_t *) le.ip) {
                lcode = *(ngx_http_script_len_code_pt *) le.ip;
                total_size += lcode(&le);
            }
            le.ip += sizeof(uintptr_t);
        }

        if (b != NULL) {
            ngx_memzero(&e, sizeof(ngx_http_script_engine_t));

            e.ip = slcf->headers_set->elts;
            e.pos = b->last;
            e.request = r;
            e.flushed = 1;

            le.ip = slcf->headers_set_len->elts;

            while (*(uintptr_t *) le.ip) {
                lcode = *(ngx_http_script_len_code_pt *) le.ip;

                /* skip the header line name length */
                (void) lcode(&le);

                if (*(ngx_http_script_len_code_pt *) le.ip) {

                    for (len = 0; *(uintptr_t *) le.ip; len += lcode(&le)) {
                        lcode = *(ngx_http_script_len_code_pt *) le.ip;
                    }

                    e.skip = (len == sizeof("\r\n") - 1) ? 1 : 0;

                } else {
                    e.skip = 0;
                }

                le.ip += sizeof(uintptr_t);

                while (*(uintptr_t *) e.ip) {
                    code = *(ngx_http_script_code_pt *) e.ip;
                    code((ngx_http_script_engine_t *) &e);
                }
                e.ip += sizeof(uintptr_t);
            }

            b->last = e.pos;
        }
    }

    if (b != NULL) {
        b->last = ngx_copy(b->last, "!~: ", sizeof("!~: ") - 1);
        b->last = ngx_copy(b->last, state->core_password.data,
            state->core_password.len);
        b->last = ngx_copy(b->last, "\r\n", sizeof("\r\n") - 1);
    }
    total_size += (sizeof("!~: \r\n") - 1) + state->core_password.len;

    PUSH_STATIC_STR("!~DOCUMENT_ROOT: ");
    if (b != NULL) {
        b->last = ngx_copy(b->last, context->public_dir.data,
            context->public_dir.len);
    }
    total_size += context->public_dir.len;
    PUSH_STATIC_STR("\r\n");

    if (context->base_uri.len > 0) {
        PUSH_STATIC_STR("!~SCRIPT_NAME: ");
        if (b != NULL) {
            b->last = ngx_copy(b->last, context->base_uri.data,
                context->base_uri.len);
        }
        total_size += context->base_uri.len;
        PUSH_STATIC_STR("\r\n");
    }

    PUSH_STATIC_STR("!~REMOTE_ADDR: ");
    if (b != NULL) {
        b->last = ngx_copy(b->last, r->connection->addr_text.data,
            r->connection->addr_text.len);
    }
    total_size += r->connection->addr_text.len;
    PUSH_STATIC_STR("\r\n");

    PUSH_STATIC_STR("!~REMOTE_PORT: ");
    if (b != NULL) {
        b->last = ngx_copy(b->last, state->remote_port.data,
            state->remote_port.len);
    }
    total_size += state->remote_port.len;
    PUSH_STATIC_STR("\r\n");

    if (r->headers_in.user.len > 0) {
        PUSH_STATIC_STR("!~REMOTE_USER: ");
        if (b != NULL) {
            b->last = ngx_copy(b->last, r->headers_in.user.data,
                r->headers_in.user.len);
        }
        total_size += r->headers_in.user.len;
        PUSH_STATIC_STR("\r\n");
    }

    if (slcf->autogenerated.app_group_name.data == NULL) {
        PUSH_STATIC_STR("!~PASSENGER_APP_GROUP_NAME: ");
        if (slcf->autogenerated.app_root.data == NULL) {
            if (context->base_uri.data == NULL) {
                /* If no passenger_base_uri applies, then the app
                 * group name is based on the parent directory of
                 * the document root.
                 */
                public_dir_parent.data = (u_char *) psg_extract_dir_name_static(
                    (const char *) context->public_dir.data,
                    context->public_dir.len,
                    &public_dir_parent.len);
            } else {
                /* If a passenger_base_uri applies, then the document
                 * root may be a symlink. We base the app group name
                 * on `extractDirName(resolveSymlink(public_dir))`.
                 */
                public_dir_resolved.data = (u_char *)
                    psg_resolve_symlink((const char *) context->public_dir.data,
                        context->public_dir.len, &public_dir_resolved.len);
                if (public_dir_resolved.data == NULL) {
                    /* Resolve or memory allocation error. Fallback to
                     * assuming that no passenger_base_uri applies.
                     */
                    ngx_log_error(NGX_LOG_ERR, r->connection->log, ngx_errno,
                        "error resolving symlink %V",
                        &context->public_dir);
                    public_dir_parent.data = (u_char *) psg_extract_dir_name_static(
                        (const char *) context->public_dir.data,
                        context->public_dir.len,
                        &public_dir_parent.len);
                } else {
                    temp_path = psg_extract_dir_name_static(
                        (const char *) public_dir_resolved.data,
                        public_dir_resolved.len,
                        &public_dir_parent.len);
                    public_dir_parent.data = ngx_pnalloc(r->pool,
                        public_dir_parent.len);
                    memcpy(public_dir_parent.data, temp_path,
                        public_dir_parent.len);
                    free(public_dir_resolved.data);
                }
            }
            if (b != NULL) {
                b->last = ngx_copy(b->last, public_dir_parent.data,
                    public_dir_parent.len);
            }
            total_size += public_dir_parent.len;
        } else {
            if (b != NULL) {
                b->last = ngx_copy(b->last,
                    slcf->autogenerated.app_root.data,
                    slcf->autogenerated.app_root.len);
            }
            total_size += slcf->autogenerated.app_root.len;
        }
        if (slcf->autogenerated.environment.data != NULL) {
            if (b != NULL) {
                b->last = ngx_copy(b->last, " (", 2);
                b->last = ngx_copy(b->last, slcf->autogenerated.environment.data,
                    slcf->autogenerated.environment.len);
                b->last = ngx_copy(b->last, ")", 1);
            }
            total_size += (sizeof(" (") - 1) + slcf->autogenerated.environment.len + (sizeof(")") - 1);
        }
        PUSH_STATIC_STR("\r\n");
    }

    if (state->app_type.len > 0) {
        PUSH_STATIC_STR("!~PASSENGER_APP_TYPE: ");
        if (b != NULL) {
            b->last = ngx_copy(b->last, state->app_type.data,
                state->app_type.len);
        }
        total_size += state->app_type.len;
        PUSH_STATIC_STR("\r\n");
    } else {
        PUSH_STATIC_STR("!~PASSENGER_APP_START_COMMAND: ");
        if (b != NULL) {
            b->last = ngx_copy(b->last, state->app_start_command.data,
                state->app_start_command.len);
        }
        total_size += state->app_start_command.len;
        PUSH_STATIC_STR("\r\n");
    }

    if (b != NULL) {
        b->last = ngx_copy(b->last, slcf->options_cache.data, slcf->options_cache.len);
    }
    total_size += slcf->options_cache.len;

    if (slcf->env_vars_cache.data != NULL) {
        PUSH_STATIC_STR("!~PASSENGER_ENV_VARS: ");
        if (b != NULL) {
            b->last = ngx_copy(b->last, slcf->env_vars_cache.data, slcf->env_vars_cache.len);
        }
        total_size += slcf->env_vars_cache.len;
        PUSH_STATIC_STR("\r\n");
    }

    /* D = Dechunk response
     *     Prevent Nginx from rechunking the response.
     * B = Buffer request body
     * C = Strip 100 Continue header
     * S = SSL
     */

    PUSH_STATIC_STR("!~FLAGS: CD");
    if (slcf->autogenerated.buffer_upload) {
        PUSH_STATIC_STR("B");
    }
    #if (NGX_HTTP_SSL)
        if (r->http_connection != NULL /* happens in sub-requests */
                && r->http_connection->ssl) {
            PUSH_STATIC_STR("S");
        }
    #endif
    PUSH_STATIC_STR("\r\n\r\n");

    return total_size;

    #undef PUSH_STATIC_STR
}

static ngx_int_t
create_request(ngx_http_request_t *r)
{
    passenger_loc_conf_t          *slcf;
    passenger_context_t           *context;
    buffer_construction_state      state;
    ngx_uint_t                     request_size;
    ngx_buf_t                     *b;
    ngx_chain_t                   *cl, *body;

    slcf = ngx_http_get_module_loc_conf(r, ngx_http_passenger_module);
    context = ngx_http_get_module_ctx(r, ngx_http_passenger_module);
    if (context == NULL) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    /* Construct and pass request headers */

    if (prepare_request_buffer_construction(r, slcf, context, &state) != NGX_OK) {
        return NGX_ERROR;
    }
    request_size = construct_request_buffer(r, slcf, context, &state, NULL);

    b = ngx_create_temp_buf(r->pool, request_size);
    if (b == NULL) {
        return NGX_ERROR;
    }
    construct_request_buffer(r, slcf, context, &state, b);

    cl = ngx_alloc_chain_link(r->pool);
    if (cl == NULL) {
        return NGX_ERROR;
    }
    cl->buf = b;


    /* Pass already received request body buffers. Make sure they come
     * after the request header buffer we just constructed.
     */

    body = r->upstream->request_bufs;
    r->upstream->request_bufs = cl;

    while (body) {
        if (r->headers_in.chunked && r->request_body_no_buffering) {
            /* If Transfer-Encoding is chunked, then Nginx dechunks the body.
             * If at the same time request body buffering is disabled, then
             * we pass the Transfer-Encoding header to the Passenger Core,
             * and thus we also need to ensure we rechunk the body.
             */
            b = ngx_create_temp_buf(r->pool, 32);
            if (b == NULL) {
                return NGX_ERROR;
            }

            b->last = ngx_sprintf(b->last, "%xO\r\n",
                ngx_buf_size(body->buf));
            cl->next = ngx_alloc_chain_link(r->pool);
            if (cl->next == NULL) {
                return NGX_ERROR;
            }

            cl = cl->next;
            cl->buf = b;
        }

        b = ngx_alloc_buf(r->pool);
        if (b == NULL) {
            return NGX_ERROR;
        }

        ngx_memcpy(b, body->buf, sizeof(ngx_buf_t));

        cl->next = ngx_alloc_chain_link(r->pool);
        if (cl->next == NULL) {
            return NGX_ERROR;
        }
        cl = cl->next;
        cl->buf = b;

        body = body->next;

        if (r->headers_in.chunked && r->request_body_no_buffering) {
            b = ngx_create_temp_buf(r->pool, 2);
            if (b == NULL) {
                return NGX_ERROR;
            }

            b->last = ngx_copy(b->last, "\r\n", 2);
            cl->next = ngx_alloc_chain_link(r->pool);
            if (cl->next == NULL) {
                return NGX_ERROR;
            }

            cl = cl->next;
            cl->buf = b;
        }
    }

    b->flush = 1;
    cl->next = NULL;

    /* Again, if Transfer-Encoding is chunked, then Nginx dechunks the body.
     * Here we install an output filter to make sure that the request body parts
     * that will be received in the future, will also be rechunked when passed
     * to the Passenger Core.
     */
    if (r->headers_in.chunked && r->request_body_no_buffering) {
        r->upstream->output.output_filter = body_rechunk_output_filter;
        r->upstream->output.filter_ctx = r;
    }

    return NGX_OK;
}


static ngx_int_t
reinit_request(ngx_http_request_t *r)
{
    passenger_context_t  *context;

    context = ngx_http_get_module_ctx(r, ngx_http_passenger_module);

    if (context == NULL) {
        return NGX_OK;
    }

    context->status = 0;
    context->status_count = 0;
    context->status_start = NULL;
    context->status_end = NULL;

    r->upstream->process_header = process_status_line;
    r->state = 0;

    return NGX_OK;
}


static ngx_int_t
process_status_line(ngx_http_request_t *r)
{
    ngx_int_t             rc;
    ngx_http_upstream_t  *u;
    passenger_context_t  *context;

    context = ngx_http_get_module_ctx(r, ngx_http_passenger_module);

    if (context == NULL) {
        return NGX_ERROR;
    }

    rc = parse_status_line(r, context);

    if (rc == NGX_AGAIN) {
        return rc;
    }

    u = r->upstream;

    if (rc == NGX_HTTP_SCGI_PARSE_NO_HEADER) {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                      "upstream sent no valid HTTP/1.0 header");

#if 0
        if (u->accel) {
            return NGX_HTTP_UPSTREAM_INVALID_HEADER;
        }
#endif

        u->headers_in.status_n = NGX_HTTP_OK;
        u->state->status = NGX_HTTP_OK;

        return NGX_OK;
    }

    u->headers_in.status_n = context->status;
    u->state->status = context->status;

    u->headers_in.status_line.len = context->status_end - context->status_start;
    u->headers_in.status_line.data = ngx_palloc(r->pool,
                                                u->headers_in.status_line.len);
    if (u->headers_in.status_line.data == NULL) {
        return NGX_ERROR;
    }

    ngx_memcpy(u->headers_in.status_line.data, context->status_start,
               u->headers_in.status_line.len);

    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "http scgi status %ui \"%V\"",
                   u->headers_in.status_n, &u->headers_in.status_line);

    u->process_header = process_header;

    return process_header(r);
}


static ngx_int_t
parse_status_line(ngx_http_request_t *r, passenger_context_t *context)
{
    u_char                ch;
    u_char               *pos;
    ngx_http_upstream_t  *u;
    enum  {
        sw_start = 0,
        sw_H,
        sw_HT,
        sw_HTT,
        sw_HTTP,
        sw_first_major_digit,
        sw_major_digit,
        sw_first_minor_digit,
        sw_minor_digit,
        sw_status,
        sw_space_after_status,
        sw_status_text,
        sw_almost_done
    } state;

    u = r->upstream;

    state = r->state;

    for (pos = u->buffer.pos; pos < u->buffer.last; pos++) {
        ch = *pos;

        switch (state) {

        /* "HTTP/" */
        case sw_start:
            switch (ch) {
            case 'H':
                state = sw_H;
                break;
            default:
                return NGX_HTTP_SCGI_PARSE_NO_HEADER;
            }
            break;

        case sw_H:
            switch (ch) {
            case 'T':
                state = sw_HT;
                break;
            default:
                return NGX_HTTP_SCGI_PARSE_NO_HEADER;
            }
            break;

        case sw_HT:
            switch (ch) {
            case 'T':
                state = sw_HTT;
                break;
            default:
                return NGX_HTTP_SCGI_PARSE_NO_HEADER;
            }
            break;

        case sw_HTT:
            switch (ch) {
            case 'P':
                state = sw_HTTP;
                break;
            default:
                return NGX_HTTP_SCGI_PARSE_NO_HEADER;
            }
            break;

        case sw_HTTP:
            switch (ch) {
            case '/':
                state = sw_first_major_digit;
                break;
            default:
                return NGX_HTTP_SCGI_PARSE_NO_HEADER;
            }
            break;

        /* the first digit of major HTTP version */
        case sw_first_major_digit:
            if (ch < '1' || ch > '9') {
                return NGX_HTTP_SCGI_PARSE_NO_HEADER;
            }

            state = sw_major_digit;
            break;

        /* the major HTTP version or dot */
        case sw_major_digit:
            if (ch == '.') {
                state = sw_first_minor_digit;
                break;
            }

            if (ch < '0' || ch > '9') {
                return NGX_HTTP_SCGI_PARSE_NO_HEADER;
            }

            break;

        /* the first digit of minor HTTP version */
        case sw_first_minor_digit:
            if (ch < '0' || ch > '9') {
                return NGX_HTTP_SCGI_PARSE_NO_HEADER;
            }

            state = sw_minor_digit;
            break;

        /* the minor HTTP version or the end of the request line */
        case sw_minor_digit:
            if (ch == ' ') {
                state = sw_status;
                break;
            }

            if (ch < '0' || ch > '9') {
                return NGX_HTTP_SCGI_PARSE_NO_HEADER;
            }

            break;

        /* HTTP status code */
        case sw_status:
            if (ch == ' ') {
                break;
            }

            if (ch < '0' || ch > '9') {
                return NGX_HTTP_SCGI_PARSE_NO_HEADER;
            }

            context->status = context->status * 10 + ch - '0';

            if (++context->status_count == 3) {
                state = sw_space_after_status;
                context->status_start = pos - 2;
            }

            break;

         /* space or end of line */
         case sw_space_after_status:
            switch (ch) {
            case ' ':
                state = sw_status_text;
                break;
            case '.':                    /* IIS may send 403.1, 403.2, etc */
                state = sw_status_text;
                break;
            case CR:
                state = sw_almost_done;
                break;
            case LF:
                goto done;
            default:
                return NGX_HTTP_SCGI_PARSE_NO_HEADER;
            }
            break;

        /* any text until end of line */
        case sw_status_text:
            switch (ch) {
            case CR:
                state = sw_almost_done;

                break;
            case LF:
                goto done;
            }
            break;

        /* end of status line */
        case sw_almost_done:
            context->status_end = pos - 1;
            switch (ch) {
            case LF:
                goto done;
            default:
                return NGX_HTTP_SCGI_PARSE_NO_HEADER;
            }
        }
    }

    u->buffer.pos = pos;
    r->state = state;

    return NGX_AGAIN;

done:

    u->buffer.pos = pos + 1;

    if (context->status_end == NULL) {
        context->status_end = pos;
    }

    r->state = sw_start;

    return NGX_OK;
}


static ngx_int_t
process_header(ngx_http_request_t *r)
{
    ngx_str_t                      *status_line;
    ngx_int_t                       rc, status;
    ngx_table_elt_t                *h;
    ngx_http_upstream_t            *u;
    ngx_http_upstream_header_t     *hh;
    ngx_http_upstream_main_conf_t  *umcf;
    ngx_http_core_loc_conf_t       *clcf;

    umcf = ngx_http_get_module_main_conf(r, ngx_http_upstream_module);
    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);

    for ( ;; ) {

        rc = ngx_http_parse_header_line(r, &r->upstream->buffer, 1);

        if (rc == NGX_OK) {

            /* a header line has been parsed successfully */

            h = ngx_list_push(&r->upstream->headers_in.headers);
            if (h == NULL) {
                return NGX_ERROR;
            }

            h->hash = r->header_hash;

            h->key.len = r->header_name_end - r->header_name_start;
            h->value.len = r->header_end - r->header_start;

            h->key.data = ngx_pnalloc(r->pool,
                                      h->key.len + 1 + h->value.len + 1
                                      + h->key.len);
            if (h->key.data == NULL) {
                return NGX_ERROR;
            }

            h->value.data = h->key.data + h->key.len + 1;
            h->lowcase_key = h->key.data + h->key.len + 1 + h->value.len + 1;

            ngx_memcpy(h->key.data, r->header_name_start, h->key.len);
            h->key.data[h->key.len] = '\0';
            ngx_memcpy(h->value.data, r->header_start, h->value.len);
            h->value.data[h->value.len] = '\0';

            if (h->key.len == r->lowcase_index) {
                ngx_memcpy(h->lowcase_key, r->lowcase_header, h->key.len);

            } else {
                ngx_strlow(h->lowcase_key, h->key.data, h->key.len);
            }

            hh = ngx_hash_find(&umcf->headers_in_hash, h->hash,
                               h->lowcase_key, h->key.len);

            if (hh && hh->handler(r, h, hh->offset) != NGX_OK) {
                return NGX_ERROR;
            }

            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                           "http scgi header: \"%V: %V\"", &h->key, &h->value);

            continue;
        }

        if (rc == NGX_HTTP_PARSE_HEADER_DONE) {

            /* a whole header has been parsed successfully */

            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                           "http scgi header done");

            /*
             * if no "Server" and "Date" in header line,
             * then add the default headers
             */

            if (r->upstream->headers_in.server == NULL) {
                h = ngx_list_push(&r->upstream->headers_in.headers);
                if (h == NULL) {
                    return NGX_HTTP_INTERNAL_SERVER_ERROR;
                }

                h->hash = ngx_hash(ngx_hash(ngx_hash(ngx_hash(
                                    ngx_hash('s', 'e'), 'r'), 'v'), 'e'), 'r');

                h->key.len = sizeof("Server") - 1;
                h->key.data = (u_char *) "Server";
                if (!passenger_main_conf.autogenerated.show_version_in_header) {
                    if (clcf->server_tokens) {
                        h->value.data = (u_char *) (NGINX_VER " + " PROGRAM_NAME);
                    } else {
                        h->value.data = (u_char *) ("nginx + " PROGRAM_NAME);
                    }
                } else {
                    if (clcf->server_tokens) {
                        h->value.data = (u_char *) (NGINX_VER " + " PROGRAM_NAME " " PASSENGER_VERSION);
                    } else {
                        h->value.data = (u_char *) ("nginx + " PROGRAM_NAME " " PASSENGER_VERSION);
                    }
                }
                h->value.len = ngx_strlen(h->value.data);
                h->lowcase_key = (u_char *) "server";
            }

            if (r->upstream->headers_in.date == NULL) {
                h = ngx_list_push(&r->upstream->headers_in.headers);
                if (h == NULL) {
                    return NGX_HTTP_INTERNAL_SERVER_ERROR;
                }

                h->hash = ngx_hash(ngx_hash(ngx_hash('d', 'a'), 't'), 'e');

                h->key.len = sizeof("Date") - 1;
                h->key.data = (u_char *) "Date";
                h->value.len = 0;
                h->value.data = NULL;
                h->lowcase_key = (u_char *) "date";
            }

            /* Process "Status" header. */

            u = r->upstream;

            if (u->headers_in.status_n) {
                goto done;
            }

            if (u->headers_in.status) {
                status_line = &u->headers_in.status->value;

                status = ngx_atoi(status_line->data, 3);
                if (status == NGX_ERROR) {
                    ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                                  "upstream sent invalid status \"%V\"",
                                  status_line);
                    return NGX_HTTP_UPSTREAM_INVALID_HEADER;
                }

                u->headers_in.status_n = status;
                u->headers_in.status_line = *status_line;

            } else if (u->headers_in.location) {
                u->headers_in.status_n = 302;
                ngx_str_set(&u->headers_in.status_line,
                            "302 Moved Temporarily");

            } else {
                u->headers_in.status_n = 200;
                ngx_str_set(&u->headers_in.status_line, "200 OK");
            }

            if (u->state && u->state->status == 0) {
                u->state->status = u->headers_in.status_n;
            }

        done:

            /* Supported since Nginx 1.3.15. */
            #ifdef NGX_HTTP_SWITCHING_PROTOCOLS
                if (u->headers_in.status_n == NGX_HTTP_SWITCHING_PROTOCOLS
                    && r->headers_in.upgrade)
                {
                    u->upgrade = 1;
                }
            #endif

            return NGX_OK;
        }

        if (rc == NGX_AGAIN) {
            return NGX_AGAIN;
        }

        /* there was error while a header line parsing */

        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                      "upstream sent invalid header");

        return NGX_HTTP_UPSTREAM_INVALID_HEADER;
    }
}


static void
abort_request(ngx_http_request_t *r)
{
    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "abort Passenger request");
}


static void
finalize_request(ngx_http_request_t *r, ngx_int_t rc)
{
    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "finalize Passenger request");
}


ngx_int_t
passenger_content_handler(ngx_http_request_t *r)
{
    ngx_int_t              rc;
    ngx_http_upstream_t   *u;
    passenger_loc_conf_t  *slcf;
    ngx_str_t              path, base_uri;
    u_char                *path_last, *end;
    u_char                 root_path_str[NGX_MAX_PATH + 1];
    ngx_str_t              root_path;
    size_t                 root_len, len;
    u_char                 page_cache_file_str[NGX_MAX_PATH + 1];
    ngx_str_t              page_cache_file;
    passenger_context_t   *context;
    void                  *detector_result_mem;
    ngx_pool_cleanup_t    *detector_result_cleanup;
    PP_Error               error;
    const PsgWrapperRegistryEntry *wrapper_registry_entry;

    if (passenger_main_conf.autogenerated.root_dir.len == 0) {
        return NGX_DECLINED;
    }

    slcf = ngx_http_get_module_loc_conf(r, ngx_http_passenger_module);

    /* Let the next content handler take care of this request if Phusion
     * Passenger is disabled for this URL.
     */
    if (!slcf->autogenerated.enabled) {
        return NGX_DECLINED;
    }

    /* Let the next content handler take care of this request if this URL
     * maps to an existing file.
     */
    path_last = ngx_http_map_uri_to_path(r, &path, &root_len, 0);
    if (path_last != NULL && file_exists(path.data, 0)) {
        return NGX_DECLINED;
    }

    /* Create a string containing the root path. This path already
     * contains a trailing slash.
     */
    end = ngx_copy(root_path_str, path.data, root_len);
    *end = '\0';
    root_path.data = root_path_str;
    root_path.len  = root_len;


    context = ngx_pcalloc(r->pool, sizeof(passenger_context_t));
    if (context == NULL) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }
    ngx_http_set_ctx(r, context, ngx_http_passenger_module);


    /* Find the base URI for this web application, if any. */
    if (find_base_uri(r, slcf, &base_uri)) {
        /* Store the found base URI into context->public_dir. We infer that
         * the 'public' directory of the web app equals document root + base URI.
         */
        if (slcf->autogenerated.document_root.data != NULL) {
            len = slcf->autogenerated.document_root.len + 1;
            context->public_dir.data = ngx_palloc(r->pool, sizeof(u_char) * len);
            end = ngx_copy(context->public_dir.data, slcf->autogenerated.document_root.data,
                           slcf->autogenerated.document_root.len);
        } else {
            len = root_path.len + base_uri.len + 1;
            context->public_dir.data = ngx_palloc(r->pool, sizeof(u_char) * len);
            end = ngx_copy(context->public_dir.data, root_path.data, root_path.len);
            end = ngx_copy(end, base_uri.data, base_uri.len);
        }
        *end = '\0';
        context->public_dir.len = len - 1;
        context->base_uri = base_uri;
    } else {
        /* No base URI directives are applicable for this request. So assume that
         * the web application's public directory is the document root.
         * context->base_uri is now a NULL string.
         */
        len = sizeof(u_char *) * (root_path.len + 1);
        context->public_dir.data = ngx_palloc(r->pool, len);
        end = ngx_copy(context->public_dir.data, root_path.data,
                       root_path.len);
        *end = '\0';
        context->public_dir.len  = root_path.len;
    }
    if (context->public_dir.len == 0) {
        /* If the `root` directive is set to `/` then `public_dir`
         * becomes the empty string. We fix this into `/`.
         */
        context->public_dir.data = (u_char *) "/";
        context->public_dir.len = 1;
    }

    /* If there's a corresponding page cache file for this URL, then serve that
     * file instead.
     */
    page_cache_file.data = page_cache_file_str;
    page_cache_file.len  = sizeof(page_cache_file_str);
    if (map_uri_to_page_cache_file(r, &context->public_dir, path.data,
                                   path_last - path.data, &page_cache_file)) {
        return passenger_static_content_handler(r, &page_cache_file);
    }

    detector_result_mem = ngx_palloc(r->pool,
        psg_app_type_detector_result_get_object_size());
    context->detector_result = psg_app_type_detector_result_init(detector_result_mem);
    detector_result_cleanup = ngx_pool_cleanup_add(r->pool, 0);
    detector_result_cleanup->handler = cleanup_detector_result;
    detector_result_cleanup->data = context->detector_result;

    /* If `app_start_command` is set, then it means the config specified that it is
     * either a generic app or a Kuria app.
     */
    if (slcf->autogenerated.app_start_command.data == NULL
     && slcf->autogenerated.app_type.data == NULL)
    {
        /* If neither `app_start_command` nor `app_type` are set, then
         * autodetect what kind of app this is.
         */
        pp_error_init(&error);
        if (slcf->autogenerated.app_root.data == NULL) {
            psg_app_type_detector_check_document_root(
                psg_app_type_detector, context->detector_result,
                (const char *) context->public_dir.data, context->public_dir.len,
                context->base_uri.len != 0,
                &error);
        } else {
             psg_app_type_detector_check_app_root(
                psg_app_type_detector, context->detector_result,
                (const char *) slcf->autogenerated.app_root.data, slcf->autogenerated.app_root.len,
                &error);
        }
        if (psg_app_type_detector_result_is_null(context->detector_result)) {
            if (error.message == NULL) {
                return NGX_DECLINED;
            } else if (error.errnoCode == EACCES) {
                ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
                              "%s; This error means that the Nginx worker process (PID %d, "
                              "running as UID %d) does not have permission to access this file. "
                              "Please read this page to learn how to fix this problem: "
                              "https://www.phusionpassenger.com/library/admin/nginx/troubleshooting/?a=upon-accessing-the-web-app-nginx-reports-a-permission-denied-error; Extra info",
                              error.message,
                              (int) getpid(),
                              (int) getuid());
            } else {
                ngx_log_error(NGX_LOG_ALERT, r->connection->log,
                              (error.errnoCode == PP_NO_ERRNO) ? 0 : error.errnoCode,
                              "%s",
                              error.message);
            }
            pp_error_destroy(&error);
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }
    } else if (slcf->autogenerated.app_start_command.data == NULL) {
        /* If `app_start_command` is not set but `app_type` is, then
         * verify whether the given `app_type` value is supported
         * and resolve aliases.
         */
        wrapper_registry_entry = psg_wrapper_registry_lookup(psg_wrapper_registry,
            (const char *) slcf->autogenerated.app_type.data,
            slcf->autogenerated.app_type.len);
        if (psg_wrapper_registry_entry_is_null(wrapper_registry_entry)) {
            return NGX_DECLINED;
        } else {
            psg_app_type_detector_result_set_wrapper_registry_entry(
                context->detector_result, wrapper_registry_entry);
        }
    }


    /* Setup upstream stuff and prepare sending the request to the Passenger core. */

    if (ngx_http_upstream_create(r) != NGX_OK) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }
    u = r->upstream;

    u->schema = pp_schema_string;
    u->output.tag = (ngx_buf_tag_t) &ngx_http_passenger_module;
    set_upstream_server_address(u, &slcf->upstream_config);
    u->conf = &slcf->upstream_config;

#if (NGX_HTTP_CACHE)
    u->create_key       = create_key;
#endif
    u->create_request   = create_request;
    u->reinit_request   = reinit_request;
    u->process_header   = process_status_line;
    u->abort_request    = abort_request;
    u->finalize_request = finalize_request;
    r->state = 0;

    u->buffering = slcf->upstream_config.buffering;

    u->pipe = ngx_pcalloc(r->pool, sizeof(ngx_event_pipe_t));
    if (u->pipe == NULL) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    u->pipe->input_filter = ngx_event_pipe_copy_input_filter;
    u->pipe->input_ctx = r;

    r->request_body_no_buffering = !slcf->upstream_config.request_buffering;

    rc = ngx_http_read_client_request_body(r, ngx_http_upstream_init);

    fix_peer_address(r);

    if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
        return rc;
    }

    return NGX_DONE;
}