#ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include /* chmod(2) */ #include #include #include #include #include /* strdup(3) */ #include /* basename(3) */ #include /* tolower(3) */ #include #include #include #include #include #include #include #define PCRE2_CODE_UNIT_WIDTH 8 #include #include #include #include #include struct symbol *strbuf_envtab = (struct symbol *)0; struct symbol *envtab_install( struct symbol **sym, const char *n, const char *v, envtab_error fatal ) { struct symbol *ep = NULL; size_t len; if( !sym || !n || !v || !fatal ) return (struct symbol *)NULL; ep = (struct symbol *)malloc( sizeof(struct symbol) ); if( !ep ) { fatal( "cannot allocate memory for symbol '%s'", n ); } bzero( (void *)ep, sizeof(struct symbol) ); len = strlen( n ) + 1; ep->name = (char *)malloc( len ); if( !ep->name ) { fatal( "cannot allocate memory for symbol name '%s'", n ); } bzero( (void *)ep->name, len ); strncpy( ep->name, n, len ); len = strlen( v ) + 1; ep->value = (char *)malloc( len ); if( !ep->value ) { fatal( "cannot allocate memory for symbol value '%s'", v ); } bzero( (void *)ep->value, len ); strncpy( ep->value, v, len ); ep->next = *sym; *sym = ep; return ep; } struct symbol *envtab_lookup( struct symbol **sym, const char *n ) { struct symbol *ep = NULL; if( !sym || !n ) return (struct symbol *)NULL; for( ep = *sym; ep != (struct symbol *)0; ep = ep->next ) if( strcmp( ep->name, n ) == 0 ) return( ep ); return (struct symbol *)NULL; } void envtab_release( struct symbol **sym ) { struct symbol *tab = *sym; while( tab ) { struct symbol *ep = tab; tab = ep->next; if( ep->name ) free( ep->name ); if( ep->value ) free( ep->value ); free( ep ); } *sym = (struct symbol *)NULL; } #define STRBUF_ERRMSG_SIZE 4096 void strbuf_fatal( const char *fmt, ... ) { va_list arg_ptr; char buf[STRBUF_ERRMSG_SIZE]; char msg[STRBUF_ERRMSG_SIZE]; char *format = "%s: %s\n"; va_start( arg_ptr, fmt ); vsnprintf( msg, STRBUF_ERRMSG_SIZE, (const void *)fmt, arg_ptr ); va_end( arg_ptr ); /* Reset variable arguments. */ snprintf( buf, STRBUF_ERRMSG_SIZE, format, "strbuf", msg ); (void)write( STDERR_FILENO, buf, strlen( buf ) ); exit( 1 ); } char strbuf_slopbuf[1]; #define alloc_nz(x) (((x)+16)*3/2) static void alloc_grow( struct strbuf *sb, size_t size ) { if( size > sb->alloc ) { if( alloc_nz(sb->alloc) < (size) ) sb->alloc = size; else sb->alloc = alloc_nz(sb->alloc); sb->buf = xrealloc( (void *)sb->buf, sb->alloc ); memset( (void *)(sb->buf + sb->len), 0, (size_t)(sb->alloc - sb->len) ); } } void strbuf_grow( struct strbuf *sb, size_t extra ) { int new_buf = !sb->alloc; if( new_buf ) sb->buf = NULL; alloc_grow( sb, sb->len + extra + 1 ); if( new_buf ) sb->buf[0] = '\0'; } /* Функцию strbuf_init() надо вызывать в том случае, когда необходимо задать внешнюю функцию обработки фатальной ошибки. Функция обработки фатальной ошибки, в отличие от стандартной, может выводить как JSON ответы, так и файлы 404.html, 50x.html, используя собственный буфер. */ void strbuf_init( struct strbuf *sb, strbuf_error fatal, size_t hint ) { sb->alloc = sb->len = 0; sb->fatal = strbuf_fatal; sb->buf = strbuf_slopbuf; if( fatal ) sb->fatal = fatal; if( hint ) strbuf_grow( sb, hint ); } void strbuf_release( struct strbuf *sb ) { if( sb->alloc ) { free( sb->buf ); strbuf_init( sb, (strbuf_error)0, 0 ); } } char *strbuf_detach( struct strbuf *sb, size_t *sz ) { char *ret; strbuf_grow( sb, 0 ); ret = sb->buf; if( sz ) *sz = sb->len; strbuf_init( sb, sb->fatal, 0 ); return ret; } void strbuf_attach( struct strbuf *sb, void *buf, size_t len, size_t alloc ) { strbuf_release( sb ); sb->buf = buf; sb->len = len; sb->alloc = alloc; strbuf_grow( sb, 0 ); sb->buf[sb->len] = '\0'; } void strbuf_ltrim( struct strbuf *sb ) { char *b = sb->buf; while( sb->len > 0 && isspace(*b) ) { b++; sb->len--; } memmove( sb->buf, b, sb->len ); sb->buf[sb->len] = '\0'; } void strbuf_rtrim( struct strbuf *sb ) { while( sb->len > 0 && isspace((unsigned char)sb->buf[sb->len - 1]) ) sb->len--; sb->buf[sb->len] = '\0'; } void strbuf_trim( struct strbuf *sb ) { strbuf_rtrim( sb ); strbuf_ltrim( sb ); } void strbuf_trim_trailing_dir_sep( struct strbuf *sb ) { while( sb->len > 0 && is_dir_sep((unsigned char)sb->buf[sb->len - 1]) ) sb->len--; sb->buf[sb->len] = '\0'; } void strbuf_trim_trailing_newline( struct strbuf *sb ) { if( sb->len > 0 && sb->buf[sb->len - 1] == '\n' ) { if( --sb->len > 0 && sb->buf[sb->len - 1] == '\r' ) --sb->len; sb->buf[sb->len] = '\0'; } } int strbuf_cmp( const struct strbuf *a, const struct strbuf *b ) { size_t len = a->len < b->len ? a->len: b->len; int cmp = memcmp( a->buf, b->buf, len ); if( cmp ) return cmp; return a->len < b->len ? -1: a->len != b->len; } /* Adding data to the buffer */ void strbuf_add( struct strbuf *sb, const void *data, size_t len ) { strbuf_grow( sb, len ); memcpy( sb->buf + sb->len, data, len ); strbuf_setlen( sb, sb->len + len ); } void strbuf_addbuf( struct strbuf *sb, const struct strbuf *sb2 ) { strbuf_grow( sb, sb2->len ); memcpy( sb->buf + sb->len, sb2->buf, sb2->len ); strbuf_setlen( sb, sb->len + sb2->len ); } void strbuf_addbuf_percentquote( struct strbuf *dst, const struct strbuf *src ) { size_t i, len = src->len; for (i = 0; i < len; i++) { if( src->buf[i] == '%' ) strbuf_addch( dst, '%' ); strbuf_addch( dst, src->buf[i] ); } } void strbuf_addchars( struct strbuf *sb, int c, size_t n ) { strbuf_grow( sb, n ); memset( sb->buf + sb->len, c, n ); strbuf_setlen( sb, sb->len + n ); } void strbuf_vaddf( struct strbuf *sb, const char *fmt, va_list ap ) { int len; va_list cp; if( !strbuf_avail( sb ) ) strbuf_grow( sb, 64 ); va_copy( cp, ap ); len = vsnprintf( sb->buf + sb->len, sb->alloc - sb->len, fmt, cp ); va_end( cp ); if( len < 0 ) sb->fatal( "your vsnprintf is broken (returned %d)", len ); if( len > strbuf_avail( sb ) ) { strbuf_grow( sb, len ); len = vsnprintf( sb->buf + sb->len, sb->alloc - sb->len, fmt, ap ); if( len > strbuf_avail( sb ) ) sb->fatal( "your vsnprintf is broken (insatiable)" ); } strbuf_setlen( sb, sb->len + len ); } void strbuf_addf( struct strbuf *sb, const char *fmt, ... ) { va_list ap; va_start( ap, fmt ); strbuf_vaddf( sb, fmt, ap ); va_end( ap ); } size_t strbuf_fread( struct strbuf *sb, FILE *fp ) { size_t ret, nb = 64, read = 0; size_t oldalloc = sb->alloc; if( !sb || !fp ) return read; do { strbuf_grow( sb, nb ); ret = fread( sb->buf + sb->len, 1, nb, fp ); if( ret > 0 ) { strbuf_setlen( sb, sb->len + ret ); read += ret; } else if( oldalloc == 0 ) { strbuf_release( sb ); return ret; } } while( ret == nb ); return read; } #define NAMELEN_MAX 128 size_t strbuf_env_fread( struct strbuf *sb, FILE *fp ) { size_t read = 0; char *ln, line[STRBUF_MAXLINE], retline[STRBUF_MAXLINE]; if( !sb || !fp ) return read; bzero( (void *)line, STRBUF_MAXLINE ); bzero( (void *)retline, STRBUF_MAXLINE ); while( (ln = fgets( line, STRBUF_MAXLINE, fp )) ) { char *start = NULL, *stop = NULL; char *sp = ln; if( (start = strstr( sp, "${" )) && (stop = strstr( sp, "}" )) && ((stop - start) > 1) ) { struct symbol *sym = NULL; char *lp = retline; do { /* may be multiple variables on a single line: */ *start = '\0'; *stop++ = '\0'; if( (sym = envtab_lookup( &strbuf_envtab, start+2 )) && sym->value && sym->value[0] ) { strncpy( lp, (const char *)sp, (size_t)(start - sp + 1) ); lp += (start - sp); strcpy( lp, (const char *)sym->value ); lp += strlen( sym->value ); strcpy( lp, (const char *)stop ); sp = stop; } else { strncpy( lp, (const char *)sp, (size_t)(start - sp + 1) ); lp += (start - sp); strcpy( lp, (const char *)stop ); sp = stop; } } while( (start = strstr( sp, "${" )) && (stop = strstr( sp, "}" )) && ((stop - start) > 1) ); strbuf_addstr( sb, retline ); read += strlen( retline );; } else { strbuf_addstr( sb, line ); read += strlen( line );; } } /* End of while( ln ) */ return read; } ssize_t strbuf_read( struct strbuf *sb, int fd, size_t hint ) { size_t oldlen = sb->len; size_t oldalloc = sb->alloc; strbuf_grow(sb, hint ? hint : 8192); for( ;; ) { ssize_t want = sb->alloc - sb->len - 1; ssize_t got = read_in_full( fd, sb->buf + sb->len, want ); if( got < 0 ) { if( oldalloc == 0 ) strbuf_release( sb ); else strbuf_setlen( sb, oldlen ); return -1; } sb->len += got; if( got < want ) break; strbuf_grow( sb, 8192 ); } sb->buf[sb->len] = '\0'; return sb->len - oldlen; } size_t strbuf_fwrite( struct strbuf *sb, FILE *fp ) { return sb->len ? fwrite( sb->buf, 1, sb->len, fp ) : 0; } ssize_t strbuf_write( struct strbuf *sb, int fd ) { return sb->len ? write( fd, (const void *)sb->buf, sb->len ) : 0; } /* XML quoted: */ void strbuf_addstr_xml_quoted( struct strbuf *sb, const char *s ) { while( *s ) { size_t len = strcspn( s, "\"<>&" ); strbuf_add( sb, s, len ); s += len; switch( *s ) { case '"': strbuf_addstr( sb, """ ); break; case '<': strbuf_addstr( sb, "<" ); break; case '>': strbuf_addstr( sb, ">" ); break; case '&': strbuf_addstr( sb, "&" ); break; case 0: return; } s++; } } static int is_html_quoted( const char *str ) { int rc = 0, error = 0; PCRE2_SIZE offset = 0; const char pattern[] = "^(&[#A-Za-z0-9]*;)"; pcre2_match_data *match; pcre2_code *regexp = pcre2_compile( (PCRE2_SPTR)pattern, PCRE2_ZERO_TERMINATED, 0, &error, &offset, NULL ); if( regexp == NULL ) { return 0; /* PCRE compilation failed */ } match = pcre2_match_data_create_from_pattern( regexp, NULL ); rc = pcre2_match( regexp, (PCRE2_SPTR)str, (PCRE2_SIZE)strlen(str), 0, 0, match, NULL ); /* sizeof(match)/sizeof(match[0]) */ if( rc < 0 ) { /* not match */ pcre2_match_data_free( match ); pcre2_code_free( regexp ); return 0; } else { /* match */ pcre2_match_data_free( match ); pcre2_code_free( regexp ); return 1; } } void strbuf_addstr_html_quoted( struct strbuf *sb, const char *s ) { while( *s ) { size_t len = strcspn( s, "\"<>&" ); strbuf_add( sb, s, len ); s += len; switch( *s ) { case '"': strbuf_addstr( sb, """ ); break; case '<': strbuf_addstr( sb, "<" ); break; case '>': strbuf_addstr( sb, ">" ); break; case '&': if( !is_html_quoted( s ) ) strbuf_addstr( sb, "&" ); else strbuf_addch( sb, *s ); break; case 0: return; } s++; } } /* urlencode: */ int is_rfc3986_reserved_or_unreserved( char ch ) { if( is_rfc3986_unreserved(ch) ) return 1; switch( ch ) { case '!': case '*': case '\'': case '(': case ')': case ';': case ':': case '@': case '&': case '=': case '+': case '$': case ',': case '/': case '?': case '#': case '[': case ']': return 1; } return 0; } int is_rfc3986_unreserved( char ch ) { return isalnum(ch) || ch == '-' || ch == '_' || ch == '.' || ch == '~'; } static void strbuf_add_urlencode( struct strbuf *sb, const char *s, size_t len, char_predicate allow_unencoded_fn ) { strbuf_grow( sb, len ); while( len-- ) { char ch = *s++; if( allow_unencoded_fn(ch) ) strbuf_addch( sb, ch ); else strbuf_addf( sb, "%%%02x", (unsigned char)ch ); } } void strbuf_addstr_urlencode( struct strbuf *sb, const char *s, char_predicate allow_unencoded_fn ) { strbuf_add_urlencode( sb, s, strlen(s), allow_unencoded_fn ); } /* humanise: */ static void strbuf_humanise( struct strbuf *sb, off_t bytes, int humanise_rate ) { if( bytes > 1 << 30 ) { strbuf_addf( sb, humanise_rate == 0 ? /* TRANSLATORS: IEC 80000-13:2008 gibibyte */ _("%u.%2.2u GiB") : /* TRANSLATORS: IEC 80000-13:2008 gibibyte/second */ _("%u.%2.2u GiB/s"), (unsigned)(bytes >> 30), (unsigned)(bytes & ((1 << 30) - 1)) / 10737419 ); } else if( bytes > 1 << 20 ) { unsigned x = bytes + 5243; /* for rounding */ strbuf_addf( sb, humanise_rate == 0 ? /* TRANSLATORS: IEC 80000-13:2008 mebibyte */ _("%u.%2.2u MiB") : /* TRANSLATORS: IEC 80000-13:2008 mebibyte/second */ _("%u.%2.2u MiB/s"), x >> 20, ((x & ((1 << 20) - 1)) * 100) >> 20 ); } else if( bytes > 1 << 10 ) { unsigned x = bytes + 5; /* for rounding */ strbuf_addf( sb, humanise_rate == 0 ? /* TRANSLATORS: IEC 80000-13:2008 kibibyte */ _("%u.%2.2u KiB") : /* TRANSLATORS: IEC 80000-13:2008 kibibyte/second */ _("%u.%2.2u KiB/s"), x >> 10, ((x & ((1 << 10) - 1)) * 100) >> 10 ); } else { strbuf_addf( sb, humanise_rate == 0 ? /* TRANSLATORS: IEC 80000-13:2008 byte */ Q_("%u byte", "%u bytes", (unsigned)bytes) : /* TRANSLATORS: IEC 80000-13:2008 byte/second */ Q_("%u byte/s", "%u bytes/s", (unsigned)bytes), (unsigned)bytes ); } } void strbuf_humanise_bytes( struct strbuf *sb, off_t bytes ) { strbuf_humanise( sb, bytes, 0 ); } void strbuf_humanise_rate( struct strbuf *sb, off_t bytes ) { strbuf_humanise( sb, bytes, 1 ); } int is_directory( const char *path ) { struct stat st; return ( !stat( path, &st ) && S_ISDIR(st.st_mode) ); } void strbuf_selfdir( struct strbuf *sb ) { char path[PATH_MAX]; ssize_t len; bzero( (void *)path, PATH_MAX ); len = readlink( "/proc/self/exe", &path[0], (size_t)PATH_MAX ); if( len > 0 && len < PATH_MAX ) { strbuf_addstr( sb, (const char *)dirname( (char *)&path[0] ) ); } else sb->fatal( "cannot get selfdir" ); } void strbuf_relpath( struct strbuf *sb, const char *path ) { struct strbuf self = STRBUF_INIT; char p[PATH_MAX]; bzero( (void *)p, PATH_MAX ); if( realpath( path, (char *)&p[0] ) == NULL ) { sb->fatal( "cannot get relative path of '%s'", path ); } strbuf_init( &self, sb->fatal, 0 ); strbuf_selfdir( &self ); strbuf_addstr( sb, (const char *)&p[self.len] ); strbuf_release( &self ); } void strbuf_abspath( struct strbuf *sb, const char *path ) { char p[PATH_MAX]; if( realpath( path, (char *)&p[0] ) == NULL ) { sb->fatal( "cannot get absolute path of '%s'", path ); } strbuf_addstr( sb, (char *)&p[0] ); }