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; +}