summaryrefslogtreecommitdiffstats
path: root/third_party/libjingle/files/talk/base/tarstream.cc
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/libjingle/files/talk/base/tarstream.cc')
-rw-r--r--third_party/libjingle/files/talk/base/tarstream.cc601
1 files changed, 601 insertions, 0 deletions
diff --git a/third_party/libjingle/files/talk/base/tarstream.cc b/third_party/libjingle/files/talk/base/tarstream.cc
new file mode 100644
index 0000000..e1f17b1
--- /dev/null
+++ b/third_party/libjingle/files/talk/base/tarstream.cc
@@ -0,0 +1,601 @@
+#include "talk/base/basicdefs.h"
+#include "talk/base/basictypes.h"
+#include "talk/base/tarstream.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/common.h"
+
+using namespace talk_base;
+
+///////////////////////////////////////////////////////////////////////////////
+// TarStream
+///////////////////////////////////////////////////////////////////////////////
+
+TarStream::TarStream() : mode_(M_NONE), next_block_(NB_NONE), block_pos_(0),
+ current_(NULL), current_bytes_(0) {
+}
+
+TarStream::~TarStream() {
+ Close();
+}
+
+bool TarStream::AddFilter(const std::string& pathname) {
+ if (pathname.empty())
+ return false;
+ Pathname archive_path(pathname);
+ archive_path.SetFolderDelimiter('/');
+ archive_path.Normalize();
+ filters_.push_back(archive_path.pathname());
+ return true;
+}
+
+bool TarStream::Open(const std::string& folder, bool read) {
+ Close();
+
+ Pathname root_folder;
+ root_folder.SetFolder(folder);
+ root_folder.Normalize();
+ root_folder_.assign(root_folder.folder());
+
+ if (read) {
+ std::string pattern(root_folder_);
+ DirectoryIterator *iter = new DirectoryIterator();
+
+ if (iter->Iterate(pattern) == false) {
+ delete iter;
+ return false;
+ }
+ mode_ = M_READ;
+ find_.push_front(iter);
+ next_block_ = NB_FILE_HEADER;
+ block_pos_ = BLOCK_SIZE;
+ int error;
+ if (SR_SUCCESS != ProcessNextEntry(find_.front(), &error)) {
+ return false;
+ }
+ } else {
+ if (!Filesystem::CreateFolder(root_folder_)) {
+ return false;
+ }
+
+ mode_ = M_WRITE;
+ next_block_ = NB_FILE_HEADER;
+ block_pos_ = 0;
+ }
+ return true;
+}
+
+StreamState TarStream::GetState() const {
+ return (M_NONE == mode_) ? SS_CLOSED : SS_OPEN;
+}
+
+StreamResult TarStream::Read(void* buffer, size_t buffer_len,
+ size_t* read, int* error) {
+ if (M_READ != mode_) {
+ return SR_EOS;
+ }
+ return ProcessBuffer(buffer, buffer_len, read, error);
+}
+
+StreamResult TarStream::Write(const void* data, size_t data_len,
+ size_t* written, int* error) {
+ if (M_WRITE != mode_) {
+ return SR_EOS;
+ }
+ // Note: data is not modified unless M_READ == mode_
+ return ProcessBuffer(const_cast<void*>(data), data_len, written, error);
+}
+
+void TarStream::Close() {
+ root_folder_.clear();
+ next_block_ = NB_NONE;
+ block_pos_ = 0;
+ delete current_;
+ current_ = NULL;
+ current_bytes_ = 0;
+ for (DirectoryList::iterator it = find_.begin(); it != find_.end(); ++it) {
+ delete(*it);
+ }
+ find_.clear();
+ subfolder_.clear();
+}
+
+StreamResult TarStream::ProcessBuffer(void* buffer, size_t buffer_len,
+ size_t* consumed, int* error) {
+ size_t local_consumed;
+ if (!consumed) consumed = &local_consumed;
+ int local_error;
+ if (!error) error = &local_error;
+
+ StreamResult result = SR_SUCCESS;
+ *consumed = 0;
+
+ while (*consumed < buffer_len) {
+ size_t available = BLOCK_SIZE - block_pos_;
+ if (available == 0) {
+ result = ProcessNextBlock(error);
+ if (SR_SUCCESS != result) {
+ break;
+ }
+ } else {
+ size_t bytes_to_copy = talk_base::_min(available, buffer_len - *consumed);
+ char* buffer_ptr = static_cast<char*>(buffer) + *consumed;
+ char* block_ptr = block_ + block_pos_;
+ if (M_READ == mode_) {
+ memcpy(buffer_ptr, block_ptr, bytes_to_copy);
+ } else {
+ memcpy(block_ptr, buffer_ptr, bytes_to_copy);
+ }
+ *consumed += bytes_to_copy;
+ block_pos_ += bytes_to_copy;
+ }
+ }
+
+ // SR_EOS means no data was consumed on this operation. So we may need to
+ // return SR_SUCCESS instead, and then we will return SR_EOS next time.
+ if ((SR_EOS == result) && (*consumed > 0)) {
+ result = SR_SUCCESS;
+ }
+
+ return result;
+}
+
+StreamResult TarStream::ProcessNextBlock(int* error) {
+ ASSERT(NULL != error);
+ ASSERT(M_NONE != mode_);
+ ASSERT(BLOCK_SIZE == block_pos_);
+
+ StreamResult result;
+ if (NB_NONE == next_block_) {
+
+ return SR_EOS;
+
+ } else if (NB_TRAILER == next_block_) {
+
+ // Trailer block is zeroed
+ result = ProcessEmptyBlock(0, error);
+ if (SR_SUCCESS != result)
+ return result;
+ next_block_ = NB_NONE;
+
+ } else if (NB_FILE_HEADER == next_block_) {
+
+ if (M_READ == mode_) {
+ result = ReadNextFile(error);
+ } else {
+ result = WriteNextFile(error);
+ }
+
+ // If there are no more files, we are at the first trailer block
+ if (SR_EOS == result) {
+ block_pos_ = 0;
+ next_block_ = NB_TRAILER;
+ result = ProcessEmptyBlock(0, error);
+ }
+ if (SR_SUCCESS != result)
+ return result;
+
+ } else if (NB_DATA == next_block_) {
+
+ size_t block_consumed = 0;
+ size_t block_available = talk_base::_min<size_t>(BLOCK_SIZE, current_bytes_);
+ while (block_consumed < block_available) {
+ void* block_ptr = static_cast<char*>(block_) + block_consumed;
+ size_t available = block_available - block_consumed, consumed;
+ if (M_READ == mode_) {
+ ASSERT(NULL != current_);
+ result = current_->Read(block_ptr, available, &consumed, error);
+ } else if (current_) {
+ result = current_->Write(block_ptr, available, &consumed, error);
+ } else {
+ consumed = available;
+ result = SR_SUCCESS;
+ }
+ switch (result) {
+ case SR_ERROR:
+ return result;
+ case SR_BLOCK:
+ case SR_EOS:
+ ASSERT(false);
+ *error = 0; // TODO: make errors
+ return SR_ERROR;
+ case SR_SUCCESS:
+ block_consumed += consumed;
+ break;
+ }
+ }
+
+ current_bytes_ -= block_consumed;
+ if (current_bytes_ == 0) {
+ // The remainder of the block is zeroed
+ result = ProcessEmptyBlock(block_consumed, error);
+ if (SR_SUCCESS != result)
+ return result;
+ delete current_;
+ current_ = NULL;
+ next_block_ = NB_FILE_HEADER;
+ }
+
+ } else {
+ ASSERT(false);
+ }
+
+ block_pos_ = 0;
+ return SR_SUCCESS;
+}
+
+StreamResult TarStream::ProcessEmptyBlock(size_t start, int* error) {
+ ASSERT(NULL != error);
+ ASSERT(M_NONE != mode_);
+ if (M_READ == mode_) {
+ memset(block_ + start, 0, BLOCK_SIZE - start);
+ } else {
+ if (!talk_base::memory_check(block_ + start, 0, BLOCK_SIZE - start)) {
+ *error = 0; // TODO: make errors
+ return SR_ERROR;
+ }
+ }
+ return SR_SUCCESS;
+}
+
+StreamResult TarStream::ReadNextFile(int* error) {
+ ASSERT(NULL != error);
+ ASSERT(M_READ == mode_);
+ ASSERT(NB_FILE_HEADER == next_block_);
+ ASSERT(BLOCK_SIZE == block_pos_);
+ ASSERT(NULL == current_);
+
+ // ReadNextFile conducts a depth-first recursive search through the directory
+ // tree. find_ maintains a stack of open directory handles, which
+ // corresponds to our current position in the tree. At any point, the
+ // directory at the top (front) of the stack is being enumerated. If a
+ // directory is found, it is opened and pushed onto the top of the stack.
+ // When a directory enumeration completes, that directory is popped off the
+ // top of the stack.
+
+ // Note: Since ReadNextFile can only return one block of data at a time, we
+ // cannot simultaneously return the entry for a directory, and the entry for
+ // the first element in that directory at the same time. In this case, we
+ // push a NULL entry onto the find_ stack, which indicates that the next
+ // iteration should begin enumeration of the "new" directory.
+ StreamResult result = SR_SUCCESS;
+ while (BLOCK_SIZE == block_pos_) {
+ ASSERT(!find_.empty());
+
+ if (NULL != find_.front()) {
+ if (find_.front()->Next()) {
+ result = ProcessNextEntry(find_.front(), error);
+ if (SR_SUCCESS != result) {
+ return result;
+ }
+ continue;
+ }
+ delete(find_.front());
+ } else {
+ Pathname pattern(root_folder_);
+ pattern.AppendFolder(subfolder_);
+ find_.front() = new DirectoryIterator();
+ if (find_.front()->Iterate(pattern.pathname())) {
+ result = ProcessNextEntry(find_.front(), error);
+ if (SR_SUCCESS != result) {
+ return result;
+ }
+ continue;
+ }
+ // TODO: should this be an error?
+ LOG_F(LS_WARNING) << "Couldn't open folder: " << pattern.pathname();
+ }
+
+ find_.pop_front();
+ subfolder_ = Pathname(subfolder_).parent_folder();
+
+ if (find_.empty()) {
+ return SR_EOS;
+ }
+ }
+
+ ASSERT(0 == block_pos_);
+ return SR_SUCCESS;
+}
+
+StreamResult TarStream::WriteNextFile(int* error) {
+ ASSERT(NULL != error);
+ ASSERT(M_WRITE == mode_);
+ ASSERT(NB_FILE_HEADER == next_block_);
+ ASSERT(BLOCK_SIZE == block_pos_);
+ ASSERT(NULL == current_);
+ ASSERT(0 == current_bytes_);
+
+ std::string pathname, link, linked_name, magic, mversion;
+ size_t file_size, modify_time, unused, checksum;
+
+ size_t block_data = 0;
+ ReadFieldS(block_data, 100, &pathname);
+ ReadFieldN(block_data, 8, &unused); // mode
+ ReadFieldN(block_data, 8, &unused); // owner uid
+ ReadFieldN(block_data, 8, &unused); // owner gid
+ ReadFieldN(block_data, 12, &file_size);
+ ReadFieldN(block_data, 12, &modify_time);
+ ReadFieldN(block_data, 8, &checksum);
+ if (checksum == 0)
+ block_data -= 8; // back-compatiblity
+ ReadFieldS(block_data, 1, &link);
+ ReadFieldS(block_data, 100, &linked_name); // name of linked file
+ ReadFieldS(block_data, 6, &magic);
+ ReadFieldS(block_data, 2, &mversion);
+
+ if (pathname.empty())
+ return SR_EOS;
+
+ std::string user, group, dev_major, dev_minor, prefix;
+ if (magic == "ustar" || magic == "ustar ") {
+ ReadFieldS(block_data, 32, &user);
+ ReadFieldS(block_data, 32, &group);
+ ReadFieldS(block_data, 8, &dev_major);
+ ReadFieldS(block_data, 8, &dev_minor);
+ ReadFieldS(block_data, 155, &prefix);
+
+ pathname = prefix + pathname;
+ }
+
+ // Rest of the block must be empty
+ StreamResult result = ProcessEmptyBlock(block_data, error);
+ if (SR_SUCCESS != result) {
+ return result;
+ }
+
+ Pathname archive_path(pathname);
+ archive_path.SetFolderDelimiter('/');
+ archive_path.Normalize();
+
+ bool is_folder = archive_path.filename().empty();
+ if (is_folder) {
+ ASSERT(NB_FILE_HEADER == next_block_);
+ ASSERT(0 == file_size);
+ } else if (file_size > 0) {
+ // We assign current_bytes_ because we must skip over the upcoming data
+ // segments, regardless of whether we want to write them.
+ next_block_ = NB_DATA;
+ current_bytes_ = file_size;
+ }
+
+ if (!CheckFilter(archive_path.pathname())) {
+ // If it's a directory, we will ignore it and all children by nature of
+ // filter prefix matching. If it is a file, we will ignore it because
+ // current_ is NULL.
+ return SR_SUCCESS;
+ }
+
+ // Sanity checks:
+ // 1) No .. path segments
+ if (archive_path.pathname().find("../") != std::string::npos) {
+ LOG_F(LS_WARNING) << "Skipping path with .. entry: "
+ << archive_path.pathname();
+ return SR_SUCCESS;
+ }
+ // 2) No drive letters
+ if (archive_path.pathname().find(':') != std::string::npos) {
+ LOG_F(LS_WARNING) << "Skipping path with drive letter: "
+ << archive_path.pathname();
+ return SR_SUCCESS;
+ }
+ // 3) No absolute paths
+ if (archive_path.pathname().find("//") != std::string::npos) {
+ LOG_F(LS_WARNING) << "Skipping absolute path: "
+ << archive_path.pathname();
+ return SR_SUCCESS;
+ }
+
+ Pathname local_path(root_folder_);
+ local_path.AppendPathname(archive_path.pathname());
+ local_path.Normalize();
+
+ if (is_folder) {
+ if (!Filesystem::CreateFolder(local_path)) {
+ LOG_F(LS_WARNING) << "Couldn't create folder: " << local_path.pathname();
+ *error = 0; // TODO
+ return SR_ERROR;
+ }
+ } else {
+ FileStream* stream = new FileStream;
+
+ if (!stream->Open(local_path.pathname().c_str(), "wb")) {
+ LOG_F(LS_WARNING) << "Couldn't create file: " << local_path.pathname();
+ *error = 0; // TODO
+ delete stream;
+ return SR_ERROR;
+ }
+ if (file_size > 0) {
+ current_ = stream;
+ } else {
+ stream->Close();
+ delete stream;
+ }
+ }
+
+ SignalNextEntry(archive_path.filename(), current_bytes_);
+
+
+ return SR_SUCCESS;
+}
+
+StreamResult TarStream::ProcessNextEntry(const DirectoryIterator *data, int *error) {
+ ASSERT(M_READ == mode_);
+ ASSERT(NB_FILE_HEADER == next_block_);
+ ASSERT(BLOCK_SIZE == block_pos_);
+ ASSERT(NULL == current_);
+ ASSERT(0 == current_bytes_);
+
+ if (data->IsDirectory() &&
+ (data->Name() == "." || data->Name() == ".."))
+ return SR_SUCCESS;
+
+ Pathname archive_path;
+ archive_path.SetFolder(subfolder_);
+ if (data->IsDirectory()) {
+ archive_path.AppendFolder(data->Name());
+ } else {
+ archive_path.SetFilename(data->Name());
+ }
+ archive_path.SetFolderDelimiter('/');
+ archive_path.Normalize();
+
+ if (!CheckFilter(archive_path.pathname()))
+ return SR_SUCCESS;
+
+ if (archive_path.pathname().length() > 255) {
+ // Cannot send a file name longer than 255 (yet)
+ return SR_ERROR;
+ }
+
+ Pathname local_path(root_folder_);
+ local_path.AppendPathname(archive_path.pathname());
+ local_path.Normalize();
+
+ if (data->IsDirectory()) {
+ // Note: the NULL handle indicates that we need to open the folder next
+ // time.
+ find_.push_front(NULL);
+ subfolder_ = archive_path.pathname();
+ } else {
+ FileStream* stream = new FileStream;
+ if (!stream->Open(local_path.pathname().c_str(), "rb")) {
+ // TODO: Should this be an error?
+ LOG_F(LS_WARNING) << "Couldn't open file: " << local_path.pathname();
+ delete stream;
+ return SR_SUCCESS;
+ }
+ current_ = stream;
+ current_bytes_ = data->FileSize();
+ }
+
+ time_t modify_time = data->FileModifyTime();
+
+ std::string pathname = archive_path.pathname();
+ std::string magic, user, group, dev_major, dev_minor, prefix;
+ std::string name = pathname;
+ bool ustar = false;
+ if (name.length() > 100) {
+ ustar = true;
+ // Put last 100 characters into the name, and rest in prefix
+ size_t path_length = pathname.length();
+ prefix = pathname.substr(0, path_length - 100);
+ name = pathname.substr(path_length - 100);
+ }
+
+ size_t block_data = 0;
+ memset(block_, 0, BLOCK_SIZE);
+ WriteFieldS(block_data, 100, name.c_str());
+ WriteFieldS(block_data, 8, data->IsDirectory() ? "777" : "666"); // mode
+ WriteFieldS(block_data, 8, "5"); // owner uid
+ WriteFieldS(block_data, 8, "5"); // owner gid
+ WriteFieldN(block_data, 12, current_bytes_);
+ WriteFieldN(block_data, 12, modify_time);
+ WriteFieldS(block_data, 8, " "); // Checksum. To be filled in later.
+ WriteFieldS(block_data, 1, data->IsDirectory() ? "5" : "0"); // link indicator (0 == normal file, 5 == directory)
+ WriteFieldS(block_data, 100, ""); // name of linked file
+
+ if (ustar) {
+ WriteFieldS(block_data, 6, "ustar");
+ WriteFieldS(block_data, 2, "");
+ WriteFieldS(block_data, 32, user.c_str());
+ WriteFieldS(block_data, 32, group.c_str());
+ WriteFieldS(block_data, 8, dev_major.c_str());
+ WriteFieldS(block_data, 8, dev_minor.c_str());
+ WriteFieldS(block_data, 155, prefix.c_str());
+ }
+
+ // Rest of the block must be empty
+ StreamResult result = ProcessEmptyBlock(block_data, error);
+ WriteChecksum();
+
+ block_pos_ = 0;
+ if (current_bytes_ > 0) {
+ next_block_ = data->IsDirectory() ? NB_FILE_HEADER : NB_DATA;
+ }
+
+ SignalNextEntry(archive_path.filename(), current_bytes_);
+
+ return result;
+}
+
+void TarStream::WriteChecksum() {
+ unsigned int sum = 0;
+
+ for (int i = 0; i < BLOCK_SIZE; i++)
+ sum += static_cast<unsigned char>(block_[i]);
+
+ sprintf(block_ + 148, "%06o", sum);
+}
+
+bool TarStream::CheckFilter(const std::string& pathname) {
+ if (filters_.empty())
+ return true;
+
+ // pathname is allowed when there is a filter which:
+ // A) Equals name
+ // B) Matches a folder prefix of name
+ for (size_t i=0; i<filters_.size(); ++i) {
+ const std::string& filter = filters_[i];
+ // Make sure the filter is a prefix of name
+ if (_strnicmp(pathname.c_str(), filter.data(), filter.length()) != 0)
+ continue;
+
+ // If the filter is not a directory, must match exactly
+ if (!Pathname::IsFolderDelimiter(filter[filter.length()-1])
+ && (filter.length() != pathname.length()))
+ continue;
+
+ return true;
+ }
+
+ return false;
+}
+
+void TarStream::WriteFieldN(size_t& pos, size_t max_len, size_t numeric_field) {
+ WriteFieldF(pos, max_len, "%.*o", max_len - 1, numeric_field);
+}
+
+void TarStream::WriteFieldS(size_t& pos, size_t max_len,
+ const char* string_field) {
+ ASSERT(pos + max_len <= BLOCK_SIZE);
+ size_t len = strlen(string_field);
+ size_t use_len = _min(len, max_len);
+ memcpy(block_ + pos, string_field, use_len);
+ pos += max_len;
+}
+
+void TarStream::WriteFieldF(size_t& pos, size_t max_len,
+ const char* format, ...) {
+ va_list args;
+ va_start(args, format);
+ char buffer[BLOCK_SIZE];
+ vsprintfn(buffer, ARRAY_SIZE(buffer), format, args);
+ WriteFieldS(pos, max_len, buffer);
+ va_end(args);
+}
+
+void TarStream::ReadFieldN(size_t& pos, size_t max_len, size_t* numeric_field) {
+ ASSERT(NULL != numeric_field);
+ std::string buffer;
+ ReadFieldS(pos, max_len, &buffer);
+
+ int value;
+ if (!buffer.empty() && (1 == sscanf(buffer.c_str(), "%o", &value))) {
+ *numeric_field = value;
+ } else {
+ *numeric_field = 0;
+ }
+}
+
+void TarStream::ReadFieldS(size_t& pos, size_t max_len,
+ std::string* string_field) {
+ ASSERT(NULL != string_field);
+ ASSERT(pos + max_len <= BLOCK_SIZE);
+ size_t value_len = talk_base::strlenn(block_ + pos, max_len);
+ string_field->assign(block_ + pos, value_len);
+ ASSERT(talk_base::memory_check(block_ + pos + value_len,
+ 0,
+ max_len - value_len));
+ pos += max_len;
+}