@@ -6,9 +6,9 @@ This guide explains how to get started with `protocol-url` for parsing, manipula
66
77Add the gem to your project:
88
9- ~~~ bash
9+ ``` shell
1010$ bundle add protocol-url
11- ~~~
11+ ```
1212
1313## Core Concepts
1414
@@ -20,14 +20,12 @@ $ bundle add protocol-url
2020
2121Additionally, the {ruby Protocol::URL::Path} module provides low-level utilities for path manipulation including splitting, joining, simplifying, and expanding paths according to RFC 3986 rules.
2222
23- ## Basic Usage
24-
25- ### Parsing URLs
23+ ## Usage
2624
2725Parse complete URLs with scheme and authority:
2826
29- ~~~ ruby
30- require ' protocol/url'
27+ ``` ruby
28+ require " protocol/url"
3129
3230# Parse an absolute URL:
3331url = Protocol ::URL [" https://api.example.com:8080/v1/users?page=2#results" ]
@@ -36,11 +34,11 @@ url.authority # => "api.example.com:8080"
3634url.path # => "/v1/users"
3735url.query # => "page=2"
3836url.fragment # => "results"
39- ~~~
37+ ```
4038
4139Parse relative URLs and references:
4240
43- ~~~ ruby
41+ ``` ruby
4442# Parse a relative URL:
4543relative = Protocol ::URL [" /api/v1/users" ]
4644relative.path # => "/api/v1/users"
@@ -50,13 +48,13 @@ reference = Protocol::URL["/search?q=ruby#top"]
5048reference.path # => "/search"
5149reference.query # => "q=ruby"
5250reference.fragment # => "top"
53- ~~~
51+ ```
5452
5553### Constructing URLs
5654
5755Build URLs programmatically:
5856
59- ~~~ ruby
57+ ``` ruby
6058# Create an absolute URL:
6159url = Protocol ::URL ::Absolute .new (" https" , " example.com" , " /api/users" )
6260url.to_s # => "https://example.com/api/users"
6866# Create a reference with components:
6967reference = Protocol ::URL ::Reference .new (" /api/search" , " q=ruby&limit=10" , " results" )
7068reference.to_s # => "/api/search?q=ruby&limit=10#results"
71- ~~~
69+ ```
7270
7371### Combining URLs
7472
7573URLs can be combined following RFC 3986 resolution rules:
7674
77- ~~~ ruby
75+ ``` ruby
7876# Combine absolute URL with relative path:
7977base = Protocol ::URL [" https://example.com/docs/guide/" ]
8078relative = Protocol ::URL ::Relative .new (" ../api/reference.html" )
@@ -86,15 +84,15 @@ result.to_s # => "https://example.com/docs/api/reference.html"
8684absolute_path = Protocol ::URL ::Relative .new (" /completely/different/path" )
8785result = base + absolute_path
8886result.to_s # => "https://example.com/completely/different/path"
89- ~~~
87+ ```
9088
9189## Path Manipulation
9290
9391The {ruby Protocol::URL::Path} module provides powerful utilities for working with URL paths:
9492
9593### Splitting and Joining Paths
9694
97- ~~~ ruby
95+ ``` ruby
9896# Split paths into components:
9997Protocol ::URL ::Path .split(" /a/b/c" ) # => ["", "a", "b", "c"]
10098Protocol ::URL ::Path .split(" a/b/c" ) # => ["a", "b", "c"]
@@ -103,13 +101,13 @@ Protocol::URL::Path.split("a/b/c/") # => ["a", "b", "c", ""]
103101# Join components back into paths:
104102Protocol ::URL ::Path .join([" " , " a" , " b" , " c" ]) # => "/a/b/c"
105103Protocol ::URL ::Path .join([" a" , " b" , " c" ]) # => "a/b/c"
106- ~~~
104+ ```
107105
108106### Simplifying Paths
109107
110108Remove dot segments (` . ` and ` .. ` ) from paths:
111109
112- ~~~ ruby
110+ ``` ruby
113111# Simplify a path:
114112components = [" a" , " b" , " .." , " c" , " ." , " d" ]
115113simplified = Protocol ::URL ::Path .simplify(components)
@@ -119,13 +117,13 @@ simplified = Protocol::URL::Path.simplify(components)
119117components = [" " , " a" , " b" , " .." , " .." , " c" ]
120118simplified = Protocol ::URL ::Path .simplify(components)
121119# => ["", "c"]
122- ~~~
120+ ```
123121
124122### Expanding Paths
125123
126124Merge two paths according to RFC 3986 rules:
127125
128- ~~~ ruby
126+ ``` ruby
129127# Expand a relative path against a base:
130128result = Protocol ::URL ::Path .expand(" /a/b/c" , " ../d" )
131129# => "/a/b/d"
@@ -137,42 +135,63 @@ result = Protocol::URL::Path.expand("/a/b/c/d", "../../e/f")
137135# Absolute relative paths replace the base:
138136result = Protocol ::URL ::Path .expand(" /a/b/c" , " /x/y/z" )
139137# => "/x/y/z"
140- ~~~
138+ ```
141139
142140The ` expand ` method has an optional ` pop ` parameter (default: ` true ` ) that controls whether the last component of the base path is removed before merging:
143141
144- ~~~ ruby
142+ ``` ruby
145143# With pop=true (default), behaves like URI resolution:
146144Protocol ::URL ::Path .expand(" /a/b/file.html" , " other.html" )
147145# => "/a/b/other.html"
148146
149147# With pop=false, treats base as a directory:
150148Protocol ::URL ::Path .expand(" /a/b/file.html" , " other.html" , false )
151149# => "/a/b/file.html/other.html"
152- ~~~
150+ ```
151+
152+ ### Converting to Local File System Paths
153+
154+ Convert URL paths to local file system paths safely:
155+
156+ ``` ruby
157+ # Convert URL path to local file system path:
158+ Protocol ::URL ::Path .to_local_path(" /documents/report.pdf" )
159+ # => "/documents/report.pdf"
160+
161+ # Handles percent-encoded characters:
162+ Protocol ::URL ::Path .to_local_path(" /files/My%20Document.txt" )
163+ # => "/files/My Document.txt"
164+
165+ # Security: Preserves percent-encoded path separators
166+ # This prevents directory traversal attacks:
167+ Protocol ::URL ::Path .to_local_path(" /folder/safe%2Fname/file.txt" )
168+ # => "/folder/safe%2Fname/file.txt"
169+ # %2F (/) and %5C (\) are NOT decoded, preventing them from creating
170+ # additional path components in the file system
171+ ```
153172
154173## Working with References
155174
156- {ruby Protocol::URL::Reference} extends relative URLs with query parameters and fragments. For detailed information on working with references, see the [ Working with References] ( working-with-references.md ) guide.
175+ {ruby Protocol::URL::Reference} extends relative URLs with query parameters and fragments. For detailed information on working with references, see the [ Working with References] ( ../ working-with-references/ ) guide.
157176
158177Quick example:
159178
160- ~~~ ruby
179+ ``` ruby
161180# Create a reference with query and fragment:
162181reference = Protocol ::URL ::Reference .new (" /api/users" , " status=active" , " results" )
163182reference.to_s # => "/api/users?status=active#results"
164183
165184# Update components immutably:
166185updated = reference.with(query: " status=inactive" )
167186updated.to_s # => "/api/users?status=inactive#results"
168- ~~~
187+ ```
169188
170189## URL Encoding
171190
172191The library handles URL encoding automatically for path components:
173192
174- ~~~ ruby
175- require ' protocol/url/encoding'
193+ ``` ruby
194+ require " protocol/url/encoding"
176195
177196# Escape path components (preserves slashes):
178197escaped = Protocol ::URL ::Encoding .escape_path(" /path/with spaces/file.html" )
@@ -185,13 +204,13 @@ escaped = Protocol::URL::Encoding.escape("hello world!")
185204# Unescape percent-encoded strings:
186205unescaped = Protocol ::URL ::Encoding .unescape(" hello%20world%21" )
187206# => "hello world!"
188- ~~~
207+ ```
189208
190209## Practical Examples
191210
192211### Building API URLs
193212
194- ~~~ ruby
213+ ``` ruby
195214# Build a base API URL:
196215base = Protocol ::URL ::Absolute .new (" https" , " api.example.com" , " /v2" )
197216
@@ -202,13 +221,13 @@ users_endpoint.to_s # => "https://api.example.com/v2/users"
202221# Add specific resource ID:
203222user_detail = users_endpoint + Protocol ::URL ::Relative .new (" 123" )
204223user_detail.to_s # => "https://api.example.com/v2/users/123"
205- ~~~
224+ ```
206225
207226### Resolving Relative Links
208227
209228When parsing HTML or processing links, you often need to resolve relative URLs:
210229
211- ~~~ ruby
230+ ``` ruby
212231# Page URL:
213232page = Protocol ::URL [" https://example.com/docs/guide/intro.html" ]
214233
@@ -221,20 +240,20 @@ resolved.to_s # => "https://example.com/docs/api/reference.html"
221240link = Protocol ::URL ::Relative .new (" getting-started.html" )
222241resolved = page + link
223242resolved.to_s # => "https://example.com/docs/guide/getting-started.html"
224- ~~~
243+ ```
225244
226245### URL Normalization
227246
228247Clean up URLs by simplifying paths:
229248
230- ~~~ ruby
249+ ``` ruby
231250# URL with redundant path segments:
232251messy = Protocol ::URL [" https://example.com/a/b/../c/./d" ]
233252
234253# The path is automatically simplified:
235254messy.path # => "/a/c/d"
236255messy.to_s # => "https://example.com/a/c/d"
237- ~~~
256+ ```
238257
239258## Best Practices
240259
@@ -263,36 +282,36 @@ When manipulating paths:
263282
264283The ` expand ` method pops the last path component by default to match RFC 3986 URI resolution:
265284
266- ~~~ ruby
285+ ``` ruby
267286# This might be surprising:
268287Protocol ::URL ::Path .expand(" /api/users" , " groups" )
269288# => "/api/groups" (not "/api/users/groups")
270289
271290# To prevent popping, use pop=false:
272291Protocol ::URL ::Path .expand(" /api/users" , " groups" , false )
273292# => "/api/users/groups"
274- ~~~
293+ ```
275294
276295### Empty Paths
277296
278297Empty relative paths return the base unchanged:
279298
280- ~~~ ruby
299+ ``` ruby
281300base = Protocol ::URL ::Reference .new (" /api/users" )
282301same = base.with(path: " " )
283302same.to_s # => "/api/users" (unchanged)
284- ~~~
303+ ```
285304
286305### Trailing Slashes
287306
288307Trailing slashes are preserved and have semantic meaning:
289308
290- ~~~ ruby
309+ ``` ruby
291310# Directory (trailing slash):
292311Protocol ::URL ::Path .expand(" /docs/" , " page.html" )
293312# => "/docs/page.html"
294313
295314# File (no trailing slash):
296315Protocol ::URL ::Path .expand(" /docs" , " page.html" )
297316# => "/page.html" (pops "docs")
298- ~~~
317+ ```
0 commit comments