StringBuf: add URL-encoder that also encodes the @ character.
[citadel.git] / libcitadel / lib / stringbuf.c
index c8f8a6434d53436b8894b336f39d0e6b3b0e0337..31e99b52f06c15a477990a726314595cdda34956 100644 (file)
@@ -16,6 +16,7 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  */
 
+#define _GNU_SOURCE
 #include "sysdep.h"
 #include <ctype.h>
 #include <errno.h>
 #include <sys/types.h>
 #define SHOW_ME_VAPPEND_PRINTF
 #include <stdarg.h>
+
+#ifndef LINUX_SENDFILE
+#include <sys/sendfile.h>
+#endif
 #include "libcitadel.h"
 
 #ifdef HAVE_ICONV
 #include <execinfo.h>
 #endif
 
+#ifdef LINUX_SENDFILE
+#include <sys/sendfile.h>
+#endif
+
 #ifdef HAVE_ZLIB
 #include <zlib.h>
 int ZEXPORT compress_gzip(Bytef * dest, size_t * destLen,
@@ -1815,6 +1824,66 @@ void StrBufUrlescAppend(StrBuf *OutBuf, const StrBuf *In, const char *PlainIn)
        *pt = '\0';
 }
 
+/** 
+ * @ingroup StrBuf_DeEnCoder
+ * @brief Escape a string for feeding out as a the username/password part of an URL while appending it to a Buffer
+ * @param OutBuf the output buffer
+ * @param In Buffer to encode
+ * @param PlainIn way in from plain old c strings
+ */
+void StrBufUrlescUPAppend(StrBuf *OutBuf, const StrBuf *In, const char *PlainIn)
+{
+       const char *pch, *pche;
+       char *pt, *pte;
+       int len;
+       
+       if (((In == NULL) && (PlainIn == NULL)) || (OutBuf == NULL) )
+               return;
+       if (PlainIn != NULL) {
+               len = strlen(PlainIn);
+               pch = PlainIn;
+               pche = pch + len;
+       }
+       else {
+               pch = In->buf;
+               pche = pch + In->BufUsed;
+               len = In->BufUsed;
+       }
+
+       if (len == 0) 
+               return;
+
+       pt = OutBuf->buf + OutBuf->BufUsed;
+       pte = OutBuf->buf + OutBuf->BufSize - 4; /**< we max append 3 chars at once plus the \0 */
+
+       while (pch < pche) {
+               if (pt >= pte) {
+                       IncreaseBuf(OutBuf, 1, -1);
+                       pte = OutBuf->buf + OutBuf->BufSize - 4; /**< we max append 3 chars at once plus the \0 */
+                       pt = OutBuf->buf + OutBuf->BufUsed;
+               }
+
+               if((*pch >= 'a' && *pch <= 'z') ||
+                  (*pch >= 'A' && *pch <= 'Z') || /* A-Z */
+                  (*pch >= '0' && *pch <= ':') || /* 0-9 : */
+                  (*pch == '!') || (*pch == '_') || 
+                  (*pch == ',') || (*pch == '.'))
+               {
+                       *(pt++) = *(pch++);
+                       OutBuf->BufUsed++;
+               }                       
+               else {
+                       *pt = '%';
+                       *(pt + 1) = HexList[(unsigned char)*pch][0];
+                       *(pt + 2) = HexList[(unsigned char)*pch][1];
+                       pt += 3;
+                       OutBuf->BufUsed += 3;
+                       pch ++;
+               }
+       }
+       *pt = '\0';
+}
+
 /** 
  * @ingroup StrBuf_DeEnCoder
  * @brief append a string in hex encoding to the buffer
@@ -3782,6 +3851,195 @@ long IOBufferStrLength(IOBuffer *FB)
        return StrLength(FB->Buf) - (FB->ReadWritePointer - FB->Buf->buf);
 }
 
+void FDIOBufferInit(FDIOBuffer *FDB, IOBuffer *IO, int FD, long TotalSendSize)
+{
+       memset(FDB, 0, sizeof(FDIOBuffer));
+       FDB->ChunkSize = 
+               FDB->TotalSendSize = TotalSendSize;
+       FDB->IOB = IO;
+#ifndef LINUX_SENDFILE
+       FDB->ChunkBuffer = NewStrBufPlain(NULL, TotalSendSize + 1);
+#else
+       pipe(FDB->SplicePipe);
+#endif
+       FDB->OtherFD = FD;
+}
+
+void FDIOBufferDelete(FDIOBuffer *FDB)
+{
+#ifndef LINUX_SENDFILE
+       FreeStrBuf(&FDB->ChunkBuffer);
+#else
+       close(FDB->SplicePipe[0]);
+       close(FDB->SplicePipe[1]);
+       
+#endif
+       close(FDB->OtherFD);
+       memset(FDB, 0, sizeof(FDIOBuffer));     
+}
+
+int FileSendChunked(FDIOBuffer *FDB, const char **Err)
+{
+       char *pRead;
+       long nRead = 0;
+       
+#ifdef LINUX_SENDFILE
+       ssize_t sent;
+       sent = sendfile(FDB->IOB->fd, FDB->OtherFD, &FDB->TotalSentAlready, FDB->ChunkSendRemain);
+       if (sent == -1)
+       {
+               *Err = strerror(errno);
+               return sent;
+       }
+       FDB->ChunkSendRemain -= sent;
+       FDB->TotalSentAlready += sent;
+       return FDB->ChunkSendRemain;
+#else
+       pRead = FDB->ChunkBuffer->buf;
+       while ((FDB->ChunkBuffer->BufUsed < FDB->TotalSendSize) && (nRead >= 0))
+       {
+               nRead = read(FDB->OtherFD, pRead, FDB->TotalSendSize - FDB->ChunkBuffer->BufUsed);
+               if (nRead > 0) {
+                       FDB->ChunkBuffer->BufUsed += nRead;
+                       FDB->ChunkBuffer->buf[FDB->ChunkBuffer->BufUsed] = '\0';
+               }
+               else if (nRead == 0) {}
+               else return nRead;
+               
+       }
+
+       nRead = write(FDB->IOB->fd, FDB->ChunkBuffer->buf + FDB->TotalSentAlready, FDB->ChunkSendRemain);
+
+       if (nRead >= 0) {
+               FDB->TotalSentAlready += nRead;
+               FDB->ChunkSendRemain -= nRead;
+               return FDB->ChunkSendRemain;
+       }
+       else {
+               return nRead;
+       }
+#endif
+}
+
+int FileRecvChunked(FDIOBuffer *FDB, const char **Err)
+{
+       ssize_t sent, pipesize;
+
+#ifdef LINUX_SENDFILE
+
+       pipesize = splice(FDB->IOB->fd, NULL, 
+                         FDB->SplicePipe[1], NULL, 
+                         FDB->ChunkSendRemain, 
+                         SPLICE_F_MORE | SPLICE_F_MOVE|SPLICE_F_NONBLOCK);
+       if (pipesize == -1)
+       {
+               *Err = strerror(errno);
+               return pipesize;
+       }
+       
+       sent = splice(FDB->SplicePipe[0], NULL, 
+                     FDB->OtherFD, &FDB->TotalSentAlready, 
+                     pipesize, SPLICE_F_MORE | SPLICE_F_MOVE);
+       if (sent == -1)
+       {
+               *Err = strerror(errno);
+               return sent;
+       }
+       FDB->ChunkSendRemain -= sent;
+       return sent;
+#else
+       
+       sent = read(FDB->IOB->fd, FDB->ChunkBuffer->buf, FDB->ChunkSendRemain);
+       if (sent > 0) {
+               int nWritten = 0;
+               int rc; 
+               
+               FDB->ChunkBuffer->BufUsed = sent;
+
+               while (nWritten < FDB->ChunkBuffer->BufUsed) {
+                       rc =  write(FDB->OtherFD, FDB->ChunkBuffer->buf + nWritten, FDB->ChunkBuffer->BufUsed - nWritten);
+                       if (rc < 0) {
+                               *Err = strerror(errno);
+                               return rc;
+                       }
+                       nWritten += rc;
+
+               }
+               FDB->ChunkBuffer->BufUsed = 0;
+               FDB->TotalSentAlready += sent;
+               FDB->ChunkSendRemain -= sent;
+               return FDB->ChunkSendRemain;
+       }
+       else if (sent < 0) {
+               *Err = strerror(errno);
+               return sent;
+       }
+
+#endif
+       return 0;
+}
+
+eReadState WriteIOBAlreadyRead(FDIOBuffer *FDB, const char **Error)
+{
+       int IsNonBlock;
+       int fdflags;
+       long rlen;
+       long should_write;
+       int nSuccessLess = 0;
+       struct timeval tv;
+       fd_set rfds;
+
+       fdflags = fcntl(FDB->OtherFD, F_GETFL);
+       IsNonBlock = (fdflags & O_NONBLOCK) == O_NONBLOCK;
+
+       while ((FDB->IOB->ReadWritePointer - FDB->IOB->Buf->buf < FDB->IOB->Buf->BufUsed) &&
+              (FDB->ChunkSendRemain > 0))
+       {
+               if (IsNonBlock){
+                       tv.tv_sec = 1; /* selectresolution; */
+                       tv.tv_usec = 0;
+                       
+                       FD_ZERO(&rfds);
+                       FD_SET(FDB->OtherFD, &rfds);
+                       if (select(FDB->OtherFD + 1, NULL, &rfds, NULL, &tv) == -1) {
+                               *Error = strerror(errno);
+                               return eReadFail;
+                       }
+               }
+               if (IsNonBlock && !  FD_ISSET(FDB->OtherFD, &rfds)) {
+                       nSuccessLess ++;
+                       continue;
+               }
+
+               should_write = FDB->IOB->Buf->BufUsed - 
+                       (FDB->IOB->ReadWritePointer - FDB->IOB->Buf->buf);
+               if (should_write > FDB->ChunkSendRemain)
+                       should_write = FDB->ChunkSendRemain;
+
+               rlen = write(FDB->OtherFD, 
+                            FDB->IOB->ReadWritePointer, 
+                            should_write);
+               if (rlen < 1) {
+                       *Error = strerror(errno);
+                                               
+                       return eReadFail;
+               }
+               FDB->TotalSentAlready += rlen;
+               FDB->IOB->ReadWritePointer += rlen;
+               FDB->ChunkSendRemain -= rlen;
+       }
+       if (FDB->IOB->ReadWritePointer >= FDB->IOB->Buf->buf + FDB->IOB->Buf->BufUsed)
+       {
+               FlushStrBuf(FDB->IOB->Buf);
+               FDB->IOB->ReadWritePointer = NULL;
+       }
+
+       if (FDB->ChunkSendRemain == 0)
+               return eReadSuccess;
+       else 
+               return eMustReadMore;
+}
+
 /*******************************************************************************
  *           File I/O; Prefer buffered read since its faster!                  *
  *******************************************************************************/
@@ -4458,9 +4716,10 @@ void StrBufStripSlashes(StrBuf *Dir, int RemoveTrailingSlash)
                        b++; a++;
                }
        }
-       if ((RemoveTrailingSlash) && (*(b - 1) != '/')){
-               *b = '/';
-               b++;
+       if ((RemoveTrailingSlash) &&
+           (b > Dir->buf) && 
+           (*(b - 1) == '/')){
+               b--;
        }
        *b = '\0';
        Dir->BufUsed = b - Dir->buf;