File Coverage

blib/lib/Apache/Scriptor.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             package Apache::Scriptor;
2             $VERSION="1.21";
3 1     1   7185 use CGI::WebOut;
  0            
  0            
4             use Cwd;
5            
6             # constructor new()
7             # Создает новый Apache::Scriptor-объект.
8             sub new
9             { my ($class)=@_;
10             my $this = {
11             Handlers => {},
12             HandDir => ".",
13             htaccess => ".htaccess",
14             # Запоминаем, какой запрос в действительности был выполнен, чтобы
15             # потом искать его в htaccess-ах.
16             self_scriptname => $ENV{SCRIPT_NAME}
17             };
18             return bless($this,$class);
19             }
20            
21            
22             # void set_handlers_dir(string $dir)
23             # Устанавливает директорию для поиска обработчиков.
24             sub set_handlers_dir
25             { my ($this,$dir)=@_;
26             $this->{HandDir}=$dir;
27             }
28            
29             # void addhandler(ext1=>[h1, h2,...], ext2=>[...])
30             # Устанавливает обработчик(и) для расширений ext1 и ext2.
31             # Здесь h1, h2 и т.д. представляют собой ССЫЛКИ на функции-обработчики.
32             # Если же они заданы не как ссылки, а как СТРОКИ, то в момент обращения
33             # к очередному обработчику производится попытка его загрузить из файла,
34             # имя которого совпадает с именем обработчика с расширением ".pl" из
35             # директории, которая задана вызовом set_handlers_dir().
36             sub addhandler
37             { my ($this,%hands)=@_;
38             %{$this->{Handlers}}=(%{$this->{Handlers}},%hands);
39             return;
40             }
41            
42             # void pushhandler(string ext, func &func)
43             # Добавляет обработчик для расширения ext в конец списка обработчиков.
44             sub pushhandler
45             { my ($this,$ext,$func)=@_;
46             $this->{Handlers}{$ext}||=[];
47             push(@{$this->{Handlers}{$ext}},$func);
48             return;
49             }
50            
51             # void removehandler(ext1, ext2, ...)
52             # Удаляет обработчик(и) для расширений ext1 и ext2.
53             sub removehandler
54             { my ($this,@ext)=@_;
55             foreach (@ext) { delete $this->{Handlers}{$_} }
56             return;
57             }
58            
59             # void set_404_url($url)
60             # Устанавливает адрес страницы 404-й ошибки, на которую будет произведен
61             # редирект, если файл не найден.
62             sub set_404_url
63             { my ($th,$url)=@_;
64             $th->{404}=$url;
65             }
66            
67             # void set_htaccess_name($name)
68             # Устанавливает имя htaccess-файла. По умолчанию это .htaccess.
69             sub set_htaccess_name
70             { my ($th,$htaccess)=@_;
71             $th->{htaccess}=$htaccess;
72             }
73            
74             sub process_htaccess
75             { my ($th,$fname)=@_;
76             open(local *F,$fname) or return;
77             # Сначала собираем все директивы из .htaccess
78             my %Action=();
79             my @AddHandler=();
80             while(!eof(F)) {
81             my $s=; $s=~s/^\s+|#.*|\s+$//sg; next if $s eq "";
82             # Директива Action
83             if($s=~m/Action\s+([\w\d-]+)\s*"?([^"]+)"?/si) {
84             $Action{$1}=1 if $2 eq $th->{self_scriptname};
85             }
86             # Директива AddHandler
87             if($s=~m/AddHandler\s+([\w\d-]+)\s*(.+)/si) {
88             push @AddHandler, [ $1, [ map { s/^\s*\.?|\s+$//sg; $_?($_):() } split /\s+/, $2 ] ];
89             }
90             # Директива ErrorDocument 404
91             if($s=~/ErrorDocument\s+404\s+"?([^"]+)"?/si) {
92             $th->set_404_url($1);
93             }
94             }
95             # Затем добавляем цепочки обработчиков
96             my %ProcessedExt=();
97             foreach my $info (@AddHandler) {
98             my ($hand,$ext)=@$info;
99             # Сразу отметаем обработчики, которые НЕ указывают на Apache::Scriptor.
100             # Мы не могли этого сделать в верхнем цикле, потопму что директивы
101             # Action и AddHandler могут идти не по порядку.
102             next if !$Action{$hand};
103             # Добавляем для каждого расширения обработчик в цепочку
104             foreach my $ext (@$ext) {
105             # Если это расширение встречается в текущем htaccess-файле
106             # впервые, это значит, что начата очередная цепочка обработчиков.
107             # В этом случае нужно удалить уже имеющуюся цепочку.
108             if(!$ProcessedExt{$ext}) {
109             $th->removehandler($ext);
110             $ProcessedExt{$ext}=1;
111             }
112             # Затем спокойно вызываем pushhandler()
113             $th->pushhandler($ext,$hand);
114             }
115             }
116             }
117            
118             sub process_htaccesses
119             { my ($th,$path)=@_;
120             # Сначала определяем все полные пути к htaccess-файлам
121             my @Hts=();
122             while($path=~m{[/\\]}) {
123             if(-d $path) {
124             my $ht="$path/$th->{htaccess}";
125             unshift(@Hts,$ht) if -f $ht;
126             }
127             $path=~s{[/\\][^/\\]*$}{}s;
128             }
129             # Затем обрабатываем эти файлы, начиная с самого корневого
130             map { $th->process_htaccess($_) } @Hts;
131             }
132            
133             # void run_uri(string $uri [,string $path_translated])
134             # Запускает указанный URI на обработку. Если указан параметр $path_translated,
135             # то он содержит полное имя файла с содержимым для обработки. В противном
136             # случае имя файла вычисляется автоматически на основе $uri (это не всегда
137             # работает правильно - например, такая штука не пройдет, если директория была
138             # заведена как Alias Apache).
139             sub run_uri
140             { my ($this,$uri,$path)=@_;
141             Header("X-Powered-by: Apache::Scriptor v$VERSION. (C) Dmitry Koterov ") if !$CopySend++;
142            
143             # Теперь работаем с КОПИЕЙ объекта. Таким образом, дальнейшие вызовы
144             # process_htaccesses и т.д. не отразятся на общем состоянии объекта
145             # после окончания запроса.
146             local $this->{Handlers}={%{$this->{Handlers}}};
147             local $this->{404}=$this->{404};
148            
149             # Разделяем на URL и QUERY_STRING
150             local ($ENV{SCRIPT_NAME},$q) = split /\?/, $uri, 2;
151             $ENV{QUERY_STRING}=defined $q? $q : "";
152            
153             # Вычисляем путь к файлу скрипта по URI
154             if(!$path) {
155             $path="$ENV{DOCUMENT_ROOT}$ENV{SCRIPT_NAME}";
156             }
157            
158             # Готовим новые переменные окружения, чтобы скрыть Apache::Scriptor;
159             local $ENV{REQUEST_URI} = $uri;
160             local $ENV{SCRIPT_FILENAME} = $path;
161             local $ENV{REDIRECT_URL}; delete($ENV{REDIRECT_URL});
162             local $ENV{REDIRECT_STATUS}; delete($ENV{REDIRECT_STATUS});
163             # Меняем текущую директорию.
164             my $MyDir=getcwd();
165             ($MyDir) = $MyDir=~/(.*)/;
166             my ($dir) = $path; $dir=~s{(.)[/\\][^/\\]*$}{$1}sg;
167            
168             chdir($dir); getcwd(); # getcwd: Сбрасывает $ENV{PWD}. Нам это надо? Фиг знает...
169             # Обрабатываем файлы .htaccess.
170             $this->process_htaccesses($path);
171            
172             # Все. Теперь состояние переменных скрипта такое же, как у страницы,
173             # которая в дальнейшем получит управление. Запускаем обработчики.
174             $this->__run_handlers();
175            
176             # Восстанавливаем текущую директорию
177             chdir($MyDir); getcwd();
178             }
179            
180            
181             # Внутренняя функция - запускает обработчики для файла, который задан в %ENV.
182             # Вызывается В КОНТЕКСТЕ ЭТОГО ФАЙЛА (то есть, %ENV находится в таком же состоянии,
183             # как после обячного запуска скрипта Апачем, и текущая директория соответствует
184             # директории со страницей).
185             sub __run_handlers
186             { my ($th)=@_;
187             # расширение файла
188             my ($ext) = $ENV{SCRIPT_FILENAME}=~m|\.([^.]*)$|; if(!defined $ext) { $ext=""; }
189            
190             # выбираем список обработчиков для этого расширения
191             $th->{Handlers}{$ext}
192             or die "$ENV{SCRIPT_NAME}: could not find handlers chain for extension \"$ext\"\n";
193            
194             # входной буфер (вначале в нем содержимое файла, если доступно)
195             my $input="";
196             if(open(local *F, $ENV{SCRIPT_FILENAME})) { local ($/,$\); binmode(F); $input=; }
197            
198             # проходимся по всем обработчикам
199             my $next=1; # номер следующего обработчика
200             my @hands=@{$th->{Handlers}{$ext}};
201             NoAutoflush() if @hands>1;
202             foreach my $hand (@hands)
203             { # Объект перенаправления вывода. Если у нас всего один обработчик, то
204             # перенаправлять вывод не потребуется. Иначе - потребуется, что и делается
205             my $OutObj=$hands[$next++]? CGI::WebOut->new : undef;
206             my $func=$hand; # указатель на функцию
207             # Проверяем - нужно ли загрузить обработчик?
208             if((ref($func)||"") ne "CODE") {
209             # переключаем пакет
210             package Apache::Scriptor::Handlers;
211             # обработчика еще нет в этом пакете?
212             if(!*{$func}{CODE}) {
213             my $hname="$th->{HandDir}/$func.pl";
214             -f $hname or die "$ENV{SCRIPT_NAME}: could not load the file $hname for handler $hand\n";
215             do "$hname";
216             *{$func}{CODE} or die "$ENV{SCRIPT_NAME}: cannot find handler $hand in $hname after loading $hname\n";
217             }
218             # получаем указатель на функцию обработчика
219             local $this=$th;
220             $func=*{$func}{CODE};
221             }
222             # Функция обработчика принимает параметр: входной буфер.
223             # Ее задача - обработать его и, используя print, пропечатать результат.
224             # В случае ошибки (файл не найден) функция должна возвратить -1!
225             my $result=&$func($input);
226             if($result eq "-1") {
227             if($th->{404} && $th->{404} ne $th->{self_scriptname}) {
228             Redirect($th->{404});
229             exit;
230             } else {
231             die "$hand: could not find the file $ENV{SCRIPT_FILENAME}\n";
232             }
233             }
234            
235             # То, что получилось, кладем во входной буфер для следующего обработчика.
236             # Если вывод не перенаправлялся, то кладем туда "".
237             $input=$OutObj?$OutObj->buf:"";
238             }
239             # Окончательный результат окажется во входном буфере (как будто готовый для
240             # следующего обработчика, которого нет). Его-то мы и выводим в браузер.
241             print $input;
242             }
243            
244            
245            
246             package Apache::Scriptor::Handlers;
247             use CGI::WebOut;
248             # В этом пакете перечисляются стандартные обработчики,
249             # которые, скорее всего, будут испрользованы в первую очередь.
250             # Именно в этот пакет попадают обработчики, загруженные автоматически.
251            
252             # Обработчик по умолчанию - просто выводит текст
253             sub default
254             { my ($input,$fname)=@_;
255             -f $ENV{SCRIPT_FILENAME} or return -1;
256             CGI::WebOut::Header("Content-type: text/html");
257             print $input;
258             }
259            
260             # Обработчик perl-скриптов. Подразумевается, что вывод скрипта идет через print.
261             sub perl
262             { my ($input)=@_;
263             -f $ENV{SCRIPT_FILENAME} or return -1;
264             eval("\n#line 1 \"$ENV{SCRIPT_NAME}\"\npackage main; $input");
265             }
266            
267             return 1;
268             __END__