JFIF  H H (ICC_PROFILE   0 mntrRGB XYZ acsp   - desc trXYZ d gXYZ x bXYZ  rTRC  (gTRC  (bTRC  (wtpt  cprt  ب V 7eud akt !Q ,*R G PQ NČzŬb 56 6 6p z  "c8 3' 'Mq 6 G i t q l@ 9 0.ۚA c N|li: 9 蠃p / ^ Zn L  x84 v ;[#$ n h '  c 3 2o 8Ɛ5K Av*_ ?n X ?  p y ֆ ̈́# z 2 3 F0O e> { Gu . V (C ' h/ o%> x1X r:(>} { yc X 8b ] U : dO Ms VA p 'Z 3'3E - y bj: > j Ov Uw # 2cl~ @gF C jț bz ^: -t df j{XTh  8aU  M ^+d Al "(KK m1$$ X K K q rž o q g V FF#pG~ : ] ֦ !!Ң :pC+ A  zӬ ]. t `= ' h _/ }   @L \ , x㌼ z԰+ J - v + Y  [ '8 '  % X q= [4 Sj~ 4 ݢ # d ʂē R L5k X؊  > l :t\ ba B A\Q3 8 ( < A ; d ) n ӓLr Œ * |J : Ƒ 2rF G A 8 &\ A - J) /j `t.  E  A : T m "%Cb"! 7 wΦ Æ % ; [ ] U zmF kx + 7 ^ Ү R  "7 N+- b7 c ,.55 H T E`) P T ci 20y=MڭyٽS Odc i UJnq&~zn$ Rp )fG' 2 '  . mEϙ Z oV 𶛒u S ] | A @. )<  + '> ө :& $ ' L #   d{q H >*5; jDo ˷ Y m Ԉ*[9* 5 t Uf %3 = GU W\ ' AR _ W 4 _ P պ - | 4R F  JZ v0 d[ˠzJ| PU 8 ;鯌 ' V  cL , κ >   *e b)f, 0 "j1  1 v՘; b J ++ C Oz7 % > N U ,=z \ c EV K >Ic 7} . jHn;^觱GHI  H V ǂO: ߊ{mSV?K|@ A iA T`2 j 1 ~ K8’$ ~]=9ឦG * 9 E Mּm5X f 9 Q ĸ}i D% +t+;F : v : - & h _ rD | !  Ә ;Z l 븂' : C & Ҁae Kkm6 uT".}J&\ hci B2B + T 7 t?EPþЌ m A F\ l c2 ЊABm E,cն\Q J ]d 5  `O  Y@ 8 ?lx#  K iI& Ri1f R2f1N %_ g *T Ө ??N: }+ל ɀ h٭ q L ,ʈ B r c  u  Bmz  ̢TV^  뙏8' u fUX 3㓏 >NG : / ; H p饕 B 1 K  /%iɫ V Q% : _ - 1o L ? R oa\i^ F| W*Na)  P  X #y㯨 z+C ح\  в j i rm *d W ҡQb BȨ i'% NEb ? '[ h =`Oi U϶ u n R }GW 4f VJm  i R ~ J|l%m' % " LW  V 2 7 Ns X b ?_ = {s }ij4 Ý=SN jx FV "ԎI( E  )] ӝ Ж m [4 m `5 -7W f 󪥎 b Z K a#Vb% K Y 5 T $y` 4 6 ̼ p C 7 HM 'l{Oi + Pw~j,S5T4(F Ԩ; A `*HJ TP g t (6/ cg 5 U 1@Qb8 Y!  r < r 6h ) [{h h 8 3  0< y+V >BzOQ KF+6 v %= ~XD PpW c _-:߈   #c 坣 C W1 : ^ Q 06Ӭ q@ Dj $ UW $ ?9 F \cS M5 = 2> hב\,( ) JB k ; J #j徻W (p= o Q D #U Mh U *F  c ?N = n F5 Ӑ12 w 0 5Z 뀩4ڣ 霅 uC1 y %n J 8Ԑ *c r R 9W= H 9 , A ׻, . ZF b QEGVNs, q ' ;O Mm btN Vq Ow*֙kPѬ؈ X J  I ӯ E Wo k + R { %   j;   Y x 7&& , LԜ Y bw" lI (* `۹F A S - #$ 4 13)Q pڤ3Rx O J $c P]v i uq { k y N. - B ,@󪳪ڸ Ao  r% X0 U N b  9 򍕑 7)* 23 3 | Rn b @ q |E U y ́$!7 & ["X O y  ~ƓtADhP  ] m G 파 9 p= ԜZ]ӭ ሎ O eyq ! b I GU : 5~ F o L S T[е $]$g * w] ! ; m@ ޠ &IU ڬ{ ҄բ-.Sm Ғ `    vU"o L ; ' 5s " W )  @ ä  L 1i  } ^Y5 $+ P  v0o  HU @ȓ s Q M )  ? M\ 4 Q  $2 ;g  WJ /NwF JG D : y ό  Mf  W!  QUZ%a zҳ  DK _ X h M ^r ԝ^!d '<#ﴛ O { /F ? + $ Y ' wg'$ l  jG AH 7l 4   x Ѷ c }  I *e s g ~ ;ͤ t@  B 2 iB' 9C a 8 f TR: %  N O  @ m 3 s v w .@ 濾   ,FYrT0m UW _v>>d [ A 6 ڊx : Y T L Hv Ӻ! n S D} >[m E T0 _ ,'C] p  j  V ᅷ PDZ[- V0`  > } ܟpmֻ 2O6 O* ݹ X HS N(J / +1՟ #( P *g EO # w&j 6 ?; ڡ )+ U ]l \ /{莄 * I /  9 <'$ '#' y t/& -\\WP L`/p9 q  a' e ¨ E] f A A E Y nGtm, ϮwXT >AaN#  F j  ŵj Q Fd  q^< H? y J ܃2 * 3 T4 Ч H **W; (ꭽ ¾ P f= e yA 4 FJ 6{e ]J q OC  f#3 S J f i MM\ i T $ rG 8$ 9>2 :  M) Q #N K e &" E 8    . z <  mHk ? # t ˒ A ]; G0H : f! i ! j { 1 m } o 7 u; $   99Ӱ \n c z:zA Lq ޶w y K  T<  4 X, '!Y; Ͱ6 q G M~_ ~܂} t> ( z l }:r  L 9 fe}.- R*<`c6 o ܛ= pr ҽGB)nQ% )s*&i΍ %—Zb{ m[ N Nkb wB w Q* d F HP iܛ ; aV68j}\e I  GI'͔B ;yA  :^ bn - m # @ S6 ˎr  ; ~ cm ƻgU  : X6 G % < r ' dzX^} - H X D -C W`#  کpNH5 E y = 1 G # 꽁  C 5Yx z Y_"& A * C +q7Km 1Pm @in 88  N ^c.d p >[ g c {]c 4 ) f psgY ˅ > *"1 }2<1 7  5)] V5)kd kM ~` ΂  :{ 4  nG PTT b   뎱 ϶w FFᓂG8 >z F _ H  9_r D l: ҶH5  Z!Bj.y k} e rb:SOT ]!ǎ?n:H ε Z s y z tn [ 7N  Z#UQO$ . J #]Cr# Y X  9 c < '$ z 9  . $ $P nDS n 2 u5 X g\  ? |A  ᬰⶵ >> bE) Cb  -ruMc ׺ *,\)`^ m ge \k . ۫8  گ oK 1 gWM p U޻zI=EE rz: # 6 - -/ Wm \z8 מ 0x t A _c ?Ծ  c ^Mژ I MyO> l0 ċ }t7[ \ ʲ9̜ m_a[ 姯 rְ j P \k x 1 ' Cdm vF70e +  m- ]a ?ݝK uSMUm 8f Yb! ) 2Z.U  D \~: ܽ8z R ̪K c b s  &ߦ  1 $ , w \gc  3F$  iU#< ` : I 4{w . 2  a Q $ EX < p} x>N o?Gm N N rE $#J n҉ ! A H'< w  i i͗k ;  S;(PY y, | v G!H $ 4 ƴ @  V !қ 7  $ { O y W SY : I doI 5b _ s G q $ qIOg - B H]_ V  G  ! w0 p$    n U #ROMB $Zn: BD U KE 4 ec q } 3 ^  n BZ j b W  `l  Z d " " b ]nm!ij. {&*   F O? c u ]  u VQbj: J B .Xg J Fsq d祚N x ʣR ;! $ : F cU $R1- K 'ɋ I x8 Ϗoߠ F~ z " _^v 2 - f K  kW8 c . Ms- 6 =eV = ev \  L = g n22p< ׵?r Fۏ >z eP z Rog 8u ( tJ P߁ Z .p Ð ͢A ռ65 X syvm 6R W7W ; |0 >O үG @ ٳ< > om Լ()  kZJlK, F 4 )sn  O Q {u㌏ = A o  ,ծ ?eKB zG 6e6  f| C U _ 4 C[j͕M 8,%em M 8 \ c v COwTGi _9 4 BDf%  'ns 8 M P c  & - y"8R _j 3a +z+ N 9 Q lDQ 40 E V ۋ rH 2 I _ Y O ',:뿉 WN Hvﵐ0 B}n a*E zUb N# 0 e" f.Q  H  5" - H Vn= + #  Eiy- v8= g  `  o [ 鼛 2 . D ^ Q  eB  ؅q Ͽ R C~ B+J ۍ 8 4 * Ӗ Ν1 R B  I\  1[0 I 9y )  Y " 7 ]6 q g\ vP   s = Ѭ֕)פ  E < c` ϷU9 W ,: ? y 1h s U( T g e /룮 J A ]|   4a ZV AI eIhBI! l $ 3![q Hnw\7R { o M / ִ > 5&  gw j F E dc@K:V& W /k + = Y k[ @fU 5 zzmF ȖH,[  n -dc  w d[ z" g 4 ϘR r 0`B 8_; # $^Z  o5K ZYKj GY% s  "!a [9I2TF -  w# a ] ˒  I tٮ ei_ F pVЧ૱W 3e Ci 7 " } H  A pG> h  ֝ 5i T٧- ' `d X1   AF$ $< y  9~ , n  e boW >ޙ_ Z]ڷ T C U y J  G O }   > A" 5 a> Z U R - Z \9 jrW ݖQ Rݳ * ļ ] $ڵ Xq = / w z\ I  I # { ӭ l ^; F_R쵎 [ָ[gK KͰ o Q )W녕 і s *kuz ŨQde` WU_ KLE ~" g r< 2G y  އ L ( -V Y -Z( I U  0  | \ ; 8 C  m hl :̄D  k YHO k |:  *D Y ts س | zrO; 񍃌s T3 1 = j w y Ш ^nZ H m _G̮ 0W  󿓹 S ;  _$ mٛI D V =f -H } U  ]H A * vԶ  3\ Wh*I#$ @6  x ^ O Z C&J U 16 X D* if& B DGFY YP [KL X u X .Z h q Ghb  8 M# Mq t \c > aT 00= ㎨ʕ G Q Ԫ R ,Ĝ I9 zӦ JO 3Rn ` C 5 ܊@Q O1 N.ؔ"I\ YÖ Đ H d L \}IP~jm $ y ; :ZG  ZV Jv =&*UF#` `R`*S+p \=) ҭ 9 k  ̮Rš x 0' **ԑL kɺk +zJb # :|M Z ? j$ݼ & X )$ 6 F Y6ѕ/  ;  J *n l C *ų_ ԕ{ _ 6 :\47ڷ s 4R m Ċ = z * ʪ XT [ ] 5 Bl#a -˙bv 8 @ H   |R we9A%5& M % Z02T N)&&GfM 儀o Œ M ;= ./ /k~ E"a9/3 y ,> l j >  Z X y &ε Y P &h g e c< `` ] !}i'c KQ ulF Iʓ_\T58 ( +cJ q~ [d gm m/` Xڙ ht k q ו$ " c[P VY [uɜ&# 몵 "  Ⱦ qC  " Ü Ȕ!  <  M j8 u- d x * g ϫ t T Ld K laWڭ  \ ~ |  7 u` h( w ֋ c L= ˼ = F  vcG s } зU BS М; FI;  Q $8 +V|[C S 쮙 1 % YP Q% L VVK+& , c Ib ]  Vy i ~h  ?y F4  "5 A s- F ݆x5 5P&E :W@f;}  G y ^ ]U ITki 1 d﫠 *c N h ' c ؗY n s L :b  ? H :k M~ @ 8# I qɔ ~ :f ] P*i]H 'f jhx Tҗ 1 O : ^t $ 1]  UXz&  t OD T > (^ s &3 #N_ / x - 䬦? ~ v U- W$4  ' ӎ v RG |jy SW? u 4 ( 1 G[ ِ2 2jʎ hr m oյ ش gRͮ%ϟ Ѭ 9 o R  n-  &F - @  hgY _qN ;"2  !K J  šA ^, " aG 8` =1 4 =5 Mq k > U@UT  : R gj rK F. O$ I9'  =i }. _ One Hat Cyber Team
  • Your IP: 216.73.216.6
  • Server IP: 13.234.148.13
  • Server: Linux ip-172-31-36-101 5.15.0-1031-aws #35~20.04.1-Ubuntu SMP Sat Feb 11 16:19:06 UTC 2023 x86_64
  • Server Software: Apache/2.4.41 (Ubuntu)
  • PHP Version: 7.4.3-4ubuntu2.29
  • Buat File | Buat Folder
View File Name : CGI.pm
tag # sub textarea { my($self,@p) = self_or_default(@_); my($name,$default,$rows,$cols,$override,$tabindex,@other) = rearrange([NAME,[DEFAULT,VALUE],ROWS,[COLS,COLUMNS],[OVERRIDE,FORCE],TABINDEX],@p); my($current)= $override ? $default : (defined($self->param($name)) ? $self->param($name) : $default); $name = defined($name) ? $self->_maybe_escapeHTML($name) : ''; $current = defined($current) ? $self->_maybe_escapeHTML($current) : ''; my($r) = $rows ? qq/ rows="$rows"/ : ''; my($c) = $cols ? qq/ cols="$cols"/ : ''; my($other) = @other ? " @other" : ''; $tabindex = $self->element_tab($tabindex); return qq{}; } #### Method: button # Create a javascript button. # Parameters: # $name -> (optional) Name for the button. (-name) # $value -> (optional) Value of the button when selected (and visible name) (-value) # $onclick -> (optional) Text of the JavaScript to run when the button is # clicked. # Returns: # A string containing a tag #### sub button { my($self,@p) = self_or_default(@_); my($label,$value,$script,$tabindex,@other) = rearrange([NAME,[VALUE,LABEL], [ONCLICK,SCRIPT],TABINDEX],@p); $label=$self->_maybe_escapeHTML($label); $value=$self->_maybe_escapeHTML($value,1); $script=$self->_maybe_escapeHTML($script); $script ||= ''; my($name) = ''; $name = qq/ name="$label"/ if $label; $value = $value || $label; my($val) = ''; $val = qq/ value="$value"/ if $value; $script = qq/ onclick="$script"/ if $script; my($other) = @other ? " @other" : ''; $tabindex = $self->element_tab($tabindex); return $XHTML ? qq() : qq(); } #### Method: submit # Create a "submit query" button. # Parameters: # $name -> (optional) Name for the button. # $value -> (optional) Value of the button when selected (also doubles as label). # $label -> (optional) Label printed on the button(also doubles as the value). # Returns: # A string containing a tag #### sub submit { my($self,@p) = self_or_default(@_); my($label,$value,$tabindex,@other) = rearrange([NAME,[VALUE,LABEL],TABINDEX],@p); $label=$self->_maybe_escapeHTML($label); $value=$self->_maybe_escapeHTML($value,1); my $name = $NOSTICKY ? '' : 'name=".submit" '; $name = qq/name="$label" / if defined($label); $value = defined($value) ? $value : $label; my $val = ''; $val = qq/value="$value" / if defined($value); $tabindex = $self->element_tab($tabindex); my($other) = @other ? "@other " : ''; return $XHTML ? qq() : qq(); } #### Method: reset # Create a "reset" button. # Parameters: # $name -> (optional) Name for the button. # Returns: # A string containing a tag #### sub reset { my($self,@p) = self_or_default(@_); my($label,$value,$tabindex,@other) = rearrange(['NAME',['VALUE','LABEL'],TABINDEX],@p); $label=$self->_maybe_escapeHTML($label); $value=$self->_maybe_escapeHTML($value,1); my ($name) = ' name=".reset"'; $name = qq/ name="$label"/ if defined($label); $value = defined($value) ? $value : $label; my($val) = ''; $val = qq/ value="$value"/ if defined($value); my($other) = @other ? " @other" : ''; $tabindex = $self->element_tab($tabindex); return $XHTML ? qq() : qq(); } #### Method: defaults # Create a "defaults" button. # Parameters: # $name -> (optional) Name for the button. # Returns: # A string containing a tag # # Note: this button has a special meaning to the initialization script, # and tells it to ERASE the current query string so that your defaults # are used again! #### sub defaults { my($self,@p) = self_or_default(@_); my($label,$tabindex,@other) = rearrange([[NAME,VALUE],TABINDEX],@p); $label=$self->_maybe_escapeHTML($label,1); $label = $label || "Defaults"; my($value) = qq/ value="$label"/; my($other) = @other ? " @other" : ''; $tabindex = $self->element_tab($tabindex); return $XHTML ? qq() : qq//; } #### Method: comment # Create an HTML # Parameters: a string sub comment { my($self,@p) = self_or_CGI(@_); return ""; } #### Method: checkbox # Create a checkbox that is not logically linked to any others. # The field value is "on" when the button is checked. # Parameters: # $name -> Name of the checkbox # $checked -> (optional) turned on by default if true # $value -> (optional) value of the checkbox, 'on' by default # $label -> (optional) a user-readable label printed next to the box. # Otherwise the checkbox name is used. # Returns: # A string containing a field #### sub checkbox { my($self,@p) = self_or_default(@_); my($name,$checked,$value,$label,$labelattributes,$override,$tabindex,@other) = rearrange([NAME,[CHECKED,SELECTED,ON],VALUE,LABEL,LABELATTRIBUTES, [OVERRIDE,FORCE],TABINDEX],@p); $value = defined $value ? $value : 'on'; if (!$override && ($self->{'.fieldnames'}->{$name} || defined $self->param($name))) { $checked = grep($_ eq $value,$self->param($name)) ? $self->_checked(1) : ''; } else { $checked = $self->_checked($checked); } my($the_label) = defined $label ? $label : $name; $name = $self->_maybe_escapeHTML($name); $value = $self->_maybe_escapeHTML($value,1); $the_label = $self->_maybe_escapeHTML($the_label); my($other) = @other ? "@other " : ''; $tabindex = $self->element_tab($tabindex); $self->register_parameter($name); return $XHTML ? CGI::label($labelattributes, qq{$the_label}) : qq{$the_label}; } # Escape HTML sub escapeHTML { require HTML::Entities; # hack to work around earlier hacks push @_,$_[0] if @_==1 && $_[0] eq 'CGI'; my ($self,$toencode,$newlinestoo) = CGI::self_or_default(@_); return undef unless defined($toencode); my $encode_entities = $ENCODE_ENTITIES; $encode_entities .= "\012\015" if ( $encode_entities && $newlinestoo ); return HTML::Entities::encode_entities($toencode,$encode_entities); } # unescape HTML -- used internally sub unescapeHTML { require HTML::Entities; # hack to work around earlier hacks push @_,$_[0] if @_==1 && $_[0] eq 'CGI'; my ($self,$string) = CGI::self_or_default(@_); return undef unless defined($string); return HTML::Entities::decode_entities($string); } # Internal procedure - don't use sub _tableize { my($rows,$columns,$rowheaders,$colheaders,@elements) = @_; my @rowheaders = $rowheaders ? @$rowheaders : (); my @colheaders = $colheaders ? @$colheaders : (); my($result); if (defined($columns)) { $rows = int(0.99 + @elements/$columns) unless defined($rows); } if (defined($rows)) { $columns = int(0.99 + @elements/$rows) unless defined($columns); } # rearrange into a pretty table $result = ""; my($row,$column); unshift(@colheaders,'') if @colheaders && @rowheaders; $result .= "" if @colheaders; for (@colheaders) { $result .= ""; } for ($row=0;$row<$rows;$row++) { $result .= ""; $result .= "" if @rowheaders; for ($column=0;$column<$columns;$column++) { $result .= "" if defined($elements[$column*$rows + $row]); } $result .= ""; } $result .= "
$_
$rowheaders[$row]" . $elements[$column*$rows + $row] . "
"; return $result; } #### Method: radio_group # Create a list of logically-linked radio buttons. # Parameters: # $name -> Common name for all the buttons. # $values -> A pointer to a regular array containing the # values for each button in the group. # $default -> (optional) Value of the button to turn on by default. Pass '-' # to turn _nothing_ on. # $linebreak -> (optional) Set to true to place linebreaks # between the buttons. # $labels -> (optional) # A pointer to a hash of labels to print next to each checkbox # in the form $label{'value'}="Long explanatory label". # Otherwise the provided values are used as the labels. # Returns: # An ARRAY containing a series of fields #### sub radio_group { my($self,@p) = self_or_default(@_); $self->_box_group('radio',@p); } #### Method: checkbox_group # Create a list of logically-linked checkboxes. # Parameters: # $name -> Common name for all the check boxes # $values -> A pointer to a regular array containing the # values for each checkbox in the group. # $defaults -> (optional) # 1. If a pointer to a regular array of checkbox values, # then this will be used to decide which # checkboxes to turn on by default. # 2. If a scalar, will be assumed to hold the # value of a single checkbox in the group to turn on. # $linebreak -> (optional) Set to true to place linebreaks # between the buttons. # $labels -> (optional) # A pointer to a hash of labels to print next to each checkbox # in the form $label{'value'}="Long explanatory label". # Otherwise the provided values are used as the labels. # Returns: # An ARRAY containing a series of fields #### sub checkbox_group { my($self,@p) = self_or_default(@_); $self->_box_group('checkbox',@p); } sub _box_group { my $self = shift; my $box_type = shift; my($name,$values,$defaults,$linebreak,$labels,$labelattributes, $attributes,$rows,$columns,$rowheaders,$colheaders, $override,$nolabels,$tabindex,$disabled,@other) = rearrange([NAME,[VALUES,VALUE],[DEFAULT,DEFAULTS],LINEBREAK,LABELS,LABELATTRIBUTES, ATTRIBUTES,ROWS,[COLUMNS,COLS],[ROWHEADERS,ROWHEADER],[COLHEADERS,COLHEADER], [OVERRIDE,FORCE],NOLABELS,TABINDEX,DISABLED ],@_); my($result,$checked,@elements,@values); @values = $self->_set_values_and_labels($values,\$labels,$name); my %checked = $self->previous_or_default($name,$defaults,$override); # If no check array is specified, check the first by default $checked{$values[0]}++ if $box_type eq 'radio' && !%checked; $name=$self->_maybe_escapeHTML($name); my %tabs = (); if ($TABINDEX && $tabindex) { if (!ref $tabindex) { $self->element_tab($tabindex); } elsif (ref $tabindex eq 'ARRAY') { %tabs = map {$_=>$self->element_tab} @$tabindex; } elsif (ref $tabindex eq 'HASH') { %tabs = %$tabindex; } } %tabs = map {$_=>$self->element_tab} @values unless %tabs; my $other = @other ? "@other " : ''; my $radio_checked; # for disabling groups of radio/checkbox buttons my %disabled; for (@{$disabled}) { $disabled{$_}=1; } for (@values) { my $disable=""; if ($disabled{$_}) { $disable="disabled='1'"; } my $checkit = $self->_checked($box_type eq 'radio' ? ($checked{$_} && !$radio_checked++) : $checked{$_}); my($break); if ($linebreak) { $break = $XHTML ? "
" : "
"; } else { $break = ''; } my($label)=''; unless (defined($nolabels) && $nolabels) { $label = $_; $label = $labels->{$_} if defined($labels) && defined($labels->{$_}); $label = $self->_maybe_escapeHTML($label,1); $label = "$label" if $disabled{$_}; } my $attribs = $self->_set_attributes($_, $attributes); my $tab = $tabs{$_}; $_=$self->_maybe_escapeHTML($_); if ($XHTML) { push @elements, CGI::label($labelattributes, qq($label)).${break}; } else { push(@elements,qq/${label}${break}/); } } $self->register_parameter($name); return wantarray ? @elements : "@elements" unless defined($columns) || defined($rows); return _tableize($rows,$columns,$rowheaders,$colheaders,@elements); } #### Method: popup_menu # Create a popup menu. # Parameters: # $name -> Name for all the menu # $values -> A pointer to a regular array containing the # text of each menu item. # $default -> (optional) Default item to display # $labels -> (optional) # A pointer to a hash of labels to print next to each checkbox # in the form $label{'value'}="Long explanatory label". # Otherwise the provided values are used as the labels. # Returns: # A string containing the definition of a popup menu. #### sub popup_menu { my($self,@p) = self_or_default(@_); my($name,$values,$default,$labels,$attributes,$override,$tabindex,@other) = rearrange([NAME,[VALUES,VALUE],[DEFAULT,DEFAULTS],LABELS, ATTRIBUTES,[OVERRIDE,FORCE],TABINDEX],@p); my($result,%selected); if (!$override && defined($self->param($name))) { $selected{$self->param($name)}++; } elsif (defined $default) { %selected = map {$_=>1} ref($default) eq 'ARRAY' ? @$default : $default; } $name=$self->_maybe_escapeHTML($name); # RT #30057 - ignore -multiple, if you need this # then use scrolling_list @other = grep { $_ !~ /^multiple=/i } @other; my($other) = @other ? " @other" : ''; my(@values); @values = $self->_set_values_and_labels($values,\$labels,$name); $tabindex = $self->element_tab($tabindex); $name = q{} if ! defined $name; $result = qq/"; return $result; } #### Method: optgroup # Create a optgroup. # Parameters: # $name -> Label for the group # $values -> A pointer to a regular array containing the # values for each option line in the group. # $labels -> (optional) # A pointer to a hash of labels to print next to each item # in the form $label{'value'}="Long explanatory label". # Otherwise the provided values are used as the labels. # $labeled -> (optional) # A true value indicates the value should be used as the label attribute # in the option elements. # The label attribute specifies the option label presented to the user. # This defaults to the content of the \n/; for (@values) { if (/_set_attributes($_, $attributes); my($label) = $_; $label = $labels->{$_} if defined($labels) && defined($labels->{$_}); $label=$self->_maybe_escapeHTML($label); my($value)=$self->_maybe_escapeHTML($_,1); $result .= $labeled ? $novals ? "$label\n" : "$label\n" : $novals ? "$label\n" : "$label\n"; } } $result .= ""; return $result; } #### Method: scrolling_list # Create a scrolling list. # Parameters: # $name -> name for the list # $values -> A pointer to a regular array containing the # values for each option line in the list. # $defaults -> (optional) # 1. If a pointer to a regular array of options, # then this will be used to decide which # lines to turn on by default. # 2. Otherwise holds the value of the single line to turn on. # $size -> (optional) Size of the list. # $multiple -> (optional) If set, allow multiple selections. # $labels -> (optional) # A pointer to a hash of labels to print next to each checkbox # in the form $label{'value'}="Long explanatory label". # Otherwise the provided values are used as the labels. # Returns: # A string containing the definition of a scrolling list. #### sub scrolling_list { my($self,@p) = self_or_default(@_); my($name,$values,$defaults,$size,$multiple,$labels,$attributes,$override,$tabindex,@other) = rearrange([NAME,[VALUES,VALUE],[DEFAULTS,DEFAULT], SIZE,MULTIPLE,LABELS,ATTRIBUTES,[OVERRIDE,FORCE],TABINDEX],@p); my($result,@values); @values = $self->_set_values_and_labels($values,\$labels,$name); $size = $size || scalar(@values); my(%selected) = $self->previous_or_default($name,$defaults,$override); my($is_multiple) = $multiple ? qq/ multiple="multiple"/ : ''; my($has_size) = $size ? qq/ size="$size"/: ''; my($other) = @other ? " @other" : ''; $name=$self->_maybe_escapeHTML($name); $tabindex = $self->element_tab($tabindex); $result = qq/"; $self->register_parameter($name); return $result; } #### Method: hidden # Parameters: # $name -> Name of the hidden field # @default -> (optional) Initial values of field (may be an array) # or # $default->[initial values of field] # Returns: # A string containing a #### sub hidden { my($self,@p) = self_or_default(@_); # this is the one place where we departed from our standard # calling scheme, so we have to special-case (darn) my(@result,@value); my($name,$default,$override,@other) = rearrange([NAME,[DEFAULT,VALUE,VALUES],[OVERRIDE,FORCE]],@p); my $do_override = 0; if ( ref($p[0]) || substr($p[0],0,1) eq '-') { @value = ref($default) ? @{$default} : $default; $do_override = $override; } else { for ($default,$override,@other) { push(@value,$_) if defined($_); } undef @other; } # use previous values if override is not set my @prev = $self->param($name); @value = @prev if !$do_override && @prev; $name=$self->_maybe_escapeHTML($name); for (@value) { $_ = defined($_) ? $self->_maybe_escapeHTML($_,1) : ''; push @result,$XHTML ? qq() : qq(); } return wantarray ? @result : join('',@result); } #### Method: image_button # Parameters: # $name -> Name of the button # $src -> URL of the image source # $align -> Alignment style (TOP, BOTTOM or MIDDLE) # Returns: # A string containing a #### sub image_button { my($self,@p) = self_or_default(@_); my($name,$src,$alignment,@other) = rearrange([NAME,SRC,ALIGN],@p); my($align) = $alignment ? " align=\L\"$alignment\"" : ''; my($other) = @other ? " @other" : ''; $name=$self->_maybe_escapeHTML($name); return $XHTML ? qq() : qq//; } #### Method: self_url # Returns a URL containing the current script and all its # param/value pairs arranged as a query. You can use this # to create a link that, when selected, will reinvoke the # script with all its state information preserved. #### sub self_url { my($self,@p) = self_or_default(@_); return $self->url('-path_info'=>1,'-query'=>1,'-full'=>1,@p); } # This is provided as a synonym to self_url() for people unfortunate # enough to have incorporated it into their programs already! sub state { &self_url; } #### Method: url # Like self_url, but doesn't return the query string part of # the URL. #### sub url { my($self,@p) = self_or_default(@_); my ($relative,$absolute,$full,$path_info,$query,$base,$rewrite) = rearrange(['RELATIVE','ABSOLUTE','FULL',['PATH','PATH_INFO'],['QUERY','QUERY_STRING'],'BASE','REWRITE'],@p); my $url = ''; $full++ if $base || !($relative || $absolute); $rewrite++ unless defined $rewrite; my $path = $self->path_info; my $script_name = $self->script_name; my $request_uri = $self->request_uri || ''; my $query_str = $query ? $self->query_string : ''; $script_name =~ s/\?.*$//s; # remove query string $request_uri =~ s/\?.*$//s; # remove query string $request_uri = unescape($request_uri); my $uri = $rewrite && $request_uri ? $request_uri : $script_name; if ( defined( $ENV{PATH_INFO} ) ) { # IIS sometimes sets PATH_INFO to the same value as SCRIPT_NAME so only sub it out # if SCRIPT_NAME isn't defined or isn't the same value as PATH_INFO $uri =~ s/\Q$ENV{PATH_INFO}\E$// if ( ! defined( $ENV{SCRIPT_NAME} ) or $ENV{PATH_INFO} ne $ENV{SCRIPT_NAME} ); # if we're not IIS then keep to spec, the relevant info is here: # https://tools.ietf.org/html/rfc3875#section-4.1.13, namely # "No PATH_INFO segment (see section 4.1.5) is included in the # SCRIPT_NAME value." (see GH #126, GH #152, GH #176) if ( ! $IIS ) { $uri =~ s/\Q$ENV{PATH_INFO}\E$//; } } if ($full) { my $protocol = $self->protocol(); $url = "$protocol://"; my $vh = http('x_forwarded_host') || http('host') || ''; $vh =~ s/^.*,\s*//; # x_forwarded_host may be a comma-separated list (e.g. when the request has # passed through multiple reverse proxies. Take the last one. $vh =~ s/\:\d+$//; # some clients add the port number (incorrectly). Get rid of it. $url .= $vh || server_name(); my $port = $self->virtual_port; # add the port to the url unless it's the protocol's default port $url .= ':' . $port unless (lc($protocol) eq 'http' && $port == 80) or (lc($protocol) eq 'https' && $port == 443); return $url if $base; $url .= $uri; } elsif ($relative) { ($url) = $uri =~ m!([^/]+)$!; } elsif ($absolute) { $url = $uri; } $url .= $path if $path_info and defined $path; $url .= "?$query_str" if $query and $query_str ne ''; $url ||= ''; $url =~ s/([^a-zA-Z0-9_.%;&?\/\\:+=~-])/sprintf("%%%02X",ord($1))/eg; return $url; } #### Method: cookie # Set or read a cookie from the specified name. # Cookie can then be passed to header(). # Usual rules apply to the stickiness of -value. # Parameters: # -name -> name for this cookie (optional) # -value -> value of this cookie (scalar, array or hash) # -path -> paths for which this cookie is valid (optional) # -domain -> internet domain in which this cookie is valid (optional) # -secure -> if true, cookie only passed through secure channel (optional) # -expires -> expiry date in format Wdy, DD-Mon-YYYY HH:MM:SS GMT (optional) #### sub cookie { my($self,@p) = self_or_default(@_); my($name,$value,$path,$domain,$secure,$expires,$httponly,$max_age,$samesite) = rearrange([NAME,[VALUE,VALUES],PATH,DOMAIN,SECURE,EXPIRES,HTTPONLY,'MAX-AGE',SAMESITE],@p); require CGI::Cookie; # if no value is supplied, then we retrieve the # value of the cookie, if any. For efficiency, we cache the parsed # cookies in our state variables. unless ( defined($value) ) { $self->{'.cookies'} = CGI::Cookie->fetch; # If no name is supplied, then retrieve the names of all our cookies. return () unless $self->{'.cookies'}; return keys %{$self->{'.cookies'}} unless $name; return () unless $self->{'.cookies'}->{$name}; return $self->{'.cookies'}->{$name}->value if defined($name) && $name ne ''; } # If we get here, we're creating a new cookie return undef unless defined($name) && $name ne ''; # this is an error my @param; push(@param,'-name'=>$name); push(@param,'-value'=>$value); push(@param,'-domain'=>$domain) if $domain; push(@param,'-path'=>$path) if $path; push(@param,'-expires'=>$expires) if $expires; push(@param,'-secure'=>$secure) if $secure; push(@param,'-httponly'=>$httponly) if $httponly; push(@param,'-max_age'=>$max_age) if $max_age; push(@param,'-samesite'=>$samesite) if $samesite; return CGI::Cookie->new(@param); } sub parse_keywordlist { my($self,$tosplit) = @_; $tosplit = unescape($tosplit); # unescape the keywords $tosplit=~tr/+/ /; # pluses to spaces my(@keywords) = split(/\s+/,$tosplit); return @keywords; } sub param_fetch { my($self,@p) = self_or_default(@_); my($name) = rearrange([NAME],@p); return [] unless defined $name; unless (exists($self->{param}{$name})) { $self->add_parameter($name); $self->{param}{$name} = []; } return $self->{param}{$name}; } ############################################### # OTHER INFORMATION PROVIDED BY THE ENVIRONMENT ############################################### #### Method: path_info # Return the extra virtual path information provided # after the URL (if any) #### sub path_info { my ($self,$info) = self_or_default(@_); if (defined($info)) { $info = "/$info" if $info ne '' && substr($info,0,1) ne '/'; $self->{'.path_info'} = $info; } elsif (! defined($self->{'.path_info'}) ) { my (undef,$path_info) = $self->_name_and_path_from_env; $self->{'.path_info'} = $path_info || ''; } return $self->{'.path_info'}; } # This function returns a potentially modified version of SCRIPT_NAME # and PATH_INFO. Some HTTP servers do sanitise the paths in those # variables. It is the case of at least Apache 2. If for instance the # user requests: /path/./to/script.cgi/x//y/z/../x?y, Apache will set: # REQUEST_URI=/path/./to/script.cgi/x//y/z/../x?y # SCRIPT_NAME=/path/to/env.cgi # PATH_INFO=/x/y/x # # This is all fine except that some bogus CGI scripts expect # PATH_INFO=/http://foo when the user requests # http://xxx/script.cgi/http://foo # # Old versions of this module used to accomodate with those scripts, so # this is why we do this here to keep those scripts backward compatible. # Basically, we accomodate with those scripts but within limits, that is # we only try to preserve the number of / that were provided by the user # if $REQUEST_URI and "$SCRIPT_NAME$PATH_INFO" only differ by the number # of consecutive /. # # So for instance, in: http://foo/x//y/script.cgi/a//b, we'll return a # script_name of /x//y/script.cgi and a path_info of /a//b, but in: # http://foo/./x//z/script.cgi/a/../b//c, we'll return the versions # possibly sanitised by the HTTP server, so in the case of Apache 2: # script_name == /foo/x/z/script.cgi and path_info == /b/c. # # Future versions of this module may no longer do that, so one should # avoid relying on the browser, proxy, server, and CGI.pm preserving the # number of consecutive slashes as no guarantee can be made there. sub _name_and_path_from_env { my $self = shift; my $script_name = $ENV{SCRIPT_NAME} || ''; my $path_info = $ENV{PATH_INFO} || ''; my $uri = $self->request_uri || ''; $uri =~ s/\?.*//s; $uri = unescape($uri); if ( $IIS ) { # IIS doesn't set $ENV{PATH_INFO} correctly. It sets it to # $ENV{SCRIPT_NAME}path_info # IIS also doesn't set $ENV{REQUEST_URI} so we don't want to do # the test below, hence this comes first $path_info =~ s/^\Q$script_name\E(.*)/$1/; } elsif ($uri ne "$script_name$path_info") { my $script_name_pattern = quotemeta($script_name); my $path_info_pattern = quotemeta($path_info); $script_name_pattern =~ s{(?:\\/)+}{/+}g; $path_info_pattern =~ s{(?:\\/)+}{/+}g; if ($uri =~ /^($script_name_pattern)($path_info_pattern)$/s) { # REQUEST_URI and SCRIPT_NAME . PATH_INFO only differ by the # numer of consecutive slashes, so we can extract the info from # REQUEST_URI: ($script_name, $path_info) = ($1, $2); } } return ($script_name,$path_info); } #### Method: request_method # Returns 'POST', 'GET', 'PUT', 'PATCH' or 'HEAD' #### sub request_method { return (defined $ENV{'REQUEST_METHOD'}) ? $ENV{'REQUEST_METHOD'} : undef; } #### Method: content_type # Returns the content_type string #### sub content_type { return (defined $ENV{'CONTENT_TYPE'}) ? $ENV{'CONTENT_TYPE'} : undef; } #### Method: path_translated # Return the physical path information provided # by the URL (if any) #### sub path_translated { return (defined $ENV{'PATH_TRANSLATED'}) ? $ENV{'PATH_TRANSLATED'} : undef; } #### Method: request_uri # Return the literal request URI #### sub request_uri { return (defined $ENV{'REQUEST_URI'}) ? $ENV{'REQUEST_URI'} : undef; } #### Method: query_string # Synthesize a query string from our current # parameters #### sub query_string { my($self) = self_or_default(@_); my($param,$value,@pairs); for $param ($self->param) { my($eparam) = escape($param); for $value ($self->param($param)) { $value = escape($value); next unless defined $value; push(@pairs,"$eparam=$value"); } } for (keys %{$self->{'.fieldnames'}}) { push(@pairs,".cgifields=".escape("$_")); } return join($USE_PARAM_SEMICOLONS ? ';' : '&',@pairs); } sub env_query_string { return (defined $ENV{'QUERY_STRING'}) ? $ENV{'QUERY_STRING'} : undef; } #### Method: accept # Without parameters, returns an array of the # MIME types the browser accepts. # With a single parameter equal to a MIME # type, will return undef if the browser won't # accept it, 1 if the browser accepts it but # doesn't give a preference, or a floating point # value between 0.0 and 1.0 if the browser # declares a quantitative score for it. # This handles MIME type globs correctly. #### sub Accept { my($self,$search) = self_or_CGI(@_); my(%prefs,$type,$pref,$pat); my(@accept) = defined $self->http('accept') ? split(',',$self->http('accept')) : (); for (@accept) { ($pref) = /q=(\d\.\d+|\d+)/; ($type) = m#(\S+/[^;]+)#; next unless $type; $prefs{$type}=$pref || 1; } return keys %prefs unless $search; # if a search type is provided, we may need to # perform a pattern matching operation. # The MIME types use a glob mechanism, which # is easily translated into a perl pattern match # First return the preference for directly supported # types: return $prefs{$search} if $prefs{$search}; # Didn't get it, so try pattern matching. for (keys %prefs) { next unless /\*/; # not a pattern match ($pat = $_) =~ s/([^\w*])/\\$1/g; # escape meta characters $pat =~ s/\*/.*/g; # turn it into a pattern return $prefs{$_} if $search=~/$pat/; } } #### Method: user_agent # If called with no parameters, returns the user agent. # If called with one parameter, does a pattern match (case # insensitive) on the user agent. #### sub user_agent { my($self,$match)=self_or_CGI(@_); my $user_agent = $self->http('user_agent'); return $user_agent unless defined $match && $match && $user_agent; return $user_agent =~ /$match/i; } #### Method: raw_cookie # Returns the magic cookies for the session. # The cookies are not parsed or altered in any way, i.e. # cookies are returned exactly as given in the HTTP # headers. If a cookie name is given, only that cookie's # value is returned, otherwise the entire raw cookie # is returned. #### sub raw_cookie { my($self,$key) = self_or_CGI(@_); require CGI::Cookie; if (defined($key)) { $self->{'.raw_cookies'} = CGI::Cookie->raw_fetch unless $self->{'.raw_cookies'}; return () unless $self->{'.raw_cookies'}; return () unless $self->{'.raw_cookies'}->{$key}; return $self->{'.raw_cookies'}->{$key}; } return $self->http('cookie') || $ENV{'COOKIE'} || ''; } #### Method: virtual_host # Return the name of the virtual_host, which # is not always the same as the server ###### sub virtual_host { my $vh = http('x_forwarded_host') || http('host') || server_name(); $vh =~ s/:\d+$//; # get rid of port number return $vh; } #### Method: remote_host # Return the name of the remote host, or its IP # address if unavailable. If this variable isn't # defined, it returns "localhost" for debugging # purposes. #### sub remote_host { return $ENV{'REMOTE_HOST'} || $ENV{'REMOTE_ADDR'} || 'localhost'; } #### Method: remote_addr # Return the IP addr of the remote host. #### sub remote_addr { return $ENV{'REMOTE_ADDR'} || '127.0.0.1'; } #### Method: script_name # Return the partial URL to this script for # self-referencing scripts. Also see # self_url(), which returns a URL with all state information # preserved. #### sub script_name { my ($self,@p) = self_or_default(@_); if (@p) { $self->{'.script_name'} = shift @p; } elsif (!exists $self->{'.script_name'}) { my ($script_name,$path_info) = $self->_name_and_path_from_env(); $self->{'.script_name'} = $script_name; } return $self->{'.script_name'}; } #### Method: referer # Return the HTTP_REFERER: useful for generating # a GO BACK button. #### sub referer { my($self) = self_or_CGI(@_); return $self->http('referer'); } #### Method: server_name # Return the name of the server #### sub server_name { return $ENV{'SERVER_NAME'} || 'localhost'; } #### Method: server_software # Return the name of the server software #### sub server_software { return $ENV{'SERVER_SOFTWARE'} || 'cmdline'; } #### Method: virtual_port # Return the server port, taking virtual hosts into account #### sub virtual_port { my($self) = self_or_default(@_); my $vh = $self->http('x_forwarded_host') || $self->http('host'); my $protocol = $self->protocol; if ($vh) { return ($vh =~ /:(\d+)$/)[0] || ($protocol eq 'https' ? 443 : 80); } else { return $self->server_port(); } } #### Method: server_port # Return the tcp/ip port the server is running on #### sub server_port { return $ENV{'SERVER_PORT'} || 80; # for debugging } #### Method: server_protocol # Return the protocol (usually HTTP/1.0) #### sub server_protocol { return $ENV{'SERVER_PROTOCOL'} || 'HTTP/1.0'; # for debugging } #### Method: http # Return the value of an HTTP variable, or # the list of variables if none provided #### sub http { my ($self,$parameter) = self_or_CGI(@_); if ( defined($parameter) ) { $parameter =~ tr/-a-z/_A-Z/; if ( $parameter =~ /^HTTP(?:_|$)/ ) { return $ENV{$parameter}; } return $ENV{"HTTP_$parameter"}; } return grep { /^HTTP(?:_|$)/ } keys %ENV; } #### Method: https # Return the value of HTTPS, or # the value of an HTTPS variable, or # the list of variables #### sub https { my ($self,$parameter) = self_or_CGI(@_); if ( defined($parameter) ) { $parameter =~ tr/-a-z/_A-Z/; if ( $parameter =~ /^HTTPS(?:_|$)/ ) { return $ENV{$parameter}; } return $ENV{"HTTPS_$parameter"}; } return wantarray ? grep { /^HTTPS(?:_|$)/ } keys %ENV : $ENV{'HTTPS'}; } #### Method: protocol # Return the protocol (http or https currently) #### sub protocol { local($^W)=0; my $self = shift; return 'https' if uc($self->https()) eq 'ON'; return 'https' if $self->server_port == 443; my $prot = $self->server_protocol; my($protocol,$version) = split('/',$prot); return "\L$protocol\E"; } #### Method: remote_ident # Return the identity of the remote user # (but only if his host is running identd) #### sub remote_ident { return (defined $ENV{'REMOTE_IDENT'}) ? $ENV{'REMOTE_IDENT'} : undef; } #### Method: auth_type # Return the type of use verification/authorization in use, if any. #### sub auth_type { return (defined $ENV{'AUTH_TYPE'}) ? $ENV{'AUTH_TYPE'} : undef; } #### Method: remote_user # Return the authorization name used for user # verification. #### sub remote_user { return (defined $ENV{'REMOTE_USER'}) ? $ENV{'REMOTE_USER'} : undef; } #### Method: user_name # Try to return the remote user's name by hook or by # crook #### sub user_name { my ($self) = self_or_CGI(@_); return $self->http('from') || $ENV{'REMOTE_IDENT'} || $ENV{'REMOTE_USER'}; } #### Method: nosticky # Set or return the NOSTICKY global flag #### sub nosticky { my ($self,$param) = self_or_CGI(@_); $CGI::NOSTICKY = $param if defined($param); return $CGI::NOSTICKY; } #### Method: nph # Set or return the NPH global flag #### sub nph { my ($self,$param) = self_or_CGI(@_); $CGI::NPH = $param if defined($param); return $CGI::NPH; } #### Method: private_tempfiles # Set or return the private_tempfiles global flag #### sub private_tempfiles { warn "private_tempfiles has been deprecated"; return 0; } #### Method: close_upload_files # Set or return the close_upload_files global flag #### sub close_upload_files { my ($self,$param) = self_or_CGI(@_); $CGI::CLOSE_UPLOAD_FILES = $param if defined($param); return $CGI::CLOSE_UPLOAD_FILES; } #### Method: default_dtd # Set or return the default_dtd global #### sub default_dtd { my ($self,$param,$param2) = self_or_CGI(@_); if (defined $param2 && defined $param) { $CGI::DEFAULT_DTD = [ $param, $param2 ]; } elsif (defined $param) { $CGI::DEFAULT_DTD = $param; } return $CGI::DEFAULT_DTD; } # -------------- really private subroutines ----------------- sub _maybe_escapeHTML { # hack to work around earlier hacks push @_,$_[0] if @_==1 && $_[0] eq 'CGI'; my ($self,$toencode,$newlinestoo) = CGI::self_or_default(@_); return undef unless defined($toencode); return $toencode if ref($self) && !$self->{'escape'}; return $self->escapeHTML($toencode, $newlinestoo); } sub previous_or_default { my($self,$name,$defaults,$override) = @_; my(%selected); if (!$override && ($self->{'.fieldnames'}->{$name} || defined($self->param($name)) ) ) { $selected{$_}++ for $self->param($name); } elsif (defined($defaults) && ref($defaults) && (ref($defaults) eq 'ARRAY')) { $selected{$_}++ for @{$defaults}; } else { $selected{$defaults}++ if defined($defaults); } return %selected; } sub register_parameter { my($self,$param) = @_; $self->{'.parametersToAdd'}->{$param}++; } sub get_fields { my($self) = @_; return $self->CGI::hidden('-name'=>'.cgifields', '-values'=>[keys %{$self->{'.parametersToAdd'}}], '-override'=>1); } sub read_from_cmdline { my($input,@words); my($query_string); my($subpath); if ($DEBUG && @ARGV) { @words = @ARGV; } elsif ($DEBUG > 1) { require Text::ParseWords; print STDERR "(offline mode: enter name=value pairs on standard input; press ^D or ^Z when done)\n"; chomp(@lines = ); # remove newlines $input = join(" ",@lines); @words = &Text::ParseWords::old_shellwords($input); } for (@words) { s/\\=/%3D/g; s/\\&/%26/g; } if ("@words"=~/=/) { $query_string = join('&',@words); } else { $query_string = join('+',@words); } if ($query_string =~ /^(.*?)\?(.*)$/) { $query_string = $2; $subpath = $1; } return { 'query_string' => $query_string, 'subpath' => $subpath }; } ##### # subroutine: read_multipart # # Read multipart data and store it into our parameters. # An interesting feature is that if any of the parts is a file, we # create a temporary file and open up a filehandle on it so that the # caller can read from it if necessary. ##### sub read_multipart { my($self,$boundary,$length) = @_; my($buffer) = $self->new_MultipartBuffer($boundary,$length); return unless $buffer; my(%header,$body); my $filenumber = 0; while (!$buffer->eof) { %header = $buffer->readHeader; unless (%header) { $self->cgi_error("400 Bad request (malformed multipart POST)"); return; } $header{'Content-Disposition'} ||= ''; # quench uninit variable warning my $param = _mp_value_parse( $header{'Content-Disposition'},'name' ); $param .= $TAINTED; # See RFC 1867, 2183, 2045 # NB: File content will be loaded into memory should # content-disposition parsing fail. my ($filename) = $header{'Content-Disposition'} =~/ filename=(("[^"]*")|([a-z\d!\#'\*\+,\.^_\`\{\}\|\~]*))/i; $filename ||= ''; # quench uninit variable warning $filename =~ s/^"([^"]*)"$/$1/; # Test for Opera's multiple upload feature my($multipart) = ( defined( $header{'Content-Type'} ) && $header{'Content-Type'} =~ /multipart\/mixed/ ) ? 1 : 0; # add this parameter to our list $self->add_parameter($param); # If no filename specified, then just read the data and assign it # to our parameter list. if ( ( !defined($filename) || $filename eq '' ) && !$multipart ) { my($value) = $buffer->readBody; $value .= $TAINTED; push(@{$self->{param}{$param}},$value); next; } UPLOADS: { # If we get here, then we are dealing with a potentially large # uploaded form. Save the data to a temporary file, then open # the file for reading. # skip the file if uploads disabled if ($DISABLE_UPLOADS) { while (defined($data = $buffer->read)) { } last UPLOADS; } # set the filename to some recognizable value if ( ( !defined($filename) || $filename eq '' ) && $multipart ) { $filename = "multipart/mixed"; } my $tmp_dir = $CGI::OS eq 'WINDOWS' ? ( $ENV{TEMP} || $ENV{TMP} || ( $ENV{WINDIR} ? ( $ENV{WINDIR} . $SL . 'TEMP' ) : undef ) ) : undef; # File::Temp defaults to TMPDIR require CGI::File::Temp; my $filehandle = CGI::File::Temp->new( UNLINK => $UNLINK_TMP_FILES, DIR => $tmp_dir, ); $filehandle->_mp_filename( $filename ); $CGI::DefaultClass->binmode($filehandle) if $CGI::needs_binmode && defined fileno($filehandle); # if this is an multipart/mixed attachment, save the header # together with the body for later parsing with an external # MIME parser module if ( $multipart ) { for ( keys %header ) { print $filehandle "$_: $header{$_}${CRLF}"; } print $filehandle "${CRLF}"; } my ($data); local($\) = ''; my $totalbytes = 0; while (defined($data = $buffer->read)) { if (defined $self->{'.upload_hook'}) { $totalbytes += length($data); &{$self->{'.upload_hook'}}($filename ,$data, $totalbytes, $self->{'.upload_data'}); } print $filehandle $data if ($self->{'use_tempfile'}); } # back up to beginning of file seek($filehandle,0,0); ## Close the filehandle if requested this allows a multipart MIME ## upload to contain many files, and we won't die due to too many ## open file handles. The user can access the files using the hash ## below. close $filehandle if $CLOSE_UPLOAD_FILES; $CGI::DefaultClass->binmode($filehandle) if $CGI::needs_binmode; # Save some information about the uploaded file where we can get # at it later. # Use the typeglob + filename as the key, as this is guaranteed to be # unique for each filehandle. Don't use the file descriptor as # this will be re-used for each filehandle if the # close_upload_files feature is used. $self->{'.tmpfiles'}->{$$filehandle . $filehandle} = { hndl => $filehandle, name => $filehandle->filename, info => {%header}, }; push(@{$self->{param}{$param}},$filehandle); } } } sub _mp_value_parse { my ( $string,$field ) = @_; my $is_quoted = $string =~/[\s;]$field="/ ? 1 : 0; my $param; if ( $is_quoted ) { # a quoted token cannot contain anything but an unescaped quote ($param) = $string =~/[\s;]$field="((?:\\"|[^"])*)"/; } else { # a plain token cannot contain any reserved characters # https://tools.ietf.org/html/rfc2616#section-2.2 # separators = "(" | ")" | "<" | ">" | "@" # | "," | ";" | ":" | "\" | <"> # | "/" | "[" | "]" | "?" | "=" # | "{" | "}" | SP | HT ($param) = $string =~/[\s;]$field=([^\(\)<>\@,;:\\"\/\[\]\?=\{\} \015\n\t]*)/; } return $param; } ##### # subroutine: read_multipart_related # # Read multipart/related data and store it into our parameters. The # first parameter sets the start of the data. The part identified by # this Content-ID will not be stored as a file upload, but will be # returned by this method. All other parts will be available as file # uploads accessible by their Content-ID ##### sub read_multipart_related { my($self,$start,$boundary,$length) = @_; my($buffer) = $self->new_MultipartBuffer($boundary,$length); return unless $buffer; my(%header,$body); my $filenumber = 0; my $returnvalue; while (!$buffer->eof) { %header = $buffer->readHeader; unless (%header) { $self->cgi_error("400 Bad request (malformed multipart POST)"); return; } my($param) = $header{'Content-ID'}=~/\<([^\>]*)\>/; $param .= $TAINTED; # If this is the start part, then just read the data and assign it # to our return variable. if ( $param eq $start ) { $returnvalue = $buffer->readBody; $returnvalue .= $TAINTED; next; } # add this parameter to our list $self->add_parameter($param); UPLOADS: { # If we get here, then we are dealing with a potentially large # uploaded form. Save the data to a temporary file, then open # the file for reading. # skip the file if uploads disabled if ($DISABLE_UPLOADS) { while (defined($data = $buffer->read)) { } last UPLOADS; } my $tmp_dir = $CGI::OS eq 'WINDOWS' ? ( $ENV{TEMP} || $ENV{TMP} || ( $ENV{WINDIR} ? ( $ENV{WINDIR} . $SL . 'TEMP' ) : undef ) ) : undef; # File::Temp defaults to TMPDIR require CGI::File::Temp; my $filehandle = CGI::File::Temp->new( UNLINK => $UNLINK_TMP_FILES, DIR => $tmp_dir, ); $filehandle->_mp_filename( $filehandle->filename ); $CGI::DefaultClass->binmode($filehandle) if $CGI::needs_binmode && defined fileno($filehandle); my ($data); local($\) = ''; my $totalbytes; while (defined($data = $buffer->read)) { if (defined $self->{'.upload_hook'}) { $totalbytes += length($data); &{$self->{'.upload_hook'}}($param ,$data, $totalbytes, $self->{'.upload_data'}); } print $filehandle $data if ($self->{'use_tempfile'}); } # back up to beginning of file seek($filehandle,0,0); ## Close the filehandle if requested this allows a multipart MIME ## upload to contain many files, and we won't die due to too many ## open file handles. The user can access the files using the hash ## below. close $filehandle if $CLOSE_UPLOAD_FILES; $CGI::DefaultClass->binmode($filehandle) if $CGI::needs_binmode; # Save some information about the uploaded file where we can get # at it later. # Use the typeglob + filename as the key, as this is guaranteed to be # unique for each filehandle. Don't use the file descriptor as # this will be re-used for each filehandle if the # close_upload_files feature is used. $self->{'.tmpfiles'}->{$$filehandle . $filehandle} = { hndl => $filehandle, name => $filehandle->filename, info => {%header}, }; push(@{$self->{param}{$param}},$filehandle); } } return $returnvalue; } sub upload { my($self,$param_name) = self_or_default(@_); my @param = grep {ref($_) && defined(fileno($_))} $self->param($param_name); return unless @param; return wantarray ? @param : $param[0]; } sub tmpFileName { my($self,$filename) = self_or_default(@_); # preferred calling convention: $filename came directly from param or upload if (ref $filename) { return $self->{'.tmpfiles'}->{$$filename . $filename}->{name} || ''; } # backwards compatible with older versions: $filename is merely equal to # one of our filenames when compared as strings foreach my $param_name ($self->param) { foreach my $filehandle ($self->multi_param($param_name)) { if ($filehandle eq $filename) { return $self->{'.tmpfiles'}->{$$filehandle . $filehandle}->{name} || ''; } } } return ''; } sub uploadInfo { my($self,$filename) = self_or_default(@_); return if ! defined $$filename; return $self->{'.tmpfiles'}->{$$filename . $filename}->{info}; } # internal routine, don't use sub _set_values_and_labels { my $self = shift; my ($v,$l,$n) = @_; $$l = $v if ref($v) eq 'HASH' && !ref($$l); return $self->param($n) if !defined($v); return $v if !ref($v); return ref($v) eq 'HASH' ? keys %$v : @$v; } # internal routine, don't use sub _set_attributes { my $self = shift; my($element, $attributes) = @_; return '' unless defined($attributes->{$element}); $attribs = ' '; for my $attrib (keys %{$attributes->{$element}}) { (my $clean_attrib = $attrib) =~ s/^-//; $attribs .= "@{[lc($clean_attrib)]}=\"$attributes->{$element}{$attrib}\" "; } $attribs =~ s/ $//; return $attribs; } ######################################################### # Globals and stubs for other packages that we use. ######################################################### ######################## CGI::MultipartBuffer #################### package CGI::MultipartBuffer; $_DEBUG = 0; # how many bytes to read at a time. We use # a 4K buffer by default. $MultipartBuffer::INITIAL_FILLUNIT ||= 1024 * 4; $MultipartBuffer::TIMEOUT ||= 240*60; # 4 hour timeout for big files $MultipartBuffer::SPIN_LOOP_MAX ||= 2000; # bug fix for some Netscape servers $MultipartBuffer::CRLF ||= $CGI::CRLF; $INITIAL_FILLUNIT = $MultipartBuffer::INITIAL_FILLUNIT; $TIMEOUT = $MultipartBuffer::TIMEOUT; $SPIN_LOOP_MAX = $MultipartBuffer::SPIN_LOOP_MAX; $CRLF = $MultipartBuffer::CRLF; sub new { my($package,$interface,$boundary,$length) = @_; $FILLUNIT = $INITIAL_FILLUNIT; $CGI::DefaultClass->binmode($IN); # if $CGI::needs_binmode; # just do it always # If the user types garbage into the file upload field, # then Netscape passes NOTHING to the server (not good). # We may hang on this read in that case. So we implement # a read timeout. If nothing is ready to read # by then, we return. # Netscape seems to be a little bit unreliable # about providing boundary strings. my $boundary_read = 0; if ($boundary) { # Under the MIME spec, the boundary consists of the # characters "--" PLUS the Boundary string # BUG: IE 3.01 on the Macintosh uses just the boundary -- not # the two extra hyphens. We do a special case here on the user-agent!!!! $boundary = "--$boundary" unless CGI::user_agent('MSIE\s+3\.0[12];\s*Mac|DreamPassport'); } else { # otherwise we find it ourselves my($old); ($old,$/) = ($/,$CRLF); # read a CRLF-delimited line $boundary = ; # BUG: This won't work correctly under mod_perl $length -= length($boundary); chomp($boundary); # remove the CRLF $/ = $old; # restore old line separator $boundary_read++; } my $self = {LENGTH=>$length, CHUNKED=>!$length, BOUNDARY=>$boundary, INTERFACE=>$interface, BUFFER=>'', }; $FILLUNIT = length($boundary) if length($boundary) > $FILLUNIT; my $retval = bless $self,ref $package || $package; # Read the preamble and the topmost (boundary) line plus the CRLF. unless ($boundary_read) { while ($self->read(0)) { } } die "Malformed multipart POST: data truncated\n" if $self->eof; return $retval; } sub readHeader { my($self) = @_; my($end); my($ok) = 0; my($bad) = 0; local($CRLF) = "\015\012" if $CGI::OS eq 'VMS' || $CGI::EBCDIC; do { $self->fillBuffer($FILLUNIT); $ok++ if ($end = index($self->{BUFFER},"${CRLF}${CRLF}")) >= 0; $ok++ if $self->{BUFFER} eq ''; $bad++ if !$ok && $self->{LENGTH} <= 0; # this was a bad idea # $FILLUNIT *= 2 if length($self->{BUFFER}) >= $FILLUNIT; } until $ok || $bad; return () if $bad; #EBCDIC NOTE: translate header into EBCDIC, but watch out for continuation lines! my($header) = substr($self->{BUFFER},0,$end+2); substr($self->{BUFFER},0,$end+4) = ''; my %return; if ($CGI::EBCDIC) { warn "untranslated header=$header\n" if $_DEBUG; $header = CGI::Util::ascii2ebcdic($header); warn "translated header=$header\n" if $_DEBUG; } # See RFC 2045 Appendix A and RFC 822 sections 3.4.8 # (Folding Long Header Fields), 3.4.3 (Comments) # and 3.4.5 (Quoted-Strings). my $token = '[-\w!\#$%&\'*+.^_\`|{}~]'; $header=~s/$CRLF\s+/ /og; # merge continuation lines while ($header=~/($token+):\s+([^$CRLF]*)/mgox) { my ($field_name,$field_value) = ($1,$2); $field_name =~ s/\b(\w)/uc($1)/eg; #canonicalize $return{$field_name}=$field_value; } return %return; } # This reads and returns the body as a single scalar value. sub readBody { my($self) = @_; my($data); my($returnval)=''; #EBCDIC NOTE: want to translate returnval into EBCDIC HERE while (defined($data = $self->read)) { $returnval .= $data; } if ($CGI::EBCDIC) { warn "untranslated body=$returnval\n" if $_DEBUG; $returnval = CGI::Util::ascii2ebcdic($returnval); warn "translated body=$returnval\n" if $_DEBUG; } return $returnval; } # This will read $bytes or until the boundary is hit, whichever happens # first. After the boundary is hit, we return undef. The next read will # skip over the boundary and begin reading again; sub read { my($self,$bytes) = @_; # default number of bytes to read $bytes = $bytes || $FILLUNIT; # Fill up our internal buffer in such a way that the boundary # is never split between reads. $self->fillBuffer($bytes); my $boundary_start = $CGI::EBCDIC ? CGI::Util::ebcdic2ascii($self->{BOUNDARY}) : $self->{BOUNDARY}; my $boundary_end = $CGI::EBCDIC ? CGI::Util::ebcdic2ascii($self->{BOUNDARY}.'--') : $self->{BOUNDARY}.'--'; # Find the boundary in the buffer (it may not be there). my $start = index($self->{BUFFER},$boundary_start); warn "boundary=$self->{BOUNDARY} length=$self->{LENGTH} start=$start\n" if $_DEBUG; # protect against malformed multipart POST operations die "Malformed multipart POST\n" unless $self->{CHUNKED} || ($start >= 0 || $self->{LENGTH} > 0); #EBCDIC NOTE: want to translate boundary search into ASCII here. # If the boundary begins the data, then skip past it # and return undef. if ($start == 0) { # clear us out completely if we've hit the last boundary. if (index($self->{BUFFER},$boundary_end)==0) { $self->{BUFFER}=''; $self->{LENGTH}=0; return undef; } # just remove the boundary. substr($self->{BUFFER},0,length($boundary_start))=''; $self->{BUFFER} =~ s/^\012\015?//; return undef; } my $bytesToReturn; if ($start > 0) { # read up to the boundary $bytesToReturn = $start-2 > $bytes ? $bytes : $start; } else { # read the requested number of bytes # leave enough bytes in the buffer to allow us to read # the boundary. Thanks to Kevin Hendrick for finding # this one. $bytesToReturn = $bytes - (length($boundary_start)+1); } my $returnval=substr($self->{BUFFER},0,$bytesToReturn); substr($self->{BUFFER},0,$bytesToReturn)=''; # If we hit the boundary, remove the CRLF from the end. return ($bytesToReturn==$start) ? substr($returnval,0,-2) : $returnval; } # This fills up our internal buffer in such a way that the # boundary is never split between reads sub fillBuffer { my($self,$bytes) = @_; return unless $self->{CHUNKED} || $self->{LENGTH}; my($boundaryLength) = length($self->{BOUNDARY}); my($bufferLength) = length($self->{BUFFER}); my($bytesToRead) = $bytes - $bufferLength + $boundaryLength + 2; $bytesToRead = $self->{LENGTH} if !$self->{CHUNKED} && $self->{LENGTH} < $bytesToRead; # Try to read some data. We may hang here if the browser is screwed up. my $bytesRead = $self->{INTERFACE}->read_from_client(\$self->{BUFFER}, $bytesToRead, $bufferLength); warn "bytesToRead=$bytesToRead, bufferLength=$bufferLength, buffer=$self->{BUFFER}\n" if $_DEBUG; $self->{BUFFER} = '' unless defined $self->{BUFFER}; # An apparent bug in the Apache server causes the read() # to return zero bytes repeatedly without blocking if the # remote user aborts during a file transfer. I don't know how # they manage this, but the workaround is to abort if we get # more than SPIN_LOOP_MAX consecutive zero reads. if ($bytesRead <= 0) { die "CGI.pm: Server closed socket during multipart read (client aborted?).\n" if ($self->{ZERO_LOOP_COUNTER}++ >= $SPIN_LOOP_MAX); } else { $self->{ZERO_LOOP_COUNTER}=0; } $self->{LENGTH} -= $bytesRead if !$self->{CHUNKED} && $bytesRead; } # Return true when we've finished reading sub eof { my($self) = @_; return 1 if (length($self->{BUFFER}) == 0) && ($self->{LENGTH} <= 0); undef; } 1; package CGI; # We get a whole bunch of warnings about "possibly uninitialized variables" # when running with the -w switch. Touch them all once to get rid of the # warnings. This is ugly and I hate it. if ($^W) { $CGI::CGI = ''; $CGI::CGI=<