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