/* * (C) 2013-2024 by Christian Hesse <mail@eworm.de> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. * */ #include "../config.h" #include "../version.h" /* define structs and functions */ #include "libcqrlogo.h" /*** cqr_png_write_fn ***/ void cqr_png_write_fn(png_structp png_ptr, png_bytep data, png_size_t length) { struct cqr_png * png; png = (struct cqr_png *)png_get_io_ptr(png_ptr); png->buffer = realloc(png->buffer, png->size + length); memcpy(png->buffer + png->size, data, length); png->size += length; } #if defined PNG_TEXT_SUPPORTED /*** cqr_png_add_text ***/ png_text * cqr_png_add_text(png_text *pngtext, unsigned int *textcount, char *key, char *text) { pngtext = realloc(pngtext, ((*textcount) + 1) * sizeof(png_text)); pngtext[*textcount].compression = PNG_TEXT_COMPRESSION_zTXt; pngtext[*textcount].key = key; pngtext[*textcount].text = text; (*textcount)++; return pngtext; } #endif /*** cqr_bitmap_new ***/ struct cqr_bitmap * cqr_bitmap_new(int width, int height) { struct cqr_bitmap *bitmap; if ((bitmap = malloc(sizeof(struct cqr_bitmap))) == NULL) return NULL; bitmap->width = width; bitmap->height = height; if ((bitmap->pixel = malloc(width * height * sizeof(uint8_t))) == NULL) { free(bitmap); return NULL; } /* initialize with white */ memset(bitmap->pixel, 0xff, width * height); return bitmap; } /*** cqr_bitmap_free ***/ void cqr_bitmap_free(struct cqr_bitmap * bitmap) { free(bitmap->pixel); free(bitmap); } /*** cqr_encode_qrcode_to_bitmap ***/ struct cqr_bitmap * cqr_encode_qrcode_to_bitmap(const char *text, const struct cqr_conf conf) { QRcode *qrcode; struct cqr_bitmap *bitmap, *scaled; int i, j, k, l; unsigned char *data; qrcode = QRcode_encodeString8bit(text, 0, conf.level); /* this happens if the string is too long * possibly we have an URL (referer) that is too long, so the code * automatically falls back to http_server (see main()) */ if (qrcode == NULL) return NULL; data = qrcode->data; /* wirte QR code to bitmap */ if ((bitmap = cqr_bitmap_new(qrcode->width + conf.border * 2, qrcode->width + conf.border * 2)) == NULL) return NULL; for (i = conf.border; i < qrcode->width + conf.border; i++) for (j = conf.border; j < qrcode->width + conf.border; j++) { bitmap->pixel[i * (qrcode->width + conf.border * 2) + j] = !(*data & 0x1) * 0xff; data++; } QRcode_free(qrcode); if (conf.scale == 1) return bitmap; /* scale bitmap */ if ((scaled = cqr_bitmap_new(bitmap->width * conf.scale, bitmap->height * conf.scale)) == NULL) return NULL; for (i = 0; i < bitmap->height; i++) for (j = 0; j < bitmap->width; j++) for (k = 0; k < conf.scale; k++) for (l = 0; l < conf.scale; l++) scaled->pixel[i * bitmap->width * conf.scale * conf.scale + k * bitmap->width * conf.scale + j * conf.scale + l] = bitmap->pixel[i * bitmap->width + j]; cqr_bitmap_free(bitmap); return scaled; } /*** cqr_bitmap_to_png ***/ struct cqr_png * cqr_bitmap_to_png(struct cqr_bitmap *bitmap, const char *text, const uint8_t meta) { png_structp png_ptr = NULL; png_infop info_ptr = NULL; png_byte ** row_pointers = NULL; unsigned int x, y; uint8_t bit, byte; struct cqr_png * png; png = malloc(sizeof(struct cqr_png)); png->buffer = NULL; png->size = 0; if ((png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL)) == NULL) return NULL; if ((info_ptr = png_create_info_struct (png_ptr)) == NULL || (setjmp (png_jmpbuf (png_ptr)))) { png_destroy_write_struct (&png_ptr, &info_ptr); return NULL; } png_set_IHDR (png_ptr, info_ptr, bitmap->width, bitmap->height, 1 /* depth */, PNG_COLOR_TYPE_GRAY, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); /* use best compression */ png_set_compression_level(png_ptr, Z_BEST_COMPRESSION); /* use compression strategy filtered * this way pngcrush can not optimize any more */ png_set_compression_strategy(png_ptr, Z_FILTERED); #if defined PNG_TEXT_SUPPORTED unsigned int textcount = 0; png_text *pngtext = NULL; char *referer = NULL, *libsstr = NULL, *qrver; if (meta) { if (meta & CQR_META_COMMENT) pngtext = cqr_png_add_text(pngtext, &textcount, "comment", CQR_COMMENTSTR); if (meta & CQR_META_REFERER) { referer = strdup(text); /* text in png file may have a max length of 79 chars */ if (strlen(referer) > 79) sprintf(referer + 76, "..."); pngtext = cqr_png_add_text(pngtext, &textcount, "referer", referer); } if (meta & CQR_META_VERSION) pngtext = cqr_png_add_text(pngtext, &textcount, "version", CQR_VERSIONSTR); if (meta & CQR_META_LIBVERSION) { qrver = QRcode_APIVersionString(); libsstr = malloc(sizeof(CQR_LIBSSTR) + strlen(qrver) + strlen(png_libpng_ver) + strlen(zlib_version)); sprintf(libsstr, CQR_LIBSSTR, qrver, png_libpng_ver, zlib_version); pngtext = cqr_png_add_text(pngtext, &textcount, "libs", libsstr); } png_set_text(png_ptr, info_ptr, pngtext, textcount); png_free (png_ptr, pngtext); if (referer) free(referer); if (libsstr) free(libsstr); } #endif row_pointers = png_malloc (png_ptr, bitmap->height * sizeof (png_byte *)); for (y = 0; y < bitmap->height; ++y) { /* we need to round up, need a complete byte for less than eight bits */ row_pointers[y] = png_malloc (png_ptr, (sizeof(uint8_t) * bitmap->width + 7) / 8); for (x = 0; x < bitmap->width; ++x) { /* bit are written in reverse order! */ bit = 7 - (x % 8); byte = x / 8; if (bitmap->pixel[y * bitmap->width + x]) row_pointers[y][byte] |= 1 << (bit); else row_pointers[y][byte] &= ~(1 << (bit)); } } /* with FastCGI we can not just open stdout for writing... * define a write function instead */ png_set_write_fn(png_ptr, png, cqr_png_write_fn, NULL); png_set_rows(png_ptr, info_ptr, row_pointers); png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL); for (y = 0; y < bitmap->height; ++y) png_free(png_ptr, row_pointers[y]); png_free(png_ptr, row_pointers); png_destroy_write_struct(&png_ptr, &info_ptr); return png; } /*** cqr_encode_qrcode_to_png ***/ struct cqr_png * cqr_encode_qrcode_to_png(const char *text, const struct cqr_conf conf, const uint8_t meta) { struct cqr_bitmap * bitmap; struct cqr_png * png; if ((bitmap = cqr_encode_qrcode_to_bitmap(text, conf)) == NULL) { fprintf(stderr, "Failed encoding QR-Code to bitmap.\n"); return NULL; } if ((png = cqr_bitmap_to_png(bitmap, text, meta)) == NULL) { fprintf(stderr, "Failed to convert bitmap to png.\n"); return NULL; } cqr_bitmap_free(bitmap); return png; } /*** cqr_get_query_uint ***/ unsigned int cqr_get_query_uint(const char *query_string, const char *pattern, unsigned int value, unsigned int min, unsigned int max) { char *match = NULL, *newpattern = NULL; unsigned int length; int tmp = -1; newpattern = strdup(pattern); length = strlen(newpattern); /* length is without null termination, allocacte 4 bytes so we * have "=", "%u" and null termination */ newpattern = realloc(newpattern, length + 4); sprintf(newpattern + length, "="); if ((match = strstr(query_string, newpattern)) != NULL) { sprintf(newpattern + length + 1, "%%u"); if ((sscanf(match, newpattern, &tmp)) > 0) if (tmp >= min && tmp <= max) value = tmp; } free(newpattern); return value; } /*** get_query_char ***/ char * cqr_get_query_char(const char *query_string, const char *pattern) { char *value = NULL, *cut = NULL; const char *tmp = NULL, *match = NULL; if ((match = strstr(query_string, pattern)) == NULL) return NULL; if ((tmp = strchr(match, '=')) == NULL) return NULL; if (strlen(tmp) < 1) return NULL; value = strdup(tmp + 1); if ((cut = strchr(value, '&')) != NULL) *cut = '\0'; return value; } /*** get_ini_value ***/ unsigned int get_ini_value(dictionary * ini, uint8_t type, const char * section, const char * parameter, unsigned int value, unsigned int min, unsigned int max) { char * key; unsigned int tmp; key = malloc(strlen(section) + strlen(parameter) + 2); sprintf(key, "%s:%s", section, parameter); if (type) tmp = iniparser_getint(ini, key, value); else tmp = iniparser_getboolean(ini, key, value); if (tmp >= min && tmp <= max) value = tmp; free(key); return value; } /*** cqr_conf_file ***/ void cqr_conf_file(const char * server_name, struct cqr_conf * conf) { dictionary * ini; /* parse config file */ if ((ini = iniparser_load(CONFIGFILE)) == NULL) { fprintf(stderr, "cannot parse file " CONFIGFILE ", continue anyway\n"); return; } conf->scale = get_ini_value(ini, 1, "general", "scale", conf->scale, 1, QRCODE_MAX_SCALE); conf->border = get_ini_value(ini, 1, "general", "border", conf->border, 0, QRCODE_MAX_BORDER); conf->level = get_ini_value(ini, 1, "general", "level", conf->level, QR_ECLEVEL_L, QR_ECLEVEL_H); conf->overwrite = get_ini_value(ini, 0, "general", "allow overwrite", conf->overwrite, false, true); conf->scale = get_ini_value(ini, 1, server_name, "scale", conf->scale, 1, QRCODE_MAX_SCALE); conf->border = get_ini_value(ini, 1, server_name, "border", conf->border, 0, QRCODE_MAX_BORDER); conf->level = get_ini_value(ini, 1, server_name, "level", conf->level, QR_ECLEVEL_L, QR_ECLEVEL_H); conf->overwrite = get_ini_value(ini, 0, server_name, "allow overwrite", conf->overwrite, false, true); /* done reading config file, free */ iniparser_freedict(ini); } /*** cqr_conf_string ***/ void cqr_conf_string(const char * query_string, struct cqr_conf * conf) { if (conf->overwrite == false) return; if (query_string == NULL) return; /* do we have a special scale? */ conf->scale = cqr_get_query_uint(query_string, "scale", conf->scale, 1, QRCODE_MAX_SCALE); /* width of the border? */ conf->border = cqr_get_query_uint(query_string, "border", conf->border, 0, QRCODE_MAX_BORDER); /* error correction level? */ conf->level = cqr_get_query_uint(query_string, "level", conf->level, QR_ECLEVEL_L, QR_ECLEVEL_H); }