-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathrouter.php
1241 lines (1046 loc) · 45.9 KB
/
router.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?php
// to ensure Redirector compatibility, please set display_errors to Off in php.ini
// constants
// all constants, globals, function names are prefixed with "router" to avoid any potential name collisions in included scripts
const ROUTER_MAD4FP = false;
const ROUTER_HTDOCS = 'htdocs';
const ROUTER_CGI_BIN = 'cgi-bin';
const ROUTER_BUILD_HTTP_QUERY = true;
const ROUTER_ROUTE_PATHNAMES_FROM_HTDOCS_CONTENT = true;
const ROUTER_ALLOW_CROSSDOMAIN = true;
const ROUTER_SSL = true;
const ROUTER_MKDIR_MODE = 0755;
const ROUTER_FILE_HEADER_STATUS_PATTERN = '/^\s*http\s*\/\s*\d+\s*\.\s*\d+\s+(\d+)/i';
const ROUTER_FILE_HEADER_PATTERN = '/^[^:\S]*([^:\s]+)[^:\S]*:\s*(\S+(?:\s+\S+)*)\s*$/i';
const ROUTER_FILE_READ_LENGTH = 8192;
const ROUTER_RETRY_SLEEP = 10000;
const ROUTER_KBSIZE = 1024;
const ROUTER_WARN_PERCENTAGE = 50;
const ROUTER_TAB = "\t";
const ROUTER_RETURN = "\r";
const ROUTER_NEWLINE = "\n";
$router_base_urls = false;
// the html extension is first for legacy reasons
$router_index_extensions = array('html', 'htm');
$router_script_extensions = array('php', 'php5', 'phtml');
$router_extension_mimetypes = array(
'htm' => 'text/html',
'html' => 'text/html',
'css' => 'text/css',
'js' => 'text/javascript',
'svg' => 'image/svg+xml',
'xml' => 'application/xml',
'wasm' => 'application/wasm',
'swf' => 'application/x-shockwave-flash',
'dir' => 'application/x-director',
'dxr' => 'application/x-director',
'dcr' => 'application/x-director',
'cst' => 'application/x-director',
'cxt' => 'application/x-director',
'cct' => 'application/x-director',
'swa' => 'application/x-director',
'w3d' => 'application/x-director',
'aam' => 'application/x-authorware-map',
'class' => 'application/java',
'jar' => 'application/java-archive',
'xap' => 'application/x-silverlight-app',
'unity3d' => 'application/vnd.unity',
'cmo' => 'application/x-virtools',
'vmo' => 'application/x-virtools',
'nmo' => 'application/x-virtools',
'nms' => 'application/x-virtools',
'cnc' => 'application/x-cnc',
'pwc' => 'application/x-pulse-player',
'pwn' => 'application/x-pulse-download',
'pw3' => 'application/x-pulse-player-32',
'pws' => 'application/x-pulse-stream',
'wrl' => 'model/vrml',
'vrt' => 'x-world/x-vrt',
'svr' => 'x-world/x-svr',
'xvr' => 'x-world/x-xvr'
);
// URL Reserved Characters (RFC1738)
$router_url_reserved_characters = array(';', '/', '?', ':', '@', '=', '&');
ignore_user_abort(true);
set_time_limit(0);
// this is the stream where errors will be output
$router_file_pointer_resource_stdout = @fopen('php://stdout', 'w');
function router_output($message) {
global $router_file_pointer_resource_stdout;
if ($router_file_pointer_resource_stdout !== false) {
@fwrite($router_file_pointer_resource_stdout, $message . ROUTER_NEWLINE);
}
}
function router_get_base_urls() {
// open file in same directory
// we don't use flags to trim newline characters because PHP 4 doesn't support it
$base_urls = false;
$base_urls_file = @file(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'router_base_urls.txt');
if ($base_urls_file === false) {
router_output('Failed to Get Base URLs File');
return $base_urls;
}
$base_urls = array();
foreach ($base_urls_file as $base_url) {
// comments may be created by using a pound sign
// everything after is ignored
$base_url_exploded = explode('#', $base_url, 2);
if (isset($base_url_exploded[0]) === true) {
$base_url = $base_url_exploded[0];
}
// we trim the newlines off the end, then read this as a space delimited associative array
$base_url_matches = array();
$base_url_match = preg_match('/^\s*(\S+)\s+(\S.*)$/', rtrim($base_url, ROUTER_RETURN . ROUTER_NEWLINE), $base_url_matches);
// ignore lines where the Base URL is invalid
if ($base_url_match === 1) {
$base_urls[$base_url_matches[1]] = $base_url_matches[2];
}
}
return $base_urls;
}
// get the date specified (or if unspecified, the current date) in HTTP-Date format (RFC1123)
function router_get_http_date($timestamp = null) {
if (is_null($timestamp) === true) {
$timestamp = time();
}
return gmdate('D, d M Y H:i:s', $timestamp) . ' GMT';
}
// gets a pathname that may be reliably compared to another pathname
function router_get_pathname_comparable($pathname) {
$pathname_comparable = @realpath($pathname);
if ($pathname_comparable === false) {
return false;
}
return strtolower($pathname_comparable);
}
// compares if one pathname has another pathname
function router_has_pathname_comparable($pathname_top, $pathname) {
$pathname_top_comparable = router_get_pathname_comparable($pathname_top);
$pathname_comparable = router_get_pathname_comparable($pathname);
if ($pathname_top_comparable === false || $pathname_comparable === false) {
return false;
}
if (strpos($pathname_comparable, $pathname_top_comparable) !== 0 || $pathname_top_comparable === $pathname_comparable) {
return false;
}
return true;
}
// this will check if any extension in basename $pathname_info_basename
// matches any extension in array $extensions, case-insensitively
// more specifically, it will check the basename for any instance of
// a) a period, followed by the extension, and then another period
// b) a period, followed by the extension at the end of the basename
// it will return true if an instance of the pattern is found
// where the basename is $pathname_info_basename and the extension
// is any extension in array $extensions
// this is an intentional design - conventionally, extensions
// may only be at the end of the basename. However, in Apache,
// a file may have multiple extensions, each seperated by
// a period, not including the text before the first period.
// This is taken into consideration here
function router_compare_extensions($pathname_info_basename, $extensions) {
// notice this adds a period to the end of the basename
$pathname_info_basename_period = $pathname_info_basename . '.';
$extensions_count = count($extensions);
for ($i = 0; $i < $extensions_count; $i++) {
// since the basename has a period on the end, we don't need two conditions
if (stripos($pathname_info_basename_period, '.' . $extensions[$i] . '.') !== false) {
return true;
}
}
return false;
}
function router_get_mimetype($pathname_info_basename, $mimetype_default = 'application/octet-stream') {
global $router_extension_mimetypes;
// first, get all the extensions as an array
// we slice off the first element as it's not an extension
// if the file has no extension, this leaves us with an empty array
$pathname_info_basename_extensions = array_slice(explode('.', $pathname_info_basename), 1);
// we go from the last extension to first, since the last extension takes precedence
for ($i = count($pathname_info_basename_extensions) - 1; $i >= 0; $i--) {
foreach ($router_extension_mimetypes as $extension => $mimetype) {
// both lowercase, for a case-insensitive compare
if (strtolower($pathname_info_basename_extensions[$i]) === strtolower($extension)) {
return $mimetype;
}
}
}
return $mimetype_default;
}
// this function will read a file in chunks
// the individual chunks being read are significantly
// smaller than PHP's memory limit, the caller
// then echoes the read chunk and flushes the memory
// so we can read large files without hitting any memory limit
// if the read fails, the caller doesn't continue attempting
// to read the file, but sometimes the download
// just needs to catch up so we sleep the program
// and try again
function router_read_file($file_pointer_resource) {
//router_output(ROUTER_TAB . 'Reading File');
if ($file_pointer_resource === false) {
// error, caller does not continue
return false;
}
$read_file = @fread($file_pointer_resource, ROUTER_FILE_READ_LENGTH);
$read_file_length = strlen($read_file);
if ($read_file === false || $read_file_length <= 0) {
if (@feof($file_pointer_resource) === true) {
// error, caller does not continue
return false;
}
usleep(ROUTER_RETRY_SLEEP);
// no error, caller continues
return true;
}
// no error, caller read the file
return $read_file;
}
// this will safely close and delete the file if the write fails
// this prevents corrupt files from getting saved
function router_write_file($file_pointer_resource, $pathname, $wrote_file) {
//router_output(ROUTER_TAB . 'Writing File');
if ($file_pointer_resource === false) {
// error, caller does not continue
return false;
}
if (@fwrite($file_pointer_resource, $wrote_file) === false) {
if (@fclose($file_pointer_resource) === false) {
router_output(ROUTER_TAB . 'Failed to Close File');
// error, caller does not continue
return false;
}
if ($pathname !== false) {
if (@unlink($pathname) === false) {
router_output(ROUTER_TAB . 'Failed to Unlink File');
// error, caller does not continue
return false;
}
}
// no error, caller continues
return true;
}
// no error, caller wrote the file
return $wrote_file;
}
// send the specified file headers
function router_send_file_headers($file_headers) {
//router_output(ROUTER_TAB . 'Sending File Headers');
if (headers_sent() === true) {
return;
}
// prevent PHP from sending a text/html header by default
// we do this here specifically because, it's a PHP default behaviour
// we want to prevent on ANY status code
header('Content-Type:');
// if we're using PHP 5.3.0 or greater, we can remove the header altogether instead of sending a blank one
// (we can safely assume we're using at least PHP 4)
if (version_compare(PHP_VERSION, '5.3.0', '>=') === true) {
header_remove('Content-Type');
}
$file_headers_count = count($file_headers);
// for each file header...
for ($i = 0; $i < $file_headers_count; $i++) {
// even though the regex is case-insensitive, we need to string compare it after
// so we make the header lowercase
$file_header_lower = strtolower($file_headers[$i]);
// check if this is a Status (like HTTP/1.1 200 OK for example)
$file_header_lower_status_match = preg_match(ROUTER_FILE_HEADER_STATUS_PATTERN, $file_header_lower);
// if it's a Status, just send it
if ($file_header_lower_status_match === 1) {
//router_output(ROUTER_TAB . 'Status Header Sent: ' . $file_headers[$i]);
header($file_headers[$i]);
} else {
// not a HTTP Status header - so let's check if this message is valid and allowed
$file_header_lower_matches = array();
$file_header_lower_match = preg_match(ROUTER_FILE_HEADER_PATTERN, $file_header_lower, $file_header_lower_matches);
if ($file_header_lower_match === 1) {
// this is a valid HTTP header
// disallow closed connections because this causes a Redirector bug
// also disallow the application/octet-stream mimetype
// and Flash dislikes Content-Disposition
// we also disallow Date because PHP already is sending it (in the wrong format but w/e)
// disallow Report-To (Cloudflare header) because it causes "not a valid Director file" from Shockwave
if (($file_header_lower_matches[1] === 'content-type' && $file_header_lower_matches[2] !== 'application/octet-stream')
|| $file_header_lower_matches[1] === 'content-length'
|| $file_header_lower_matches[1] === 'last-modified'
|| $file_header_lower_matches[1] === 'location') {
//router_output(ROUTER_TAB . 'Header Sent: ' . $file_headers[$i]);
header($file_headers[$i]);
}
}
}
}
// this was moved here
// it's specifically here so it'll always be sent with any status code
if (ROUTER_ALLOW_CROSSDOMAIN === true) {
header('Access-Control-Allow-Headers: *');
header('Access-Control-Allow-Methods: *');
header('Access-Control-Allow-Origin: *');
}
}
// serve a file from cgi-bin as a script
function router_serve_file_from_cgi_bin($pathname_cgi_bin, $pathname_cgi_bin_info, $pathname_search_hash, $pathname_trailing_slash, $index_script_extensions = array(), $index_script_extension_cgi_bin = -1) {
router_output('Serving File From CGI-BIN: ' . $pathname_cgi_bin);
if ($index_script_extension_cgi_bin >= count($index_script_extensions)) {
return false;
}
// get last modified date so we can send that header for Shockwave
$filemtime = @filemtime($pathname_cgi_bin);
if ($filemtime === false) {
router_output(ROUTER_TAB . 'File Locked With Only Readers');
return false;
}
$dirname_file = dirname(__FILE__);
// go to the working directory of THAT script
if (@chdir($pathname_cgi_bin_info['dirname']) === false) {
router_output(ROUTER_TAB . 'Failed to Change Directory: ' . $pathname_cgi_bin_info['dirname']);
return false;
}
// redirect if we're going to an index.htm/.html file
if ($index_script_extension_cgi_bin !== -1 && $pathname_trailing_slash === '/') {
$file_headers = array(
$_SERVER['SERVER_PROTOCOL'] . ' 301 Moved Permanently',
'Location: ' . $_SERVER['SCRIPT_NAME'] . '/index.' . $index_script_extensions[$index_script_extension_cgi_bin] . $pathname_search_hash
);
router_send_file_headers($file_headers);
return true;
}
// send these headers before the other script starts potentially echoing stuff
$file_headers = array(
$_SERVER['SERVER_PROTOCOL'] . ' 200 OK',
'Last-Modified: ' . router_get_http_date($filemtime)
);
router_send_file_headers($file_headers);
// include the script
// careful! include is a language construct not a function
// if you change these parenthesis, it will blow up on you
if ((include $pathname_cgi_bin_info['basename']) === false) {
router_output(ROUTER_TAB . 'Failed to Include: ' . $pathname_cgi_bin_info['basename']);
return false;
}
// return to the working directory of THIS script afterwards
if (@chdir($dirname_file) === false) {
router_output(ROUTER_TAB . 'Failed to Change Directory after Including: ' . $dirname_file);
return false;
}
return true;
}
// serve a local file from htdocs
function router_serve_file_from_htdocs($pathname_htdocs, $pathname_search_hash, $pathname_trailing_slash, $pathname_htdocs_info_basename, $index_extension_htdocs = -1) {
global $router_index_extensions;
router_output('Serving File From HTDOCS: ' . $pathname_htdocs);
// this function always returns filesize, and a filesize of zero is a fail
// however when this function fails we just continue to try and download
// the file from the internet
$filesize = 0;
if ($index_extension_htdocs >= count($router_index_extensions)) {
return $filesize;
}
// if we fail to get any of these, bail
$filesize = @filesize($pathname_htdocs);
$filemtime = @filemtime($pathname_htdocs);
$file_pointer_resource_htdocs = @fopen($pathname_htdocs, 'rb');
if ($filesize === false || $filemtime === false || $file_pointer_resource_htdocs === false) {
router_output(ROUTER_TAB . 'File Locked With Only Readers');
$filesize = 0;
return $filesize;
}
// empty files are ignored as if they don't exist (because Archive.org serves empty files instead of 404'ing)
if ($filesize <= 0) {
router_output(ROUTER_TAB . 'Empty File');
// in case filesize is somehow negative, we clamp it to zero bytes size
$filesize = 0;
return $filesize;
}
// if redirecting to an index.htm/.html page, send the headers now that we've confirmed we can load the file successfully
if ($index_extension_htdocs !== -1 && $pathname_trailing_slash === '/') {
$file_headers = array(
$_SERVER['SERVER_PROTOCOL'] . ' 301 Moved Permanently',
'Location: ' . $_SERVER['SCRIPT_NAME'] . '/index.' . $router_index_extensions[$index_extension_htdocs] . $pathname_search_hash
);
router_send_file_headers($file_headers);
return $filesize;
}
// determine the mimetype of the local file based on its extension
$header_mimetype = router_get_mimetype($pathname_htdocs_info_basename);
// we have to make up all the headers to send since we're serving this from a local file
$file_headers = array(
$_SERVER['SERVER_PROTOCOL'] . ' 200 OK',
'Content-Length: ' . $filesize,
'Content-Type: ' . $header_mimetype,
'Last-Modified: ' . router_get_http_date($filemtime)
);
// send those headers
router_send_file_headers($file_headers);
// and now the file contents
// we use less memory by reading the file in chunks
// this allows us to output large files without eating up all that memory
while (@feof($file_pointer_resource_htdocs) === false) {
$read_file = router_read_file($file_pointer_resource_htdocs);
if ($read_file === false) {
break;
} else {
if ($read_file === true) {
continue;
}
}
echo($read_file);
// clean out the buffer memory so less memory is used
flush();
}
if (@fclose($file_pointer_resource_htdocs) === false) {
router_output(ROUTER_TAB . 'Failed to Close File');
$filesize = 0;
}
return $filesize;
}
// get a file pointer resource (which can be read) from a URL
function router_create_file_pointer_resource_from_url($url) {
global $router_url_reserved_characters;
router_output(ROUTER_TAB . 'Creating File Pointer Resource From URL: ' . $url);
// this makes URLs with special characters in them acceptable to the Infinity servers
$url = rawurlencode($url);
// but reserved characters, such as : or /, need exceptions so the URL is still valid
$router_url_reserved_characters_count = count($router_url_reserved_characters);
for ($i = 0; $i < $router_url_reserved_characters_count; $i++) {
$url = str_ireplace(rawurlencode($router_url_reserved_characters[$i]), $router_url_reserved_characters[$i], $url);
}
// unused now, this has been resolved server side
$file_pointer_resource = false;
if (version_compare(PHP_VERSION, '5.0.0', '<') === true) {
// fopen does not support contexts in this PHP version
$file_pointer_resource = @fopen($url, 'rb');
} else {
// request that the response be in a supported encoding
$options = array(
'http' => array(
'header' => 'Accept-Encoding: identity' . ROUTER_RETURN . ROUTER_NEWLINE
)
);
if (ROUTER_MAD4FP === true && ROUTER_BUILD_HTTP_QUERY === true) {
// forward POST requests
$options['http']['method'] = $_SERVER['REQUEST_METHOD'];
if ($options['http']['method'] === 'POST') {
$options['http']['header'] .= 'Content-Type: ' . isset($_SERVER['CONTENT_TYPE']) ? $_SERVER['CONTENT_TYPE'] : 'application/x-www-form-urlencoded' . ROUTER_RETURN . ROUTER_NEWLINE;
$options['http']['content'] = http_build_query($_POST);
}
}
if (ROUTER_SSL !== true) {
$options['ssl'] = array(
'verify_peer' => false,
'verify_peer_name' => false
);
}
$file_pointer_resource = @fopen($url, 'rb', false, stream_context_create($options));
}
if ($file_pointer_resource === false) {
router_output(ROUTER_TAB . 'Failed to Open File');
return $file_pointer_resource;
}
// if we're already at the end of the file - it's empty
if (@feof($file_pointer_resource) === true) {
router_output(ROUTER_TAB . 'Empty File');
$file_pointer_resource = false;
}
return $file_pointer_resource;
}
function router_destroy_file_pointer_resource_from_url($file_pointer_resource) {
//router_output(ROUTER_TAB . 'Destroying File Pointer Resource From URL');
if (@fclose($file_pointer_resource) === false) {
router_output(ROUTER_TAB . 'Failed to Close File');
return false;
}
return true;
}
// specifically for downloaded files, send the correct headers
// this function is only called when we've downloaded at least a bit of the file so we know we can successfully download it
function router_send_downloaded_file_headers($file_headers, $file_location, $file_contents_length, $pathname_search_hash, $pathname_trailing_slash, $index_extension = -1) {
global $router_index_extensions;
//router_output(ROUTER_TAB . 'Sending Downloaded File Headers');
if ($index_extension >= count($router_index_extensions)) {
// error, caller does not continue
return false;
}
// if we are going to an index.htm/.html file...
if ($file_location === '' && $index_extension !== -1 && $pathname_trailing_slash === '/') {
// now that we've determined we can successfully download the file
// and since we're redirecting, send the redirection header
$file_location = $_SERVER['SCRIPT_NAME'] . '/index.' . $router_index_extensions[$index_extension] . $pathname_search_hash;
}
// if we're supposed to redirect...
if ($file_location !== '') {
// send the location to redirect to
$file_headers = array(
$_SERVER['SERVER_PROTOCOL'] . ' 301 Moved Permanently',
'Location: ' . $file_location
);
router_send_file_headers($file_headers);
// no error, caller returns this length
return $file_contents_length;
}
// otherwise just send the normal 200 OK header
// edit: this is now done after writing the file
//router_send_file_headers($file_headers);
// no error, caller continues
return true;
}
// downloads a file to a specific location, in htdocs
// if it fails to download it as a file, the function
// continues to download the rest of the file and echo it
// (e.g., if the user is out of disk space or the file is locked)
function router_download_file_htdocs($file_pointer_resource, $file_headers, $file_location, $file_content_length, $file_pointer_resource_htdocs_index, $pathname_search_hash, $pathname_trailing_slash, $pathname_htdocs_index, $index_extension = -1) {
global $router_index_extensions;
//router_output(ROUTER_TAB . 'Downloading File To HTDOCS');
// this function always returns the length, if it's zero, it is a fail
$file_contents_length = 0;
if ($index_extension >= count($router_index_extensions)) {
return $file_contents_length;
}
$file_content_length_kb = intval(ceil($file_content_length / ROUTER_KBSIZE));
$file_contents = '';
$file_contents_length = 0;
$sent_file_headers = false;
$sent_downloaded_file_headers = false;
$read_file = false;
$wrote_file = false;
$next_warn_percentage = ROUTER_WARN_PERCENTAGE;
$prev_warn_length = 0;
$next_warn_length = $file_content_length * ($next_warn_percentage / 100);
$next_warn_iteration = 0;
/*
if ($file_content_length === -1) {
router_output(ROUTER_TAB . 'Filesize: Unknown');
} else {
router_output(ROUTER_TAB . 'Filesize: ' . $file_content_length_kb . ' KB');
}
*/
// while there's still more to download
while (@feof($file_pointer_resource) === false) {
$read_file = router_read_file($file_pointer_resource);
if ($read_file === false) {
break;
} else {
if ($read_file === true) {
continue;
}
}
$file_contents_length += strlen($read_file);
// if we know the length
if ($file_content_length !== -1) {
// stream it!
if ($file_contents_length !== 0) {
// send the downloaded file headers, but only if we have not already
if ($sent_downloaded_file_headers === false) {
$sent_downloaded_file_headers = router_send_downloaded_file_headers($file_headers, $file_location, $file_contents_length, $pathname_search_hash, $pathname_trailing_slash, $index_extension);
if ($sent_downloaded_file_headers === false) {
$file_contents_length = 0;
return $file_contents_length;
} else {
if ($sent_downloaded_file_headers === $file_contents_length) {
return $file_contents_length;
}
}
}
// attempt to write the file BEFORE headers
// if it fails, at least we didn't send bad headers
if ($file_pointer_resource_htdocs_index !== false) {
$wrote_file = router_write_file($file_pointer_resource_htdocs_index, $pathname_htdocs_index, $read_file);
if ($wrote_file === false) {
//$file_contents_length = 0;
return $file_contents_length;
} else {
if ($wrote_file === true) {
$file_pointer_resource_htdocs_index = false;
}
}
}
if ($sent_file_headers === false) {
router_send_file_headers($file_headers);
$sent_file_headers = true;
}
echo($read_file);
flush();
}
// progress meter
while ($file_contents_length >= $next_warn_length && $prev_warn_length < $next_warn_length) {
router_output(ROUTER_TAB . 'Downloaded and Streamed ' . $next_warn_percentage . '% of ' . $file_content_length_kb . ' KB');
$next_warn_percentage += ROUTER_WARN_PERCENTAGE;
if ($next_warn_percentage > 100) {
$next_warn_percentage = 100;
}
$prev_warn_length = $next_warn_length;
$next_warn_length = $file_content_length * ($next_warn_percentage / 100);
}
} else {
// we don't have enough info to stream the file... we have to download it whole
$file_contents .= $read_file;
$next_warn_iteration++;
// progress meter - except we don't know what percent we're at
// so just warn every few iterations
if ($next_warn_iteration > ROUTER_WARN_PERCENTAGE) {
$file_content_length_kb = intval(ceil($file_contents_length / ROUTER_KBSIZE));
router_output(ROUTER_TAB . 'Downloaded ' . $file_content_length_kb . ' KB');
$next_warn_iteration = 0;
}
}
}
// empty file means fail
if ($file_contents_length === 0) {
router_output(ROUTER_TAB . 'Empty File');
return $file_contents_length;
}
// if we didn't know the length - well, the download finished so we do now, so handle for that
if ($file_content_length === -1) {
$file_content_length_kb = intval(ceil($file_contents_length / ROUTER_KBSIZE));
router_output(ROUTER_TAB . 'Downloaded 100% of ' . $file_content_length_kb . ' KB');
array_push($file_headers, 'Content-Length: ' . $file_contents_length);
if ($sent_downloaded_file_headers === false) {
$sent_downloaded_file_headers = router_send_downloaded_file_headers($file_headers, $file_location, $file_contents_length, $pathname_search_hash, $pathname_trailing_slash, $index_extension);
if ($sent_downloaded_file_headers === false) {
$file_contents_length = 0;
return $file_contents_length;
} else {
if ($sent_downloaded_file_headers === $file_contents_length) {
return $file_contents_length;
}
}
}
// attempt to write the file BEFORE headers
// if it fails, at least we didn't send bad headers
if ($file_pointer_resource_htdocs_index !== false) {
$wrote_file = router_write_file($file_pointer_resource_htdocs_index, $pathname_htdocs_index, $file_contents);
if ($wrote_file === false) {
//$file_contents_length = 0;
return $file_contents_length;
} else {
if ($wrote_file === true) {
$file_pointer_resource_htdocs_index = false;
}
}
}
if ($sent_file_headers === false) {
router_send_file_headers($file_headers);
$sent_file_headers = true;
}
echo($file_contents);
flush();
}
return $file_contents_length;
}
// download the file (it's pretty self explanatory dude)
function router_download_file($file_pointer_resource, $file_headers, $file_location, $file_content_length, $file_last_modified, $file_content_encoding, $pathname_search_hash, $pathname_trailing_slash, $pathname_htdocs, $index_extension = 1) {
global $router_index_extensions;
router_output(ROUTER_TAB . 'Downloading File To: ' . $pathname_htdocs);
// this function always returns the length, if it's zero, it is a fail
$file_contents_length = 0;
if ($index_extension >= count($router_index_extensions)) {
return $file_contents_length;
}
// this is where we handle creating the file to download in htdocs
$pathname_htdocs_index = false;
$file_pointer_resource_htdocs_index = false;
if ($index_extension !== -1) {
// treat as directory name
if (@is_dir($pathname_htdocs) === true || @mkdir($pathname_htdocs, ROUTER_MKDIR_MODE, true) === true) {
// do NOT use $pathname_trailing_slash here because $pathname_htdocs may be $pathname_htdocs_index
$pathname_htdocs_index = $pathname_htdocs . (substr($pathname_htdocs, -1) === '/' ? '' : '/') . 'index.' . $router_index_extensions[$index_extension];
}
} else {
// do NOT use $pathname_trailing_slash here because $pathname_htdocs may be $pathname_htdocs_index
if (substr($pathname_htdocs, -1) === '/') {
// treat as directory name because even though there is no file extension
// and we weren't redirected to an index, it has a trailing slash
if (count($router_index_extensions) > 0) {
if (@is_dir($pathname_htdocs) === true || @mkdir($pathname_htdocs, ROUTER_MKDIR_MODE, true) === true) {
// yes, it does have a trailing slash already
$pathname_htdocs_index = $pathname_htdocs . 'index.' . $router_index_extensions[0];
}
}
} else {
// treat as filename
$pathname_htdocs_info = pathinfo($pathname_htdocs);
if (@is_dir($pathname_htdocs_info['dirname']) === true || @mkdir($pathname_htdocs_info['dirname'], ROUTER_MKDIR_MODE, true) === true) {
$pathname_htdocs_index = $pathname_htdocs;
}
}
}
$file_content_encoding_supported = true;
$file_content_encodings_lower = preg_split('/[^\,\S]*\,\s*/i', strtolower($file_content_encoding), -1, PREG_SPLIT_NO_EMPTY);
if ($file_content_encodings_lower !== false) {
$file_content_encodings_lower_count = count($file_content_encodings_lower);
for ($i = 0; $i < $file_content_encodings_lower_count; $i++) {
if ($file_content_encodings_lower[$i] !== 'identity') {
// if there is any content encoding that is not identity, it is an unsupported encoding
$file_content_encoding_supported = false;
}
}
}
// if we managed to make the directory, save the file in htdocs if it isn't locked - but don't stop if we error in doing so
if ($pathname_htdocs_index !== false) {
if (ROUTER_MAD4FP === true || $file_content_encoding_supported === true) {
if ($file_content_encoding_supported !== true) {
router_output(ROUTER_TAB . 'WARNING: Downloading File With Unsupported Content Encoding (' . $file_content_encoding . ')');
}
$file_pointer_resource_htdocs_index = @fopen($pathname_htdocs_index, 'wb');
if ($file_pointer_resource_htdocs_index === false) {
router_output(ROUTER_TAB . 'File Locked With Only Readers');
}
}
} else {
router_output(ROUTER_TAB . 'Failed to Make Directory');
}
// now we actually download it to htdocs
$file_contents_length = router_download_file_htdocs($file_pointer_resource, $file_headers, $file_location, $file_content_length, $file_pointer_resource_htdocs_index, $pathname_search_hash, $pathname_trailing_slash, $pathname_htdocs_index, $index_extension);
if ($file_pointer_resource_htdocs_index !== false) {
if (@fclose($file_pointer_resource_htdocs_index) === false) {
router_output(ROUTER_TAB . 'Failed to Close File');
$file_contents_length = 0;
return $file_contents_length;
}
if ($pathname_htdocs_index !== false) {
if ($file_last_modified !== false) {
if (touch($pathname_htdocs_index, $file_last_modified) === false) {
router_output(ROUTER_TAB . 'Failed to Touch File');
$file_contents_length = 0;
return $file_contents_length;
}
}
// if the file is empty, delete it
if ($file_contents_length === 0) {
if (@unlink($pathname_htdocs_index) === false) {
router_output(ROUTER_TAB . 'Failed to Unlink File');
return $file_contents_length;
}
}
}
}
// if the parent directory exists and is empty, remove it
// this prevents router leaving empty directories in htdocs
if ($file_contents_length === 0 && $pathname_htdocs_index !== false) {
// get the comparable pathname of the file's parent directory
$dirname_htdocs_index_comparable = router_get_pathname_comparable(dirname($pathname_htdocs_index));
// while the pathname of the directory is within htdocs, remove the directory, then get the pathname of the parent directory
// rmdir only removes empty directories
while ($dirname_htdocs_index_comparable !== false && router_has_pathname_comparable(ROUTER_HTDOCS, $dirname_htdocs_index_comparable) === true && @is_dir($dirname_htdocs_index_comparable) === true && @rmdir($dirname_htdocs_index_comparable) === true) {
$dirname_htdocs_index_comparable = router_get_pathname_comparable(dirname($dirname_htdocs_index_comparable));
}
}
return $file_contents_length;
}
// all your base are belong to us
function router_serve_file_from_base_urls($pathname, $pathname_search_hash, $pathname_trailing_slash, $pathname_no_extension, $pathname_htdocs, $index_extension = -1) {
global $router_index_extensions;
global $router_base_urls;
router_output('Serving File From Base URLs: ' . $pathname);
// the function stream_get_meta_data that we must use is in 4.3.0 minimum
// we assume at least PHP 4 is in use elsewhere in the script
if (version_compare(PHP_VERSION, '4.3.0', '<') === true) {
router_output('Unsupported PHP Version: ' . PHP_VERSION);
return false;
}
$pathname_index = $pathname;
if ($index_extension >= count($router_index_extensions)) {
return false;
}
if ($index_extension !== -1) {
$pathname_index = $pathname . $pathname_trailing_slash . 'index.' . $router_index_extensions[$index_extension];
}
$pathname_info_index = pathinfo($pathname_index);
$pathname_info_basename_index = $pathname_info_index['basename'];
$pathname_index_search_hash = $pathname_index . $pathname_search_hash;
if ($router_base_urls === false) {
return false;
}
// the point of this next section is mainly to obtain four important pieces of information
// - the file status code - so we know if it exists
// - the file location - if we get redirected somewhere else
// - the file's content length - so we know which percentage of it is downloaded when we stream it
// - the file's last modified date - because Shockwave requires it
$file_pointer_resource = false;
$stream_meta_data = array();
$file_header = '';
$file_header_status_matches = array();
$file_header_status_match = false;
$file_header_matches = array();
$file_header_match = false;
$file_headers = array();
$file_header_pathname_index_pos = false;
$file_status_code = -1;
$file_location = '';
$file_content_length = -1;
$file_last_modified = false;
$file_content_encoding = 'identity';
$file_contents_length = 0;
// we loop through every Base URL
// if there's an error, we proceed to the next one
// if we get an empty file, we also try for index.htm/.html
// (at which point which extension we're currently attempting is defined by $index_extensions)
foreach ($router_base_urls as $base => $url) {
router_output(ROUTER_TAB . 'Using Base: ' . $base);
$file_pointer_resource = router_create_file_pointer_resource_from_url($url . $pathname_index_search_hash);
if ($file_pointer_resource !== false) {
// sometimes there is a 301 Redirect, in which case two headers are found this way
// (one for the redirect and the other for OK)
$stream_meta_data = stream_get_meta_data($file_pointer_resource);
$file_headers = array();
// reset these so they aren't carried over from the previous base loop
$file_status_code = -1;
$file_location = '';
$file_content_length = -1;
$file_last_modified = false;
$file_content_encoding = 'identity';
$file_contents_length = 0;
if (isset($stream_meta_data) === true && isset($stream_meta_data['wrapper_data']) === true) {
$wrapper_data = $stream_meta_data['wrapper_data'];
$wrapper_data_count = count($wrapper_data);
for ($i = 0; $i < $wrapper_data_count; $i++) {
//router_output('Header: ' . $wrapper_data[$i]);
$file_header = $wrapper_data[$i];
// we'll be comparing the headers ourselves after, and they're case-insensitive
$file_header_status_matches = array();
// is this a Status message according to our regex?
$file_header_status_match = preg_match(ROUTER_FILE_HEADER_STATUS_PATTERN, $file_header, $file_header_status_matches);
if ($file_header_status_match === 1) {
// yes
$file_status_code = intval($file_header_status_matches[1]);
if ($file_status_code === 200) {
// ensure the HTTP version of the response matches that of the request (for Shockwave)
array_push($file_headers, $_SERVER['SERVER_PROTOCOL'] . ' 200 OK');
}
} else {
// this isn't a Status message, but is it a valid HTTP header?
$file_header_matches = array();
$file_header_match = preg_match(ROUTER_FILE_HEADER_PATTERN, $file_header, $file_header_matches);
if ($file_header_match === 1) {
// yes
// first, if there's no extension, be on the lookout for a redirect indicating this is actually a directory
if ($pathname_no_extension === true && $file_status_code >= 300 && $file_status_code < 400 && strtolower($file_header_matches[1]) === 'location') {
// location header - we're redirecting elsewhere
// but where, relative to our current path?
$file_header_pathname_index_pos = strrpos($file_header_matches[2], strval($pathname_index));
if ($file_header_pathname_index_pos !== false) {
// $file_location will contain the redirect to forward
$file_location = substr($file_header_matches[2], $file_header_pathname_index_pos + strlen($pathname_index));
if ($file_location !== '') {
if ($index_extension !== -1) {
// factor in index
$file_location = $pathname_trailing_slash . 'index.' . $router_index_extensions[$index_extension] . $file_location;
}
$file_location = $_SERVER['SCRIPT_NAME'] . $file_location;
}
}
}
if ($file_status_code === 200) {
// only in the scenario where the status is already OK, check for the length of the response and last modified date
switch (strtolower($file_header_matches[1])) {
case 'content-length':
$file_content_length = intval($file_header_matches[2]);
break;
case 'last-modified':
$file_last_modified = @strtotime($file_header_matches[2]);
if (version_compare(PHP_VERSION, '5.1.0', '<') === true) {