From 45939109b4d2aebac59573a0cac0ff5641d47d57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20A=C3=9Fmus?= Date: Wed, 27 Oct 2010 14:03:31 +0000 Subject: [PATCH] Patch done by Christophe Huriaux as part of GSoC 2010 "Services Kit" project: Integrated the classes in the Network Kit (libbnetapi.so). Only the foundation classed BUrl, BUrlContext, BNetworkCookie, BNetworkCookieJar and the private HttpTime code is currently compiled. The BUrlProtocol currently contains some misplaced BUrlProtocolHttp specific stuff, and the HTTP stuff itself has a dependency on libcrypto and should live in an add-on instead. I've sprinkled some TODOs in the code, and I've done some renaming compared to the last version of the GSoC patch. Any help to bring this further along is appreciated. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@39161 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- headers/os/net/HttpAuthentication.h | 94 ++ headers/os/net/HttpForm.h | 192 ++++ headers/os/net/HttpHeaders.h | 84 ++ headers/os/net/NetworkCookie.h | 126 +++ headers/os/net/NetworkCookieJar.h | 141 +++ headers/os/net/Url.h | 145 +++ headers/os/net/UrlContext.h | 27 + headers/os/net/UrlProtocol.h | 139 +++ .../os/net/UrlProtocolAsynchronousListener.h | 46 + .../os/net/UrlProtocolDispatchingListener.h | 66 ++ headers/os/net/UrlProtocolHttp.h | 235 +++++ headers/os/net/UrlProtocolListener.h | 120 +++ headers/os/net/UrlRequest.h | 68 ++ headers/os/net/UrlResult.h | 56 ++ headers/os/net/UrlSynchronousRequest.h | 43 + headers/private/net/HttpTime.h | 46 + .../network/libnetapi/HttpAuthentication.cpp | 395 ++++++++ src/kits/network/libnetapi/HttpForm.cpp | 781 ++++++++++++++++ src/kits/network/libnetapi/HttpHeaders.cpp | 315 +++++++ src/kits/network/libnetapi/HttpTime.cpp | 132 +++ src/kits/network/libnetapi/Jamfile | 25 +- src/kits/network/libnetapi/NetworkCookie.cpp | 813 ++++++++++++++++ .../network/libnetapi/NetworkCookieJar.cpp | 683 ++++++++++++++ .../libnetapi/NetworkCookieJarPrivate.h | 27 + src/kits/network/libnetapi/Url.cpp | 866 +++++++++++++++++ src/kits/network/libnetapi/UrlContext.cpp | 37 + src/kits/network/libnetapi/UrlProtocol.cpp | 298 ++++++ .../UrlProtocolAsynchronousListener.cpp | 196 ++++ .../UrlProtocolDispatchingListener.cpp | 126 +++ .../network/libnetapi/UrlProtocolHttp.cpp | 868 ++++++++++++++++++ .../network/libnetapi/UrlProtocolListener.cpp | 91 ++ src/kits/network/libnetapi/UrlRequest.cpp | 245 +++++ src/kits/network/libnetapi/UrlResult.cpp | 117 +++ .../libnetapi/UrlSynchronousRequest.cpp | 104 +++ 34 files changed, 7746 insertions(+), 1 deletion(-) create mode 100644 headers/os/net/HttpAuthentication.h create mode 100644 headers/os/net/HttpForm.h create mode 100644 headers/os/net/HttpHeaders.h create mode 100644 headers/os/net/NetworkCookie.h create mode 100644 headers/os/net/NetworkCookieJar.h create mode 100644 headers/os/net/Url.h create mode 100644 headers/os/net/UrlContext.h create mode 100644 headers/os/net/UrlProtocol.h create mode 100644 headers/os/net/UrlProtocolAsynchronousListener.h create mode 100644 headers/os/net/UrlProtocolDispatchingListener.h create mode 100644 headers/os/net/UrlProtocolHttp.h create mode 100644 headers/os/net/UrlProtocolListener.h create mode 100644 headers/os/net/UrlRequest.h create mode 100644 headers/os/net/UrlResult.h create mode 100644 headers/os/net/UrlSynchronousRequest.h create mode 100644 headers/private/net/HttpTime.h create mode 100644 src/kits/network/libnetapi/HttpAuthentication.cpp create mode 100644 src/kits/network/libnetapi/HttpForm.cpp create mode 100644 src/kits/network/libnetapi/HttpHeaders.cpp create mode 100644 src/kits/network/libnetapi/HttpTime.cpp create mode 100644 src/kits/network/libnetapi/NetworkCookie.cpp create mode 100644 src/kits/network/libnetapi/NetworkCookieJar.cpp create mode 100644 src/kits/network/libnetapi/NetworkCookieJarPrivate.h create mode 100644 src/kits/network/libnetapi/Url.cpp create mode 100644 src/kits/network/libnetapi/UrlContext.cpp create mode 100644 src/kits/network/libnetapi/UrlProtocol.cpp create mode 100644 src/kits/network/libnetapi/UrlProtocolAsynchronousListener.cpp create mode 100644 src/kits/network/libnetapi/UrlProtocolDispatchingListener.cpp create mode 100644 src/kits/network/libnetapi/UrlProtocolHttp.cpp create mode 100644 src/kits/network/libnetapi/UrlProtocolListener.cpp create mode 100644 src/kits/network/libnetapi/UrlRequest.cpp create mode 100644 src/kits/network/libnetapi/UrlResult.cpp create mode 100644 src/kits/network/libnetapi/UrlSynchronousRequest.cpp diff --git a/headers/os/net/HttpAuthentication.h b/headers/os/net/HttpAuthentication.h new file mode 100644 index 0000000000..8726917d70 --- /dev/null +++ b/headers/os/net/HttpAuthentication.h @@ -0,0 +1,94 @@ +/* + * Copyright 2010 Haiku Inc. All rights reserved. + * Distributed under the terms of the MIT License. + */ +#ifndef _B_HTTP_AUTHENTICATION_H_ +#define _B_HTTP_AUTHENTICATION_H_ + + +#include +#include + +// HTTP authentication method +enum BHttpAuthenticationMethod { + B_HTTP_AUTHENTICATION_NONE = 0, + // No authentication + B_HTTP_AUTHENTICATION_BASIC = 1, + // Basic base64 authentication method (unsecure) + B_HTTP_AUTHENTICATION_DIGEST = 2, + // Digest authentication + B_HTTP_AUTHENTICATION_IE_DIGEST = 4 + // Slightly modified digest authentication to mimic old IE one +}; + + +enum BHttpAuthenticationAlgorithm { + B_HTTP_AUTHENTICATION_ALGORITHM_NONE, + B_HTTP_AUTHENTICATION_ALGORITHM_MD5, + B_HTTP_AUTHENTICATION_ALGORITHM_MD5_SESS +}; + + +enum BHttpAuthenticationQop { + B_HTTP_QOP_NONE, + B_HTTP_QOP_AUTH, + B_HTTP_QOP_AUTHINT +}; + + +class BHttpAuthentication { +public: + BHttpAuthentication(); + BHttpAuthentication(const BString& username, + const BString& password); + + // Field modification + void SetUserName(const BString& username); + void SetPassword(const BString& password); + void SetMethod( + BHttpAuthenticationMethod type); + status_t Initialize(const BString& wwwAuthenticate); + + // Field access + const BString& UserName() const; + const BString& Password() const; + BHttpAuthenticationMethod Method() const; + + BString Authorization(const BUrl& url, + const BString& method) const; + + // Base64 encoding + // TODO: Move to a common place. We may have multiple implementations + // in the Haiku tree... + static BString Base64Encode(const BString& string); + static BString Base64Decode(const BString& string); + + +private: + BString _DigestResponse(const BString& uri, + const BString& method) const; + // TODO: Rename these? _H seems to return a hash value, + // _KD returns a hash value of the "data" prepended by + // the "secret" string... + BString _H(const BString& value) const; + BString _KD(const BString& secret, + const BString& data) const; + +private: + BHttpAuthenticationMethod fAuthenticationMethod; + BString fUserName; + BString fPassword; + + BString fRealm; + BString fDigestNonce; + mutable BString fDigestCnonce; + mutable int fDigestNc; + BString fDigestOpaque; + bool fDigestStale; + BHttpAuthenticationAlgorithm fDigestAlgorithm; + BHttpAuthenticationQop fDigestQop; + + BString fAuthorizationString; +}; + +#endif // _B_HTTP_AUTHENTICATION_H_ diff --git a/headers/os/net/HttpForm.h b/headers/os/net/HttpForm.h new file mode 100644 index 0000000000..a1a766d76b --- /dev/null +++ b/headers/os/net/HttpForm.h @@ -0,0 +1,192 @@ +/* + * Copyright 2010 Haiku Inc. All rights reserved. + * Distributed under the terms of the MIT License. + */ +#ifndef _B_HTTP_FORM_H_ +#define _B_HTTP_FORM_H_ + + +#include +#include + +#include + + +enum form_type { + B_HTTP_FORM_URL_ENCODED, + B_HTTP_FORM_MULTIPART +}; + + +enum form_content_type { + B_HTTPFORM_UNKNOWN, + B_HTTPFORM_STRING, + B_HTTPFORM_FILE, + B_HTTPFORM_BUFFER +}; + + +class BHttpFormData { +private: + // Empty constructor is only kept for compatibility with map<>::operator[], + // but never used (see BHttpForm::operator[] which does the necessary + // check up) + BHttpFormData(); + friend class std::map; + +public: + BHttpFormData(const BString& name, + const BString& value); + BHttpFormData(const BString& name, + const BPath& file); + BHttpFormData(const BString& name, + const void* buffer, ssize_t size); + BHttpFormData(const BHttpFormData& other); + ~BHttpFormData(); + + // Retrieve data informations + bool InitCheck() const; + + const BString& Name() const; + const BString& String() const; + const BPath& File() const; + const void* Buffer() const; + ssize_t BufferSize() const; + + bool IsFile() const; + const BString& Filename() const; + const BString& MimeType() const; + form_content_type Type() const; + + // Change behavior + status_t MarkAsFile(const BString& filename, + const BString& mimeType); + status_t MarkAsFile(const BString& filename); + void UnmarkAsFile(); + status_t CopyBuffer(); + + // Overloaded operators + BHttpFormData& operator=(const BHttpFormData& other); + +private: + form_content_type fDataType; + bool fCopiedBuffer; + bool fFileMark; + + BString fName; + BString fStringValue; + BPath fPathValue; + const void* fBufferValue; + ssize_t fBufferSize; + + BString fFilename; + BString fMimeType; +}; + + +class BHttpForm { +public: + // Nested types + class Iterator; + typedef std::map FormStorage; + +public: + BHttpForm(); + BHttpForm(const BHttpForm& other); + BHttpForm(const BString& formString); + ~BHttpForm(); + + // Form string parsing + void ParseString(const BString& formString); + BString RawData() const; + + // Form add + status_t AddString(const BString& name, + const BString& value); + status_t AddInt(const BString& name, int32 value); + status_t AddFile(const BString& fieldName, + const BPath& file); + status_t AddBuffer(const BString& fieldName, + const void* buffer, ssize_t size); + status_t AddBufferCopy(const BString& fieldName, + const void* buffer, ssize_t size); + + // Mark a field as a filename + void MarkAsFile(const BString& fieldName, + const BString& filename, + const BString& mimeType); + void MarkAsFile(const BString& fieldName, + const BString& filename); + void UnmarkAsFile(const BString& fieldName); + + // Change form type + void SetFormType(form_type type); + + // Form test + bool HasField(const BString& name) const; + + // Form retrieve + BString GetMultipartHeader(const BString& fieldName) + const; + form_content_type GetType(const BString& fieldname) const; + + // Form informations + form_type GetFormType() const; + const BString& GetMultipartBoundary() const; + BString GetMultipartFooter() const; + ssize_t ContentLength() const; + + // Form iterator + Iterator GetIterator(); + + // Form clear + void Clear(); + + // Overloaded operators + BHttpFormData& operator[](const BString& name); + +private: + void _ExtractNameValuePair(const BString& string, + int32* index); + void _GenerateMultipartBoundary(); + BString _GetMultipartHeader( + const BHttpFormData* element) const; + form_content_type _GetType(FormStorage::const_iterator it) const; + void _Erase(FormStorage::iterator it); + +private: + friend class Iterator; + + FormStorage fFields; + form_type fType; + BString fMultipartBoundary; +}; + + + +class BHttpForm::Iterator { +public: + Iterator(const Iterator& other); + + BHttpFormData* Next(); + bool HasNext() const; + void Remove(); + BString MultipartHeader(); + + Iterator& operator=(const Iterator& other); + +private: + Iterator(BHttpForm* form); + void _FindNext(); + +private: + friend class BHttpForm; + + BHttpForm* fForm; + BHttpForm::FormStorage::iterator + fStdIterator; + BHttpFormData* fElement; + BHttpFormData* fPrevElement; +}; + +#endif // _B_HTTP_FORM_H_ diff --git a/headers/os/net/HttpHeaders.h b/headers/os/net/HttpHeaders.h new file mode 100644 index 0000000000..aa80cfffe2 --- /dev/null +++ b/headers/os/net/HttpHeaders.h @@ -0,0 +1,84 @@ +/* + * Copyright 2010 Haiku Inc. All rights reserved. + * Distributed under the terms of the MIT License. + */ +#ifndef _B_HTTP_HEADERS_H_ +#define _B_HTTP_HEADERS_H_ + + +#include +#include + + +class BHttpHeader { +public: + BHttpHeader(); + BHttpHeader(const char* string); + BHttpHeader(const char* name, + const char* value); + BHttpHeader(const BHttpHeader& copy); + + // Header data modification + void SetName(const char* name); + void SetValue(const char* value); + bool SetHeader(const char* string); + + // Header data access + const char* Name() const; + const char* Value() const; + const char* Header() const; + + // Header data test + bool NameIs(const char* name) const; + + // Overloaded members + BHttpHeader& operator=(const BHttpHeader& other); + +private: + BString fName; + BString fValue; + + mutable BString fRawHeader; + mutable bool fRawHeaderValid; +}; + + +class BHttpHeaders { +public: + BHttpHeaders(); + BHttpHeaders(const BHttpHeaders& copy); + ~BHttpHeaders(); + + // Header list access + const char* HeaderValue(const char* name) const; + BHttpHeader& HeaderAt(int32 index) const; + + // Header count + int32 CountHeaders() const; + + // Header list tests + int32 HasHeader(const char* name) const; + + // Header add or replacement + bool AddHeader(const char* line); + bool AddHeader(const char* name, + const char* value); + bool AddHeader(const char* name, + int32 value); + + // Header deletion + void Clear(); + + // Overloaded operators + BHttpHeaders& operator=(const BHttpHeaders& other); + BHttpHeader& operator[](int32 index) const; + const char* operator[](const char* name) const; + +private: + void _EraseData(); + +private: + BList fHeaderList; +}; + +#endif // _B_HTTP_HEADERS_H_ diff --git a/headers/os/net/NetworkCookie.h b/headers/os/net/NetworkCookie.h new file mode 100644 index 0000000000..168c9b10e4 --- /dev/null +++ b/headers/os/net/NetworkCookie.h @@ -0,0 +1,126 @@ +/* + * Copyright 2010 Haiku Inc. All rights reserved. + * Distributed under the terms of the MIT License. + */ +#ifndef _B_NETWORK_COOKIE_H_ +#define _B_NETWORK_COOKIE_H_ + + +#include +#include +#include +#include +#include + + +class BNetworkCookie : public BArchivable { +public: + BNetworkCookie(const char* name, + const char* value); + BNetworkCookie(const BNetworkCookie& other); + BNetworkCookie(const BString& cookieString); + BNetworkCookie(const BString& cookieString, + const BUrl& url); + BNetworkCookie(BMessage* archive); + BNetworkCookie(); + virtual ~BNetworkCookie(); + + // Parse a "SetCookie" string, or "name=value" + + BNetworkCookie& ParseCookieStringFromUrl(const BString& string, + const BUrl& url); + BNetworkCookie& ParseCookieString(const BString& cookieString); + + // Modify the cookie fields + BNetworkCookie& SetComment(const BString& comment); + BNetworkCookie& SetCommentUrl(const BString& commentUrl); + BNetworkCookie& SetDiscard(bool discard); + BNetworkCookie& SetDomain(const BString& domain); + BNetworkCookie& SetMaxAge(int32 maxAge); + BNetworkCookie& SetExpirationDate(time_t expireDate); + BNetworkCookie& SetExpirationDate(BDateTime& expireDate); + BNetworkCookie& SetPath(const BString& path); + BNetworkCookie& SetSecure(bool secure); + BNetworkCookie& SetVersion(int8 version); + BNetworkCookie& SetName(const BString& name); + BNetworkCookie& SetValue(const BString& value); + + // Access the cookie fields + const BString& CommentUrl() const; + const BString& Comment() const; + bool Discard() const; + const BString& Domain() const; + int32 MaxAge() const; + time_t ExpirationDate() const; + const BString& ExpirationString() const; + const BString& Path() const; + bool Secure() const; + int8 Version() const; + const BString& Name() const; + const BString& Value() const; + const BString& RawCookie(bool full) const; + bool IsSessionCookie() const; + bool IsValid(bool strict = false) const; + bool IsValidForUrl(const BUrl& url) const; + bool IsValidForDomain(const BString& domain) const; + bool IsValidForPath(const BString& path) const; + + // Test if cookie fields are defined + bool HasCommentUrl() const; + bool HasComment() const; + bool HasDiscard() const; + bool HasDomain() const; + bool HasMaxAge() const; + bool HasExpirationDate() const; + bool HasPath() const; + bool HasVersion() const; + bool HasName() const; + bool HasValue() const; + + // Test if cookie could be deleted + bool ShouldDeleteAtExit() const; + bool ShouldDeleteNow() const; + + // BArchivable members + virtual status_t Archive(BMessage* into, + bool deep = true) const; + static BArchivable* Instantiate(BMessage* archive); + + // Overloaded operators + BNetworkCookie& operator=(const BNetworkCookie& other); + BNetworkCookie& operator=(const char* string); + bool operator==(const BNetworkCookie& other); + bool operator!=(const BNetworkCookie& other); +private: + void _Reset(); + void _ExtractNameValuePair( + const BString& cookieString, int16* index, + bool parseField = false); + +private: + mutable BString fRawCookie; + mutable bool fRawCookieValid; + mutable BString fRawFullCookie; + mutable bool fRawFullCookieValid; + + BString fComment; + BString fCommentUrl; + bool fDiscard; + BString fDomain; + BDateTime fExpiration; + mutable BString fExpirationString; + mutable bool fExpirationStringValid; + BString fPath; + bool fSecure; + int8 fVersion; + BString fName; + BString fValue; + + bool fHasDiscard; + bool fHasExpirationDate; + bool fSessionCookie; + bool fHasVersion; +}; + +#endif // _B_NETWORK_COOKIE_H_ + diff --git a/headers/os/net/NetworkCookieJar.h b/headers/os/net/NetworkCookieJar.h new file mode 100644 index 0000000000..31cd196a5b --- /dev/null +++ b/headers/os/net/NetworkCookieJar.h @@ -0,0 +1,141 @@ +/* + * Copyright 2010 Haiku Inc. All rights reserved. + * Distributed under the terms of the MIT License. + */ +#ifndef _B_NETWORK_COOKIE_JAR_H_ +#define _B_NETWORK_COOKIE_JAR_H_ + +#include +#include +#include +#include +#include +#include +#include + + +typedef BList BNetworkCookieList; + + +class BNetworkCookieJar : public BArchivable, public BFlattenable { +public: + // Nested types + class Iterator; + class UrlIterator; + struct PrivateIterator; + struct PrivateHashMap; + +public: + BNetworkCookieJar(); + BNetworkCookieJar( + const BNetworkCookieJar& other); + BNetworkCookieJar( + const BNetworkCookieList& otherList); + BNetworkCookieJar(BMessage* archive); + virtual ~BNetworkCookieJar(); + + bool AddCookie(const BNetworkCookie& cookie); + bool AddCookie(BNetworkCookie* cookie); + bool AddCookies( + const BNetworkCookieList& cookies); + + uint32 DeleteOutdatedCookies(); + uint32 PurgeForExit(); + + // BArchivable members + virtual status_t Archive(BMessage* into, + bool deep = true) const; + static BArchivable* Instantiate(BMessage* archive); + + // BFlattenable members + virtual bool IsFixedSize() const; + virtual type_code TypeCode() const; + virtual ssize_t FlattenedSize() const; + virtual status_t Flatten(void* buffer, ssize_t size) + const; + virtual bool AllowsTypeCode(type_code code) const; + virtual status_t Unflatten(type_code code, + const void* buffer, ssize_t size); + + // Iterators + Iterator GetIterator() const; + UrlIterator GetUrlIterator(const BUrl& url) const; + +private: + void _DoFlatten() const; + +private: + friend class Iterator; + friend class UrlIterator; + + PrivateHashMap* fCookieHashMap; + mutable BString fFlattened; +}; + + +class BNetworkCookieJar::Iterator { +public: + Iterator(const Iterator& other); + ~Iterator(); + + bool HasNext() const; + BNetworkCookie* Next(); + BNetworkCookie* NextDomain(); + BNetworkCookie* Remove(); + void RemoveDomain(); + Iterator& operator=(const Iterator& other); + +private: + Iterator(const BNetworkCookieJar* map); + + void _FindNext(); + +private: + friend class BNetworkCookieJar; + + BNetworkCookieJar* fCookieJar; + PrivateIterator* fIterator; + BNetworkCookieList* fLastList; + BNetworkCookieList* fList; + BNetworkCookie* fElement; + BNetworkCookie* fLastElement; + int32 fIndex; +}; + + +class BNetworkCookieJar::UrlIterator { +public: + UrlIterator(const UrlIterator& other); + ~UrlIterator(); + + bool HasNext() const; + BNetworkCookie* Next(); + BNetworkCookie* Remove(); + UrlIterator& operator=(const UrlIterator& other); + +private: + UrlIterator(const BNetworkCookieJar* map, + const BUrl& url); + + bool _SupDomain(); + void _FindNext(); + void _FindDomain(); + bool _FindPath(); + +private: + friend class BNetworkCookieJar; + + BNetworkCookieJar* fCookieJar; + PrivateIterator* fIterator; + BNetworkCookieList* fList; + BNetworkCookieList* fLastList; + BNetworkCookie* fElement; + BNetworkCookie* fLastElement; + + int32 fIndex; + int32 fLastIndex; + + BUrl fUrl; +}; + +#endif // _B_NETWORK_COOKIE_JAR_ diff --git a/headers/os/net/Url.h b/headers/os/net/Url.h new file mode 100644 index 0000000000..9ce8a48a03 --- /dev/null +++ b/headers/os/net/Url.h @@ -0,0 +1,145 @@ +/* + * Copyright 2010 Haiku Inc. All rights reserved. + * Distributed under the terms of the MIT License. + */ +#ifndef _B_URL_H_ +#define _B_URL_H_ + + +#include +#include +#include + + +class BUrl : public BArchivable { +public: + BUrl(const char* url); + BUrl(BMessage* archive); + BUrl(const BUrl& other); + BUrl(); + virtual ~BUrl(); + + // URL fields modifiers + BUrl& SetUrlString(const BString& url); + BUrl& SetProtocol(const BString& scheme); + BUrl& SetUserName(const BString& user); + BUrl& SetPassword(const BString& password); + BUrl& SetHost(const BString& host); + BUrl& SetPort(int port); + BUrl& SetPath(const BString& path); + BUrl& SetRequest(const BString& request); + BUrl& SetFragment(const BString& fragment); + + // URL fields access + const BString& UrlString() const; + const BString& Protocol() const; + const BString& UserName() const; + const BString& Password() const; + const BString& UserInfo() const; + const BString& Host() const; + int Port() const; + const BString& Authority() const; + const BString& Path() const; + const BString& Request() const; + const BString& Fragment() const; + + // URL fields tests + bool IsValid() const; + bool HasProtocol() const; + bool HasUserName() const; + bool HasPassword() const; + bool HasUserInfo() const; + bool HasHost() const; + bool HasPort() const; + bool HasAuthority() const; + bool HasPath() const; + bool HasRequest() const; + bool HasFragment() const; + + // Url encoding/decoding of needed fields + void UrlEncode(bool strict = false); + void UrlDecode(bool strict = false); + + // Url encoding/decoding of strings + static BString UrlEncode(const BString& url, + bool strict = false, + bool directory = false); + static BString UrlDecode(const BString& url, + bool strict = false); + + // BArchivable members + virtual status_t Archive(BMessage* into, + bool deep = true) const; + static BArchivable* Instantiate(BMessage* archive); + + // URL comparison + bool operator==(BUrl& other) const; + bool operator!=(BUrl& other) const; + + // URL assignment + const BUrl& operator=(const BUrl& other); + const BUrl& operator=(const BString& string); + const BUrl& operator=(const char* string); + + // URL to string conversion + operator const char*() const; + +private: + void _ResetFields(); + void _ExplodeUrlString(const BString& urlString); + + void _ExtractProtocol(const BString& urlString, + int16* origin); + void _ExtractAuthority(const BString& urlString, + int16* origin); + void _ExtractPath(const BString& urlString, + int16* origin); + void _ExtractRequestAndFragment( + const BString& urlString, int16* origin); + + static BString _DoUrlEncodeChunk(const BString& chunk, + bool strict, bool directory = false); + static BString _DoUrlDecodeChunk(const BString& chunk, + bool strict); + + bool _IsProtocolValid(); + static bool _IsAuthorityTerminator(char c); + static bool _IsPathTerminator(char c); + static bool _IsRequestTerminator(char c); + static bool _IsUnreserved(char c); + static bool _IsGenDelim(char c); + static bool _IsSubDelim(char c); + +private: + mutable BString fUrlString; + mutable BString fAuthority; + mutable BString fUserInfo; + + BString fProtocol; + BString fUser; + BString fPassword; + BString fHost; + int fPort; + BString fPath; + BString fRequest; + BString fFragment; + + mutable bool fUrlStringValid : 1; + mutable bool fAuthorityValid : 1; + mutable bool fUserInfoValid : 1; + + bool fBasicUri : 1; + + bool fHasProtocol : 1; + bool fHasUserName : 1; + bool fHasPassword : 1; + bool fHasUserInfo : 1; + bool fHasHost : 1; + bool fHasPort : 1; + bool fHasAuthority : 1; + bool fHasPath : 1; + bool fHasRequest : 1; + bool fHasFragment : 1; +}; + +#endif // _B_URL_H_ diff --git a/headers/os/net/UrlContext.h b/headers/os/net/UrlContext.h new file mode 100644 index 0000000000..414a371106 --- /dev/null +++ b/headers/os/net/UrlContext.h @@ -0,0 +1,27 @@ +/* + * Copyright 2010 Haiku Inc. All rights reserved. + * Distributed under the terms of the MIT License. + */ +#ifndef _B_URL_CONTEXT_H_ +#define _B_URL_CONTEXT_H_ + + +#include + + +class BUrlContext { +public: + BUrlContext(); + + // Context modifiers + void SetCookieJar( + const BNetworkCookieJar& cookieJar); + + // Context accessors + BNetworkCookieJar& GetCookieJar(); + +private: + BNetworkCookieJar fCookieJar; +}; + +#endif // _B_URL_CONTEXT_H_ diff --git a/headers/os/net/UrlProtocol.h b/headers/os/net/UrlProtocol.h new file mode 100644 index 0000000000..fdc5472295 --- /dev/null +++ b/headers/os/net/UrlProtocol.h @@ -0,0 +1,139 @@ +/* + * Copyright 2010 Haiku Inc. All rights reserved. + * Distributed under the terms of the MIT License. + */ +#ifndef _B_URL_PROTOCOL_H_ +#define _B_URL_PROTOCOL_H_ + + +#include +#include +#include +#include +#include + + +class BUrlProtocol { +public: + BUrlProtocol(const BUrl& url, + BUrlProtocolListener* listener, + BUrlContext* context, + BUrlResult* result, + const char* threadName, + const char* protocolName); + + // URL protocol required members + // TODO: (stippi) I know it's sometimes appealing to have these + // "simplistic" methods that can do anything, but they remove + // type-safety. Why not overload SetOption with all possible types? + // Like: + // SetOption(uint32 option, bool value); + // SetOption(uint32 option, int8 value); + // SetOption(uint32 option, int16 value); + // SetOption(uint32 option, int32 value); + // ... + // This keeps the calling side even more simple, since it + // don't need to do pointer stuff with the parameters. Also, since + // this method is forced to be implemented in derived classes, why + // is it here at all? Why not have non-virtual setters for specific + // things, where the setter is properly named? + virtual status_t SetOption(uint32 name, void* value) = 0; + + // URL protocol thread management + virtual thread_id Run(); + virtual status_t Pause(); + virtual status_t Resume(); + virtual status_t Stop(); + + // URL protocol parameters modification + status_t SetUrl(const BUrl& url); + status_t SetResult(BUrlResult* result); + status_t SetContext(BUrlContext* context); + status_t SetListener(BUrlProtocolListener* listener); + + // URL protocol parameters access + const BUrl& Url() const; + BUrlResult* Result() const; + BUrlContext* Context() const; + BUrlProtocolListener* Listener() const; + const BString& Protocol() const; + // TODO: Does not belong here. + BHttpHeaders& Headers() { return fRequestHeaders; } + + // URL protocol informations + bool IsRunning() const; + status_t Status() const; + virtual const char* StatusString(status_t threadStatus) + const; + + +protected: + static int32 _ThreadEntry(void* arg); + virtual status_t _ProtocolLoop(); + virtual void _EmitDebug(BUrlProtocolDebugMessage type, + const char* format, ...); + + // URL result parameters access + BMallocIO& _ResultRawData(); + BHttpHeaders& _ResultHeaders(); + void _SetResultStatusCode(int32 statusCode); + BString& _ResultStatusText(); + +protected: + BUrl fUrl; + BHttpHeaders fRequestHeaders; + // TODO: Does not belong here. + BUrlResult* fResult; + BUrlContext* fContext; + BUrlProtocolListener* fListener; + + bool fQuit; + bool fRunning; + status_t fThreadStatus; + thread_id fThreadId; + BString fThreadName; + BString fProtocol; +}; + + +// TODO: Rename, this is in the global namespace. +enum { + B_PROT_THREAD_STATUS__BASE = 0, + B_PROT_SUCCESS = B_PROT_THREAD_STATUS__BASE, + B_PROT_RUNNING, + B_PROT_PAUSED, + B_PROT_ABORTED, + B_PROT_SOCKET_ERROR, + B_PROT_CONNECTION_FAILED, + B_PROT_CANT_RESOLVE_HOSTNAME, + B_PROT_WRITE_FAILED, + B_PROT_READ_FAILED, + B_PROT_NO_MEMORY, + B_PROT_PROTOCOL_ERROR, + // Thread status over this one are guaranteed to be + // errors + B_PROT_THREAD_STATUS__END +}; + + +namespace BPrivate { + +class BUrlProtocolOption { +public: + BUrlProtocolOption(void* value) : fValuePtr(value) { } + + bool Bool() const { return *reinterpret_cast(fValuePtr); } + int8 Int8() const { return *reinterpret_cast(fValuePtr); } + int16 Int16() const { return *reinterpret_cast(fValuePtr); } + int32 Int32() const { return *reinterpret_cast(fValuePtr); } + char* String() const { return reinterpret_cast(fValuePtr); } + void* Pointer() const { return fValuePtr; } + +private: + void* fValuePtr; +}; + +} // namespace BPrivate + + +#endif // _B_URL_PROTOCOL_H_ diff --git a/headers/os/net/UrlProtocolAsynchronousListener.h b/headers/os/net/UrlProtocolAsynchronousListener.h new file mode 100644 index 0000000000..52d708e736 --- /dev/null +++ b/headers/os/net/UrlProtocolAsynchronousListener.h @@ -0,0 +1,46 @@ +/* + * Copyright 2010 Haiku Inc. All rights reserved. + * Distributed under the terms of the MIT License. + */ +#ifndef _B_URL_PROTOCOL_ASYNCHRONOUS_LISTENER_H_ +#define _B_URL_PROTOCOL_ASYNCHRONOUS_LISTENER_H_ + + +#include +#include +#include + + +class BUrlProtocolAsynchronousListener : public BHandler { +public: + BUrlProtocolAsynchronousListener( + bool transparent = false); + virtual ~BUrlProtocolAsynchronousListener(); + + virtual void ConnectionOpened(BUrlProtocol* caller); + virtual void HostnameResolved(BUrlProtocol* caller, + const char* ip); + virtual void ResponseStarted(BUrlProtocol* caller); + virtual void HeadersReceived(BUrlProtocol* caller); + virtual void DataReceived(BUrlProtocol* caller, + const char* data, ssize_t size); + virtual void DownloadProgress(BUrlProtocol* caller, + ssize_t bytesReceived, ssize_t bytesTotal); + virtual void UploadProgress(BUrlProtocol* caller, + ssize_t bytesSent, ssize_t bytesTotal); + virtual void RequestCompleted(BUrlProtocol* caller, + bool success); + + // Synchronous listener access + BUrlProtocolListener* SynchronousListener(); + + // BHandler interface + virtual void MessageReceived(BMessage* message); + +private: + BUrlProtocolDispatchingListener* + fSynchronousListener; +}; + +#endif // _B_URL_PROTOCOL_ASYNCHRONOUS_LISTENER_H_ + diff --git a/headers/os/net/UrlProtocolDispatchingListener.h b/headers/os/net/UrlProtocolDispatchingListener.h new file mode 100644 index 0000000000..b3cdf5875e --- /dev/null +++ b/headers/os/net/UrlProtocolDispatchingListener.h @@ -0,0 +1,66 @@ +/* + * Copyright 2010 Haiku Inc. All rights reserved. + * Distributed under the terms of the MIT License. + */ +#ifndef _B_URL_PROTOCOL_DISPATCHING_LISTENER_H_ +#define _B_URL_PROTOCOL_DISPATCHING_LISTENER_H_ + + +#include +#include +#include + + +//! To be in AppTypes.h +enum { + B_URL_PROTOCOL_NOTIFICATION = '_UPN' +}; + + +// Notification types +enum { + B_URL_PROTOCOL_CONNECTION_OPENED, + B_URL_PROTOCOL_HOSTNAME_RESOLVED, + B_URL_PROTOCOL_RESPONSE_STARTED, + B_URL_PROTOCOL_HEADERS_RECEIVED, + B_URL_PROTOCOL_DATA_RECEIVED, + B_URL_PROTOCOL_DOWNLOAD_PROGRESS, + B_URL_PROTOCOL_UPLOAD_PROGRESS, + B_URL_PROTOCOL_REQUEST_COMPLETED +}; + + +class BUrlProtocolDispatchingListener : public BUrlProtocolListener { +public: + BUrlProtocolDispatchingListener( + BHandler* handler); + BUrlProtocolDispatchingListener( + const BMessenger& messenger); + + virtual void ConnectionOpened(BUrlProtocol* caller); + virtual void HostnameResolved(BUrlProtocol* caller, + const char* ip); + virtual void ResponseStarted(BUrlProtocol* caller); + virtual void HeadersReceived(BUrlProtocol* caller); + virtual void DataReceived(BUrlProtocol* caller, + const char* data, ssize_t size); + virtual void DownloadProgress(BUrlProtocol* caller, + ssize_t bytesReceived, ssize_t bytesTotal); + virtual void UploadProgress(BUrlProtocol* caller, + ssize_t bytesSent, ssize_t bytesTotal); + virtual void RequestCompleted(BUrlProtocol* caller, + bool success); + virtual void DebugMessage(BUrlProtocol*, + BUrlProtocolDebugMessage, + const char*) { } + +private: + void _SendMessage(BMessage* message, + int8 notification, BUrlProtocol* caller); + +private: + BMessenger fMessenger; +}; + +#endif // _B_URL_PROTOCOL_DISPATCHING_LISTENER_H_ + diff --git a/headers/os/net/UrlProtocolHttp.h b/headers/os/net/UrlProtocolHttp.h new file mode 100644 index 0000000000..7542510d40 --- /dev/null +++ b/headers/os/net/UrlProtocolHttp.h @@ -0,0 +1,235 @@ +/* + * Copyright 2010 Haiku Inc. All rights reserved. + * Distributed under the terms of the MIT License. + */ +#ifndef _B_URL_PROTOCOL_HTTP_H_ +#define _B_URL_PROTOCOL_HTTP_H_ + + +#include + + +#include +#include +#include +#include +#include +#include + + +class BUrlProtocolHttp : public BUrlProtocol { +public: + BUrlProtocolHttp(BUrl& url, + BUrlProtocolListener* listener = NULL, + BUrlContext* context = NULL, + BUrlResult* result = NULL); + + virtual status_t SetOption(uint32 option, void* value); + + static bool IsInformationalStatusCode(int16 code); + static bool IsSuccessStatusCode(int16 code); + static bool IsRedirectionStatusCode(int16 code); + static bool IsClientErrorStatusCode(int16 code); + static bool IsServerErrorStatusCode(int16 code); + static int16 StatusCodeClass(int16 code); + + virtual const char* StatusString(status_t threadStatus) const; + +private: + void _ResetOptions(); + status_t _ProtocolLoop(); + bool _ResolveHostName(); + status_t _MakeRequest(); + + void _CreateRequest(); + void _AddHeaders(); + + status_t _GetLine(BString& destString); + + void _ParseStatus(); + void _ParseHeaders(); + + void _CopyChunkInBuffer(char** buffer, + ssize_t* bytesReceived); + + void _AddOutputBufferLine(const char* line); + + +private: + BNetEndpoint fSocket; + BNetAddress fRemoteAddr; + + int8 fRequestMethod; + int8 fHttpVersion; + + BString fOutputBuffer; + BNetBuffer fInputBuffer; + + BHttpHeaders fHeaders; + BHttpAuthentication fAuthentication; + + // Request status + BHttpHeaders fOutputHeaders; + bool fStatusReceived; + bool fHeadersReceived; + bool fContentReceived; + bool fTrailingHeadersReceived; + + + // Protocol options + uint8 fOptMaxRedirs; + BString fOptReferer; + BString fOptUserAgent; + BString fOptUsername; + BString fOptPassword; + uint32 fOptAuthMethods; + BHttpHeaders* fOptHeaders; + BHttpForm* fOptPostFields; + BDataIO* fOptInputData; + bool fOptSetCookies : 1; + bool fOptFollowLocation : 1; + bool fOptDiscardData : 1; + bool fOptDisableListener : 1; + bool fOptAutoReferer : 1; +}; + +// ProtocolLoop return status +enum { + B_PROT_HTTP_NOT_FOUND = B_PROT_THREAD_STATUS__END, + B_PROT_HTTP_THREAD_STATUS__END +}; + + +// Request method +enum { + B_HTTP_GET = 1, + B_HTTP_POST, + B_HTTP_PUT, + B_HTTP_HEAD, + B_HTTP_DELETE, + B_HTTP_OPTIONS +}; + + +// HTTP Version +enum { + B_HTTP_10 = 1, + B_HTTP_11 +}; + + +// HTTP Protocol options +enum { + B_HTTPOPT_METHOD = 0, + // (int) Request method (see B_HTTP_GET, ...) + B_HTTPOPT_FOLLOWLOCATION, + // (bool) Follow Location: headers + B_HTTPOPT_MAXREDIRS, + // (int) Max relocation + B_HTTPOPT_HEADERS, + // (BHttpHeaders*) Headers to be sent + B_HTTPOPT_REFERER, + // (string) Referer + B_HTTPOPT_USERAGENT, + // (string) User-Agent + B_HTTPOPT_SETCOOKIES, + // (bool) Send cookies from context + B_HTTPOPT_DISCARD_DATA, + // (bool) Discard incoming data (still notified) + B_HTTPOPT_DISABLE_LISTENER, + // (bool) Don't send notification to the listener + B_HTTPOPT_AUTOREFERER, + // (bool) Automatically set the Referer header + B_HTTPOPT_POSTFIELDS, + // (BHttpForm*) POST data to be sent + B_HTTPOPT_INPUTDATA, + // (BDataIO*) Input data to be sent (POST, PUT) + B_HTTPOPT_AUTHUSERNAME, + // (string) Authentication username + B_HTTPOPT_AUTHPASSWORD, + // (string) Authentication password + B_HTTPOPT_AUTHMETHOD, + // (int) Allowed authentication methods (see BHttpAuthenticationMethod) + + B_HTTPOPT__OPT_NUM +}; + + +// HTTP status classes +enum http_status_code_class { + B_HTTP_STATUS_CLASS_INVALID = 000, + B_HTTP_STATUS_CLASS_INFORMATIONAL = 100, + B_HTTP_STATUS_CLASS_SUCCESS = 200, + B_HTTP_STATUS_CLASS_REDIRECTION = 300, + B_HTTP_STATUS_CLASS_CLIENT_ERROR = 400, + B_HTTP_STATUS_CLASS_SERVER_ERROR = 500 +}; + + +// Known HTTP status codes +enum http_status_code { + // Informational status codes + B_HTTP_STATUS__INFORMATIONAL_BASE = 100, + B_HTTP_STATUS_CONTINUE = B_HTTP_STATUS__INFORMATIONAL_BASE, + B_HTTP_STATUS_SWITCHING_PROTOCOLS, + B_HTTP_STATUS__INFORMATIONAL_END, + + // Success status codes + B_HTTP_STATUS__SUCCESS_BASE = 200, + B_HTTP_STATUS_OK = B_HTTP_STATUS__SUCCESS_BASE, + B_HTTP_STATUS_CREATED, + B_HTTP_STATUS_ACCEPTED, + B_HTTP_STATUS_NON_AUTHORITATIVE_INFORMATION, + B_HTTP_STATUS_NO_CONTENT, + B_HTTP_STATUS_RESET_CONTENT, + B_HTTP_STATUS_PARTIAL_CONTENT, + B_HTTP_STATUS__SUCCESS_END, + + // Redirection status codes + B_HTTP_STATUS__REDIRECTION_BASE = 300, + B_HTTP_STATUS_MULTIPLE_CHOICE = B_HTTP_STATUS__REDIRECTION_BASE, + B_HTTP_STATUS_MOVED_PERMANENTLY, + B_HTTP_STATUS_FOUND, + B_HTTP_STATUS_SEE_OTHER, + B_HTTP_STATUS_NOT_MODIFIED, + B_HTTP_STATUS_USE_PROXY, + B_HTTP_STATUS_TEMPORARY_REDIRECT, + B_HTTP_STATUS__REDIRECTION_END, + + // Client error status codes + B_HTTP_STATUS__CLIENT_ERROR_BASE = 400, + B_HTTP_STATUS_BAD_REQUEST = B_HTTP_STATUS__CLIENT_ERROR_BASE, + B_HTTP_STATUS_UNAUTHORIZED, + B_HTTP_STATUS_PAYMENT_REQUIRED, + B_HTTP_STATUS_FORBIDDEN, + B_HTTP_STATUS_NOT_FOUND, + B_HTTP_STATUS_METHOD_NOT_ALLOWED, + B_HTTP_STATUS_NOT_ACCEPTABLE, + B_HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED, + B_HTTP_STATUS_REQUEST_TIMEOUT, + B_HTTP_STATUS_CONFLICT, + B_HTTP_STATUS_GONE, + B_HTTP_STATUS_LENGTH_REQUIRED, + B_HTTP_STATUS_PRECONDITION_FAILED, + B_HTTP_STATUS_REQUEST_ENTITY_TOO_LARGE, + B_HTTP_STATUS_REQUEST_URI_TOO_LARGE, + B_HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE, + B_HTTP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE, + B_HTTP_STATUS_EXPECTATION_FAILED, + B_HTTP_STATUS__CLIENT_ERROR_END, + + // Server error status codes + B_HTTP_STATUS__SERVER_ERROR_BASE = 500, + B_HTTP_STATUS_INTERNAL_SERVER_ERROR = B_HTTP_STATUS__SERVER_ERROR_BASE, + B_HTTP_STATUS_NOT_IMPLEMENTED, + B_HTTP_STATUS_BAD_GATEWAY, + B_HTTP_STATUS_SERVICE_UNAVAILABLE, + B_HTTP_STATUS_GATEWAY_TIMEOUT, + B_HTTP_STATUS__SERVER_ERROR_END +}; + + +// HTTP default User-Agent +#define B_HTTP_PROTOCOL_USER_AGENT_FORMAT "ServicesKit (%s)" + +#endif // _B_URL_PROTOCOL_HTTP_H_ diff --git a/headers/os/net/UrlProtocolListener.h b/headers/os/net/UrlProtocolListener.h new file mode 100644 index 0000000000..e046e4b756 --- /dev/null +++ b/headers/os/net/UrlProtocolListener.h @@ -0,0 +1,120 @@ +/* + * Copyright 2010 Haiku Inc. All rights reserved. + * Distributed under the terms of the MIT License. + */ +#ifndef _B_URL_PROTOCOL_LISTENER_H_ +#define _B_URL_PROTOCOL_LISTENER_H_ + + +#include +#include + +class BUrlProtocol; + + +enum BUrlProtocolDebugMessage { + B_URL_PROTOCOL_DEBUG_TEXT, + B_URL_PROTOCOL_DEBUG_ERROR, + B_URL_PROTOCOL_DEBUG_HEADER_IN, + B_URL_PROTOCOL_DEBUG_HEADER_OUT, + B_URL_PROTOCOL_DEBUG_TRANSFER_IN, + B_URL_PROTOCOL_DEBUG_TRANSFER_OUT +}; + + +class BUrlProtocolListener { +public: + /* + ConnectionOpened() + Frequency: Once + + Called when the socket is opened. + */ + virtual void ConnectionOpened(BUrlProtocol* caller); + + /* + HostnameResolved(ip) + Frequency: Once + Parameters: ip String representing the IP address of the resource + host. + + Called when the final IP is discovered + */ + virtual void HostnameResolved(BUrlProtocol* caller, + const char* ip); + + /* + ReponseStarted() + Frequency: Once + + Called when the request has been emitted and the server begins to + reply. Typically when the HTTP status code is received. + */ + virtual void ResponseStarted(BUrlProtocol* caller); + + /* + HeadersReceived() + Frequency: Once + + Called when all the server response metadata (such as headers) have + been read and parsed. + */ + virtual void HeadersReceived(BUrlProtocol* caller); + + /* + DataReceived(data, size) + Frequency: Zero or more + Parameters: data Pointer to the data block in memory + size Size of the data block + + Called each time a full block of data is received. + */ + virtual void DataReceived(BUrlProtocol* caller, + const char* data, ssize_t size); + + /* + DownloadProgress(bytesReceived, bytesTotal) + Frequency: Once or more + Parameters: bytesReceived Number of data bytes received + bytesTotal Total number of data bytes expected + + Called each time a data block is received. + */ + virtual void DownloadProgress(BUrlProtocol* caller, + ssize_t bytesReceived, ssize_t bytesTotal); + + /* + UploadProgress(bytesSent, bytesTotal) + Frequency: Once or more + Parameters: bytesSent Number of data bytes sent + bytesTotal Total number of data bytes expected + + Called each time a data block is emitted. + */ + virtual void UploadProgress(BUrlProtocol* caller, + ssize_t bytesSent, ssize_t bytesTotal); + + /* + RequestCompleted(success) + Frequency: Once + Parameters: success true if the resource have been successfully + false if not + + Called once the request is complete. + */ + virtual void RequestCompleted(BUrlProtocol* caller, + bool success); + + /* + DebugMessage(type, text) + Frequency: zero or more + Parameters: type Type of the verbose message (see BUrlProtocolDebug) + + Called each time a debug message is emitted + */ + virtual void DebugMessage(BUrlProtocol* caller, + BUrlProtocolDebugMessage type, + const char* text); +}; + +#endif // _B_URL_PROTOCOL_LISTENER_H_ diff --git a/headers/os/net/UrlRequest.h b/headers/os/net/UrlRequest.h new file mode 100644 index 0000000000..984dfa42e4 --- /dev/null +++ b/headers/os/net/UrlRequest.h @@ -0,0 +1,68 @@ +/* + * Copyright 2010 Haiku Inc. All rights reserved. + * Distributed under the terms of the MIT License. + */ +#ifndef _B_URL_REQUEST_H_ +#define _B_URL_REQUEST_H_ + + +#include +#include +#include +#include +#include +#include + + +enum { + B_NO_HANDLER_FOR_PROTOCOL = B_ERROR +}; + + +class BUrlRequest { +public: + BUrlRequest(const BUrl& url, + BUrlProtocolListener* listener); + BUrlRequest(const BUrl& url); + BUrlRequest(const BUrlRequest& other); + virtual ~BUrlRequest() { }; + + // Request parameters modification + status_t SetUrl(const BUrl& url); + void SetContext(BUrlContext* context); + void SetProtocolListener( + BUrlProtocolListener* listener); + bool SetProtocolOption(int32 option, + void* value); + // Request parameters access + const BUrlProtocol* Protocol(); + const BUrlResult& Result(); + const BUrl& Url(); + + // Request control + status_t Identify(); + virtual status_t Perform(); + // TODO: Rename to Run() perhaps? "Perform" is used for FBC stuff. + virtual status_t Pause(); + virtual status_t Resume(); + virtual status_t Abort(); + + // Request informations + virtual bool InitCheck() const; + bool IsRunning() const; + status_t Status() const; + + // Overloaded members + BUrlRequest& operator=(const BUrlRequest& other); + + +protected: + BUrlProtocolListener* fListener; + BUrlProtocol* fUrlProtocol; + BUrlResult fResult; + BUrlContext* fContext; + BUrl fUrl; + bool fReady; +}; + +#endif // _B_URL_REQUEST_H_ diff --git a/headers/os/net/UrlResult.h b/headers/os/net/UrlResult.h new file mode 100644 index 0000000000..623003526c --- /dev/null +++ b/headers/os/net/UrlResult.h @@ -0,0 +1,56 @@ +/* + * Copyright 2010 Haiku Inc. All rights reserved. + * Distributed under the terms of the MIT License. + */ +#ifndef _B_URL_RESULT_H_ +#define _B_URL_RESULT_H_ + + +#include + +#include +#include +#include +#include + + +class BUrlProtocol; + + +class BUrlResult { + friend class BUrlProtocol; + +public: + BUrlResult(const BUrl& url); + BUrlResult(const BUrlResult& other); + + // Result parameters modifications + void SetUrl(const BUrl& url); + + // Result parameters access + const BUrl& Url() const; + const BMallocIO& RawData() const; + const BHttpHeaders& Headers() const; + const BString& StatusText() const; + int32 StatusCode() const; + + // Result tests + bool HasHeaders() const; + + // Overloaded members + BUrlResult& operator=(const BUrlResult& other); + friend std::ostream& operator<<(std::ostream& out, + const BUrlResult& result); + +private: + BUrl fUrl; + BMallocIO fRawData; + BHttpHeaders fHeaders; + // TODO: HTTP specific stuff should not live here. + + int32 fStatusCode; + BString fStatusString; +}; + + +#endif // _B_URL_RESULT_H_ diff --git a/headers/os/net/UrlSynchronousRequest.h b/headers/os/net/UrlSynchronousRequest.h new file mode 100644 index 0000000000..13f927c296 --- /dev/null +++ b/headers/os/net/UrlSynchronousRequest.h @@ -0,0 +1,43 @@ +/* + * Copyright 2010 Haiku Inc. All rights reserved. + * Distributed under the terms of the MIT License. + */ +#ifndef _B_URL_SYNCHRONOUS_REQUEST_H_ +#define _B_URL_SYNCHRONOUS_REQUEST_H_ + + +#include +#include + + +class BUrlSynchronousRequest : public BUrlRequest, public BUrlProtocolListener { +public: + BUrlSynchronousRequest(BUrl& url); + virtual ~BUrlSynchronousRequest() { }; + + // Synchronous wait + virtual status_t Perform(); + virtual status_t WaitUntilCompletion(); + + // Protocol hooks + virtual void ConnectionOpened(BUrlProtocol* caller); + virtual void HostnameResolved(BUrlProtocol* caller, + const char* ip); + virtual void ResponseStarted(BUrlProtocol* caller); + virtual void HeadersReceived(BUrlProtocol* caller); + virtual void DataReceived(BUrlProtocol* caller, + const char* data, ssize_t size); + virtual void DownloadProgress(BUrlProtocol* caller, + ssize_t bytesReceived, ssize_t bytesTotal); + virtual void UploadProgress(BUrlProtocol* caller, + ssize_t bytesSent, ssize_t bytesTotal); + virtual void RequestCompleted(BUrlProtocol* caller, + bool success); + + +protected: + bool fRequestComplete; +}; + + +#endif // _B_URL_SYNCHRONOUS_REQUEST_H_ diff --git a/headers/private/net/HttpTime.h b/headers/private/net/HttpTime.h new file mode 100644 index 0000000000..cb9527105c --- /dev/null +++ b/headers/private/net/HttpTime.h @@ -0,0 +1,46 @@ +/* + * Copyright 2010 Haiku Inc. All rights reserved. + * Distributed under the terms of the MIT License. + */ +#ifndef _B_HTTP_TIME_H_ +#define _B_HTTP_TIME_H_ + +#include + +#include + +namespace BPrivate { + +enum { + B_HTTP_TIME_FORMAT_PARSED = -1, + B_HTTP_TIME_FORMAT_RFC1123 = 0, + B_HTTP_TIME_FORMAT_PREFERRED = B_HTTP_TIME_FORMAT_RFC1123, + B_HTTP_TIME_FORMAT_COOKIE, + B_HTTP_TIME_FORMAT_RFC1036, + B_HTTP_TIME_FORMAT_ASCTIME +}; + + +class BHttpTime { +public: + BHttpTime(); + BHttpTime(time_t date); + BHttpTime(const BString& dateString); + + // Date modification + void SetString(const BString& string); + void SetDate(time_t date); + + + // Date conversion + time_t Parse(); + BString ToString(int8 format = B_HTTP_TIME_FORMAT_PARSED); + +private: + BString fDateString; + time_t fDate; + int8 fDateFormat; +}; + +} +#endif // _B_HTTP_TIME_H_ diff --git a/src/kits/network/libnetapi/HttpAuthentication.cpp b/src/kits/network/libnetapi/HttpAuthentication.cpp new file mode 100644 index 0000000000..889beeaf66 --- /dev/null +++ b/src/kits/network/libnetapi/HttpAuthentication.cpp @@ -0,0 +1,395 @@ +/* + * Copyright 2010 Haiku Inc. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Christophe Huriaux, c.huriaux@gmail.com + */ + + +#include + +#include +#include + +#include +#define PRINT(x) printf x + + +static const char* kBase64Symbols + = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + + +BHttpAuthentication::BHttpAuthentication() + : + fAuthenticationMethod(B_HTTP_AUTHENTICATION_NONE) +{ +} + + +BHttpAuthentication::BHttpAuthentication(const BString& username, const BString& password) + : + fAuthenticationMethod(B_HTTP_AUTHENTICATION_NONE), + fUserName(username), + fPassword(password) +{ +} + + +// #pragma mark Field modification + + +void +BHttpAuthentication::SetUserName(const BString& username) +{ + fUserName = username; +} + + +void +BHttpAuthentication::SetPassword(const BString& password) +{ + fPassword = password; +} + + +void +BHttpAuthentication::SetMethod(BHttpAuthenticationMethod method) +{ + fAuthenticationMethod = method; +} + + +status_t +BHttpAuthentication::Initialize(const BString& wwwAuthenticate) +{ + fAuthenticationMethod = B_HTTP_AUTHENTICATION_NONE; + fDigestQop = B_HTTP_QOP_NONE; + + if (wwwAuthenticate.Length() == 0) + return B_BAD_VALUE; + + BString authRequired; + BString additionalData; + int32 firstSpace = wwwAuthenticate.FindFirst(' '); + + if (firstSpace == -1) + wwwAuthenticate.CopyInto(authRequired, 0, wwwAuthenticate.Length()); + else { + wwwAuthenticate.CopyInto(authRequired, 0, firstSpace); + wwwAuthenticate.CopyInto(additionalData, firstSpace+1, + wwwAuthenticate.Length()); + } + + authRequired.ToLower(); + if (authRequired == "basic") + fAuthenticationMethod = B_HTTP_AUTHENTICATION_BASIC; + else if (authRequired == "digest") { + fAuthenticationMethod = B_HTTP_AUTHENTICATION_DIGEST; + fDigestAlgorithm = B_HTTP_AUTHENTICATION_ALGORITHM_MD5; + } else + return B_ERROR; + + + while (additionalData.Length()) { + int32 firstColon = additionalData.FindFirst(','); + if (firstColon == -1) + firstColon = additionalData.Length(); + + BString value; + additionalData.MoveInto(value, 0, firstColon); + additionalData.Remove(0, 1); + additionalData.Trim(); + + int32 equal = value.FindFirst('='); + if (equal == -1) + continue; + + BString name; + value.MoveInto(name, 0, equal); + value.Remove(0, 1); + name.ToLower(); + + if (value[0] == '"') { + value.Remove(0, 1); + value.Remove(value.Length()-1, 1); + } + + PRINT(("HttpAuth: name=%s, value=%s\n", name.String(), + value.String())); + + if (name == "realm") + fRealm = value; + else if (name == "nonce") + fDigestNonce = value; + else if (name == "opaque") + fDigestOpaque = value; + else if (name == "stale") { + value.ToLower(); + fDigestStale = (value == "true"); + } else if (name == "algorithm") { + value.ToLower(); + + if (value == "md5") + fDigestAlgorithm = B_HTTP_AUTHENTICATION_ALGORITHM_MD5; + else if (value == "md5-sess") + fDigestAlgorithm = B_HTTP_AUTHENTICATION_ALGORITHM_MD5_SESS; + else + fDigestAlgorithm = B_HTTP_AUTHENTICATION_ALGORITHM_NONE; + } else if (name == "qop") + fDigestQop = B_HTTP_QOP_AUTH; + } + + if (fAuthenticationMethod == B_HTTP_AUTHENTICATION_BASIC) + return B_OK; + else if (fAuthenticationMethod == B_HTTP_AUTHENTICATION_DIGEST + && fDigestNonce.Length() > 0 + && fDigestAlgorithm != B_HTTP_AUTHENTICATION_ALGORITHM_NONE) + return B_OK; + else + return B_ERROR; +} + + +// #pragma mark Field access + + +const BString& +BHttpAuthentication::UserName() const +{ + return fUserName; +} + + +const BString& +BHttpAuthentication::Password() const +{ + return fPassword; +} + + +BHttpAuthenticationMethod +BHttpAuthentication::Method() const +{ + return fAuthenticationMethod; +} + + +BString +BHttpAuthentication::Authorization(const BUrl& url, const BString& method) const +{ + BString authorizationString; + + switch (fAuthenticationMethod) { + case B_HTTP_AUTHENTICATION_NONE: + break; + + case B_HTTP_AUTHENTICATION_BASIC: + { + BString basicEncode; + basicEncode << fUserName << ':' << fPassword; + authorizationString << "Basic " << Base64Encode(basicEncode); + break; + } + + case B_HTTP_AUTHENTICATION_DIGEST: + case B_HTTP_AUTHENTICATION_IE_DIGEST: + authorizationString << "Digest " << "username=\"" << fUserName + << "\", realm=\"" << fRealm << "\", nonce=\"" << fDigestNonce + << "\", algorithm="; + + if (fDigestAlgorithm == B_HTTP_AUTHENTICATION_ALGORITHM_MD5) + authorizationString << "MD5"; + else + authorizationString << "MD5-sess"; + + if (fDigestOpaque.Length() > 0) + authorizationString << ", opaque=\"" << fDigestOpaque << "\""; + + if (fDigestQop != B_HTTP_QOP_NONE) { + if (fDigestCnonce.Length() == 0) { + fDigestCnonce = _H(fDigestOpaque); + //fDigestCnonce = "03c6790a055cbbac"; + fDigestNc = 0; + } + + authorizationString << ", uri=\"" << url.Path() << "\""; + authorizationString << ", qop=auth, cnonce=\"" << fDigestCnonce + << "\""; + + char strNc[9]; + snprintf(strNc, 9, "%08x", ++fDigestNc); + authorizationString << ", nc=" << strNc; + + } + + authorizationString << ", response=\"" + << _DigestResponse(url.Path(), method) << "\""; + break; + } + + return authorizationString; +} + + +// #pragma mark Base64 encoding + + +/*static*/ BString +BHttpAuthentication::Base64Encode(const BString& string) +{ + BString result; + BString tmpString = string; + + while (tmpString.Length()) { + char in[3] = { 0, 0, 0 }; + char out[4] = { 0, 0, 0, 0 }; + int8 remaining = tmpString.Length(); + + tmpString.MoveInto(in, 0, 3); + + out[0] = (in[0] & 0xFC) >> 2; + out[1] = ((in[0] & 0x03) << 4) | ((in[1] & 0xF0) >> 4); + out[2] = ((in[1] & 0x0F) << 2) | ((in[2] & 0xC0) >> 6); + out[3] = in[2] & 0x3F; + + for (int i = 0; i < 4; i++) + out[i] = kBase64Symbols[(int)out[i]]; + + // Add padding if the input length is not a multiple + // of 3 + switch (remaining) { + case 1: + out[2] = '='; + // Fall through + case 2: + out[3] = '='; + break; + } + + result.Append(out, 4); + } + + return result; +} + + +/*static*/ BString +BHttpAuthentication::Base64Decode(const BString& string) +{ + BString base64Reverse(kBase64Symbols); + BString result; + + // Invalid input + if (string.Length() % 4 != 0) + return BString(""); + + + BString tmpString(string); + while (tmpString.Length()) { + char in[4] = { 0, 0, 0, 0 }; + char out[3] = { 0, 0, 0 }; + + tmpString.MoveInto(in, 0, 4); + + for (int i = 0; i < 4; i++) { + if (in[i] == '=') + in[i] = 0; + else + in[i] = base64Reverse.FindFirst(in[i], 0); + } + + out[0] = (in[0] << 2) | ((in[1] & 0x30) >> 4); + out[1] = ((in[1] & 0x0F) << 4) | ((in[2] & 0x3C) >> 2); + out[2] = ((in[2] & 0x03) << 6) | in[3]; + + result.Append(out, 3); + } + + return result; +} + + +BString +BHttpAuthentication::_DigestResponse(const BString& uri, const BString& method) const +{ + PRINT(("HttpAuth: Computing digest response: \n")); + PRINT(("HttpAuth: > username = %s\n", fUserName.String())); + PRINT(("HttpAuth: > password = %s\n", fPassword.String())); + PRINT(("HttpAuth: > realm = %s\n", fRealm.String())); + PRINT(("HttpAuth: > nonce = %s\n", fDigestNonce.String())); + PRINT(("HttpAuth: > cnonce = %s\n", fDigestCnonce.String())); + PRINT(("HttpAuth: > nc = %08x\n", fDigestNc)); + PRINT(("HttpAuth: > uri = %s\n", uri.String())); + PRINT(("HttpAuth: > method = %s\n", method.String())); + PRINT(("HttpAuth: > algorithm = %d (MD5:%d, MD5-sess:%d)\n", + fDigestAlgorithm, B_HTTP_AUTHENTICATION_ALGORITHM_MD5, + B_HTTP_AUTHENTICATION_ALGORITHM_MD5_SESS)); + + BString A1; + A1 << fUserName << ':' << fRealm << ':' << fPassword; + + if (fDigestAlgorithm == B_HTTP_AUTHENTICATION_ALGORITHM_MD5_SESS) { + A1 = _H(A1); + A1 << ':' << fDigestNonce << ':' << fDigestCnonce; + } + + + BString A2; + A2 << method << ':' << uri; + + PRINT(("HttpAuth: > A1 = %s\n", A1.String())); + PRINT(("HttpAuth: > A2 = %s\n", A2.String())); + PRINT(("HttpAuth: > H(A1) = %s\n", _H(A1).String())); + PRINT(("HttpAuth: > H(A2) = %s\n", _H(A2).String())); + + char strNc[9]; + snprintf(strNc, 9, "%08x", fDigestNc); + + BString secretResp; + secretResp << fDigestNonce << ':' << strNc << ':' << fDigestCnonce + << ":auth:" << _H(A2); + + PRINT(("HttpAuth: > R2 = %s\n", secretResp.String())); + + BString response = _KD(_H(A1), secretResp); + PRINT(("HttpAuth: > response = %s\n", response.String())); + + return response; +} + + +BString +BHttpAuthentication::_H(const BString& value) const +{ + unsigned char* hashResult + = MD5(reinterpret_cast(value.String()), + value.Length(), NULL); + + BString result; + // TODO: This is slower than it needs to be. If we already know the + // final hash string length, we can use + // BString::LockBuffer(MD5_DIGEST_LENGTH * 2) to preallocate it. + // I am not making the change since I can not test it right now (stippi). + for (int i = 0; i < MD5_DIGEST_LENGTH; i++) { + char c = ((hashResult[i] & 0xF0) >> 4); + c += (c > 9) ? 'a' - 10 : '0'; + result << c; + + c = hashResult[i] & 0x0F; + c += (c > 9) ? 'a' - 10 : '0'; + result << c; + } + + return result; +} + + +BString +BHttpAuthentication::_KD(const BString& secret, const BString& data) const +{ + BString encode; + encode << secret << ':' << data; + + return _H(encode); +} diff --git a/src/kits/network/libnetapi/HttpForm.cpp b/src/kits/network/libnetapi/HttpForm.cpp new file mode 100644 index 0000000000..030abe5143 --- /dev/null +++ b/src/kits/network/libnetapi/HttpForm.cpp @@ -0,0 +1,781 @@ +/* + * Copyright 2010 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; + + +// #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; +} + + +status_t +BHttpFormData::MarkAsFile(const BString& filename) +{ + return MarkAsFile(filename, ""); +} + + +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 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&) + : fType(B_HTTP_FORM_URL_ENCODED) +{ +} + + +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) +{ + 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; +} diff --git a/src/kits/network/libnetapi/HttpHeaders.cpp b/src/kits/network/libnetapi/HttpHeaders.cpp new file mode 100644 index 0000000000..72ee4b6608 --- /dev/null +++ b/src/kits/network/libnetapi/HttpHeaders.cpp @@ -0,0 +1,315 @@ +/* + * Copyright 2010 Haiku Inc. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Christophe Huriaux, c.huriaux@gmail.com + */ + + +#include +#include + +#include +#include + + +// #pragma mark -- BHttpHeader + + +BHttpHeader::BHttpHeader() + : + fName(), + fValue(), + fRawHeader(), + fRawHeaderValid(true) +{ +} + + +BHttpHeader::BHttpHeader(const char* string) + : + fRawHeaderValid(true) +{ + SetHeader(string); +} + + +BHttpHeader::BHttpHeader(const char* name, const char* value) + : + fName(name), + fValue(value), + fRawHeaderValid(false) +{ +} + + +BHttpHeader::BHttpHeader(const BHttpHeader& copy) + : + fName(copy.fName), + fValue(copy.fValue), + fRawHeaderValid(false) +{ +} + + +void +BHttpHeader::SetName(const char* name) +{ + fRawHeaderValid = false; + fName = name; +} + + +void +BHttpHeader::SetValue(const char* value) +{ + fRawHeaderValid = false; + fValue = value; +} + + +bool +BHttpHeader::SetHeader(const char* string) +{ + BString strLine(string); + + fRawHeaderValid = false; + fName.Truncate(0); + fValue.Truncate(0); + + int32 separatorLocation = strLine.FindFirst(": "); + + if (separatorLocation == B_ERROR) + return false; + + strLine.MoveInto(fName, 0, separatorLocation); + strLine.Remove(0, 2); + fValue = strLine; + + return true; +} + + +const char* +BHttpHeader::Name() const +{ + return fName.String(); +} + + +const char* +BHttpHeader::Value() const +{ + return fValue.String(); +} + + +const char* +BHttpHeader::Header() const +{ + if (!fRawHeaderValid) { + fRawHeaderValid = true; + + fRawHeader.Truncate(0); + fRawHeader << fName << ": " << fValue; + } + + return fRawHeader.String(); +} + + +bool +BHttpHeader::NameIs(const char* name) const +{ + return fName == BString(name); +} + + +BHttpHeader& +BHttpHeader::operator=(const BHttpHeader& other) +{ + fName = other.fName; + fValue = other.fValue; + fRawHeaderValid = false; + + return *this; +} + + +// #pragma mark -- BHttpHeaders + + +BHttpHeaders::BHttpHeaders() + : + fHeaderList() +{ +} + + +BHttpHeaders::BHttpHeaders(const BHttpHeaders& copy) + : + fHeaderList() +{ + for (int32 i = 0; i < copy.CountHeaders(); i++) + AddHeader(copy.HeaderAt(i).Name(), copy.HeaderAt(i).Value()); +} + + +BHttpHeaders::~BHttpHeaders() +{ + _EraseData(); +} + + +// #pragma mark Header access + + +const char* +BHttpHeaders::HeaderValue(const char* name) const +{ + for (int32 i = 0; i < fHeaderList.CountItems(); i++) { + BHttpHeader* header + = reinterpret_cast(fHeaderList.ItemAtFast(i)); + + if (header->NameIs(name)) + return header->Value(); + } + + return NULL; +} + + +BHttpHeader& +BHttpHeaders::HeaderAt(int32 index) const +{ + //! Note: index _must_ be in-bounds + BHttpHeader* header + = reinterpret_cast(fHeaderList.ItemAtFast(index)); + + return *header; +} + + +// #pragma mark Header count + + +int32 +BHttpHeaders::CountHeaders() const +{ + return fHeaderList.CountItems(); +} + + +// #pragma Header tests + + +int32 +BHttpHeaders::HasHeader(const char* name) const +{ + for (int32 i = 0; i < fHeaderList.CountItems(); i++) { + BHttpHeader* header + = reinterpret_cast(fHeaderList.ItemAt(i)); + + if (header->NameIs(name)) + return i; + } + + return B_ERROR; +} + + +// #pragma mark Header add/replace + + +bool +BHttpHeaders::AddHeader(const char* line) +{ + BHttpHeader* heapHeader = new(std::nothrow) BHttpHeader(line); + + if (heapHeader != NULL) { + fHeaderList.AddItem(heapHeader); + return true; + } + + return false; +} + + +bool +BHttpHeaders::AddHeader(const char* name, const char* value) +{ + BHttpHeader* heapHeader = new(std::nothrow) BHttpHeader(name, value); + + if (heapHeader != NULL) { + fHeaderList.AddItem(heapHeader); + return true; + } + + return false; +} + + +bool +BHttpHeaders::AddHeader(const char* name, int32 value) +{ + BString strValue; + strValue << value; + + return AddHeader(name, strValue); +} + + +// #pragma mark Header deletion + + +void +BHttpHeaders::Clear() +{ + _EraseData(); + fHeaderList.MakeEmpty(); +} + + +// #pragma mark Overloaded operators + + +BHttpHeaders& +BHttpHeaders::operator=(const BHttpHeaders& other) +{ + for (int32 i = 0; i < other.CountHeaders(); i++) + AddHeader(other.HeaderAt(i).Name(), other.HeaderAt(i).Value()); + + return *this; +} + + +BHttpHeader& +BHttpHeaders::operator[](int32 index) const +{ + //! Note: Index _must_ be in-bounds + BHttpHeader* header + = reinterpret_cast(fHeaderList.ItemAtFast(index)); + + return *header; +} + + +const char* +BHttpHeaders::operator[](const char* name) const +{ + return HeaderValue(name); +} + + +void +BHttpHeaders::_EraseData() +{ + // Free allocated data; + for (int32 i = 0; i < fHeaderList.CountItems(); i++) { + BHttpHeader* header + = reinterpret_cast(fHeaderList.ItemAtFast(i)); + + delete header; + } +} diff --git a/src/kits/network/libnetapi/HttpTime.cpp b/src/kits/network/libnetapi/HttpTime.cpp new file mode 100644 index 0000000000..067a1f7b26 --- /dev/null +++ b/src/kits/network/libnetapi/HttpTime.cpp @@ -0,0 +1,132 @@ +/* + * Copyright 2010 Haiku Inc. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Christophe Huriaux, c.huriaux@gmail.com + */ + +#include + +#include + +#include + + +#define PRINT(x) printf x + + +static const char* kRfc1123Format = "%a, %d %b %Y %H:%M:%S GMT"; +static const char* kCookieFormat = "%a, %d-%b-%Y %H:%M:%S GMT"; +static const char* kRfc1036Format = "%A, %d-%b-%y %H:%M:%S GMT"; +static const char* kAscTimeFormat = "%a %d %b %H:%M:%S %Y"; +static const uint16 kTimetToStringMaxLength = 128; + +using namespace BPrivate; + + +BHttpTime::BHttpTime() + : + fDate(0), + fDateFormat(B_HTTP_TIME_FORMAT_PREFERRED) +{ +} + + +BHttpTime::BHttpTime(time_t date) + : + fDate(date), + fDateFormat(B_HTTP_TIME_FORMAT_PREFERRED) +{ +} + + +BHttpTime::BHttpTime(const BString& dateString) + : + fDateString(dateString), + fDate(0), + fDateFormat(B_HTTP_TIME_FORMAT_PREFERRED) +{ +} + + +// #pragma mark Date modification + + +void +BHttpTime::SetString(const BString& string) +{ + fDateString = string; +} + + +void +BHttpTime::SetDate(time_t date) +{ + fDate = date; +} + + +// #pragma mark Date conversion + + +time_t +BHttpTime::Parse() +{ + struct tm expireTime; + + if (fDateString.Length() < 4) + return 0; + + if (fDateString[3] == ',') { + if (strptime(fDateString.String(), kRfc1123Format, &expireTime) + == NULL) { + strptime(fDateString.String(), kCookieFormat, &expireTime); + fDateFormat = B_HTTP_TIME_FORMAT_COOKIE; + } else + fDateFormat = B_HTTP_TIME_FORMAT_RFC1123; + } else if (fDateString[3] == ' ') { + strptime(fDateString.String(), kRfc1036Format, &expireTime); + fDateFormat = B_HTTP_TIME_FORMAT_RFC1036; + } else { + strptime(fDateString.String(), kAscTimeFormat, &expireTime); + fDateFormat = B_HTTP_TIME_FORMAT_ASCTIME; + } + +// return timegm(&expireTime); +// TODO: The above was used initially. See http://en.wikipedia.org/wiki/Time.h +// stippi: I don't know how Christophe had this code compiling initially, +// since Haiku does not appear to implement timegm(). +return mktime(&expireTime); +} + + +BString +BHttpTime::ToString(int8 format) +{ + BString expirationFinal; + struct tm* expirationTm = localtime(&fDate); + char expirationString[kTimetToStringMaxLength + 1]; + size_t strLength; + + switch ((format == B_HTTP_TIME_FORMAT_PARSED)?fDateFormat:format) { + default: + case B_HTTP_TIME_FORMAT_RFC1123: + strLength = strftime(expirationString, kTimetToStringMaxLength, + kRfc1123Format, expirationTm); + break; + + case B_HTTP_TIME_FORMAT_RFC1036: + strLength = strftime(expirationString, kTimetToStringMaxLength, + kRfc1036Format, expirationTm); + break; + + case B_HTTP_TIME_FORMAT_ASCTIME: + strLength = strftime(expirationString, kTimetToStringMaxLength, + kAscTimeFormat, expirationTm); + break; + } + + expirationFinal.SetTo(expirationString, strLength); + return expirationFinal; +} diff --git a/src/kits/network/libnetapi/Jamfile b/src/kits/network/libnetapi/Jamfile index 2ffee488ba..33a5544a4e 100644 --- a/src/kits/network/libnetapi/Jamfile +++ b/src/kits/network/libnetapi/Jamfile @@ -12,8 +12,31 @@ SharedLibrary libbnetapi.so : NetworkAddress.cpp NetworkAddressResolver.cpp + NetworkCookie.cpp + NetworkCookieJar.cpp NetworkInterface.cpp NetworkRoster.cpp - : be $(TARGET_NETWORK_LIBS) $(TARGET_LIBSUPC++) + # TODO: The HTTP stuff should all go into an add-on. It needs linking + # against libcrypto.so and only the add-on should link against it. + # Building of the commented out files has not been completely tested after + # integrating the code from the GSoC 2010 "Services Kit" project and doing + # some renaming of types, constants and methods. +# HttpAuthentication.cpp +# HttpHeaders.cpp +# HttpForm.cpp + HttpTime.cpp + + Url.cpp + UrlContext.cpp +# UrlProtocol.cpp +# UrlProtocolAsynchronousListener.cpp +# UrlProtocolDispatchingListener.cpp +# UrlProtocolHttp.cpp # TODO: -> add-on, See above. +# UrlProtocolListener.cpp +# UrlRequest.cpp +# UrlResult.cpp +# UrlSynchronousRequest.cpp + + : be $(TARGET_NETWORK_LIBS) $(TARGET_LIBSUPC++) libshared.a ; diff --git a/src/kits/network/libnetapi/NetworkCookie.cpp b/src/kits/network/libnetapi/NetworkCookie.cpp new file mode 100644 index 0000000000..16fad675fa --- /dev/null +++ b/src/kits/network/libnetapi/NetworkCookie.cpp @@ -0,0 +1,813 @@ +/* + * Copyright 2010 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 +#define PRINT(x) printf x; + +using BPrivate::BHttpTime; + +static const char* kArchivedCookieComment = "be:cookie.comment"; +static const char* kArchivedCookieCommentUrl = "be:cookie.commenturl"; +static const char* kArchivedCookieDiscard = "be:cookie.discard"; +static const char* kArchivedCookieDomain = "be:cookie.domain"; +static const char* kArchivedCookieExpirationDate = "be:cookie.expiredate"; +static const char* kArchivedCookiePath = "be:cookie.path"; +static const char* kArchivedCookieSecure = "be:cookie.secure"; +static const char* kArchivedCookieVersion = "be:cookie.version"; +static const char* kArchivedCookieName = "be:cookie.name"; +static const char* kArchivedCookieValue = "be:cookie.value"; + + +BNetworkCookie::BNetworkCookie(const char* name, const char* value) + : + fDiscard(false), + fExpiration(BDateTime::CurrentDateTime(B_GMT_TIME)), + fVersion(0), + fName(name), + fValue(value), + fSessionCookie(true) +{ + _Reset(); +} + + +BNetworkCookie::BNetworkCookie(const BNetworkCookie& other) + : + BArchivable(), + fDiscard(false), + fExpiration(BDateTime::CurrentDateTime(B_GMT_TIME)), + fVersion(0), + fSessionCookie(true) +{ + _Reset(); + *this = other; +} + + +BNetworkCookie::BNetworkCookie(const BString& cookieString) + : + fDiscard(false), + fExpiration(BDateTime::CurrentDateTime(B_GMT_TIME)), + fVersion(0), + fSessionCookie(true) +{ + _Reset(); + ParseCookieString(cookieString); +} + + +BNetworkCookie::BNetworkCookie(const BString& cookieString, + const BUrl& url) + : + fDiscard(false), + fExpiration(BDateTime::CurrentDateTime(B_GMT_TIME)), + fVersion(0), + fSessionCookie(true) +{ + _Reset(); + ParseCookieStringFromUrl(cookieString, url); +} + + +BNetworkCookie::BNetworkCookie(BMessage* archive) + : + fDiscard(false), + fExpiration(BDateTime::CurrentDateTime(B_GMT_TIME)), + fVersion(0), + fSessionCookie(true) +{ + _Reset(); + + archive->FindString(kArchivedCookieName, &fName); + archive->FindString(kArchivedCookieValue, &fValue); + + archive->FindString(kArchivedCookieComment, &fComment); + archive->FindString(kArchivedCookieCommentUrl, &fCommentUrl); + archive->FindString(kArchivedCookieDomain, &fDomain); + archive->FindString(kArchivedCookiePath, &fPath); + archive->FindBool(kArchivedCookieSecure, &fSecure); + + if (archive->FindBool(kArchivedCookieDiscard, &fDiscard) == B_OK) + fHasDiscard = true; + + if (archive->FindInt8(kArchivedCookieVersion, &fVersion) == B_OK) + fHasVersion = true; + + int32 expiration; + if (archive->FindInt32(kArchivedCookieExpirationDate, &expiration) + == B_OK) { + SetExpirationDate((time_t)expiration); + } +} + + +BNetworkCookie::BNetworkCookie() + : + fDiscard(false), + fExpiration(BDateTime::CurrentDateTime(B_GMT_TIME)), + fPath("/"), + fVersion(0), + fSessionCookie(true) +{ + _Reset(); +} + + +BNetworkCookie::~BNetworkCookie() +{ +} + + +// #pragma mark String to cookie fields + + +BNetworkCookie& +BNetworkCookie::ParseCookieStringFromUrl(const BString& string, + const BUrl& url) +{ + BString cookieString(string); + int16 index = 0; + + _Reset(); + + // Default values from url + SetDomain(url.Host()); + SetPath(url.Path()); + + _ExtractNameValuePair(cookieString, &index); + + while (index < cookieString.Length()) + _ExtractNameValuePair(cookieString, &index, true); + + return *this; +} + + +BNetworkCookie& +BNetworkCookie::ParseCookieString(const BString& string) +{ + BUrl url; + ParseCookieStringFromUrl(string, url); + return *this; +} + + +// #pragma mark Cookie fields modification + + +BNetworkCookie& +BNetworkCookie::SetComment(const BString& comment) +{ + fComment = comment; + fRawFullCookieValid = false; + return *this; +} + + +BNetworkCookie& +BNetworkCookie::SetCommentUrl(const BString& commentUrl) +{ + fCommentUrl = commentUrl; + fRawFullCookieValid = false; + return *this; +} + + +BNetworkCookie& +BNetworkCookie::SetDiscard(bool discard) +{ + fDiscard = discard; + fHasDiscard = true; + fRawFullCookieValid = false; + return *this; +} + + +BNetworkCookie& +BNetworkCookie::SetDomain(const BString& domain) +{ + fDomain = domain; + + // We always use pre-dotted domains for tail matching + if (fDomain.ByteAt(0) != '.') + fDomain.Prepend("."); + + fRawFullCookieValid = false; + return *this; +} + + +BNetworkCookie& +BNetworkCookie::SetMaxAge(int32 maxAge) +{ + BDateTime expiration = BDateTime::CurrentDateTime(B_GMT_TIME); + expiration.Time().AddSeconds(maxAge); + return SetExpirationDate(expiration); +} + + +BNetworkCookie& +BNetworkCookie::SetExpirationDate(time_t expireDate) +{ + BDateTime expiration; + expiration.SetTime_t(expireDate); + return SetExpirationDate(expiration); +} + + +BNetworkCookie& +BNetworkCookie::SetExpirationDate(BDateTime& expireDate) +{ + if (expireDate.Time_t() <= 0) { + fExpiration.SetTime_t(0); + fSessionCookie = true; + fExpirationStringValid = false; + fRawFullCookieValid = false; + fHasExpirationDate = false; + } else { + fExpiration = expireDate; + fSessionCookie = false; + fExpirationStringValid = false; + fRawFullCookieValid = false; + fHasExpirationDate = true; + } + + return *this; +} + + +BNetworkCookie& +BNetworkCookie::SetPath(const BString& path) +{ + fPath = path; + fRawFullCookieValid = false; + return *this; +} + + +BNetworkCookie& +BNetworkCookie::SetSecure(bool secure) +{ + fSecure = secure; + fRawFullCookieValid = false; + return *this; +} + + +BNetworkCookie& +BNetworkCookie::SetVersion(int8 version) +{ + fVersion = version; + fHasVersion = true; + fRawCookieValid = false; + return *this; +} + + +BNetworkCookie& +BNetworkCookie::SetName(const BString& name) +{ + fName = name; + fRawFullCookieValid = false; + fRawCookieValid = false; + return *this; +} + + +BNetworkCookie& +BNetworkCookie::SetValue(const BString& value) +{ + fValue = value; + fRawFullCookieValid = false; + fRawCookieValid = false; + return *this; +} + + +// #pragma mark Cookie fields access + + +const BString& +BNetworkCookie::Comment() const +{ + return fComment; +} + + +const BString& +BNetworkCookie::CommentUrl() const +{ + return fCommentUrl; +} + + +bool +BNetworkCookie::Discard() const +{ + return fDiscard; +} + + +const BString& +BNetworkCookie::Domain() const +{ + return fDomain; +} + + +int32 +BNetworkCookie::MaxAge() const +{ + return fExpiration.Time_t() - BDateTime::CurrentDateTime(B_GMT_TIME).Time_t(); +} + + +time_t +BNetworkCookie::ExpirationDate() const +{ + return fExpiration.Time_t(); +} + + +const BString& +BNetworkCookie::ExpirationString() const +{ + BHttpTime date(ExpirationDate()); + + if (!fExpirationStringValid) { + fExpirationString = date.ToString(BPrivate::B_HTTP_TIME_FORMAT_COOKIE); + fExpirationStringValid = true; + } + + return fExpirationString; +} + + +const BString& +BNetworkCookie::Path() const +{ + return fPath; +} + + +bool +BNetworkCookie::Secure() const +{ + return fSecure; +} + + +int8 +BNetworkCookie::Version() const +{ + return fVersion; +} + + +const BString& +BNetworkCookie::Name() const +{ + return fName; +} + + +const BString& +BNetworkCookie::Value() const +{ + return fValue; +} + + +const BString& +BNetworkCookie::RawCookie(bool full) const +{ + if (full && !fRawFullCookieValid) { + fRawFullCookie.Truncate(0); + fRawFullCookieValid = true; + + fRawFullCookie << fName << "=" << fValue; + + if (HasCommentUrl()) + fRawFullCookie << "; Comment-Url=" << fCommentUrl; + if (HasComment()) + fRawFullCookie << "; Comment=" << fComment; + if (HasDiscard()) + fRawFullCookie << "; Discard=" << (fDiscard?"true":"false"); + if (HasDomain()) + fRawFullCookie << "; Domain=" << fDomain; + if (HasExpirationDate()) + fRawFullCookie << "; Max-Age=" << MaxAge(); +// fRawFullCookie << "; Expires=" << ExpirationString(); + if (HasPath()) + fRawFullCookie << "; Path=" << fPath; + if (Secure() && fSecure) + fRawFullCookie << "; Secure=" << (fSecure?"true":"false"); + if (HasVersion()) + fRawFullCookie << ", Version=" << fVersion; + + } else if (!full && !fRawCookieValid) { + fRawCookie.Truncate(0); + fRawCookieValid = true; + + fRawCookie << fName << "=" << fValue; + } + + return full?fRawFullCookie:fRawCookie; +} + + +// #pragma mark Cookie test + + +bool +BNetworkCookie::IsSessionCookie() const +{ + return fSessionCookie; +} + + +bool +BNetworkCookie::IsValid(bool strict) const +{ + return HasName() && HasValue() && (!strict || HasVersion()); +} + + +bool +BNetworkCookie::IsValidForUrl(const BUrl& url) const +{ + BString urlHost = url.Host(); + BString urlPath = url.Path(); + + return IsValidForDomain(urlHost) && IsValidForPath(urlPath); +} + + +bool +BNetworkCookie::IsValidForDomain(const BString& domain) const +{ + if (fDomain.Length() > domain.Length()) + return false; + + return domain.FindLast(fDomain) == (domain.Length() - fDomain.Length()); +} + + +bool +BNetworkCookie::IsValidForPath(const BString& path) const +{ + if (fPath.Length() > path.Length()) + return false; + + return path.FindFirst(fPath) == 0; +} + + +// #pragma mark Cookie fields existence tests + + +bool +BNetworkCookie::HasCommentUrl() const +{ + return fCommentUrl.Length() > 0; +} + + +bool +BNetworkCookie::HasComment() const +{ + return fComment.Length() > 0; +} + + +bool +BNetworkCookie::HasDiscard() const +{ + return fHasDiscard; +} + + +bool +BNetworkCookie::HasDomain() const +{ + return fDomain.Length() > 0; +} + + +bool +BNetworkCookie::HasPath() const +{ + return fPath.Length() > 0; +} + + +bool +BNetworkCookie::HasVersion() const +{ + return fHasVersion; +} + + +bool +BNetworkCookie::HasName() const +{ + return fName.Length() > 0; +} + + +bool +BNetworkCookie::HasValue() const +{ + return fValue.Length() > 0; +} + + +bool +BNetworkCookie::HasExpirationDate() const +{ + return fHasExpirationDate; +} + + +// #pragma mark Cookie delete test + + +bool +BNetworkCookie::ShouldDeleteAtExit() const +{ + return (HasDiscard() && Discard()) + || (!IsSessionCookie() && ShouldDeleteNow()) + || IsSessionCookie(); +} + + +bool +BNetworkCookie::ShouldDeleteNow() const +{ + if (!IsSessionCookie() && HasExpirationDate()) + return (BDateTime::CurrentDateTime(B_GMT_TIME) > fExpiration); + + return false; +} + + +// #pragma mark BArchivable members + + +status_t +BNetworkCookie::Archive(BMessage* into, bool deep) const +{ + status_t error = BArchivable::Archive(into, deep); + + if (error != B_OK) + return error; + + error = into->AddString(kArchivedCookieName, fName); + if (error != B_OK) + return error; + + error = into->AddString(kArchivedCookieValue, fValue); + if (error != B_OK) + return error; + + + // We add optional fields only if they're defined + if (HasComment()) { + error = into->AddString(kArchivedCookieComment, fComment); + if (error != B_OK) + return error; + } + + if (HasCommentUrl()) { + error = into->AddString(kArchivedCookieCommentUrl, fCommentUrl); + if (error != B_OK) + return error; + } + + if (HasDiscard()) { + error = into->AddBool(kArchivedCookieDiscard, fDiscard); + if (error != B_OK) + return error; + } + + if (HasDomain()) { + error = into->AddString(kArchivedCookieDomain, fDomain); + if (error != B_OK) + return error; + } + + if (fHasExpirationDate) { + error = into->AddInt32(kArchivedCookieExpirationDate, + fExpiration.Time_t()); + if (error != B_OK) + return error; + } + + if (HasPath()) { + error = into->AddString(kArchivedCookiePath, fPath); + if (error != B_OK) + return error; + } + + if (Secure()) { + error = into->AddBool(kArchivedCookieSecure, fSecure); + if (error != B_OK) + return error; + } + + if (HasVersion()) { + error = into->AddInt8(kArchivedCookieVersion, fVersion); + if (error != B_OK) + return error; + } + + return B_OK; +} + + +/*static*/ BArchivable* +BNetworkCookie::Instantiate(BMessage* archive) +{ + if (archive->HasString(kArchivedCookieName) + && archive->HasString(kArchivedCookieValue)) + return new(std::nothrow) BNetworkCookie(archive); + + return NULL; +} + + +// #pragma mark Overloaded operators + + +BNetworkCookie& +BNetworkCookie::operator=(const BNetworkCookie& other) +{ + // Should we prefer to discard the cache ? + fRawCookie = other.fRawCookie; + fRawCookieValid = other.fRawCookieValid; + fRawFullCookie = other.fRawFullCookie; + fRawFullCookieValid = other.fRawFullCookieValid; + fExpirationString = other.fExpirationString; + fExpirationStringValid = other.fExpirationStringValid; + + fComment = other.fComment; + fCommentUrl = other.fCommentUrl; + fDiscard = other.fDiscard; + fDomain = other.fDomain; + fExpiration = other.fExpiration; + fPath = other.fPath; + fSecure = other.fSecure; + fVersion = other.fVersion; + fName = other.fName; + fValue = other.fValue; + + fHasDiscard = other.fHasDiscard; + fHasExpirationDate = other.fHasExpirationDate; + fSessionCookie = other.fSessionCookie; + fHasVersion = other.fHasVersion; + + return *this; +} + + +BNetworkCookie& +BNetworkCookie::operator=(const char* string) +{ + return ParseCookieString(string); +} + + +bool +BNetworkCookie::operator==(const BNetworkCookie& other) +{ + // Equality : name and values equals + return fName == other.fName && fValue == other.fValue; +} + + +bool +BNetworkCookie::operator!=(const BNetworkCookie& other) +{ + return !(*this == other); +} + + +void +BNetworkCookie::_Reset() +{ + fComment.Truncate(0); + fCommentUrl.Truncate(0); + fDomain.Truncate(0); + fPath.Truncate(0); + fName.Truncate(0); + fValue.Truncate(0); + fDiscard = false; + fSecure = false; + fVersion = 0; + fExpiration = 0; + + fHasDiscard = false; + fHasExpirationDate = false; + fSessionCookie = true; + fHasVersion = false; + + fRawCookieValid = false; + fRawFullCookieValid = false; + fExpirationStringValid = false; +} + + +void +BNetworkCookie::_ExtractNameValuePair(const BString& cookieString, + int16* index, bool parseField) +{ + // Skip whitespaces + while (cookieString.ByteAt(*index) == ' ' + && *index < cookieString.Length()) + (*index)++; + + if (*index >= cookieString.Length()) + return; + + + // Look for a name=value pair + int16 firstSemiColon = cookieString.FindFirst(";", *index); + int16 firstEqual = cookieString.FindFirst("=", *index); + + BString name; + BString value; + + if (firstSemiColon == -1) { + if (firstEqual != -1) { + cookieString.CopyInto(name, *index, firstEqual - *index); + cookieString.CopyInto(value, firstEqual + 1, + cookieString.Length() - firstEqual - 1); + } else + cookieString.CopyInto(value, *index, + cookieString.Length() - *index); + + *index = cookieString.Length() + 1; + } else { + if (firstEqual != -1 && firstEqual < firstSemiColon) { + cookieString.CopyInto(name, *index, firstEqual - *index); + cookieString.CopyInto(value, firstEqual + 1, + firstSemiColon - firstEqual - 1); + } else + cookieString.CopyInto(value, *index, firstSemiColon - *index); + + *index = firstSemiColon + 1; + } + + // Cookie name/value pair + if (!parseField) { + SetName(name); + SetValue(value); + return; + } + + name.ToLower(); + name.Trim(); + value.Trim(); + + // Cookie comment + if (name == "comment") + SetComment(value); + // Cookie comment URL + else if (name == "comment-url") + SetCommentUrl(value); + // Cookie discard flag + else if (name == "discard") + SetDiscard(value.Length() == 0 || value.ToLower() == "true"); + // Cookie max-age + else if (name == "maxage") + SetMaxAge(atoi(value.String())); + // Cookie expiration date + else if (name == "expires") { + BHttpTime date(value); + SetExpirationDate(date.Parse()); + // Cookie valid domain + } else if (name == "domain") + SetDomain(value); + // Cookie valid path + else if (name == "path") + SetPath(value); + // Cookie secure flag + else if (name == "secure") + SetSecure(value.Length() == 0 || value.ToLower() == "true"); + // Cookie version + else if (name == "version") + SetVersion(atoi(value.String())); +} diff --git a/src/kits/network/libnetapi/NetworkCookieJar.cpp b/src/kits/network/libnetapi/NetworkCookieJar.cpp new file mode 100644 index 0000000000..eef91d110b --- /dev/null +++ b/src/kits/network/libnetapi/NetworkCookieJar.cpp @@ -0,0 +1,683 @@ +/* + * Copyright 2010 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 "NetworkCookieJarPrivate.h" + +const char* kArchivedCookieMessageName = "be:cookie"; + + +BNetworkCookieJar::BNetworkCookieJar() + : + fCookieHashMap(new PrivateHashMap) +{ +} + + +BNetworkCookieJar::BNetworkCookieJar(const BNetworkCookieJar&) + : + BArchivable(), + fCookieHashMap(new PrivateHashMap) +{ + // TODO +} + + +BNetworkCookieJar::BNetworkCookieJar(const BNetworkCookieList& otherList) + : + fCookieHashMap(new PrivateHashMap) +{ + AddCookies(otherList); +} + + +BNetworkCookieJar::BNetworkCookieJar(BMessage* archive) + : + fCookieHashMap(new PrivateHashMap) +{ + BMessage extractedCookie; + + for (int32 i = 0; + archive->FindMessage(kArchivedCookieMessageName, i, &extractedCookie) + == B_OK; + i++) { + BNetworkCookie* heapCookie + = new(std::nothrow) BNetworkCookie(&extractedCookie); + + if (heapCookie == NULL || !AddCookie(heapCookie)) + break; + } +} + + +BNetworkCookieJar::~BNetworkCookieJar() +{ + BNetworkCookie* cookiePtr; + + for (Iterator it(GetIterator()); (cookiePtr = it.Next()); ) + delete it.Remove(); +} + + +// #pragma mark Add cookie to cookie jar + + +bool +BNetworkCookieJar::AddCookie(const BNetworkCookie& cookie) +{ + BNetworkCookie* heapCookie = new(std::nothrow) BNetworkCookie(cookie); + + if (!AddCookie(heapCookie)) { + delete heapCookie; + return false; + } + + return true; +} + + +bool +BNetworkCookieJar::AddCookie(BNetworkCookie* cookie) +{ + if (cookie != NULL) { + HashString key(cookie->Domain()); + + if (!fCookieHashMap->fHashMap.ContainsKey(key)) + fCookieHashMap->fHashMap.Put(key, new BList); + + BNetworkCookieList* list = fCookieHashMap->fHashMap.Get(key); + + for (int32 i = 0; i < list->CountItems(); i++) { + BNetworkCookie* c + = reinterpret_cast(list->ItemAt(i)); + + if (c->Name() == cookie->Name()) { + list->RemoveItem(i); + break; + } + } + + // Discard the cookie if it's to be deleted + if (!cookie->ShouldDeleteNow()) + list->AddItem(cookie); + } + + return true; +} + + +bool +BNetworkCookieJar::AddCookies(const BNetworkCookieList& cookies) +{ + for (int32 i = 0; i < cookies.CountItems(); i++) { + BNetworkCookie* cookiePtr + = reinterpret_cast(cookies.ItemAt(i)); + + // Using AddCookie by reference in order to avoid multiple + // cookie jar share the same cookie pointers + if (!AddCookie(*cookiePtr)) + return false; + } + + return true; +} + + +// #pragma mark Purge useless cookies + + +uint32 +BNetworkCookieJar::DeleteOutdatedCookies() +{ + int32 deleteCount = 0; + BNetworkCookie* cookiePtr; + + for (Iterator it(GetIterator()); (cookiePtr = it.Next()); ) { + if (cookiePtr->ShouldDeleteNow()) { + delete it.Remove(); + deleteCount++; + } + } + + return deleteCount; +} + + +uint32 +BNetworkCookieJar::PurgeForExit() +{ + int32 deleteCount = 0; + BNetworkCookie* cookiePtr; + + for (Iterator it(GetIterator()); (cookiePtr = it.Next()); ) { + if (cookiePtr->ShouldDeleteAtExit()) { + delete it.Remove(); + deleteCount++; + } + } + + return deleteCount; +} + + +// #pragma mark BArchivable interface + + +status_t +BNetworkCookieJar::Archive(BMessage* into, bool deep) const +{ + status_t error = BArchivable::Archive(into, deep); + + if (error == B_OK) { + BNetworkCookie* cookiePtr; + + for (Iterator it(GetIterator()); (cookiePtr = it.Next()); ) { + BMessage subArchive; + + error = cookiePtr->Archive(&subArchive, deep); + if (error != B_OK) + return error; + + error = into->AddMessage(kArchivedCookieMessageName, &subArchive); + if (error != B_OK) + return error; + } + } + + return error; +} + + +BArchivable* +BNetworkCookieJar::Instantiate(BMessage* archive) +{ + if (archive->HasMessage(kArchivedCookieMessageName)) + return new(std::nothrow) BNetworkCookieJar(archive); + + return NULL; +} + + +// #pragma mark BFlattenable interface + + +bool +BNetworkCookieJar::IsFixedSize() const +{ + // Flattened size vary + return false; +} + + +type_code +BNetworkCookieJar::TypeCode() const +{ + // TODO: Add a B_COOKIEJAR_TYPE + return B_ANY_TYPE; +} + + +ssize_t +BNetworkCookieJar::FlattenedSize() const +{ + _DoFlatten(); + return fFlattened.Length() + 1; +} + + +status_t +BNetworkCookieJar::Flatten(void* buffer, ssize_t size) const +{ + if (FlattenedSize() > size) + return B_ERROR; + + fFlattened.CopyInto(reinterpret_cast(buffer), 0, + fFlattened.Length()); + reinterpret_cast(buffer)[fFlattened.Length()] = 0; + + return B_OK; +} + + +bool +BNetworkCookieJar::AllowsTypeCode(type_code) const +{ + // TODO + return false; +} + + +status_t +BNetworkCookieJar::Unflatten(type_code, const void* buffer, ssize_t size) +{ + BString flattenedCookies; + flattenedCookies.SetTo(reinterpret_cast(buffer), size); + + while (flattenedCookies.Length() > 0) { + BNetworkCookie tempCookie; + BString tempCookieLine; + + int32 endOfLine = flattenedCookies.FindFirst('\n', 0); + if (endOfLine == -1) + tempCookieLine = flattenedCookies; + else { + flattenedCookies.MoveInto(tempCookieLine, 0, endOfLine); + flattenedCookies.Remove(0, 1); + } + + if (tempCookieLine.Length() != 0 && tempCookieLine[0] != '#') { + for (int32 field = 0; field < 7; field++) { + BString tempString; + + int32 endOfField = tempCookieLine.FindFirst('\t', 0); + if (endOfField == -1) + tempString = tempCookieLine; + else { + tempCookieLine.MoveInto(tempString, 0, endOfField); + tempCookieLine.Remove(0, 1); + } + + switch (field) { + case 0: + tempCookie.SetDomain(tempString); + break; + + case 1: + // TODO: Useless field ATM + break; + + case 2: + tempCookie.SetPath(tempString); + break; + + case 3: + tempCookie.SetSecure(tempString == "TRUE"); + break; + + case 4: + tempCookie.SetExpirationDate(atoi(tempString)); + break; + + case 5: + tempCookie.SetName(tempString); + break; + + case 6: + tempCookie.SetValue(tempString); + break; + } // switch + } // for loop + + AddCookie(tempCookie); + } + } + + return B_OK; +} + + +// #pragma mark Iterators + + +BNetworkCookieJar::Iterator +BNetworkCookieJar::GetIterator() const +{ + return BNetworkCookieJar::Iterator(this); +} + + +BNetworkCookieJar::UrlIterator +BNetworkCookieJar::GetUrlIterator(const BUrl& url) const +{ + if (!url.HasPath()) { + BUrl copy(url); + copy.SetPath("/"); + return BNetworkCookieJar::UrlIterator(this, copy); + } + + return BNetworkCookieJar::UrlIterator(this, url); +} + + +void +BNetworkCookieJar::_DoFlatten() const +{ + fFlattened.Truncate(0); + + BNetworkCookie* cookiePtr; + for (Iterator it(GetIterator()); (cookiePtr = it.Next()); ) { + fFlattened << cookiePtr->Domain() << '\t' << "TRUE" << '\t' + << cookiePtr->Path() << '\t' + << (cookiePtr->Secure()?"TRUE":"FALSE") << '\t' + << (int32)cookiePtr->ExpirationDate() << '\t' + << cookiePtr->Name() << '\t' << cookiePtr->Value() << '\n'; + } +} + + +// #pragma mark Iterator + + +BNetworkCookieJar::Iterator::Iterator(const Iterator& other) + : + fCookieJar(other.fCookieJar), + fIterator(other.fIterator), + fLastList(other.fLastList), + fList(other.fList), + fElement(other.fElement), + fLastElement(other.fLastElement), + fIndex(other.fIndex) +{ +} + + +BNetworkCookieJar::Iterator::Iterator(const BNetworkCookieJar* cookieJar) + : + fCookieJar(const_cast(cookieJar)), + fIterator(NULL), + fLastList(NULL), + fList(NULL), + fElement(NULL), + fLastElement(NULL), + fIndex(0) +{ + fIterator = new(std::nothrow) PrivateIterator( + fCookieJar->fCookieHashMap->fHashMap.GetIterator()); + + // Locate first cookie + _FindNext(); +} + + +BNetworkCookieJar::Iterator::~Iterator() +{ + delete fIterator; +} + + +bool +BNetworkCookieJar::Iterator::HasNext() const +{ + return fElement; +} + + +BNetworkCookie* +BNetworkCookieJar::Iterator::Next() +{ + if (!fElement) + return NULL; + + BNetworkCookie* result = fElement; + _FindNext(); + return result; +} + + +BNetworkCookie* +BNetworkCookieJar::Iterator::NextDomain() +{ + if (!fElement) + return NULL; + + BNetworkCookie* result = fElement; + + if (!fIterator->fCookieMapIterator.HasNext()) { + fElement = NULL; + return NULL; + } + + fList = *(fIterator->fCookieMapIterator.NextValue()); + fIndex = 0; + fElement = reinterpret_cast(fList->ItemAt(fIndex)); + + return result; +} + + +BNetworkCookie* +BNetworkCookieJar::Iterator::Remove() +{ + if (!fLastElement) + return NULL; + + BNetworkCookie* result = fLastElement; + + if (fIndex == 0) { + if (fLastList->CountItems() == 1) { + fIterator->fCookieMapIterator.Remove(); + delete fLastList; + } + else + fLastList->RemoveItem(fLastList->CountItems() - 1); + } else { + fList->RemoveItem(fIndex-1); + fIndex--; + } + + fLastElement = NULL; + return result; +} + + +BNetworkCookieJar::Iterator& +BNetworkCookieJar::Iterator::operator=(const BNetworkCookieJar::Iterator& other) +{ + fCookieJar = other.fCookieJar; + fIterator = other.fIterator; + fLastList = other.fLastList; + fList = other.fList; + fElement = other.fElement; + fLastElement = other.fLastElement; + fIndex = other.fIndex; + return *this; +} + + +void +BNetworkCookieJar::Iterator::_FindNext() +{ + fLastElement = fElement; + + fIndex++; + if (fList && fIndex < fList->CountItems()) { + fElement = reinterpret_cast(fList->ItemAt(fIndex)); + return; + } + + if (!fIterator->fCookieMapIterator.HasNext()) { + fElement = NULL; + return; + } + + fLastList = fList; + fList = *(fIterator->fCookieMapIterator.NextValue()); + fIndex = 0; + fElement = reinterpret_cast(fList->ItemAt(fIndex)); +} + + +// #pragma mark URL Iterator + + +BNetworkCookieJar::UrlIterator::UrlIterator(const UrlIterator& other) +{ + *this = other; +} + + +BNetworkCookieJar::UrlIterator::UrlIterator(const BNetworkCookieJar* cookieJar, + const BUrl& url) + : + fCookieJar(const_cast(cookieJar)), + fIterator(NULL), + fList(NULL), + fLastList(NULL), + fElement(NULL), + fLastElement(NULL), + fIndex(0), + fLastIndex(0), + fUrl(const_cast(url)) +{ + BString domain(url.Host()); + + if (!domain.Length()) + return; + + if (domain[0] != '.') + domain.Prepend("."); + + // Prepending another dot since _FindNext is going to + // call _SupDomain() + domain.Prepend("."); + + fIterator = new(std::nothrow) PrivateIterator( + fCookieJar->fCookieHashMap->fHashMap.GetIterator()); + fIterator->fKey.SetTo(domain, domain.Length()); + + _FindNext(); +} + + +BNetworkCookieJar::UrlIterator::~UrlIterator() +{ + delete fIterator; +} + + +bool +BNetworkCookieJar::UrlIterator::HasNext() const +{ + return fElement; +} + + +BNetworkCookie* +BNetworkCookieJar::UrlIterator::Next() +{ + if (!fElement) + return NULL; + + BNetworkCookie* result = fElement; + _FindNext(); + return result; +} + + +BNetworkCookie* +BNetworkCookieJar::UrlIterator::Remove() +{ + if (!fLastElement) + return NULL; + + BNetworkCookie* result = fLastElement; + + fLastList->RemoveItem(fLastIndex); + + if (fLastList->CountItems() == 0) { + HashString lastKey(fLastElement->Domain(), + fLastElement->Domain().Length()); + + delete fCookieJar->fCookieHashMap->fHashMap.Remove(lastKey); + } + + fLastElement = NULL; + return result; +} + + +BNetworkCookieJar::UrlIterator& +BNetworkCookieJar::UrlIterator::operator=( + const BNetworkCookieJar::UrlIterator& other) +{ + fCookieJar = other.fCookieJar; + fList = other.fList; + fLastList = other.fLastList; + fElement = other.fElement; + fLastElement = other.fLastElement; + fIndex = other.fIndex; + fLastIndex = other.fLastIndex; + fUrl = other.fUrl; + fIterator = other.fIterator; + return *this; +} + + +bool +BNetworkCookieJar::UrlIterator::_SupDomain() +{ + BString domain(fIterator->fKey.GetString()); + int32 nextDot = domain.FindFirst('.', 1); + + if (nextDot == -1) + return false; + + domain.Remove(0, nextDot); + fIterator->fKey.SetTo(domain.String(), domain.Length()); + return true; +} + + +void +BNetworkCookieJar::UrlIterator::_FindNext() +{ + fLastIndex = fIndex; + fLastElement = fElement; + + if (_FindPath()) + return; + + fLastList = fList; + do { + if (!_SupDomain()) { + fElement = NULL; + return; + } + + _FindDomain(); + } while (!_FindPath()); +} + + +void +BNetworkCookieJar::UrlIterator::_FindDomain() +{ + fList = fCookieJar->fCookieHashMap->fHashMap.Get(fIterator->fKey); + + if (fList == NULL) + fElement = NULL; + + fIndex = -1; +} + + +bool +BNetworkCookieJar::UrlIterator::_FindPath() +{ + fIndex++; + if (fList && fIndex < fList->CountItems()) { + do { + fElement + = reinterpret_cast(fList->ItemAt(fIndex)); + + if (fElement->IsValidForPath(fUrl.Path())) + return true; + + fIndex++; + } while (fList && fIndex < fList->CountItems()); + } + + return false; +} diff --git a/src/kits/network/libnetapi/NetworkCookieJarPrivate.h b/src/kits/network/libnetapi/NetworkCookieJarPrivate.h new file mode 100644 index 0000000000..2f5f6b96ef --- /dev/null +++ b/src/kits/network/libnetapi/NetworkCookieJarPrivate.h @@ -0,0 +1,27 @@ +/* + * Copyright 2010 Haiku Inc. All rights reserved. + * Distributed under the terms of the MIT License. + */ +#ifndef _B_NETWORK_COOKIE_JAR_PRIVATE_H_ +#define _B_NETWORK_COOKIE_JAR_PRIVATE_H_ + +typedef BPrivate::HashMap BNetworkCookieHashMap; + +struct BNetworkCookieJar::PrivateHashMap { + BNetworkCookieHashMap fHashMap; +}; + +struct BNetworkCookieJar::PrivateIterator { + PrivateIterator( + BNetworkCookieHashMap::Iterator it) + : + fCookieMapIterator(it) + { + } + + HashString fKey; + BNetworkCookieHashMap::Iterator + fCookieMapIterator; +}; + +#endif // _B_NETWORK_COOKIE_JAR_PRIVATE_H_ diff --git a/src/kits/network/libnetapi/Url.cpp b/src/kits/network/libnetapi/Url.cpp new file mode 100644 index 0000000000..073e3a75db --- /dev/null +++ b/src/kits/network/libnetapi/Url.cpp @@ -0,0 +1,866 @@ +/* + * Copyright 2010 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 + +static const char* kArchivedUrl = "be:url string"; + + +BUrl::BUrl(const char* url) + : + fUrlString(), + fProtocol(), + fUser(), + fPassword(), + fHost(), + fPort(0), + fPath(), + fRequest(), + fHasAuthority(false) +{ + SetUrlString(url); +} + + +BUrl::BUrl(BMessage* archive) + : + fUrlString(), + fProtocol(), + fUser(), + fPassword(), + fHost(), + fPort(0), + fPath(), + fRequest(), + fHasAuthority(false) +{ + BString url; + + if (archive->FindString(kArchivedUrl, &url) == B_OK) + SetUrlString(url); +} + + +BUrl::BUrl(const BUrl& other) + : + BArchivable(), + fUrlString(), + fProtocol(), + fUser(), + fPassword(), + fHost(), + fPort(0), + fPath(), + fRequest(), + fHasAuthority(false) +{ + *this = other; +} + + +BUrl::BUrl() + : + fUrlString(), + fProtocol(), + fUser(), + fPassword(), + fHost(), + fPort(0), + fPath(), + fRequest(), + fHasAuthority(false) +{ +} + + +BUrl::~BUrl() +{ +} + + +// #pragma mark URL fields modifiers + + +BUrl& +BUrl::SetUrlString(const BString& url) +{ + _ExplodeUrlString(url); + return *this; +} + + +BUrl& +BUrl::SetProtocol(const BString& protocol) +{ + fProtocol = protocol; + fHasProtocol = true; + fUrlStringValid = false; + return *this; +} + + +BUrl& +BUrl::SetUserName(const BString& user) +{ + fUser = user; + fHasUserName = true; + fUrlStringValid = false; + fAuthorityValid = false; + fUserInfoValid = false; + return *this; +} + + +BUrl& +BUrl::SetPassword(const BString& password) +{ + fPassword = password; + fHasPassword = true; + fUrlStringValid = false; + fAuthorityValid = false; + fUserInfoValid = false; + return *this; +} + + +BUrl& +BUrl::SetHost(const BString& host) +{ + fHost = host; + fHasHost = true; + fUrlStringValid = false; + fAuthorityValid = false; + return *this; +} + + +BUrl& +BUrl::SetPort(int port) +{ + fPort = port; + fHasPort = true; + fUrlStringValid = false; + fAuthorityValid = false; + return *this; +} + + +BUrl& +BUrl::SetPath(const BString& path) +{ + fPath = path; + fHasPath = true; + fUrlStringValid = false; + return *this; +} + + +BUrl& +BUrl::SetRequest(const BString& request) +{ + fRequest = request; + fHasRequest = true; + fUrlStringValid = false; + return *this; +} + + +BUrl& +BUrl::SetFragment(const BString& fragment) +{ + fFragment = fragment; + fHasFragment = true; + fUrlStringValid = false; + return *this; +} + + +// #pragma mark URL fields access + + +const BString& +BUrl::UrlString() const +{ + if (!fUrlStringValid) { + fUrlString.Truncate(0); + + if (HasProtocol()) { + fUrlString << fProtocol << ':'; + if (HasAuthority()) + fUrlString << "//"; + } + + fUrlString << Authority(); + fUrlString << Path(); + + if (HasRequest()) + fUrlString << '?' << fRequest; + + if (HasFragment()) + fUrlString << '#' << fFragment; + + fUrlStringValid = true; + } + + return fUrlString; +} + + +const BString& +BUrl::Protocol() const +{ + return fProtocol; +} + + +const BString& +BUrl::UserName() const +{ + return fUser; +} + + +const BString& +BUrl::Password() const +{ + return fPassword; +} + + +const BString& +BUrl::UserInfo() const +{ + if (!fUserInfoValid) { + fUserInfo = fUser; + + if (HasPassword()) + fUserInfo << ':' << fPassword; + + fUserInfoValid = true; + } + + return fUserInfo; +} + + +const BString& +BUrl::Host() const +{ + return fHost; +} + + +int +BUrl::Port() const +{ + return fPort; +} + + +const BString& +BUrl::Authority() const +{ + if (!fAuthorityValid) { + fAuthority.Truncate(0); + + if (HasUserInfo()) + fAuthority << UserInfo() << '@'; + fAuthority << Host(); + + if (HasPort()) + fAuthority << ':' << fPort; + + fAuthorityValid = true; + } + return fAuthority; +} + + +const BString& +BUrl::Path() const +{ + return fPath; +} + + +const BString& +BUrl::Request() const +{ + return fRequest; +} + + +const BString& +BUrl::Fragment() const +{ + return fFragment; +} + + +// #pragma mark URL fields tests + + +bool +BUrl::IsValid() const +{ + // TODO + return false; +} + + +bool +BUrl::HasProtocol() const +{ + return fHasProtocol; +} + + +bool +BUrl::HasAuthority() const +{ + return fHasAuthority; +} + + +bool +BUrl::HasUserName() const +{ + return fHasUserName; +} + + +bool +BUrl::HasPassword() const +{ + return fHasPassword; +} + + +bool +BUrl::HasUserInfo() const +{ + return fHasUserInfo; +} + + +bool +BUrl::HasHost() const +{ + return fHasHost; +} + + +bool +BUrl::HasPort() const +{ + return fHasPort; +} + + +bool +BUrl::HasPath() const +{ + return fHasPath; +} + + +bool +BUrl::HasRequest() const +{ + return fHasRequest; +} + + +bool +BUrl::HasFragment() const +{ + return fHasFragment; +} + + +// #pragma mark URL encoding/decoding of needed fields + + +void +BUrl::UrlEncode(bool strict) +{ + fUser = _DoUrlEncodeChunk(fUser, strict); + fPassword = _DoUrlEncodeChunk(fPassword, strict); + fHost = _DoUrlEncodeChunk(fHost, strict); + fFragment = _DoUrlEncodeChunk(fFragment, strict); + fPath = _DoUrlEncodeChunk(fPath, strict, true); +} + + +void +BUrl::UrlDecode(bool strict) +{ + fUser = _DoUrlDecodeChunk(fUser, strict); + fPassword = _DoUrlDecodeChunk(fPassword, strict); + fHost = _DoUrlDecodeChunk(fHost, strict); + fFragment = _DoUrlDecodeChunk(fFragment, strict); + fPath = _DoUrlDecodeChunk(fPath, strict); +} + + +// #pragma mark Url encoding/decoding of string + + +/*static*/ BString +BUrl::UrlEncode(const BString& url, bool strict, bool directory) +{ + return _DoUrlEncodeChunk(url, strict, directory); +} + + +/*static*/ BString +BUrl::UrlDecode(const BString& url, bool strict) +{ + return _DoUrlDecodeChunk(url, strict); +} + + +// #pragma mark BArchivable members + + +status_t +BUrl::Archive(BMessage* into, bool deep) const +{ + status_t ret = BArchivable::Archive(into, deep); + + if (ret == B_OK) + ret = into->AddString(kArchivedUrl, UrlString()); + + return ret; +} + + +/*static*/ BArchivable* +BUrl::Instantiate(BMessage* archive) +{ + if (validate_instantiation(archive, "BUrl")) + return new(std::nothrow) BUrl(archive); + return NULL; +} + + +// #pragma mark URL comparison + + +bool +BUrl::operator==(BUrl& other) const +{ + UrlString(); + other.UrlString(); + + return fUrlString == other.fUrlString; +} + + +bool +BUrl::operator!=(BUrl& other) const +{ + return !(*this == other); +} + + +// #pragma mark URL assignment + + +const BUrl& +BUrl::operator=(const BUrl& other) +{ + fUrlStringValid = other.fUrlStringValid; + if (fUrlStringValid) + fUrlString = other.fUrlString; + + fAuthorityValid = other.fAuthorityValid; + if (fAuthorityValid) + fAuthority = other.fAuthority; + + fUserInfoValid = other.fUserInfoValid; + if (fUserInfoValid) + fUserInfo = other.fUserInfo; + + fProtocol = other.fProtocol; + fUser = other.fUser; + fPassword = other.fPassword; + fHost = other.fHost; + fPort = other.fPort; + fPath = other.fPath; + fRequest = other.fRequest; + fFragment = other.fFragment; + + fHasProtocol = other.fHasProtocol; + fHasUserName = other.fHasUserName; + fHasPassword = other.fHasPassword; + fHasUserInfo = other.fHasUserInfo; + fHasHost = other.fHasHost; + fHasPort = other.fHasPort; + fHasAuthority = other.fHasAuthority; + fHasPath = other.fHasPath; + fHasRequest = other.fHasRequest; + fHasFragment = other.fHasFragment; + + return *this; +} + + +const BUrl& +BUrl::operator=(const BString& string) +{ + SetUrlString(string); + return *this; +} + + +const BUrl& +BUrl::operator=(const char* string) +{ + SetUrlString(string); + return *this; +} + + +// #pragma mark URL to string conversion + + +BUrl::operator const char*() const +{ + return UrlString(); +} + + +void +BUrl::_ResetFields() +{ + fHasProtocol = false; + fHasUserName = false; + fHasPassword = false; + fHasUserInfo = false; + fHasHost = false; + fHasPort = false; + fHasAuthority = false; + fHasPath = false; + fHasRequest = false; + fHasFragment = false; + + fProtocol.Truncate(0); + fUser.Truncate(0); + fPassword.Truncate(0); + fHost.Truncate(0); + fPort = 0; + fPath.Truncate(0); + fRequest.Truncate(0); + fFragment.Truncate(0); + + // Force re-generation of these fields + fUrlStringValid = false; + fUserInfoValid = false; + fAuthorityValid = false; +} + + +void +BUrl::_ExplodeUrlString(const BString& url) +{ + int16 urlIndex = 0; + + _ResetFields(); + + _ExtractProtocol(url, &urlIndex); + _ExtractAuthority(url, &urlIndex); + _ExtractPath(url, &urlIndex); + _ExtractRequestAndFragment(url, &urlIndex); +} + + +void +BUrl::_ExtractProtocol(const BString& urlString, int16* origin) +{ + int16 firstColon = urlString.FindFirst(':', *origin); + + // If no colon is found, assume the protocol + // is not present + if (firstColon == -1) + return; + else { + urlString.CopyInto(fProtocol, *origin, firstColon - *origin); + *origin = firstColon + 1; + } + + if (!_IsProtocolValid()) { + fHasProtocol = false; + fProtocol.Truncate(0); + } else + fHasProtocol = true; +} + + +void +BUrl::_ExtractAuthority(const BString& urlString, int16* origin) +{ + // URI doesn't contain an authority field + if (urlString.FindFirst("//", *origin) != *origin) + return; + + fHasAuthority = true; + +// while (urlString.ByteAt(*origin) == '/') +// (*origin)++; + + (*origin) += 2; + + + int16 userInfoEnd = urlString.FindFirst('@', *origin); + + // URL contains userinfo field + if (userInfoEnd != -1) { + BString userInfo; + urlString.CopyInto(userInfo, *origin, userInfoEnd - *origin); + + int16 colonDelimiter = userInfo.FindFirst(':', 0); + + if (colonDelimiter == *origin) { + fHasPassword = true; + fPassword = userInfo; + } else if (colonDelimiter != -1) { + fHasUserName = true; + fHasPassword = true; + + userInfo.CopyInto(fUser, 0, colonDelimiter); + userInfo.CopyInto(fPassword, colonDelimiter + 1, + userInfo.Length() - colonDelimiter); + } else { + fHasUserName = true; + fUser = userInfo; + } + + fHasUserInfo = true; + *origin = userInfoEnd + 1; + } + + + // Extract the host part + int16 hostEnd = *origin; + + while (hostEnd < urlString.Length() + && !_IsAuthorityTerminator(urlString.ByteAt(hostEnd)) + && urlString.ByteAt(hostEnd) != ':') { + hostEnd++; + } + + // The host is likely to be present if an authority is + // defined, but in some weird cases, it's not. + if (hostEnd != *origin) { + urlString.CopyInto(fHost, *origin, hostEnd - *origin); + + *origin = hostEnd; + fHasHost = true; + } + + // Extract the port part + fPort = 0; + if (urlString.ByteAt(*origin) == ':') { + int16 portEnd = ++(*origin); + + while (portEnd < urlString.Length() + && !_IsAuthorityTerminator(urlString.ByteAt(portEnd))) + portEnd++; + + BString portString; + urlString.CopyInto(portString, *origin, portEnd - *origin); + fPort = atoi(portString.String()); + + // Even if the port is invalid, the URL is considered to + // have a port. + fHasPort = portString.Length() > 0; + *origin = portEnd; + } +} + + +void +BUrl::_ExtractPath(const BString& urlString, int16* origin) +{ + // Extract path from URL + if (urlString.ByteAt(*origin) == '/' || !HasAuthority()) { + int16 pathEnd = *origin; + + while (pathEnd < urlString.Length() + && !_IsPathTerminator(urlString.ByteAt(pathEnd))) { + pathEnd++; + } + + urlString.CopyInto(fPath, *origin, pathEnd - *origin); + + *origin = pathEnd; + fHasPath = true; + } +} + + +void +BUrl::_ExtractRequestAndFragment(const BString& urlString, int16* origin) +{ + // Extract request field from URL + if (urlString.ByteAt(*origin) == '?') { + (*origin)++; + int16 requestEnd = urlString.FindFirst('#', *origin); + + fHasRequest = true; + + if (requestEnd == -1) { + urlString.CopyInto(fRequest, *origin, urlString.Length() - *origin); + return; + } else { + urlString.CopyInto(fRequest, *origin, requestEnd - *origin); + *origin = requestEnd; + } + } + + // Extract fragment field if needed + if (urlString.ByteAt(*origin) == '#') { + (*origin)++; + urlString.CopyInto(fFragment, *origin, urlString.Length() - *origin); + + fHasFragment = true; + } +} + + +/*static*/ BString +BUrl::_DoUrlEncodeChunk(const BString& chunk, bool strict, bool directory) +{ + BString result; + + for (int32 i = 0; i < chunk.Length(); i++) { + if (_IsUnreserved(chunk[i]) + || (directory && (chunk[i] == '/' || chunk[i] == '\\'))) + result << chunk[i]; + else { + if (chunk[i] == ' ' && !strict) { + result << '+'; + // In non-strict mode, spaces are encoded by a plus sign + } else { + char hexString[5]; + snprintf(hexString, 5, "%X", chunk[i]); + + result << '%' << hexString; + } + } + } + + return result; +} + + +/*static*/ BString +BUrl::_DoUrlDecodeChunk(const BString& chunk, bool strict) +{ + BString result; + + for (int32 i = 0; i < chunk.Length(); i++) { + if (chunk[i] == '+' && !strict) + result << ' '; + else if (chunk[i] != '%') + result << chunk[i]; + else { + char hexString[] = { chunk[i+1], chunk[i+2], 0 }; + result << (char)strtol(hexString, NULL, 16); + + i += 2; + } + } + return result; +} + + +bool +BUrl::_IsProtocolValid() +{ + for (int8 index = 0; index < fProtocol.Length(); index++) { + char c = fProtocol[index]; + + if (index == 0 && !isalpha(c)) + return false; + else if (!isalnum(c) && c != '+' && c != '-' && c != '.') + return false; + } + + return true; +} + + +bool +BUrl::_IsAuthorityTerminator(char c) +{ + if (c == '/' || _IsPathTerminator(c)) + return true; + else + return false; +} + + +bool +BUrl::_IsPathTerminator(char c) +{ + if (c == '?' || _IsRequestTerminator(c)) + return true; + else + return false; +} + + +bool +BUrl::_IsRequestTerminator(char c) +{ + if (c == '#') + return true; + else + return false; +} + + +bool +BUrl::_IsUnreserved(char c) +{ + if (isalnum(c) || c == '-' || c == '.' || c == '_' || c == '~') + return true; + else + return false; +} + + +bool +BUrl::_IsGenDelim(char c) +{ + if (c == ':' || c == '/' || c == '?' || c == '#' || c == '[' + || c == ']' || c == '@') + return true; + else + return false; +} + + +bool +BUrl::_IsSubDelim(char c) +{ + if (c == '!' || c == '$' || c == '&' || c == '\'' || c == '(' + || c == ')' || c == '*' || c == '+' || c == ',' || c == ';' + || c == '=') + return true; + else + return false; +} diff --git a/src/kits/network/libnetapi/UrlContext.cpp b/src/kits/network/libnetapi/UrlContext.cpp new file mode 100644 index 0000000000..a8693043c2 --- /dev/null +++ b/src/kits/network/libnetapi/UrlContext.cpp @@ -0,0 +1,37 @@ +/* + * Copyright 2010 Haiku Inc. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Christophe Huriaux, c.huriaux@gmail.com + */ + + +#include + + +BUrlContext::BUrlContext() + : + fCookieJar() +{ +} + + +// #pragma mark Context modifiers + + +void +BUrlContext::SetCookieJar(const BNetworkCookieJar& cookieJar) +{ + fCookieJar = cookieJar; +} + + +// #pragma mark Context accessors + + +BNetworkCookieJar& +BUrlContext::GetCookieJar() +{ + return fCookieJar; +} diff --git a/src/kits/network/libnetapi/UrlProtocol.cpp b/src/kits/network/libnetapi/UrlProtocol.cpp new file mode 100644 index 0000000000..3a3efde886 --- /dev/null +++ b/src/kits/network/libnetapi/UrlProtocol.cpp @@ -0,0 +1,298 @@ +/* + * Copyright 2010 Haiku Inc. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Christophe Huriaux, c.huriaux@gmail.com + */ + + +#include +#include +#include + + +static const char* kProtocolThreadStrStatus[B_PROT_THREAD_STATUS__END+1] + = { + "Request successfully completed", + "Request running", + "Socket error", + "Connection failed", + "Hostname resolution failed", + "Network write failed", + "Network read failed", + "Out of memory", + "Protocol-specific error", + "Unknown error" + }; + + +BUrlProtocol::BUrlProtocol(const BUrl& url, BUrlProtocolListener* listener, + BUrlContext* context, BUrlResult* result, const char* threadName, + const char* protocolName) + : + fUrl(url), + fResult(result), + fContext(context), + fListener(listener), + fQuit(false), + fRunning(false), + fThreadId(0), + fThreadName(threadName), + fProtocol(protocolName) +{ +} + + +// #pragma mark URL protocol thread management + + +thread_id +BUrlProtocol::Run() +{ + // Thread already running + if (fRunning) { + PRINT(("BUrlProtocol::Run() : Oops, already running ! " + "[urlProtocol=%p]!\n", this)); + return fThreadId; + } + + fThreadId = spawn_thread(BUrlProtocol::_ThreadEntry, fThreadName, + B_NORMAL_PRIORITY, this); + + if (fThreadId < B_OK) + return fThreadId; + + status_t launchErr = resume_thread(fThreadId); + if (launchErr < B_OK) { + PRINT(("BUrlProtocol::Run() : Failed to resume thread %ld\n", + fThreadId)); + return launchErr; + } + + fRunning = true; + return fThreadId; +} + + +status_t +BUrlProtocol::Pause() +{ + // TODO + return B_ERROR; +} + + +status_t +BUrlProtocol::Resume() +{ + // TODO + return B_ERROR; +} + + +status_t +BUrlProtocol::Stop() +{ + if (!fRunning) + return B_ERROR; + + status_t threadStatus = B_OK; + fQuit = true; + + wait_for_thread(fThreadId, &threadStatus); + return threadStatus; +} + + +// #pragma mark URL protocol parameters modification + + +status_t +BUrlProtocol::SetUrl(const BUrl& url) +{ + // We should avoid to change URL while the thread is running ... + if (IsRunning()) + return B_ERROR; + + fUrl = url; + return B_OK; +} + + +status_t +BUrlProtocol::SetResult(BUrlResult* result) +{ + if (IsRunning()) + return B_ERROR; + + fResult = result; + return B_OK; +} + + +status_t +BUrlProtocol::SetContext(BUrlContext* context) +{ + if (IsRunning()) + return B_ERROR; + + fContext = context; + return B_OK; +} + + +status_t +BUrlProtocol::SetListener(BUrlProtocolListener* listener) +{ + if (IsRunning()) + return B_ERROR; + + fListener = listener; + return B_OK; +} + + +// #pragma mark URL protocol parameters access + + +const BUrl& +BUrlProtocol::Url() const +{ + return fUrl; +} + + +BUrlResult* +BUrlProtocol::Result() const +{ + return fResult; +} + + +BUrlContext* +BUrlProtocol::Context() const +{ + return fContext; +} + + +BUrlProtocolListener* +BUrlProtocol::Listener() const +{ + return fListener; +} + + +const BString& +BUrlProtocol::Protocol() const +{ + return fProtocol; +} + + +// #pragma mark URL protocol informations + + +bool +BUrlProtocol::IsRunning() const +{ + return fRunning; +} + + +status_t +BUrlProtocol::Status() const +{ + return fThreadStatus; +} + + +const char* +BUrlProtocol::StatusString(status_t threadStatus) const +{ + if (threadStatus < B_PROT_THREAD_STATUS__BASE) + threadStatus = B_PROT_THREAD_STATUS__END; + else if (threadStatus >= B_PROT_PROTOCOL_ERROR) + threadStatus = B_PROT_PROTOCOL_ERROR; + + return kProtocolThreadStrStatus[threadStatus]; +} + + +// #pragma mark Thread management + + +/*static*/ int32 +BUrlProtocol::_ThreadEntry(void* arg) +{ + BUrlProtocol* urlProtocol = reinterpret_cast(arg); + urlProtocol->fThreadStatus = B_PROT_RUNNING; + + status_t protocolLoopExitStatus = urlProtocol->_ProtocolLoop(); + + urlProtocol->fRunning = false; + urlProtocol->fThreadStatus = protocolLoopExitStatus; + + if (urlProtocol->fListener != NULL) + urlProtocol->fListener->RequestCompleted(urlProtocol, + protocolLoopExitStatus == B_PROT_SUCCESS); + + return B_OK; +} + + +status_t +BUrlProtocol::_ProtocolLoop() +{ + // Dummy _ProtocolLoop + while (!fQuit) + snooze(1000); + + return B_PROT_SUCCESS; +} + + +void +BUrlProtocol::_EmitDebug(BUrlProtocolDebugMessage type, + const char* format, ...) +{ + if (fListener == NULL) + return; + + va_list arguments; + va_start(arguments, format); + + char debugMsg[256]; + vsnprintf(debugMsg, 256, format, arguments); + fListener->DebugMessage(this, type, debugMsg); + va_end(arguments); +} + + +BMallocIO& +BUrlProtocol::_ResultRawData() +{ + return fResult->fRawData; +} + + +BHttpHeaders& +BUrlProtocol::_ResultHeaders() +{ + return fResult->fHeaders; +} + + +void +BUrlProtocol::_SetResultStatusCode(int32 statusCode) +{ + fResult->fStatusCode = statusCode; +} + + +BString& +BUrlProtocol::_ResultStatusText() +{ + return fResult->fStatusString; +} diff --git a/src/kits/network/libnetapi/UrlProtocolAsynchronousListener.cpp b/src/kits/network/libnetapi/UrlProtocolAsynchronousListener.cpp new file mode 100644 index 0000000000..5d61442ad9 --- /dev/null +++ b/src/kits/network/libnetapi/UrlProtocolAsynchronousListener.cpp @@ -0,0 +1,196 @@ +/* + * Copyright 2010 Haiku Inc. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Christophe Huriaux, c.huriaux@gmail.com + */ + + +#include + +#include +#include +#include + +extern const char* kUrlProtocolMessageType; +extern const char* kUrlProtocolCaller; + +BUrlProtocolAsynchronousListener::BUrlProtocolAsynchronousListener( + bool transparent) + : + BHandler("UrlProtocolAsynchronousListener"), + fSynchronousListener(NULL) +{ + if (be_app->Lock()) { + be_app->AddHandler(this); + be_app->Unlock(); + } + else + PRINT(("Cannot lock be_app\n")); + + if (transparent) + fSynchronousListener + = new(std::nothrow) BUrlProtocolDispatchingListener(this); +} + + +BUrlProtocolAsynchronousListener::~BUrlProtocolAsynchronousListener() +{ + if (be_app->Lock()) { + be_app->RemoveHandler(this); + be_app->Unlock(); + } + delete fSynchronousListener; +} + + +void +BUrlProtocolAsynchronousListener::ConnectionOpened(BUrlProtocol*) +{ +} + + +void +BUrlProtocolAsynchronousListener::HostnameResolved(BUrlProtocol*, const char*) +{ +} + + +void +BUrlProtocolAsynchronousListener::ResponseStarted(BUrlProtocol*) +{ +} + + +void +BUrlProtocolAsynchronousListener::HeadersReceived(BUrlProtocol*) +{ +} + + +void +BUrlProtocolAsynchronousListener::DataReceived(BUrlProtocol*, const char*, + ssize_t) +{ +} + + +void +BUrlProtocolAsynchronousListener::DownloadProgress(BUrlProtocol*, ssize_t, + ssize_t) +{ +} + + +void +BUrlProtocolAsynchronousListener::UploadProgress(BUrlProtocol*, ssize_t, + ssize_t) +{ +} + + +void +BUrlProtocolAsynchronousListener::RequestCompleted(BUrlProtocol*, bool) +{ +} + + +// #pragma mark Synchronous listener access + + +BUrlProtocolListener* +BUrlProtocolAsynchronousListener::SynchronousListener() +{ + return fSynchronousListener; +} + + +void +BUrlProtocolAsynchronousListener::MessageReceived(BMessage* message) +{ + if (message->what != B_URL_PROTOCOL_NOTIFICATION) { + BHandler::MessageReceived(message); + return; + } + + BUrlProtocol* caller; + if (message->FindPointer(kUrlProtocolCaller, + reinterpret_cast(&caller)) != B_OK) + return; + + int8 notification; + if (message->FindInt8(kUrlProtocolMessageType, ¬ification) + != B_OK) + return; + + switch (notification) { + case B_URL_PROTOCOL_CONNECTION_OPENED: + ConnectionOpened(caller); + break; + + case B_URL_PROTOCOL_HOSTNAME_RESOLVED: + { + const char* ip; + message->FindString("url:ip", &ip); + + HostnameResolved(caller, ip); + } + break; + + case B_URL_PROTOCOL_RESPONSE_STARTED: + ResponseStarted(caller); + break; + + case B_URL_PROTOCOL_HEADERS_RECEIVED: + HeadersReceived(caller); + break; + + case B_URL_PROTOCOL_DATA_RECEIVED: + { + const char* data; + ssize_t size; + message->FindData("url:data", B_STRING_TYPE, + reinterpret_cast(&data), &size); + + DataReceived(caller, data, size); + } + break; + + case B_URL_PROTOCOL_DOWNLOAD_PROGRESS: + { + int32 bytesReceived; + int32 bytesTotal; + message->FindInt32("url:bytesReceived", &bytesReceived); + message->FindInt32("url:bytesTotal", &bytesTotal); + + DownloadProgress(caller, bytesReceived, bytesTotal); + } + break; + + case B_URL_PROTOCOL_UPLOAD_PROGRESS: + { + int32 bytesSent; + int32 bytesTotal; + message->FindInt32("url:bytesSent", &bytesSent); + message->FindInt32("url:bytesTotal", &bytesTotal); + + UploadProgress(caller, bytesSent, bytesTotal); + } + break; + + case B_URL_PROTOCOL_REQUEST_COMPLETED: + { + bool success; + message->FindBool("url:success", &success); + + RequestCompleted(caller, success); + } + break; + + default: + PRINT(("BUrlProtocolAsynchronousListener: Unknown notification %d\n", + notification)); + break; + } +} diff --git a/src/kits/network/libnetapi/UrlProtocolDispatchingListener.cpp b/src/kits/network/libnetapi/UrlProtocolDispatchingListener.cpp new file mode 100644 index 0000000000..5bba318b96 --- /dev/null +++ b/src/kits/network/libnetapi/UrlProtocolDispatchingListener.cpp @@ -0,0 +1,126 @@ +/* + * Copyright 2010 Haiku Inc. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Christophe Huriaux, c.huriaux@gmail.com + */ + + +#include +#include + + +const char* kUrlProtocolMessageType = "be:urlProtocolMessageType"; +const char* kUrlProtocolCaller = "be:urlProtocolCaller"; + + +BUrlProtocolDispatchingListener::BUrlProtocolDispatchingListener + (BHandler* handler) + : + fMessenger(handler) +{ +} + + +BUrlProtocolDispatchingListener::BUrlProtocolDispatchingListener + (const BMessenger& messenger) + : + fMessenger(messenger) +{ +} + + +void +BUrlProtocolDispatchingListener::ConnectionOpened(BUrlProtocol* caller) +{ + BMessage message(B_URL_PROTOCOL_NOTIFICATION); + _SendMessage(&message, B_URL_PROTOCOL_CONNECTION_OPENED, caller); +} + + +void +BUrlProtocolDispatchingListener::HostnameResolved(BUrlProtocol* caller, + const char* ip) +{ + BMessage message(B_URL_PROTOCOL_NOTIFICATION); + message.AddString("url:hostIp", ip); + + _SendMessage(&message, B_URL_PROTOCOL_HOSTNAME_RESOLVED, caller); +} + + +void +BUrlProtocolDispatchingListener::ResponseStarted(BUrlProtocol* caller) +{ + BMessage message(B_URL_PROTOCOL_NOTIFICATION); + _SendMessage(&message, B_URL_PROTOCOL_RESPONSE_STARTED, caller); +} + + +void +BUrlProtocolDispatchingListener::HeadersReceived(BUrlProtocol* caller) +{ + BMessage message(B_URL_PROTOCOL_NOTIFICATION); + _SendMessage(&message, B_URL_PROTOCOL_HEADERS_RECEIVED, caller); +} + + +void +BUrlProtocolDispatchingListener::DataReceived(BUrlProtocol* caller, + const char* data, ssize_t size) +{ + BMessage message(B_URL_PROTOCOL_NOTIFICATION); + message.AddData("url:data", B_STRING_TYPE, data, size, true, 1); + + _SendMessage(&message, B_URL_PROTOCOL_DATA_RECEIVED, caller); +} + + +void +BUrlProtocolDispatchingListener::DownloadProgress(BUrlProtocol* caller, + ssize_t bytesReceived, ssize_t bytesTotal) +{ + BMessage message(B_URL_PROTOCOL_NOTIFICATION); + message.AddInt32("url:bytesReceived", bytesReceived); + message.AddInt32("url:bytesTotal", bytesTotal); + + _SendMessage(&message, B_URL_PROTOCOL_DOWNLOAD_PROGRESS, caller); +} + + +void +BUrlProtocolDispatchingListener::UploadProgress(BUrlProtocol* caller, + ssize_t bytesSent, ssize_t bytesTotal) +{ + BMessage message(B_URL_PROTOCOL_NOTIFICATION); + message.AddInt32("url:bytesSent", bytesSent); + message.AddInt32("url:bytesTotal", bytesTotal); + + _SendMessage(&message, B_URL_PROTOCOL_UPLOAD_PROGRESS, caller); +} + + + +void +BUrlProtocolDispatchingListener::RequestCompleted(BUrlProtocol* caller, + bool success) +{ + BMessage message(B_URL_PROTOCOL_NOTIFICATION); + message.AddBool("url:success", success); + + _SendMessage(&message, B_URL_PROTOCOL_REQUEST_COMPLETED, caller); +} + + +void +BUrlProtocolDispatchingListener::_SendMessage(BMessage* message, + int8 notification, BUrlProtocol* caller) +{ + ASSERT(message != NULL); + + message->AddPointer(kUrlProtocolCaller, caller); + message->AddInt8(kUrlProtocolMessageType, notification); + + ASSERT(fMessenger.SendMessage(message) == B_OK); +} diff --git a/src/kits/network/libnetapi/UrlProtocolHttp.cpp b/src/kits/network/libnetapi/UrlProtocolHttp.cpp new file mode 100644 index 0000000000..4971d3b11d --- /dev/null +++ b/src/kits/network/libnetapi/UrlProtocolHttp.cpp @@ -0,0 +1,868 @@ +/* + * Copyright 2010 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 + + +using BPrivate::BUrlProtocolOption; + + +static const int32 kHttpProtocolReceiveBufferSize = 1024; +static const char* kHttpProtocolThreadStrStatus[ + B_PROT_HTTP_THREAD_STATUS__END - B_PROT_THREAD_STATUS__END] + = { + "The remote server did not found the requested resource" + }; + + +BUrlProtocolHttp::BUrlProtocolHttp(BUrl& url, BUrlProtocolListener* listener, + BUrlContext* context, BUrlResult* result) + : + BUrlProtocol(url, listener, context, result, "BUrlProtocol.HTTP", "HTTP"), + fRequestMethod(B_HTTP_GET), + fHttpVersion(B_HTTP_11) +{ + _ResetOptions(); +} + + +status_t +BUrlProtocolHttp::SetOption(uint32 name, void* value) +{ + BUrlProtocolOption option(value); + + switch (name) { + case B_HTTPOPT_METHOD: + fRequestMethod = option.Int8(); + break; + + case B_HTTPOPT_FOLLOWLOCATION: + fOptFollowLocation = option.Bool(); + break; + + case B_HTTPOPT_MAXREDIRS: + fOptMaxRedirs = option.Int8(); + break; + + case B_HTTPOPT_REFERER: + fOptReferer = option.String(); + break; + + case B_HTTPOPT_USERAGENT: + fOptUserAgent = option.String(); + break; + + case B_HTTPOPT_HEADERS: + fOptHeaders = reinterpret_cast(option.Pointer()); + break; + + case B_HTTPOPT_DISCARD_DATA: + fOptDiscardData = option.Bool(); + break; + + case B_HTTPOPT_DISABLE_LISTENER: + fOptDisableListener = option.Bool(); + break; + + case B_HTTPOPT_AUTOREFERER: + fOptAutoReferer = option.Bool(); + break; + + case B_HTTPOPT_POSTFIELDS: + fOptPostFields = reinterpret_cast(option.Pointer()); + + if (fOptPostFields != NULL) + fRequestMethod = B_HTTP_POST; + break; + + case B_HTTPOPT_INPUTDATA: + fOptInputData = reinterpret_cast(option.Pointer()); + break; + + case B_HTTPOPT_AUTHUSERNAME: + fOptUsername = option.String(); + break; + + case B_HTTPOPT_AUTHPASSWORD: + fOptPassword = option.String(); + break; + + default: + return B_ERROR; + } + + return B_OK; +} + + +/*static*/ bool +BUrlProtocolHttp::IsInformationalStatusCode(int16 code) +{ + return (code >= B_HTTP_STATUS__INFORMATIONAL_BASE) + && (code < B_HTTP_STATUS__INFORMATIONAL_END); +} + + +/*static*/ bool +BUrlProtocolHttp::IsSuccessStatusCode(int16 code) +{ + return (code >= B_HTTP_STATUS__SUCCESS_BASE) + && (code < B_HTTP_STATUS__SUCCESS_END); +} + + +/*static*/ bool +BUrlProtocolHttp::IsRedirectionStatusCode(int16 code) +{ + return (code >= B_HTTP_STATUS__REDIRECTION_BASE) + && (code < B_HTTP_STATUS__REDIRECTION_END); +} + + +/*static*/ bool +BUrlProtocolHttp::IsClientErrorStatusCode(int16 code) +{ + return (code >= B_HTTP_STATUS__CLIENT_ERROR_BASE) + && (code < B_HTTP_STATUS__CLIENT_ERROR_END); +} + + +/*static*/ bool +BUrlProtocolHttp::IsServerErrorStatusCode(int16 code) +{ + return (code >= B_HTTP_STATUS__SERVER_ERROR_BASE) + && (code < B_HTTP_STATUS__SERVER_ERROR_END); +} + + +/*static*/ int16 +BUrlProtocolHttp::StatusCodeClass(int16 code) +{ + if (BUrlProtocolHttp::IsInformationalStatusCode(code)) + return B_HTTP_STATUS_CLASS_INFORMATIONAL; + else if (BUrlProtocolHttp::IsSuccessStatusCode(code)) + return B_HTTP_STATUS_CLASS_SUCCESS; + else if (BUrlProtocolHttp::IsRedirectionStatusCode(code)) + return B_HTTP_STATUS_CLASS_REDIRECTION; + else if (BUrlProtocolHttp::IsClientErrorStatusCode(code)) + return B_HTTP_STATUS_CLASS_CLIENT_ERROR; + else if (BUrlProtocolHttp::IsServerErrorStatusCode(code)) + return B_HTTP_STATUS_CLASS_SERVER_ERROR; + + return B_HTTP_STATUS_CLASS_INVALID; +} + + +const char* +BUrlProtocolHttp::StatusString(status_t threadStatus) const +{ + if (threadStatus < B_PROT_THREAD_STATUS__END) + return BUrlProtocol::StatusString(threadStatus); + else if (threadStatus >= B_PROT_HTTP_THREAD_STATUS__END) + return BUrlProtocol::StatusString(-1); + else + return kHttpProtocolThreadStrStatus[threadStatus + - B_PROT_THREAD_STATUS__END]; +} + + +void +BUrlProtocolHttp::_ResetOptions() +{ + fOptFollowLocation = true; + fOptMaxRedirs = 8; + fOptReferer = ""; + fOptUserAgent = "Services Kit (Haiku)"; + fOptUsername = ""; + fOptPassword = ""; + fOptAuthMethods = B_HTTP_AUTHENTICATION_BASIC | B_HTTP_AUTHENTICATION_DIGEST + | B_HTTP_AUTHENTICATION_IE_DIGEST; + fOptHeaders = NULL; + fOptPostFields = NULL; + fOptSetCookies = true; + fOptDiscardData = false; + fOptDisableListener = false; + fOptAutoReferer = true; +} + +#include +status_t +BUrlProtocolHttp::_ProtocolLoop() +{ + printf("UHP[%p]::{Loop} %s\n", this, fUrl.UrlString().String()); + // Socket initialization + fSocket = BNetEndpoint(SOCK_STREAM); + if (fSocket.InitCheck() != B_OK) + return B_PROT_SOCKET_ERROR; + + // Initialize the request redirection loop + int8 maxRedirs = fOptMaxRedirs; + bool newRequest; + + do { + newRequest = false; + + // Result reset + fOutputBuffer.Truncate(0, true); + fOutputHeaders.Clear(); + fHeaders.Clear(); + _ResultHeaders().Clear(); + _ResultRawData().Seek(SEEK_SET, 0); + _ResultRawData().SetSize(0); + + if (!_ResolveHostName()) { + _EmitDebug(B_URL_PROTOCOL_DEBUG_ERROR, + "Unable to resolve hostname, aborting."); + return B_PROT_CANT_RESOLVE_HOSTNAME; + } + + _CreateRequest(); + _AddHeaders(); + _AddOutputBufferLine(""); + + status_t requestStatus = _MakeRequest(); + if (requestStatus != B_PROT_SUCCESS) + return requestStatus; + + // Prepare the referer for the next request if needed + if (fOptAutoReferer) + fOptReferer = fUrl.UrlString(); + + switch (StatusCodeClass(fResult->StatusCode())) { + case B_HTTP_STATUS_CLASS_INFORMATIONAL: + // Header 100:continue should have been + // handled in the _MakeRequest read loop + break; + + case B_HTTP_STATUS_CLASS_SUCCESS: + break; + + case B_HTTP_STATUS_CLASS_REDIRECTION: + // Redirection has been explicitly disabled + if (!fOptFollowLocation) + break; + + // TODO: Some browsers seems to translate POST requests to + // GET when following a 302 redirection + if (fResult->StatusCode() == B_HTTP_STATUS_MOVED_PERMANENTLY) { + BString locationUrl = fHeaders["Location"]; + + // Absolute path + if (locationUrl[0] == '/') + fUrl.SetPath(locationUrl); + // URI + else + fUrl.SetUrlString(locationUrl); + + if (--maxRedirs > 0) { + newRequest = true; + + _EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT, + "Following: %s\n", + fUrl.UrlString().String()); + } + } + break; + + case B_HTTP_STATUS_CLASS_CLIENT_ERROR: + switch (fResult->StatusCode()) { + case B_HTTP_STATUS_UNAUTHORIZED: + if (fAuthentication.Method() != B_HTTP_AUTHENTICATION_NONE) { + newRequest = false; + break; + } + + newRequest = false; + if (fOptUsername.Length() > 0 + && fAuthentication.Initialize(fHeaders["WWW-Authenticate"]) + == B_OK) { + fAuthentication.SetUserName(fOptUsername); + fAuthentication.SetPassword(fOptPassword); + newRequest = true; + } + break; + } + break; + + case B_HTTP_STATUS_CLASS_SERVER_ERROR: + break; + + default: + case B_HTTP_STATUS_CLASS_INVALID: + break; + } + } while (newRequest); + + _EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT, + "%ld headers and %ld bytes of data remaining", + fHeaders.CountHeaders(), + fInputBuffer.Size()); + + if (fResult->StatusCode() == 404) + return B_PROT_HTTP_NOT_FOUND; + + return B_PROT_SUCCESS; +} + + +bool +BUrlProtocolHttp::_ResolveHostName() +{ + _EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT, "Resolving %s", + fUrl.UrlString().String()); + + if (fUrl.HasPort()) + fRemoteAddr = BNetAddress(fUrl.Host(), fUrl.Port()); + else + fRemoteAddr = BNetAddress(fUrl.Host(), 80); + + if (fRemoteAddr.InitCheck() != B_OK) + return false; + + char addr[15]; + struct in_addr ip; + fRemoteAddr.GetAddr(ip); + inet_ntop(AF_INET, &ip, addr, 15); + + //! ProtocolHook:HostnameResolved + if (fListener != NULL) + fListener->HostnameResolved(this, const_cast(addr)); + + _EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT, "Hostname resolved to: %s", addr); + + return true; +} + + +status_t +BUrlProtocolHttp::_MakeRequest() +{ + _EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT, "Connection to %s.", + fUrl.Authority().String()); + status_t connectError = fSocket.Connect(fRemoteAddr); + + if (connectError != B_OK) { + _EmitDebug(B_URL_PROTOCOL_DEBUG_ERROR, "Connection error: %s.", + fSocket.ErrorStr()); + return B_PROT_CONNECTION_FAILED; + } + + //! ProtocolHook:ConnectionOpened + if (fListener != NULL) + fListener->ConnectionOpened(this); + + _EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT, "Connection opened."); + + _EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT, "Sending request (size=%d)", + fOutputBuffer.Length()); + fSocket.Send(fOutputBuffer.String(), fOutputBuffer.Length()); + fOutputBuffer.Truncate(0); + _EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT, "Request sent."); + + if (fRequestMethod == B_HTTP_POST && fOptPostFields != NULL) { + if (fOptPostFields->GetFormType() != B_HTTP_FORM_MULTIPART) { + fOutputBuffer = fOptPostFields->RawData(); + _EmitDebug(B_URL_PROTOCOL_DEBUG_TRANSFER_OUT, + fOutputBuffer.String()); + fSocket.Send(fOutputBuffer.String(), fOutputBuffer.Length()); + } else { + for (BHttpForm::Iterator it = fOptPostFields->GetIterator(); + const BHttpFormData* currentField = it.Next(); + ) { + _EmitDebug(B_URL_PROTOCOL_DEBUG_TRANSFER_OUT, + it.MultipartHeader().String()); + fSocket.Send(it.MultipartHeader().String(), + it.MultipartHeader().Length()); + + switch (currentField->Type()) { + case B_HTTPFORM_UNKNOWN: + ASSERT(0); + break; + + case B_HTTPFORM_STRING: + fSocket.Send(currentField->String().String(), + currentField->String().Length()); + 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) { + fSocket.Send(readBuffer, readSize); + readSize = upFile.Read(readBuffer, 1024); + } + } + break; + + case B_HTTPFORM_BUFFER: + fSocket.Send(currentField->Buffer(), + currentField->BufferSize()); + break; + } + + fSocket.Send("\r\n", 2); + } + + fSocket.Send(fOptPostFields->GetMultipartFooter().String(), + fOptPostFields->GetMultipartFooter().Length()); + } + } else if ((fRequestMethod == B_HTTP_POST || fRequestMethod == B_HTTP_PUT) + && fOptInputData != NULL) { + char outputTempBuffer[1024]; + ssize_t read = 0; + + while (read != -1) { + read = fOptInputData->Read(outputTempBuffer, 1024); + + if (read > 0) { + char hexSize[16]; + size_t hexLength = sprintf(hexSize, "%ld", read); + + fSocket.Send(hexSize, hexLength); + fSocket.Send("\r\n", 2); + fSocket.Send(outputTempBuffer, read); + fSocket.Send("\r\n", 2); + } + } + + fSocket.Send("0\r\n\r\n", 5); + } + fOutputBuffer.Truncate(0, true); + + fSocket.SetNonBlocking(false); + + fStatusReceived = false; + fHeadersReceived = false; + + // Receive loop + bool receiveEnd = false; + bool parseEnd = false; + bool readByChunks = false; + bool readError = false; + int32 receiveBufferSize = 32; + ssize_t bytesRead = 0; + ssize_t bytesReceived = 0; + ssize_t bytesTotal = 0; + char* inputTempBuffer = NULL; + fQuit = false; + + while (!fQuit && !(receiveEnd && parseEnd)) { + if (!receiveEnd) { + bytesRead = fSocket.Receive(fInputBuffer, receiveBufferSize); + + if (bytesRead < 0) { + readError = true; + fQuit = true; + continue; + } else if (bytesRead == 0) + receiveEnd = true; + } + else + bytesRead = 0; + + if (!fStatusReceived) { + _ParseStatus(); + + //! ProtocolHook:ResponseStarted + if (fStatusReceived && fListener != NULL) + fListener->ResponseStarted(this); + } else if (!fHeadersReceived) { + _ParseHeaders(); + + if (fHeadersReceived) { + receiveBufferSize = kHttpProtocolReceiveBufferSize; + _ResultHeaders() = fHeaders; + + //! ProtocolHook:HeadersReceived + if (fListener != NULL) + fListener->HeadersReceived(this); + + // Parse received cookies + if ((fContext != NULL) && fHeaders.HasHeader("Set-Cookie")) { + for (int32 i = 0; i < fHeaders.CountHeaders(); i++) { + if (fHeaders.HeaderAt(i).NameIs("Set-Cookie")) { + } + } + } + + if (BString(fHeaders["Transfer-Encoding"]) == "chunked") + readByChunks = true; + + int32 index = fHeaders.HasHeader("Content-Length"); + if (index != B_ERROR) + bytesTotal = atoi(fHeaders.HeaderAt(index).Value()); + else + bytesTotal = 0; + } + } else { + // If Transfer-Encoding is chunked, we should read a complete + // chunk in buffer before handling it + if (readByChunks) { + _CopyChunkInBuffer(&inputTempBuffer, &bytesRead); + + // A chunk of 0 bytes indicates the end of the chunked transfer + if (bytesRead == 0) { + receiveEnd = true; + } + } + else { + bytesRead = fInputBuffer.Size(); + + if (bytesRead > 0) { + inputTempBuffer = new char[bytesRead]; + fInputBuffer.RemoveData(inputTempBuffer, bytesRead); + } + } + + if (bytesRead > 0) { + bytesReceived += bytesRead; + _EmitDebug(B_URL_PROTOCOL_DEBUG_TRANSFER_IN, "%d bytes", + bytesRead); + + if (fListener != NULL) { + fListener->DataReceived(this, inputTempBuffer, bytesRead); + fListener->DownloadProgress(this, bytesReceived, + bytesTotal); + } + + ssize_t dataWrite = _ResultRawData().Write(inputTempBuffer, + bytesRead); + + if (dataWrite != bytesRead) { + _EmitDebug(B_URL_PROTOCOL_DEBUG_ERROR, + "Unable to write %dbytes of data (%d).", bytesRead, + dataWrite); + return B_PROT_NO_MEMORY; + } + + if (bytesTotal > 0 && bytesReceived >= bytesTotal) + receiveEnd = true; + + delete[] inputTempBuffer; + } + } + + parseEnd = (fInputBuffer.Size() == 0); + } + + fSocket.Close(); + + if (readError) + return B_PROT_READ_FAILED; + + return fQuit?B_PROT_ABORTED:B_PROT_SUCCESS; +} + + +status_t +BUrlProtocolHttp::_GetLine(BString& destString) +{ + // Find a complete line in inputBuffer + uint32 characterIndex = 0; + + while ((characterIndex < fInputBuffer.Size()) + && ((fInputBuffer.Data())[characterIndex] != '\n')) + characterIndex++; + + if (characterIndex == fInputBuffer.Size()) + return B_ERROR; + + char* temporaryBuffer = new(std::nothrow) char[characterIndex + 1]; + fInputBuffer.RemoveData(temporaryBuffer, characterIndex + 1); + + // Strip end-of-line character(s) + if (temporaryBuffer[characterIndex-1] == '\r') + destString.SetTo(temporaryBuffer, characterIndex - 1); + else + destString.SetTo(temporaryBuffer, characterIndex); + + delete[] temporaryBuffer; + return B_OK; +} + + +void +BUrlProtocolHttp::_ParseStatus() +{ + // Status line should be formatted like: HTTP/M.m SSS ... + // With: M = Major version of the protocol + // m = Minor version of the protocol + // SSS = three-digit status code of the response + // ... = additional text info + BString statusLine; + if (_GetLine(statusLine) == B_ERROR) + return; + + if (statusLine.CountChars() < 12) + return; + + fStatusReceived = true; + + BString statusCodeStr; + BString statusText; + statusLine.CopyInto(statusCodeStr, 9, 3); + _SetResultStatusCode(atoi(statusCodeStr.String())); + + statusLine.CopyInto(_ResultStatusText(), 13, statusLine.Length() - 13); + + _EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT, "Status line received: Code %d (%s)", + atoi(statusCodeStr.String()), _ResultStatusText().String()); +} + + +void +BUrlProtocolHttp::_ParseHeaders() +{ + BString currentHeader; + if (_GetLine(currentHeader) == B_ERROR) + return; + + // Empty line + if (currentHeader.Length() == 0) { + fHeadersReceived = true; + return; + } + + _EmitDebug(B_URL_PROTOCOL_DEBUG_HEADER_IN, "%s", currentHeader.String()); + fHeaders.AddHeader(currentHeader.String()); +} + + +void +BUrlProtocolHttp::_CopyChunkInBuffer(char** buffer, ssize_t* bytesReceived) +{ + static ssize_t chunkSize = -1; + BString chunkHeader; + + if (chunkSize >= 0) { + if ((ssize_t)fInputBuffer.Size() >= chunkSize + 2) { + // 2 more bytes to handle the closing CR+LF + *bytesReceived = chunkSize; + *buffer = new char[chunkSize+2]; + fInputBuffer.RemoveData(*buffer, chunkSize+2); + chunkSize = -1; + } else { + *bytesReceived = -1; + *buffer = NULL; + } + } else { + if (_GetLine(chunkHeader) == B_ERROR) { + chunkSize = -1; + *buffer = NULL; + *bytesReceived = -1; + return; + } + + // Format of a chunk header: + // [; optional data] + int32 semiColonIndex = chunkHeader.FindFirst(";", 0); + + // Cut-off optional data if present + if (semiColonIndex != -1) + chunkHeader.Remove(semiColonIndex, + chunkHeader.Length() - semiColonIndex); + + chunkSize = strtol(chunkHeader.String(), NULL, 16); + PRINT(("BHP[%p] Chunk %s=%d\n", this, chunkHeader.String(), chunkSize)); + if (chunkSize == 0) { + fContentReceived = true; + } + + *bytesReceived = -1; + *buffer = NULL; + } +} + + +void +BUrlProtocolHttp::_CreateRequest() +{ + BString request; + + switch (fRequestMethod) { + case B_HTTP_POST: + request << "POST"; + break; + + case B_HTTP_PUT: + request << "PUT"; + break; + + default: + case B_HTTP_GET: + request << "GET"; + break; + } + + if (Url().HasPath()) + request << ' ' << Url().Path(); + else + request << " /"; + + if (Url().HasRequest()) + request << '?' << Url().Request(); + + if (Url().HasFragment()) + request << '#' << Url().Fragment(); + + request << ' '; + + switch (fHttpVersion) { + case B_HTTP_11: + request << "HTTP/1.1"; + break; + + default: + case B_HTTP_10: + request << "HTTP/1.0"; + break; + } + + _AddOutputBufferLine(request.String()); +} + + +void +BUrlProtocolHttp::_AddHeaders() +{ + // HTTP 1.1 additional headers + if (fHttpVersion == B_HTTP_11) { + fOutputHeaders.AddHeader("Host", Url().Host()); + + fOutputHeaders.AddHeader("Accept", "*/*"); + fOutputHeaders.AddHeader("Accept-Encoding", "chunked"); + // Allow the remote server to send dynamic content by chunks + // rather than waiting for the full content to be generated and + // sending us data. + + fOutputHeaders.AddHeader("Connection", "close"); + // Let the remote server close the connection after response since + // we don't handle multiple request on a single connection + } + + // Classic HTTP headers + if (fOptUserAgent.CountChars() > 0) + fOutputHeaders.AddHeader("User-Agent", fOptUserAgent.String()); + + if (fOptReferer.CountChars() > 0) + fOutputHeaders.AddHeader("Referer", fOptReferer.String()); + + // Authentication + if (fAuthentication.Method() != B_HTTP_AUTHENTICATION_NONE) { + BString request; + switch (fRequestMethod) { + case B_HTTP_POST: + request = "POST"; + break; + + case B_HTTP_PUT: + request = "PUT"; + break; + + default: + case B_HTTP_GET: + request = "GET"; + break; + } + + fOutputHeaders.AddHeader("Authorization", + fAuthentication.Authorization(fUrl, request)); + } + + // Required headers for POST data + if (fOptPostFields != NULL && fRequestMethod == B_HTTP_POST) { + BString contentType; + + switch (fOptPostFields->GetFormType()) { + case B_HTTP_FORM_MULTIPART: + contentType << "multipart/form-data; boundary=" + << fOptPostFields->GetMultipartBoundary() << ""; + break; + + case B_HTTP_FORM_URL_ENCODED: + contentType << "application/x-www-form-urlencoded"; + break; + } + + fOutputHeaders.AddHeader("Content-Type", contentType); + fOutputHeaders.AddHeader("Content-Length", + fOptPostFields->ContentLength()); + } else if (fOptInputData != NULL + && (fRequestMethod == B_HTTP_POST || fRequestMethod == B_HTTP_PUT)) + fOutputHeaders.AddHeader("Transfer-Encoding", "chunked"); + + // Request headers + for (int32 headerIndex = 0; + headerIndex < fRequestHeaders.CountHeaders(); + headerIndex++) { + BHttpHeader& optHeader = fRequestHeaders[headerIndex]; + int32 replaceIndex = fOutputHeaders.HasHeader(optHeader.Name()); + + // Add or replace the current option header to the + // output header list + if (replaceIndex == -1) + fOutputHeaders.AddHeader(optHeader.Name(), optHeader.Value()); + else + fOutputHeaders[replaceIndex].SetValue(optHeader.Value()); + } + + // Optional headers specified by the user + if (fOptHeaders != NULL) { + for (int32 headerIndex = 0; + headerIndex < fOptHeaders->CountHeaders(); + headerIndex++) { + BHttpHeader& optHeader = (*fOptHeaders)[headerIndex]; + int32 replaceIndex = fOutputHeaders.HasHeader(optHeader.Name()); + + // Add or replace the current option header to the + // output header list + if (replaceIndex == -1) + fOutputHeaders.AddHeader(optHeader.Name(), optHeader.Value()); + else + fOutputHeaders[replaceIndex].SetValue(optHeader.Value()); + } + } + + // Context cookies + if (fOptSetCookies && (fContext != NULL)) { + BNetworkCookie* cookie; + + for (BNetworkCookieJar::UrlIterator + it(fContext->GetCookieJar().GetUrlIterator(fUrl)); + (cookie = it.Next()) != NULL; + ) + fOutputHeaders.AddHeader("Cookie", cookie->RawCookie(false)); + } + + // Write output headers to output stream + for (int32 headerIndex = 0; + headerIndex < fOutputHeaders.CountHeaders(); + headerIndex++) + _AddOutputBufferLine(fOutputHeaders.HeaderAt(headerIndex).Header()); +} + + +void +BUrlProtocolHttp::_AddOutputBufferLine(const char* line) +{ + _EmitDebug(B_URL_PROTOCOL_DEBUG_HEADER_OUT, "%s", line); + fOutputBuffer << line << "\r\n"; +} diff --git a/src/kits/network/libnetapi/UrlProtocolListener.cpp b/src/kits/network/libnetapi/UrlProtocolListener.cpp new file mode 100644 index 0000000000..b26d088d8d --- /dev/null +++ b/src/kits/network/libnetapi/UrlProtocolListener.cpp @@ -0,0 +1,91 @@ +/* + * Copyright 2010 Haiku Inc. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Christophe Huriaux, c.huriaux@gmail.com + */ + +#include +#include + +#include +#include + +using namespace std; + + +void +BUrlProtocolListener::ConnectionOpened(BUrlProtocol*) +{ +} + + +void +BUrlProtocolListener::HostnameResolved(BUrlProtocol*, const char*) +{ +} + + +void +BUrlProtocolListener::ResponseStarted(BUrlProtocol*) +{ +} + + +void +BUrlProtocolListener::HeadersReceived(BUrlProtocol*) +{ +} + + +void +BUrlProtocolListener::DataReceived(BUrlProtocol*, const char*, ssize_t) +{ +} + + +void +BUrlProtocolListener::DownloadProgress(BUrlProtocol*, ssize_t, ssize_t) +{ +} + + +void +BUrlProtocolListener::UploadProgress(BUrlProtocol*, ssize_t, ssize_t) +{ +} + + +void +BUrlProtocolListener::RequestCompleted(BUrlProtocol*, bool) +{ +} + + +void +BUrlProtocolListener::DebugMessage(BUrlProtocol* caller, + BUrlProtocolDebugMessage type, const char* text) +{ + switch (type) { + case B_URL_PROTOCOL_DEBUG_TEXT: + cout << " "; + break; + + case B_URL_PROTOCOL_DEBUG_ERROR: + cout << "!!!"; + break; + + case B_URL_PROTOCOL_DEBUG_TRANSFER_IN: + case B_URL_PROTOCOL_DEBUG_HEADER_IN: + cout << "<--"; + break; + + case B_URL_PROTOCOL_DEBUG_TRANSFER_OUT: + case B_URL_PROTOCOL_DEBUG_HEADER_OUT: + cout << "-->"; + break; + } + + cout << " " << caller->Protocol() << ": " << text << endl; +} diff --git a/src/kits/network/libnetapi/UrlRequest.cpp b/src/kits/network/libnetapi/UrlRequest.cpp new file mode 100644 index 0000000000..bc1a92417f --- /dev/null +++ b/src/kits/network/libnetapi/UrlRequest.cpp @@ -0,0 +1,245 @@ +/* + * Copyright 2010 Haiku Inc. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Christophe Huriaux, c.huriaux@gmail.com + */ + +#include + +#include +#include +#include + + +BUrlRequest::BUrlRequest(const BUrl& url, BUrlProtocolListener* listener) + : + fListener(listener), + fUrlProtocol(NULL), + fResult(url), + fContext(), + fUrl(), + fReady(false) +{ + SetUrl(url); +} + + +BUrlRequest::BUrlRequest(const BUrl& url) + : + fListener(NULL), + fUrlProtocol(NULL), + fResult(url), + fContext(), + fUrl(), + fReady(false) +{ + SetUrl(url); +} + + +BUrlRequest::BUrlRequest(const BUrlRequest& other) + : + fListener(NULL), + fUrlProtocol(NULL), + fResult(other.fUrl), + fContext(), + fUrl(), + fReady(false) +{ + *this = other; +} + + +// #pragma mark Request parameters modification + + +status_t +BUrlRequest::SetUrl(const BUrl& url) +{ + fUrl = url; + fResult.SetUrl(url); + + if (fUrlProtocol != NULL && url.Protocol() == fUrl.Protocol()) + fUrlProtocol->SetUrl(url); + else { + status_t err = Identify(); + if (err != B_OK) + return err; + } + + return B_OK; +} + + +void +BUrlRequest::SetContext(BUrlContext* context) +{ + fContext = context; +} + + +void +BUrlRequest::SetProtocolListener(BUrlProtocolListener* listener) +{ + fListener = listener; + + if (fUrlProtocol != NULL) + fUrlProtocol->SetListener(listener); +} + + +bool +BUrlRequest::SetProtocolOption(int32 option, void* value) +{ + if (fUrlProtocol == NULL) + return false; + + fUrlProtocol->SetOption(option, value); + return true; +} + + +// #pragma mark Request parameters access + + +const BUrlProtocol* +BUrlRequest::Protocol() +{ + return fUrlProtocol; +} + + +const BUrlResult& +BUrlRequest::Result() +{ + return fResult; +} + + +const BUrl& +BUrlRequest::Url() +{ + return fUrl; +} + + +// #pragma mark Request control + + +status_t +BUrlRequest::Identify() +{ + // TODO: instanciate the correct BUrlProtocol w/ the services roster + delete fUrlProtocol; + fUrlProtocol = NULL; + + if (fUrl.Protocol() == "http") { + fUrlProtocol = new(std::nothrow) BUrlHttpProtocol(fUrl, fListener, fContext, &fResult); + fReady = true; + return B_OK; + } + + fReady = false; + return B_NO_HANDLER_FOR_PROTOCOL; +} + + +status_t +BUrlRequest::Perform() +{ + if (fUrlProtocol == NULL) { + PRINT(("BUrlRequest::Perform() : Oops, no BUrlProtocol defined!\n")); + return B_ERROR; + } + + thread_id protocolThread = fUrlProtocol->Run(); + + if (protocolThread < B_OK) + return protocolThread; + + return B_OK; +} + + +status_t +BUrlRequest::Pause() +{ + if (fUrlProtocol == NULL) + return B_ERROR; + + return fUrlProtocol->Pause(); +} + + +status_t +BUrlRequest::Resume() +{ + if (fUrlProtocol == NULL) + return B_ERROR; + + return fUrlProtocol->Resume(); +} + + +status_t +BUrlRequest::Abort() +{ + if (fUrlProtocol == NULL) + return B_ERROR; + + status_t returnCode = fUrlProtocol->Stop(); + delete fUrlProtocol; + fUrlProtocol = NULL; + + return returnCode; +} + + +// #pragma mark Request informations + + +bool +BUrlRequest::InitCheck() const +{ + return fReady; +} + + +bool +BUrlRequest::IsRunning() const +{ + if (fUrlProtocol == NULL) + return false; + + return fUrlProtocol->IsRunning(); +} + + +status_t +BUrlRequest::Status() const +{ + if (fUrlProtocol == NULL) + return B_ERROR; + + return fUrlProtocol->Status(); +} + + +// #pragma mark Overloaded members + + +BUrlRequest& +BUrlRequest::operator=(const BUrlRequest& other) +{ + delete fUrlProtocol; + fUrlProtocol = NULL; + fUrl = other.fUrl; + fListener = other.fListener; + fContext = other.fContext; + fResult = BUrlResult(other.fUrl); + SetUrl(other.fUrl); + + return *this; +} diff --git a/src/kits/network/libnetapi/UrlResult.cpp b/src/kits/network/libnetapi/UrlResult.cpp new file mode 100644 index 0000000000..260503b58e --- /dev/null +++ b/src/kits/network/libnetapi/UrlResult.cpp @@ -0,0 +1,117 @@ +/* + * Copyright 2010 Haiku Inc. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Christophe Huriaux, c.huriaux@gmail.com + */ + + +#include +#include + + +using std::ostream; + + +BUrlResult::BUrlResult(const BUrl& url) + : + fUrl(url), + fRawData(), + fHeaders() +{ +} + + +BUrlResult::BUrlResult(const BUrlResult& other) + : + fUrl(), + fRawData(), + fHeaders() +{ + *this = other; +} + + +// #pragma mark Result parameters modifications + + +void +BUrlResult::SetUrl(const BUrl& url) +{ + fUrl = url; +} + + +// #pragma mark Result parameters access + + +const BUrl& +BUrlResult::Url() const +{ + return fUrl; +} + + +const BMallocIO& +BUrlResult::RawData() const +{ + return fRawData; +} + + +const BHttpHeaders& +BUrlResult::Headers() const +{ + return fHeaders; +} + + +int32 +BUrlResult::StatusCode() const +{ + return fStatusCode; +} + + +const BString& +BUrlResult::StatusText() const +{ + return fStatusString; +} + + +// #pragma mark Result tests + + +bool +BUrlResult::HasHeaders() const +{ + return (fHeaders.CountHeaders() > 0); +} + + +// #pragma mark Overloaded operators + + +BUrlResult& +BUrlResult::operator=(const BUrlResult& other) +{ + fUrl = other.fUrl; + fHeaders = other.fHeaders; + + fRawData.SetSize(other.fRawData.BufferLength()); + fRawData.WriteAt(0, fRawData.Buffer(), fRawData.BufferLength()); + + return *this; +} + + +ostream& +operator<<(ostream& out, const BUrlResult& result) +{ + out.write(reinterpret_cast(result.fRawData.Buffer()), + result.fRawData.BufferLength()); + + return out; +} diff --git a/src/kits/network/libnetapi/UrlSynchronousRequest.cpp b/src/kits/network/libnetapi/UrlSynchronousRequest.cpp new file mode 100644 index 0000000000..ae95424f95 --- /dev/null +++ b/src/kits/network/libnetapi/UrlSynchronousRequest.cpp @@ -0,0 +1,104 @@ +/* + * Copyright 2010 Haiku Inc. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Christophe Huriaux, c.huriaux@gmail.com + */ + +#include + +#include + +#define PRINT(x) printf x; + + +BUrlSynchronousRequest::BUrlSynchronousRequest(BUrl& url) + : + BUrlRequest(url, this), + fRequestComplete(false) +{ +} + + +status_t +BUrlSynchronousRequest::Perform() +{ + SetProtocolListener(this); + fRequestComplete = false; + + return BUrlRequest::Perform(); +} + + +status_t +BUrlSynchronousRequest::WaitUntilCompletion() +{ + while (!fRequestComplete) + snooze(10000); + + return B_OK; +} + + +void +BUrlSynchronousRequest::ConnectionOpened(BUrlProtocol*) +{ + PRINT(("SynchronousRequest::ConnectionOpened()\n")); +} + + +void +BUrlSynchronousRequest::HostnameResolved(BUrlProtocol*, const char* ip) +{ + PRINT(("SynchronousRequest::HostnameResolved(%s)\n", ip)); +} + + +void +BUrlSynchronousRequest::ResponseStarted(BUrlProtocol*) +{ + PRINT(("SynchronousRequest::ResponseStarted()\n")); +} + + +void +BUrlSynchronousRequest::HeadersReceived(BUrlProtocol*) +{ + PRINT(("SynchronousRequest::HeadersReceived()\n")); +} + + +void +BUrlSynchronousRequest::DataReceived(BUrlProtocol*, const char*, + ssize_t size) +{ + PRINT(("SynchronousRequest::DataReceived(%zd)\n", size)); +} + + +void +BUrlSynchronousRequest::DownloadProgress(BUrlProtocol*, + ssize_t bytesReceived, ssize_t bytesTotal) +{ + PRINT(("SynchronousRequest::DownloadProgress(%zd, %zd)\n", bytesReceived, + bytesTotal)); +} + + +void +BUrlSynchronousRequest::UploadProgress(BUrlProtocol*, ssize_t bytesSent, + ssize_t bytesTotal) +{ + PRINT(("SynchronousRequest::UploadProgress(%zd, %zd)\n", bytesSent, + bytesTotal)); +} + + +void +BUrlSynchronousRequest::RequestCompleted(BUrlProtocol* caller, bool success) +{ + PRINT(("SynchronousRequest::RequestCompleted(%s) : %s\n", (success?"true":"false"), + caller->StatusString(caller->Status()))); + fRequestComplete = true; +}