Skip to content

Commit df8cb45

Browse files
committed
IronDrop: Finalize after search and memory metrics
1 parent 0723fbf commit df8cb45

37 files changed

Lines changed: 634 additions & 638 deletions

Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ glob = "0.3.1"
99
log = "0.4.20"
1010
env_logger = "0.11.3"
1111
base64 = "0.22.1"
12-
chrono = { version = "0.4", features = ["serde"] }
1312

1413
[dev-dependencies]
1514
reqwest = { version = "0.12.22", features = ["blocking", "json"] }

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
[![Rust CI](https://github.com/dev-harsh1998/IronDrop/actions/workflows/rust.yml/badge.svg)](https://github.com/dev-harsh1998/IronDrop/actions/workflows/rust.yml)
77
</div>
88

9-
A lightweight, high-performance file server written in Rust with **zero external dependencies**.
9+
A lightweight, high-performance file server written in Rust with **zero external dependencies**. Production-ready with comprehensive upload functionality, dual-mode search engine, and enterprise-grade security.
1010

1111
## 🚀 Features
1212

@@ -82,7 +82,7 @@ cargo fmt && cargo clippy
8282

8383
## 📋 Current Version
8484

85-
**v2.5** - Latest stable release with advanced search system and monitoring dashboard
85+
**v2.5.0** - Latest stable release with advanced search system, comprehensive file upload functionality, and monitoring dashboard
8686

8787
## 📖 Documentation
8888

doc/API_REFERENCE.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -378,13 +378,13 @@ GET /api/search?q=readme&path=/docs
378378

379379
### 5. Static Assets
380380

381-
#### `GET /_static/<asset-path>`
381+
#### `GET /_irondrop/static/<asset-path>`
382382
Serves template assets (CSS, JavaScript, images).
383383

384384
**Examples:**
385-
- `GET /_static/directory/styles.css`
386-
- `GET /_static/upload/script.js`
387-
- `GET /_static/error/styles.css`
385+
- `GET /_irondrop/static/directory/styles.css`
386+
- `GET /_irondrop/static/upload/script.js`
387+
- `GET /_irondrop/static/error/styles.css`
388388

389389
**Response:**
390390
```http
@@ -650,7 +650,7 @@ X-RateLimit-Reset: 1704110400
650650
<html>
651651
<head>
652652
<title>Error 404 - Not Found</title>
653-
<link rel="stylesheet" href="/_static/error/styles.css">
653+
<link rel="stylesheet" href="/_irondrop/static/error/styles.css">
654654
</head>
655655
<body>
656656
<div class="error-container">

doc/ARCHITECTURE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ Request → Cache Check → Hit: Return Cached Results
310310

311311
The native template engine provides:
312312
- **Variable Interpolation**: `{{VARIABLE}}` syntax with HTML escaping
313-
- **Static Asset Serving**: Organized CSS/JS delivery via `/_static/` routes
313+
- **Static Asset Serving**: Organized CSS/JS delivery via `/_irondrop/static/` routes
314314
- **Modular Templates**: Separated concerns (HTML structure, CSS styling, JS behavior)
315315
- **Caching**: In-memory template storage for performance
316316

doc/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ Native zero-dependency template engine: variables, conditionals, embedded assets
187187
### 🎨 **Modern Web Interface**
188188
- **Professional Blackish-Grey UI** – Clean, corporate-grade design with sophisticated glassmorphism effects
189189
- **Modular Template System** – Organized HTML/CSS/JS architecture with variable interpolation
190-
- **Static Asset Serving** – Efficient delivery of stylesheets and scripts via `/_static/` routes
190+
- **Static Asset Serving** – Efficient delivery of stylesheets and scripts via `/_irondrop/static/` routes
191191
- **Responsive Design** – Mobile-friendly interface with adaptive layouts
192192

193193
### 🔐 **Advanced Security & Monitoring**
@@ -823,7 +823,7 @@ Don't know where to start? Here are some **beginner-friendly test contributions:
823823
- **Path Traversal Prevention**: All paths are canonicalized and validated against the served directory
824824
- **Extension Filtering**: Configurable glob patterns restrict downloadable file types
825825
- **Basic Authentication**: Optional username/password protection with proper challenge responses
826-
- **Static Asset Protection**: Template files served only through controlled `/_static/` routes
826+
- **Static Asset Protection**: Template files served only through controlled `/_irondrop/static/` routes
827827

828828
### Advanced Protection
829829
- **Rate Limiting**: DoS protection with configurable requests per minute (default: 120)
@@ -889,7 +889,7 @@ templates/error/ # Error page templates
889889
```
890890

891891
### Static Asset Delivery
892-
- **Optimized Serving**: CSS/JS files delivered via `/_static/` routes with proper caching headers
892+
- **Optimized Serving**: CSS/JS files delivered via `/_irondrop/static/` routes with proper caching headers
893893
- **MIME Detection**: Accurate Content-Type headers for all static assets
894894
- **Security**: Path traversal protection prevents access outside template directories
895895
- **Performance**: Efficient file streaming with conditional request support

doc/TEMPLATE_SYSTEM.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ The system emphasizes simplicity (no runtime parsing of template files from disk
2626
```
2727
Request ─┬──────────────▶ Route Layer (http.rs)
2828
│ │
29-
│ (HTML Page Route) │ (Static Asset Route /_static/...)
29+
│ (HTML Page Route) │ (Static Asset Route /_irondrop/static/...)
3030
▼ ▼
3131
TemplateEngine get_static_asset()
3232
│ │
@@ -132,13 +132,13 @@ Served through controlled paths (example mapping):
132132

133133
| Request Path | Engine Key | MIME |
134134
|--------------|-----------|------|
135-
| `/_static/common/base.css` | `common/base.css` | `text/css` |
136-
| `/_static/directory/styles.css` | `directory/styles.css` | `text/css` |
137-
| `/_static/directory/script.js` | `directory/script.js` | `application/javascript` |
138-
| `/_static/error/styles.css` | `error/styles.css` | `text/css` |
139-
| `/_static/error/script.js` | `error/script.js` | `application/javascript` |
140-
| `/_static/upload/styles.css` | `upload/styles.css` | `text/css` |
141-
| `/_static/upload/script.js` | `upload/script.js` | `application/javascript` |
135+
| `/_irondrop/static/common/base.css` | `common/base.css` | `text/css` |
136+
| `/_irondrop/static/directory/styles.css` | `directory/styles.css` | `text/css` |
137+
| `/_irondrop/static/directory/script.js` | `directory/script.js` | `application/javascript` |
138+
| `/_irondrop/static/error/styles.css` | `error/styles.css` | `text/css` |
139+
| `/_irondrop/static/error/script.js` | `error/script.js` | `application/javascript` |
140+
| `/_irondrop/static/upload/styles.css` | `upload/styles.css` | `text/css` |
141+
| `/_irondrop/static/upload/script.js` | `upload/script.js` | `application/javascript` |
142142

143143
Favicon assets are similarly handled (e.g. `/favicon.ico`).
144144

doc/UPLOAD_INTEGRATION.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ The upload UI system consists of core components plus a shared design system:
2323
- **Responsive Design**: Works on desktop, tablet, and mobile devices
2424

2525
### 🎨 Visual Design
26-
- **Shared Design System**: Inherits global tokens & components via `/_static/common/base.css`
26+
- **Shared Design System**: Inherits global tokens & components via `/_irondrop/static/common/base.css`
2727
- **Dark Theme Integration**: Professional blackish-grey palette (#0a0a0a → #ffffff)
2828
- **Glass Effects**: Backdrop blur & translucent surfaces
2929
- **Smooth Animations**: CSS transitions (no JS dependency)
@@ -57,9 +57,9 @@ pub fn get_upload_form(&self) -> Result<String, AppError>
5757
### Static Asset Serving
5858

5959
Upload assets are served via the static asset system:
60-
- `/_static/common/base.css` (shared foundation)
61-
- `/_static/upload/styles.css`
62-
- `/_static/upload/script.js`
60+
- `/_irondrop/static/common/base.css` (shared foundation)
61+
- `/_irondrop/static/upload/styles.css`
62+
- `/_irondrop/static/upload/script.js`
6363

6464
## Usage Examples
6565

@@ -237,7 +237,7 @@ const allowedTypes = ['*']; // All types allowed
237237

238238
- **Complete Upload System**: Production-ready file upload handling
239239
- **Professional UI**: Modern blackish-grey theme with glassmorphism effects
240-
- **Template Integration**: All templates embedded and served via `/_static/` routes
240+
- **Template Integration**: All templates embedded and served via `/_irondrop/static/` routes
241241
- **Security Integration**: Upload validation respects CLI security configurations
242242
- **Multi-file Support**: Concurrent upload handling with progress tracking
243243
- **Error Handling**: Comprehensive client and server-side error management

src/config/mod.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -334,13 +334,13 @@ mod tests {
334334
assert_eq!(config.threads, 8);
335335
assert_eq!(config.chunk_size, 1024);
336336
assert_eq!(config.directory, temp_dir.path());
337-
assert_eq!(config.enable_upload, false);
337+
assert!(!config.enable_upload);
338338
assert_eq!(config.max_upload_size, 10240 * 1024 * 1024);
339339
assert_eq!(config.username, None);
340340
assert_eq!(config.password, None);
341341
assert_eq!(config.allowed_extensions, vec!["*.zip", "*.txt"]);
342-
assert_eq!(config.verbose, false);
343-
assert_eq!(config.detailed_logging, false);
342+
assert!(!config.verbose);
343+
assert!(!config.detailed_logging);
344344
}
345345

346346
#[test]
@@ -383,13 +383,13 @@ detailed = false
383383
assert_eq!(config.port, 9000);
384384
assert_eq!(config.threads, 16);
385385
assert_eq!(config.chunk_size, 2048);
386-
assert_eq!(config.enable_upload, true);
386+
assert!(config.enable_upload);
387387
assert_eq!(config.max_upload_size, 5 * 1024 * 1024 * 1024);
388388
assert_eq!(config.username, Some("testuser".to_string()));
389389
assert_eq!(config.password, Some("testpass".to_string()));
390390
assert_eq!(config.allowed_extensions, vec!["*.pdf", "*.doc"]);
391-
assert_eq!(config.verbose, true);
392-
assert_eq!(config.detailed_logging, false);
391+
assert!(config.verbose);
392+
assert!(!config.detailed_logging);
393393
}
394394

395395
#[test]
@@ -417,7 +417,7 @@ threads = 16
417417
// CLI should override INI
418418
assert_eq!(config.listen, "192.168.1.1");
419419
assert_eq!(config.port, 7777);
420-
assert_eq!(config.verbose, true);
420+
assert!(config.verbose);
421421

422422
// INI should provide non-overridden values
423423
assert_eq!(config.threads, 16);
@@ -455,7 +455,7 @@ max_upload_size = 2GB
455455

456456
let config = Config::load(&cli).unwrap();
457457

458-
assert_eq!(config.enable_upload, true);
458+
assert!(config.enable_upload);
459459
assert_eq!(config.max_upload_size, 2 * 1024 * 1024 * 1024);
460460
}
461461

src/handlers.rs

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@ use std::sync::Arc;
66

77
use crate::error::AppError;
88
use crate::http::{Request, Response, ResponseBody};
9+
use crate::search::{perform_search, SearchParams, SearchResult};
910
use crate::upload::UploadHandler;
1011
use crate::utils::parse_query_params;
12+
use log::debug;
13+
use std::time::Instant;
1114

1215
/// Register all internal routes under /_irondrop/.
1316
pub fn register_internal_routes(
@@ -28,6 +31,22 @@ pub fn register_internal_routes(
2831
Box::new(|_| Ok(create_health_check_response())),
2932
);
3033

34+
// Compatibility routes for legacy endpoints
35+
router.register_exact(
36+
"GET",
37+
"/_health",
38+
Box::new(|_| Ok(create_health_check_response())),
39+
);
40+
41+
// Legacy monitor endpoint compatibility
42+
if let Some(stats_arc) = stats.clone() {
43+
router.register_exact(
44+
"GET",
45+
"/monitor",
46+
Box::new(move |req: &Request| handle_monitor_request(req, Some(stats_arc.as_ref()))),
47+
);
48+
}
49+
3150
// Static assets (new namespace)
3251
router.register_prefix(
3352
"GET",
@@ -88,6 +107,15 @@ pub fn register_internal_routes(
88107
Box::new(move |req: &Request| handle_monitor_request(req, Some(stats_arc.as_ref()))),
89108
);
90109
}
110+
111+
// Search endpoint
112+
if let Some(base_arc) = base_dir {
113+
router.register_exact(
114+
"GET",
115+
"/_irondrop/search",
116+
Box::new(move |req: &Request| handle_search_api_request(req, &base_arc)),
117+
);
118+
}
91119
}
92120

93121
pub fn create_health_check_response() -> Response {
@@ -499,3 +527,123 @@ fn normalize_path(path: &std::path::Path) -> Result<std::path::PathBuf, AppError
499527
}
500528
Ok(components.iter().collect())
501529
}
530+
531+
/// URL decode function for parsing query parameters
532+
fn url_decode(s: &str) -> String {
533+
let mut result = String::with_capacity(s.len());
534+
let mut chars = s.chars();
535+
while let Some(ch) = chars.next() {
536+
if ch == '%' {
537+
let hex: String = chars.by_ref().take(2).collect();
538+
if let Ok(byte) = u8::from_str_radix(&hex, 16) {
539+
result.push(byte as char);
540+
} else {
541+
result.push(ch);
542+
}
543+
} else if ch == '+' {
544+
result.push(' ');
545+
} else {
546+
result.push(ch);
547+
}
548+
}
549+
result
550+
}
551+
552+
/// Handle search API requests with optimizations
553+
pub fn handle_search_api_request(
554+
request: &Request,
555+
base_dir: &Arc<std::path::PathBuf>,
556+
) -> Result<Response, AppError> {
557+
let start_time = Instant::now();
558+
559+
// Parse query parameters manually
560+
let query_params: HashMap<String, String> =
561+
if let Some(query_string) = request.path.split('?').nth(1) {
562+
query_string
563+
.split('&')
564+
.filter_map(|param| {
565+
let mut parts = param.splitn(2, '=');
566+
match (parts.next(), parts.next()) {
567+
(Some(key), Some(value)) => Some((url_decode(key), url_decode(value))),
568+
_ => None,
569+
}
570+
})
571+
.collect()
572+
} else {
573+
HashMap::new()
574+
};
575+
576+
let search_query = query_params.get("q").ok_or(AppError::BadRequest)?;
577+
578+
// Validate query length for performance
579+
if search_query.len() < 2 {
580+
return Err(AppError::BadRequest);
581+
}
582+
if search_query.len() > 100 {
583+
return Err(AppError::BadRequest);
584+
}
585+
586+
let search_path = query_params.get("path").map_or("/", |v| v);
587+
let limit = query_params
588+
.get("limit")
589+
.and_then(|v| v.parse::<usize>().ok())
590+
.unwrap_or(50)
591+
.min(200); // Cap at 200 results
592+
let offset = query_params
593+
.get("offset")
594+
.and_then(|v| v.parse::<usize>().ok())
595+
.unwrap_or(0);
596+
597+
let params = SearchParams {
598+
query: search_query.clone(),
599+
path: search_path.to_string(),
600+
limit,
601+
offset,
602+
case_sensitive: false,
603+
};
604+
605+
// Perform optimized search with caching and indexing
606+
let mut results = perform_search(base_dir, &params)?;
607+
608+
// Sort by relevance score
609+
results.sort_by(|a, b| {
610+
b.score
611+
.partial_cmp(&a.score)
612+
.unwrap_or(std::cmp::Ordering::Equal)
613+
});
614+
615+
// Apply pagination
616+
let _total_count = results.len();
617+
let paginated_results: Vec<SearchResult> =
618+
results.into_iter().skip(offset).take(limit).collect();
619+
620+
let _elapsed_ms = start_time.elapsed().as_millis();
621+
622+
// Create simple JSON manually to avoid serde dependency
623+
let json_items: Vec<String> = paginated_results
624+
.iter()
625+
.map(|result| {
626+
format!(
627+
r#"{{"name":"{}","path":"{}","size":"{}","type":"{}"}}"#,
628+
result.name.replace('"', r#"\""#),
629+
result.path.replace('"', r#"\""#),
630+
result.size,
631+
result.file_type
632+
)
633+
})
634+
.collect();
635+
636+
let json_response = format!("[{}]", json_items.join(","));
637+
638+
Ok(Response {
639+
status_code: 200,
640+
status_text: "OK".to_string(),
641+
headers: {
642+
let mut map = HashMap::new();
643+
map.insert("Content-Type".to_string(), "application/json".to_string());
644+
map.insert("Access-Control-Allow-Origin".to_string(), "*".to_string());
645+
map
646+
},
647+
body: ResponseBody::Text(json_response),
648+
})
649+
}

0 commit comments

Comments
 (0)