xref: /haiku/src/apps/haikudepot/tar/TarArchiveService.cpp (revision be622abddb00c474c53631429ad1102d78688d4f)
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 
8 #include <stdio.h>
9 
10 #include <Directory.h>
11 #include <File.h>
12 #include <StringList.h>
13 
14 #include "Logger.h"
15 #include "StorageUtils.h"
16 
17 
18 #define LENGTH_BLOCK 512
19 
20 
21 status_t
22 TarArchiveService::Unpack(BDataIO& tarDataIo, BPath& targetDirectory)
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 	fprintf(stdout, "will unpack to [%s]\n", targetDirectory.Path());
30 
31 	memset(zero_buffer, 0, sizeof zero_buffer);
32 
33 	while (B_OK == result && B_OK == (result = tarDataIo.ReadExactly(buffer,
34 		LENGTH_BLOCK))) {
35 
36 		count_items_read++;
37 
38 		if (0 == memcmp(zero_buffer, buffer, sizeof zero_buffer)) {
39 			fprintf(stdout, "detected end of tar-ball\n");
40 			return B_OK; // end of tar-ball.
41 		} else {
42 			TarArchiveHeader* header = TarArchiveHeader::CreateFromBlock(
43 				buffer);
44 
45 			if (NULL == header) {
46 				fprintf(stderr, "unable to parse a tar header\n");
47 				result = B_ERROR;
48 			}
49 
50 			if (B_OK == result)
51 				result = _UnpackItem(tarDataIo, targetDirectory, *header);
52 
53 			delete header;
54 		}
55 	}
56 
57 	fprintf(stdout, "did unpack %d tar items\n", count_items_read);
58 
59 	if (B_OK != result) {
60 		fprintf(stdout, "error occurred unpacking tar items; %s\n",
61 			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 			fprintf(stdout, "malformed component; [%s]\n", 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 	if (Logger::IsDebugEnabled()) {
112 		fprintf(stdout, "will unpack item [%s] length [%" B_PRIu32 "]b\n",
113 			entryFileName.String(), entryLength);
114 	}
115 
116 	// if the path ends in "/" then it is a directory and there's no need to
117 	// unpack it although if there is a length, it will need to be skipped.
118 
119 	if (!entryFileName.EndsWith("/") ||
120 			header.GetFileType() != TAR_FILE_TYPE_NORMAL) {
121 
122 		result = _EnsurePathToTarItemFile(targetDirectoryPath,
123 			entryFileName);
124 
125 		if (result == B_OK) {
126 			BPath targetFilePath(targetDirectoryPath);
127 			targetFilePath.Append(entryFileName, false);
128 			result = _UnpackItemData(tarDataIo, targetFilePath, entryLength);
129 		}
130 	} else {
131 		off_t blocksToSkip = (entryLength / LENGTH_BLOCK);
132 
133 		if (entryLength % LENGTH_BLOCK > 0)
134 			blocksToSkip += 1;
135 
136 		if (0 != blocksToSkip) {
137 			uint8 buffer[LENGTH_BLOCK];
138 
139 			for (uint32 i = 0; B_OK == result && i < blocksToSkip; i++)
140 				tarDataIo.ReadExactly(buffer, LENGTH_BLOCK);
141 		}
142 	}
143 
144 	return result;
145 }
146 
147 
148 status_t
149 TarArchiveService::_UnpackItemData(BDataIO& tarDataIo,
150 	BPath& targetFilePath, uint32 length)
151 {
152 	uint8 buffer[LENGTH_BLOCK];
153 	size_t remainingInItem = length;
154 	status_t result = B_OK;
155 	BFile targetFile(targetFilePath.Path(), O_WRONLY | O_CREAT);
156 
157 	while (remainingInItem > 0 &&
158 		B_OK == result &&
159 		B_OK == (result = tarDataIo.ReadExactly(buffer, LENGTH_BLOCK))) {
160 
161 		size_t writeFromBuffer = LENGTH_BLOCK;
162 
163 		if (remainingInItem < LENGTH_BLOCK)
164 			writeFromBuffer = remainingInItem;
165 
166 		result = targetFile.WriteExactly(buffer, writeFromBuffer);
167 		remainingInItem -= writeFromBuffer;
168 	}
169 
170 	if (result != B_OK)
171 		fprintf(stdout, "unable to unpack item data to; [%s]\n",
172 			targetFilePath.Path());
173 
174 	return result;
175 }
176 
177 
178 status_t
179 TarArchiveService::_ValidatePathComponent(const BString& component)
180 {
181 	if (component.Length() == 0)
182 		return	B_ERROR;
183 
184 	if (component == ".." || component == "." || component == "~")
185 		return B_ERROR;
186 
187 	return B_OK;
188 }
189 
190