/* * Copyright 2010-2013 Haiku Inc. All rights reserved. * Distributed under the terms of the MIT License. * * Authors: * Christophe Huriaux, c.huriaux@gmail.com */ #include #include #include #include #include #include #include #include static int32 kBoundaryRandomSize = 16; using namespace std; using namespace BPrivate::Network; // #pragma mark - BHttpFormData BHttpFormData::BHttpFormData() : fDataType(B_HTTPFORM_STRING), fCopiedBuffer(false), fFileMark(false), fBufferValue(NULL), fBufferSize(0) { } BHttpFormData::BHttpFormData(const BString& name, const BString& value) : fDataType(B_HTTPFORM_STRING), fCopiedBuffer(false), fFileMark(false), fName(name), fStringValue(value), fBufferValue(NULL), fBufferSize(0) { } BHttpFormData::BHttpFormData(const BString& name, const BPath& file) : fDataType(B_HTTPFORM_FILE), fCopiedBuffer(false), fFileMark(false), fName(name), fPathValue(file), fBufferValue(NULL), fBufferSize(0) { } BHttpFormData::BHttpFormData(const BString& name, const void* buffer, ssize_t size) : fDataType(B_HTTPFORM_BUFFER), fCopiedBuffer(false), fFileMark(false), fName(name), fBufferValue(buffer), fBufferSize(size) { } BHttpFormData::BHttpFormData(const BHttpFormData& other) : fCopiedBuffer(false), fFileMark(false), fBufferValue(NULL), fBufferSize(0) { *this = other; } BHttpFormData::~BHttpFormData() { if (fCopiedBuffer) delete[] reinterpret_cast(fBufferValue); } // #pragma mark - Retrieve data informations bool BHttpFormData::InitCheck() const { if (fDataType == B_HTTPFORM_BUFFER) return fBufferValue != NULL; return true; } const BString& BHttpFormData::Name() const { return fName; } const BString& BHttpFormData::String() const { return fStringValue; } const BPath& BHttpFormData::File() const { return fPathValue; } const void* BHttpFormData::Buffer() const { return fBufferValue; } ssize_t BHttpFormData::BufferSize() const { return fBufferSize; } bool BHttpFormData::IsFile() const { return fFileMark; } const BString& BHttpFormData::Filename() const { return fFilename; } const BString& BHttpFormData::MimeType() const { return fMimeType; } form_content_type BHttpFormData::Type() const { return fDataType; } // #pragma mark - Change behavior status_t BHttpFormData::MarkAsFile(const BString& filename, const BString& mimeType) { if (fDataType == B_HTTPFORM_UNKNOWN || fDataType == B_HTTPFORM_FILE) return B_ERROR; fFilename = filename; fMimeType = mimeType; fFileMark = true; return B_OK; } void BHttpFormData::UnmarkAsFile() { fFilename.Truncate(0, true); fMimeType.Truncate(0, true); fFileMark = false; } status_t BHttpFormData::CopyBuffer() { if (fDataType != B_HTTPFORM_BUFFER) return B_ERROR; char* copiedBuffer = new(std::nothrow) char[fBufferSize]; if (copiedBuffer == NULL) return B_NO_MEMORY; memcpy(copiedBuffer, fBufferValue, fBufferSize); fBufferValue = copiedBuffer; fCopiedBuffer = true; return B_OK; } BHttpFormData& BHttpFormData::operator=(const BHttpFormData& other) { fDataType = other.fDataType; fCopiedBuffer = false; fFileMark = other.fFileMark; fName = other.fName; fStringValue = other.fStringValue; fPathValue = other.fPathValue; fBufferValue = other.fBufferValue; fBufferSize = other.fBufferSize; fFilename = other.fFilename; fMimeType = other.fMimeType; if (other.fCopiedBuffer) CopyBuffer(); return *this; } // #pragma mark - BHttpForm BHttpForm::BHttpForm() : fType(B_HTTP_FORM_URL_ENCODED) { } BHttpForm::BHttpForm(const BHttpForm& other) : fFields(other.fFields), fType(other.fType), fMultipartBoundary(other.fMultipartBoundary) { } BHttpForm::BHttpForm(const BString& formString) : fType(B_HTTP_FORM_URL_ENCODED) { ParseString(formString); } BHttpForm::~BHttpForm() { Clear(); } // #pragma mark - Form string parsing void BHttpForm::ParseString(const BString& formString) { int32 index = 0; while (index < formString.Length()) _ExtractNameValuePair(formString, &index); } BString BHttpForm::RawData() const { BString result; if (fType == B_HTTP_FORM_URL_ENCODED) { for (FormStorage::const_iterator it = fFields.begin(); it != fFields.end(); it++) { const BHttpFormData* currentField = &it->second; switch (currentField->Type()) { case B_HTTPFORM_UNKNOWN: break; case B_HTTPFORM_STRING: result << '&' << BUrl::UrlEncode(currentField->Name()) << '=' << BUrl::UrlEncode(currentField->String()); break; case B_HTTPFORM_FILE: break; case B_HTTPFORM_BUFFER: // Send the buffer only if its not marked as a file if (!currentField->IsFile()) { result << '&' << BUrl::UrlEncode(currentField->Name()) << '='; result.Append( reinterpret_cast(currentField->Buffer()), currentField->BufferSize()); } break; } } result.Remove(0, 1); } else if (fType == B_HTTP_FORM_MULTIPART) { // Very slow and memory consuming method since we're caching the // file content, this should be preferably handled by the protocol for (FormStorage::const_iterator it = fFields.begin(); it != fFields.end(); it++) { const BHttpFormData* currentField = &it->second; result << _GetMultipartHeader(currentField); switch (currentField->Type()) { case B_HTTPFORM_UNKNOWN: break; case B_HTTPFORM_STRING: result << currentField->String(); break; case B_HTTPFORM_FILE: { BFile upFile(currentField->File().Path(), B_READ_ONLY); char readBuffer[1024]; ssize_t readSize; readSize = upFile.Read(readBuffer, 1024); while (readSize > 0) { result.Append(readBuffer, readSize); readSize = upFile.Read(readBuffer, 1024); } break; } case B_HTTPFORM_BUFFER: result.Append( reinterpret_cast(currentField->Buffer()), currentField->BufferSize()); break; } result << "\r\n"; } result << "--" << fMultipartBoundary << "--\r\n"; } return result; } // #pragma mark - Form add status_t BHttpForm::AddString(const BString& fieldName, const BString& value) { BHttpFormData formData(fieldName, value); if (!formData.InitCheck()) return B_ERROR; fFields.insert(pair(fieldName, formData)); return B_OK; } status_t BHttpForm::AddInt(const BString& fieldName, int32 value) { BString strValue; strValue << value; return AddString(fieldName, strValue); } status_t BHttpForm::AddFile(const BString& fieldName, const BPath& file) { BHttpFormData formData(fieldName, file); if (!formData.InitCheck()) return B_ERROR; fFields.insert(pair(fieldName, formData)); if (fType != B_HTTP_FORM_MULTIPART) SetFormType(B_HTTP_FORM_MULTIPART); return B_OK; } status_t BHttpForm::AddBuffer(const BString& fieldName, const void* buffer, ssize_t size) { BHttpFormData formData(fieldName, buffer, size); if (!formData.InitCheck()) return B_ERROR; fFields.insert(pair(fieldName, formData)); return B_OK; } status_t BHttpForm::AddBufferCopy(const BString& fieldName, const void* buffer, ssize_t size) { BHttpFormData formData(fieldName, buffer, size); if (!formData.InitCheck()) return B_ERROR; // Copy the buffer of the inserted form data copy to // avoid an unneeded copy of the buffer upon insertion pair insertResult = fFields.insert(pair(fieldName, formData)); return insertResult.first->second.CopyBuffer(); } // #pragma mark - Mark a field as a filename void BHttpForm::MarkAsFile(const BString& fieldName, const BString& filename, const BString& mimeType) { FormStorage::iterator it = fFields.find(fieldName); if (it == fFields.end()) return; it->second.MarkAsFile(filename, mimeType); if (fType != B_HTTP_FORM_MULTIPART) SetFormType(B_HTTP_FORM_MULTIPART); } void BHttpForm::MarkAsFile(const BString& fieldName, const BString& filename) { MarkAsFile(fieldName, filename, ""); } void BHttpForm::UnmarkAsFile(const BString& fieldName) { FormStorage::iterator it = fFields.find(fieldName); if (it == fFields.end()) return; it->second.UnmarkAsFile(); } // #pragma mark - Change form type void BHttpForm::SetFormType(form_type type) { fType = type; if (fType == B_HTTP_FORM_MULTIPART) _GenerateMultipartBoundary(); } // #pragma mark - Form test bool BHttpForm::HasField(const BString& name) const { return (fFields.find(name) != fFields.end()); } // #pragma mark - Form retrieve BString BHttpForm::GetMultipartHeader(const BString& fieldName) const { FormStorage::const_iterator it = fFields.find(fieldName); if (it == fFields.end()) return BString(""); return _GetMultipartHeader(&it->second); } form_type BHttpForm::GetFormType() const { return fType; } const BString& BHttpForm::GetMultipartBoundary() const { return fMultipartBoundary; } BString BHttpForm::GetMultipartFooter() const { BString result = "--"; result << fMultipartBoundary << "--\r\n"; return result; } ssize_t BHttpForm::ContentLength() const { if (fType == B_HTTP_FORM_URL_ENCODED) return RawData().Length(); ssize_t contentLength = 0; for (FormStorage::const_iterator it = fFields.begin(); it != fFields.end(); it++) { const BHttpFormData* c = &it->second; contentLength += _GetMultipartHeader(c).Length(); switch (c->Type()) { case B_HTTPFORM_UNKNOWN: break; case B_HTTPFORM_STRING: contentLength += c->String().Length(); break; case B_HTTPFORM_FILE: { BFile upFile(c->File().Path(), B_READ_ONLY); upFile.Seek(0, SEEK_END); contentLength += upFile.Position(); break; } case B_HTTPFORM_BUFFER: contentLength += c->BufferSize(); break; } contentLength += 2; } contentLength += fMultipartBoundary.Length() + 6; return contentLength; } // #pragma mark Form iterator BHttpForm::Iterator BHttpForm::GetIterator() { return BHttpForm::Iterator(this); } // #pragma mark - Form clear void BHttpForm::Clear() { fFields.clear(); } // #pragma mark - Overloaded operators BHttpFormData& BHttpForm::operator[](const BString& name) { if (!HasField(name)) AddString(name, ""); return fFields[name]; } void BHttpForm::_ExtractNameValuePair(const BString& formString, int32* index) { // Look for a name=value pair int16 firstAmpersand = formString.FindFirst("&", *index); int16 firstEqual = formString.FindFirst("=", *index); BString name; BString value; if (firstAmpersand == -1) { if (firstEqual != -1) { formString.CopyInto(name, *index, firstEqual - *index); formString.CopyInto(value, firstEqual + 1, formString.Length() - firstEqual - 1); } else formString.CopyInto(value, *index, formString.Length() - *index); *index = formString.Length() + 1; } else { if (firstEqual != -1 && firstEqual < firstAmpersand) { formString.CopyInto(name, *index, firstEqual - *index); formString.CopyInto(value, firstEqual + 1, firstAmpersand - firstEqual - 1); } else formString.CopyInto(value, *index, firstAmpersand - *index); *index = firstAmpersand + 1; } AddString(name, value); } void BHttpForm::_GenerateMultipartBoundary() { fMultipartBoundary = "----------------------------"; srand(time(NULL)); // TODO: Maybe a more robust way to seed the random number // generator is needed? for (int32 i = 0; i < kBoundaryRandomSize; i++) fMultipartBoundary << (char)(rand() % 10 + '0'); } // #pragma mark - Field information access by std iterator BString BHttpForm::_GetMultipartHeader(const BHttpFormData* element) const { BString result; result << "--" << fMultipartBoundary << "\r\n"; result << "Content-Disposition: form-data; name=\"" << element->Name() << '"'; switch (element->Type()) { case B_HTTPFORM_UNKNOWN: break; case B_HTTPFORM_FILE: { result << "; filename=\"" << element->File().Leaf() << '"'; BNode fileNode(element->File().Path()); BNodeInfo fileInfo(&fileNode); result << "\r\nContent-Type: "; char tempMime[128]; if (fileInfo.GetType(tempMime) == B_OK) result << tempMime; else result << "application/octet-stream"; break; } case B_HTTPFORM_STRING: case B_HTTPFORM_BUFFER: if (element->IsFile()) { result << "; filename=\"" << element->Filename() << '"'; if (element->MimeType().Length() > 0) result << "\r\nContent-Type: " << element->MimeType(); else result << "\r\nContent-Type: text/plain"; } break; } result << "\r\n\r\n"; return result; } // #pragma mark - Iterator BHttpForm::Iterator::Iterator(BHttpForm* form) : fElement(NULL) { fForm = form; fStdIterator = form->fFields.begin(); _FindNext(); } BHttpForm::Iterator::Iterator(const Iterator& other) { *this = other; } bool BHttpForm::Iterator::HasNext() const { return fStdIterator != fForm->fFields.end(); } BHttpFormData* BHttpForm::Iterator::Next() { BHttpFormData* element = fElement; _FindNext(); return element; } void BHttpForm::Iterator::Remove() { fForm->fFields.erase(fStdIterator); fElement = NULL; } BString BHttpForm::Iterator::MultipartHeader() { return fForm->_GetMultipartHeader(fPrevElement); } BHttpForm::Iterator& BHttpForm::Iterator::operator=(const Iterator& other) { fForm = other.fForm; fStdIterator = other.fStdIterator; fElement = other.fElement; fPrevElement = other.fPrevElement; return *this; } void BHttpForm::Iterator::_FindNext() { fPrevElement = fElement; if (fStdIterator != fForm->fFields.end()) { fElement = &fStdIterator->second; fStdIterator++; } else fElement = NULL; }