[haiku-commits] haiku: hrev47571 - src/kits/network/libnetapi headers/os/net

  • From: revol@xxxxxxx
  • To: haiku-commits@xxxxxxxxxxxxx
  • Date: Sat, 26 Jul 2014 01:47:21 +0200 (CEST)

hrev47571 adds 9 changesets to branch 'master'
old head: 1b4510eebfb4aa1975fef0c46f90f4a1dfd71159
new head: e95d0f00ceeeb436c9f279824c44da038db9d246
overview: http://cgit.haiku-os.org/haiku/log/?qt=range&q=e95d0f0+%5E1b4510e

----------------------------------------------------------------------------

0c1a4eb: Preliminary support for Gopher
  
  Currently parses information and text items and retrives files.

2e8b8fd: gopher: Handle binary, directory and error types

f74e08f: gopher: Handle info resources and add proper title
  
  We now create a proper title from the error message, or
  the TITLE resource if present.

0e48c9a: gopher: Handle some more item types

ec0e815: gopher: Handle audio and video types, add a default case

6983b35: gopher: Add a stylesheet
  
  Modified version from my attempt at adding gopher to NetSurf.

0716bfd: gopher: remove debug printfs

cf2bf30: gopher: Add TODOs

e95d0f0: gopher: Set a default MIME type to force downloading

                                          [ François Revol <revol@xxxxxxx> ]

----------------------------------------------------------------------------

4 files changed, 805 insertions(+)
headers/os/net/GopherRequest.h                   |  55 ++
src/kits/network/libnetapi/GopherRequest.cpp     | 744 +++++++++++++++++++
src/kits/network/libnetapi/Jamfile               |   3 +
src/kits/network/libnetapi/UrlProtocolRoster.cpp |   3 +

############################################################################

Commit:      0c1a4ebf8b65b69d3a6606b3578ba83155f47b58
URL:         http://cgit.haiku-os.org/haiku/commit/?id=0c1a4eb
Author:      François Revol <revol@xxxxxxx>
Date:        Fri Jul 25 20:08:59 2014 UTC

Preliminary support for Gopher

Currently parses information and text items and retrives files.

----------------------------------------------------------------------------

diff --git a/headers/os/net/GopherRequest.h b/headers/os/net/GopherRequest.h
new file mode 100644
index 0000000..9173349
--- /dev/null
+++ b/headers/os/net/GopherRequest.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2014 Haiku Inc. All rights reserved.
+ * Distributed under the terms of the MIT License.
+ */
+#ifndef _B_GOPHER_REQUEST_H_
+#define _B_GOPHER_REQUEST_H_
+
+
+#include <deque>
+
+#include <NetBuffer.h>
+#include <NetworkAddress.h>
+#include <UrlRequest.h>
+
+
+class BAbstractSocket;
+
+
+class BGopherRequest : public BUrlRequest {
+public:
+                                                               
BGopherRequest(const BUrl& url,
+                                                                       
BUrlProtocolListener* listener = NULL,
+                                                                       
BUrlContext* context = NULL);
+       virtual                                         ~BGopherRequest();
+
+                       status_t                        Stop();
+       const   BUrlResult&                     Result() const;
+            void                SetDisableListener(bool disable);
+
+private:
+                       status_t                        _ProtocolLoop();
+                       bool                            _ResolveHostName();
+                       void                            _SendRequest();
+
+                       bool                            _NeedsParsing();
+                       bool                            _NeedsLastDotStrip();
+                       void                            _ParseInput(bool last);
+
+                       status_t                        _GetLine(BString& 
destString);
+                       BString&                        
_HTMLEscapeString(BString &str);
+
+private:
+                       char                            fItemType;
+                       BString                         fPath;
+                       BAbstractSocket*        fSocket;
+                       BNetworkAddress         fRemoteAddr;
+
+                       BNetBuffer                      fInputBuffer;
+                       ssize_t                         fPosition;
+
+                       BUrlResult                      fResult;
+};
+
+
+#endif // _B_GOPHER_REQUEST_H_
diff --git a/src/kits/network/libnetapi/GopherRequest.cpp 
b/src/kits/network/libnetapi/GopherRequest.cpp
new file mode 100644
index 0000000..1d193f9
--- /dev/null
+++ b/src/kits/network/libnetapi/GopherRequest.cpp
@@ -0,0 +1,531 @@
+/*
+ * Copyright 2013-2014 Haiku Inc. All rights reserved.
+ * Distributed under the terms of the MIT License.
+ *
+ * Authors:
+ *             François Revol, revol@xxxxxxx
+ */
+
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <Directory.h>
+#include <DynamicBuffer.h>
+#include <File.h>
+#include <GopherRequest.h>
+#include <NodeInfo.h>
+#include <Path.h>
+#include <Socket.h>
+#include <String.h>
+#include <StringList.h>
+
+/*
+ * TODO: move parsing stuff to a translator?
+ *
+ * docs:
+ * gopher://gopher.floodgap.com/1/gopher/tech
+ * gopher://gopher.floodgap.com/0/overbite/dbrowse?pluginm%201
+ *
+ * tests:
+ * gopher://sdf.org/1/sdf/historical   images
+ * gopher://gopher.r-36.net/1/ large photos
+ * gopher://sdf.org/1/sdf/classes      binaries
+ * gopher://sdf.org/1/users/   long page
+ * gopher://jgw.mdns.org/1/    search items
+ * gopher://jgw.mdns.org/1/MISC/       's' item (sound)
+ * gopher://gopher.floodgap.com/1/gopher       broken link
+ * gopher://sdf.org/1/maps/m   missing lines
+ */
+
+/** Type of Gopher items */
+typedef enum {
+       GOPHER_TYPE_NONE        = 0,    /**< none set */
+       GOPHER_TYPE_ENDOFPAGE   = '.',  /**< a dot alone on a line */
+       /* these come from http://tools.ietf.org/html/rfc1436 */
+       GOPHER_TYPE_TEXTPLAIN   = '0',  /**< text/plain */
+       GOPHER_TYPE_DIRECTORY   = '1',  /**< gopher directory */
+       GOPHER_TYPE_CSO_SEARCH  = '2',  /**< CSO search */
+       GOPHER_TYPE_ERROR       = '3',  /**< error message */
+       GOPHER_TYPE_BINHEX      = '4',  /**< binhex encoded text */
+       GOPHER_TYPE_BINARCHIVE  = '5',  /**< binary archive file */
+       GOPHER_TYPE_UUENCODED   = '6',  /**< uuencoded text */
+       GOPHER_TYPE_QUERY       = '7',  /**< gopher search query */
+       GOPHER_TYPE_TELNET      = '8',  /**< telnet link */
+       GOPHER_TYPE_BINARY      = '9',  /**< generic binary */
+       GOPHER_TYPE_DUPSERV     = '+',  /**< duplicated server */
+       GOPHER_TYPE_GIF         = 'g',  /**< GIF image */
+       GOPHER_TYPE_IMAGE       = 'I',  /**< image (depends, usually jpeg) */
+       GOPHER_TYPE_TN3270      = 'T',  /**< tn3270 session */
+       /* not standardized but widely used,
+        * cf. 
http://en.wikipedia.org/wiki/Gopher_%28protocol%29#Gopher_item_types
+        */
+       GOPHER_TYPE_HTML        = 'h',  /**< HTML file or URL */
+       GOPHER_TYPE_INFO        = 'i',  /**< information text */
+       GOPHER_TYPE_AUDIO       = 's',  /**< audio (wav?) */
+       /* not standardized, some servers use them */
+       GOPHER_TYPE_PDF_ALT     = 'd',  /**< seems to be only for PDF files */
+       GOPHER_TYPE_PNG         = 'p',  /**< PNG image */
+               /* cf. gopher://namcub.accelera-labs.com/1/pics */
+       GOPHER_TYPE_MIME        = 'M',  /**< multipart/mixed MIME data */
+               /* cf. 
http://www.pms.ifi.lmu.de/mitarbeiter/ohlbach/multimedia/IT/IBMtutorial/3376c61.html
 */
+       /* cf. 
http://nofixedpoint.motd.org/2011/02/22/an-introduction-to-the-gopher-protocol/ 
*/
+       GOPHER_TYPE_PDF         = 'P',  /**< PDF file */
+       GOPHER_TYPE_BITMAP      = ':',  /**< Bitmap image (Gopher+) */
+       GOPHER_TYPE_MOVIE       = ';',  /**< Movie (Gopher+) */
+       GOPHER_TYPE_SOUND       = '<',  /**< Sound (Gopher+) */
+       GOPHER_TYPE_CALENDAR    = 'c',  /**< Calendar */
+       GOPHER_TYPE_EVENT       = 'e',  /**< Event */
+       GOPHER_TYPE_MBOX        = 'm',  /**< mbox file */
+} gopher_item_type;
+
+/** Types of fields in a line */
+typedef enum {
+       FIELD_NAME,
+       FIELD_SELECTOR,
+       FIELD_HOST,
+       FIELD_PORT,
+       FIELD_GPFLAG,
+       FIELD_EOL,
+       FIELD_COUNT = FIELD_EOL
+} gopher_field;
+
+/** Map of gopher types to MIME types */
+static struct {
+       gopher_item_type type;
+       const char *mime;
+} gopher_type_map[] = {
+       /* these come from http://tools.ietf.org/html/rfc1436 */
+       { GOPHER_TYPE_TEXTPLAIN, "text/plain" },
+       { GOPHER_TYPE_DIRECTORY, "text/html;charset=UTF-8" },
+       { GOPHER_TYPE_QUERY, "text/html;charset=UTF-8" },
+       { GOPHER_TYPE_GIF, "image/gif" },
+       { GOPHER_TYPE_HTML, "text/html" },
+       /* those are not standardized */
+       { GOPHER_TYPE_PDF_ALT, "application/pdf" },
+       { GOPHER_TYPE_PDF, "application/pdf" },
+       { GOPHER_TYPE_PNG, "image/png"},
+       { GOPHER_TYPE_NONE, NULL }
+};
+
+static const int32 kGopherBufferSize = 4096;
+
+
+
+
+BGopherRequest::BGopherRequest(const BUrl& url, BUrlProtocolListener* listener,
+       BUrlContext* context)
+       :
+       BUrlRequest(url, listener, context, "BUrlProtocol.Gopher", "gopher"),
+       fItemType(GOPHER_TYPE_NONE),
+       fPosition(0),
+       fResult()
+{
+       fSocket = new(std::nothrow) BSocket();
+
+       fUrl.UrlDecode();
+       // the first part of the path is actually the document type
+
+       fPath = Url().Path();
+       if (!Url().HasPath() || (fPath.Length() == 1 && fPath[0] == '/')) {
+               // default entry
+               fItemType = GOPHER_TYPE_DIRECTORY;
+               fPath = "";
+       } else if (fPath.Length() > 1 && fPath[0] == '/') {
+               fItemType = fPath[1];
+               fPath.Remove(0, 2);
+       }
+       fprintf(stderr, "t: '%c' p:'%s'\n", fItemType, fPath.String());
+}
+
+
+BGopherRequest::~BGopherRequest()
+{
+       Stop();
+
+       delete fSocket;
+}
+
+
+status_t
+BGopherRequest::Stop()
+{
+       if (fSocket != NULL) {
+               fSocket->Disconnect();
+                       // Unlock any pending connect, read or write operation.
+       }
+       return BUrlRequest::Stop();
+}
+
+
+const BUrlResult&
+BGopherRequest::Result() const
+{
+       return fResult;
+}
+
+
+status_t
+BGopherRequest::_ProtocolLoop()
+{
+       if (fSocket == NULL)
+               return B_NO_MEMORY;
+
+       if (!_ResolveHostName()) {
+               _EmitDebug(B_URL_PROTOCOL_DEBUG_ERROR,
+                       "Unable to resolve hostname (%s), aborting.",
+                               fUrl.Host().String());
+               return B_SERVER_NOT_FOUND;
+       }
+
+       _EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT, "Connection to %s on port %d.",
+               fUrl.Authority().String(), fRemoteAddr.Port());
+       status_t connectError = fSocket->Connect(fRemoteAddr);
+
+       if (connectError != B_OK) {
+               _EmitDebug(B_URL_PROTOCOL_DEBUG_ERROR, "Socket connection error 
%s",
+                       strerror(connectError));
+               return connectError;
+       }
+
+       //! ProtocolHook:ConnectionOpened
+       if (fListener != NULL)
+               fListener->ConnectionOpened(this);
+
+       _EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT,
+               "Connection opened, sending request.");
+
+       _SendRequest();
+       _EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT, "Request sent.");
+
+       // Receive loop
+       bool receiveEnd = false;
+       status_t readError = B_OK;
+       ssize_t bytesRead = 0;
+       //ssize_t bytesReceived = 0;
+       //ssize_t bytesTotal = 0;
+       bool dataValidated = false;
+
+       while (!fQuit && !receiveEnd) {
+               fSocket->WaitForReadable();
+               BNetBuffer chunk(kGopherBufferSize);
+               bytesRead = fSocket->Read(chunk.Data(), kGopherBufferSize);
+               fprintf(stderr, "Read: %d\n", (int)bytesRead);
+
+               if (bytesRead < 0) {
+                       readError = bytesRead;
+                       break;
+               } else if (bytesRead == 0)
+                       receiveEnd = true;
+
+               fInputBuffer.AppendData(chunk.Data(), bytesRead);
+
+               if (!dataValidated) {
+                       size_t i;
+                       // on error (file doesn't exist, ...) the server sends
+                       // a faked directory entry with an error message
+                       if (fInputBuffer.Size() && fInputBuffer.Data()[0] == 
'3') {
+                               int tabs = 0;
+                               bool crlf = false;
+
+                               // make sure the buffer only contains printable 
characters
+                               // and has at least 3 tabs before a CRLF
+                               for (i = 0; i < fInputBuffer.Size(); i++) {
+                                       char c = fInputBuffer.Data()[i];
+                                       if (c == '\t') {
+                                               fprintf(stderr, "tab\n");
+                                               if (!crlf)
+                                                       tabs++;
+                                       } else if (c == '\r' || c == '\n') {
+                                               fprintf(stderr, "crlf\n");
+                                               if (tabs < 3)
+                                                       break;
+                                               crlf = true;
+                                       } else if 
(!isprint(fInputBuffer.Data()[i])) {
+                                               fprintf(stderr, "!isprint at 
%lu\n", i);
+                                               crlf = false;
+                                               break;
+                                       }
+                               }
+                               if (crlf && tabs > 2 && tabs < 5) {
+                                       fprintf(stderr, "error\n");
+                                       // TODO:
+                                       //if enough data
+                                       // else continue
+                                       fItemType = GOPHER_TYPE_DIRECTORY;
+                                       //readError = B_RESOURCE_NOT_FOUND;
+                                       // continue parsing the error text 
anyway
+                               }
+                       }
+
+                       // now we probably have correct data
+                       dataValidated = true;
+
+                       //! ProtocolHook:ResponseStarted
+                       if (fListener != NULL)
+                               fListener->ResponseStarted(this);
+
+                       // we don't really have headers but well...
+                       //! ProtocolHook:HeadersReceived
+                       if (fListener != NULL)
+                               fListener->HeadersReceived(this);
+
+                       // now we can assign MIME type if we know it
+                       for (i = 0; gopher_type_map[i].type != 
GOPHER_TYPE_NONE; i++) {
+                               if (gopher_type_map[i].type == fItemType) {
+                                       fprintf(stderr, "MIME:'%s'\n", 
gopher_type_map[i].mime);
+                                       
fResult.SetContentType(gopher_type_map[i].mime);
+                                       break;
+                               }
+                       }
+               }
+
+               if (_NeedsParsing())
+                       _ParseInput(receiveEnd);
+               else if (fInputBuffer.Size()) {
+                       // send input directly
+                       fListener->DataReceived(this, (const char 
*)fInputBuffer.Data(),
+                                                               fPosition, 
fInputBuffer.Size());
+
+                       fPosition += fInputBuffer.Size();
+
+                       // XXX: this is plain stupid, we already copied the data
+                       // and just want to drop it...
+                       char *inputTempBuffer = new(std::nothrow) 
char[bytesRead];
+                       if (inputTempBuffer == NULL) {
+                               readError = B_NO_MEMORY;
+                               break;
+                       }
+                       fInputBuffer.RemoveData(inputTempBuffer, 
fInputBuffer.Size());
+                       delete[] inputTempBuffer;
+               }
+       }
+
+       if (fPosition > 0) {
+               fResult.SetLength(fPosition);
+               fListener->DownloadProgress(this, fPosition, fPosition);
+       }
+
+       fSocket->Disconnect();
+
+       if (readError != B_OK)
+               return readError;
+
+       return fQuit ? B_INTERRUPTED : B_OK;
+}
+
+
+bool
+BGopherRequest::_ResolveHostName()
+{
+       _EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT, "Resolving %s",
+               fUrl.UrlString().String());
+       
+       uint16_t port;
+       if (fUrl.HasPort())
+               port = fUrl.Port();
+       else
+               port = 70;
+
+       // FIXME stop forcing AF_INET, when BNetworkAddress stops giving IPv6
+       // addresses when there isn't an IPv6 link available.
+       fRemoteAddr = BNetworkAddress(AF_INET, fUrl.Host(), port);
+
+       if (fRemoteAddr.InitCheck() != B_OK)
+               return false;
+
+       //! ProtocolHook:HostnameResolved
+       if (fListener != NULL)
+               fListener->HostnameResolved(this, 
fRemoteAddr.ToString().String());
+
+       _EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT, "Hostname resolved to: %s",
+               fRemoteAddr.ToString().String());
+
+       return true;
+}
+
+
+void
+BGopherRequest::_SendRequest()
+{
+       BString request;
+
+       request << fPath;
+
+       if (Url().HasRequest())
+               request << '\t' << Url().Request();
+
+       request << "\r\n";
+
+       fSocket->Write(request.String(), request.Length());
+}
+
+
+bool
+BGopherRequest::_NeedsParsing()
+{
+       if (fItemType == GOPHER_TYPE_DIRECTORY
+               || fItemType == GOPHER_TYPE_QUERY)
+               return true;
+       return false;
+}
+
+
+bool
+BGopherRequest::_NeedsLastDotStrip()
+{
+       if (fItemType == GOPHER_TYPE_DIRECTORY
+               || fItemType == GOPHER_TYPE_QUERY
+               || fItemType == GOPHER_TYPE_TEXTPLAIN)
+               return true;
+       return false;
+}
+
+
+void
+BGopherRequest::_ParseInput(bool last)
+{
+       BString line;
+
+       while (_GetLine(line) == B_OK) {
+               char type = GOPHER_TYPE_NONE;
+               BStringList fields;
+
+               line.MoveInto(&type, 0, 1);
+
+               line.Split("\t", false, fields);
+
+               if (type != GOPHER_TYPE_ENDOFPAGE
+                       && fields.CountStrings() < FIELD_GPFLAG)
+                       _EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT,
+                               "Unterminated gopher item (type '%c')", type);
+
+               fprintf(stderr, "type: '%c' name: '%s'\n", type, 
fields.StringAt(FIELD_NAME).String());
+               //fields.PrintToStream();
+
+               BString item;
+               BString title = fields.StringAt(FIELD_NAME);
+               BString link("gopher://";);
+               if (fields.CountStrings() > 3) {
+                       link << fields.StringAt(FIELD_HOST);
+                       if (fields.StringAt(FIELD_PORT).Length())
+                               link << ":" << fields.StringAt(FIELD_PORT);
+                       link << "/" << type;
+                       //if (fields.StringAt(FIELD_SELECTOR).ByteAt(0) != '/')
+                       //      link << "/";
+                       link << fields.StringAt(FIELD_SELECTOR);
+               }
+               fprintf(stderr, "link: '%s'\n", link.String());
+               _HTMLEscapeString(title);
+               _HTMLEscapeString(link);
+
+               switch (type) {
+                       case GOPHER_TYPE_ENDOFPAGE:
+                               /* end of the page */
+                               break;
+                       case GOPHER_TYPE_TEXTPLAIN:
+                               item << "<a href=\"" << link << "\">"
+                                               "<span class=\"text\">" << 
title << "</span></a>"
+                                               "<br/>\n";
+                               break;
+                       default:
+                               item << "<div>" << fields.StringAt(FIELD_NAME) 
<< "</div>";
+                               break;
+               }
+
+               if (fPosition == 0) {
+                       BString title = "TITLE";
+                       const char *uplink = ".";
+                       if (fPath.EndsWith("/"))
+                               uplink = "..";
+
+                       // emit header
+                       BString header;
+                       header << 
+                               "<html>\n"
+                               "<head>\n"
+                               "<meta http-equiv=\"Content-Type\""
+                                       " content=\"text/html; charset=UTF-8\" 
/>\n"
+                               //FIXME: fix links
+                               "<link rel=\"stylesheet\" title=\"Standard\" "
+                                       "type=\"text/css\" 
href=\"resource:internal.css\">\n"
+                               "<link rel=\"icon\" type=\"image/png\""
+                                       " 
href=\"resource:icons/directory.png\">\n"
+                               "<title>" << title << "</title>\n"
+                               "</head>\n"
+                               "<body id=\"gopher\">\n"
+                               "<div class=\"uplink dontprint\">\n"
+                               "<a href=" << uplink << ">[up]</a>\n"
+                               "<a href=\"/\">[top]</a>\n"
+                               "</div>\n"
+                               "<h1>" << title << "</h1>\n";
+
+                       fListener->DataReceived(this, header.String(), 
fPosition,
+                               header.Length());
+
+                       fPosition += header.Length();
+               }
+
+               if (item.Length()) {
+                       fListener->DataReceived(this, item.String(), fPosition,
+                               item.Length());
+
+                       fPosition += item.Length();
+               }
+       }
+
+       if (last) {
+               // emit footer
+               BString footer =
+                       "</div>\n"
+                       "</body>\n"
+                       "</html>\n";
+
+               fListener->DataReceived(this, footer.String(), fPosition,
+                       footer.Length());
+
+               fPosition += footer.Length();
+       }
+}
+
+
+status_t
+BGopherRequest::_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];
+       if (temporaryBuffer == NULL)
+               return B_NO_MEMORY;
+
+       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;
+}
+
+
+BString&
+BGopherRequest::_HTMLEscapeString(BString &str)
+{
+       str.ReplaceAll("&", "&amp;");
+       str.ReplaceAll("<", "&lt;");
+       str.ReplaceAll(">", "&gt;");
+       return str;
+}
diff --git a/src/kits/network/libnetapi/Jamfile 
b/src/kits/network/libnetapi/Jamfile
index ad74ae7..fb8da38 100644
--- a/src/kits/network/libnetapi/Jamfile
+++ b/src/kits/network/libnetapi/Jamfile
@@ -70,6 +70,9 @@ for architectureObject in [ MultiArchSubDirSetup ] {
                        # TODO: another add-on for file:// (a much simpler one)
                        FileRequest.cpp
 
+                       # TODO: another add-on for gopher://
+                       GopherRequest.cpp
+
                        notifications.cpp
 
                        $(md5Sources)
diff --git a/src/kits/network/libnetapi/UrlProtocolRoster.cpp 
b/src/kits/network/libnetapi/UrlProtocolRoster.cpp
index 4b44c99..54e27e2 100644
--- a/src/kits/network/libnetapi/UrlProtocolRoster.cpp
+++ b/src/kits/network/libnetapi/UrlProtocolRoster.cpp
@@ -14,6 +14,7 @@
 #include <DataRequest.h>
 #include <Debug.h>
 #include <FileRequest.h>
+#include <GopherRequest.h>
 #include <HttpRequest.h>
 #include <UrlRequest.h>
 
@@ -45,6 +46,8 @@ BUrlProtocolRoster::MakeRequest(const BUrl& url,
                return new(std::nothrow) BFileRequest(url, listener, context);
        } else if (url.Protocol() == "data") {
                return new(std::nothrow) BDataRequest(url, listener, context);
+       } else if (url.Protocol() == "gopher") {
+               return new(std::nothrow) BGopherRequest(url, listener, context);
        }
 
        return NULL;

############################################################################

Commit:      2e8b8fd0468ecd67b0b22d753172c571b1600e78
URL:         http://cgit.haiku-os.org/haiku/commit/?id=2e8b8fd
Author:      François Revol <revol@xxxxxxx>
Date:        Fri Jul 25 20:16:29 2014 UTC

gopher: Handle binary, directory and error types

----------------------------------------------------------------------------

diff --git a/src/kits/network/libnetapi/GopherRequest.cpp 
b/src/kits/network/libnetapi/GopherRequest.cpp
index 1d193f9..2686618 100644
--- a/src/kits/network/libnetapi/GopherRequest.cpp
+++ b/src/kits/network/libnetapi/GopherRequest.cpp
@@ -430,6 +430,26 @@ BGopherRequest::_ParseInput(bool last)
                                                "<span class=\"text\">" << 
title << "</span></a>"
                                                "<br/>\n";
                                break;
+                       case GOPHER_TYPE_BINARY:
+                       case GOPHER_TYPE_BINHEX:
+                       case GOPHER_TYPE_BINARCHIVE:
+                       case GOPHER_TYPE_UUENCODED:
+                               item << "<a href=\"" << link << "\">"
+                                               "<span class=\"binary\">" << 
title << "</span></a>"
+                                               "<br/>\n";
+                               break;
+                       case GOPHER_TYPE_DIRECTORY:
+                               /*
+                                * directory link
+                                */
+                               item << "<a href=\"" << link << "\">"
+                                               "<span class=\"dir\">" << title 
<< "</span></a>"
+                                               "<br/>\n";
+                               break;
+                       case GOPHER_TYPE_ERROR:
+                               item << "<span class=\"error\">" << title << 
"</span>"
+                                               "<br/>\n";
+                               break;
                        default:
                                item << "<div>" << fields.StringAt(FIELD_NAME) 
<< "</div>";
                                break;

############################################################################

Commit:      f74e08fca8197c3b3227b6d5c1c9a4052cc6ad2b
URL:         http://cgit.haiku-os.org/haiku/commit/?id=f74e08f
Author:      François Revol <revol@xxxxxxx>
Date:        Fri Jul 25 20:35:31 2014 UTC

gopher: Handle info resources and add proper title

We now create a proper title from the error message, or
the TITLE resource if present.

----------------------------------------------------------------------------

diff --git a/src/kits/network/libnetapi/GopherRequest.cpp 
b/src/kits/network/libnetapi/GopherRequest.cpp
index 2686618..cbfaa61 100644
--- a/src/kits/network/libnetapi/GopherRequest.cpp
+++ b/src/kits/network/libnetapi/GopherRequest.cpp
@@ -405,6 +405,7 @@ BGopherRequest::_ParseInput(bool last)
                fprintf(stderr, "type: '%c' name: '%s'\n", type, 
fields.StringAt(FIELD_NAME).String());
                //fields.PrintToStream();
 
+               BString pageTitle;
                BString item;
                BString title = fields.StringAt(FIELD_NAME);
                BString link("gopher://";);
@@ -449,6 +450,19 @@ BGopherRequest::_ParseInput(bool last)
                        case GOPHER_TYPE_ERROR:
                                item << "<span class=\"error\">" << title << 
"</span>"
                                                "<br/>\n";
+                               if (fPosition == 0 && pageTitle.Length() == 0)
+                                       pageTitle << "Error: " << title;
+                               break;
+                       case GOPHER_TYPE_INFO:
+                               // TITLE resource, cf.
+                               // 
gopher://gophernicus.org/0/doc/gopher/gopher-title-resource.txt
+                               if (fPosition == 0 && pageTitle.Length() == 0
+                                       && fields.StringAt(FIELD_SELECTOR) == 
"TITLE") {
+                                               pageTitle = title;
+                                               break;
+                               }
+                               item << "<span class=\"info\">" << title << 
"</span>"
+                                               "<br/>\n";
                                break;
                        default:
                                item << "<div>" << fields.StringAt(FIELD_NAME) 
<< "</div>";
@@ -456,7 +470,9 @@ BGopherRequest::_ParseInput(bool last)
                }
 
                if (fPosition == 0) {
-                       BString title = "TITLE";
+                       if (pageTitle.Length() == 0)
+                               pageTitle << "Index of " << Url();
+
                        const char *uplink = ".";
                        if (fPath.EndsWith("/"))
                                uplink = "..";
@@ -473,14 +489,14 @@ BGopherRequest::_ParseInput(bool last)
                                        "type=\"text/css\" 
href=\"resource:internal.css\">\n"
                                "<link rel=\"icon\" type=\"image/png\""
                                        " 
href=\"resource:icons/directory.png\">\n"
-                               "<title>" << title << "</title>\n"
+                               "<title>" << pageTitle << "</title>\n"
                                "</head>\n"
                                "<body id=\"gopher\">\n"
                                "<div class=\"uplink dontprint\">\n"
                                "<a href=" << uplink << ">[up]</a>\n"
                                "<a href=\"/\">[top]</a>\n"
                                "</div>\n"
-                               "<h1>" << title << "</h1>\n";
+                               "<h1>" << pageTitle << "</h1>\n";
 
                        fListener->DataReceived(this, header.String(), 
fPosition,
                                header.Length());

############################################################################

Commit:      0e48c9aecd39191b20a991517534cba7c5b471a7
URL:         http://cgit.haiku-os.org/haiku/commit/?id=0e48c9a
Author:      François Revol <revol@xxxxxxx>
Date:        Fri Jul 25 21:34:52 2014 UTC

gopher: Handle some more item types

----------------------------------------------------------------------------

diff --git a/src/kits/network/libnetapi/GopherRequest.cpp 
b/src/kits/network/libnetapi/GopherRequest.cpp
index cbfaa61..887ad3c 100644
--- a/src/kits/network/libnetapi/GopherRequest.cpp
+++ b/src/kits/network/libnetapi/GopherRequest.cpp
@@ -112,7 +112,7 @@ static struct {
 
 static const int32 kGopherBufferSize = 4096;
 
-
+static const bool kInlineImages = true;
 
 
 BGopherRequest::BGopherRequest(const BUrl& url, BUrlProtocolListener* listener,
@@ -409,6 +409,7 @@ BGopherRequest::_ParseInput(bool last)
                BString item;
                BString title = fields.StringAt(FIELD_NAME);
                BString link("gopher://";);
+               BString user;
                if (fields.CountStrings() > 3) {
                        link << fields.StringAt(FIELD_HOST);
                        if (fields.StringAt(FIELD_PORT).Length())
@@ -453,6 +454,104 @@ BGopherRequest::_ParseInput(bool last)
                                if (fPosition == 0 && pageTitle.Length() == 0)
                                        pageTitle << "Error: " << title;
                                break;
+                       case GOPHER_TYPE_QUERY:
+                               /* TODO: handle search better.
+                                * For now we use an unnamed input field and 
accept sending ?=foo
+                                * as it seems at least Veronica-2 ignores the 
= but it's unclean.
+                                */
+                               item << "<form method=\"get\" action=\"" << 
link << "\">"
+                                               "<span class=\"query\">"
+                                               "<label>" << title << " "
+                                               "<input name=\"\" type=\"text\" 
align=\"right\" />"
+                                               "</label>"
+                                               "</span></form>"
+                                               "<br/>\n";
+                               break;
+                       case GOPHER_TYPE_TELNET:
+                               /* telnet: links
+                                * cf. gopher://78.80.30.202/1/ps3
+                                * -> gopher://78.80.30.202:23/8/ps3/new -> 
new@78.80.30.202
+                                */
+                               link = "telnet://";
+                               user = fields.StringAt(FIELD_SELECTOR);
+                               if (user.FindLast('/') > -1) {
+                                       user.Remove(0, user.FindLast('/'));
+                                       link << user << "@";
+                               }
+                               link << fields.StringAt(FIELD_HOST);
+                               if (fields.StringAt(FIELD_PORT) != "23")
+                                       link << ":" << 
fields.StringAt(FIELD_PORT);
+
+                               item << "<a href=\"" << link << "\">"
+                                               "<span class=\"telnet\">" << 
title << "</span></a>"
+                                               "<br/>\n";
+                               break;
+                       case GOPHER_TYPE_TN3270:
+                               /* tn3270: URI scheme, cf. 
http://tools.ietf.org/html/rfc6270 */
+                               link = "tn3270://";
+                               user = fields.StringAt(FIELD_SELECTOR);
+                               if (user.FindLast('/') > -1) {
+                                       user.Remove(0, user.FindLast('/'));
+                                       link << user << "@";
+                               }
+                               link << fields.StringAt(FIELD_HOST);
+                               if (fields.StringAt(FIELD_PORT) != "23")
+                                       link << ":" << 
fields.StringAt(FIELD_PORT);
+
+                               item << "<a href=\"" << link << "\">"
+                                               "<span class=\"telnet\">" << 
title << "</span></a>"
+                                               "<br/>\n";
+                               break;
+                       case GOPHER_TYPE_CSO_SEARCH:
+                               /* CSO search.
+                                * At least Lynx supports a cso:// URI scheme:
+                                * 
http://lynx.isc.org/lynx2.8.5/lynx2-8-5/lynx_help/lynx_url_support.html
+                                */
+                               link = "cso://";
+                               user = fields.StringAt(FIELD_SELECTOR);
+                               if (user.FindLast('/') > -1) {
+                                       user.Remove(0, user.FindLast('/'));
+                                       link << user << "@";
+                               }
+                               link << fields.StringAt(FIELD_HOST);
+                               if (fields.StringAt(FIELD_PORT) != "105")
+                                       link << ":" << 
fields.StringAt(FIELD_PORT);
+
+                               item << "<a href=\"" << link << "\">"
+                                               "<span class=\"cso\">" << title 
<< "</span></a>"
+                                               "<br/>\n";
+                               break;
+                       case GOPHER_TYPE_GIF:
+                       case GOPHER_TYPE_IMAGE:
+                       case GOPHER_TYPE_PNG:
+                       case GOPHER_TYPE_BITMAP:
+                               /* quite dangerous, cf. 
gopher://namcub.accela-labs.com/1/pics */
+                               if (kInlineImages) {
+                                       item << "<a href=\"" << link << "\">"
+                                                       "<span class=\"img\">" 
<< title << " "
+                                                       "<img src=\"" << link 
<< "\" "
+                                                               "alt=\"" << 
title << "\"/>"
+                                                       "</span></a>"
+                                                       "<br/>\n";
+                                       break;
+                               }
+                               /* fallback to default, link them */
+                               item << "<a href=\"" << link << "\">"
+                                               "<span class=\"img\">" << title 
<< "</span></a>"
+                                               "<br/>\n";
+                               break;
+                       case GOPHER_TYPE_HTML:
+                               /* cf. gopher://pineapple.vg/1 */
+                               if 
(fields.StringAt(FIELD_SELECTOR).StartsWith("URL:")) {
+                                       link = fields.StringAt(FIELD_SELECTOR);
+                                       link.Remove(0, 4);
+                               }
+                               /* cf. gopher://sdf.org/1/sdf/classes/ */
+
+                               item << "<a href=\"" << link << "\">"
+                                               "<span class=\"html\">" << 
title << "</span></a>"
+                                               "<br/>\n";
+                               break;
                        case GOPHER_TYPE_INFO:
                                // TITLE resource, cf.
                                // 
gopher://gophernicus.org/0/doc/gopher/gopher-title-resource.txt

############################################################################

Commit:      ec0e815354f9b7d53e00b4105cfe2bf87bd0bef1
URL:         http://cgit.haiku-os.org/haiku/commit/?id=ec0e815
Author:      François Revol <revol@xxxxxxx>
Date:        Fri Jul 25 21:56:04 2014 UTC

gopher: Handle audio and video types, add a default case

----------------------------------------------------------------------------

diff --git a/src/kits/network/libnetapi/GopherRequest.cpp 
b/src/kits/network/libnetapi/GopherRequest.cpp
index 887ad3c..d4a53ff 100644
--- a/src/kits/network/libnetapi/GopherRequest.cpp
+++ b/src/kits/network/libnetapi/GopherRequest.cpp
@@ -563,8 +563,36 @@ BGopherRequest::_ParseInput(bool last)
                                item << "<span class=\"info\">" << title << 
"</span>"
                                                "<br/>\n";
                                break;
+                       case GOPHER_TYPE_AUDIO:
+                       case GOPHER_TYPE_SOUND:
+                               item << "<a href=\"" << link << "\">"
+                                               "<span class=\"audio\">" << 
title << "</span></a>"
+                                               "<audio src=\"" << link << "\" "
+                                                       "alt=\"" << title << 
"\"/>"
+                                               "<span>[player]</span></audio>"
+                                               "<br/>\n";
+                               break;
+                       case GOPHER_TYPE_PDF:
+                       case GOPHER_TYPE_PDF_ALT:
+                               /* generic case for known-to-work items */
+                               item << "<a href=\"" << link << "\">"
+                                               "<span class=\"other\">" << 
title << "</span></a>"
+                                               "<br/>\n";
+                               break;
+                       case GOPHER_TYPE_MOVIE:
+                               item << "<a href=\"" << link << "\">"
+                                               "<span class=\"video\">" << 
title << "</span></a>"
+                                               "<video src=\"" << link << "\" "
+                                                       "alt=\"" << title << 
"\"/>"
+                                               "<span>[player]</span></audio>"
+                                               "<br/>\n";
+                               break;
                        default:
-                               item << "<div>" << fields.StringAt(FIELD_NAME) 
<< "</div>";
+                               _EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT,
+                                       "Unknown gopher item (type 0x%02x 
'%c')", type, type);
+                               item << "<a href=\"" << link << "\">"
+                                               "<span class=\"unknown\">" << 
title << "</span></a>"
+                                               "<br/>\n";
                                break;
                }
 

############################################################################

Commit:      6983b35de56a9a25b3aff10eac19e69fbed8b0af
URL:         http://cgit.haiku-os.org/haiku/commit/?id=6983b35
Author:      François Revol <revol@xxxxxxx>
Date:        Fri Jul 25 23:08:07 2014 UTC

gopher: Add a stylesheet

Modified version from my attempt at adding gopher to NetSurf.

----------------------------------------------------------------------------

diff --git a/src/kits/network/libnetapi/GopherRequest.cpp 
b/src/kits/network/libnetapi/GopherRequest.cpp
index d4a53ff..8a13313 100644
--- a/src/kits/network/libnetapi/GopherRequest.cpp
+++ b/src/kits/network/libnetapi/GopherRequest.cpp
@@ -110,6 +110,63 @@ static struct {
        { GOPHER_TYPE_NONE, NULL }
 };
 
+static const char *kStyleSheet = "\n"
+"/*\n"
+" * gopher listing style\n"
+" */\n"
+"\n"
+"body#gopher {\n"
+"      /* margin: 10px;*/\n"
+"      background-color: Window;\n"
+"      color: WindowText;\n"
+"      font-size: 100%;\n"
+"      padding-bottom: 2em; }\n"
+"\n"
+"body#gopher div.uplink {\n"
+"      padding: 0;\n"
+"      margin: 0;\n"
+"      position: fixed;\n"
+"      top: 5px;\n"
+"      right: 5px; }\n"
+"\n"
+"body#gopher h1 {\n"
+"      padding: 5mm;\n"
+"      margin: 0;\n"
+"      border-bottom: 2px solid #777; }\n"
+"\n"
+"body#gopher span {\n"
+"      margin-left: 1em;\n"
+"      padding-left: 2em;\n"
+"      font-family: 'DejaVu Sans Mono', Courier, monospace;\n"
+"      word-wrap: break-word;\n"
+"      white-space: pre-wrap; }\n"
+"\n"
+"body#gopher span.error {\n"
+"      color: #f00; }\n"
+"\n"
+"body#gopher span.unknown {\n"
+"      color: #800; }\n"
+"\n"
+"body#gopher span.dir {\n"
+"      background-image: url('resource:icons/directory.png');\n"
+"      background-repeat: no-repeat;\n"
+"      background-position: bottom left; }\n"
+"\n"
+"body#gopher span.text {\n"
+"      background-image: url('resource:icons/content.png');\n"
+"      background-repeat: no-repeat;\n"
+"      background-position: bottom left; }\n"
+"\n"
+"body#gopher span.query {\n"
+"      background-image: url('resource:icons/search.png');\n"
+"      background-repeat: no-repeat;\n"
+"      background-position: bottom left; }\n"
+"\n"
+"body#gopher span.img img {\n"
+"      display: block;\n"
+"      margin-left:auto;\n"
+"      margin-right:auto; }\n";
+
 static const int32 kGopherBufferSize = 4096;
 
 static const bool kInlineImages = true;
@@ -612,10 +669,9 @@ BGopherRequest::_ParseInput(bool last)
                                "<meta http-equiv=\"Content-Type\""
                                        " content=\"text/html; charset=UTF-8\" 
/>\n"
                                //FIXME: fix links
-                               "<link rel=\"stylesheet\" title=\"Standard\" "
-                                       "type=\"text/css\" 
href=\"resource:internal.css\">\n"
-                               "<link rel=\"icon\" type=\"image/png\""
-                                       " 
href=\"resource:icons/directory.png\">\n"
+                               //"<link rel=\"icon\" type=\"image/png\""
+                               //      " 
href=\"resource:icons/directory.png\">\n"
+                               "<style type=\"text/css\">\n" << kStyleSheet << 
"</style>\n"
                                "<title>" << pageTitle << "</title>\n"
                                "</head>\n"
                                "<body id=\"gopher\">\n"

############################################################################

Commit:      0716bfd63ce42d0100af811b79cb8b42102589d7
URL:         http://cgit.haiku-os.org/haiku/commit/?id=0716bfd
Author:      François Revol <revol@xxxxxxx>
Date:        Fri Jul 25 23:23:24 2014 UTC

gopher: remove debug printfs

----------------------------------------------------------------------------

diff --git a/src/kits/network/libnetapi/GopherRequest.cpp 
b/src/kits/network/libnetapi/GopherRequest.cpp
index 8a13313..e1764cf 100644
--- a/src/kits/network/libnetapi/GopherRequest.cpp
+++ b/src/kits/network/libnetapi/GopherRequest.cpp
@@ -194,7 +194,6 @@ BGopherRequest::BGopherRequest(const BUrl& url, 
BUrlProtocolListener* listener,
                fItemType = fPath[1];
                fPath.Remove(0, 2);
        }
-       fprintf(stderr, "t: '%c' p:'%s'\n", fItemType, fPath.String());
 }
 
 
@@ -269,7 +268,6 @@ BGopherRequest::_ProtocolLoop()
                fSocket->WaitForReadable();
                BNetBuffer chunk(kGopherBufferSize);
                bytesRead = fSocket->Read(chunk.Data(), kGopherBufferSize);
-               fprintf(stderr, "Read: %d\n", (int)bytesRead);
 
                if (bytesRead < 0) {
                        readError = bytesRead;
@@ -292,22 +290,18 @@ BGopherRequest::_ProtocolLoop()
                                for (i = 0; i < fInputBuffer.Size(); i++) {
                                        char c = fInputBuffer.Data()[i];
                                        if (c == '\t') {
-                                               fprintf(stderr, "tab\n");
                                                if (!crlf)
                                                        tabs++;
                                        } else if (c == '\r' || c == '\n') {
-                                               fprintf(stderr, "crlf\n");
                                                if (tabs < 3)
                                                        break;
                                                crlf = true;
                                        } else if 
(!isprint(fInputBuffer.Data()[i])) {
-                                               fprintf(stderr, "!isprint at 
%lu\n", i);
                                                crlf = false;
                                                break;
                                        }
                                }
                                if (crlf && tabs > 2 && tabs < 5) {
-                                       fprintf(stderr, "error\n");
                                        // TODO:
                                        //if enough data
                                        // else continue
@@ -332,7 +326,6 @@ BGopherRequest::_ProtocolLoop()
                        // now we can assign MIME type if we know it
                        for (i = 0; gopher_type_map[i].type != 
GOPHER_TYPE_NONE; i++) {
                                if (gopher_type_map[i].type == fItemType) {
-                                       fprintf(stderr, "MIME:'%s'\n", 
gopher_type_map[i].mime);
                                        
fResult.SetContentType(gopher_type_map[i].mime);
                                        break;
                                }
@@ -459,9 +452,6 @@ BGopherRequest::_ParseInput(bool last)
                        _EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT,
                                "Unterminated gopher item (type '%c')", type);
 
-               fprintf(stderr, "type: '%c' name: '%s'\n", type, 
fields.StringAt(FIELD_NAME).String());
-               //fields.PrintToStream();
-
                BString pageTitle;
                BString item;
                BString title = fields.StringAt(FIELD_NAME);
@@ -476,7 +466,6 @@ BGopherRequest::_ParseInput(bool last)
                        //      link << "/";
                        link << fields.StringAt(FIELD_SELECTOR);
                }
-               fprintf(stderr, "link: '%s'\n", link.String());
                _HTMLEscapeString(title);
                _HTMLEscapeString(link);
 

############################################################################

Commit:      cf2bf30633ed24c948164536be1407a9e2342b4c
URL:         http://cgit.haiku-os.org/haiku/commit/?id=cf2bf30
Author:      François Revol <revol@xxxxxxx>
Date:        Fri Jul 25 23:24:48 2014 UTC

gopher: Add TODOs

----------------------------------------------------------------------------

diff --git a/src/kits/network/libnetapi/GopherRequest.cpp 
b/src/kits/network/libnetapi/GopherRequest.cpp
index e1764cf..64b8013 100644
--- a/src/kits/network/libnetapi/GopherRequest.cpp
+++ b/src/kits/network/libnetapi/GopherRequest.cpp
@@ -23,6 +23,9 @@
 #include <StringList.h>
 
 /*
+ * TODO: add proper favicon
+ * TODO: add proper dir and document icons
+ * TODO: correctly eat the extraneous .\r\n at end of text files
  * TODO: move parsing stuff to a translator?
  *
  * docs:

############################################################################

Revision:    hrev47571
Commit:      e95d0f00ceeeb436c9f279824c44da038db9d246
URL:         http://cgit.haiku-os.org/haiku/commit/?id=e95d0f0
Author:      François Revol <revol@xxxxxxx>
Date:        Fri Jul 25 23:35:19 2014 UTC

gopher: Set a default MIME type to force downloading

----------------------------------------------------------------------------

diff --git a/src/kits/network/libnetapi/GopherRequest.cpp 
b/src/kits/network/libnetapi/GopherRequest.cpp
index 64b8013..70d44af 100644
--- a/src/kits/network/libnetapi/GopherRequest.cpp
+++ b/src/kits/network/libnetapi/GopherRequest.cpp
@@ -327,12 +327,14 @@ BGopherRequest::_ProtocolLoop()
                                fListener->HeadersReceived(this);
 
                        // now we can assign MIME type if we know it
+                       const char *mime = "application/octet-stream";
                        for (i = 0; gopher_type_map[i].type != 
GOPHER_TYPE_NONE; i++) {
                                if (gopher_type_map[i].type == fItemType) {
-                                       
fResult.SetContentType(gopher_type_map[i].mime);
+                                       mime = gopher_type_map[i].mime;
                                        break;
                                }
                        }
+                       fResult.SetContentType(mime);
                }
 
                if (_NeedsParsing())


Other related posts:

  • » [haiku-commits] haiku: hrev47571 - src/kits/network/libnetapi headers/os/net - revol