xref: /haiku/src/apps/haikudepot/tar/TarArchiveService.cpp (revision 21258e2674226d6aa732321b6f8494841895af5f)
1 /*
2  * Copyright 2017-2020, Andrew Lindesay <apl@lindesay.co.nz>.
3  * All rights reserved. Distributed under the terms of the MIT License.
4  */
5 
6 
7 #include "TarArchiveService.h"
8 
9 #include <Directory.h>
10 #include <File.h>
11 #include <StringList.h>
12 
13 #include "Logger.h"
14 #include "StorageUtils.h"
15 
16 
17 #define LENGTH_BLOCK 512
18 
19 
20 status_t
21 TarArchiveService::Unpack(BDataIO& tarDataIo, BPath& targetDirectory,
22 	Stoppable* stoppable)
23 {
24 	uint8 buffer[LENGTH_BLOCK];
25 	uint8 zero_buffer[LENGTH_BLOCK];
26 	status_t result = B_OK;
27 	uint32_t count_items_read = 0;
28 
29 	HDINFO("will unpack to [%s]", targetDirectory.Path());
30 
31 	memset(zero_buffer, 0, sizeof zero_buffer);
32 
33 	while (B_OK == result
34 		&& (NULL == stoppable || !stoppable->WasStopped())
35 		&& B_OK == (result = tarDataIo.ReadExactly(buffer, LENGTH_BLOCK))) {
36 
37 		count_items_read++;
38 
39 		if (0 == memcmp(zero_buffer, buffer, sizeof zero_buffer)) {
40 			HDDEBUG("detected end of tar-ball");
41 			return B_OK; // end of tar-ball.
42 		} else {
43 			TarArchiveHeader* header = TarArchiveHeader::CreateFromBlock(
44 				buffer);
45 
46 			if (NULL == header) {
47 				HDERROR("unable to parse a tar header");
48 				result = B_ERROR;
49 			}
50 
51 			if (B_OK == result)
52 				result = _UnpackItem(tarDataIo, targetDirectory, *header);
53 
54 			delete header;
55 		}
56 	}
57 
58 	HDERROR("did unpack %d tar items", count_items_read);
59 
60 	if (B_OK != result) {
61 		HDERROR("error occurred unpacking tar items; %s", strerror(result));
62 	}
63 
64 	return result;
65 }
66 
67 
68 status_t
69 TarArchiveService::_EnsurePathToTarItemFile(
70 	BPath& targetDirectoryPath, BString &tarItemPath)
71 {
72 	if (tarItemPath.Length() == 0)
73 		return B_ERROR;
74 
75 	BStringList components;
76 	tarItemPath.Split("/", false, components);
77 
78 	for (int32 i = 0; i < components.CountStrings(); i++) {
79 		BString component = components.StringAt(i);
80 
81 		if (_ValidatePathComponent(component) != B_OK) {
82 			HDERROR("malformed component; [%s]", component.String());
83 			return B_ERROR;
84 		}
85 	}
86 
87 	BPath parentPath;
88 	BPath assembledPath(targetDirectoryPath);
89 
90 	status_t result = assembledPath.Append(tarItemPath);
91 
92 	if (result == B_OK)
93 		result = assembledPath.GetParent(&parentPath);
94 
95 	if (result == B_OK)
96 		result = create_directory(parentPath.Path(), 0777);
97 
98 	return result;
99 }
100 
101 
102 status_t
103 TarArchiveService::_UnpackItem(BDataIO& tarDataIo,
104 		BPath& targetDirectoryPath,
105 		TarArchiveHeader& header)
106 {
107 	status_t result = B_OK;
108 	BString entryFileName = header.GetFileName();
109 	uint32 entryLength = header.GetLength();
110 
111 	HDDEBUG("will unpack item [%s] length [%" B_PRIu32 "]b",
112 		entryFileName.String(), entryLength);
113 
114 	// if the path ends in "/" then it is a directory and there's no need to
115 	// unpack it although if there is a length, it will need to be skipped.
116 
117 	if (!entryFileName.EndsWith("/") ||
118 			header.GetFileType() != TAR_FILE_TYPE_NORMAL) {
119 
120 		result = _EnsurePathToTarItemFile(targetDirectoryPath,
121 			entryFileName);
122 
123 		if (result == B_OK) {
124 			BPath targetFilePath(targetDirectoryPath);
125 			targetFilePath.Append(entryFileName, false);
126 			result = _UnpackItemData(tarDataIo, targetFilePath, entryLength);
127 		}
128 	} else {
129 		off_t blocksToSkip = (entryLength / LENGTH_BLOCK);
130 
131 		if (entryLength % LENGTH_BLOCK > 0)
132 			blocksToSkip += 1;
133 
134 		if (0 != blocksToSkip) {
135 			uint8 buffer[LENGTH_BLOCK];
136 
137 			for (uint32 i = 0; B_OK == result && i < blocksToSkip; i++)
138 				tarDataIo.ReadExactly(buffer, LENGTH_BLOCK);
139 		}
140 	}
141 
142 	return result;
143 }
144 
145 
146 status_t
147 TarArchiveService::_UnpackItemData(BDataIO& tarDataIo,
148 	BPath& targetFilePath, uint32 length)
149 {
150 	uint8 buffer[LENGTH_BLOCK];
151 	size_t remainingInItem = length;
152 	status_t result = B_OK;
153 	BFile targetFile(targetFilePath.Path(), O_WRONLY | O_CREAT);
154 
155 	while (remainingInItem > 0 &&
156 		B_OK == result &&
157 		B_OK == (result = tarDataIo.ReadExactly(buffer, LENGTH_BLOCK))) {
158 
159 		size_t writeFromBuffer = LENGTH_BLOCK;
160 
161 		if (remainingInItem < LENGTH_BLOCK)
162 			writeFromBuffer = remainingInItem;
163 
164 		result = targetFile.WriteExactly(buffer, writeFromBuffer);
165 		remainingInItem -= writeFromBuffer;
166 	}
167 
168 	if (result != B_OK) {
169 		HDERROR("unable to unpack item data to; [%s]", targetFilePath.Path());
170 	}
171 
172 	return result;
173 }
174 
175 
176 status_t
177 TarArchiveService::_ValidatePathComponent(const BString& component)
178 {
179 	if (component.Length() == 0)
180 		return	B_ERROR;
181 
182 	if (component == ".." || component == "." || component == "~")
183 		return B_ERROR;
184 
185 	return B_OK;
186 }
187 
188