Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

908 lines (750 sloc) 23.865 kB
/* -*- Mode: c; c-basic-offset: 2 -*-
*
* raptor_stringbuffer.c - Stringbuffer class for growing strings
*
* Copyright (C) 2003-2008, David Beckett http://www.dajobe.org/
* Copyright (C) 2003-2004, University of Bristol, UK http://www.bristol.ac.uk/
*
* This package is Free Software and part of Redland http://librdf.org/
*
* It is licensed under the following three licenses as alternatives:
* 1. GNU Lesser General Public License (LGPL) V2.1 or any newer version
* 2. GNU General Public License (GPL) V2 or any newer version
* 3. Apache License, V2.0 or any newer version
*
* You may not use this file except in compliance with at least one of
* the above three licenses.
*
* See LICENSE.html or LICENSE.txt at the top of this package for the
* complete terms and further detail along with the license texts for
* the licenses in COPYING.LIB, COPYING and LICENSE-2.0.txt respectively.
*
*
*
*/
#ifdef HAVE_CONFIG_H
#include <raptor_config.h>
#endif
#ifdef WIN32
#include <win32_raptor_config.h>
#endif
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <sys/types.h>
#ifdef HAVE_STDLIB_H
#include <stdlib.h> /* for abort() as used in errors */
#endif
/* Raptor includes */
#include "raptor2.h"
#include "raptor_internal.h"
#ifndef STANDALONE
struct raptor_stringbuffer_node_s
{
struct raptor_stringbuffer_node_s* next;
unsigned char *string;
size_t length;
};
typedef struct raptor_stringbuffer_node_s raptor_stringbuffer_node;
struct raptor_stringbuffer_s
{
/* Pointing to the first item in the list of nodes */
raptor_stringbuffer_node* head;
/* and the last */
raptor_stringbuffer_node* tail;
/* total length of the string */
size_t length;
/* frozen string if already calculated, or NULL if not present */
unsigned char *string;
};
/* prototypes for local functions */
static int raptor_stringbuffer_append_string_common(raptor_stringbuffer* stringbuffer, const unsigned char *string, size_t length, int do_copy);
/* functions implementing the stringbuffer api */
/**
* raptor_new_stringbuffer:
*
* Create a new stringbuffer.
*
* Return value: pointer to a raptor_stringbuffer object or NULL on failure
**/
raptor_stringbuffer*
raptor_new_stringbuffer(void)
{
raptor_stringbuffer* sb;
sb = RAPTOR_CALLOC(raptor_stringbuffer*, 1, sizeof(*sb));
return sb;
}
/**
* raptor_free_stringbuffer:
* @stringbuffer: stringbuffer object to destroy.
*
* Destroy a stringbuffer.
*
**/
void
raptor_free_stringbuffer(raptor_stringbuffer *stringbuffer)
{
if(!stringbuffer)
return;
if(stringbuffer->head) {
raptor_stringbuffer_node *node = stringbuffer->head;
while(node) {
raptor_stringbuffer_node *next = node->next;
if(node->string)
RAPTOR_FREE(char*, node->string);
RAPTOR_FREE(raptor_stringbuffer_node, node);
node = next;
}
}
if(stringbuffer->string)
RAPTOR_FREE(char*, stringbuffer->string);
RAPTOR_FREE(raptor_stringbuffer, stringbuffer);
}
/**
* raptor_stringbuffer_append_string_common:
* @stringbuffer: raptor stringbuffer
* @string: string
* @length: length of string
* @do_copy: non-0 to copy the string
*
* Add a string to the stringbuffer.
*
* INTERNAL
*
* If @string is NULL or @length is 0, no work is performed.
*
* If @do_copy is non-0, the passed-in string is copied into new memory
* otherwise the stringbuffer becomes the owner of the string pointer
* and will free it when the stringbuffer is destroyed.
*
* Return value: non-0 on failure
**/
static int
raptor_stringbuffer_append_string_common(raptor_stringbuffer* stringbuffer,
const unsigned char *string,
size_t length,
int do_copy)
{
raptor_stringbuffer_node *node;
if(!string || !length)
return 0;
node = RAPTOR_MALLOC(raptor_stringbuffer_node*, sizeof(*node));
if(!node) {
if(!do_copy)
RAPTOR_FREE(char*, string);
return 1;
}
if(do_copy) {
/* Note this copy does not include the \0 character - not needed */
node->string = RAPTOR_MALLOC(unsigned char*, length);
if(!node->string) {
RAPTOR_FREE(raptor_stringbuffer_node, node);
return 1;
}
memcpy(node->string, string, length);
} else
node->string = (unsigned char*)string;
node->length = length;
if(stringbuffer->tail) {
stringbuffer->tail->next = node;
stringbuffer->tail = node;
} else
stringbuffer->head = stringbuffer->tail = node;
node->next = NULL;
if(stringbuffer->string) {
RAPTOR_FREE(char*, stringbuffer->string);
stringbuffer->string = NULL;
}
stringbuffer->length += length;
return 0;
}
/**
* raptor_stringbuffer_append_counted_string:
* @stringbuffer: raptor stringbuffer
* @string: string
* @length: length of string
* @do_copy: non-0 to copy the string
*
* Add a counted string to the stringbuffer.
*
* If @string is NULL or @length is 0, no work is performed.
*
* If @do_copy is non-0, the passed-in string is copied into new memory
* otherwise the stringbuffer becomes the owner of the string pointer
* and will free it when the stringbuffer is destroyed.
*
* Return value: non-0 on failure
**/
int
raptor_stringbuffer_append_counted_string(raptor_stringbuffer* stringbuffer,
const unsigned char *string, size_t length,
int do_copy)
{
if(!string || !length)
return 0;
return raptor_stringbuffer_append_string_common(stringbuffer, string, length, do_copy);
}
/**
* raptor_stringbuffer_append_string:
* @stringbuffer: raptor stringbuffer
* @string: string
* @do_copy: non-0 to copy the string
*
* Add a string to the stringbuffer.
*
* If @string is NULL, no work is performed.
*
* If @do_copy is non-0, the passed-in string is copied into new memory
* otherwise the stringbuffer becomes the owner of the string pointer
* and will free it when the stringbuffer is destroyed.
*
* Return value: non-0 on failure
**/
int
raptor_stringbuffer_append_string(raptor_stringbuffer* stringbuffer,
const unsigned char *string, int do_copy)
{
if(!string)
return 0;
return raptor_stringbuffer_append_string_common(stringbuffer, string, strlen((const char*)string), do_copy);
}
/**
* raptor_stringbuffer_append_decimal:
* @stringbuffer: raptor stringbuffer
* @integer: integer to format as decimal and add
*
* Add an integer in decimal to the stringbuffer.
*
* Return value: non-0 on failure
**/
int
raptor_stringbuffer_append_decimal(raptor_stringbuffer* stringbuffer,
int integer)
{
/* enough for 64 bit signed integer
* INT64_MAX is 9223372036854775807 (19 digits) + 1 for sign
*/
unsigned char buf[20];
unsigned char *p;
int i = integer;
size_t length = 1;
if(integer < 0) {
length++;
i= -integer;
}
while(i /= 10)
length++;
p = buf+length-1;
i = integer;
if(i < 0)
i= -i;
do {
*p-- ='0'+(i %10);
i /= 10;
} while(i);
if(integer < 0)
*p= '-';
return raptor_stringbuffer_append_counted_string(stringbuffer, buf, length, 1);
}
/**
* raptor_stringbuffer_append_stringbuffer:
* @stringbuffer: #raptor_stringbuffer
* @append: #raptor_stringbuffer to append
*
* Add a stringbuffer to the stringbuffer.
*
* This function removes the content from the appending stringbuffer,
* making it empty and appends it to the supplied stringbuffer.
*
* Return value: non-0 on failure
**/
int
raptor_stringbuffer_append_stringbuffer(raptor_stringbuffer* stringbuffer,
raptor_stringbuffer* append)
{
raptor_stringbuffer_node *node = append->head;
if(!node)
return 0;
/* move all append nodes to stringbuffer */
if(stringbuffer->tail) {
stringbuffer->tail->next = node;
} else
stringbuffer->head = node;
stringbuffer->tail = append->tail;
/* adjust our length */
stringbuffer->length += append->length;
if(stringbuffer->string) {
RAPTOR_FREE(char*, stringbuffer->string);
stringbuffer->string = NULL;
}
/* zap append content */
append->head = append->tail = NULL;
append->length = 0;
if(append->string) {
RAPTOR_FREE(char*, append->string);
append->string = NULL;
}
return 0;
}
/**
* raptor_stringbuffer_prepend_string_common:
* @stringbuffer: raptor stringbuffer
* @string: string
* @length: length of string
* @do_copy: non-0 to copy the string
*
* Add a string to the start of a stringbuffer.
*
* INTERNAL
*
* If do_copy is non-0, the passed-in string is copied into new memory
* otherwise the stringbuffer becomes the owner of the string pointer
* and will free it when the stringbuffer is destroyed.
*
* Return value: non-0 on failure
**/
static int
raptor_stringbuffer_prepend_string_common(raptor_stringbuffer* stringbuffer,
const unsigned char *string, size_t length,
int do_copy)
{
raptor_stringbuffer_node *node;
node = RAPTOR_MALLOC(raptor_stringbuffer_node*, sizeof(*node));
if(!node)
return 1;
if(do_copy) {
/* Note this copy does not include the \0 character - not needed */
node->string = RAPTOR_MALLOC(unsigned char*, length);
if(!node->string) {
RAPTOR_FREE(raptor_stringbuffer_node, node);
return 1;
}
memcpy(node->string, string, length);
} else
node->string = (unsigned char*)string;
node->length = length;
node->next = stringbuffer->head;
if(stringbuffer->head)
stringbuffer->head = node;
else
stringbuffer->head = stringbuffer->tail = node;
if(stringbuffer->string) {
RAPTOR_FREE(char*, stringbuffer->string);
stringbuffer->string = NULL;
}
stringbuffer->length += length;
return 0;
}
/**
* raptor_stringbuffer_prepend_counted_string:
* @stringbuffer: raptor stringbuffer
* @string: string
* @length: length of string
* @do_copy: non-0 to copy the string
* If do_copy is non-0, the passed-in string is copied into new memory
* otherwise the stringbuffer becomes the owner of the string pointer
* and will free it when the stringbuffer is destroyed.
*
* Add a string to the start of the stringbuffer.
*
* Return value: non-0 on failure
**/
int
raptor_stringbuffer_prepend_counted_string(raptor_stringbuffer* stringbuffer,
const unsigned char *string, size_t length,
int do_copy)
{
return raptor_stringbuffer_prepend_string_common(stringbuffer, string, length, do_copy);
}
/**
* raptor_stringbuffer_prepend_string:
* @stringbuffer: raptor stringbuffer
* @string: string
* @do_copy: non-0 to copy the string
*
* Add a string to the start of the stringbuffer.
*
* If do_copy is non-0, the passed-in string is copied into new memory
* otherwise the stringbuffer becomes the owner of the string pointer
* and will free it when the stringbuffer is destroyed.
*
* Return value: non-0 on failure
**/
int
raptor_stringbuffer_prepend_string(raptor_stringbuffer* stringbuffer,
const unsigned char *string, int do_copy)
{
return raptor_stringbuffer_prepend_string_common(stringbuffer, string, strlen((const char*)string), do_copy);
}
/**
* raptor_stringbuffer_length:
* @stringbuffer: raptor stringbuffer
*
* Return the stringbuffer length.
*
* Return value: size of stringbuffer
**/
size_t
raptor_stringbuffer_length(raptor_stringbuffer* stringbuffer)
{
return stringbuffer->length;
}
/**
* raptor_stringbuffer_as_string:
* @stringbuffer: raptor stringbuffer
*
* Return the stringbuffer as a C string.
*
* Note: the return value is a to a shared string that the stringbuffer
* allocates and manages.
*
* Return value: NULL on failure or stringbuffer is empty, otherwise
* a pointer to a shared copy of the string.
**/
unsigned char *
raptor_stringbuffer_as_string(raptor_stringbuffer* stringbuffer)
{
raptor_stringbuffer_node *node;
unsigned char *p;
if(!stringbuffer->length)
return NULL;
if(stringbuffer->string)
return stringbuffer->string;
stringbuffer->string = RAPTOR_MALLOC(unsigned char*, stringbuffer->length + 1);
if(!stringbuffer->string)
return NULL;
node = stringbuffer->head;
p = stringbuffer->string;
while(node) {
memcpy(p, node->string, node->length);
p+= node->length;
node = node->next;
}
*p='\0';
return stringbuffer->string;
}
/**
* raptor_stringbuffer_copy_to_string:
* @stringbuffer: raptor stringbuffer
* @string: output string
* @length: size of output string
*
* Copy the stringbuffer into a string.
*
* Copies the underlying string to a pre-allocated buffer. The
* output string is always '\0' terminated.
*
* Return value: non-0 on failure such as stringbuffer is empty, buffer is too small
**/
int
raptor_stringbuffer_copy_to_string(raptor_stringbuffer* stringbuffer,
unsigned char *string, size_t length)
{
raptor_stringbuffer_node *node;
unsigned char *p;
if(!string || length < 1)
return 1;
if(!stringbuffer->length)
return 0;
p = string;
for(node = stringbuffer->head; node; node = node->next) {
if(node->length > length) {
p[-1]='\0';
return 1;
}
memcpy(p, node->string, node->length);
p+= node->length;
length-= node->length;
}
*p='\0';
return 0;
}
/**
* raptor_stringbuffer_append_hexadecimal:
* @stringbuffer: raptor stringbuffer
* @hex: integer to format
*
* Add an integer formatted in hexdecimal (base 16) to the stringbuffer.
*
* Return value: non-0 on failure
**/
int
raptor_stringbuffer_append_hexadecimal(raptor_stringbuffer* stringbuffer,
int hex)
{
unsigned char buf[2];
if(hex < 0 || hex > 0xF)
return 1;
*buf = (hex < 10) ? ('0' + hex) : ('A' + hex - 10);
buf[1] = '\0';
return raptor_stringbuffer_append_counted_string(stringbuffer, buf, 1, 1);
}
/* RFC3986 Unreserved */
#define IS_URI_UNRESERVED(c) ( (c >= 'A' && c <= 'F') || \
(c >= 'a' && c <= 'f') || \
(c >= '0' && c <= '9') || \
(c == '-' || c == '.' || c == '_' || c == '~') )
#define IS_URI_SAFE(c) (IS_URI_UNRESERVED(c))
/**
* raptor_stringbuffer_append_uri_escaped_counted_string:
* @sb: raptor stringbuffer
* @string: string
* @length: length of string
* @space_is_plus: if non-0, escape spaces as '+' otherwise percent-encode them
*
* Add a URI-escaped version of @string to the stringbuffer.
*
* If @string is NULL or @length is 0, no work is performed.
*
* Return value: non-0 on failure
**/
int
raptor_stringbuffer_append_uri_escaped_counted_string(raptor_stringbuffer* sb,
const char* string,
size_t length,
int space_is_plus)
{
unsigned int i;
unsigned char buf[2];
buf[1] = '\0';
if(!string || !length)
return 0;
for(i = 0; i < length; i++) {
int c = string[i];
if(!c)
break;
if(IS_URI_SAFE(c)) {
*buf = c;
if(raptor_stringbuffer_append_counted_string(sb, buf, 1, 1))
return 1;
} else if (c == ' ' && space_is_plus) {
*buf = '+';
if(raptor_stringbuffer_append_counted_string(sb, buf, 1, 1))
return 1;
} else {
*buf = '%';
if(raptor_stringbuffer_append_counted_string(sb, buf, 1, 1))
return 1;
if(raptor_stringbuffer_append_hexadecimal(sb, (c & 0xf0) >> 4))
return 1;
if(raptor_stringbuffer_append_hexadecimal(sb, (c & 0x0f)))
return 1;
}
}
return 0;
}
#endif
#ifdef STANDALONE
/* one more prototype */
int main(int argc, char *argv[]);
int
main(int argc, char *argv[])
{
const char *program = raptor_basename(argv[0]);
#define TEST_ITEMS_COUNT 9
const char *items[TEST_ITEMS_COUNT] = { "the", "quick" ,"brown", "fox", "jumps", "over", "the", "lazy", "dog" };
const char *items_string = "thequickbrownfoxjumpsoverthelazydog";
const size_t items_len = 35;
const char *test_integer_string = "abcd";
#define TEST_INTEGERS_COUNT 7
const int test_integers[TEST_INTEGERS_COUNT]={ 0, 1, -1, 11, 1234, 12345, -12345 };
const char *test_integer_results[TEST_INTEGERS_COUNT]={ "abcd0", "abcd1", "abcd-1", "abcd11", "abcd1234", "abcd12345", "abcd-12345" };
raptor_stringbuffer *sb;
unsigned char *str;
size_t len;
int i = 0;
raptor_stringbuffer *sb1, *sb2;
#define TEST_APPEND_COUNT 2
const char *test_append_results[TEST_APPEND_COUNT]={ "thebrownjumpsthedog", "quickfoxoverlazy" };
const char *test_append_results_total="thebrownjumpsthedogquickfoxoverlazy";
#define COPY_STRING_BUFFER_SIZE 100
unsigned char *copy_string;
#if defined(RAPTOR_DEBUG) && RAPTOR_DEBUG > 1
fprintf(stderr, "%s: Creating string buffer\n", program);
#endif
/* test appending */
sb = raptor_new_stringbuffer();
if(!sb) {
fprintf(stderr, "%s: Failed to create string buffer\n", program);
exit(1);
}
for(i = 0; i < TEST_ITEMS_COUNT; i++) {
int rc;
len = strlen(items[i]);
#if defined(RAPTOR_DEBUG) && RAPTOR_DEBUG > 1
fprintf(stderr, "%s: Adding string buffer item '%s'\n", program, items[i]);
#endif
rc = raptor_stringbuffer_append_counted_string(sb, (unsigned char*)items[i], len, 1);
if(rc) {
fprintf(stderr, "%s: Adding string buffer item %d '%s' failed, returning error %d\n",
program, i, items[i], rc);
exit(1);
}
}
len = raptor_stringbuffer_length(sb);
if(len != items_len) {
fprintf(stderr, "%s: string buffer len is %d, expected %d\n", program,
(int)len, (int)items_len);
exit(1);
}
str = raptor_stringbuffer_as_string(sb);
if(strcmp((const char*)str, items_string)) {
fprintf(stderr, "%s: string buffer contains '%s', expected '%s'\n",
program, str, items_string);
exit(1);
}
raptor_free_stringbuffer(sb);
/* test prepending */
#if defined(RAPTOR_DEBUG) && RAPTOR_DEBUG > 1
fprintf(stderr, "%s: Creating string buffer\n", program);
#endif
sb = raptor_new_stringbuffer();
if(!sb) {
fprintf(stderr, "%s: Failed to create string buffer\n", program);
exit(1);
}
for(i = TEST_ITEMS_COUNT-1; i>=0 ; i--) {
int rc;
len = strlen(items[i]);
#if defined(RAPTOR_DEBUG) && RAPTOR_DEBUG > 1
fprintf(stderr, "%s: Prepending string buffer item '%s'\n", program, items[i]);
#endif
rc = raptor_stringbuffer_prepend_counted_string(sb, (unsigned char*)items[i], len, 1);
if(rc) {
fprintf(stderr, "%s: Prepending string buffer item %d '%s' failed, returning error %d\n",
program, i, items[i], rc);
exit(1);
}
}
len = raptor_stringbuffer_length(sb);
if(len != items_len) {
fprintf(stderr, "%s: string buffer len is %d, expected %d\n", program,
(int)len, (int)items_len);
exit(1);
}
str = raptor_stringbuffer_as_string(sb);
if(strcmp((const char*)str, items_string)) {
fprintf(stderr, "%s: string buffer contains '%s', expected '%s'\n",
program, str, items_string);
exit(1);
}
/* test adding integers */
for(i = 0; i < TEST_INTEGERS_COUNT; i++) {
raptor_stringbuffer *isb = raptor_new_stringbuffer();
if(!isb) {
fprintf(stderr, "%s: Failed to create string buffer\n", program);
exit(1);
}
raptor_stringbuffer_append_string(isb,
(const unsigned char*)test_integer_string, 1);
#if defined(RAPTOR_DEBUG) && RAPTOR_DEBUG > 1
fprintf(stderr, "%s: Adding decimal integer %d to buffer\n", program, test_integers[i]);
#endif
raptor_stringbuffer_append_decimal(isb, test_integers[i]);
str = raptor_stringbuffer_as_string(isb);
if(strcmp((const char*)str, test_integer_results[i])) {
fprintf(stderr, "%s: string buffer contains '%s', expected '%s'\n",
program, str, test_integer_results[i]);
exit(1);
}
#if defined(RAPTOR_DEBUG) && RAPTOR_DEBUG > 1
fprintf(stderr, "%s: Freeing string buffer\n", program);
#endif
raptor_free_stringbuffer(isb);
}
#if defined(RAPTOR_DEBUG) && RAPTOR_DEBUG > 1
fprintf(stderr, "%s: Creating two stringbuffers to join\n", program);
#endif
sb1 = raptor_new_stringbuffer();
if(!sb1) {
fprintf(stderr, "%s: Failed to create string buffer\n", program);
exit(1);
}
sb2 = raptor_new_stringbuffer();
if(!sb2) {
fprintf(stderr, "%s: Failed to create string buffer\n", program);
exit(1);
}
for(i = 0; i < TEST_ITEMS_COUNT; i++) {
raptor_stringbuffer *sbx;
int rc;
len = strlen(items[i]);
sbx = (i % 2) ? sb2 : sb1;
rc = raptor_stringbuffer_append_counted_string(sbx, (unsigned char*)items[i], len, 1);
if(rc) {
fprintf(stderr, "%s: Adding string buffer item %d '%s' failed, returning error %d\n",
program, i, items[i], rc);
exit(1);
}
}
if(1) {
int rc;
rc = raptor_stringbuffer_append_counted_string(sb1, (unsigned char*)"X", 0, 1);
if(rc) {
fprintf(stderr, "%s: Adding 0-length counted string failed, returning error %d\n",
program, rc);
exit(1);
}
rc = raptor_stringbuffer_append_string(sb1, NULL, 1);
if(rc) {
fprintf(stderr, "%s: Adding NULL string failed, returning error %d\n",
program, rc);
exit(1);
}
}
str = raptor_stringbuffer_as_string(sb1);
if(strcmp((const char*)str, test_append_results[0])) {
fprintf(stderr, "%s: string buffer sb1 contains '%s', expected '%s'\n",
program, str, test_append_results[0]);
exit(1);
}
str = raptor_stringbuffer_as_string(sb2);
if(strcmp((const char*)str, test_append_results[1])) {
fprintf(stderr, "%s: string buffer sb2 contains '%s', expected '%s'\n",
program, str, test_append_results[1]);
exit(1);
}
#if defined(RAPTOR_DEBUG) && RAPTOR_DEBUG > 1
fprintf(stderr, "%s: Appended two stringbuffers\n", program);
#endif
if(raptor_stringbuffer_append_stringbuffer(sb1, sb2)) {
fprintf(stderr, "%s: Failed to append string buffer\n", program);
exit(1);
}
str = raptor_stringbuffer_as_string(sb1);
if(strcmp((const char*)str, test_append_results_total)) {
fprintf(stderr, "%s: appended string buffer contains '%s', expected '%s'\n",
program, str, test_append_results_total);
exit(1);
}
len = raptor_stringbuffer_length(sb2);
if(len) {
fprintf(stderr, "%s: appended string buffer is length %d, not empty'\n",
program, (int)len);
exit(1);
}
#if defined(RAPTOR_DEBUG) && RAPTOR_DEBUG > 1
fprintf(stderr, "%s: Copying string buffer to string\n", program);
#endif
copy_string = (unsigned char*)malloc(COPY_STRING_BUFFER_SIZE);
if(raptor_stringbuffer_copy_to_string(sb1, copy_string, COPY_STRING_BUFFER_SIZE)) {
fprintf(stderr, "%s: copying string buffer to string failed\n",
program);
exit(1);
}
if(strcmp((const char*)copy_string, test_append_results_total)) {
fprintf(stderr, "%s: copied string buffer contains '%s', expected '%s'\n",
program, copy_string, test_append_results_total);
exit(1);
}
free(copy_string);
#if defined(RAPTOR_DEBUG) && RAPTOR_DEBUG > 1
fprintf(stderr, "%s: Freeing string buffers\n", program);
#endif
raptor_free_stringbuffer(sb1);
raptor_free_stringbuffer(sb2);
raptor_free_stringbuffer(sb);
/* keep gcc -Wall happy */
return(0);
}
#endif
Jump to Line
Something went wrong with that request. Please try again.