escape_utils 0.2.4 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. data/.gitignore +2 -1
  2. data/.travis.yml +13 -0
  3. data/CHANGELOG.md +7 -0
  4. data/MIT-LICENSE +1 -1
  5. data/Rakefile +5 -18
  6. data/benchmark/html_escape.rb +9 -2
  7. data/benchmark/xml_escape.rb +29 -0
  8. data/escape_utils.gemspec +2 -3
  9. data/ext/escape_utils/buffer.c +181 -160
  10. data/ext/escape_utils/buffer.h +90 -68
  11. data/ext/escape_utils/escape_utils.c +77 -39
  12. data/ext/escape_utils/extconf.rb +1 -1
  13. data/ext/escape_utils/houdini.h +37 -8
  14. data/ext/escape_utils/houdini_href_e.c +115 -0
  15. data/ext/escape_utils/houdini_html_e.c +90 -0
  16. data/ext/escape_utils/houdini_html_u.c +122 -0
  17. data/ext/escape_utils/{houdini_js.c → houdini_js_e.c} +17 -75
  18. data/ext/escape_utils/houdini_js_u.c +60 -0
  19. data/ext/escape_utils/{uri_escape.h → houdini_uri_e.c} +68 -2
  20. data/ext/escape_utils/houdini_uri_u.c +65 -0
  21. data/ext/escape_utils/houdini_xml_e.c +136 -0
  22. data/lib/escape_utils/version.rb +1 -1
  23. data/lib/escape_utils/xml/builder.rb +8 -0
  24. data/test/helper.rb +14 -0
  25. data/test/html/escape_test.rb +61 -0
  26. data/test/html/unescape_test.rb +48 -0
  27. data/test/html_safety_test.rb +46 -0
  28. data/test/javascript/escape_test.rb +42 -0
  29. data/test/javascript/unescape_test.rb +46 -0
  30. data/test/query/escape_test.rb +50 -0
  31. data/test/query/unescape_test.rb +52 -0
  32. data/test/uri/escape_test.rb +50 -0
  33. data/test/uri/unescape_test.rb +55 -0
  34. data/test/url/escape_test.rb +58 -0
  35. data/test/url/unescape_test.rb +60 -0
  36. data/test/xml/escape_test.rb +67 -0
  37. metadata +136 -152
  38. data/.rspec +0 -2
  39. data/ext/escape_utils/houdini_html.c +0 -214
  40. data/ext/escape_utils/houdini_uri.c +0 -130
  41. data/spec/html/escape_spec.rb +0 -42
  42. data/spec/html/unescape_spec.rb +0 -37
  43. data/spec/html_safety_spec.rb +0 -48
  44. data/spec/javascript/escape_spec.rb +0 -34
  45. data/spec/javascript/unescape_spec.rb +0 -37
  46. data/spec/query/escape_spec.rb +0 -44
  47. data/spec/query/unescape_spec.rb +0 -46
  48. data/spec/rcov.opts +0 -3
  49. data/spec/spec_helper.rb +0 -5
  50. data/spec/uri/escape_spec.rb +0 -43
  51. data/spec/uri/unescape_spec.rb +0 -57
  52. data/spec/url/escape_spec.rb +0 -52
  53. data/spec/url/unescape_spec.rb +0 -57
data/.gitignore CHANGED
@@ -6,4 +6,5 @@ doc/*
6
6
  *.rbc
7
7
  tmp/
8
8
  Gemfile.lock
9
- vendor/*
9
+ vendor/*
10
+ bin/
data/.travis.yml ADDED
@@ -0,0 +1,13 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.8.7
4
+ - 1.9.2
5
+ - 1.9.3
6
+ - 2.0.0
7
+ - ree
8
+ - rbx-18mode
9
+ - rbx-19mode
10
+ matrix:
11
+ allow_failures:
12
+ - rvm: rbx-18mode
13
+ - rvm: rbx-19mode
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.0 (February 26th, 2013)
4
+ * add xml escaping support
5
+ * in Ruby 1.9 - escape_utils now requires the input string be UTF-8
6
+ * update upstream houdini to pull in some speed optimizations
7
+ * a couple of other perf tweaks
8
+ * switched to minitest
9
+
3
10
  ## 0.2.4 (September 7th, 2011)
4
11
  * swap out custom escaping routines for houdini - https://github.com/tanoku/houdini
5
12
  * add RSTRING_NOT_MODIFIED define for a Rubinius speedup
data/MIT-LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2010-2011 Brian Lopez - http://github.com/brianmario
1
+ Copyright (c) 2010-2013 Brian Lopez - http://github.com/brianmario
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/Rakefile CHANGED
@@ -1,23 +1,10 @@
1
- # rspec
2
- begin
3
- require 'rspec'
4
- require 'rspec/core/rake_task'
1
+ require 'rake/testtask'
5
2
 
6
- desc "Run all examples with RCov"
7
- RSpec::Core::RakeTask.new('spec:rcov') do |t|
8
- t.rcov = true
9
- end
10
- RSpec::Core::RakeTask.new('spec') do |t|
11
- t.verbose = true
12
- end
13
-
14
- task :default => :spec
15
- rescue LoadError
16
- puts "rspec, or one of its dependencies, is not available. Install it with: sudo gem install rspec"
3
+ Rake::TestTask.new do |t|
4
+ t.pattern = "test/**/*_test.rb"
17
5
  end
18
6
 
19
- # rake-compiler
20
- require 'rake' unless defined? Rake
7
+ task :default => :test
21
8
 
22
9
  gem 'rake-compiler', '>= 0.7.5'
23
10
  require "rake/extensiontask"
@@ -29,4 +16,4 @@ Rake::ExtensionTask.new('escape_utils') do |ext|
29
16
  ext.lib_dir = File.join 'lib', 'escape_utils'
30
17
  end
31
18
 
32
- Rake::Task[:spec].prerequisites << :compile
19
+ Rake::Task[:test].prerequisites << :compile
@@ -19,7 +19,7 @@ end
19
19
  times = 100
20
20
  url = "http://en.wikipedia.org/wiki/Line_of_succession_to_the_British_throne"
21
21
  html = `curl -s #{url}`
22
- html = html.force_encoding('binary') if html.respond_to?(:force_encoding)
22
+ html = html.force_encoding('utf-8') if html.respond_to?(:force_encoding)
23
23
  puts "Escaping #{html.bytesize} bytes of html #{times} times, from #{url}"
24
24
 
25
25
  Benchmark.bmbm do |x|
@@ -47,6 +47,13 @@ Benchmark.bmbm do |x|
47
47
  end
48
48
  end
49
49
 
50
+ x.report "String#gsub" do
51
+ html_escape = { '&' => '&amp;', '>' => '&gt;', '<' => '&lt;', '"' => '&quot;', "'" => '&#39;' }
52
+ times.times do
53
+ html.gsub(/[&"'><]/, html_escape)
54
+ end
55
+ end
56
+
50
57
  x.report "fast_xs_extra#fast_xs_html" do
51
58
  times.times do
52
59
  html.fast_xs_html
@@ -58,4 +65,4 @@ Benchmark.bmbm do |x|
58
65
  EscapeUtils.escape_html(html)
59
66
  end
60
67
  end
61
- end
68
+ end
@@ -0,0 +1,29 @@
1
+ # encoding: utf-8
2
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/..')
3
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
4
+
5
+ require 'rubygems'
6
+ require 'benchmark'
7
+
8
+ require 'builder'
9
+ require 'escape_utils'
10
+
11
+ times = 100
12
+ url = "http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml"
13
+ xml = `curl -s #{url}`
14
+ xml = xml.force_encoding('binary') if xml.respond_to?(:force_encoding)
15
+ puts "Escaping #{xml.bytesize} bytes of xml #{times} times, from #{url}"
16
+
17
+ Benchmark.bmbm do |x|
18
+ x.report "Builder::String.to_xs" do
19
+ times.times do
20
+ xml.to_xs
21
+ end
22
+ end
23
+
24
+ x.report "EscapeUtils.escape_xml" do
25
+ times.times do
26
+ EscapeUtils.escape_xml(xml)
27
+ end
28
+ end
29
+ end
data/escape_utils.gemspec CHANGED
@@ -4,20 +4,19 @@ Gem::Specification.new do |s|
4
4
  s.name = %q{escape_utils}
5
5
  s.version = EscapeUtils::VERSION
6
6
  s.authors = ["Brian Lopez"]
7
- s.date = Time.now.utc.strftime("%Y-%m-%d")
8
7
  s.email = %q{seniorlopez@gmail.com}
9
8
  s.extensions = ["ext/escape_utils/extconf.rb"]
10
9
  s.files = `git ls-files`.split("\n")
11
10
  s.homepage = %q{http://github.com/brianmario/escape_utils}
12
11
  s.rdoc_options = ["--charset=UTF-8"]
13
- s.require_paths = ["lib", "ext"]
12
+ s.require_paths = ["lib"]
14
13
  s.rubygems_version = %q{1.4.2}
15
14
  s.summary = %q{Faster string escaping routines for your web apps}
16
15
  s.test_files = `git ls-files spec`.split("\n")
17
16
 
18
17
  # tests
19
18
  s.add_development_dependency 'rake-compiler', ">= 0.7.5"
20
- s.add_development_dependency 'rspec', ">= 2.0.0"
19
+ s.add_development_dependency 'minitest'
21
20
  # benchmarks
22
21
  s.add_development_dependency 'rack'
23
22
  s.add_development_dependency 'haml'
@@ -1,228 +1,249 @@
1
1
  /*
2
- * Copyright (c) 2008, Natacha Porté
3
- * Copyright (c) 2011, Vicent Martí
2
+ * Copyright (C) the libgit2 contributors. All rights reserved.
4
3
  *
5
- * Permission to use, copy, modify, and distribute this software for any
6
- * purpose with or without fee is hereby granted, provided that the above
7
- * copyright notice and this permission notice appear in all copies.
8
- *
9
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
4
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
5
+ * a Linking Exception. For full terms see the included COPYING file.
16
6
  */
17
-
18
- #define BUFFER_MAX_ALLOC_SIZE (1024 * 1024 * 16) //16mb
19
-
20
- #include "buffer.h"
21
-
7
+ #include <stdarg.h>
8
+ #include <ctype.h>
9
+ #include <string.h>
10
+ #include <assert.h>
11
+ #include <string.h>
22
12
  #include <stdio.h>
23
13
  #include <stdlib.h>
24
- #include <string.h>
14
+ #include <sys/param.h>
25
15
 
26
- /* MSVC compat */
27
- #if defined(_MSC_VER)
28
- # define _buf_vsnprintf _vsnprintf
29
- #else
30
- # define _buf_vsnprintf vsnprintf
31
- #endif
16
+ #include "buffer.h"
32
17
 
33
- int
34
- bufprefix(const struct buf *buf, const char *prefix)
35
- {
36
- size_t i;
18
+ /* Used as default value for gh_buf->ptr so that people can always
19
+ * assume ptr is non-NULL and zero terminated even for new gh_bufs.
20
+ */
21
+ char gh_buf__initbuf[1];
22
+ char gh_buf__oom[1];
37
23
 
38
- for (i = 0; i < buf->size; ++i) {
39
- if (prefix[i] == 0)
40
- return 0;
24
+ #define ENSURE_SIZE(b, d) \
25
+ if ((d) > buf->asize && gh_buf_grow(b, (d)) < 0)\
26
+ return -1;
41
27
 
42
- if (buf->data[i] != prefix[i])
43
- return buf->data[i] - prefix[i];
44
- }
28
+ void gh_buf_init(gh_buf *buf, size_t initial_size)
29
+ {
30
+ buf->asize = 0;
31
+ buf->size = 0;
32
+ buf->ptr = gh_buf__initbuf;
45
33
 
46
- return 0;
34
+ if (initial_size)
35
+ gh_buf_grow(buf, initial_size);
47
36
  }
48
37
 
49
- /* bufgrow: increasing the allocated size to the given value */
50
- int
51
- bufgrow(struct buf *buf, size_t neosz)
38
+ int gh_buf_try_grow(gh_buf *buf, size_t target_size, bool mark_oom)
52
39
  {
53
- size_t neoasz;
54
- void *neodata;
55
- if (!buf || !buf->unit || neosz > BUFFER_MAX_ALLOC_SIZE)
56
- return BUF_ENOMEM;
40
+ char *new_ptr;
41
+ size_t new_size;
57
42
 
58
- if (buf->asize >= neosz)
59
- return BUF_OK;
43
+ if (buf->ptr == gh_buf__oom)
44
+ return -1;
60
45
 
61
- neoasz = buf->asize + buf->unit;
62
- while (neoasz < neosz)
63
- neoasz += buf->unit;
46
+ if (target_size <= buf->asize)
47
+ return 0;
64
48
 
65
- neodata = realloc(buf->data, neoasz);
66
- if (!neodata)
67
- return BUF_ENOMEM;
49
+ if (buf->asize == 0) {
50
+ new_size = target_size;
51
+ new_ptr = NULL;
52
+ } else {
53
+ new_size = buf->asize;
54
+ new_ptr = buf->ptr;
55
+ }
68
56
 
69
- buf->data = neodata;
70
- buf->asize = neoasz;
71
- return BUF_OK;
72
- }
57
+ /* grow the buffer size by 1.5, until it's big enough
58
+ * to fit our target size */
59
+ while (new_size < target_size)
60
+ new_size = (new_size << 1) - (new_size >> 1);
73
61
 
62
+ /* round allocation up to multiple of 8 */
63
+ new_size = (new_size + 7) & ~7;
74
64
 
75
- /* bufnew: allocation of a new buffer */
76
- struct buf *
77
- bufnew(size_t unit)
78
- {
79
- struct buf *ret;
80
- ret = malloc(sizeof (struct buf));
65
+ new_ptr = realloc(new_ptr, new_size);
81
66
 
82
- if (ret) {
83
- ret->data = 0;
84
- ret->size = ret->asize = 0;
85
- ret->unit = unit;
67
+ if (!new_ptr) {
68
+ if (mark_oom)
69
+ buf->ptr = gh_buf__oom;
70
+ return -1;
86
71
  }
87
- return ret;
72
+
73
+ buf->asize = new_size;
74
+ buf->ptr = new_ptr;
75
+
76
+ /* truncate the existing buffer size if necessary */
77
+ if (buf->size >= buf->asize)
78
+ buf->size = buf->asize - 1;
79
+ buf->ptr[buf->size] = '\0';
80
+
81
+ return 0;
88
82
  }
89
83
 
90
- /* bufnullterm: NULL-termination of the string array */
91
- const char *
92
- bufcstr(struct buf *buf)
84
+ void gh_buf_free(gh_buf *buf)
93
85
  {
94
- if (!buf || !buf->unit)
95
- return NULL;
86
+ if (!buf) return;
96
87
 
97
- if (buf->size < buf->asize && buf->data[buf->size] == 0)
98
- return (char *)buf->data;
88
+ if (buf->ptr != gh_buf__initbuf && buf->ptr != gh_buf__oom)
89
+ free(buf->ptr);
99
90
 
100
- if (buf->size + 1 <= buf->asize || bufgrow(buf, buf->size + 1) == 0) {
101
- buf->data[buf->size] = 0;
102
- return (char *)buf->data;
103
- }
104
-
105
- return NULL;
91
+ gh_buf_init(buf, 0);
106
92
  }
107
93
 
108
- /* bufprintf: formatted printing to a buffer */
109
- void
110
- bufprintf(struct buf *buf, const char *fmt, ...)
94
+ void gh_buf_clear(gh_buf *buf)
111
95
  {
112
- va_list ap;
113
- if (!buf || !buf->unit)
114
- return;
96
+ buf->size = 0;
97
+ if (buf->asize > 0)
98
+ buf->ptr[0] = '\0';
99
+ }
115
100
 
116
- va_start(ap, fmt);
117
- vbufprintf(buf, fmt, ap);
118
- va_end(ap);
101
+ int gh_buf_set(gh_buf *buf, const char *data, size_t len)
102
+ {
103
+ if (len == 0 || data == NULL) {
104
+ gh_buf_clear(buf);
105
+ } else {
106
+ if (data != buf->ptr) {
107
+ ENSURE_SIZE(buf, len + 1);
108
+ memmove(buf->ptr, data, len);
109
+ }
110
+ buf->size = len;
111
+ buf->ptr[buf->size] = '\0';
112
+ }
113
+ return 0;
119
114
  }
120
115
 
121
- /* bufput: appends raw data to a buffer */
122
- void
123
- bufput(struct buf *buf, const void *data, size_t len)
116
+ int gh_buf_sets(gh_buf *buf, const char *string)
124
117
  {
125
- if (!buf)
126
- return;
118
+ return gh_buf_set(buf, string, string ? strlen(string) : 0);
119
+ }
127
120
 
128
- if (buf->size + len > buf->asize && bufgrow(buf, buf->size + len) < 0)
129
- return;
121
+ int gh_buf_putc(gh_buf *buf, char c)
122
+ {
123
+ ENSURE_SIZE(buf, buf->size + 2);
124
+ buf->ptr[buf->size++] = c;
125
+ buf->ptr[buf->size] = '\0';
126
+ return 0;
127
+ }
130
128
 
131
- memcpy(buf->data + buf->size, data, len);
129
+ int gh_buf_put(gh_buf *buf, const void *data, size_t len)
130
+ {
131
+ ENSURE_SIZE(buf, buf->size + len + 1);
132
+ memmove(buf->ptr + buf->size, data, len);
132
133
  buf->size += len;
134
+ buf->ptr[buf->size] = '\0';
135
+ return 0;
133
136
  }
134
137
 
135
- /* bufputs: appends a NUL-terminated string to a buffer */
136
- void
137
- bufputs(struct buf *buf, const char *str)
138
+ int gh_buf_puts(gh_buf *buf, const char *string)
138
139
  {
139
- bufput(buf, str, strlen(str));
140
+ assert(string);
141
+ return gh_buf_put(buf, string, strlen(string));
140
142
  }
141
143
 
142
-
143
- /* bufputc: appends a single uint8_t to a buffer */
144
- void
145
- bufputc(struct buf *buf, int c)
144
+ int gh_buf_vprintf(gh_buf *buf, const char *format, va_list ap)
146
145
  {
147
- if (!buf)
148
- return;
146
+ int len;
147
+ const size_t expected_size = buf->size + (strlen(format) * 2);
149
148
 
150
- if (buf->size + 1 > buf->asize && bufgrow(buf, buf->size + 1) < 0)
151
- return;
149
+ ENSURE_SIZE(buf, expected_size);
152
150
 
153
- buf->data[buf->size] = c;
154
- buf->size += 1;
155
- }
151
+ while (1) {
152
+ va_list args;
153
+ va_copy(args, ap);
156
154
 
157
- /* bufrelease: decrease the reference count and free the buffer if needed */
158
- void
159
- bufrelease(struct buf *buf)
160
- {
161
- if (!buf)
162
- return;
155
+ len = vsnprintf(
156
+ buf->ptr + buf->size,
157
+ buf->asize - buf->size,
158
+ format, args
159
+ );
163
160
 
164
- free(buf->data);
165
- free(buf);
166
- }
161
+ if (len < 0) {
162
+ free(buf->ptr);
163
+ buf->ptr = gh_buf__oom;
164
+ return -1;
165
+ }
166
+
167
+ if ((size_t)len + 1 <= buf->asize - buf->size) {
168
+ buf->size += len;
169
+ break;
170
+ }
167
171
 
172
+ ENSURE_SIZE(buf, buf->size + len + 1);
173
+ }
168
174
 
169
- /* bufreset: frees internal data of the buffer */
170
- void
171
- bufreset(struct buf *buf)
175
+ return 0;
176
+ }
177
+
178
+ int gh_buf_printf(gh_buf *buf, const char *format, ...)
172
179
  {
173
- if (!buf)
174
- return;
180
+ int r;
181
+ va_list ap;
175
182
 
176
- free(buf->data);
177
- buf->data = NULL;
178
- buf->size = buf->asize = 0;
183
+ va_start(ap, format);
184
+ r = gh_buf_vprintf(buf, format, ap);
185
+ va_end(ap);
186
+
187
+ return r;
179
188
  }
180
189
 
181
- /* bufslurp: removes a given number of bytes from the head of the array */
182
- void
183
- bufslurp(struct buf *buf, size_t len)
190
+ void gh_buf_copy_cstr(char *data, size_t datasize, const gh_buf *buf)
184
191
  {
185
- if (!buf || !buf->unit || len <= 0)
186
- return;
192
+ size_t copylen;
193
+
194
+ assert(data && datasize && buf);
187
195
 
188
- if (len >= buf->size) {
189
- buf->size = 0;
196
+ data[0] = '\0';
197
+
198
+ if (buf->size == 0 || buf->asize <= 0)
190
199
  return;
191
- }
192
200
 
193
- buf->size -= len;
194
- memmove(buf->data, buf->data + len, buf->size);
201
+ copylen = buf->size;
202
+ if (copylen > datasize - 1)
203
+ copylen = datasize - 1;
204
+ memmove(data, buf->ptr, copylen);
205
+ data[copylen] = '\0';
195
206
  }
196
207
 
197
- /* vbufprintf: stdarg variant of formatted printing into a buffer */
198
- void
199
- vbufprintf(struct buf *buf, const char *fmt, va_list ap)
208
+ void gh_buf_swap(gh_buf *buf_a, gh_buf *buf_b)
200
209
  {
201
- int n;
210
+ gh_buf t = *buf_a;
211
+ *buf_a = *buf_b;
212
+ *buf_b = t;
213
+ }
202
214
 
203
- if (buf == 0 || (buf->size >= buf->asize && bufgrow(buf, buf->size + 1)) < 0)
204
- return;
215
+ char *gh_buf_detach(gh_buf *buf)
216
+ {
217
+ char *data = buf->ptr;
205
218
 
206
- n = _buf_vsnprintf((char *)buf->data + buf->size, buf->asize - buf->size, fmt, ap);
219
+ if (buf->asize == 0 || buf->ptr == gh_buf__oom)
220
+ return NULL;
207
221
 
208
- if (n < 0) {
209
- #ifdef _MSC_VER
210
- n = _vscprintf(fmt, ap);
211
- #else
212
- return;
213
- #endif
214
- }
222
+ gh_buf_init(buf, 0);
215
223
 
216
- if ((size_t)n >= buf->asize - buf->size) {
217
- if (bufgrow(buf, buf->size + n + 1) < 0)
218
- return;
224
+ return data;
225
+ }
219
226
 
220
- n = _buf_vsnprintf((char *)buf->data + buf->size, buf->asize - buf->size, fmt, ap);
227
+ void gh_buf_attach(gh_buf *buf, char *ptr, size_t asize)
228
+ {
229
+ gh_buf_free(buf);
230
+
231
+ if (ptr) {
232
+ buf->ptr = ptr;
233
+ buf->size = strlen(ptr);
234
+ if (asize)
235
+ buf->asize = (asize < buf->size) ? buf->size + 1 : asize;
236
+ else /* pass 0 to fall back on strlen + 1 */
237
+ buf->asize = buf->size + 1;
238
+ } else {
239
+ gh_buf_grow(buf, asize);
221
240
  }
241
+ }
222
242
 
223
- if (n < 0)
224
- return;
225
-
226
- buf->size += n;
243
+ int gh_buf_cmp(const gh_buf *a, const gh_buf *b)
244
+ {
245
+ int result = memcmp(a->ptr, b->ptr, MIN(a->size, b->size));
246
+ return (result != 0) ? result :
247
+ (a->size < b->size) ? -1 : (a->size > b->size) ? 1 : 0;
227
248
  }
228
249