forked from rembish/TextAtAnyCost
-
Notifications
You must be signed in to change notification settings - Fork 0
/
stored-rar.php
194 lines (164 loc) · 7.34 KB
/
stored-rar.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
<?php
// Создание stored-RAR архивов
// Версия 0.3
// Автор: Алексей Рембиш a.k.a Ramon
// E-mail: [email protected]
// Copyright 2009
// Класс создания RAR-архивов со stored-сжатием
// Пример работы:
// $rar = new store_rar;
// $rar->create("archive.rar"); # создаём архив
// $rar->addFile("a.txt"); # пишем в него файл a.txt
// $rar->addDirectory("b/c"); # создаём в архиве директорию "b" с поддиректорией "c"
// $rar->addFile("d/e.txt"); # создаём директорию "d" и пишем в неё e.txt
// $rar->close(); # закрываем архив
class store_rar {
// Указатель на архив
private $id = null;
// Внутренняя структура каталогов, чтобы не создавать лишние.
private $tree = array();
// Функция, создающая новый архив, после чего записывающая в него обязательные заголовки.
public function create($filename) {
$this->id = fopen($filename, "wb");
if (!$this->id)
return false;
$this->tree = array();
$this->writeHeader(0x72, 0x1a21);
$this->writeHeader(0x73, 0x0000, array(array(0, 2), array(0, 4)));
return true;
}
// Функция, закрывающая записанный архив
public function close() {
fclose($this->id);
}
// Функция, что добавляет в архив директорию. Нормально справляется с рекурсивными
// директориями. Например, для $name = "a/b" создаст директорию "a", а в ней поддиректорию
// "b". Не будет создавать дубликаты директорий.
public function addDirectory($name) {
$name = str_replace("/", "\\", $name);
if ($name[0] == "\\") $name = substr($name, 1);
if ($name[strlen($name) - 1] == "\\") $name = substr($name, 0, -1);
$parts = explode("\\", $name);
$c = &$this->tree;
$cname = ""; $delim = "";
for ($i = 0; $i < count($parts); $i++) {
$cname .= $delim.$parts[$i];
if (!isset($c[$parts[$i]])) {
$c[$parts[$i]] = array();
$this->writeHeader(0x74, $this->setBits(array(5, 6, 7, 15)), array(
array(0, 4), // Packed size = 0 for directories
array(0, 4), // Unpacked size = 0 for directories
array(0, 1), // Host OS = "MS DOS"
array(0, 4), // File CRC = 0 for directories
array($this->getDateTime(), 4), // File time = Current date and time in MS DOS format
array(20, 1), // RAR version = 2.0
array(0x30, 1), // Method = Store
array(strlen($cname), 2), // Name size
array(0x10, 4), // File attributes = Directory
$cname, // Filename = Directory name
));
}
$c = &$c[$parts[$i]];
$delim = "\\";
}
return $name;
}
// Функция записывающая файл $name в архив. Если задан параметр $dir, то файл будет записан
// в соответствующую директорию внутри архива (директория может не существовать до записи
// файла). $name - путь к файлу на сервере. Файл будет записан с basename($name).
public function addFile($name, $dir = null) {
if (!file_exists($name))
return false;
$c = &$this->tree;
if (!is_null($dir)) {
$dir = $this->addDirectory($dir);
$parts = explode("\\", $dir);
for($i = 0; $i < count($parts); $i++)
$c = &$c[$parts[$i]];
}
$fname = pathinfo($name, PATHINFO_BASENAME);
if (in_array($fname, $c))
return true;
$data = file_get_contents($name);
$size = strlen($data);
if (!is_null($dir))
$fname = $dir."\\".$name;
$this->writeHeader(0x74, $this->setBits(array(15)), array(
array($size, 4), // Packed size = File size
array($size, 4), // Unpacked size = File size
array(0, 1), // Host OS = "MS DOS"
array(crc32($data), 4), // File CRC
array($this->getDateTime(filemtime($name)), 4), // File time
array(20, 1), // RAR version = 2.0
array(0x30, 1), // Method = store
array(strlen($fname), 2), // Name size
array(0x20, 4), // File attributes = Archived
$fname, // Filename
));
fwrite($this->id, $data);
$c[] = $fname;
return true;
}
// Внутренняя функция, пишущая заголовок блока в соответствии с форматом RAR.
// Работает только с тремя типами заголовков: блок-маркер, заголовок архива и
// заголовок файла - $headType. $headFlags - флаги заголовка, $data - возможно
// пустой массив дополнительных параметров заголовка, что идут после первых
// обязательных 7 байт.
private function writeHeader($headType, $headFlags, $data = array()) {
if (!in_array($headType, array(0x72, 0x73, 0x74)))
return false;
$headSize = 2 + 1 + 2 + 2;
foreach ($data as $key => $value)
$headSize += is_array($value) ? $value[1] : strlen($value);
$header = $this->writeBytesToString(array_merge(array($headType, array($headFlags, 2), array($headSize, 2)), $data));
$header = ($headType == 0x72 ? "Ra" : $this->getCRC($header)).$header;
fwrite($this->id, $header);
}
// Расчёт CRC для заголовка блока. CRC урезается до 2 байт из 4х.
private function getCRC($string) {
$crc = crc32($string);
return chr($crc & 0xFF).chr(($crc >> 8) & 0xFF);
}
// Внутренняя функция записи данных в обратном порядке байтов.
private function getBytes($data, $bytes = 0) {
$output = "";
if (!$bytes)
$bytes = strlen($bytes);
if (is_int($data) || is_float($data)) {
$data = sprintf("%0".($bytes * 2)."x", $data);
for ($i = 0; $i < strlen($data); $i += 2)
$output = chr(hexdec(substr($data, $i, 2))).$output;
} else
$output = $data;
return $output;
}
// Запись массива данных в байт-строку с учётом размерности переданных данных.
private function writeBytesToString($data) {
$output = "";
for ($i = 0; $i < count($data); $i++) {
if (is_array($data[$i]))
$output .= $this->getBytes($data[$i][0], $data[$i][1]);
else
$output .= $this->getBytes($data[$i]);
}
return $output;
}
// Установка соответствующих битов в числе. $bits - массив номеров битов или отдельный номер бита.
private function setBits($bits) {
$out = 0;
if (is_int($bits))
$bits[] = $bits;
for ($i = 0; $i < count($bits); $i++)
$out |= 1 << $bits[$i];
return $out;
}
// Получение даты в 4хбитном формате MSDOS. $time - timestamp, от которого получить дату.
private function getDateTime($time = null) {
if (!is_null($time))
$time = time();
$dt = getdate();
$out = $dt["seconds"] | ($dt["minutes"] << 5) | ($dt["hours"] << 11) | ($dt["mday"] << 16) | ($dt["mon"] << 21) | (($dt["year"] - 1980) << 25);
return $out;
}
};
?>