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__
|