Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/cairo/scripts/url_parser/.tool-versions
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
scarb 2.11.3
58 changes: 58 additions & 0 deletions examples/cairo/scripts/url_parser/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# URL Parser in Cairo

This module provides URL parsing functionality implemented in Cairo. It can parse URLs into their components including protocol, domain, path, query parameters, and fragment identifiers.

## Features

- Extract protocol (http, https, etc.)
- Extract domain and subdomains
- Extract path components
- Extract query parameters
- Extract fragment identifiers

## Usage

```cairo
use url_parser::URLParserTrait;

fn main() {
let url = 'https://example.com/path?param=value#section1';
let parsed_url = URLParserTrait::parse_url(url);

// Access URL components
let protocol = parsed_url.protocol; // 'https'
let domain = parsed_url.domain; // 'example.com'
let path = parsed_url.path; // '/path'
let query = parsed_url.query; // 'param=value'
let fragment = parsed_url.fragment; // 'section1'
}
```

## Building and Testing

1. Navigate to the url_parser directory:

```bash
cd examples/cairo/scripts/url_parser
```

2. Build the project:

```bash
scarb build
```

3. Run the tests:

```bash
scarb test
```

## Implementation Details

The URL parser implements the following traits:

- `URLParserTrait`: Main trait containing all parsing functions
- `URL`: Struct containing parsed URL components

Each component is extracted using dedicated functions that handle different URL formats and edge cases.
6 changes: 6 additions & 0 deletions examples/cairo/scripts/url_parser/Scarb.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Code generated by scarb DO NOT EDIT.
version = 1

[[package]]
name = "url_parser"
version = "0.1.0"
13 changes: 13 additions & 0 deletions examples/cairo/scripts/url_parser/Scarb.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "url_parser"
version = "0.1.0"
edition = "2023_01"

[dependencies]
starknet = ">=2.3.1"

[dev-dependencies]
cairo_test = "2.3.1"

[[target.starknet-contract]]
crate-type = ["lib"]
123 changes: 123 additions & 0 deletions examples/cairo/scripts/url_parser/src/lib.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
use core::array::ArrayTrait;
use core::option::OptionTrait;
use core::traits::Into;
use core::clone::Clone;

mod string_utils;

#[derive(Drop, Clone)]
pub struct URL {
protocol: felt252,
domain: felt252,
path: felt252,
query: felt252,
fragment: felt252,
}

pub trait URLParserTrait {
fn parse_url(url: felt252) -> URL;
fn extract_protocol(url: felt252) -> felt252;
fn extract_domain(url: felt252) -> felt252;
fn extract_path(url: felt252) -> felt252;
fn extract_query(url: felt252) -> felt252;
fn extract_fragment(url: felt252) -> felt252;
}

impl URLParser of URLParserTrait {
fn parse_url(url: felt252) -> URL {
URL {
protocol: Self::extract_protocol(url),
domain: Self::extract_domain(url),
path: Self::extract_path(url),
query: Self::extract_query(url),
fragment: Self::extract_fragment(url)
}
}

fn extract_protocol(url: felt252) -> felt252 {
let (protocol, remaining) = string_utils::split_string(url, '://');
if remaining == 0 {
return 'http';
}
protocol
}

fn extract_domain(url: felt252) -> felt252 {
let (_, after_protocol) = string_utils::split_string(url, '://');
if after_protocol == 0 {
// No protocol found, treat entire URL as domain until first slash
let (domain, _) = string_utils::split_string(url, '/');
return domain;
}

// Extract domain from the remaining URL
let (domain, _) = string_utils::split_string(after_protocol, '/');
if domain == 0 {
return after_protocol;
}

// Remove query string if present
let (domain_no_query, _) = string_utils::split_string(domain, '?');
if domain_no_query == 0 {
return domain;
}

// Remove fragment if present
let (final_domain, _) = string_utils::split_string(domain_no_query, '#');
if final_domain == 0 {
return domain_no_query;
}

final_domain
}

fn extract_path(url: felt252) -> felt252 {
// First split on protocol
let (_, after_protocol) = string_utils::split_string(url, '://');
let url_to_check = if after_protocol == 0 { url } else { after_protocol };

// Split by '/' first
let (_, after_slash) = string_utils::split_string(url_to_check, '/');
if after_slash == 0 {
return '/';
}

// Get just the path part (before ? or #)
let mut path = after_slash;

// Remove query part if exists
let (before_query, _) = string_utils::split_string(path, '?');
if before_query != 0 {
path = before_query;
}

// Remove fragment part if exists
let (before_fragment, _) = string_utils::split_string(path, '#');
if before_fragment != 0 {
path = before_fragment;
}

// Return with leading slash
if path == 0 {
return '/';
}

let path_with_slash = '/';
path_with_slash + path
}

fn extract_query(url: felt252) -> felt252 {
let (_, after_query) = string_utils::split_string(url, '?');
if after_query == 0 {
return 0;
}

let (query, _) = string_utils::split_string(after_query, '#');
query
}

fn extract_fragment(url: felt252) -> felt252 {
let (_, fragment) = string_utils::split_string(url, '#');
fragment
}
}
151 changes: 151 additions & 0 deletions examples/cairo/scripts/url_parser/src/string_utils.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
use core::array::ArrayTrait;
use core::option::OptionTrait;
use core::traits::{Into, TryInto};
use core::integer::{u256_from_felt252, U256TryIntoFelt252};

fn string_to_array(str: felt252) -> Array<felt252> {
let mut result = ArrayTrait::new();

if str == 0 {
return result;
}

// Convert the string to bytes in reverse order
let mut chars = ArrayTrait::new();
let mut remaining = str;

loop {
// Convert to u256 for division
let remaining_u256 = u256_from_felt252(remaining);
let div_u256 = u256_from_felt252(256);

// Perform division and get remainder
let quotient = remaining_u256 / div_u256;
let remainder = remaining_u256 - (quotient * div_u256);

// Convert back to felt252
let char: felt252 = remainder.try_into().unwrap();
chars.append(char);
remaining = quotient.try_into().unwrap();

if remaining == 0 {
break;
}
};

// Reverse the array to get correct order
let mut i = chars.len();
loop {
if i == 0 {
break;
}
i -= 1;
result.append(*chars.at(i));
};

result
}

fn find_substring(str: felt252, substr: felt252) -> u32 {
if str == 0 || substr == 0 {
return 0;
}

let str_arr = string_to_array(str);
let substr_arr = string_to_array(substr);

if substr_arr.len() == 0 || str_arr.len() < substr_arr.len() {
return 0;
}

let mut i: u32 = 0;
let max_i = str_arr.len() - substr_arr.len();

loop {
if i >= max_i.try_into().unwrap() {
break;
}

let mut found = true;
let mut j: u32 = 0;
let max_j = substr_arr.len().try_into().unwrap();

loop {
if j >= max_j {
break;
}

let str_char = *str_arr.at(i.try_into().unwrap() + j.try_into().unwrap());
let substr_char = *substr_arr.at(j.try_into().unwrap());

if str_char != substr_char {
found = false;
break;
}
j += 1;
};

if found {
return i;
}
i += 1;
};

0
}

fn split_string(str: felt252, delimiter: felt252) -> (felt252, felt252) {
if str == 0 || delimiter == 0 {
return (str, 0);
}

let str_arr = string_to_array(str);
let delimiter_arr = string_to_array(delimiter);

let pos = find_substring(str, delimiter);
if pos == 0 {
return (str, 0);
}

let mut first = ArrayTrait::new();
let mut second = ArrayTrait::new();

let mut i: u32 = 0;
let max_i = str_arr.len().try_into().unwrap();
let delimiter_len: u32 = delimiter_arr.len().try_into().unwrap();

loop {
if i >= max_i {
break;
}

if i < pos {
first.append(*str_arr.at(i.try_into().unwrap()));
} else if i >= pos + delimiter_len {
second.append(*str_arr.at(i.try_into().unwrap()));
}

i += 1;
};

(array_to_string(first), array_to_string(second))
}

fn array_to_string(arr: Array<felt252>) -> felt252 {
if arr.len() == 0 {
return 0;
}

let mut result: felt252 = 0;
let mut i = 0_usize;

loop {
if i >= arr.len() {
break;
}
result = result * 256 + *arr.at(i);
i += 1;
};

result
}
1 change: 1 addition & 0 deletions examples/cairo/scripts/url_parser/tests/lib.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mod test_url_parser;
Loading