Skip to content

Commit

Permalink
Add source code for https://smolsite.zip
Browse files Browse the repository at this point in the history
This is a website that hosts websites within the URL: zip, base-64
encode the zip, and Lwan will serve that for you.  It's pretty much
useless, but it was fun to implement.

There are other things that could be cleaned up, including the
hardcoded HTML/JS and ZIP files within the C source code.

ZIP reading code needs extensive fuzzing too.
  • Loading branch information
lpereira committed Aug 29, 2023
1 parent a73f13a commit 8727cd9
Show file tree
Hide file tree
Showing 6 changed files with 926 additions and 1 deletion.
2 changes: 1 addition & 1 deletion src/lib/lwan-private.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ struct lwan_request_parser_helper {
int urls_rewritten; /* Times URLs have been rewritten */
};

#define DEFAULT_BUFFER_SIZE 4096
#define DEFAULT_BUFFER_SIZE 32768
#define DEFAULT_HEADERS_SIZE 2048

#define N_HEADER_START 64
Expand Down
1 change: 1 addition & 0 deletions src/samples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ if (NOT ${CMAKE_BUILD_TYPE} MATCHES "Coverage")
add_subdirectory(websocket)
add_subdirectory(asyncawait)
add_subdirectory(pastebin)
add_subdirectory(smolsite)
endif()

add_subdirectory(techempower)
9 changes: 9 additions & 0 deletions src/samples/smolsite/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
add_executable(smolsite
main.c
junzip.c
)

target_link_libraries(smolsite
${LWAN_COMMON_LIBS}
${ADDITIONAL_LIBRARIES}
)
325 changes: 325 additions & 0 deletions src/samples/smolsite/junzip.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,325 @@
// JUnzip library by Joonas Pihlajamaa. See junzip.h for license and details.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "junzip.h"

unsigned char jzBuffer[JZ_BUFFER_SIZE]; // limits maximum zip descriptor size

// Read ZIP file end record. Will move within file.
int jzReadEndRecord(JZFile *zip, JZEndRecord *endRecord)
{
size_t fileSize, readBytes, i;
JZEndRecord *er;

if (zip->seek(zip, 0, SEEK_END)) {
fprintf(stderr, "Couldn't go to end of zip file!");
return Z_ERRNO;
}

if ((fileSize = zip->tell(zip)) <= sizeof(JZEndRecord)) {
fprintf(stderr, "Too small file to be a zip!");
return Z_ERRNO;
}

readBytes = (fileSize < sizeof(jzBuffer)) ? fileSize : sizeof(jzBuffer);

if (zip->seek(zip, fileSize - readBytes, SEEK_SET)) {
fprintf(stderr, "Cannot seek in zip file!");
return Z_ERRNO;
}

if (zip->read(zip, jzBuffer, readBytes) < readBytes) {
fprintf(stderr, "Couldn't read end of zip file!");
return Z_ERRNO;
}

// Naively assume signature can only be found in one place...
for (i = readBytes - sizeof(JZEndRecord); i; i--) {
er = (JZEndRecord *)(jzBuffer + i);
if (er->signature == 0x06054B50)
goto signature_found;
}

fprintf(stderr, "End record signature not found in zip!");
return Z_ERRNO;

signature_found:
memcpy(endRecord, er, sizeof(JZEndRecord));

if (endRecord->diskNumber || endRecord->centralDirectoryDiskNumber ||
endRecord->numEntries != endRecord->numEntriesThisDisk) {
fprintf(stderr, "Multifile zips not supported!");
return Z_ERRNO;
}

return Z_OK;
}

// Read ZIP file global directory. Will move within file.
int jzReadCentralDirectory(JZFile *zip,
JZEndRecord *endRecord,
JZRecordCallback callback,
void *user_data)
{
JZGlobalFileHeader fileHeader;
JZFileHeader header;
int i;

if (zip->seek(zip, endRecord->centralDirectoryOffset, SEEK_SET)) {
fprintf(stderr, "Cannot seek in zip file!");
return Z_ERRNO;
}

for (i = 0; i < endRecord->numEntries; i++) {
if (zip->read(zip, &fileHeader, sizeof(JZGlobalFileHeader)) <
sizeof(JZGlobalFileHeader)) {
fprintf(stderr, "Couldn't read file header %d!", i);
return Z_ERRNO;
}

if (fileHeader.signature != 0x02014B50) {
fprintf(stderr, "Invalid file header signature %d!", i);
return Z_ERRNO;
}

if (fileHeader.fileNameLength + 1 >= JZ_BUFFER_SIZE) {
fprintf(stderr, "Too long file name %d!", i);
return Z_ERRNO;
}

if (zip->read(zip, jzBuffer, fileHeader.fileNameLength) <
fileHeader.fileNameLength) {
fprintf(stderr, "Couldn't read filename %d!", i);
return Z_ERRNO;
}

jzBuffer[fileHeader.fileNameLength] = '\0'; // NULL terminate

if (zip->seek(zip, fileHeader.extraFieldLength, SEEK_CUR) ||
zip->seek(zip, fileHeader.fileCommentLength, SEEK_CUR)) {
fprintf(stderr, "Couldn't skip extra field or file comment %d", i);
return Z_ERRNO;
}

// Construct JZFileHeader from global file header
memcpy(&header, &fileHeader.compressionMethod, sizeof(header));
header.offset = fileHeader.relativeOffsetOflocalHeader;

if (!callback(zip, i, &header, (char *)jzBuffer, user_data))
break; // end if callback returns zero
}

return Z_OK;
}

// Read local ZIP file header. Silent on errors so optimistic reading possible.
int jzReadLocalFileHeaderRaw(JZFile *zip,
JZLocalFileHeader *header,
char *filename,
int len)
{

if (zip->read(zip, header, sizeof(JZLocalFileHeader)) <
sizeof(JZLocalFileHeader))
return Z_ERRNO;

if (header->signature != 0x04034B50)
return Z_ERRNO;

if (len) { // read filename
if (header->fileNameLength >= len)
return Z_ERRNO; // filename cannot fit

if (zip->read(zip, filename, header->fileNameLength) <
header->fileNameLength)
return Z_ERRNO; // read fail

filename[header->fileNameLength] = '\0'; // NULL terminate
} else { // skip filename
if (zip->seek(zip, header->fileNameLength, SEEK_CUR))
return Z_ERRNO;
}

if (header->extraFieldLength) {
if (zip->seek(zip, header->extraFieldLength, SEEK_CUR))
return Z_ERRNO;
}

// For now, silently ignore bit flags and hope ZLIB can uncompress
// if(header->generalPurposeBitFlag)
// return Z_ERRNO; // Flags not supported

if (header->compressionMethod == 0 &&
(header->compressedSize != header->uncompressedSize))
return Z_ERRNO; // Method is "store" but sizes indicate otherwise, abort

return Z_OK;
}

int jzReadLocalFileHeader(JZFile *zip,
JZFileHeader *header,
char *filename,
int len)
{
JZLocalFileHeader localHeader;

if (jzReadLocalFileHeaderRaw(zip, &localHeader, filename, len) != Z_OK)
return Z_ERRNO;

memcpy(header, &localHeader.compressionMethod, sizeof(JZFileHeader));
header->offset = 0; // not used in local context

return Z_OK;
}

// Read data from file stream, described by header, to preallocated buffer
int jzReadData(JZFile *zip, JZFileHeader *header, void *buffer)
{
#ifdef HAVE_ZLIB
unsigned char *bytes = (unsigned char *)buffer; // cast
long compressedLeft, uncompressedLeft;
int ret;
z_stream strm;
#endif

if (header->compressionMethod == 0) { // Store - just read it
if (zip->read(zip, buffer, header->uncompressedSize) <
header->uncompressedSize ||
zip->error(zip))
return Z_ERRNO;
#ifdef HAVE_ZLIB
} else if (header->compressionMethod == 8) { // Deflate - using zlib
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;

strm.avail_in = 0;
strm.next_in = Z_NULL;

// Use inflateInit2 with negative window bits to indicate raw data
if ((ret = inflateInit2(&strm, -MAX_WBITS)) != Z_OK)
return ret; // Zlib errors are negative

// Inflate compressed data
for (compressedLeft = header->compressedSize,
uncompressedLeft = header->uncompressedSize;
compressedLeft && uncompressedLeft && ret != Z_STREAM_END;
compressedLeft -= strm.avail_in) {
// Read next chunk
strm.avail_in =
zip->read(zip, jzBuffer,
(sizeof(jzBuffer) < compressedLeft) ? sizeof(jzBuffer)
: compressedLeft);

if (strm.avail_in == 0 || zip->error(zip)) {
inflateEnd(&strm);
return Z_ERRNO;
}

strm.next_in = jzBuffer;
strm.avail_out = uncompressedLeft;
strm.next_out = bytes;

compressedLeft -= strm.avail_in; // inflate will change avail_in

ret = inflate(&strm, Z_NO_FLUSH);

if (ret == Z_STREAM_ERROR)
return ret; // shouldn't happen

switch (ret) {
case Z_NEED_DICT:
ret = Z_DATA_ERROR; /* and fall through */
case Z_DATA_ERROR:
case Z_MEM_ERROR:
(void)inflateEnd(&strm);
return ret;
}

bytes += uncompressedLeft - strm.avail_out; // bytes uncompressed
uncompressedLeft = strm.avail_out;
}

inflateEnd(&strm);
#else
#ifdef HAVE_PUFF
} else if (header->compressionMethod == 8) { // Deflate - using puff()
unsigned long destlen = header->uncompressedSize,
sourcelen = header->compressedSize;
unsigned char *comp = (unsigned char *)malloc(sourcelen);
if (comp == NULL)
return Z_ERRNO; // couldn't allocate
unsigned long read = zip->read(zip, comp, sourcelen);
if (read != sourcelen)
return Z_ERRNO; // TODO: more robust read loop
int ret = puff((unsigned char *)buffer, &destlen, comp, &sourcelen);
free(comp);
if (ret)
return Z_ERRNO; // something went wrong
#endif // HAVE_PUFF
#endif
} else {
return Z_ERRNO;
}

return Z_OK;
}

typedef struct {
JZFile handle;
FILE *fp;
} StdioJZFile;

static size_t stdio_read_file_handle_read(JZFile *file, void *buf, size_t size)
{
StdioJZFile *handle = (StdioJZFile *)file;
return fread(buf, 1, size, handle->fp);
}

static size_t stdio_read_file_handle_tell(JZFile *file)
{
StdioJZFile *handle = (StdioJZFile *)file;
return (size_t)ftell(handle->fp);
}

static int stdio_read_file_handle_seek(JZFile *file, size_t offset, int whence)
{
StdioJZFile *handle = (StdioJZFile *)file;
return fseek(handle->fp, (long)offset, whence);
}

static int stdio_read_file_handle_error(JZFile *file)
{
StdioJZFile *handle = (StdioJZFile *)file;
return ferror(handle->fp);
}

static void stdio_read_file_handle_close(JZFile *file)
{
StdioJZFile *handle = (StdioJZFile *)file;
fclose(handle->fp);
free(file);
}

JZFile *jzfile_from_stdio_file(FILE *fp)
{
StdioJZFile *handle = (StdioJZFile *)malloc(sizeof(StdioJZFile));

handle->handle.read = stdio_read_file_handle_read;
handle->handle.tell = stdio_read_file_handle_tell;
handle->handle.seek = stdio_read_file_handle_seek;
handle->handle.error = stdio_read_file_handle_error;
handle->handle.close = stdio_read_file_handle_close;
handle->fp = fp;

return &(handle->handle);
}

void jzfile_free(JZFile *f)
{
if (f->close)
f->close(f);
}
Loading

0 comments on commit 8727cd9

Please sign in to comment.